diff --git a/res/values/arrays.xml b/res/values/arrays.xml
index ad4ec3808..9da443c70 100644
--- a/res/values/arrays.xml
+++ b/res/values/arrays.xml
@@ -132,7 +132,7 @@
- @string/mute_for_two_hours
- @string/mute_for_one_day
- @string/mute_for_seven_days
- - @string/mute_for_one_year
+ - @string/mute_forever
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 042de8ef1..feea793c7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -156,7 +156,7 @@
Mute for 2 hours
Mute for 1 day
Mute for 7 days
- Mute for 1 year
+ Mute forever
once
for 5 minutes
diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java
index 0a1c90c12..9e7d0bd3a 100644
--- a/src/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/src/org/thoughtcrime/securesms/ApplicationContext.java
@@ -10,18 +10,17 @@ import android.support.multidex.MultiDexApplication;
import com.b44t.messenger.DcContext;
import com.b44t.messenger.DcEventCenter;
-import org.thoughtcrime.securesms.geolocation.DcLocationManager;
import org.thoughtcrime.securesms.connect.ApplicationDcContext;
import org.thoughtcrime.securesms.crypto.PRNGFixes;
+import org.thoughtcrime.securesms.geolocation.DcLocationManager;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.persistence.JavaJobSerializer;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
+import org.thoughtcrime.securesms.util.AndroidSignalProtocolLogger;
import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.ScreenLockUtil;
-import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.SignalProtocolLoggerProvider;
-import org.thoughtcrime.securesms.util.AndroidSignalProtocolLogger;
//import com.squareup.leakcanary.LeakCanary;
public class ApplicationContext extends MultiDexApplication implements DefaultLifecycleObserver {
@@ -54,6 +53,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
initializeJobManager();
initializeIncomingMessageNotifier();
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
+ MessageNotifier.init(this);
dcLocationManager = new DcLocationManager(this);
try {
@@ -106,12 +106,6 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
return false;
}
});
-
- // in five seconds, the system should be up and ready so we can start issuing notifications.
-
- Util.runOnBackgroundDelayed(() -> {
- MessageNotifier.updateNotification(dcContext.context);
- }, 5000);
}
private void initializeJobManager() {
diff --git a/src/org/thoughtcrime/securesms/ConversationActivity.java b/src/org/thoughtcrime/securesms/ConversationActivity.java
index 156dcdd3e..0c4874060 100644
--- a/src/org/thoughtcrime/securesms/ConversationActivity.java
+++ b/src/org/thoughtcrime/securesms/ConversationActivity.java
@@ -1426,6 +1426,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
else {
processComposeControls(ACTION_SEND_OUT);
+ MessageNotifier.playSendSound();
}
}
diff --git a/src/org/thoughtcrime/securesms/MuteDialog.java b/src/org/thoughtcrime/securesms/MuteDialog.java
index 0042c8146..01586ace2 100644
--- a/src/org/thoughtcrime/securesms/MuteDialog.java
+++ b/src/org/thoughtcrime/securesms/MuteDialog.java
@@ -25,22 +25,19 @@ public class MuteDialog extends AlertDialog {
public static void show(final Context context, final @NonNull MuteSelectionListener listener) {
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(R.string.menu_mute);
- builder.setItems(R.array.mute_durations, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, final int which) {
- final long muteUntil;
+ builder.setItems(R.array.mute_durations, (dialog, which) -> {
+ final long muteUntil;
- switch (which) {
- case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
- case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2); break;
- case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break;
- case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break;
- case 4: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(365); break;
- default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
- }
-
- listener.onMuted(muteUntil);
+ switch (which) {
+ case 0: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
+ case 1: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(2); break;
+ case 2: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1); break;
+ case 3: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(7); break;
+ case 4: muteUntil = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(36500); break;
+ default: muteUntil = System.currentTimeMillis() + TimeUnit.HOURS.toMillis(1); break;
}
+
+ listener.onMuted(muteUntil);
});
builder.show();
diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
index bf8d02aef..fa0f34b67 100644
--- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
+++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
@@ -23,11 +23,8 @@ import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
-import android.media.AudioAttributes;
import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.net.Uri;
+import android.media.SoundPool;
import android.os.AsyncTask;
import android.os.Build;
import android.service.notification.StatusBarNotification;
@@ -41,6 +38,7 @@ import com.b44t.messenger.DcMsg;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.connect.ApplicationDcContext;
import org.thoughtcrime.securesms.connect.DcHelper;
+import org.thoughtcrime.securesms.connect.ForegroundDetector;
import org.thoughtcrime.securesms.connect.KeepAliveService;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient;
@@ -48,17 +46,11 @@ import org.thoughtcrime.securesms.util.Pair;
import org.thoughtcrime.securesms.util.Prefs;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.SpanUtil;
-import org.thoughtcrime.securesms.util.Util;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import me.leolin.shortcutbadger.ShortcutBadger;
@@ -81,12 +73,55 @@ public class MessageNotifier {
private static final int PENDING_MESSAGES_ID = 1111;
private static final String NOTIFICATION_GROUP = "messages";
private static final long MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(20);
- private static final long DESKTOP_ACTIVITY_PERIOD = TimeUnit.MINUTES.toMillis(1);
+ private static final long STARTUP_SILENCE_DELTA = TimeUnit.MINUTES.toMillis(1);
+ private static final long INITIAL_STARTUP = System.currentTimeMillis();
- private volatile static long visibleChatId = NO_VISIBLE_CHAT_ID;
- private volatile static long lastDesktopActivityTimestamp = -1;
- private volatile static long lastAudibleNotification = -1;
- private static final CancelableExecutor executor = new CancelableExecutor();
+ private volatile static long visibleChatId = NO_VISIBLE_CHAT_ID;
+ private volatile static long lastAudibleNotification = -1;
+
+ private final SoundPool soundPool;
+ private final int soundIn;
+ private final int soundOut;
+ private boolean soundInLoaded;
+ private boolean soundOutLoaded;
+ private Context context;
+
+ private static MessageNotifier instance;
+
+ private MessageNotifier(Context context) {
+ this.context = context.getApplicationContext();
+ soundPool = new SoundPool(3, AudioManager.STREAM_SYSTEM, 0);
+ soundIn = soundPool.load(context, R.raw.sound_in, 1);
+ soundOut = soundPool.load(context, R.raw.sound_out, 1);
+
+ soundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> {
+ if (status == 0) {
+ if (sampleId == soundIn) {
+ soundInLoaded = true;
+ } else if (sampleId == soundOut) {
+ soundOutLoaded = true;
+ }
+ }
+ });
+ }
+
+ public static MessageNotifier init(Context context) {
+ if (instance == null) {
+ instance = new MessageNotifier(context);
+ }
+ return instance;
+ }
+
+ public static void playSendSound() {
+ if (instance == null) {
+ Log.w(TAG, "Message notifier not initialized. Cannot play sounds");
+ return;
+ }
+
+ if (Prefs.isInChatNotifications(instance.context) && instance.soundOutLoaded) {
+ instance.soundPool.play(instance.soundIn, 1.0f, 1.0f, 1, 0, 1.0f);
+ }
+ }
private static LinkedList> pendingNotifications = new LinkedList<>();
@@ -103,10 +138,6 @@ public class MessageNotifier {
}
}
- public static void cancelDelayedNotifications() {
- executor.cancel();
- }
-
private static void cancelActiveNotifications(@NonNull Context context) {
NotificationManager notifications = ServiceUtil.getNotificationManager(context);
notifications.cancel(SUMMARY_NOTIFICATION_ID);
@@ -168,12 +199,7 @@ public class MessageNotifier {
public static void updateNotification(@NonNull Context context, int chatId)
{
- if (System.currentTimeMillis() - lastDesktopActivityTimestamp < DESKTOP_ACTIVITY_PERIOD) {
- Log.w(TAG, "Scheduling delayed notification...");
- executor.execute(new DelayedNotification(context, chatId));
- } else {
- updateNotification(context, chatId, true);
- }
+ updateNotification(context, chatId, true);
}
public static void updateNotification(@NonNull Context context,
@@ -193,12 +219,16 @@ public class MessageNotifier {
return;
}
+ removePendingNotifications(chatId);
if (isVisible && signal) {
sendInChatNotification(context, chatId);
- } else if (visibleChatId != NO_VISIBLE_CHAT_ID) {
- pendingNotifications.push(new Pair<>(chatId, signal));
- } else {
+ } else if (ForegroundDetector.getInstance().isBackground()) {
updateNotification(context, signal, 0);
+ } else if (isVisible || visibleChatId == NO_VISIBLE_CHAT_ID) {
+ updateNotification(context, false, 0);
+ } else {
+ //different chat is visible
+ pendingNotifications.push(new Pair<>(chatId, signal));
}
}
@@ -220,10 +250,13 @@ public class MessageNotifier {
NotificationState notificationState = constructNotificationState(dcContext, freshMessages);
- if (signal && (System.currentTimeMillis() - lastAudibleNotification) < MIN_AUDIBLE_PERIOD_MILLIS) {
+ long now = System.currentTimeMillis();
+ if (signal && (
+ (now - INITIAL_STARTUP) < STARTUP_SILENCE_DELTA ||
+ (now - lastAudibleNotification) < MIN_AUDIBLE_PERIOD_MILLIS)) {
signal = false;
} else if (signal) {
- lastAudibleNotification = System.currentTimeMillis();
+ lastAudibleNotification = now;
}
if (notificationState.hasMultipleChats()) {
@@ -248,7 +281,8 @@ public class MessageNotifier {
private static void sendSingleChatNotification(@NonNull Context context,
@NonNull NotificationState notificationState,
- boolean signal, boolean bundled)
+ boolean signal,
+ boolean bundled)
{
if (notificationState.getNotifications().isEmpty()) {
if (!bundled) cancelActiveNotifications(context);
@@ -278,10 +312,10 @@ public class MessageNotifier {
builder.addAndroidAutoAction(notificationState.getAndroidAutoReplyIntent(context, notifications.get(0).getRecipient()),
notificationState.getAndroidAutoHeardIntent(context, notificationId), notifications.get(0).getTimestamp());
- ListIterator iterator = notifications.listIterator(notifications.size());
+ ListIterator iterator = notifications.listIterator();
- while(iterator.hasPrevious()) {
- NotificationItem item = iterator.previous();
+ while(iterator.hasNext()) {
+ NotificationItem item = iterator.next();
builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText());
}
@@ -338,44 +372,35 @@ public class MessageNotifier {
NotificationManagerCompat.from(context).notify(SUMMARY_NOTIFICATION_ID, builder.build());
}
+ private static void removePendingNotifications(int chatId) {
+ for (Pair pendingNotification : pendingNotifications) {
+ if (pendingNotification.first() == chatId) {
+ pendingNotifications.remove(pendingNotification);
+ break;
+ }
+ }
+ }
+
private static void sendInChatNotification(Context context, int chatId) {
+ if (instance == null) {
+ Log.w(TAG, "Message notifier not initialized. Cannot play sounds");
+ return;
+ }
+
if (!Prefs.isInChatNotifications(context) ||
ServiceUtil.getAudioManager(context).getRingerMode() != AudioManager.RINGER_MODE_NORMAL)
{
return;
}
- if( Prefs.isChatMuted(context, chatId) ) {
+ if(Prefs.isChatMuted(context, chatId)) {
Log.d(TAG, "chat muted");
return;
}
- Uri uri = Prefs.getChatRingtone(context, chatId);
- if (uri == null) {
- uri = Prefs.getNotificationRingtone(context);
+ if (instance.soundInLoaded) {
+ instance.soundPool.play(instance.soundIn, 1.0f, 1.0f, 1, 0, 1.0f);
}
-
- if (uri.toString().isEmpty()) {
- Log.d(TAG, "ringtone uri is empty");
- return;
- }
-
- Ringtone ringtone = RingtoneManager.getRingtone(context, uri);
-
- if (ringtone == null) {
- Log.w(TAG, "ringtone is null");
- return;
- }
-
- if (Build.VERSION.SDK_INT >= 21) {
- ringtone.setAudioAttributes(new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
- .build());
- } else {
- ringtone.setStreamType(AudioManager.STREAM_NOTIFICATION);
- }
-
- ringtone.play();
}
private static NotificationState constructNotificationState(@NonNull ApplicationDcContext dcContext,
@@ -386,6 +411,9 @@ public class MessageNotifier {
for(int msgId : freshMessages) {
DcMsg record = dcContext.getMsg(msgId);
+ if (record.isInfo()) {
+ continue;
+ }
int id = record.getId();
boolean mms = record.isMms() || record.isMediaPending();
int chatId = record.getChatId();
@@ -474,75 +502,4 @@ public class MessageNotifier {
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
-
- private static class DelayedNotification implements Runnable {
-
- private static final long DELAY = TimeUnit.SECONDS.toMillis(5);
-
- private final AtomicBoolean canceled = new AtomicBoolean(false);
-
- private final Context context;
- private final int chatId;
- private final long delayUntil;
-
- private DelayedNotification(Context context, int chatId) {
- this.context = context;
- this.chatId = chatId;
- this.delayUntil = System.currentTimeMillis() + DELAY;
- }
-
- @Override
- public void run() {
- MessageNotifier.updateNotification(context);
-
- long delayMillis = delayUntil - System.currentTimeMillis();
- Log.w(TAG, "Waiting to notify: " + delayMillis);
-
- if (delayMillis > 0) {
- Util.sleep(delayMillis);
- }
-
- if (!canceled.get()) {
- Log.w(TAG, "Not canceled, notifying...");
- MessageNotifier.updateNotification(context, chatId, true);
- MessageNotifier.cancelDelayedNotifications();
- } else {
- Log.w(TAG, "Canceled, not notifying...");
- }
- }
-
- public void cancel() {
- canceled.set(true);
- }
- }
-
- private static class CancelableExecutor {
-
- private final Executor executor = Executors.newSingleThreadExecutor();
- private final Set tasks = new HashSet<>();
-
- public void execute(final DelayedNotification runnable) {
- synchronized (tasks) {
- tasks.add(runnable);
- }
-
- Runnable wrapper = () -> {
- runnable.run();
-
- synchronized (tasks) {
- tasks.remove(runnable);
- }
- };
-
- executor.execute(wrapper);
- }
-
- public void cancel() {
- synchronized (tasks) {
- for (DelayedNotification task : tasks) {
- task.cancel();
- }
- }
- }
- }
}
diff --git a/src/org/thoughtcrime/securesms/notifications/NotificationState.java b/src/org/thoughtcrime/securesms/notifications/NotificationState.java
index 77dd964cb..967530547 100644
--- a/src/org/thoughtcrime/securesms/notifications/NotificationState.java
+++ b/src/org/thoughtcrime/securesms/notifications/NotificationState.java
@@ -34,7 +34,7 @@ public class NotificationState {
}
public void addNotification(NotificationItem item) {
- notifications.addFirst(item);
+ notifications.add(item);
if (chats.contains(item.getChatId())) {
chats.remove(item.getChatId());