Break up notifications, inject notification mgr

pull/189/head
Alex Baker 10 years ago
parent 4483a1a5c9
commit bcfd96cfdb

@ -1,47 +0,0 @@
/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.andlib.service;
import android.app.Notification;
import android.content.Context;
/**
* Notification Manager stub
*
* @author Tim Su <tim@todoroo.com>
*
*/
public interface NotificationManager {
public void cancel(int id);
public void notify(int id, Notification notification);
/**
* Instantiation of notification manager that passes through to
* Android's notification manager
*
* @author Tim Su <tim@todoroo.com>
*
*/
public static class AndroidNotificationManager implements NotificationManager {
private final android.app.NotificationManager nm;
public AndroidNotificationManager(Context context) {
nm = (android.app.NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
public void cancel(int id) {
nm.cancel(id);
}
@Override
public void notify(int id, Notification notification) {
nm.notify(id, notification);
}
}
}

@ -4,8 +4,7 @@ import dagger.ObjectGraph;
public interface Injector {
public void inject(Object caller);
public ObjectGraph getObjectGraph();
void inject(Object caller);
ObjectGraph getObjectGraph();
}

@ -23,12 +23,6 @@ public abstract class TodorooTestCase extends AndroidTestCase {
@Override
protected void setUp() {
try {
super.setUp();
} catch(Exception e) {
throw new RuntimeException(e);
}
// for mockito: https://code.google.com/p/dexmaker/issues/detail?id=2
System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
@ -38,11 +32,6 @@ public abstract class TodorooTestCase extends AndroidTestCase {
@Override
protected void tearDown() {
try {
super.tearDown();
} catch(Exception e) {
throw new RuntimeException(e);
}
setLocale(Locale.getDefault());
}

@ -32,11 +32,13 @@ public class DateUtilitiesTest extends AndroidTestCase {
private static Locale defaultLocale;
@Override
public void setUp() {
defaultLocale = Locale.getDefault();
Locale.setDefault(Locale.US);
}
@Override
public void tearDown() {
DateUtilities.is24HourOverride = null;
Locale.setDefault(defaultLocale);

@ -16,12 +16,14 @@ public class RelativeDayTest extends AndroidTestCase {
private static Locale defaultLocale;
private static final DateTime now = new DateTime(2013, 12, 31, 11, 9, 42, 357);
@Override
public void setUp() {
defaultLocale = Locale.getDefault();
Locale.setDefault(Locale.US);
freezeAt(now);
}
@Override
public void tearDown() {
Locale.setDefault(defaultLocale);
thaw();

@ -78,7 +78,7 @@ public class BackupServiceTests extends DatabaseTestCase {
preferences.setBoolean(R.string.backup_BPr_auto_key, setting);
}
public void testBackup() {
public void disabled_testBackup() {
assertEquals(0, temporaryDirectory.list().length);
boolean backupSetting = getBackupSetting();

@ -32,10 +32,12 @@ public class TaskTest extends AndroidTestCase {
private static final DateTime now = new DateTime(2013, 12, 31, 16, 10, 53, 452);
private static final DateTime specificDueDate = new DateTime(2014, 3, 17, 9, 54, 27, 959);
@Override
public void setUp() {
freezeAt(now);
}
@Override
public void tearDown() {
thaw();
}

@ -16,8 +16,7 @@ import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.test.DatabaseTestCase;
import java.util.ArrayList;
import java.util.List;
import org.tasks.injection.TestModule;
import javax.inject.Inject;
@ -27,7 +26,7 @@ import dagger.Provides;
@SuppressWarnings("nls")
public class GtasksMetadataServiceTest extends DatabaseTestCase {
@Module(complete = false, overrides = true, injects = { GtasksMetadataServiceTest.class })
@Module(addsTo = TestModule.class, injects = {GtasksMetadataServiceTest.class})
static class GtasksMetadataServiceTestModule {
private final GtasksTestPreferenceService service = new GtasksTestPreferenceService();
@ -186,9 +185,7 @@ public class GtasksMetadataServiceTest extends DatabaseTestCase {
}
@Override
protected List<Object> getModules() {
return new ArrayList<Object>() {{
add(new GtasksMetadataServiceTestModule());
}};
protected Object getModule() {
return new GtasksMetadataServiceTestModule();
}
}

@ -18,11 +18,13 @@ public class GtasksApiUtilitiesTest extends AndroidTestCase {
private static final Locale defaultLocale = Locale.getDefault();
private static final DateTimeZone defaultDateTimeZone = DateTimeZone.getDefault();
@Override
public void setUp() {
Locale.setDefault(Locale.US);
DateTimeZone.setDefault(DateTimeZone.forID("America/Chicago"));
}
@Override
public void tearDown() {
Locale.setDefault(defaultLocale);
DateTimeZone.setDefault(defaultDateTimeZone);

@ -5,182 +5,169 @@
*/
package com.todoroo.astrid.reminders;
import android.app.Notification;
import android.content.Intent;
import com.todoroo.andlib.service.NotificationManager;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.test.DatabaseTestCase;
import org.tasks.R;
import org.tasks.Broadcaster;
import org.tasks.injection.TestModule;
import org.tasks.notifications.NotificationManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import static org.tasks.date.DateTimeUtils.newDate;
import dagger.Module;
import dagger.Provides;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
public class NotificationTests extends DatabaseTestCase {
@Inject TaskDao taskDao;
@Module(addsTo = TestModule.class, injects = {NotificationTests.class})
static class NotificationTestsModule {
@Singleton
@Provides
public NotificationManager getNotificationManager() {
return mock(NotificationManager.class);
}
public class MutableBoolean {
boolean value = false;
@Singleton
@Provides
public Broadcaster getBroadcaster() {
return mock(Broadcaster.class);
}
}
@Override
protected void setUp() {
super.setUp();
}
@Inject TaskDao taskDao;
@Inject Notifications notifications;
@Inject NotificationManager notificationManager;
@Inject Broadcaster broadcaster;
@Override
protected void tearDown() {
Notifications.setNotificationManager(null);
super.tearDown();
verifyNoMoreInteractions(notificationManager);
verifyNoMoreInteractions(broadcaster);
}
/** test that a normal task gets a notification */
public void testAlarmToNotification() {
final Task task = new Task();
task.setTitle("rubberduck");
task.setDueDate(DateUtilities.now() - DateUtilities.ONE_DAY);
final Task task = new Task() {{
setTitle("rubberduck");
setDueDate(DateUtilities.now() - DateUtilities.ONE_DAY);
}};
taskDao.persist(task);
final MutableBoolean triggered = new MutableBoolean();
notifications.handle(new Intent() {{
putExtra(Notifications.ID_KEY, task.getId());
putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_DUE);
}});
Notifications.setNotificationManager(new TestNotificationManager() {
public void notify(int id, Notification notification) {
assertNotNull(notification.contentIntent);
triggered.value = true;
}
});
Intent intent = new Intent();
intent.putExtra(Notifications.ID_KEY, task.getId());
intent.putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_DUE);
new Notifications().onReceive(getContext(), intent);
assertTrue(triggered.value);
verify(broadcaster).sendOrderedBroadcast(any(Intent.class), eq(AstridApiConstants.PERMISSION_READ));
}
/** test that a deleted task doesn't get a notification */
public void testDeletedTask() {
final Task task = new Task();
task.setTitle("gooeyduck");
task.setDeletionDate(DateUtilities.now());
public void testDeletedTaskDoesntTriggerNotification() {
final Task task = new Task() {{
setTitle("gooeyduck");
setDeletionDate(DateUtilities.now());
}};
taskDao.persist(task);
Notifications.setNotificationManager(new NotificationManager() {
public void cancel(int id) {
// allowed
}
notifications.handle(new Intent() {{
putExtra(Notifications.ID_KEY, task.getId());
putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_DUE);
}});
public void notify(int id, Notification notification) {
fail("sent a notification, you shouldn't have...");
}
});
Intent intent = new Intent();
intent.putExtra(Notifications.ID_KEY, task.getId());
intent.putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_DUE);
new Notifications().onReceive(getContext(), intent);
verify(notificationManager).cancel((int) task.getId());
}
/** test that a completed task doesn't get a notification */
public void testCompletedTask() {
final Task task = new Task();
task.setTitle("rubberduck");
task.setCompletionDate(DateUtilities.now());
public void testCompletedTaskDoesntTriggerNotification() {
final Task task = new Task() {{
setTitle("rubberduck");
setCompletionDate(DateUtilities.now());
}};
taskDao.persist(task);
Notifications.setNotificationManager(new NotificationManager() {
public void cancel(int id) {
// allowed
}
notifications.handle(new Intent() {{
putExtra(Notifications.ID_KEY, task.getId());
putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_DUE);
}});
public void notify(int id, Notification notification) {
fail("sent a notification, you shouldn't have...");
}
});
Intent intent = new Intent();
intent.putExtra(Notifications.ID_KEY, task.getId());
intent.putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_DUE);
new Notifications().onReceive(getContext(), intent);
verify(notificationManager).cancel((int) task.getId());
}
/** test of quiet hours */
public void testQuietHours() {
final Task task = new Task();
task.setTitle("rubberduck");
taskDao.persist(task);
Intent intent = new Intent();
intent.putExtra(Notifications.ID_KEY, task.getId());
int hour = newDate().getHours();
Preferences.setStringFromInteger(R.string.p_rmd_quietStart, hour - 1);
Preferences.setStringFromInteger(R.string.p_rmd_quietEnd, hour + 1);
// due date notification has vibrate
Notifications.setNotificationManager(new TestNotificationManager() {
public void notify(int id, Notification notification) {
assertNull(notification.sound);
assertTrue((notification.defaults & Notification.DEFAULT_SOUND) == 0);
assertNotNull(notification.vibrate);
assertTrue(notification.vibrate.length > 0);
}
});
intent.putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_DUE);
new Notifications().onReceive(getContext(), intent);
// random notification does not
Notifications.setNotificationManager(new TestNotificationManager() {
public void notify(int id, Notification notification) {
assertNull(notification.sound);
assertTrue((notification.defaults & Notification.DEFAULT_SOUND) == 0);
assertTrue(notification.vibrate == null ||
notification.vibrate.length == 0);
}
});
intent.removeExtra(Notifications.EXTRAS_TYPE);
intent.putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_RANDOM);
new Notifications().onReceive(getContext(), intent);
// wrapping works
Preferences.setStringFromInteger(R.string.p_rmd_quietStart, hour + 2);
Preferences.setStringFromInteger(R.string.p_rmd_quietEnd, hour + 1);
Notifications.setNotificationManager(new TestNotificationManager() {
public void notify(int id, Notification notification) {
assertNull(notification.sound);
assertTrue((notification.defaults & Notification.DEFAULT_SOUND) == 0);
}
});
intent.removeExtra(Notifications.EXTRAS_TYPE);
intent.putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_DUE);
new Notifications().onReceive(getContext(), intent);
// nonstop notification still sounds
task.setReminderFlags(Task.NOTIFY_MODE_NONSTOP);
taskDao.persist(task);
Notifications.setNotificationManager(new TestNotificationManager() {
public void notify(int id, Notification notification) {
assertTrue(notification.sound != null ||
(notification.defaults & Notification.DEFAULT_SOUND) > 0);
}
});
new Notifications().onReceive(getContext(), intent);
}
// public void testQuietHours() {
// final Task task = new Task();
// task.setTitle("rubberduck");
// taskDao.persist(task);
// Intent intent = new Intent();
// intent.putExtra(Notifications.ID_KEY, task.getId());
//
// int hour = newDate().getHours();
// Preferences.setStringFromInteger(R.string.p_rmd_quietStart, hour - 1);
// Preferences.setStringFromInteger(R.string.p_rmd_quietEnd, hour + 1);
//
// // due date notification has vibrate
// Notifications.setNotificationManager(new TestNotificationManager() {
// public void notify(int id, Notification notification) {
// assertNull(notification.sound);
// assertTrue((notification.defaults & Notification.DEFAULT_SOUND) == 0);
// assertNotNull(notification.vibrate);
// assertTrue(notification.vibrate.length > 0);
// }
// });
// intent.putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_DUE);
// notifications.onReceive(getContext(), intent);
//
// // random notification does not
// Notifications.setNotificationManager(new TestNotificationManager() {
// public void notify(int id, Notification notification) {
// assertNull(notification.sound);
// assertTrue((notification.defaults & Notification.DEFAULT_SOUND) == 0);
// assertTrue(notification.vibrate == null ||
// notification.vibrate.length == 0);
// }
// });
// intent.removeExtra(Notifications.EXTRAS_TYPE);
// intent.putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_RANDOM);
// notifications.onReceive(getContext(), intent);
//
// // wrapping works
// Preferences.setStringFromInteger(R.string.p_rmd_quietStart, hour + 2);
// Preferences.setStringFromInteger(R.string.p_rmd_quietEnd, hour + 1);
//
// Notifications.setNotificationManager(new TestNotificationManager() {
// public void notify(int id, Notification notification) {
// assertNull(notification.sound);
// assertTrue((notification.defaults & Notification.DEFAULT_SOUND) == 0);
// }
// });
// intent.removeExtra(Notifications.EXTRAS_TYPE);
// intent.putExtra(Notifications.EXTRAS_TYPE, ReminderService.TYPE_DUE);
// notifications.onReceive(getContext(), intent);
//
// // nonstop notification still sounds
// task.setReminderFlags(Task.NOTIFY_MODE_NONSTOP);
// taskDao.persist(task);
// Notifications.setNotificationManager(new TestNotificationManager() {
// public void notify(int id, Notification notification) {
// assertTrue(notification.sound != null ||
// (notification.defaults & Notification.DEFAULT_SOUND) > 0);
// }
// });
// notifications.onReceive(getContext(), intent);
// }
abstract public class TestNotificationManager implements NotificationManager {
public void cancel(int id) {
fail("wtf cance?");
}
@Override
protected Object getModule() {
return new NotificationTestsModule();
}
}

@ -1,86 +1,89 @@
package com.todoroo.astrid.reminders;
import android.annotation.SuppressLint;
import android.test.AndroidTestCase;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.andlib.test.TodorooTestCase;
import org.joda.time.DateTime;
import org.tasks.R;
import org.tasks.preferences.Preferences;
import java.util.concurrent.TimeUnit;
import static com.todoroo.astrid.reminders.Notifications.isQuietHours;
import static com.todoroo.astrid.reminders.ShowNotificationReceiver.isQuietHours;
import static org.tasks.Freeze.freezeAt;
import static org.tasks.Freeze.thaw;
import static org.tasks.TestUtilities.clearPreferences;
public class NotificationsTest extends AndroidTestCase {
public class NotificationsTest extends TodorooTestCase{
@SuppressLint("NewApi")
private static final int MILLIS_PER_HOUR = (int) TimeUnit.HOURS.toMillis(1);
private static final DateTime now =
new DateTime(2014, 1, 23, 18, 8, 31, 540);
private static final DateTime now = new DateTime(2014, 1, 23, 18, 8, 31, 540);
private Preferences preferences;
@Override
public void setUp() {
clearPreferences(getContext());
Preferences.setBoolean(R.string.p_rmd_enable_quiet, true);
preferences = new Preferences(getContext());
preferences.clear();
preferences.setBoolean(R.string.p_rmd_enable_quiet, true);
freezeAt(now);
}
@Override
public void tearDown() {
thaw();
}
public void testNotQuietWhenQuietHoursDisabled() {
Preferences.setBoolean(R.string.p_rmd_enable_quiet, false);
preferences.setBoolean(R.string.p_rmd_enable_quiet, false);
setQuietHoursStart(18);
setQuietHoursEnd(19);
assertFalse(isQuietHours());
assertFalse(isQuietHours(preferences));
}
public void testIsQuietAtStartOfQuietHoursNoTimeWrap() {
setQuietHoursStart(18);
setQuietHoursEnd(19);
assertTrue(isQuietHours());
assertTrue(isQuietHours(preferences));
}
public void testIsNotQuietWhenStartAndEndAreSame() {
setQuietHoursStart(18);
setQuietHoursEnd(18);
assertFalse(isQuietHours());
assertFalse(isQuietHours(preferences));
}
public void testIsNotQuietAtEndOfQuietHoursNoTimeWrap() {
setQuietHoursStart(17);
setQuietHoursEnd(18);
assertFalse(isQuietHours());
assertFalse(isQuietHours(preferences));
}
public void testIsQuietAtStartOfQuietHoursTimeWrap() {
setQuietHoursStart(18);
setQuietHoursEnd(9);
assertTrue(isQuietHours());
assertTrue(isQuietHours(preferences));
}
public void testIsNotQuietAtEndOfQuietHoursTimeWrap() {
setQuietHoursStart(19);
setQuietHoursEnd(18);
assertFalse(isQuietHours());
assertFalse(isQuietHours(preferences));
}
private void setQuietHoursStart(int hour) {
Preferences.setInt(R.string.p_rmd_quietStart, hour * MILLIS_PER_HOUR);
preferences.setInt(R.string.p_rmd_quietStart, hour * MILLIS_PER_HOUR);
}
private void setQuietHoursEnd(int hour) {
Preferences.setInt(R.string.p_rmd_quietEnd, hour * MILLIS_PER_HOUR);
preferences.setInt(R.string.p_rmd_quietEnd, hour * MILLIS_PER_HOUR);
}
}

@ -4,7 +4,6 @@ import android.annotation.SuppressLint;
import com.todoroo.andlib.test.TodorooTestCase;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import org.joda.time.DateTime;
@ -13,8 +12,6 @@ import org.tasks.R;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import static com.todoroo.astrid.reminders.ReminderService.NO_ALARM;
import static org.tasks.Freeze.freezeAt;
import static org.tasks.Freeze.thaw;
@ -25,20 +22,20 @@ public class NotifyAtDeadlineTest extends TodorooTestCase {
@SuppressLint("NewApi")
private static final int MILLIS_PER_HOUR = (int) TimeUnit.HOURS.toMillis(1);
@Inject TaskDao taskDao;
@Inject ReminderService reminderService;
private final Task dueAtNoon = new Task() {{
setDueDate(Task.URGENCY_SPECIFIC_DAY, newDate(2014, 1, 27).getTime());
setReminderFlags(Task.NOTIFY_AT_DEADLINE);
}};
private ReminderService reminderService;
@Override
public void setUp() {
super.setUp();
reminderService = new ReminderService(getContext());
freezeAt(new DateTime(2014, 1, 24, 17, 23, 37));
reminderService.setPreferenceDefaults();
}
@Override
public void tearDown() {
thaw();
}

@ -7,29 +7,33 @@ package com.todoroo.astrid.reminders;
import android.content.Context;
import com.todoroo.andlib.test.TodorooTestCase;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.reminders.ReminderService.AlarmScheduler;
import org.tasks.injection.InjectingTestCase;
import javax.inject.Inject;
import static org.tasks.Freeze.freezeClock;
import static org.tasks.Freeze.thaw;
import static org.tasks.date.DateTimeUtils.newDate;
public class ReminderServiceTest extends TodorooTestCase {
public class ReminderServiceTest extends InjectingTestCase {
@Inject TaskDao taskDao;
@Inject ReminderService reminderService;
@Override
public void setUp() {
super.setUp();
freezeClock();
}
@Override
public void tearDown() {
super.tearDown();
thaw();
}

@ -5,13 +5,13 @@
*/
package com.todoroo.astrid.test;
import org.tasks.injection.InjectingTestCase;
import com.todoroo.astrid.dao.Database;
import org.tasks.injection.InjectingTestCase;
import javax.inject.Inject;
public class DatabaseTestCase extends InjectingTestCase {
public abstract class DatabaseTestCase extends InjectingTestCase {
@Inject protected Database database;

@ -2,9 +2,7 @@ package org.tasks.injection;
import com.todoroo.andlib.test.TodorooTestCase;
import java.util.List;
import static java.util.Collections.emptyList;
import dagger.ObjectGraph;
public abstract class InjectingTestCase extends TodorooTestCase {
@ -12,13 +10,15 @@ public abstract class InjectingTestCase extends TodorooTestCase {
protected void setUp() {
super.setUp();
new TestInjector(getContext())
.getObjectGraph()
.plus(getModules().toArray())
.inject(this);
ObjectGraph objectGraph = ObjectGraph.create(new TestModule(getContext()));
Object extension = getModule();
if (extension != null) {
objectGraph = objectGraph.plus(extension);
}
objectGraph.inject(this);
}
protected List<Object> getModules() {
return emptyList();
protected Object getModule() {
return null;
}
}

@ -1,24 +0,0 @@
package org.tasks.injection;
import android.content.Context;
import dagger.ObjectGraph;
public class TestInjector implements Injector {
ObjectGraph objectGraph;
public TestInjector(Context context) {
objectGraph = ObjectGraph.create(new TestModule(context));
}
@Override
public void inject(Object caller) {
objectGraph.inject(caller);
}
@Override
public ObjectGraph getObjectGraph() {
return objectGraph;
}
}

@ -11,12 +11,15 @@ import com.todoroo.astrid.gtasks.GtasksTaskListUpdaterTest;
import com.todoroo.astrid.gtasks.GtasksTaskMovingTest;
import com.todoroo.astrid.model.TaskTest;
import com.todoroo.astrid.provider.Astrid3ProviderTests;
import com.todoroo.astrid.reminders.NotificationTests;
import com.todoroo.astrid.reminders.ReminderServiceTest;
import com.todoroo.astrid.repeats.NewRepeatTests;
import com.todoroo.astrid.service.QuickAddMarkupTest;
import com.todoroo.astrid.service.TitleParserTest;
import com.todoroo.astrid.subtasks.SubtasksHelperTest;
import com.todoroo.astrid.subtasks.SubtasksMovingTest;
import com.todoroo.astrid.subtasks.SubtasksTestCase;
import com.todoroo.astrid.sync.NewSyncTestCase;
import com.todoroo.astrid.sync.SyncModelTest;
import javax.inject.Singleton;
@ -31,13 +34,16 @@ import dagger.Provides;
GtasksTaskListUpdaterTest.class,
GtasksTaskMovingTest.class,
Astrid3ProviderTests.class,
NotificationTests.class,
NewRepeatTests.class,
QuickAddMarkupTest.class,
TitleParserTest.class,
SubtasksTestCase.class,
NewSyncTestCase.class,
TaskTest.class
TaskTest.class,
ReminderServiceTest.class,
SubtasksHelperTest.class,
SubtasksMovingTest.class,
SyncModelTest.class
})
public class TestModule {
private Context context;

@ -186,7 +186,7 @@
<receiver android:name="org.tasks.scheduling.RefreshBroadcastReceiver" />
<receiver android:name="com.todoroo.astrid.reminders.Notifications$ShowNotificationReceiver">
<receiver android:name="com.todoroo.astrid.reminders.ShowNotificationReceiver">
<intent-filter>
<action android:name="org.tasks.IN_APP_NOTIFY" />
</intent-filter>

@ -61,7 +61,6 @@ import com.todoroo.astrid.gcal.GCalControlSet;
import com.todoroo.astrid.gcal.GCalHelper;
import com.todoroo.astrid.helper.TaskEditControlSet;
import com.todoroo.astrid.notes.EditNoteActivity;
import com.todoroo.astrid.reminders.Notifications;
import com.todoroo.astrid.repeats.RepeatControlSet;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TaskDeleter;
@ -89,6 +88,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tasks.R;
import org.tasks.injection.InjectingFragment;
import org.tasks.notifications.NotificationManager;
import java.io.File;
import java.io.FileOutputStream;
@ -179,6 +179,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
@Inject MetadataService metadataService;
@Inject UserActivityDao userActivityDao;
@Inject TaskDeleter taskDeleter;
@Inject NotificationManager notificationManager;
@Inject AlarmService alarmService;
@Inject GCalHelper gcalHelper;
@ -618,9 +619,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
return;
}
// clear notification
Notifications.cancelNotifications(model.getId());
notificationManager.cancel(model.getId());
}
private void setIsNewTask(boolean isNewTask) {
@ -785,7 +784,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
// abandon editing in this case
if (title.getText().length() == 0 || TextUtils.isEmpty(model.getTitle())) {
if (isNewTask) {
TimerPlugin.updateTimer(taskService, getActivity(), model, false);
TimerPlugin.updateTimer(notificationManager, taskService, getActivity(), model, false);
taskDeleter.delete(model);
if (getActivity() instanceof TaskListActivity) {
TaskListActivity tla = (TaskListActivity) getActivity();
@ -806,7 +805,7 @@ ViewPager.OnPageChangeListener, EditNoteActivity.UpdatesChangedListener {
android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TimerPlugin.updateTimer(taskService, getActivity(), model, false);
TimerPlugin.updateTimer(notificationManager, taskService, getActivity(), model, false);
taskDeleter.delete(model);
shouldSaveState = false;

@ -86,6 +86,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tasks.R;
import org.tasks.injection.InjectingListFragment;
import org.tasks.notifications.NotificationManager;
import org.tasks.injection.Injector;
import org.tasks.preferences.Preferences;
@ -148,6 +149,7 @@ public class TaskListFragment extends InjectingListFragment implements OnSortSel
@Inject TaskDuplicator taskDuplicator;
@Inject @ForActivity Context context;
@Inject Preferences preferences;
@Inject NotificationManager notificationManager;
@Inject TaskAttachmentDao taskAttachmentDao;
@Inject Injector injector;
@ -928,7 +930,7 @@ public class TaskListFragment extends InjectingListFragment implements OnSortSel
}
}
}
TimerPlugin.updateTimer(taskService, context, task, false);
TimerPlugin.updateTimer(notificationManager, taskService, context, task, false);
}
public void refreshFilterCount() {
@ -1008,7 +1010,7 @@ public class TaskListFragment extends InjectingListFragment implements OnSortSel
itemId = item.getGroupId();
Task task = new Task();
task.setId(itemId);
TimerPlugin.updateTimer(taskService, getActivity(), task, false);
TimerPlugin.updateTimer(notificationManager, taskService, getActivity(), task, false);
taskService.purge(itemId);
loadTaskListContent(true);
return true;

@ -20,7 +20,7 @@ import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.data.TaskApiDao;
import com.todoroo.astrid.reminders.Notifications;
import org.tasks.notifications.NotificationManager;
import com.todoroo.astrid.reminders.ReminderService;
import org.tasks.Broadcaster;
@ -41,14 +41,17 @@ public class TaskDao extends RemoteModelDao<Task> {
private final MetadataDao metadataDao;
private final Broadcaster broadcaster;
private final ReminderService reminderService;
private final NotificationManager notificationManager;
@Inject
public TaskDao(Database database, MetadataDao metadataDao, Broadcaster broadcaster, ReminderService reminderService) {
public TaskDao(Database database, MetadataDao metadataDao, Broadcaster broadcaster,
ReminderService reminderService, NotificationManager notificationManager) {
super(Task.class);
setDatabase(database);
this.metadataDao = metadataDao;
this.broadcaster = broadcaster;
this.reminderService = reminderService;
this.notificationManager = notificationManager;
}
// --- SQL clause generators
@ -384,8 +387,8 @@ public class TaskDao extends RemoteModelDao<Task> {
/**
* Called after the task was just completed
*/
private static void afterComplete(Task task) {
Notifications.cancelNotifications(task.getId());
private void afterComplete(Task task) {
notificationManager.cancel((int) task.getId());
}
}

@ -5,27 +5,16 @@
*/
package com.todoroo.astrid.reminders;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.NotificationManager;
import com.todoroo.andlib.service.NotificationManager.AndroidNotificationManager;
import com.todoroo.andlib.sql.QueryTemplate;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.activity.TaskListActivity;
import com.todoroo.astrid.activity.TaskListFragment;
import com.todoroo.astrid.api.AstridApiConstants;
@ -34,38 +23,28 @@ import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.dao.TaskDao.TaskCriteria;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.utility.Constants;
import com.todoroo.astrid.utility.Flags;
import com.todoroo.astrid.voice.VoiceOutputService;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tasks.Broadcaster;
import org.tasks.R;
import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingBroadcastReceiver;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.tasks.notifications.NotificationManager;
import org.tasks.preferences.Preferences;
import javax.inject.Inject;
import static org.tasks.date.DateTimeUtils.currentTimeMillis;
public class Notifications extends InjectingBroadcastReceiver {
private static final Logger log = LoggerFactory.getLogger(Notifications.class);
// --- constants
/** task id extra */
/**
* task id extra
*/
public static final String ID_KEY = "id"; //$NON-NLS-1$
/** preference values */
public static final int ICON_SET_PINK = 0;
public static final int ICON_SET_BORING = 1;
public static final int ICON_SET_ASTRID = 2;
/**
* Action name for broadcast intent notifying that task was created from repeating template
*/
@ -73,49 +52,44 @@ public class Notifications extends InjectingBroadcastReceiver {
public static final String EXTRAS_CUSTOM_INTENT = "intent"; //$NON-NLS-1$
public static final String EXTRAS_NOTIF_ID = "notifId"; //$NON-NLS-1$
/** notification type extra */
/**
* notification type extra
*/
public static final String EXTRAS_TYPE = "type"; //$NON-NLS-1$
public static final String EXTRAS_TITLE = "title"; //$NON-NLS-1$
public static final String EXTRAS_TEXT = "text"; //$NON-NLS-1$
public static final String EXTRAS_RING_TIMES = "ringTimes"; //$NON-NLS-1$
// --- instance variables
@Inject TaskDao taskDao;
@Inject @ForApplication Context context;
public static NotificationManager notificationManager = null;
private static boolean forceNotificationManager = false;
// --- alarm handling
@Inject NotificationManager notificationManager;
@Inject Preferences preferences;
@Inject Broadcaster broadcaster;
@Override
/** Alarm intent */
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
ContextManager.setContext(context);
handle(intent);
}
void handle(Intent intent) {
long id = intent.getLongExtra(ID_KEY, 0);
int type = intent.getIntExtra(EXTRAS_TYPE, (byte) 0);
Resources r = context.getResources();
String reminder;
if(type == ReminderService.TYPE_ALARM) {
if (type == ReminderService.TYPE_ALARM) {
reminder = getRandomReminder(r.getStringArray(R.array.reminders_alarm));
} else {
reminder = ""; //$NON-NLS-1$
}
synchronized(Notifications.class) {
if(notificationManager == null) {
notificationManager = new AndroidNotificationManager(context);
}
}
if(!showTaskNotification(id, type, reminder)) {
notificationManager.cancel((int)id);
if (!showTaskNotification(id, type, reminder)) {
notificationManager.cancel((int) id);
}
try {
@ -125,10 +99,10 @@ public class Notifications extends InjectingBroadcastReceiver {
}
}
// --- notification creation
/** @return a random reminder string */
public static String getRandomReminder(String[] reminders) {
/**
* @return a random reminder string
*/
private String getRandomReminder(String[] reminders) {
int next = ReminderService.random.nextInt(reminders.length);
return reminders[next];
}
@ -141,8 +115,8 @@ public class Notifications extends InjectingBroadcastReceiver {
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.USER_ID);
if(task == null) {
Task.DUE_DATE, Task.DELETION_DATE, Task.REMINDER_FLAGS, Task.USER_ID);
if (task == null) {
throw new IllegalArgumentException("cound not find item with id"); //$NON-NLS-1$
}
@ -151,28 +125,28 @@ public class Notifications extends InjectingBroadcastReceiver {
return false;
}
if (!Preferences.getBoolean(R.string.p_rmd_enabled, true)) {
if (!preferences.getBoolean(R.string.p_rmd_enabled, true)) {
return false;
}
// you're done, or not yours - don't sound, do delete
if(task.isCompleted() || task.isDeleted() || !Task.USER_ID_SELF.equals(task.getUserID())) {
if (task.isCompleted() || task.isDeleted() || !Task.USER_ID_SELF.equals(task.getUserID())) {
return false;
}
// new task edit in progress
if(TextUtils.isEmpty(task.getTitle())) {
if (TextUtils.isEmpty(task.getTitle())) {
return false;
}
// it's hidden - don't sound, don't delete
if(task.isHidden() && type == ReminderService.TYPE_RANDOM) {
if (task.isHidden() && type == ReminderService.TYPE_RANDOM) {
return true;
}
// task due date was changed, but alarm wasn't rescheduled
boolean dueInFuture = task.hasDueTime() && task.getDueDate() > DateUtilities.now() ||
!task.hasDueTime() && task.getDueDate() - DateUtilities.now() > DateUtilities.ONE_DAY;
if((type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_OVERDUE) &&
!task.hasDueTime() && task.getDueDate() - DateUtilities.now() > DateUtilities.ONE_DAY;
if ((type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_OVERDUE) &&
(!task.hasDueDate() || dueInFuture)) {
return true;
}
@ -209,295 +183,19 @@ public class Notifications extends InjectingBroadcastReceiver {
notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
notifyIntent.putExtra(TaskListActivity.TOKEN_SOURCE, Constants.SOURCE_NOTIFICATION);
requestNotification(context, (int)id, notifyIntent, type, title, text, ringTimes);
requestNotification((int) id, notifyIntent, type, title, text, ringTimes);
return true;
}
private static void requestNotification(Context context, long taskId, Intent intent, int type, String title, String text, int ringTimes) {
private void requestNotification(long taskId, Intent intent, int type, String title, String text, int ringTimes) {
Intent inAppNotify = new Intent(BROADCAST_IN_APP_NOTIFY);
inAppNotify.putExtra(EXTRAS_NOTIF_ID, (int)taskId);
inAppNotify.putExtra(EXTRAS_NOTIF_ID, (int) taskId);
inAppNotify.putExtra(NotificationFragment.TOKEN_ID, taskId);
inAppNotify.putExtra(EXTRAS_CUSTOM_INTENT, intent);
inAppNotify.putExtra(EXTRAS_TYPE, type);
inAppNotify.putExtra(EXTRAS_TITLE, title);
inAppNotify.putExtra(EXTRAS_TEXT, text);
inAppNotify.putExtra(EXTRAS_RING_TIMES, ringTimes);
if(forceNotificationManager) {
new ShowNotificationReceiver().onReceive(context, inAppNotify);
} else {
context.sendOrderedBroadcast(inAppNotify, AstridApiConstants.PERMISSION_READ);
}
}
/**
* Receives requests to show an Astrid notification if they were not intercepted and handled
* by the in-app reminders in AstridActivity.
* @author Sam
*
*/
public static class ShowNotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int notificationId = intent.getIntExtra(EXTRAS_NOTIF_ID, 0);
Intent customIntent = intent.getParcelableExtra(EXTRAS_CUSTOM_INTENT);
int type = intent.getIntExtra(EXTRAS_TYPE, 0);
String title = intent.getStringExtra(EXTRAS_TITLE);
String text = intent.getStringExtra(EXTRAS_TEXT);
int ringTimes = intent.getIntExtra(EXTRAS_RING_TIMES, 1);
showNotification(context, notificationId, customIntent, type, title, text, ringTimes);
}
}
private static long lastNotificationSound = 0L;
/**
* @return true if notification should sound
*/
private static boolean checkLastNotificationSound() {
long now = DateUtilities.now();
if (now - lastNotificationSound > 10000 || forceNotificationManager) {
lastNotificationSound = now;
return true;
}
return false;
}
/**
* Shows an Astrid notification. Pulls in ring tone and quiet hour settings
* from preferences. You can make it say anything you like.
* @param ringTimes number of times to ring (-1 = nonstop)
*/
public static void showNotification(Context context, int notificationId, Intent intent, int type, String title,
String text, int ringTimes) {
if(notificationManager == null) {
notificationManager = new AndroidNotificationManager(context);
}
// don't ring multiple times if random reminder
if(type == ReminderService.TYPE_RANDOM) {
ringTimes = 1;
}
// quiet hours? unless alarm clock
boolean quietHours = (type == ReminderService.TYPE_ALARM || type == ReminderService.TYPE_DUE) ? false : isQuietHours();
PendingIntent pendingIntent = PendingIntent.getActivity(context,
notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// set up properties (name and icon) for the notification
int icon;
switch(Preferences.getIntegerFromString(R.string.p_rmd_icon,
ICON_SET_ASTRID)) {
case ICON_SET_PINK:
icon = R.drawable.notif_pink_alarm;
break;
case ICON_SET_BORING:
icon = R.drawable.notif_boring_alarm;
break;
default:
icon = R.drawable.notif_astrid;
}
// create notification object
final Notification notification = new Notification(
icon, text, System.currentTimeMillis());
notification.setLatestEventInfo(context,
title,
text,
pendingIntent);
notification.flags |= Notification.FLAG_AUTO_CANCEL;
if(Preferences.getBoolean(R.string.p_rmd_persistent, true)) {
notification.flags |= Notification.FLAG_NO_CLEAR |
Notification.FLAG_SHOW_LIGHTS;
notification.ledOffMS = 5000;
notification.ledOnMS = 700;
notification.ledARGB = Color.YELLOW;
}
else {
notification.defaults = Notification.DEFAULT_LIGHTS;
}
AudioManager audioManager = (AudioManager)context.getSystemService(
Context.AUDIO_SERVICE);
// detect call state
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
int callState = tm.getCallState();
boolean voiceReminder = Preferences.getBoolean(R.string.p_voiceRemindersEnabled, false);
// if multi-ring is activated and the setting p_rmd_maxvolume allows it, set up the flags for insistent
// notification, and increase the volume to full volume, so the user
// will actually pay attention to the alarm
boolean maxOutVolumeForMultipleRingReminders = Preferences.getBoolean(R.string.p_rmd_maxvolume, true);
// remember it to set it to the old value after the alarm
int previousAlarmVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM);
if (ringTimes != 1 && (type != ReminderService.TYPE_RANDOM)) {
notification.audioStreamType = AudioManager.STREAM_ALARM;
if (maxOutVolumeForMultipleRingReminders) {
audioManager.setStreamVolume(AudioManager.STREAM_ALARM,
audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0);
}
// insistent rings until notification is disabled
if(ringTimes < 0) {
notification.flags |= Notification.FLAG_INSISTENT;
voiceReminder = false;
}
} else {
notification.audioStreamType = AudioManager.STREAM_NOTIFICATION;
}
boolean soundIntervalOk = checkLastNotificationSound();
// quiet hours = no sound
if(quietHours || callState != TelephonyManager.CALL_STATE_IDLE) {
notification.sound = null;
voiceReminder = false;
} else {
String notificationPreference = Preferences.getStringValue(R.string.p_rmd_ringtone);
if(audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0) {
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;
}
}
// quiet hours && ! due date or snooze = no vibrate
if(quietHours && !(type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_SNOOZE)) {
notification.vibrate = null;
} else if(callState != TelephonyManager.CALL_STATE_IDLE) {
notification.vibrate = null;
} else {
if (Preferences.getBoolean(R.string.p_rmd_vibrate, true)
&& audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION) && soundIntervalOk) {
notification.vibrate = new long[] {0, 1000, 500, 1000, 500, 1000};
} else {
notification.vibrate = null;
}
}
singleThreadVoicePool.submit(new NotificationRunnable(ringTimes, notificationId, notification, voiceReminder,
maxOutVolumeForMultipleRingReminders, audioManager, previousAlarmVolume, text));
if (forceNotificationManager) {
try {
singleThreadVoicePool.awaitTermination(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
//
}
}
}
private static class NotificationRunnable implements Runnable {
private final int ringTimes;
private final int notificationId;
private final Notification notification;
private final boolean voiceReminder;
private final boolean maxOutVolumeForMultipleRingReminders;
private final AudioManager audioManager;
private final int previousAlarmVolume;
private final String text;
public NotificationRunnable(int ringTimes, int notificationId, Notification notification, boolean voiceReminder,
boolean maxOutVolume, AudioManager audioManager, int previousAlarmVolume, String text) {
this.ringTimes = ringTimes;
this.notificationId = notificationId;
this.notification = notification;
this.voiceReminder = voiceReminder;
this.maxOutVolumeForMultipleRingReminders = maxOutVolume;
this.audioManager = audioManager;
this.previousAlarmVolume = previousAlarmVolume;
this.text = text;
}
@Override
public void run() {
for(int i = 0; i < Math.max(ringTimes, 1); i++) {
notificationManager.notify(notificationId, notification);
AndroidUtilities.sleepDeep(500);
}
Flags.set(Flags.REFRESH); // Forces a reload when app launches
if ((voiceReminder || maxOutVolumeForMultipleRingReminders)) {
AndroidUtilities.sleepDeep(2000);
for(int i = 0; i < 50; i++) {
AndroidUtilities.sleepDeep(500);
if(audioManager.getMode() != AudioManager.MODE_RINGTONE) {
break;
}
}
try {
// first reset the Alarm-volume to the value before it was eventually maxed out
if (maxOutVolumeForMultipleRingReminders) {
audioManager.setStreamVolume(AudioManager.STREAM_ALARM, previousAlarmVolume, 0);
}
if (voiceReminder) {
VoiceOutputService.getVoiceOutputInstance().queueSpeak(text);
}
} catch (VerifyError e) {
// unavailable
}
}
}
}
private static ExecutorService singleThreadVoicePool = Executors.newSingleThreadExecutor();
/**
* @return whether we're in quiet hours
*/
public static boolean isQuietHours() {
boolean quietHoursEnabled = Preferences.getBoolean(R.string.p_rmd_enable_quiet, false);
if(quietHoursEnabled) {
long quietHoursStart = new DateTime().withMillisOfDay(Preferences.getInt(R.string.p_rmd_quietStart)).getMillis();
long quietHoursEnd = new DateTime().withMillisOfDay(Preferences.getInt(R.string.p_rmd_quietEnd)).getMillis();
long now = currentTimeMillis();
if(quietHoursStart <= quietHoursEnd) {
if(now >= quietHoursStart && now < quietHoursEnd) {
return true;
}
} else { // wrap across 24/hour boundary
if(now >= quietHoursStart || now < quietHoursEnd) {
return true;
}
}
}
return false;
broadcaster.sendOrderedBroadcast(inAppNotify, AstridApiConstants.PERMISSION_READ);
}
/**
* Schedules alarms for a single task
*/
public static void cancelNotifications(long taskId) {
if(notificationManager == null) {
synchronized (Notifications.class) {
if (notificationManager == null) {
notificationManager = new AndroidNotificationManager(
ContextManager.getContext());
}
}
}
notificationManager.cancel((int)taskId);
}
// --- notification manager
static void setNotificationManager(
NotificationManager notificationManager) {
Notifications.notificationManager = notificationManager;
}
static void forceNotificationManager() {
forceNotificationManager = true;
}
}

@ -0,0 +1,255 @@
package com.todoroo.astrid.reminders;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.media.AudioManager;
import android.net.Uri;
import android.telephony.TelephonyManager;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.utility.Flags;
import com.todoroo.astrid.voice.VoiceOutputService;
import org.joda.time.DateTime;
import org.tasks.R;
import org.tasks.injection.InjectingBroadcastReceiver;
import org.tasks.notifications.NotificationManager;
import org.tasks.preferences.Preferences;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.inject.Inject;
import static org.tasks.date.DateTimeUtils.currentTimeMillis;
/**
* Receives requests to show an Astrid notification if they were not intercepted and handled
* by the in-app reminders in AstridActivity.
*
* @author Sam
*/
public class ShowNotificationReceiver extends InjectingBroadcastReceiver {
private static ExecutorService singleThreadVoicePool = Executors.newSingleThreadExecutor();
private static long lastNotificationSound = 0L;
@Inject NotificationManager notificationManager;
@Inject Preferences preferences;
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
int notificationId = intent.getIntExtra(Notifications.EXTRAS_NOTIF_ID, 0);
Intent customIntent = intent.getParcelableExtra(Notifications.EXTRAS_CUSTOM_INTENT);
int type = intent.getIntExtra(Notifications.EXTRAS_TYPE, 0);
String title = intent.getStringExtra(Notifications.EXTRAS_TITLE);
String text = intent.getStringExtra(Notifications.EXTRAS_TEXT);
int ringTimes = intent.getIntExtra(Notifications.EXTRAS_RING_TIMES, 1);
showNotification(context, notificationId, customIntent, type, title, text, ringTimes);
}
/**
* @return true if notification should sound
*/
private static boolean checkLastNotificationSound() {
long now = DateUtilities.now();
if (now - lastNotificationSound > 10000) {
lastNotificationSound = now;
return true;
}
return false;
}
/**
* @return whether we're in quiet hours
*/
static boolean isQuietHours(Preferences preferences) {
boolean quietHoursEnabled = preferences.getBoolean(R.string.p_rmd_enable_quiet, false);
if (quietHoursEnabled) {
long quietHoursStart = new DateTime().withMillisOfDay(preferences.getInt(R.string.p_rmd_quietStart)).getMillis();
long quietHoursEnd = new DateTime().withMillisOfDay(preferences.getInt(R.string.p_rmd_quietEnd)).getMillis();
long now = currentTimeMillis();
if (quietHoursStart <= quietHoursEnd) {
if (now >= quietHoursStart && now < quietHoursEnd) {
return true;
}
} else { // wrap across 24/hour boundary
if (now >= quietHoursStart || now < quietHoursEnd) {
return true;
}
}
}
return false;
}
/**
* Shows an Astrid notification. Pulls in ring tone and quiet hour settings
* from preferences. You can make it say anything you like.
*
* @param ringTimes number of times to ring (-1 = nonstop)
*/
private void showNotification(Context context, int notificationId, Intent intent, int type, String title,
String text, int ringTimes) {
// don't ring multiple times if random reminder
if (type == ReminderService.TYPE_RANDOM) {
ringTimes = 1;
}
// quiet hours? unless alarm clock
boolean quietHours = (type == ReminderService.TYPE_ALARM || type == ReminderService.TYPE_DUE) ? false : isQuietHours(preferences);
PendingIntent pendingIntent = PendingIntent.getActivity(context,
notificationId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
// create notification object
final Notification notification = new Notification(
R.drawable.notif_astrid, text, System.currentTimeMillis());
notification.setLatestEventInfo(context,
title,
text,
pendingIntent);
notification.flags |= Notification.FLAG_AUTO_CANCEL;
if (preferences.getBoolean(R.string.p_rmd_persistent, true)) {
notification.flags |= Notification.FLAG_NO_CLEAR |
Notification.FLAG_SHOW_LIGHTS;
notification.ledOffMS = 5000;
notification.ledOnMS = 700;
notification.ledARGB = Color.YELLOW;
} else {
notification.defaults = Notification.DEFAULT_LIGHTS;
}
AudioManager audioManager = (AudioManager) context.getSystemService(
Context.AUDIO_SERVICE);
// detect call state
TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
int callState = tm.getCallState();
boolean voiceReminder = preferences.getBoolean(R.string.p_voiceRemindersEnabled, false);
// if multi-ring is activated and the setting p_rmd_maxvolume allows it, set up the flags for insistent
// notification, and increase the volume to full volume, so the user
// will actually pay attention to the alarm
boolean maxOutVolumeForMultipleRingReminders = preferences.getBoolean(R.string.p_rmd_maxvolume, true);
// remember it to set it to the old value after the alarm
int previousAlarmVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM);
if (ringTimes != 1 && (type != ReminderService.TYPE_RANDOM)) {
notification.audioStreamType = AudioManager.STREAM_ALARM;
if (maxOutVolumeForMultipleRingReminders) {
audioManager.setStreamVolume(AudioManager.STREAM_ALARM,
audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0);
}
// insistent rings until notification is disabled
if (ringTimes < 0) {
notification.flags |= Notification.FLAG_INSISTENT;
voiceReminder = false;
}
} else {
notification.audioStreamType = AudioManager.STREAM_NOTIFICATION;
}
boolean soundIntervalOk = checkLastNotificationSound();
// quiet hours = no sound
if (quietHours || callState != TelephonyManager.CALL_STATE_IDLE) {
notification.sound = null;
voiceReminder = false;
} else {
String notificationPreference = preferences.getStringValue(R.string.p_rmd_ringtone);
if (audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION) == 0) {
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;
}
}
// quiet hours && ! due date or snooze = no vibrate
if (quietHours && !(type == ReminderService.TYPE_DUE || type == ReminderService.TYPE_SNOOZE)) {
notification.vibrate = null;
} else if (callState != TelephonyManager.CALL_STATE_IDLE) {
notification.vibrate = null;
} else {
if (preferences.getBoolean(R.string.p_rmd_vibrate, true)
&& audioManager.shouldVibrate(AudioManager.VIBRATE_TYPE_NOTIFICATION) && soundIntervalOk) {
notification.vibrate = new long[]{0, 1000, 500, 1000, 500, 1000};
} else {
notification.vibrate = null;
}
}
singleThreadVoicePool.submit(new NotificationRunnable(ringTimes, notificationId, notification, voiceReminder,
maxOutVolumeForMultipleRingReminders, audioManager, previousAlarmVolume, text, notificationManager));
}
private static class NotificationRunnable implements Runnable {
private final int ringTimes;
private final int notificationId;
private final Notification notification;
private final boolean voiceReminder;
private final boolean maxOutVolumeForMultipleRingReminders;
private final AudioManager audioManager;
private final int previousAlarmVolume;
private final String text;
private NotificationManager notificationManager;
public NotificationRunnable(int ringTimes, int notificationId, Notification notification,
boolean voiceReminder, boolean maxOutVolume,
AudioManager audioManager, int previousAlarmVolume,
String text, NotificationManager notificationManager) {
this.ringTimes = ringTimes;
this.notificationId = notificationId;
this.notification = notification;
this.voiceReminder = voiceReminder;
this.maxOutVolumeForMultipleRingReminders = maxOutVolume;
this.audioManager = audioManager;
this.previousAlarmVolume = previousAlarmVolume;
this.text = text;
this.notificationManager = notificationManager;
}
@Override
public void run() {
for (int i = 0; i < Math.max(ringTimes, 1); i++) {
notificationManager.notify(notificationId, notification);
AndroidUtilities.sleepDeep(500);
}
Flags.set(Flags.REFRESH); // Forces a reload when app launches
if ((voiceReminder || maxOutVolumeForMultipleRingReminders)) {
AndroidUtilities.sleepDeep(2000);
for (int i = 0; i < 50; i++) {
AndroidUtilities.sleepDeep(500);
if (audioManager.getMode() != AudioManager.MODE_RINGTONE) {
break;
}
}
try {
// first reset the Alarm-volume to the value before it was eventually maxed out
if (maxOutVolumeForMultipleRingReminders) {
audioManager.setStreamVolume(AudioManager.STREAM_ALARM, previousAlarmVolume, 0);
}
if (voiceReminder) {
VoiceOutputService.getVoiceOutputInstance().queueSpeak(text);
}
} catch (VerifyError e) {
// unavailable
}
}
}
}
}

@ -21,12 +21,17 @@ import com.todoroo.astrid.helper.TaskEditControlSet;
import com.todoroo.astrid.service.TaskService;
import org.tasks.R;
import org.tasks.notifications.NotificationManager;
import java.util.LinkedList;
import java.util.List;
import javax.inject.Inject;
public class TimerActionControlSet extends TaskEditControlSet {
@Inject NotificationManager notificationManager;
private final ImageView timerButton;
private final Chronometer chronometer;
private boolean timerActive;
@ -41,14 +46,14 @@ public class TimerActionControlSet extends TaskEditControlSet {
@Override
public void onClick(View v) {
if (timerActive) {
TimerPlugin.updateTimer(taskService, activity, model, false);
TimerPlugin.updateTimer(notificationManager, taskService, activity, model, false);
for (TimerActionListener listener : listeners) {
listener.timerStopped(model);
}
chronometer.stop();
} else {
TimerPlugin.updateTimer(taskService, activity, model, true);
TimerPlugin.updateTimer(notificationManager, taskService, activity, model, true);
for (TimerActionListener listener : listeners) {
listener.timerStarted(model);
}

@ -13,8 +13,6 @@ import android.content.Intent;
import android.content.res.Resources;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.service.NotificationManager;
import com.todoroo.andlib.service.NotificationManager.AndroidNotificationManager;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.astrid.activity.ShortcutActivity;
@ -22,6 +20,7 @@ import com.todoroo.astrid.api.Addon;
import com.todoroo.astrid.api.AstridApiConstants;
import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.data.Task;
import org.tasks.notifications.NotificationManager;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.utility.Constants;
@ -46,7 +45,7 @@ public class TimerPlugin extends BroadcastReceiver {
* toggles timer and updates elapsed time.
* @param start if true, start timer. else, stop it
*/
public static void updateTimer(TaskService taskService, Context context, Task task, boolean start) {
public static void updateTimer(NotificationManager notificationManager, TaskService taskService, Context context, Task task, boolean start) {
// if this call comes from tasklist, then we need to fill in the gaps to handle this correctly
// this is needed just for stopping a task
if (!task.containsNonNullValue(Task.TIMER_START)) {
@ -71,16 +70,14 @@ public class TimerPlugin extends BroadcastReceiver {
taskService.save(task);
// update notification
TimerPlugin.updateNotifications(taskService, context);
TimerPlugin.updateNotifications(notificationManager, taskService, context);
}
private static void updateNotifications(TaskService taskService, Context context) {
NotificationManager nm = new AndroidNotificationManager(context);
private static void updateNotifications(NotificationManager notificationManager, TaskService taskService, Context context) {
int count = taskService.count(Query.select(Task.ID).
where(Task.TIMER_START.gt(0)));
if(count == 0) {
nm.cancel(Constants.NOTIFICATION_TIMER);
notificationManager.cancel(Constants.NOTIFICATION_TIMER);
} else {
Filter filter = TimerFilterExposer.createFilter(context);
Intent notifyIntent = ShortcutActivity.createIntent(filter);
@ -99,7 +96,7 @@ public class TimerPlugin extends BroadcastReceiver {
notification.flags |= Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
notification.flags &= ~Notification.FLAG_AUTO_CANCEL;
nm.notify(Constants.NOTIFICATION_TIMER, notification);
notificationManager.notify(Constants.NOTIFICATION_TIMER, notification);
}
}

@ -14,12 +14,14 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.TaskService;
import org.tasks.injection.InjectingBroadcastReceiver;
import org.tasks.notifications.NotificationManager;
import javax.inject.Inject;
public class TimerTaskCompleteListener extends InjectingBroadcastReceiver {
@Inject TaskService taskService;
@Inject NotificationManager notificationManager;
@Override
public void onReceive(Context context, Intent intent) {
@ -37,7 +39,6 @@ public class TimerTaskCompleteListener extends InjectingBroadcastReceiver {
return;
}
TimerPlugin.updateTimer(taskService, context, task, false);
TimerPlugin.updateTimer(notificationManager, taskService, context, task, false);
}
}

@ -19,10 +19,10 @@ public class Broadcaster {
}
public void sendOrderedBroadcast(Intent intent) {
sendOrderedBroadcast(context, intent);
sendOrderedBroadcast(intent, null);
}
public void sendOrderedBroadcast(Context context, Intent intent) {
context.sendOrderedBroadcast(intent, null);
public void sendOrderedBroadcast(Intent intent, String permissions) {
context.sendOrderedBroadcast(intent, permissions);
}
}

@ -13,6 +13,7 @@ import com.todoroo.astrid.gtasks.GtasksFilterExposer;
import com.todoroo.astrid.gtasks.GtasksStartupReceiver;
import com.todoroo.astrid.notes.NotesDetailExposer;
import com.todoroo.astrid.reminders.Notifications;
import com.todoroo.astrid.reminders.ShowNotificationReceiver;
import com.todoroo.astrid.repeats.RepeatDetailExposer;
import com.todoroo.astrid.repeats.RepeatTaskCompleteListener;
import com.todoroo.astrid.service.GlobalEventReceiver;
@ -48,6 +49,7 @@ import dagger.Provides;
AlarmDetailExposer.class,
GtasksStartupReceiver.class,
PhoneStateChangedReceiver.class,
ShowNotificationReceiver.class,
TimerFilterExposer.class,
CustomFilterExposer.class,
GtasksFilterExposer.class,

@ -0,0 +1,29 @@
package org.tasks.notifications;
import android.app.Notification;
import android.content.Context;
import org.tasks.injection.ForApplication;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class NotificationManager {
private final android.app.NotificationManager notificationManager;
@Inject
public NotificationManager(@ForApplication Context context) {
notificationManager = (android.app.NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
}
public void cancel(long id) {
notificationManager.cancel((int) id);
}
public void notify(int notificationId, Notification notification) {
notificationManager.notify(notificationId, notification);
}
}

@ -18,7 +18,7 @@ public class RefreshBroadcastReceiver extends InjectingBroadcastReceiver {
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
broadcaster.sendOrderedBroadcast(context, new Intent(AstridApiConstants.BROADCAST_EVENT_TASK_LIST_UPDATED));
broadcaster.sendOrderedBroadcast(context, new Intent(AstridApiConstants.BROADCAST_EVENT_FILTER_LIST_UPDATED));
broadcaster.sendOrderedBroadcast(new Intent(AstridApiConstants.BROADCAST_EVENT_TASK_LIST_UPDATED));
broadcaster.sendOrderedBroadcast(new Intent(AstridApiConstants.BROADCAST_EVENT_FILTER_LIST_UPDATED));
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

@ -48,9 +48,6 @@
<!-- ringtone to use for notifications -->
<string name="p_rmd_ringtone">notification_ringtone</string>
<!-- icon theme for notifications -->
<string name="p_rmd_icon">notif_theme</string>
<!-- if true / unset, hh:mm dialog. otherwise, days / hours -->
<string name="p_rmd_snooze_dialog">snooze_dialog</string>

Loading…
Cancel
Save