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;
|
||||
|
||||
import org.tasks.notifications.Notification;
|
||||
|
||||
public interface JobQueueEntry {
|
||||
long getId();
|
||||
|
||||
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;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.PendingIntent;
|
||||
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.db.AppDatabase;
|
||||
import org.tasks.injection.ApplicationScope;
|
||||
import org.tasks.injection.ForApplication;
|
||||
import org.tasks.preferences.Preferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
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;
|
||||
|
||||
@ApplicationScope
|
||||
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 AppDatabase appDatabase;
|
||||
private final Context context;
|
||||
private final Preferences preferences;
|
||||
public static final String DEFAULT_NOTIFICATION_CHANNEL = "notifications";
|
||||
|
||||
@Inject
|
||||
public NotificationManager(@ForApplication Context context, Preferences preferences) {
|
||||
public NotificationManager(@ForApplication Context context, Preferences preferences,
|
||||
AppDatabase appDatabase) {
|
||||
this.context = context;
|
||||
this.preferences = preferences;
|
||||
notificationManager = (android.app.NotificationManager)
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
this.appDatabase = appDatabase;
|
||||
if (atLeastOreo()) {
|
||||
String channelName = context.getString(R.string.notifications);
|
||||
NotificationChannel notificationChannel = new NotificationChannel(DEFAULT_NOTIFICATION_CHANNEL, 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);
|
||||
notificationChannel.setLightColor(preferences.getLEDColor());
|
||||
notificationChannel.setVibrationPattern(preferences.getVibrationPattern());
|
||||
notificationManager.createNotificationChannel(notificationChannel);
|
||||
notificationManager.createNotificationChannel(createNotificationChannel(NOTIFICATION_CHANNEL_DEFAULT, R.string.notifications));
|
||||
notificationManager.createNotificationChannel(createNotificationChannel(NOTIFICATION_CHANNEL_CALLS, R.string.missed_calls));
|
||||
notificationManager.createNotificationChannel(createNotificationChannel(NOTIFICATION_CHANNEL_TASKER, R.string.tasker_locale));
|
||||
}
|
||||
}
|
||||
|
||||
@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) {
|
||||
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)) {
|
||||
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
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
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