diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml index d1c7834abbc38b4955155c973d30184fbd5fb682..42c7979248d15ffbece6ea6d82cb05f79f8953b4 100644 --- a/briar-android/res/values/strings.xml +++ b/briar-android/res/values/strings.xml @@ -52,6 +52,10 @@ <item quantity="one">New private message.</item> <item quantity="other">%d new private messages.</item> </plurals> + <plurals name="group_message_notification_text"> + <item quantity="one">New group message.</item> + <item quantity="other">%d new group messages.</item> + </plurals> <plurals name="forum_post_notification_text"> <item quantity="one">New forum post.</item> <item quantity="other">%d new forum posts.</item> @@ -328,6 +332,7 @@ <!-- Settings Notifications --> <string name="notification_settings_title">Notifications</string> <string name="notify_private_messages_setting">Show alerts for private messages</string> + <string name="notify_group_messages_setting">Show alerts for group messages</string> <string name="notify_forum_posts_setting">Show alerts for forum posts</string> <string name="notify_blog_posts_setting">Show alerts for blog posts</string> <string name="notify_vibration_setting">Vibrate</string> diff --git a/briar-android/res/xml/settings.xml b/briar-android/res/xml/settings.xml index 92048e8cf510d015465a48d5cd154ad9a4e74e34..32519425808d4c3947b1f18c955b23ea6d9c92e6 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"/> + <CheckBoxPreference + android:defaultValue="true" + android:key="pref_key_notify_group_messages" + android:persistent="false" + android:title="@string/notify_group_messages_setting"/> + <CheckBoxPreference android:defaultValue="true" android:key="pref_key_notify_forum_posts" diff --git a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java index 973dc01307c1a0f64ee122fe64645df1f8fe967e..88ad43032d078e7f35ced8d48677d8faedfe55ad 100644 --- a/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java +++ b/briar-android/src/org/briarproject/android/AndroidNotificationManagerImpl.java @@ -18,6 +18,7 @@ import org.briarproject.android.api.AndroidExecutor; import org.briarproject.android.api.AndroidNotificationManager; import org.briarproject.android.contact.ConversationActivity; import org.briarproject.android.forum.ForumActivity; +import org.briarproject.android.privategroup.conversation.GroupActivity; import org.briarproject.api.contact.ContactId; import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DbException; @@ -25,6 +26,7 @@ import org.briarproject.api.event.BlogPostAddedEvent; import org.briarproject.api.event.Event; import org.briarproject.api.event.EventListener; import org.briarproject.api.event.ForumPostReceivedEvent; +import org.briarproject.api.event.GroupMessageAddedEvent; import org.briarproject.api.event.IntroductionRequestReceivedEvent; import org.briarproject.api.event.IntroductionResponseReceivedEvent; import org.briarproject.api.event.IntroductionSucceededEvent; @@ -64,8 +66,10 @@ import static org.briarproject.android.BriarActivity.GROUP_ID; import static org.briarproject.android.NavDrawerActivity.INTENT_BLOGS; import static org.briarproject.android.NavDrawerActivity.INTENT_CONTACTS; import static org.briarproject.android.NavDrawerActivity.INTENT_FORUMS; +import static org.briarproject.android.NavDrawerActivity.INTENT_GROUPS; import static org.briarproject.android.contact.ConversationActivity.CONTACT_ID; import static org.briarproject.android.fragment.SettingsFragment.PREF_NOTIFY_BLOG; +import static org.briarproject.android.fragment.SettingsFragment.PREF_NOTIFY_GROUP; import static org.briarproject.android.fragment.SettingsFragment.SETTINGS_NAMESPACE; class AndroidNotificationManagerImpl implements AndroidNotificationManager, @@ -73,13 +77,16 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, // Notification IDs private static final int PRIVATE_MESSAGE_NOTIFICATION_ID = 3; - private static final int FORUM_POST_NOTIFICATION_ID = 4; - private static final int BLOG_POST_NOTIFICATION_ID = 5; - private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 6; + private static final int GROUP_MESSAGE_NOTIFICATION_ID = 4; + private static final int FORUM_POST_NOTIFICATION_ID = 5; + private static final int BLOG_POST_NOTIFICATION_ID = 6; + private static final int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 7; // Content URIs to differentiate between pending intents private static final String CONTACT_URI = "content://org.briarproject/contact"; + private static final String GROUP_URI = + "content://org.briarproject/group"; private static final String FORUM_URI = "content://org.briarproject/forum"; private static final String BLOG_URI = @@ -88,6 +95,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, // Actions for intents that are broadcast when notifications are dismissed private static final String CLEAR_PRIVATE_MESSAGE_ACTION = "org.briarproject.briar.CLEAR_PRIVATE_MESSAGE_NOTIFICATION"; + private static final String CLEAR_GROUP_ACTION = + "org.briarproject.briar.CLEAR_GROUP_NOTIFICATION"; private static final String CLEAR_FORUM_ACTION = "org.briarproject.briar.CLEAR_FORUM_NOTIFICATION"; private static final String CLEAR_BLOG_ACTION = @@ -107,15 +116,17 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager, // The following must only be accessed on the main UI thread private final Map<ContactId, Integer> contactCounts = new HashMap<>(); + private final Map<GroupId, Integer> groupCounts = new HashMap<>(); private final Map<GroupId, Integer> forumCounts = new HashMap<>(); private final Map<GroupId, Integer> 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 16b3700cdaded5fa6e3e66781ca5b03554614eb2..cc2fbbcbfbfec5a7602137308b052a6966591843 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 1140ff5730dab87122eeae36fac7469347da51da..6a199a0feb5c13c5620b62ecdd702bb9c7b6338f 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 a3d6551267d1cd6fca14e571e2ebeea12236c011..60fab261fa399c11d9893fd0a197bba367a958d3 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 34ce9768f93d5f87067fb030e6b3f0a8bb830475..8146f200aaaa99b8eb2a94229940c23523fe4557 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 8be76289fe1ad638388d4526f2286a2a8f40165c..4524e484b399b3cbcdcb91a2edd0f38d508615b6 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