diff --git a/app/build.gradle b/app/build.gradle
index 926ea65d8..444d8d7dd 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,7 +1,7 @@
apply plugin: 'com.android.application'
task wrapper(type: Wrapper) {
- gradleVersion = '3.3'
+ gradleVersion = '4.1'
}
buildscript {
@@ -37,6 +37,12 @@ android {
targetSdkVersion 26
minSdkVersion 15
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+ javaCompileOptions {
+ annotationProcessorOptions {
+ arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
+ }
+ }
}
signingConfigs {
@@ -96,12 +102,17 @@ final BUTTERKNIFE_VERSION = '8.8.1'
final GPS_VERSION = '11.2.0'
final SUPPORT_VERSION = '26.0.1'
final STETHO_VERSION = '1.5.0'
+final ROOM_VERSION = '1.0.0-alpha9'
final TESTING_SUPPORT_VERSION = '1.0.0'
dependencies {
annotationProcessor "com.google.dagger:dagger-compiler:${DAGGER_VERSION}"
compile "com.google.dagger:dagger:${DAGGER_VERSION}"
+ compile "android.arch.persistence.room:rxjava2:${ROOM_VERSION}"
+ annotationProcessor "android.arch.persistence.room:compiler:${ROOM_VERSION}"
+ compile "io.reactivex.rxjava2:rxandroid:2.0.1"
+
annotationProcessor "com.jakewharton:butterknife-compiler:${BUTTERKNIFE_VERSION}"
compile "com.jakewharton:butterknife:${BUTTERKNIFE_VERSION}"
@@ -110,7 +121,6 @@ dependencies {
}
debugCompile "com.facebook.stetho:stetho-timber:${STETHO_VERSION}@aar"
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
- //noinspection GradleCompatible
debugCompile 'com.android.support:multidex:1.0.2'
compile 'com.github.rey5137:material:1.2.4'
diff --git a/app/schemas/org.tasks.db.AppDatabase/1.json b/app/schemas/org.tasks.db.AppDatabase/1.json
new file mode 100644
index 000000000..35988ac97
--- /dev/null
+++ b/app/schemas/org.tasks.db.AppDatabase/1.json
@@ -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\")"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/amazon/java/org/tasks/injection/BroadcastComponent.java b/app/src/amazon/java/org/tasks/injection/BroadcastComponent.java
index 1dd209ead..c09ed3800 100644
--- a/app/src/amazon/java/org/tasks/injection/BroadcastComponent.java
+++ b/app/src/amazon/java/org/tasks/injection/BroadcastComponent.java
@@ -8,6 +8,7 @@ import com.todoroo.astrid.repeats.RepeatTaskCompleteListener;
import com.todoroo.astrid.timers.TimerTaskCompleteListener;
import org.tasks.locale.receiver.FireReceiver;
+import org.tasks.notifications.NotificationClearedReceiver;
import org.tasks.receivers.BootCompletedReceiver;
import org.tasks.receivers.CompleteTaskReceiver;
import org.tasks.receivers.ListNotificationReceiver;
@@ -47,4 +48,6 @@ public interface BroadcastComponent {
void inject(TeslaUnreadReceiver teslaUnreadReceiver);
void inject(PushReceiver pushReceiver);
+
+ void inject(NotificationClearedReceiver notificationClearedReceiver);
}
diff --git a/app/src/androidTest/java/com/todoroo/astrid/reminders/NotificationTests.java b/app/src/androidTest/java/com/todoroo/astrid/reminders/NotificationTests.java
index a66aecd12..f678c9ebf 100644
--- a/app/src/androidTest/java/com/todoroo/astrid/reminders/NotificationTests.java
+++ b/app/src/androidTest/java/com/todoroo/astrid/reminders/NotificationTests.java
@@ -93,7 +93,7 @@ public class NotificationTests extends DatabaseTestCase {
notifier.triggerTaskNotification(task.getId(), ReminderService.TYPE_DUE);
- verify(notificationManager).notify(eq((int) task.getId()), any(Notification.class));
+ verify(notificationManager).notify(eq((int) task.getId()), any(Notification.class), true, false, false);
}
@Test
diff --git a/app/src/androidTest/java/org/tasks/injection/TestModule.java b/app/src/androidTest/java/org/tasks/injection/TestModule.java
index 38c06f6d8..55e267ef6 100644
--- a/app/src/androidTest/java/org/tasks/injection/TestModule.java
+++ b/app/src/androidTest/java/org/tasks/injection/TestModule.java
@@ -1,10 +1,12 @@
package org.tasks.injection;
+import android.arch.persistence.room.Room;
import android.content.Context;
import com.todoroo.astrid.dao.Database;
import org.tasks.analytics.Tracker;
+import org.tasks.db.AppDatabase;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.PermissivePermissionChecker;
@@ -32,6 +34,12 @@ public class TestModule {
};
}
+ @Provides
+ @ApplicationScope
+ public AppDatabase getAppDatabase() {
+ return Room.databaseBuilder(context, AppDatabase.class, "test-app-database").build();
+ }
+
@ApplicationScope
@Provides
@ForApplication
diff --git a/app/src/androidTest/java/org/tasks/jobs/JobQueueTest.java b/app/src/androidTest/java/org/tasks/jobs/JobQueueTest.java
index e72febf0c..e16859b76 100644
--- a/app/src/androidTest/java/org/tasks/jobs/JobQueueTest.java
+++ b/app/src/androidTest/java/org/tasks/jobs/JobQueueTest.java
@@ -75,7 +75,7 @@ public class JobQueueTest {
verify(jobManager).schedule(TAG, now);
- queue.remove(new Alarm(1, 1, now));
+ queue.remove(singletonList(new Alarm(1, 1, now)));
Freeze.freezeAt(now).thawAfter(new Snippet() {{
assertEquals(
@@ -93,7 +93,7 @@ public class JobQueueTest {
verify(jobManager).schedule(TAG, now);
- queue.remove(new Reminder(1, now, TYPE_DUE));
+ queue.remove(singletonList(new Reminder(1, now, TYPE_DUE)));
Freeze.freezeAt(now).thawAfter(new Snippet() {{
assertEquals(
@@ -242,7 +242,7 @@ public class JobQueueTest {
verify(jobManager).schedule(TAG, now);
Freeze.freezeAt(now).thawAfter(new Snippet() {{
- queue.remove(new Reminder(1, now, TYPE_DUE));
+ queue.remove(queue.getOverdueJobs());
}});
assertEquals(
@@ -261,8 +261,7 @@ public class JobQueueTest {
verify(jobManager).schedule(TAG, now);
Freeze.freezeAt(now + ONE_MINUTE).thawAfter(new Snippet() {{
- queue.remove(new Reminder(1, now, TYPE_DUE));
- queue.remove(new Reminder(2, now + ONE_MINUTE, TYPE_DUE));
+ queue.remove(queue.getOverdueJobs());
}});
assertEquals(
diff --git a/app/src/androidTest/java/org/tasks/preferences/PreferenceTests.java b/app/src/androidTest/java/org/tasks/preferences/PreferenceTests.java
index 29c9f9f08..8b68356f4 100644
--- a/app/src/androidTest/java/org/tasks/preferences/PreferenceTests.java
+++ b/app/src/androidTest/java/org/tasks/preferences/PreferenceTests.java
@@ -24,7 +24,7 @@ public class PreferenceTests {
@Before
public void setUp() {
- preferences = new Preferences(getTargetContext(), null, null);
+ preferences = new Preferences(getTargetContext(), null);
preferences.clear();
preferences.setBoolean(R.string.p_rmd_enable_quiet, true);
}
diff --git a/app/src/androidTestGoogleplay/java/com/todoroo/astrid/gtasks/GtasksMetadataServiceTest.java b/app/src/androidTestGoogleplay/java/com/todoroo/astrid/gtasks/GtasksMetadataServiceTest.java
index d0d654061..ea17b83e7 100644
--- a/app/src/androidTestGoogleplay/java/com/todoroo/astrid/gtasks/GtasksMetadataServiceTest.java
+++ b/app/src/androidTestGoogleplay/java/com/todoroo/astrid/gtasks/GtasksMetadataServiceTest.java
@@ -38,7 +38,7 @@ public class GtasksMetadataServiceTest extends DatabaseTestCase {
private final GtasksTestPreferenceService service;
public GtasksMetadataServiceTestModule(Context context) {
- service = new GtasksTestPreferenceService(new Preferences(context, null, null));
+ service = new GtasksTestPreferenceService(new Preferences(context, null));
}
@Provides
diff --git a/app/src/generic/java/org/tasks/injection/BroadcastComponent.java b/app/src/generic/java/org/tasks/injection/BroadcastComponent.java
index 1dd209ead..c09ed3800 100644
--- a/app/src/generic/java/org/tasks/injection/BroadcastComponent.java
+++ b/app/src/generic/java/org/tasks/injection/BroadcastComponent.java
@@ -8,6 +8,7 @@ import com.todoroo.astrid.repeats.RepeatTaskCompleteListener;
import com.todoroo.astrid.timers.TimerTaskCompleteListener;
import org.tasks.locale.receiver.FireReceiver;
+import org.tasks.notifications.NotificationClearedReceiver;
import org.tasks.receivers.BootCompletedReceiver;
import org.tasks.receivers.CompleteTaskReceiver;
import org.tasks.receivers.ListNotificationReceiver;
@@ -47,4 +48,6 @@ public interface BroadcastComponent {
void inject(TeslaUnreadReceiver teslaUnreadReceiver);
void inject(PushReceiver pushReceiver);
+
+ void inject(NotificationClearedReceiver notificationClearedReceiver);
}
diff --git a/app/src/googleplay/java/org/tasks/gtasks/GoogleTaskSyncAdapter.java b/app/src/googleplay/java/org/tasks/gtasks/GoogleTaskSyncAdapter.java
index f2948fefc..9065fd934 100644
--- a/app/src/googleplay/java/org/tasks/gtasks/GoogleTaskSyncAdapter.java
+++ b/app/src/googleplay/java/org/tasks/gtasks/GoogleTaskSyncAdapter.java
@@ -160,7 +160,7 @@ public class GoogleTaskSyncAdapter extends InjectingAbstractThreadedSyncAdapter
.setAutoCancel(true)
.setSmallIcon(android.R.drawable.ic_dialog_alert)
.setTicker(context.getString(R.string.common_google_play_services_notification_ticker));
- notificationManager.notify(Constants.NOTIFICATION_SYNC_ERROR, builder.build());
+ notificationManager.notify(Constants.NOTIFICATION_SYNC_ERROR, builder.build(), true, false, false);
}
private void synchronize() throws IOException {
diff --git a/app/src/googleplay/java/org/tasks/injection/BroadcastComponent.java b/app/src/googleplay/java/org/tasks/injection/BroadcastComponent.java
index d82e3a865..350ec3485 100644
--- a/app/src/googleplay/java/org/tasks/injection/BroadcastComponent.java
+++ b/app/src/googleplay/java/org/tasks/injection/BroadcastComponent.java
@@ -8,6 +8,7 @@ import com.todoroo.astrid.repeats.RepeatTaskCompleteListener;
import com.todoroo.astrid.timers.TimerTaskCompleteListener;
import org.tasks.locale.receiver.FireReceiver;
+import org.tasks.notifications.NotificationClearedReceiver;
import org.tasks.receivers.BootCompletedReceiver;
import org.tasks.receivers.CompleteTaskReceiver;
import org.tasks.receivers.GoogleTaskPushReceiver;
@@ -50,4 +51,6 @@ public interface BroadcastComponent {
void inject(TeslaUnreadReceiver teslaUnreadReceiver);
void inject(PushReceiver pushReceiver);
+
+ void inject(NotificationClearedReceiver notificationClearedReceiver);
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3e6553f3a..c3c4313a5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -424,7 +424,7 @@
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false" />
+
+
= Build.VERSION_CODES.M;
}
+ public static boolean atLeastNougat() {
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
+ }
+
public static boolean atLeastOreo() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
}
diff --git a/app/src/main/java/com/todoroo/astrid/calls/PhoneStateChangedReceiver.java b/app/src/main/java/com/todoroo/astrid/calls/PhoneStateChangedReceiver.java
index edb3c8f0c..ba40423ff 100644
--- a/app/src/main/java/com/todoroo/astrid/calls/PhoneStateChangedReceiver.java
+++ b/app/src/main/java/com/todoroo/astrid/calls/PhoneStateChangedReceiver.java
@@ -5,28 +5,41 @@
*/
package com.todoroo.astrid.calls;
+import android.annotation.SuppressLint;
+import android.app.PendingIntent;
+import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.CallLog.Calls;
import android.provider.ContactsContract;
+import android.support.v4.app.NotificationCompat;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
-import org.tasks.Notifier;
+import org.tasks.R;
import org.tasks.injection.BroadcastComponent;
+import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingBroadcastReceiver;
+import org.tasks.notifications.NotificationManager;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences;
+import org.tasks.reminders.MissedCallActivity;
+
+import java.io.InputStream;
import javax.inject.Inject;
import timber.log.Timber;
+import static org.tasks.time.DateTimeUtils.currentTimeMillis;
+
public class PhoneStateChangedReceiver extends InjectingBroadcastReceiver {
private static final String PREF_LAST_INCOMING_NUMBER = "last_incoming_number";
@@ -34,8 +47,9 @@ public class PhoneStateChangedReceiver extends InjectingBroadcastReceiver {
private static final long WAIT_BEFORE_READ_LOG = 3000L;
@Inject Preferences preferences;
- @Inject Notifier notifier;
+ @Inject NotificationManager notificationManager;
@Inject PermissionChecker permissionChecker;
+ @Inject @ForApplication Context context;
@Override
public void onReceive(final Context context, Intent intent) {
@@ -73,12 +87,12 @@ public class PhoneStateChangedReceiver extends InjectingBroadcastReceiver {
AndroidUtilities.sleepDeep(WAIT_BEFORE_READ_LOG);
Cursor calls;
try {
- calls = getMissedCalls(context);
+ calls = getMissedCalls();
} catch (Exception e) { // Sometimes database is locked, retry once
Timber.e(e, e.getMessage());
AndroidUtilities.sleepDeep(300L);
try {
- calls = getMissedCalls(context);
+ calls = getMissedCalls();
} catch (Exception e2) {
Timber.e(e2, e2.getMessage());
calls = null;
@@ -113,7 +127,7 @@ public class PhoneStateChangedReceiver extends InjectingBroadcastReceiver {
long contactId = getContactIdFromNumber(context, number);
- notifier.triggerMissedCallNotification(name, number, contactId);
+ triggerMissedCallNotification(name, number, contactId);
}
} catch (Exception e) {
Timber.e(e, e.getMessage());
@@ -127,7 +141,8 @@ public class PhoneStateChangedReceiver extends InjectingBroadcastReceiver {
}
}
- private Cursor getMissedCalls(Context context) {
+ @SuppressLint("MissingPermission")
+ private Cursor getMissedCalls() {
if (permissionChecker.canAccessMissedCallPermissions()) {
//noinspection MissingPermission
return context.getContentResolver().query(
@@ -174,4 +189,61 @@ public class PhoneStateChangedReceiver extends InjectingBroadcastReceiver {
return -1;
}
+ public void triggerMissedCallNotification(final String name, final String number, long contactId) {
+ final String title = context.getString(R.string.missed_call, TextUtils.isEmpty(name) ? number : name);
+
+ Intent missedCallDialog = new Intent(context, MissedCallActivity.class);
+ missedCallDialog.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ missedCallDialog.putExtra(MissedCallActivity.EXTRA_NUMBER, number);
+ missedCallDialog.putExtra(MissedCallActivity.EXTRA_NAME, name);
+ missedCallDialog.putExtra(MissedCallActivity.EXTRA_TITLE, title);
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationManager.NOTIFICATION_CHANNEL_CALLS)
+ .setTicker(title)
+ .setContentTitle(title)
+ .setContentText(context.getString(R.string.app_name))
+ .setWhen(currentTimeMillis())
+ .setShowWhen(true)
+ .setSmallIcon(R.drawable.ic_check_white_24dp)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setContentIntent(PendingIntent.getActivity(context, missedCallDialog.hashCode(), missedCallDialog, PendingIntent.FLAG_UPDATE_CURRENT));
+
+ Bitmap contactImage = getContactImage(contactId);
+ if (contactImage != null) {
+ builder.setLargeIcon(contactImage);
+ }
+
+ Intent callNow = new Intent(context, MissedCallActivity.class);
+ callNow.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ callNow.putExtra(MissedCallActivity.EXTRA_NUMBER, number);
+ callNow.putExtra(MissedCallActivity.EXTRA_NAME, name);
+ callNow.putExtra(MissedCallActivity.EXTRA_TITLE, title);
+ callNow.putExtra(MissedCallActivity.EXTRA_CALL_NOW, true);
+
+ Intent callLater = new Intent(context, MissedCallActivity.class);
+ callLater.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ callLater.putExtra(MissedCallActivity.EXTRA_NUMBER, number);
+ callLater.putExtra(MissedCallActivity.EXTRA_NAME, name);
+ callLater.putExtra(MissedCallActivity.EXTRA_TITLE, title);
+ callLater.putExtra(MissedCallActivity.EXTRA_CALL_LATER, true);
+ builder
+ .addAction(R.drawable.ic_phone_white_24dp, context.getString(R.string.MCA_return_call), PendingIntent.getActivity(context, callNow.hashCode(), callNow, PendingIntent.FLAG_UPDATE_CURRENT))
+ .addAction(R.drawable.ic_add_white_24dp, context.getString(R.string.MCA_add_task), PendingIntent.getActivity(context, callLater.hashCode(), callLater, PendingIntent.FLAG_UPDATE_CURRENT));
+
+ notificationManager.notify(number.hashCode(), builder.build(), true, false, false);
+ }
+
+ private Bitmap getContactImage(long contactId) {
+ Bitmap b = null;
+ if (contactId >= 0) {
+ Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
+ InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri);
+ try {
+ b = BitmapFactory.decodeStream(input);
+ } catch (OutOfMemoryError e) {
+ Timber.e(e, e.getMessage());
+ }
+ }
+ return b;
+ }
}
diff --git a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java
index 05be366c5..673d2f6ae 100644
--- a/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java
+++ b/app/src/main/java/com/todoroo/astrid/dao/TaskDao.java
@@ -95,6 +95,10 @@ public class TaskDao {
dao.query(Query.select(Task.PROPERTIES).where(Criterion.and(TaskCriteria.isActive(), criterion)), callback);
}
+ public Task fetch(long id) {
+ return dao.fetch(id, Task.PROPERTIES);
+ }
+
public Task fetch(long id, Property>... properties) {
return dao.fetch(id, properties);
}
diff --git a/app/src/main/java/com/todoroo/astrid/reminders/ReminderPreferences.java b/app/src/main/java/com/todoroo/astrid/reminders/ReminderPreferences.java
index 027ad8b66..e69a943cb 100644
--- a/app/src/main/java/com/todoroo/astrid/reminders/ReminderPreferences.java
+++ b/app/src/main/java/com/todoroo/astrid/reminders/ReminderPreferences.java
@@ -17,14 +17,11 @@ import android.preference.Preference;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.annotation.NonNull;
-import android.support.v4.app.JobIntentService;
import org.tasks.R;
import org.tasks.activities.TimePickerActivity;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingPreferenceActivity;
-import org.tasks.jobs.JobManager;
-import org.tasks.notifications.NotificationManager;
import org.tasks.preferences.ActivityPermissionRequestor;
import org.tasks.preferences.Device;
import org.tasks.preferences.PermissionChecker;
@@ -66,7 +63,8 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
R.string.p_rmd_time,
R.string.p_rmd_enable_quiet,
R.string.p_rmd_quietStart,
- R.string.p_rmd_quietEnd);
+ R.string.p_rmd_quietEnd,
+ R.string.p_rmd_persistent);
resetGeofencesOnChange(
R.string.p_geofence_radius,
R.string.p_geofence_responsiveness);
@@ -89,8 +87,7 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
@TargetApi(Build.VERSION_CODES.O)
private boolean openNotificationChannelSettings(Preference ignored) {
- Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
- intent.putExtra(Settings.EXTRA_CHANNEL_ID, NotificationManager.DEFAULT_NOTIFICATION_CHANNEL);
+ Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, ReminderPreferences.this.getPackageName());
startActivity(intent);
return true;
@@ -99,7 +96,7 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
private void rescheduleNotificationsOnChange(int... resIds) {
for (int resId : resIds) {
findPreference(getString(resId)).setOnPreferenceChangeListener((preference, newValue) -> {
- JobIntentService.enqueueWork(this, NotificationSchedulerIntentService.class, JobManager.JOB_ID_NOTIFICATION_SCHEDULER, new Intent());
+ NotificationSchedulerIntentService.enqueueWork(this);
return true;
});
}
@@ -108,7 +105,7 @@ public class ReminderPreferences extends InjectingPreferenceActivity {
private void resetGeofencesOnChange(int... resIds) {
for (int resId : resIds) {
findPreference(getString(resId)).setOnPreferenceChangeListener((preference, newValue) -> {
- JobIntentService.enqueueWork(this, GeofenceSchedulingIntentService.class, JobManager.JOB_ID_GEOFENCE_SCHEDULING, new Intent());
+ GeofenceSchedulingIntentService.enqueueWork(this);
return true;
});
}
diff --git a/app/src/main/java/com/todoroo/astrid/service/StartupService.java b/app/src/main/java/com/todoroo/astrid/service/StartupService.java
index d739886c0..40d6aa105 100644
--- a/app/src/main/java/com/todoroo/astrid/service/StartupService.java
+++ b/app/src/main/java/com/todoroo/astrid/service/StartupService.java
@@ -6,7 +6,6 @@
package com.todoroo.astrid.service;
import android.content.Context;
-import android.content.Intent;
import android.database.sqlite.SQLiteException;
import android.os.Environment;
@@ -15,7 +14,6 @@ import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps;
import com.todoroo.andlib.sql.Criterion;
-import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.dao.Database;
import com.todoroo.astrid.dao.MetadataDao;
import com.todoroo.astrid.dao.TagDataDao;
@@ -52,14 +50,14 @@ public class StartupService {
private final TagDataDao tagDataDao;
private final TagService tagService;
private final MetadataDao metadataDao;
- private final BackgroundScheduler backgroundScheduler;
private final LocalBroadcastManager localBroadcastManager;
+ private final Context context;
@Inject
public StartupService(Database database, Preferences preferences, TaskDeleter taskDeleter,
Tracker tracker, TagDataDao tagDataDao, TagService tagService,
- MetadataDao metadataDao, BackgroundScheduler backgroundScheduler,
- LocalBroadcastManager localBroadcastManager) {
+ MetadataDao metadataDao, LocalBroadcastManager localBroadcastManager,
+ @ForApplication Context context) {
this.database = database;
this.preferences = preferences;
this.taskDeleter = taskDeleter;
@@ -67,8 +65,8 @@ public class StartupService {
this.tagDataDao = tagDataDao;
this.tagService = tagService;
this.metadataDao = metadataDao;
- this.backgroundScheduler = backgroundScheduler;
this.localBroadcastManager = localBroadcastManager;
+ this.context = context;
}
/** Called when this application is started up */
@@ -97,12 +95,10 @@ public class StartupService {
preferences.setDefaults();
}
- // perform startup activities in a background thread
- new Thread(() -> {
- taskDeleter.deleteTasksWithEmptyTitles(null);
- }).start();
+ BackgroundScheduler.enqueueWork(context);
- backgroundScheduler.scheduleEverything();
+ // perform startup activities in a background thread
+ new Thread(() -> taskDeleter.deleteTasksWithEmptyTitles(null)).start();
}
private void upgrade(int from, int to) {
diff --git a/app/src/main/java/com/todoroo/astrid/timers/TimerPlugin.java b/app/src/main/java/com/todoroo/astrid/timers/TimerPlugin.java
index 460c6e074..02a37e036 100644
--- a/app/src/main/java/com/todoroo/astrid/timers/TimerPlugin.java
+++ b/app/src/main/java/com/todoroo/astrid/timers/TimerPlugin.java
@@ -115,7 +115,7 @@ public class TimerPlugin {
.setAutoCancel(false)
.setOngoing(true)
.build();
- notificationManager.notify(Constants.NOTIFICATION_TIMER, notification);
+ notificationManager.notify(Constants.NOTIFICATION_TIMER, notification, false, false, false);
}
}
}
diff --git a/app/src/main/java/org/tasks/Notifier.java b/app/src/main/java/org/tasks/Notifier.java
index 7ebfe07e8..6a9b873af 100644
--- a/app/src/main/java/org/tasks/Notifier.java
+++ b/app/src/main/java/org/tasks/Notifier.java
@@ -1,15 +1,9 @@
package org.tasks;
-import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.PendingIntent;
-import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.provider.ContactsContract;
import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
@@ -23,19 +17,23 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.reminders.ReminderService;
import com.todoroo.astrid.voice.VoiceOutputAssistant;
+import org.tasks.db.AppDatabase;
import org.tasks.injection.ForApplication;
+import org.tasks.jobs.JobQueueEntry;
import org.tasks.notifications.AudioManager;
import org.tasks.notifications.NotificationManager;
import org.tasks.notifications.TelephonyManager;
import org.tasks.preferences.Preferences;
import org.tasks.receivers.CompleteTaskReceiver;
-import org.tasks.reminders.MissedCallActivity;
import org.tasks.reminders.NotificationActivity;
import org.tasks.reminders.SnoozeActivity;
import org.tasks.reminders.SnoozeDialog;
import org.tasks.reminders.SnoozeOption;
-import java.io.InputStream;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
import javax.inject.Inject;
@@ -43,13 +41,13 @@ import timber.log.Timber;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static com.google.common.base.Strings.isNullOrEmpty;
+import static com.google.common.collect.Lists.transform;
+import static com.todoroo.andlib.utility.AndroidUtilities.atLeastNougat;
+import static org.tasks.notifications.NotificationManager.GROUP_KEY;
import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class Notifier {
- private static long lastNotificationSound = 0L;
-
private final Context context;
private final TaskDao taskDao;
private final NotificationManager notificationManager;
@@ -57,12 +55,13 @@ public class Notifier {
private final AudioManager audioManager;
private final VoiceOutputAssistant voiceOutputAssistant;
private final Preferences preferences;
+ private final AppDatabase appDatabase;
@Inject
public Notifier(@ForApplication Context context, TaskDao taskDao,
NotificationManager notificationManager, TelephonyManager telephonyManager,
AudioManager audioManager, VoiceOutputAssistant voiceOutputAssistant,
- Preferences preferences) {
+ Preferences preferences, AppDatabase appDatabase) {
this.context = context;
this.taskDao = taskDao;
this.notificationManager = notificationManager;
@@ -70,65 +69,7 @@ public class Notifier {
this.audioManager = audioManager;
this.voiceOutputAssistant = voiceOutputAssistant;
this.preferences = preferences;
- }
-
- public void triggerMissedCallNotification(final String name, final String number, long contactId) {
- final String title = context.getString(R.string.missed_call, TextUtils.isEmpty(name) ? number : name);
-
- Intent missedCallDialog = new Intent(context, MissedCallActivity.class);
- missedCallDialog.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- missedCallDialog.putExtra(MissedCallActivity.EXTRA_NUMBER, number);
- missedCallDialog.putExtra(MissedCallActivity.EXTRA_NAME, name);
- missedCallDialog.putExtra(MissedCallActivity.EXTRA_TITLE, title);
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationManager.DEFAULT_NOTIFICATION_CHANNEL)
- .setSmallIcon(R.drawable.ic_check_white_24dp)
- .setTicker(title)
- .setContentTitle(title)
- .setContentText(context.getString(R.string.app_name))
- .setWhen(currentTimeMillis())
- .setVibrate(preferences.isVibrationEnabled() ? preferences.getVibrationPattern() : null)
- .setLights(preferences.getLEDColor(), preferences.isLEDNotificationEnabled() ? 700 : 0, 5000)
- .setPriority(preferences.getNotificationPriority())
- .setContentIntent(PendingIntent.getActivity(context, missedCallDialog.hashCode(), missedCallDialog, PendingIntent.FLAG_UPDATE_CURRENT));
-
- Bitmap contactImage = getContactImage(contactId);
- if (contactImage != null) {
- builder.setLargeIcon(contactImage);
- }
-
- Intent callNow = new Intent(context, MissedCallActivity.class);
- callNow.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- callNow.putExtra(MissedCallActivity.EXTRA_NUMBER, number);
- callNow.putExtra(MissedCallActivity.EXTRA_NAME, name);
- callNow.putExtra(MissedCallActivity.EXTRA_TITLE, title);
- callNow.putExtra(MissedCallActivity.EXTRA_CALL_NOW, true);
-
- Intent callLater = new Intent(context, MissedCallActivity.class);
- callLater.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- callLater.putExtra(MissedCallActivity.EXTRA_NUMBER, number);
- callLater.putExtra(MissedCallActivity.EXTRA_NAME, name);
- callLater.putExtra(MissedCallActivity.EXTRA_TITLE, title);
- callLater.putExtra(MissedCallActivity.EXTRA_CALL_LATER, true);
- builder
- .addAction(R.drawable.ic_phone_white_24dp, context.getString(R.string.MCA_return_call), PendingIntent.getActivity(context, callNow.hashCode(), callNow, PendingIntent.FLAG_UPDATE_CURRENT))
- .addAction(R.drawable.ic_add_white_24dp, context.getString(R.string.MCA_add_task), PendingIntent.getActivity(context, callLater.hashCode(), callLater, PendingIntent.FLAG_UPDATE_CURRENT));
-
- activateNotification(1, number.hashCode(), builder.build(), null);
- }
-
- private Bitmap getContactImage(long contactId) {
- Bitmap b = null;
- if (contactId >= 0) {
- Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contactId);
- InputStream input = ContactsContract.Contacts.openContactPhotoInputStream(context.getContentResolver(), uri);
- try {
- b = BitmapFactory.decodeStream(input);
- } catch (OutOfMemoryError e) {
- Timber.e(e, e.getMessage());
- }
- }
- return b;
+ this.appDatabase = appDatabase;
}
public void triggerFilterNotification(final Filter filter) {
@@ -146,56 +87,56 @@ public class Notifier {
intent.putExtra(TaskListActivity.OPEN_FILTER, filter);
PendingIntent pendingIntent = PendingIntent.getActivity(context, (title + query).hashCode(), intent, PendingIntent.FLAG_UPDATE_CURRENT);
- Notification notification = new NotificationCompat.Builder(context, NotificationManager.DEFAULT_NOTIFICATION_CHANNEL)
- .setSmallIcon(R.drawable.ic_check_white_24dp)
+ NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NotificationManager.NOTIFICATION_CHANNEL_TASKER)
.setCategory(NotificationCompat.CATEGORY_REMINDER)
.setTicker(title)
- .setWhen(currentTimeMillis())
.setContentTitle(title)
.setContentText(subtitle)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
- .setVibrate(preferences.isVibrationEnabled() ? preferences.getVibrationPattern() : null)
- .setLights(preferences.getLEDColor(), preferences.isLEDNotificationEnabled() ? 700 : 0, 5000)
- .setPriority(preferences.getNotificationPriority())
- .build();
-
- activateNotification(1, (title + query).hashCode(), notification, null);
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setWhen(currentTimeMillis())
+ .setShowWhen(true);
+
+ notificationManager.notify(
+ (title + query).hashCode(),
+ notification.build(),
+ true,
+ false,
+ false);
}
public void triggerTaskNotification(long id, int type) {
- if (!showNotification(id, type)) {
- notificationManager.cancel(id);
- }
+ org.tasks.notifications.Notification notification = new org.tasks.notifications.Notification();
+ notification.taskId = id;
+ notification.type = type;
+ notification.timestamp = currentTimeMillis();
+ triggerNotifications(Collections.singletonList(notification), true);
}
- private boolean showNotification(final long id, final int type) {
- Task task;
- try {
- task = taskDao.fetch(id, Task.ID, Task.TITLE, Task.HIDE_UNTIL, Task.COMPLETION_DATE,
- Task.DUE_DATE, Task.DELETION_DATE, Task.REMINDER_FLAGS, Task.NOTES);
- if (task == null) {
- throw new IllegalArgumentException("cound not find item with id"); //$NON-NLS-1$
- }
-
- } catch (Exception e) {
- Timber.e(e, e.getMessage());
- return false;
+ private NotificationCompat.Builder getTaskNotification(org.tasks.notifications.Notification notification) {
+ long id = notification.taskId;
+ int type = notification.type;
+ long when = notification.timestamp;
+ Task task = taskDao.fetch(id);
+ if (task == null) {
+ Timber.e("Could not find %s", id);
+ return null;
}
// you're done, or not yours - don't sound, do delete
if (task.isCompleted() || task.isDeleted()) {
- return false;
+ return null;
}
// new task edit in progress
if (TextUtils.isEmpty(task.getTitle())) {
- return false;
+ return null;
}
// it's hidden - don't sound, don't delete
if (task.isHidden() && type == ReminderService.TYPE_RANDOM) {
- return false;
+ return null;
}
// task due date was changed, but alarm wasn't rescheduled
@@ -203,21 +144,18 @@ public class Notifier {
!task.hasDueTime() && task.getDueDate() - DateUtilities.now() > DateUtilities.ONE_DAY;
if ((type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_OVERDUE) &&
(!task.hasDueDate() || dueInFuture)) {
- return false;
+ return null;
}
// read properties
final String taskTitle = task.getTitle();
final String taskDescription = task.getNotes();
- boolean nonstopMode = task.isNotifyModeNonstop();
- boolean ringFiveMode = task.isNotifyModeFive();
- int ringTimes = nonstopMode ? -1 : (ringFiveMode ? 5 : 1);
// update last reminder time
- task.setReminderLast(DateUtilities.now());
+ task.setReminderLast(when);
taskDao.saveExisting(task);
- final String text = context.getString(R.string.app_name);
+ final String appName = context.getString(R.string.app_name);
final Intent intent = new Intent(context, NotificationActivity.class);
intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
@@ -225,22 +163,18 @@ public class Notifier {
intent.putExtra(NotificationActivity.EXTRA_TASK_ID, id);
intent.putExtra(NotificationActivity.EXTRA_TITLE, taskTitle);
- // don't ring multiple times if random reminder
- if (type == ReminderService.TYPE_RANDOM) {
- ringTimes = 1;
- }
-
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationManager.DEFAULT_NOTIFICATION_CHANNEL)
- .setSmallIcon(R.drawable.ic_check_white_24dp)
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NotificationManager.NOTIFICATION_CHANNEL_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_REMINDER)
.setTicker(taskTitle)
- .setWhen(currentTimeMillis())
.setContentTitle(taskTitle)
- .setContentText(text)
- .setVibrate(preferences.isVibrationEnabled() ? preferences.getVibrationPattern() : null)
- .setLights(preferences.getLEDColor(), preferences.isLEDNotificationEnabled() ? 700 : 0, 5000)
- .setPriority(preferences.getNotificationPriority())
+ .setContentText(appName)
+ .setGroup(GROUP_KEY)
+ .setSmallIcon(R.drawable.ic_check_white_24dp)
+ .setWhen(when)
+ .setShowWhen(true)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(PendingIntent.getActivity(context, (int) id, intent, PendingIntent.FLAG_UPDATE_CURRENT));
+
if (!Strings.isNullOrEmpty(taskDescription)) {
builder.setStyle(new NotificationCompat.BigTextStyle().bigText(taskDescription));
}
@@ -271,79 +205,59 @@ public class Notifier {
.build());
}
- builder.addAction(completeAction)
+ return builder.addAction(completeAction)
.addAction(R.drawable.ic_snooze_white_24dp, context.getResources().getString(R.string.rmd_NoA_snooze), snoozePendingIntent)
.extend(wearableExtender);
-
- activateNotification(ringTimes, (int) id, builder.build(), taskTitle);
-
- return true;
}
- @SuppressLint("NewApi")
- private void activateNotification(int ringTimes, int notificationId, Notification notification, String text) {
- if (preferences.getBoolean(R.string.p_rmd_persistent, true)) {
- notification.flags |= Notification.FLAG_NO_CLEAR;
- }
-
- boolean voiceReminder = preferences.getBoolean(R.string.p_voiceRemindersEnabled, false) && !isNullOrEmpty(text);
+ public void restoreNotifications() {
+ triggerNotifications(appDatabase.notificationDao().getAll(), false);
+ }
- if (ringTimes != 1) {
- notification.audioStreamType = android.media.AudioManager.STREAM_ALARM;
+ public void triggerTaskNotifications(List extends JobQueueEntry> entries) {
+ triggerNotifications(transform(entries, JobQueueEntry::toNotification), true);
+ }
- // insistent rings until notification is disabled
- if (ringTimes < 0) {
- notification.flags |= Notification.FLAG_INSISTENT;
- voiceReminder = false;
+ public void triggerNotifications(List entries, boolean alert) {
+ Map notifications = new LinkedHashMap<>();
+ boolean ringFiveTimes = false;
+ boolean ringNonstop = false;
+ for (int i = 0 ; i < entries.size() ; i++) {
+ org.tasks.notifications.Notification entry = entries.get(i);
+ Task task = taskDao.fetch(entry.taskId);
+ if (task == null) {
+ continue;
}
-
- } else {
- notification.audioStreamType = android.media.AudioManager.STREAM_NOTIFICATION;
- }
-
- boolean soundIntervalOk = checkLastNotificationSound();
-
- if (telephonyManager.callStateIdle()) {
- String notificationPreference = preferences.getStringValue(R.string.p_rmd_ringtone);
- if (audioManager.notificationsMuted()) {
- notification.sound = null;
- voiceReminder = false;
- } else if (notificationPreference != null) {
- if (notificationPreference.length() > 0 && soundIntervalOk) {
- notification.sound = Uri.parse(notificationPreference);
- } else {
- notification.sound = null;
- }
- } else if (soundIntervalOk) {
- notification.defaults |= Notification.DEFAULT_SOUND;
+ if (entry.type != ReminderService.TYPE_RANDOM) {
+ ringFiveTimes |= task.isNotifyModeFive();
+ ringNonstop |= task.isNotifyModeNonstop();
+ }
+ NotificationCompat.Builder notification = getTaskNotification(entry);
+ if (notification != null) {
+ notification.setGroupAlertBehavior(alert && (atLeastNougat() ? entries.size() == 1 : i == entries.size() - 1)
+ ? NotificationCompat.GROUP_ALERT_CHILDREN
+ : NotificationCompat.GROUP_ALERT_SUMMARY);
+ notifications.put(entry, notification.build());
}
}
- if (!telephonyManager.callStateIdle()) {
- notification.sound = null;
- notification.vibrate = null;
- voiceReminder = false;
+ if (notifications.isEmpty()) {
+ return;
+ } else {
+ Timber.d("Triggering %s", notifications.keySet());
}
- for (int i = 0; i < Math.max(ringTimes, 1); i++) {
- notificationManager.notify(notificationId, notification);
- AndroidUtilities.sleepDeep(500);
- }
- if (voiceReminder) {
- AndroidUtilities.sleepDeep(2000);
- voiceOutputAssistant.speak(text);
- }
- }
+ notificationManager.notifyTasks(notifications, alert, ringNonstop, ringFiveTimes);
- /**
- * @return true if notification should sound
- */
- private static boolean checkLastNotificationSound() {
- long now = DateUtilities.now();
- if (now - lastNotificationSound > 10000) {
- lastNotificationSound = now;
- return true;
+ if (alert &&
+ preferences.getBoolean(R.string.p_voiceRemindersEnabled, false) &&
+ !ringNonstop &&
+ !audioManager.notificationsMuted() &&
+ telephonyManager.callStateIdle()) {
+ for (Notification notification : notifications.values()) {
+ AndroidUtilities.sleepDeep(2000);
+ voiceOutputAssistant.speak(notification.tickerText.toString());
+ }
}
- return false;
}
}
diff --git a/app/src/main/java/org/tasks/db/AppDatabase.java b/app/src/main/java/org/tasks/db/AppDatabase.java
new file mode 100644
index 000000000..3ceb02354
--- /dev/null
+++ b/app/src/main/java/org/tasks/db/AppDatabase.java
@@ -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();
+}
diff --git a/app/src/main/java/org/tasks/injection/ApplicationModule.java b/app/src/main/java/org/tasks/injection/ApplicationModule.java
index 3ddcc017e..d51f48fe2 100644
--- a/app/src/main/java/org/tasks/injection/ApplicationModule.java
+++ b/app/src/main/java/org/tasks/injection/ApplicationModule.java
@@ -1,9 +1,11 @@
package org.tasks.injection;
+import android.arch.persistence.room.Room;
import android.content.Context;
import org.tasks.ErrorReportingSingleThreadExecutor;
import org.tasks.analytics.Tracker;
+import org.tasks.db.AppDatabase;
import org.tasks.locale.Locale;
import org.tasks.themes.ThemeCache;
import org.tasks.ui.CheckBoxes;
@@ -56,4 +58,10 @@ public class ApplicationModule {
public WidgetCheckBoxes getWidgetCheckBoxes(CheckBoxes checkBoxes) {
return newWidgetCheckBoxes(checkBoxes);
}
+
+ @Provides
+ @ApplicationScope
+ public AppDatabase getAppDatabase() {
+ return Room.databaseBuilder(context, AppDatabase.class, "app-database").build();
+ }
}
diff --git a/app/src/main/java/org/tasks/injection/IntentServiceComponent.java b/app/src/main/java/org/tasks/injection/IntentServiceComponent.java
index ba60ade50..88ddf8df0 100644
--- a/app/src/main/java/org/tasks/injection/IntentServiceComponent.java
+++ b/app/src/main/java/org/tasks/injection/IntentServiceComponent.java
@@ -5,17 +5,15 @@ import org.tasks.jobs.BackupJob;
import org.tasks.jobs.MidnightRefreshJob;
import org.tasks.jobs.RefreshJob;
import org.tasks.location.GeofenceTransitionsIntentService;
+import org.tasks.scheduling.BackgroundScheduler;
import org.tasks.scheduling.CalendarNotificationIntentService;
import org.tasks.scheduling.GeofenceSchedulingIntentService;
import org.tasks.scheduling.NotificationSchedulerIntentService;
-import org.tasks.scheduling.SchedulerIntentService;
import dagger.Subcomponent;
@Subcomponent(modules = IntentServiceModule.class)
public interface IntentServiceComponent {
- void inject(SchedulerIntentService schedulerIntentService);
-
void inject(GeofenceSchedulingIntentService geofenceSchedulingIntentService);
void inject(CalendarNotificationIntentService calendarNotificationIntentService);
@@ -31,4 +29,6 @@ public interface IntentServiceComponent {
void inject(MidnightRefreshJob midnightRefreshJob);
void inject(RefreshJob refreshJob);
+
+ void inject(BackgroundScheduler backgroundScheduler);
}
diff --git a/app/src/main/java/org/tasks/jobs/Alarm.java b/app/src/main/java/org/tasks/jobs/Alarm.java
index c48139bb6..9f3b81886 100644
--- a/app/src/main/java/org/tasks/jobs/Alarm.java
+++ b/app/src/main/java/org/tasks/jobs/Alarm.java
@@ -2,6 +2,11 @@ package org.tasks.jobs;
import com.todoroo.astrid.alarms.AlarmFields;
import com.todoroo.astrid.data.Metadata;
+import com.todoroo.astrid.reminders.ReminderService;
+
+import org.tasks.notifications.Notification;
+
+import static org.tasks.time.DateTimeUtils.currentTimeMillis;
public class Alarm implements JobQueueEntry {
private final long alarmId;
@@ -32,6 +37,15 @@ public class Alarm implements JobQueueEntry {
return time;
}
+ @Override
+ public Notification toNotification() {
+ Notification notification = new Notification();
+ notification.taskId = taskId;
+ notification.type = ReminderService.TYPE_ALARM;
+ notification.timestamp = currentTimeMillis();
+ return notification;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/app/src/main/java/org/tasks/jobs/JobManager.java b/app/src/main/java/org/tasks/jobs/JobManager.java
index e4914c12b..1e1904083 100644
--- a/app/src/main/java/org/tasks/jobs/JobManager.java
+++ b/app/src/main/java/org/tasks/jobs/JobManager.java
@@ -20,12 +20,12 @@ import static org.tasks.time.DateTimeUtils.printTimestamp;
public class JobManager {
static final int JOB_ID_REFRESH = 1;
+ public static final int JOB_ID_BACKGROUND_SCHEDULER = 2;
static final int JOB_ID_NOTIFICATION = 3;
public static final int JOB_ID_GEOFENCE_TRANSITION = 4;
public static final int JOB_ID_GEOFENCE_SCHEDULING = 5;
static final int JOB_ID_MIDNIGHT_REFRESH = 6;
static final int JOB_ID_BACKUP = 7;
- public static final int JOB_ID_SCHEDULER = 8;
public static final int JOB_ID_NOTIFICATION_SCHEDULER = 9;
public static final int JOB_ID_CALENDAR_NOTIFICATION = 10;
@@ -38,7 +38,7 @@ public class JobManager {
this.alarmManager = alarmManager;
}
- public void schedule(String tag, long time) {
+ void schedule(String tag, long time) {
Timber.d("%s: %s", tag, printTimestamp(time));
alarmManager.wakeup(adjust(time), getPendingIntent(tag));
}
diff --git a/app/src/main/java/org/tasks/jobs/JobQueue.java b/app/src/main/java/org/tasks/jobs/JobQueue.java
index f3ca4e645..7f6db5f09 100644
--- a/app/src/main/java/org/tasks/jobs/JobQueue.java
+++ b/app/src/main/java/org/tasks/jobs/JobQueue.java
@@ -72,10 +72,6 @@ public class JobQueue {
return result;
}
- public synchronized boolean remove(JobQueueEntry entry) {
- return jobs.remove(entry.getTime(), entry);
- }
-
synchronized void scheduleNext() {
scheduleNext(false);
}
@@ -106,4 +102,12 @@ public class JobQueue {
List getJobs() {
return ImmutableList.copyOf(jobs.values());
}
+
+ public synchronized boolean remove(List extends JobQueueEntry> entries) {
+ boolean success = true;
+ for (JobQueueEntry entry : entries) {
+ success &= jobs.remove(entry.getTime(), entry);
+ }
+ return success;
+ }
}
diff --git a/app/src/main/java/org/tasks/jobs/JobQueueEntry.java b/app/src/main/java/org/tasks/jobs/JobQueueEntry.java
index a52845d05..6ece579a9 100644
--- a/app/src/main/java/org/tasks/jobs/JobQueueEntry.java
+++ b/app/src/main/java/org/tasks/jobs/JobQueueEntry.java
@@ -1,7 +1,11 @@
package org.tasks.jobs;
+import org.tasks.notifications.Notification;
+
public interface JobQueueEntry {
long getId();
long getTime();
+
+ Notification toNotification();
}
diff --git a/app/src/main/java/org/tasks/jobs/NotificationJob.java b/app/src/main/java/org/tasks/jobs/NotificationJob.java
index c8b311328..87885cbbc 100644
--- a/app/src/main/java/org/tasks/jobs/NotificationJob.java
+++ b/app/src/main/java/org/tasks/jobs/NotificationJob.java
@@ -6,13 +6,14 @@ import android.content.Intent;
import android.support.v4.app.JobIntentService;
import com.todoroo.astrid.dao.TaskDao;
-import com.todoroo.astrid.data.Task;
-import com.todoroo.astrid.reminders.ReminderService;
+import org.tasks.BuildConfig;
import org.tasks.Notifier;
import org.tasks.injection.IntentServiceComponent;
import org.tasks.preferences.Preferences;
+import java.util.List;
+
import javax.inject.Inject;
public class NotificationJob extends Job {
@@ -34,18 +35,11 @@ public class NotificationJob extends Job {
@Override
protected void run() {
if (!preferences.isCurrentlyQuietHours()) {
- for (JobQueueEntry entry : jobQueue.getOverdueJobs()) {
- if (entry instanceof Alarm) {
- Alarm alarm = (Alarm) entry;
- Task task = taskDao.fetch(alarm.getTaskId(), Task.REMINDER_LAST);
- if (task != null && task.getReminderLast() < alarm.getTime()) {
- notifier.triggerTaskNotification(alarm.getTaskId(), ReminderService.TYPE_ALARM);
- }
- } else if (entry instanceof Reminder) {
- Reminder reminder = (Reminder) entry;
- notifier.triggerTaskNotification(reminder.getId(), reminder.getType());
- }
- jobQueue.remove(entry);
+ List extends JobQueueEntry> overdueJobs = jobQueue.getOverdueJobs();
+ notifier.triggerTaskNotifications(overdueJobs);
+ boolean success = jobQueue.remove(overdueJobs);
+ if (BuildConfig.DEBUG && !success) {
+ throw new RuntimeException("Failed to remove jobs from queue");
}
}
}
diff --git a/app/src/main/java/org/tasks/jobs/Reminder.java b/app/src/main/java/org/tasks/jobs/Reminder.java
index 06bc1f54b..43fa55a83 100644
--- a/app/src/main/java/org/tasks/jobs/Reminder.java
+++ b/app/src/main/java/org/tasks/jobs/Reminder.java
@@ -1,5 +1,9 @@
package org.tasks.jobs;
+import org.tasks.notifications.Notification;
+
+import static org.tasks.time.DateTimeUtils.currentTimeMillis;
+
public class Reminder implements JobQueueEntry {
private final long taskId;
private final long time;
@@ -21,6 +25,15 @@ public class Reminder implements JobQueueEntry {
return time;
}
+ @Override
+ public Notification toNotification() {
+ Notification notification = new Notification();
+ notification.taskId = taskId;
+ notification.type = type;
+ notification.timestamp = currentTimeMillis();
+ return notification;
+ }
+
public int getType() {
return type;
}
diff --git a/app/src/main/java/org/tasks/notifications/Notification.java b/app/src/main/java/org/tasks/notifications/Notification.java
new file mode 100644
index 000000000..bcdd5b2b3
--- /dev/null
+++ b/app/src/main/java/org/tasks/notifications/Notification.java
@@ -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 +
+ '}';
+ }
+}
diff --git a/app/src/main/java/org/tasks/notifications/NotificationClearedReceiver.java b/app/src/main/java/org/tasks/notifications/NotificationClearedReceiver.java
new file mode 100644
index 000000000..0d83dd400
--- /dev/null
+++ b/app/src/main/java/org/tasks/notifications/NotificationClearedReceiver.java
@@ -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);
+ }
+}
diff --git a/app/src/main/java/org/tasks/notifications/NotificationDao.java b/app/src/main/java/org/tasks/notifications/NotificationDao.java
new file mode 100644
index 000000000..c4675f80d
--- /dev/null
+++ b/app/src/main/java/org/tasks/notifications/NotificationDao.java
@@ -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 getAll();
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ void insertAll(List notifications);
+
+ @Query("SELECT COUNT(*) FROM notification")
+ int count();
+
+ @Query("DELETE FROM notification WHERE task = :taskId")
+ void delete(long taskId);
+}
diff --git a/app/src/main/java/org/tasks/notifications/NotificationManager.java b/app/src/main/java/org/tasks/notifications/NotificationManager.java
index b91ca77fe..208f6047b 100644
--- a/app/src/main/java/org/tasks/notifications/NotificationManager.java
+++ b/app/src/main/java/org/tasks/notifications/NotificationManager.java
@@ -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 notifications, boolean alert, boolean nonstop, boolean fiveTimes) {
+ appDatabase.notificationDao().insertAll(newArrayList(notifications.keySet()));
+ updateSummary(alert && notifications.size() > 1, nonstop, fiveTimes);
+ ArrayList> entries = newArrayList(notifications.entrySet());
+
+ int last = entries.size() - 1;
+ for (int i = 0; i < last; i++) {
+ Map.Entry entry = entries.get(i);
+ notify(entry.getKey().taskId, entry.getValue(), false, false, false);
+ }
+ Map.Entry 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);
+ }
}
}
}
diff --git a/app/src/main/java/org/tasks/preferences/MiscellaneousPreferences.java b/app/src/main/java/org/tasks/preferences/MiscellaneousPreferences.java
index eff8a3ac2..d7c3de6de 100644
--- a/app/src/main/java/org/tasks/preferences/MiscellaneousPreferences.java
+++ b/app/src/main/java/org/tasks/preferences/MiscellaneousPreferences.java
@@ -12,7 +12,7 @@ import org.tasks.R;
import org.tasks.files.FileExplore;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingPreferenceActivity;
-import org.tasks.scheduling.BackgroundScheduler;
+import org.tasks.scheduling.CalendarNotificationIntentService;
import java.io.File;
@@ -31,7 +31,6 @@ public class MiscellaneousPreferences extends InjectingPreferenceActivity {
@Inject VoiceOutputAssistant voiceOutputAssistant;
@Inject ActivityPermissionRequestor permissionRequestor;
@Inject PermissionChecker permissionChecker;
- @Inject BackgroundScheduler backgroundScheduler;
private CheckBoxPreference calendarReminderPreference;
@@ -113,7 +112,7 @@ public class MiscellaneousPreferences extends InjectingPreferenceActivity {
return true;
}
if (permissionRequestor.requestCalendarPermissions()) {
- backgroundScheduler.scheduleCalendarNotifications();
+ CalendarNotificationIntentService.enqueueWork(this);
return true;
}
return false;
diff --git a/app/src/main/java/org/tasks/preferences/Preferences.java b/app/src/main/java/org/tasks/preferences/Preferences.java
index a29445980..5eab6a2bb 100644
--- a/app/src/main/java/org/tasks/preferences/Preferences.java
+++ b/app/src/main/java/org/tasks/preferences/Preferences.java
@@ -3,10 +3,12 @@ package org.tasks.preferences;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Resources;
+import android.media.RingtoneManager;
+import android.net.Uri;
import android.preference.PreferenceManager;
-import android.support.v4.app.NotificationCompat;
import android.text.TextUtils;
+import com.google.common.base.Strings;
import com.todoroo.astrid.activity.BeastModePreferences;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.core.SortHelper;
@@ -15,7 +17,6 @@ import com.todoroo.astrid.data.TaskAttachment;
import org.tasks.R;
import org.tasks.injection.ForApplication;
-import org.tasks.themes.ThemeCache;
import org.tasks.time.DateTime;
import java.io.File;
@@ -26,7 +27,6 @@ import javax.inject.Inject;
import timber.log.Timber;
import static android.content.SharedPreferences.Editor;
-import static com.todoroo.andlib.utility.AndroidUtilities.atLeastOreo;
public class Preferences {
@@ -38,14 +38,11 @@ public class Preferences {
private final PermissionChecker permissionChecker;
private final SharedPreferences prefs;
private final SharedPreferences publicPrefs;
- private final ThemeCache themeCache;
@Inject
- public Preferences(@ForApplication Context context, PermissionChecker permissionChecker,
- ThemeCache themeCache) {
+ public Preferences(@ForApplication Context context, PermissionChecker permissionChecker) {
this.context = context;
this.permissionChecker = permissionChecker;
- this.themeCache = themeCache;
prefs = PreferenceManager.getDefaultSharedPreferences(context);
publicPrefs = context.getSharedPreferences(AstridApiConstants.PUBLIC_PREFS, Context.MODE_PRIVATE);
}
@@ -88,16 +85,8 @@ public class Preferences {
return time;
}
- public boolean isVibrationEnabled() {
- return atLeastOreo() || getBoolean(R.string.p_rmd_vibrate, true);
- }
-
public boolean isLEDNotificationEnabled() {
- return atLeastOreo() || getBoolean(R.string.p_led_notification, true);
- }
-
- public int getLEDColor() {
- return R.color.led_green;
+ return getBoolean(R.string.p_led_notification, true);
}
public boolean quietHoursEnabled() {
@@ -141,13 +130,15 @@ public class Preferences {
return defaultCalendar != null && !defaultCalendar.equals("-1") && !defaultCalendar.equals("0");
}
- public boolean isTrackingEnabled() {
- return getBoolean(R.string.p_collect_statistics, true);
+ public Uri getRingtone() {
+ String ringtone = getStringValue(R.string.p_rmd_ringtone);
+ return Strings.isNullOrEmpty(ringtone)
+ ? RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
+ : Uri.parse(ringtone);
}
- public int getNotificationPriority() {
- return Math.max(NotificationCompat.PRIORITY_MIN, Math.min(NotificationCompat.PRIORITY_MAX,
- getIntegerFromString(R.string.p_notification_priority, NotificationCompat.PRIORITY_HIGH)));
+ public boolean isTrackingEnabled() {
+ return getBoolean(R.string.p_collect_statistics, true);
}
public String getDefaultCalendar() {
@@ -411,8 +402,8 @@ public class Preferences {
return directory;
}
- public long[] getVibrationPattern() {
- return new long[] {0, 333, 333, 333};
+ public boolean isVibrationEnabled() {
+ return getBoolean(R.string.p_rmd_vibrate, true);
}
public void remove(int resId) {
diff --git a/app/src/main/java/org/tasks/receivers/BootCompletedReceiver.java b/app/src/main/java/org/tasks/receivers/BootCompletedReceiver.java
index abfccd1c6..8593b15c6 100644
--- a/app/src/main/java/org/tasks/receivers/BootCompletedReceiver.java
+++ b/app/src/main/java/org/tasks/receivers/BootCompletedReceiver.java
@@ -5,27 +5,20 @@ import android.content.Intent;
import org.tasks.injection.BroadcastComponent;
import org.tasks.injection.InjectingBroadcastReceiver;
-import org.tasks.scheduling.BackgroundScheduler;
-
-import javax.inject.Inject;
import timber.log.Timber;
public class BootCompletedReceiver extends InjectingBroadcastReceiver {
- @Inject BackgroundScheduler backgroundScheduler;
-
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
- if (!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+ if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
return;
}
Timber.d("onReceive(context, %s)", intent);
-
- backgroundScheduler.scheduleEverything();
}
@Override
diff --git a/app/src/main/java/org/tasks/receivers/MyPackageReplacedReceiver.java b/app/src/main/java/org/tasks/receivers/MyPackageReplacedReceiver.java
index 8a99ae5bc..0c5814fdf 100644
--- a/app/src/main/java/org/tasks/receivers/MyPackageReplacedReceiver.java
+++ b/app/src/main/java/org/tasks/receivers/MyPackageReplacedReceiver.java
@@ -5,27 +5,20 @@ import android.content.Intent;
import org.tasks.injection.BroadcastComponent;
import org.tasks.injection.InjectingBroadcastReceiver;
-import org.tasks.scheduling.BackgroundScheduler;
-
-import javax.inject.Inject;
import timber.log.Timber;
public class MyPackageReplacedReceiver extends InjectingBroadcastReceiver {
- @Inject BackgroundScheduler backgroundScheduler;
-
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
- if (!intent.getAction().equals(Intent.ACTION_MY_PACKAGE_REPLACED)) {
+ if (!Intent.ACTION_MY_PACKAGE_REPLACED.equals(intent.getAction())) {
return;
}
Timber.d("onReceive(context, %s)", intent);
-
- backgroundScheduler.scheduleEverything();
}
@Override
diff --git a/app/src/main/java/org/tasks/scheduling/BackgroundScheduler.java b/app/src/main/java/org/tasks/scheduling/BackgroundScheduler.java
index 02d18031f..0204b5022 100644
--- a/app/src/main/java/org/tasks/scheduling/BackgroundScheduler.java
+++ b/app/src/main/java/org/tasks/scheduling/BackgroundScheduler.java
@@ -2,29 +2,56 @@ package org.tasks.scheduling;
import android.content.Context;
import android.content.Intent;
-import android.support.v4.app.JobIntentService;
+import android.support.annotation.NonNull;
+
+import com.todoroo.andlib.sql.Criterion;
+import com.todoroo.astrid.dao.TaskDao;
+import com.todoroo.astrid.data.Task;
import org.tasks.injection.ForApplication;
+import org.tasks.injection.InjectingJobIntentService;
+import org.tasks.injection.IntentServiceComponent;
import org.tasks.jobs.JobManager;
import javax.inject.Inject;
-public class BackgroundScheduler {
- private final Context context;
+import timber.log.Timber;
+
+import static java.lang.System.currentTimeMillis;
- @Inject
- public BackgroundScheduler(@ForApplication Context context) {
- this.context = context;
+public class BackgroundScheduler extends InjectingJobIntentService {
+
+ public static void enqueueWork(Context context) {
+ BackgroundScheduler.enqueueWork(context, BackgroundScheduler.class, JobManager.JOB_ID_BACKGROUND_SCHEDULER, new Intent());
}
- public void scheduleEverything() {
- JobIntentService.enqueueWork(context, GeofenceSchedulingIntentService.class, JobManager.JOB_ID_GEOFENCE_SCHEDULING, new Intent());
- JobIntentService.enqueueWork(context, SchedulerIntentService.class, JobManager.JOB_ID_SCHEDULER, new Intent());
- JobIntentService.enqueueWork(context, NotificationSchedulerIntentService.class, JobManager.JOB_ID_NOTIFICATION_SCHEDULER, new Intent());
- scheduleCalendarNotifications();
+ @Inject @ForApplication Context context;
+ @Inject TaskDao taskDao;
+ @Inject JobManager jobManager;
+ @Inject RefreshScheduler refreshScheduler;
+
+ @Override
+ protected void onHandleWork(@NonNull Intent intent) {
+ super.onHandleWork(intent);
+
+ Timber.d("onHandleWork(%s)", intent);
+
+ NotificationSchedulerIntentService.enqueueWork(context);
+ CalendarNotificationIntentService.enqueueWork(context);
+ GeofenceSchedulingIntentService.enqueueWork(context);
+
+ 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);
}
- public void scheduleCalendarNotifications() {
- JobIntentService.enqueueWork(context, CalendarNotificationIntentService.class, JobManager.JOB_ID_CALENDAR_NOTIFICATION, new Intent());
+ @Override
+ protected void inject(IntentServiceComponent component) {
+ component.inject(this);
}
}
diff --git a/app/src/main/java/org/tasks/scheduling/CalendarNotificationIntentService.java b/app/src/main/java/org/tasks/scheduling/CalendarNotificationIntentService.java
index 112697ec8..bc982f924 100644
--- a/app/src/main/java/org/tasks/scheduling/CalendarNotificationIntentService.java
+++ b/app/src/main/java/org/tasks/scheduling/CalendarNotificationIntentService.java
@@ -26,10 +26,14 @@ import timber.log.Timber;
public class CalendarNotificationIntentService extends RecurringIntervalIntentService {
+ public static void enqueueWork(Context context) {
+ JobIntentService.enqueueWork(context, CalendarNotificationIntentService.class, JobManager.JOB_ID_CALENDAR_NOTIFICATION, new Intent());
+ }
+
public static class Broadcast extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- JobIntentService.enqueueWork(context, CalendarNotificationIntentService.class, JobManager.JOB_ID_CALENDAR_NOTIFICATION, new Intent());
+ enqueueWork(context);
}
}
diff --git a/app/src/main/java/org/tasks/scheduling/GeofenceSchedulingIntentService.java b/app/src/main/java/org/tasks/scheduling/GeofenceSchedulingIntentService.java
index d8bdeed57..48227fe0c 100644
--- a/app/src/main/java/org/tasks/scheduling/GeofenceSchedulingIntentService.java
+++ b/app/src/main/java/org/tasks/scheduling/GeofenceSchedulingIntentService.java
@@ -1,9 +1,12 @@
package org.tasks.scheduling;
+import android.content.Context;
import android.content.Intent;
+import android.support.v4.app.JobIntentService;
import org.tasks.injection.InjectingJobIntentService;
import org.tasks.injection.IntentServiceComponent;
+import org.tasks.jobs.JobManager;
import org.tasks.location.GeofenceService;
import javax.inject.Inject;
@@ -12,13 +15,17 @@ import timber.log.Timber;
public class GeofenceSchedulingIntentService extends InjectingJobIntentService {
+ public static void enqueueWork(Context context) {
+ JobIntentService.enqueueWork(context, GeofenceSchedulingIntentService.class, JobManager.JOB_ID_GEOFENCE_SCHEDULING, new Intent());
+ }
+
@Inject GeofenceService geofenceService;
@Override
protected void onHandleWork(Intent intent) {
super.onHandleWork(intent);
- Timber.d("onHandleIntent(%s)", intent);
+ Timber.d("onHandleWork(%s)", intent);
geofenceService.cancelGeofences();
geofenceService.setupGeofences();
diff --git a/app/src/main/java/org/tasks/scheduling/NotificationSchedulerIntentService.java b/app/src/main/java/org/tasks/scheduling/NotificationSchedulerIntentService.java
index 631c83428..1f9232773 100644
--- a/app/src/main/java/org/tasks/scheduling/NotificationSchedulerIntentService.java
+++ b/app/src/main/java/org/tasks/scheduling/NotificationSchedulerIntentService.java
@@ -1,13 +1,17 @@
package org.tasks.scheduling;
+import android.content.Context;
import android.content.Intent;
+import android.support.v4.app.JobIntentService;
import com.todoroo.astrid.alarms.AlarmService;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.reminders.ReminderService;
+import org.tasks.Notifier;
import org.tasks.injection.InjectingJobIntentService;
import org.tasks.injection.IntentServiceComponent;
+import org.tasks.jobs.JobManager;
import org.tasks.jobs.JobQueue;
import javax.inject.Inject;
@@ -16,19 +20,25 @@ import timber.log.Timber;
public class NotificationSchedulerIntentService extends InjectingJobIntentService {
+ public static void enqueueWork(Context context) {
+ JobIntentService.enqueueWork(context, NotificationSchedulerIntentService.class, JobManager.JOB_ID_NOTIFICATION_SCHEDULER, new Intent());
+ }
+
@Inject AlarmService alarmService;
@Inject ReminderService reminderService;
@Inject TaskDao taskDao;
@Inject JobQueue jobQueue;
+ @Inject Notifier notifier;
@Override
protected void onHandleWork(Intent intent) {
super.onHandleWork(intent);
- Timber.d("onHandleIntent(%s)", intent);
+ Timber.d("onHandleWork(%s)", intent);
jobQueue.clear();
+ notifier.restoreNotifications();
reminderService.scheduleAllAlarms(taskDao);
alarmService.scheduleAllAlarms();
}
diff --git a/app/src/main/java/org/tasks/scheduling/SchedulerIntentService.java b/app/src/main/java/org/tasks/scheduling/SchedulerIntentService.java
deleted file mode 100644
index faa0e69b5..000000000
--- a/app/src/main/java/org/tasks/scheduling/SchedulerIntentService.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/app/src/main/res/drawable-hdpi/ic_done_all_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_done_all_white_24dp.png
new file mode 100644
index 000000000..8c83ff91c
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_done_all_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_done_all_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_done_all_white_24dp.png
new file mode 100644
index 000000000..6ace20eec
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_done_all_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_done_all_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_done_all_white_24dp.png
new file mode 100644
index 000000000..1f3132a3f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_done_all_white_24dp.png differ
diff --git a/app/src/main/res/drawable/ic_done_all_white_24dp.png b/app/src/main/res/drawable/ic_done_all_white_24dp.png
new file mode 100644
index 000000000..4724d66cb
Binary files /dev/null and b/app/src/main/res/drawable/ic_done_all_white_24dp.png differ
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index a562a572c..7f065c210 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -366,7 +366,6 @@
Paramètres du Widget
Paramètres de l\'en-tête
Paramètres de la ligne
- Fréquence de notification
Permissions requises de Tasks.
Création d\'une nouvelle liste
Suppression de la liste
diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml
index d29d7cd99..becf3564c 100644
--- a/app/src/main/res/values/keys.xml
+++ b/app/src/main/res/values/keys.xml
@@ -195,7 +195,6 @@
show_completed_tasks
reverse_sort
manual_sort
- notification_priority
- @string/TEA_ctrl_when_pref
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2bb867551..153a339de 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -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