diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index d1c7834ab..42c797924 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -52,6 +52,10 @@ New private message. %d new private messages. + + New group message. + %d new group messages. + New forum post. %d new forum posts. @@ -328,6 +332,7 @@ Notifications Show alerts for private messages + Show alerts for group messages Show alerts for forum posts Show alerts for blog posts Vibrate diff --git a/briar-android/res/xml/settings.xml b/briar-android/res/xml/settings.xml index 92048e8cf..325194258 100644 --- a/briar-android/res/xml/settings.xml +++ b/briar-android/res/xml/settings.xml @@ -63,6 +63,12 @@ android:persistent="false" android:title="@string/notify_private_messages_setting"/> + + contactCounts = new HashMap<>(); + private final Map groupCounts = new HashMap<>(); private final Map forumCounts = new HashMap<>(); private final Map blogCounts = new HashMap<>(); - private int contactTotal = 0, forumTotal = 0, blogTotal = 0; + private int contactTotal = 0, groupTotal = 0, forumTotal = 0, blogTotal = 0; private int introductionTotal = 0; private int nextRequestId = 0; private ContactId blockedContact = null; private GroupId blockedGroup = null; - private boolean blockContacts = false, blockForums = false; - private boolean blockBlogs = false, blockIntroductions = false; + private boolean blockContacts = false, blockGroups = false; + private boolean blockForums = false, blockBlogs = false; + private boolean blockIntroductions = false; private volatile Settings settings = new Settings(); @@ -144,6 +155,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, public Void call() { IntentFilter filter = new IntentFilter(); filter.addAction(CLEAR_PRIVATE_MESSAGE_ACTION); + filter.addAction(CLEAR_GROUP_ACTION); filter.addAction(CLEAR_FORUM_ACTION); filter.addAction(CLEAR_BLOG_ACTION); filter.addAction(CLEAR_INTRODUCTION_ACTION); @@ -165,6 +177,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, @Override public Void call() { clearContactNotification(); + clearGroupMessageNotification(); clearForumPostNotification(); clearBlogPostNotification(); clearIntroductionSuccessNotification(); @@ -188,6 +201,15 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, nm.cancel(PRIVATE_MESSAGE_NOTIFICATION_ID); } + @UiThread + private void clearGroupMessageNotification() { + groupCounts.clear(); + groupTotal = 0; + Object o = appContext.getSystemService(NOTIFICATION_SERVICE); + NotificationManager nm = (NotificationManager) o; + nm.cancel(GROUP_MESSAGE_NOTIFICATION_ID); + } + @UiThread private void clearForumPostNotification() { forumCounts.clear(); @@ -222,6 +244,9 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, } else if (e instanceof PrivateMessageReceivedEvent) { PrivateMessageReceivedEvent p = (PrivateMessageReceivedEvent) e; showContactNotification(p.getContactId()); + } else if (e instanceof GroupMessageAddedEvent) { + GroupMessageAddedEvent g = (GroupMessageAddedEvent) e; + showGroupMessageNotification(g.getGroupId()); } else if (e instanceof ForumPostReceivedEvent) { ForumPostReceivedEvent f = (ForumPostReceivedEvent) e; showForumPostNotification(f.getGroupId()); @@ -367,6 +392,101 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, }); } + @UiThread + private void showGroupMessageNotification(final GroupId g) { + androidExecutor.runOnUiThread(new Runnable() { + @Override + public void run() { + if (blockGroups) return; + if (g.equals(blockedGroup)) return; + Integer count = groupCounts.get(g); + if (count == null) groupCounts.put(g, 1); + else groupCounts.put(g, count + 1); + groupTotal++; + updateGroupMessageNotification(); + } + }); + } + + @Override + public void clearGroupMessageNotification(final GroupId g) { + androidExecutor.runOnUiThread(new Runnable() { + @Override + public void run() { + Integer count = groupCounts.remove(g); + if (count == null) return; // Already cleared + groupTotal -= count; + updateGroupMessageNotification(); + } + }); + } + + @UiThread + private void updateGroupMessageNotification() { + if (groupTotal == 0) { + clearGroupMessageNotification(); + } else if (settings.getBoolean(PREF_NOTIFY_GROUP, true)) { + NotificationCompat.Builder b = + new NotificationCompat.Builder(appContext); + b.setSmallIcon(R.drawable.message_notification_icon); + b.setContentTitle(appContext.getText(R.string.app_name)); + b.setContentText(appContext.getResources().getQuantityString( + R.plurals.group_message_notification_text, groupTotal, + groupTotal)); + String ringtoneUri = settings.get("notifyRingtoneUri"); + if (!StringUtils.isNullOrEmpty(ringtoneUri)) + b.setSound(Uri.parse(ringtoneUri)); + b.setDefaults(getDefaults()); + b.setOnlyAlertOnce(true); + b.setAutoCancel(true); + // Clear the counters if the notification is dismissed + Intent clear = new Intent(CLEAR_GROUP_ACTION); + PendingIntent delete = PendingIntent.getBroadcast(appContext, 0, + clear, 0); + b.setDeleteIntent(delete); + if (groupCounts.size() == 1) { + // Touching the notification shows the relevant group + Intent i = new Intent(appContext, GroupActivity.class); + GroupId g = groupCounts.keySet().iterator().next(); + i.putExtra(GROUP_ID, g.getBytes()); + String idHex = StringUtils.toHexString(g.getBytes()); + i.setData(Uri.parse(GROUP_URI + "/" + idHex)); + i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP); + TaskStackBuilder t = TaskStackBuilder.create(appContext); + t.addParentStack(GroupActivity.class); + t.addNextIntent(i); + b.setContentIntent(t.getPendingIntent(nextRequestId++, 0)); + } else { + // Touching the notification shows the group list + Intent i = new Intent(appContext, NavDrawerActivity.class); + i.putExtra(INTENT_GROUPS, true); + i.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP); + i.setData(Uri.parse(GROUP_URI)); + TaskStackBuilder t = TaskStackBuilder.create(appContext); + t.addParentStack(NavDrawerActivity.class); + t.addNextIntent(i); + b.setContentIntent(t.getPendingIntent(nextRequestId++, 0)); + } + if (Build.VERSION.SDK_INT >= 21) { + b.setCategory(CATEGORY_SOCIAL); + b.setVisibility(VISIBILITY_SECRET); + } + Object o = appContext.getSystemService(NOTIFICATION_SERVICE); + NotificationManager nm = (NotificationManager) o; + nm.notify(GROUP_MESSAGE_NOTIFICATION_ID, b.build()); + } + } + + @Override + public void clearAllGroupMessageNotifications() { + androidExecutor.runOnUiThread(new Runnable() { + @Override + public void run() { + clearGroupMessageNotification(); + } + }); + } + @UiThread private void showForumPostNotification(final GroupId g) { androidExecutor.runOnUiThread(new Runnable() { @@ -654,6 +774,26 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, }); } + @Override + public void blockAllGroupMessageNotifications() { + androidExecutor.runOnUiThread(new Runnable() { + @Override + public void run() { + blockGroups = true; + } + }); + } + + @Override + public void unblockAllGroupMessageNotifications() { + androidExecutor.runOnUiThread(new Runnable() { + @Override + public void run() { + blockGroups = false; + } + }); + } + @Override public void blockAllForumPostNotifications() { androidExecutor.runOnUiThread(new Runnable() { @@ -704,6 +844,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, public void run() { if (CLEAR_PRIVATE_MESSAGE_ACTION.equals(action)) { clearContactNotification(); + } else if (CLEAR_GROUP_ACTION.equals(action)) { + clearGroupMessageNotification(); } else if (CLEAR_FORUM_ACTION.equals(action)) { clearForumPostNotification(); } else if (CLEAR_BLOG_ACTION.equals(action)) { diff --git a/briar-android/src/org/briarproject/android/NavDrawerActivity.java b/briar-android/src/org/briarproject/android/NavDrawerActivity.java index 16b3700cd..cc2fbbcbf 100644 --- a/briar-android/src/org/briarproject/android/NavDrawerActivity.java +++ b/briar-android/src/org/briarproject/android/NavDrawerActivity.java @@ -46,6 +46,7 @@ public class NavDrawerActivity extends BriarFragmentActivity implements OnNavigationItemSelectedListener { static final String INTENT_CONTACTS = "intent_contacts"; + static final String INTENT_GROUPS = "intent_groups"; static final String INTENT_FORUMS = "intent_forums"; static final String INTENT_BLOGS = "intent_blogs"; @@ -70,10 +71,10 @@ public class NavDrawerActivity extends BriarFragmentActivity implements protected void onNewIntent(Intent intent) { super.onNewIntent(intent); exitIfStartupFailed(intent); - // FIXME why was the stack cleared here? - // This prevents state from being restored properly -// clearBackStack(); - if (intent.getBooleanExtra(INTENT_FORUMS, false)) { + // TODO don't create new instances if they are on the stack (#606) + if (intent.getBooleanExtra(INTENT_GROUPS, false)) { + startFragment(GroupListFragment.newInstance()); + } else if (intent.getBooleanExtra(INTENT_FORUMS, false)) { startFragment(ForumListFragment.newInstance()); } else if (intent.getBooleanExtra(INTENT_CONTACTS, false)) { startFragment(ContactListFragment.newInstance()); diff --git a/briar-android/src/org/briarproject/android/api/AndroidNotificationManager.java b/briar-android/src/org/briarproject/android/api/AndroidNotificationManager.java index 1140ff573..6a199a0fe 100644 --- a/briar-android/src/org/briarproject/android/api/AndroidNotificationManager.java +++ b/briar-android/src/org/briarproject/android/api/AndroidNotificationManager.java @@ -13,6 +13,10 @@ public interface AndroidNotificationManager { void clearAllContactNotifications(); + void clearGroupMessageNotification(GroupId g); + + void clearAllGroupMessageNotifications(); + void clearForumPostNotification(GroupId g); void clearAllForumPostNotifications(); @@ -33,6 +37,10 @@ public interface AndroidNotificationManager { void unblockAllContactNotifications(); + void blockAllGroupMessageNotifications(); + + void unblockAllGroupMessageNotifications(); + void blockAllForumPostNotifications(); void unblockAllForumPostNotifications(); diff --git a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java index a3d655126..60fab261f 100644 --- a/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java +++ b/briar-android/src/org/briarproject/android/fragment/SettingsFragment.java @@ -50,6 +50,7 @@ public class SettingsFragment extends PreferenceFragmentCompat private static final int REQUEST_RINGTONE = 2; public static final String SETTINGS_NAMESPACE = "android-ui"; + public static final String PREF_NOTIFY_GROUP = "notifyGroupMessages"; public static final String PREF_NOTIFY_BLOG = "notifyBlogPosts"; private static final Logger LOG = @@ -60,6 +61,7 @@ public class SettingsFragment extends PreferenceFragmentCompat private ListPreference enableBluetooth; private ListPreference torOverMobile; private CheckBoxPreference notifyPrivateMessages; + private CheckBoxPreference notifyGroupMessages; private CheckBoxPreference notifyForumPosts; private CheckBoxPreference notifyBlogPosts; private CheckBoxPreference notifyVibration; @@ -91,6 +93,8 @@ public class SettingsFragment extends PreferenceFragmentCompat (ListPreference) findPreference("pref_key_tor_mobile"); notifyPrivateMessages = (CheckBoxPreference) findPreference( "pref_key_notify_private_messages"); + notifyGroupMessages = (CheckBoxPreference) findPreference( + "pref_key_notify_group_messages"); notifyForumPosts = (CheckBoxPreference) findPreference( "pref_key_notify_forum_posts"); notifyBlogPosts = (CheckBoxPreference) findPreference( @@ -102,6 +106,7 @@ public class SettingsFragment extends PreferenceFragmentCompat enableBluetooth.setOnPreferenceChangeListener(this); torOverMobile.setOnPreferenceChangeListener(this); notifyPrivateMessages.setOnPreferenceChangeListener(this); + notifyGroupMessages.setOnPreferenceChangeListener(this); notifyForumPosts.setOnPreferenceChangeListener(this); notifyBlogPosts.setOnPreferenceChangeListener(this); notifyVibration.setOnPreferenceChangeListener(this); @@ -199,6 +204,9 @@ public class SettingsFragment extends PreferenceFragmentCompat notifyPrivateMessages.setChecked(settings.getBoolean( "notifyPrivateMessages", true)); + notifyGroupMessages.setChecked(settings.getBoolean( + PREF_NOTIFY_GROUP, true)); + notifyForumPosts.setChecked(settings.getBoolean( "notifyForumPosts", true)); @@ -247,6 +255,10 @@ public class SettingsFragment extends PreferenceFragmentCompat Settings s = new Settings(); s.putBoolean("notifyPrivateMessages", (Boolean) o); storeSettings(s); + } else if (preference == notifyGroupMessages) { + Settings s = new Settings(); + s.putBoolean(PREF_NOTIFY_GROUP, (Boolean) o); + storeSettings(s); } else if (preference == notifyForumPosts) { Settings s = new Settings(); s.putBoolean("notifyForumPosts", (Boolean) o); diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java index 34ce9768f..8146f200a 100644 --- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java @@ -59,7 +59,7 @@ public class GroupControllerImpl extends @Override public void onActivityStart() { super.onActivityStart(); - // TODO: Add new notification manager methods for private groups + notificationManager.clearGroupMessageNotification(getGroupId()); } @Override diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java index 8be76289f..4524e484b 100644 --- a/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java +++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupListControllerImpl.java @@ -2,6 +2,7 @@ package org.briarproject.android.privategroup.list; import android.support.annotation.CallSuper; +import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.controller.DbControllerImpl; import org.briarproject.android.controller.handler.ResultExceptionHandler; import org.briarproject.api.clients.MessageTracker.GroupCount; @@ -41,6 +42,7 @@ public class GroupListControllerImpl extends DbControllerImpl private final PrivateGroupManager groupManager; private final GroupInvitationManager groupInvitationManager; + private final AndroidNotificationManager notificationManager; private final EventBus eventBus; protected volatile GroupListListener listener; @@ -48,10 +50,12 @@ public class GroupListControllerImpl extends DbControllerImpl @Inject GroupListControllerImpl(@DatabaseExecutor Executor dbExecutor, LifecycleManager lifecycleManager, PrivateGroupManager groupManager, - GroupInvitationManager groupInvitationManager, EventBus eventBus) { + GroupInvitationManager groupInvitationManager, + AndroidNotificationManager notificationManager, EventBus eventBus) { super(dbExecutor, lifecycleManager); this.groupManager = groupManager; this.groupInvitationManager = groupInvitationManager; + this.notificationManager = notificationManager; this.eventBus = eventBus; } @@ -67,13 +71,15 @@ public class GroupListControllerImpl extends DbControllerImpl throw new IllegalStateException( "GroupListListener needs to be attached"); eventBus.addListener(this); - // TODO: Add new notification manager methods for private groups + notificationManager.blockAllGroupMessageNotifications(); + notificationManager.clearAllGroupMessageNotifications(); } @Override @CallSuper public void onStop() { eventBus.removeListener(this); + notificationManager.unblockAllGroupMessageNotifications(); } @Override