From 34d20fafda8df3e79ea2e911b740abc632ed5544 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Tue, 1 Aug 2017 12:47:11 -0300
Subject: [PATCH] Always play a notification sound, if at least 2sec after last
 one

This is the same behavior as Signal.
We might want to adjust the delay later on.

This is also introduces a new BriarNotificationBuilder as a first step
to clean up the Notification Manager code.
---
 .../AndroidNotificationManagerImpl.java       | 165 +++++++-----------
 .../util/BriarNotificationBuilder.java        |  38 ++++
 2 files changed, 102 insertions(+), 101 deletions(-)
 create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/util/BriarNotificationBuilder.java

diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java
index dfdfc583ee..6b4df66c50 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidNotificationManagerImpl.java
@@ -5,11 +5,8 @@ import android.app.NotificationManager;
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.os.Build;
 import android.support.annotation.UiThread;
-import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.TaskStackBuilder;
-import android.support.v4.content.ContextCompat;
 
 import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.db.DatabaseExecutor;
@@ -25,12 +22,14 @@ import org.briarproject.bramble.api.settings.SettingsManager;
 import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.system.AndroidExecutor;
+import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.R;
 import org.briarproject.briar.android.contact.ConversationActivity;
 import org.briarproject.briar.android.forum.ForumActivity;
 import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
 import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
+import org.briarproject.briar.android.util.BriarNotificationBuilder;
 import org.briarproject.briar.api.android.AndroidNotificationManager;
 import org.briarproject.briar.api.blog.event.BlogPostAddedEvent;
 import org.briarproject.briar.api.forum.event.ForumPostReceivedEvent;
@@ -48,6 +47,7 @@ import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
 
@@ -61,8 +61,6 @@ import static android.content.Context.NOTIFICATION_SERVICE;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
 import static android.support.v4.app.NotificationCompat.CATEGORY_SOCIAL;
-import static android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE;
-import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
 import static org.briarproject.briar.android.contact.ConversationActivity.CONTACT_ID;
@@ -95,6 +93,8 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 	private static final String BLOG_URI =
 			"content://org.briarproject.briar/blog";
 
+	private static final long SOUND_DELAY = TimeUnit.SECONDS.toMillis(2);
+
 	private static final Logger LOG =
 			Logger.getLogger(AndroidNotificationManagerImpl.class.getName());
 
@@ -102,6 +102,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 	private final SettingsManager settingsManager;
 	private final AndroidExecutor androidExecutor;
 	private final Context appContext;
+	private final Clock clock;
 	private final AtomicBoolean used = new AtomicBoolean(false);
 
 	// The following must only be accessed on the main UI thread
@@ -117,16 +118,18 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 	private boolean blockContacts = false, blockGroups = false;
 	private boolean blockForums = false, blockBlogs = false;
 	private boolean blockIntroductions = false;
+	private long lastSound = 0;
 
 	private volatile Settings settings = new Settings();
 
 	@Inject
 	AndroidNotificationManagerImpl(@DatabaseExecutor Executor dbExecutor,
 			SettingsManager settingsManager, AndroidExecutor androidExecutor,
-			Application app) {
+			Application app, Clock clock) {
 		this.dbExecutor = dbExecutor;
 		this.settingsManager = settingsManager;
 		this.androidExecutor = androidExecutor;
+		this.clock = clock;
 		appContext = app.getApplicationContext();
 	}
 
@@ -288,22 +291,19 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		if (contactTotal == 0) {
 			clearContactNotification();
 		} else if (settings.getBoolean(PREF_NOTIFY_PRIVATE, true)) {
-			NotificationCompat.Builder b =
-					new NotificationCompat.Builder(appContext);
+			BriarNotificationBuilder b =
+					new BriarNotificationBuilder(appContext);
 			b.setSmallIcon(R.drawable.notification_private_message);
-			b.setColor(ContextCompat.getColor(appContext,
-					R.color.briar_primary));
+			b.setColorRes(R.color.briar_primary);
 			b.setContentTitle(appContext.getText(R.string.app_name));
 			b.setContentText(appContext.getResources().getQuantityString(
 					R.plurals.private_message_notification_text, contactTotal,
 					contactTotal));
-			boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
-			String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
-			if (sound && !StringUtils.isNullOrEmpty(ringtoneUri))
-				b.setSound(Uri.parse(ringtoneUri));
-			b.setDefaults(getDefaults());
-			b.setOnlyAlertOnce(true);
-			b.setAutoCancel(true);
+			b.setNumber(contactTotal);
+			boolean showOnLockScreen =
+					settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
+			b.setLockscreenVisibility(CATEGORY_MESSAGE, showOnLockScreen);
+			playSound(b);
 			if (contactCounts.size() == 1) {
 				// Touching the notification shows the relevant conversation
 				Intent i = new Intent(appContext, ConversationActivity.class);
@@ -326,21 +326,27 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 				t.addNextIntent(i);
 				b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
 			}
-			if (Build.VERSION.SDK_INT >= 21) {
-				b.setCategory(CATEGORY_MESSAGE);
-				boolean showOnLockScreen =
-						settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
-				if (showOnLockScreen)
-					b.setVisibility(VISIBILITY_PRIVATE);
-				else
-					b.setVisibility(VISIBILITY_SECRET);
-			}
 			Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
 			NotificationManager nm = (NotificationManager) o;
 			nm.notify(PRIVATE_MESSAGE_NOTIFICATION_ID, b.build());
 		}
 	}
 
+	@UiThread
+	private void playSound(BriarNotificationBuilder b) {
+		boolean sound = settings.getBoolean(PREF_NOTIFY_SOUND, true);
+		if (!sound) return;
+
+		long currentTime = clock.currentTimeMillis();
+		if (currentTime - lastSound > SOUND_DELAY) {
+			String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
+			if (!StringUtils.isNullOrEmpty(ringtoneUri))
+				b.setSound(Uri.parse(ringtoneUri));
+			b.setDefaults(getDefaults());
+			lastSound = clock.currentTimeMillis();
+		}
+	}
+
 	@UiThread
 	private int getDefaults() {
 		int defaults = DEFAULT_LIGHTS;
@@ -387,21 +393,19 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		if (groupTotal == 0) {
 			clearGroupMessageNotification();
 		} else if (settings.getBoolean(PREF_NOTIFY_GROUP, true)) {
-			NotificationCompat.Builder b =
-					new NotificationCompat.Builder(appContext);
+			BriarNotificationBuilder b =
+					new BriarNotificationBuilder(appContext);
 			b.setSmallIcon(R.drawable.notification_private_group);
-			b.setColor(ContextCompat.getColor(appContext,
-					R.color.briar_primary));
+			b.setColorRes(R.color.briar_primary);
 			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(PREF_NOTIFY_RINGTONE_URI);
-			if (!StringUtils.isNullOrEmpty(ringtoneUri))
-				b.setSound(Uri.parse(ringtoneUri));
-			b.setDefaults(getDefaults());
-			b.setOnlyAlertOnce(true);
-			b.setAutoCancel(true);
+			b.setNumber(groupTotal);
+			boolean showOnLockScreen =
+					settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
+			b.setLockscreenVisibility(CATEGORY_SOCIAL, showOnLockScreen);
+			playSound(b);
 			if (groupCounts.size() == 1) {
 				// Touching the notification shows the relevant group
 				Intent i = new Intent(appContext, GroupActivity.class);
@@ -425,15 +429,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 				t.addNextIntent(i);
 				b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
 			}
-			if (Build.VERSION.SDK_INT >= 21) {
-				b.setCategory(CATEGORY_SOCIAL);
-				boolean showOnLockScreen =
-						settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
-				if (showOnLockScreen)
-					b.setVisibility(VISIBILITY_PRIVATE);
-				else
-					b.setVisibility(VISIBILITY_SECRET);
-			}
 			Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
 			NotificationManager nm = (NotificationManager) o;
 			nm.notify(GROUP_MESSAGE_NOTIFICATION_ID, b.build());
@@ -474,21 +469,19 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		if (forumTotal == 0) {
 			clearForumPostNotification();
 		} else if (settings.getBoolean(PREF_NOTIFY_FORUM, true)) {
-			NotificationCompat.Builder b =
-					new NotificationCompat.Builder(appContext);
+			BriarNotificationBuilder b =
+					new BriarNotificationBuilder(appContext);
 			b.setSmallIcon(R.drawable.notification_forum);
-			b.setColor(ContextCompat.getColor(appContext,
-					R.color.briar_primary));
+			b.setColorRes(R.color.briar_primary);
 			b.setContentTitle(appContext.getText(R.string.app_name));
 			b.setContentText(appContext.getResources().getQuantityString(
 					R.plurals.forum_post_notification_text, forumTotal,
 					forumTotal));
-			String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
-			if (!StringUtils.isNullOrEmpty(ringtoneUri))
-				b.setSound(Uri.parse(ringtoneUri));
-			b.setDefaults(getDefaults());
-			b.setOnlyAlertOnce(true);
-			b.setAutoCancel(true);
+			b.setNumber(forumTotal);
+			boolean showOnLockScreen =
+					settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
+			b.setLockscreenVisibility(CATEGORY_SOCIAL, showOnLockScreen);
+			playSound(b);
 			if (forumCounts.size() == 1) {
 				// Touching the notification shows the relevant forum
 				Intent i = new Intent(appContext, ForumActivity.class);
@@ -512,15 +505,6 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 				t.addNextIntent(i);
 				b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
 			}
-			if (Build.VERSION.SDK_INT >= 21) {
-				b.setCategory(CATEGORY_SOCIAL);
-				boolean showOnLockScreen =
-						settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
-				if (showOnLockScreen)
-					b.setVisibility(VISIBILITY_PRIVATE);
-				else
-					b.setVisibility(VISIBILITY_SECRET);
-			}
 			Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
 			NotificationManager nm = (NotificationManager) o;
 			nm.notify(FORUM_POST_NOTIFICATION_ID, b.build());
@@ -561,21 +545,19 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		if (blogTotal == 0) {
 			clearBlogPostNotification();
 		} else if (settings.getBoolean(PREF_NOTIFY_BLOG, true)) {
-			NotificationCompat.Builder b =
-					new NotificationCompat.Builder(appContext);
+			BriarNotificationBuilder b =
+					new BriarNotificationBuilder(appContext);
 			b.setSmallIcon(R.drawable.notification_blog);
-			b.setColor(ContextCompat.getColor(appContext,
-					R.color.briar_primary));
+			b.setColorRes(R.color.briar_primary);
 			b.setContentTitle(appContext.getText(R.string.app_name));
 			b.setContentText(appContext.getResources().getQuantityString(
 					R.plurals.blog_post_notification_text, blogTotal,
 					blogTotal));
-			String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
-			if (!StringUtils.isNullOrEmpty(ringtoneUri))
-				b.setSound(Uri.parse(ringtoneUri));
-			b.setDefaults(getDefaults());
-			b.setOnlyAlertOnce(true);
-			b.setAutoCancel(true);
+			b.setNumber(blogTotal);
+			boolean showOnLockScreen =
+					settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
+			b.setLockscreenVisibility(CATEGORY_SOCIAL, showOnLockScreen);
+			playSound(b);
 			// Touching the notification shows the combined blog feed
 			Intent i = new Intent(appContext, NavDrawerActivity.class);
 			i.putExtra(INTENT_BLOGS, true);
@@ -585,15 +567,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 			t.addParentStack(NavDrawerActivity.class);
 			t.addNextIntent(i);
 			b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
-			if (Build.VERSION.SDK_INT >= 21) {
-				b.setCategory(CATEGORY_SOCIAL);
-				boolean showOnLockScreen =
-						settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
-				if (showOnLockScreen)
-					b.setVisibility(VISIBILITY_PRIVATE);
-				else
-					b.setVisibility(VISIBILITY_SECRET);
-			}
+
 			Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
 			NotificationManager nm = (NotificationManager) o;
 			nm.notify(BLOG_POST_NOTIFICATION_ID, b.build());
@@ -623,20 +597,17 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 
 	@UiThread
 	private void updateIntroductionNotification() {
-		NotificationCompat.Builder b =
-				new NotificationCompat.Builder(appContext);
+		BriarNotificationBuilder b = new BriarNotificationBuilder(appContext);
 		b.setSmallIcon(R.drawable.notification_introduction);
-		b.setColor(ContextCompat.getColor(appContext, R.color.briar_primary));
+		b.setColorRes(R.color.briar_primary);
 		b.setContentTitle(appContext.getText(R.string.app_name));
 		b.setContentText(appContext.getResources().getQuantityString(
 				R.plurals.introduction_notification_text, introductionTotal,
 				introductionTotal));
-		String ringtoneUri = settings.get(PREF_NOTIFY_RINGTONE_URI);
-		if (!StringUtils.isNullOrEmpty(ringtoneUri))
-			b.setSound(Uri.parse(ringtoneUri));
-		b.setDefaults(getDefaults());
-		b.setOnlyAlertOnce(true);
-		b.setAutoCancel(true);
+		boolean showOnLockScreen =
+				settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
+		b.setLockscreenVisibility(CATEGORY_MESSAGE, showOnLockScreen);
+		playSound(b);
 		// Touching the notification shows the contact list
 		Intent i = new Intent(appContext, NavDrawerActivity.class);
 		i.putExtra(INTENT_CONTACTS, true);
@@ -646,15 +617,7 @@ class AndroidNotificationManagerImpl implements AndroidNotificationManager,
 		t.addParentStack(NavDrawerActivity.class);
 		t.addNextIntent(i);
 		b.setContentIntent(t.getPendingIntent(nextRequestId++, 0));
-		if (Build.VERSION.SDK_INT >= 21) {
-			b.setCategory(CATEGORY_MESSAGE);
-			boolean showOnLockScreen =
-					settings.getBoolean(PREF_NOTIFY_LOCK_SCREEN, false);
-			if (showOnLockScreen)
-				b.setVisibility(VISIBILITY_PRIVATE);
-			else
-				b.setVisibility(VISIBILITY_SECRET);
-		}
+
 		Object o = appContext.getSystemService(NOTIFICATION_SERVICE);
 		NotificationManager nm = (NotificationManager) o;
 		nm.notify(INTRODUCTION_SUCCESS_NOTIFICATION_ID, b.build());
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/util/BriarNotificationBuilder.java b/briar-android/src/main/java/org/briarproject/briar/android/util/BriarNotificationBuilder.java
new file mode 100644
index 0000000000..76fab9de4b
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/util/BriarNotificationBuilder.java
@@ -0,0 +1,38 @@
+package org.briarproject.briar.android.util;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.annotation.ColorRes;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.NotificationCompat;
+
+import static android.support.v4.app.NotificationCompat.CATEGORY_MESSAGE;
+import static android.support.v4.app.NotificationCompat.VISIBILITY_PRIVATE;
+import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET;
+
+
+public class BriarNotificationBuilder extends NotificationCompat.Builder {
+
+	public BriarNotificationBuilder(Context context) {
+		super(context);
+		setAutoCancel(true);
+	}
+
+	public BriarNotificationBuilder setColorRes(@ColorRes int res) {
+		setColor(ContextCompat.getColor(mContext, res));
+		return this;
+	}
+
+	public BriarNotificationBuilder setLockscreenVisibility(String category,
+			boolean show) {
+		if (Build.VERSION.SDK_INT >= 21) {
+			setCategory(category);
+			if (show)
+				setVisibility(VISIBILITY_PRIVATE);
+			else
+				setVisibility(VISIBILITY_SECRET);
+		}
+		return this;
+	}
+
+}
-- 
GitLab