mirror of https://github.com/tasks/tasks
Bundle and persist notifications
parent
805caeb434
commit
e57adab036
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 1,
|
||||||
|
"identityHash": "eab7679fcfaa5fd45ac7da7a4b205348",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "notification",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `task` INTEGER, `timestamp` INTEGER NOT NULL, `type` INTEGER NOT NULL)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "uid",
|
||||||
|
"columnName": "uid",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "taskId",
|
||||||
|
"columnName": "task",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "timestamp",
|
||||||
|
"columnName": "timestamp",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "type",
|
||||||
|
"columnName": "type",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"uid"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_notification_task",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"task"
|
||||||
|
],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX `index_notification_task` ON `${TABLE_NAME}` (`task`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"eab7679fcfaa5fd45ac7da7a4b205348\")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package org.tasks.db;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.Database;
|
||||||
|
import android.arch.persistence.room.RoomDatabase;
|
||||||
|
|
||||||
|
import org.tasks.notifications.Notification;
|
||||||
|
import org.tasks.notifications.NotificationDao;
|
||||||
|
|
||||||
|
@Database(entities = {Notification.class}, version = 1)
|
||||||
|
public abstract class AppDatabase extends RoomDatabase {
|
||||||
|
public abstract NotificationDao notificationDao();
|
||||||
|
}
|
||||||
@ -1,7 +1,11 @@
|
|||||||
package org.tasks.jobs;
|
package org.tasks.jobs;
|
||||||
|
|
||||||
|
import org.tasks.notifications.Notification;
|
||||||
|
|
||||||
public interface JobQueueEntry {
|
public interface JobQueueEntry {
|
||||||
long getId();
|
long getId();
|
||||||
|
|
||||||
long getTime();
|
long getTime();
|
||||||
|
|
||||||
|
Notification toNotification();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,34 @@
|
|||||||
|
package org.tasks.notifications;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.ColumnInfo;
|
||||||
|
import android.arch.persistence.room.Entity;
|
||||||
|
import android.arch.persistence.room.Index;
|
||||||
|
import android.arch.persistence.room.PrimaryKey;
|
||||||
|
|
||||||
|
@Entity(tableName = "notification",
|
||||||
|
indices = {@Index(value = "task", unique = true)})
|
||||||
|
public class Notification {
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
@ColumnInfo(name = "uid")
|
||||||
|
public int uid;
|
||||||
|
|
||||||
|
@ColumnInfo(name = "task")
|
||||||
|
public Long taskId;
|
||||||
|
|
||||||
|
@ColumnInfo(name = "timestamp")
|
||||||
|
public long timestamp;
|
||||||
|
|
||||||
|
@ColumnInfo(name = "type")
|
||||||
|
public int type;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Notification{" +
|
||||||
|
"uid=" + uid +
|
||||||
|
", taskId=" + taskId +
|
||||||
|
", timestamp=" + timestamp +
|
||||||
|
", type=" + type +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package org.tasks.notifications;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
import org.tasks.db.AppDatabase;
|
||||||
|
import org.tasks.injection.BroadcastComponent;
|
||||||
|
import org.tasks.injection.InjectingBroadcastReceiver;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class NotificationClearedReceiver extends InjectingBroadcastReceiver {
|
||||||
|
|
||||||
|
@Inject NotificationManager notificationManager;
|
||||||
|
@Inject AppDatabase appDatabase;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
super.onReceive(context, intent);
|
||||||
|
|
||||||
|
long notificationId = intent.getLongExtra(NotificationManager.EXTRA_NOTIFICATION_ID, -1L);
|
||||||
|
Timber.d("cleared %s", notificationId);
|
||||||
|
notificationManager.cancel(notificationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void inject(BroadcastComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.tasks.notifications;
|
||||||
|
|
||||||
|
import android.arch.persistence.room.Dao;
|
||||||
|
import android.arch.persistence.room.Insert;
|
||||||
|
import android.arch.persistence.room.OnConflictStrategy;
|
||||||
|
import android.arch.persistence.room.Query;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import io.reactivex.Single;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public interface NotificationDao {
|
||||||
|
@Query("SELECT * FROM notification")
|
||||||
|
List<Notification> getAll();
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
void insertAll(List<Notification> notifications);
|
||||||
|
|
||||||
|
@Query("SELECT COUNT(*) FROM notification")
|
||||||
|
int count();
|
||||||
|
|
||||||
|
@Query("DELETE FROM notification WHERE task = :taskId")
|
||||||
|
void delete(long taskId);
|
||||||
|
}
|
||||||
@ -1,49 +1,154 @@
|
|||||||
package org.tasks.notifications;
|
package org.tasks.notifications;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
|
||||||
import org.tasks.R;
|
import org.tasks.R;
|
||||||
|
import org.tasks.db.AppDatabase;
|
||||||
|
import org.tasks.injection.ApplicationScope;
|
||||||
import org.tasks.injection.ForApplication;
|
import org.tasks.injection.ForApplication;
|
||||||
import org.tasks.preferences.Preferences;
|
import org.tasks.preferences.Preferences;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import io.reactivex.Completable;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
|
||||||
|
import static com.google.common.collect.Lists.newArrayList;
|
||||||
|
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastNougat;
|
||||||
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastOreo;
|
import static com.todoroo.andlib.utility.AndroidUtilities.atLeastOreo;
|
||||||
|
|
||||||
|
@ApplicationScope
|
||||||
public class NotificationManager {
|
public class NotificationManager {
|
||||||
|
|
||||||
|
public static final String NOTIFICATION_CHANNEL_DEFAULT = "notifications";
|
||||||
|
public static final String NOTIFICATION_CHANNEL_TASKER = "notifications_tasker";
|
||||||
|
public static final String NOTIFICATION_CHANNEL_CALLS = "notifications_calls";
|
||||||
|
public static final String GROUP_KEY = "tasks";
|
||||||
|
private static final int SUMMARY_NOTIFICATION_ID = 0;
|
||||||
|
static final String EXTRA_NOTIFICATION_ID = "extra_notification_id";
|
||||||
|
|
||||||
private final android.app.NotificationManager notificationManager;
|
private final android.app.NotificationManager notificationManager;
|
||||||
|
private final AppDatabase appDatabase;
|
||||||
|
private final Context context;
|
||||||
private final Preferences preferences;
|
private final Preferences preferences;
|
||||||
public static final String DEFAULT_NOTIFICATION_CHANNEL = "notifications";
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public NotificationManager(@ForApplication Context context, Preferences preferences) {
|
public NotificationManager(@ForApplication Context context, Preferences preferences,
|
||||||
|
AppDatabase appDatabase) {
|
||||||
|
this.context = context;
|
||||||
this.preferences = preferences;
|
this.preferences = preferences;
|
||||||
notificationManager = (android.app.NotificationManager)
|
notificationManager = (android.app.NotificationManager)
|
||||||
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
this.appDatabase = appDatabase;
|
||||||
if (atLeastOreo()) {
|
if (atLeastOreo()) {
|
||||||
String channelName = context.getString(R.string.notifications);
|
notificationManager.createNotificationChannel(createNotificationChannel(NOTIFICATION_CHANNEL_DEFAULT, R.string.notifications));
|
||||||
NotificationChannel notificationChannel = new NotificationChannel(DEFAULT_NOTIFICATION_CHANNEL, channelName, android.app.NotificationManager.IMPORTANCE_HIGH);
|
notificationManager.createNotificationChannel(createNotificationChannel(NOTIFICATION_CHANNEL_CALLS, R.string.missed_calls));
|
||||||
notificationChannel.enableLights(true);
|
notificationManager.createNotificationChannel(createNotificationChannel(NOTIFICATION_CHANNEL_TASKER, R.string.tasker_locale));
|
||||||
notificationChannel.enableVibration(true);
|
|
||||||
notificationChannel.setBypassDnd(true);
|
|
||||||
notificationChannel.setShowBadge(true);
|
|
||||||
notificationChannel.setImportance(android.app.NotificationManager.IMPORTANCE_HIGH);
|
|
||||||
notificationChannel.setLightColor(preferences.getLEDColor());
|
|
||||||
notificationChannel.setVibrationPattern(preferences.getVibrationPattern());
|
|
||||||
notificationManager.createNotificationChannel(notificationChannel);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.O)
|
||||||
|
private NotificationChannel createNotificationChannel(String channelId, int nameResId) {
|
||||||
|
String channelName = context.getString(nameResId);
|
||||||
|
NotificationChannel notificationChannel = new NotificationChannel(channelId, channelName, android.app.NotificationManager.IMPORTANCE_HIGH);
|
||||||
|
notificationChannel.enableLights(true);
|
||||||
|
notificationChannel.enableVibration(true);
|
||||||
|
notificationChannel.setBypassDnd(true);
|
||||||
|
notificationChannel.setShowBadge(true);
|
||||||
|
notificationChannel.setImportance(android.app.NotificationManager.IMPORTANCE_HIGH);
|
||||||
|
return notificationChannel;
|
||||||
|
}
|
||||||
|
|
||||||
public void cancel(long id) {
|
public void cancel(long id) {
|
||||||
notificationManager.cancel((int) id);
|
notificationManager.cancel((int) id);
|
||||||
|
Completable.fromAction(() -> {
|
||||||
|
appDatabase.notificationDao().delete(id);
|
||||||
|
updateSummary(false, false, false);
|
||||||
|
})
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notify(int notificationId, Notification notification) {
|
public void notifyTasks(Map<org.tasks.notifications.Notification, Notification> notifications, boolean alert, boolean nonstop, boolean fiveTimes) {
|
||||||
|
appDatabase.notificationDao().insertAll(newArrayList(notifications.keySet()));
|
||||||
|
updateSummary(alert && notifications.size() > 1, nonstop, fiveTimes);
|
||||||
|
ArrayList<Map.Entry<org.tasks.notifications.Notification, Notification>> entries = newArrayList(notifications.entrySet());
|
||||||
|
|
||||||
|
int last = entries.size() - 1;
|
||||||
|
for (int i = 0; i < last; i++) {
|
||||||
|
Map.Entry<org.tasks.notifications.Notification, Notification> entry = entries.get(i);
|
||||||
|
notify(entry.getKey().taskId, entry.getValue(), false, false, false);
|
||||||
|
}
|
||||||
|
Map.Entry<org.tasks.notifications.Notification, Notification> entry = entries.get(last);
|
||||||
|
notify(entry.getKey().taskId, entry.getValue(), alert, nonstop, fiveTimes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notify(long notificationId, Notification notification, boolean alert, boolean nonstop, boolean fiveTimes) {
|
||||||
if (preferences.getBoolean(R.string.p_rmd_enabled, true)) {
|
if (preferences.getBoolean(R.string.p_rmd_enabled, true)) {
|
||||||
notificationManager.notify(notificationId, notification);
|
int ringTimes = 1;
|
||||||
|
if (preferences.getBoolean(R.string.p_rmd_persistent, true)) {
|
||||||
|
notification.flags |= Notification.FLAG_NO_CLEAR;
|
||||||
|
}
|
||||||
|
if (preferences.isLEDNotificationEnabled()) {
|
||||||
|
notification.defaults |= Notification.DEFAULT_LIGHTS;
|
||||||
|
}
|
||||||
|
if (alert) {
|
||||||
|
if (nonstop) {
|
||||||
|
notification.flags |= Notification.FLAG_INSISTENT;
|
||||||
|
ringTimes = 1;
|
||||||
|
} else if (fiveTimes) {
|
||||||
|
ringTimes = 5;
|
||||||
|
}
|
||||||
|
if (preferences.isVibrationEnabled()) {
|
||||||
|
notification.defaults |= Notification.DEFAULT_VIBRATE;
|
||||||
|
}
|
||||||
|
notification.sound = preferences.getRingtone();
|
||||||
|
notification.audioStreamType = Notification.STREAM_DEFAULT;
|
||||||
|
}
|
||||||
|
Intent deleteIntent = new Intent(context, NotificationClearedReceiver.class);
|
||||||
|
deleteIntent.putExtra(EXTRA_NOTIFICATION_ID, notificationId);
|
||||||
|
notification.deleteIntent = PendingIntent.getBroadcast(context, (int) notificationId, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
for (int i = 0 ; i < ringTimes ; i++) {
|
||||||
|
notificationManager.notify((int) notificationId, notification);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSummary(boolean notify, boolean nonStop, boolean fiveTimes) {
|
||||||
|
if (atLeastNougat()) {
|
||||||
|
if (appDatabase.notificationDao().count() == 0) {
|
||||||
|
notificationManager.cancel(SUMMARY_NOTIFICATION_ID);
|
||||||
|
} else {
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationManager.NOTIFICATION_CHANNEL_DEFAULT)
|
||||||
|
.setGroupSummary(true)
|
||||||
|
.setGroup(GROUP_KEY)
|
||||||
|
.setShowWhen(false)
|
||||||
|
.setSmallIcon(R.drawable.ic_done_all_white_24dp);
|
||||||
|
|
||||||
|
if (notify) {
|
||||||
|
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setSound(preferences.getRingtone());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
builder.setOnlyAlertOnce(true)
|
||||||
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(NotificationManager.SUMMARY_NOTIFICATION_ID, builder.build(), notify, nonStop, fiveTimes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,45 +0,0 @@
|
|||||||
package org.tasks.scheduling;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
|
|
||||||
import com.todoroo.andlib.sql.Criterion;
|
|
||||||
import com.todoroo.astrid.dao.TaskDao;
|
|
||||||
import com.todoroo.astrid.data.Task;
|
|
||||||
|
|
||||||
import org.tasks.injection.InjectingJobIntentService;
|
|
||||||
import org.tasks.injection.IntentServiceComponent;
|
|
||||||
import org.tasks.jobs.JobManager;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
import static java.lang.System.currentTimeMillis;
|
|
||||||
|
|
||||||
public class SchedulerIntentService extends InjectingJobIntentService {
|
|
||||||
|
|
||||||
@Inject TaskDao taskDao;
|
|
||||||
@Inject JobManager jobManager;
|
|
||||||
@Inject RefreshScheduler refreshScheduler;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onHandleWork(Intent intent) {
|
|
||||||
super.onHandleWork(intent);
|
|
||||||
|
|
||||||
Timber.d("onHandleIntent(%s)", intent);
|
|
||||||
|
|
||||||
jobManager.scheduleMidnightBackup();
|
|
||||||
jobManager.scheduleMidnightRefresh();
|
|
||||||
|
|
||||||
refreshScheduler.clear();
|
|
||||||
long now = currentTimeMillis();
|
|
||||||
taskDao.selectActive(
|
|
||||||
Criterion.or(Task.HIDE_UNTIL.gt(now), Task.DUE_DATE.gt(now)),
|
|
||||||
refreshScheduler::scheduleRefresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void inject(IntentServiceComponent component) {
|
|
||||||
component.inject(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 275 B |
Binary file not shown.
|
After Width: | Height: | Size: 300 B |
Binary file not shown.
|
After Width: | Height: | Size: 398 B |
Binary file not shown.
|
After Width: | Height: | Size: 213 B |
@ -1,6 +1,6 @@
|
|||||||
#Thu Aug 10 09:35:33 CDT 2017
|
#Wed Aug 30 13:47:01 CDT 2017
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-rc-1-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
|
||||||
|
|||||||
Loading…
Reference in New Issue