diff --git a/src/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java b/src/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java index ca8b1ecd5..6f50e8409 100644 --- a/src/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java +++ b/src/org/thoughtcrime/securesms/notifications/AbstractNotificationBuilder.java @@ -24,16 +24,19 @@ import java.math.BigInteger; import java.security.MessageDigest; import java.util.List; -public abstract class AbstractNotificationBuilder extends NotificationCompat.Builder { +abstract class AbstractNotificationBuilder extends NotificationCompat.Builder { @SuppressWarnings("unused") private static final String TAG = AbstractNotificationBuilder.class.getSimpleName(); protected Context context; protected NotificationPrivacyPreference privacy; + private int notificationId; + private Uri ringtone; + private boolean vibrate; AbstractNotificationBuilder(Context context, NotificationPrivacyPreference privacy, boolean signal) { - super(context, createMsgNotificationChannel(context, signal)); + super(context, createMsgNotificationChannel(context)); this.context = context; this.privacy = privacy; @@ -50,18 +53,17 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui return builder; } + // Alarms are not set in the notification or the notification channel but handled separately + // by the MessageNotifier. It allows us to dynamically turn on and off the sounds and as well as + // to change vibration and sounds during runtime void setAlarms(@Nullable Uri ringtone, Prefs.VibrateState vibrate) { Uri defaultRingtone = Prefs.getNotificationRingtone(context); boolean defaultVibrate = Prefs.isNotificationVibrateEnabled(context); + if (ringtone == null && !TextUtils.isEmpty(defaultRingtone.toString())) this.ringtone = defaultRingtone; + else if (ringtone != null && !ringtone.toString().isEmpty()) this.ringtone = ringtone; - if (ringtone == null && !TextUtils.isEmpty(defaultRingtone.toString())) setSound(defaultRingtone); - else if (ringtone != null && !ringtone.toString().isEmpty()) setSound(ringtone); - - if (vibrate == Prefs.VibrateState.ENABLED || - (vibrate == Prefs.VibrateState.DEFAULT && defaultVibrate)) - { - setDefaults(Notification.DEFAULT_VIBRATE); - } + this.vibrate = (vibrate == Prefs.VibrateState.ENABLED || + (vibrate == Prefs.VibrateState.DEFAULT && defaultVibrate)); } private void setLed() { @@ -79,8 +81,8 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui argb = Color.rgb(0xFF, 0xFF, 0xFF); } setLights(argb, - Integer.parseInt(blinkPatternArray[0]), - Integer.parseInt(blinkPatternArray[1])); + Integer.parseInt(blinkPatternArray[0]), + Integer.parseInt(blinkPatternArray[1])); } } @@ -106,10 +108,9 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui // - NotificationChannels have default values that have a higher precedence as the Notification.Builder setting // - once created, NotificationChannels cannot be modified programmatically // - NotificationChannels can be deleted, however, on re-creation it becomes un-deleted with the old settings - // - the idea is that sound, led, vibrate is edited by the user - // via the ACTION_CHANNEL_NOTIFICATION_SETTINGS intent that takes the channelId + // - the idea is that sound and vibrate are handled outside of the scope of the notification channel - private static String createMsgNotificationChannel(Context context, boolean signal) { + private static String createMsgNotificationChannel(Context context) { String chBase = "ch_msg2_"; String chId = chBase + "unsupported"; @@ -119,16 +120,11 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui // get all values we'll use as settings for the NotificationChannel String ledColor = Prefs.getNotificationLedColor(context); - boolean defaultVibrate = Prefs.isNotificationVibrateEnabled(context); - Uri ringtone = Prefs.getNotificationRingtone(context); // compute hash from these settings String hash = ""; MessageDigest md = MessageDigest.getInstance("SHA-256"); md.update(ledColor.getBytes()); - md.update(defaultVibrate ? (byte) 1 : (byte) 0); - md.update(ringtone.toString().getBytes()); - md.update(signal ? (byte) 1 : (byte) 0); hash = String.format("%X", new BigInteger(1, md.digest())).substring(0, 16); // get channel name @@ -139,7 +135,7 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui notificationManager.deleteNotificationChannel(oldChId); } catch (Exception e) { - ; // channel not created before + // channel not created before } Prefs.setStringPreference(context, "ch_curr_" + chBase, chId); } @@ -157,7 +153,7 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui // we cannot change the settings, however, this is handled by using different values for chId if(!channelExists) { NotificationChannel channel = new NotificationChannel(chId, - "New messages", NotificationManager.IMPORTANCE_DEFAULT); + "New messages", NotificationManager.IMPORTANCE_DEFAULT); channel.setDescription("Informs about new messages."); if (!ledColor.equals("none")) { @@ -174,19 +170,8 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui channel.enableLights(false); } - if (signal) { - channel.enableVibration(defaultVibrate); - - if (!TextUtils.isEmpty(ringtone.toString())) { - channel.setSound(ringtone, - new AudioAttributes.Builder().setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN) - .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT) - .build()); - } - } else { - channel.setSound(null, null); - channel.enableVibration(false); - } + channel.setSound(null, null); + channel.enableVibration(false); notificationManager.createNotificationChannel(channel); } @@ -202,4 +187,21 @@ public abstract class AbstractNotificationBuilder extends NotificationCompat.Bui private static boolean notificationChannelsSupported() { return Build.VERSION.SDK_INT >= 26; } + + + public void setNotificationId(int notificationId) { + this.notificationId = notificationId; + } + + public int getNotificationId() { + return this.notificationId; + } + + public Uri getRingtone() { + return this.ringtone; + } + + public boolean getVibrate() { + return this.vibrate; + } } diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java index 4065f2b95..2e8c03f2b 100644 --- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java @@ -1,27 +1,12 @@ -/* - * Copyright (C) 2011 Whisper Systems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ package org.thoughtcrime.securesms.notifications; -import android.annotation.TargetApi; import android.app.NotificationManager; import android.content.Context; import android.media.AudioManager; +import android.media.RingtoneManager; import android.media.SoundPool; -import android.service.notification.StatusBarNotification; +import android.net.Uri; +import android.os.Vibrator; import android.support.annotation.NonNull; import android.support.v4.app.NotificationManagerCompat; import android.text.TextUtils; @@ -46,334 +31,353 @@ import java.util.concurrent.TimeUnit; import static org.thoughtcrime.securesms.notifications.MessageNotifierCompat.NO_VISIBLE_CHAT_ID; import static org.thoughtcrime.securesms.notifications.MessageNotifierCompat.SUMMARY_NOTIFICATION_ID; +abstract class MessageNotifier implements IMessageNotifier { -/** - * Handles posting system notifications for new messages. - * - * - * @author Moxie Marlinspike - */ + static final String TAG = org.thoughtcrime.securesms.notifications.MessageNotifierApi23.class.getSimpleName(); -@TargetApi(23) -class MessageNotifier implements IMessageNotifier { + private static final String NOTIFICATION_GROUP = "messages"; + private static final long MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(20); + private static final long STARTUP_SILENCE_DELTA = TimeUnit.MINUTES.toMillis(1); + private static final long INITIAL_STARTUP = System.currentTimeMillis(); - private static final String TAG = MessageNotifier.class.getSimpleName(); + static volatile int visibleChatId = NO_VISIBLE_CHAT_ID; + static volatile long lastAudibleNotification = -1; + final NotificationState notificationState; + final Context appContext; + private final SoundPool soundPool; + private final int soundIn; + private final int soundOut; + private boolean soundInLoaded; + private boolean soundOutLoaded; - private static final String NOTIFICATION_GROUP = "messages"; - private static final long MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(20); - private static final long STARTUP_SILENCE_DELTA = TimeUnit.MINUTES.toMillis(1); - private static final long INITIAL_STARTUP = System.currentTimeMillis(); + MessageNotifier(Context context) { + appContext = 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); + notificationState = new NotificationState(); - static volatile int visibleChatId = NO_VISIBLE_CHAT_ID; - static volatile long lastAudibleNotification = -1; - final NotificationState notificationState; - final Context appContext; - private final SoundPool soundPool; - private final int soundIn; - private final int soundOut; - private boolean soundInLoaded; - private boolean soundOutLoaded; + soundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> { + if (status == 0) { + if (sampleId == soundIn) { + soundInLoaded = true; + } else if (sampleId == soundOut) { + soundOutLoaded = true; + } + } + }); + } - MessageNotifier(Context context) { - appContext = 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); - notificationState = new NotificationState(); - - soundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> { - if (status == 0) { - if (sampleId == soundIn) { - soundInLoaded = true; - } else if (sampleId == soundOut) { - soundOutLoaded = true; + @Override + public void playSendSound() { + if (Prefs.isInChatNotifications(appContext) && soundOutLoaded) { + soundPool.play(soundOut, 1.0f, 1.0f, 1, 0, 1.0f); } - } - }); - } - - @Override - public void playSendSound() { - if (Prefs.isInChatNotifications(appContext) && soundOutLoaded) { - soundPool.play(soundOut, 1.0f, 1.0f, 1, 0, 1.0f); - } - } - - @Override - public void updateVisibleChat(int chatId) { - visibleChatId = chatId; - if (visibleChatId != NO_VISIBLE_CHAT_ID) { - removeNotifications(visibleChatId); - } - } - - @Override - public void updateNotification(int chatId, int messageId) { - boolean isVisible = visibleChatId == chatId; - ApplicationDcContext dcContext = DcHelper.getContext(appContext); - - if (isVisible) { - dcContext.marknoticedChat(chatId); } - if (!Prefs.isNotificationsEnabled(appContext) || - Prefs.isChatMuted(appContext, chatId)) + @Override + public void updateVisibleChat(int chatId) { + visibleChatId = chatId; + if (visibleChatId != NO_VISIBLE_CHAT_ID) { + removeNotifications(visibleChatId); + } + } + + @Override + public void updateNotification(int chatId, int messageId) { + boolean isVisible = visibleChatId == chatId; + ApplicationDcContext dcContext = DcHelper.getContext(appContext); + + if (isVisible) { + dcContext.marknoticedChat(chatId); + } + + if (!Prefs.isNotificationsEnabled(appContext) || + Prefs.isChatMuted(appContext, chatId)) + { + return; + } + + if (isVisible) { + sendInChatNotification(chatId); + } else if (visibleChatId != NO_VISIBLE_CHAT_ID) { + //different chat + sendNotifications(chatId, messageId, false); + } else { + //app is in background or different Activity is on top + sendNotifications(chatId, messageId, true); + } + } + + /** + * On notification privacy preference changed, + * the notification state needs to be updated. + */ + @Override + public void onNotificationPrivacyChanged() { + if (!Prefs.isNotificationsEnabled(appContext)) { + return; + } + + clearNotifications(); + ApplicationDcContext dcContext = DcHelper.getContext(appContext); + int[] freshMessages = dcContext.getFreshMsgs(); + for (int message : freshMessages) { + DcMsg record = dcContext.getMsg(message); + updateNotification(record.getChatId(), record.getId()); + } + } + + @Override + public void removeNotifications(int[] chatIds) { + List removedItems = new LinkedList<>(); + for (int id : chatIds) { + removedItems.addAll(notificationState.removeNotificationsForChat(id)); + } + cancelNotifications(removedItems); + recreateSummaryNotification(); + } + + @Override + public void removeNotifications(int chatId) { + List removedItems = notificationState.removeNotificationsForChat(chatId); + cancelNotifications(removedItems); + recreateSummaryNotification(); + } + + void cancelNotifications(List removedItems) { + NotificationManager notifications = ServiceUtil.getNotificationManager(appContext); + for (NotificationItem item : removedItems) { + notifications.cancel(item.getId()); + } + } + + private void recreateSummaryNotification() { + NotificationManager notifications = ServiceUtil.getNotificationManager(appContext); + notifications.cancel(SUMMARY_NOTIFICATION_ID); + + if (notificationState.hasMultipleChats()) { + for (Integer id : notificationState.getChats()) { + sendSingleChatNotification(appContext, new NotificationState(notificationState.getNotificationsForChat(id)), false, true); + } + sendMultipleChatNotification(appContext, notificationState, false); + } else { + sendSingleChatNotification(appContext, notificationState, false, false); + } + } + + void cancelActiveNotifications() { + NotificationManager notifications = ServiceUtil.getNotificationManager(appContext); + notifications.cancel(SUMMARY_NOTIFICATION_ID); + } + + void sendNotifications(int chatId, int messageId, boolean signal) { + ApplicationDcContext dcContext = DcHelper.getContext(appContext); + if (signal = isSignalAllowed(signal)) { + lastAudibleNotification = System.currentTimeMillis(); + } + + addMessageToNotificationState(dcContext, chatId, messageId); + if (notificationState.hasMultipleChats()) { + for (int id : notificationState.getChats()) { + sendSingleChatNotification(appContext, new NotificationState(notificationState.getNotificationsForChat(id)), false, true); + } + sendMultipleChatNotification(appContext, notificationState, signal); + } else { + sendSingleChatNotification(appContext, notificationState, signal, false); + } + } + + boolean isSignalAllowed(boolean signalRequested) { + long now = System.currentTimeMillis(); + return signalRequested && ( + now - INITIAL_STARTUP) > STARTUP_SILENCE_DELTA && + (now - lastAudibleNotification) > MIN_AUDIBLE_PERIOD_MILLIS; + } + + private void clearNotifications() { + notificationState.reset(); + cancelActiveNotifications(); + } + + void sendSingleChatNotification(@NonNull Context context, + @NonNull NotificationState notificationState, + boolean signal, + boolean bundled) { - return; + AbstractNotificationBuilder notificationBuilder = createSingleChatNotification(context, notificationState, signal, bundled); + if (notificationBuilder != null) + notify(context, notificationBuilder.getNotificationId(), notificationBuilder, signal); } - if (isVisible) { - sendInChatNotification(chatId); - } else if (visibleChatId != NO_VISIBLE_CHAT_ID) { - //different chat - sendNotifications(chatId, messageId, false); - } else { - //app is in background or different Activity is on top - sendNotifications(chatId, messageId, true); - } - } - - /** - * On notification privacy preference changed, - * the notification state needs to be updated. - */ - @Override - public void onNotificationPrivacyChanged() { - if (!Prefs.isNotificationsEnabled(appContext)) { - return; - } - - clearNotifications(); - ApplicationDcContext dcContext = DcHelper.getContext(appContext); - int[] freshMessages = dcContext.getFreshMsgs(); - for (int message : freshMessages) { - DcMsg record = dcContext.getMsg(message); - updateNotification(record.getChatId(), record.getId()); - } - } - - @Override - public void removeNotifications(int[] chatIds) { - List removedItems = new LinkedList<>(); - for (int id : chatIds) { - removedItems.addAll(notificationState.removeNotificationsForChat(id)); - } - cancelNotifications(removedItems); - recreateSummaryNotification(); - } - - @Override - public void removeNotifications(int chatId) { - List removedItems = notificationState.removeNotificationsForChat(chatId); - cancelNotifications(removedItems); - recreateSummaryNotification(); - } - - void cancelNotifications(List removedItems) { - NotificationManager notifications = ServiceUtil.getNotificationManager(appContext); - for (NotificationItem item : removedItems) { - notifications.cancel(item.getId()); - } - } - - private void recreateSummaryNotification() { - NotificationManager notifications = ServiceUtil.getNotificationManager(appContext); - notifications.cancel(SUMMARY_NOTIFICATION_ID); - - if (notificationState.hasMultipleChats()) { - for (Integer id : notificationState.getChats()) { - sendSingleChatNotification(appContext, new NotificationState(notificationState.getNotificationsForChat(id)), false, true); - } - sendMultipleChatNotification(appContext, notificationState, false); - } else { - sendSingleChatNotification(appContext, notificationState, false, false); - } - } - - private void cancelActiveNotifications() { - NotificationManager notifications = ServiceUtil.getNotificationManager(appContext); - notifications.cancel(SUMMARY_NOTIFICATION_ID); - try { - StatusBarNotification[] activeNotifications = notifications.getActiveNotifications(); - - for (StatusBarNotification activeNotification : activeNotifications) { - notifications.cancel(activeNotification.getId()); - } - } catch (Throwable e) { - // XXX Appears to be a ROM bug, see #6043 - Log.w(TAG, e); - notifications.cancelAll(); - } - } - - void sendNotifications(int chatId, int messageId, boolean signal) { - ApplicationDcContext dcContext = DcHelper.getContext(appContext); - if (signal = isSignalAllowed(signal)) { - lastAudibleNotification = System.currentTimeMillis(); - } - - addMessageToNotificationState(dcContext, chatId, messageId); - if (notificationState.hasMultipleChats()) { - for (int id : notificationState.getChats()) { - sendSingleChatNotification(appContext, new NotificationState(notificationState.getNotificationsForChat(id)), false, true); - } - sendMultipleChatNotification(appContext, notificationState, signal); - } else { - sendSingleChatNotification(appContext, notificationState, signal, false); - } - } - - boolean isSignalAllowed(boolean signalRequested) { - long now = System.currentTimeMillis(); - return signalRequested && ( - now - INITIAL_STARTUP) > STARTUP_SILENCE_DELTA && - (now - lastAudibleNotification) > MIN_AUDIBLE_PERIOD_MILLIS; - } - - private void clearNotifications() { - notificationState.reset(); - cancelActiveNotifications(); - } - - void sendSingleChatNotification(@NonNull Context context, - @NonNull NotificationState notificationState, - boolean signal, - boolean bundled) - { - if (notificationState.getNotifications().isEmpty()) { - if (!bundled) cancelActiveNotifications(); - return; - } - - SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, Prefs.getNotificationPrivacy(context), signal); - List notifications = notificationState.getNotifications(); - NotificationItem firstItem = notifications.get(0); - Recipient recipient = firstItem.getRecipient(); - int chatId = firstItem.getChatId(); - int notificationId = (SUMMARY_NOTIFICATION_ID + (bundled ? chatId : 0)); - - builder.setChat(firstItem.getRecipient()); - builder.setMessageCount(notificationState.getMessageCount()); - builder.setPrimaryMessageBody(recipient, firstItem.getIndividualRecipient(), - firstItem.getText(""), firstItem.getSlideDeck()); - builder.setContentIntent(firstItem.getPendingIntent(context)); - builder.setGroup(NOTIFICATION_GROUP); - builder.setDeleteIntent(notificationState.getMarkAsReadIntent(context, chatId, notificationId)); - - long timestamp = firstItem.getTimestamp(); - if (timestamp != 0) builder.setWhen(timestamp); - - builder.addActions(notificationState.getMarkAsReadIntent(context, chatId, notificationId), - notificationState.getQuickReplyIntent(context, recipient), - notificationState.getRemoteReplyIntent(context, recipient)); - - ListIterator iterator = notifications.listIterator(notifications.size()); - - while(iterator.hasPrevious()) { - NotificationItem item = iterator.previous(); - builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText()); - } - - if (signal) { - builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate(context)); - builder.setTicker(firstItem.getIndividualRecipient(), - firstItem.getText()); - } - - if (!bundled) { - builder.setGroupSummary(true); - } - - NotificationManagerCompat.from(context).notify(notificationId, builder.build()); - } - - void sendMultipleChatNotification(@NonNull Context context, - @NonNull NotificationState notificationState, - boolean signal) - { - MultipleRecipientNotificationBuilder builder = new MultipleRecipientNotificationBuilder(context, Prefs.getNotificationPrivacy(context), signal); - List notifications = notificationState.getNotifications(); - NotificationItem firstItem = notifications.get(0); - - builder.setMessageCount(notificationState.getMessageCount(), notificationState.getChatCount()); - builder.setMostRecentSender(firstItem.getIndividualRecipient()); - builder.setGroup(NOTIFICATION_GROUP); - builder.setDeleteIntent(notificationState.getMarkAsReadIntent(context, 0, SUMMARY_NOTIFICATION_ID)); - - long timestamp = firstItem.getTimestamp(); - if (timestamp != 0) builder.setWhen(timestamp); - - builder.addActions(notificationState.getMarkAsReadIntent(context, 0, SUMMARY_NOTIFICATION_ID)); - - ListIterator iterator = notifications.listIterator(notifications.size()); - - while(iterator.hasPrevious()) { - NotificationItem item = iterator.previous(); - builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText()); - } - - if (signal) { - builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate(context)); - builder.setTicker(firstItem.getIndividualRecipient(), - firstItem.getText()); - } - - NotificationManagerCompat.from(context).notify(SUMMARY_NOTIFICATION_ID, builder.build()); - } - - private void sendInChatNotification(int chatId) { - if (!Prefs.isInChatNotifications(appContext) || - ServiceUtil.getAudioManager(appContext).getRingerMode() != AudioManager.RINGER_MODE_NORMAL) + void sendMultipleChatNotification(@NonNull Context context, + @NonNull NotificationState notificationState, + boolean signal) { - return; + AbstractNotificationBuilder notificationBuilder = createMultipleChatNotification(context, notificationState, signal); + if (notificationBuilder != null) + notify(context, SUMMARY_NOTIFICATION_ID, notificationBuilder, signal); } - if(Prefs.isChatMuted(appContext, chatId)) { - Log.d(TAG, "chat muted"); - return; + protected AbstractNotificationBuilder createSingleChatNotification(@NonNull Context context, + @NonNull NotificationState notificationState, + boolean signal, + boolean bundled) { + if (notificationState.getNotifications().isEmpty()) { + if (!bundled) cancelActiveNotifications(); + return null; + } + + SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, Prefs.getNotificationPrivacy(context), signal); + List notifications = notificationState.getNotifications(); + NotificationItem firstItem = notifications.get(0); + Recipient recipient = firstItem.getRecipient(); + int chatId = firstItem.getChatId(); + int notificationId = (SUMMARY_NOTIFICATION_ID + (bundled ? chatId : 0)); + + builder.setNotificationId(notificationId); + builder.setChat(firstItem.getRecipient()); + builder.setMessageCount(notificationState.getMessageCount()); + builder.setPrimaryMessageBody(recipient, firstItem.getIndividualRecipient(), + firstItem.getText(""), firstItem.getSlideDeck()); + builder.setContentIntent(firstItem.getPendingIntent(context)); + builder.setGroup(NOTIFICATION_GROUP); + builder.setDeleteIntent(notificationState.getMarkAsReadIntent(context, chatId, notificationId)); + + long timestamp = firstItem.getTimestamp(); + if (timestamp != 0) builder.setWhen(timestamp); + + builder.addActions(notificationState.getMarkAsReadIntent(context, chatId, notificationId), + notificationState.getQuickReplyIntent(context, recipient), + notificationState.getRemoteReplyIntent(context, recipient)); + + ListIterator iterator = notifications.listIterator(notifications.size()); + + while(iterator.hasPrevious()) { + NotificationItem item = iterator.previous(); + builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText()); + } + + if (signal) { + builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate(context)); + builder.setTicker(firstItem.getIndividualRecipient(), + firstItem.getText()); + } + + if (!bundled) { + builder.setGroupSummary(true); + } + + return builder; + + } - if (soundInLoaded) { - soundPool.play(soundIn, 1.0f, 1.0f, 1, 0, 1.0f); - } - } + void playNotificationSound(Uri ringtone, boolean vibrate) { + RingtoneManager.getRingtone(appContext, ringtone).play(); + if (vibrate) { - void addMessageToNotificationState(ApplicationDcContext dcContext, int chatId, int msgId) { - if (Prefs.isChatMuted(appContext, chatId)) { - return; - } - - DcMsg record = dcContext.getMsg(msgId); - if (record.isInfo()) { - return; - } - - int id = record.getId(); - CharSequence body = record.getDisplayBody(); - DcMsg dcMsg = dcContext.getMsg(msgId); - Recipient chatRecipient = new Recipient(appContext, dcContext.getChat(dcMsg.getChatId()), null); - Recipient individualRecipient = new Recipient(appContext, null, dcContext.getContact(dcMsg.getFromId())); - SlideDeck slideDeck = new SlideDeck(dcContext.context, record); - long timestamp = record.getTimestamp(); - - - if(slideDeck.getSlides().isEmpty()) - slideDeck = null; - - // TODO: if message content should be hidden on screen lock, do it here. - if (record.hasFile() && TextUtils.isEmpty(body)) { - String summaryText = record.getSummarytext(100); - if (summaryText.isEmpty()) { - body = SpanUtil.italic(appContext.getString(R.string.notify_media_message)); - } else { - body = SpanUtil.italic(summaryText); - } - } else if (record.hasFile() && !record.isMediaPending()) { - String message = appContext.getString(R.string.notify_media_message_with_text, body); - int italicLength = message.length() - body.length(); - body = SpanUtil.italic(message, italicLength); + Vibrator v = (Vibrator) appContext.getSystemService(Context.VIBRATOR_SERVICE); + v.vibrate(100); + v.vibrate(200); + } } - notificationState.addNotification(new NotificationItem(id, chatRecipient, individualRecipient, chatId, body, timestamp, slideDeck)); - } + + protected AbstractNotificationBuilder createMultipleChatNotification(@NonNull Context context, + @NonNull NotificationState notificationState, + boolean signal) { + MultipleRecipientNotificationBuilder builder = new MultipleRecipientNotificationBuilder(context, Prefs.getNotificationPrivacy(context), signal); + List notifications = notificationState.getNotifications(); + NotificationItem firstItem = notifications.get(0); + + builder.setMessageCount(notificationState.getMessageCount(), notificationState.getChatCount()); + builder.setMostRecentSender(firstItem.getIndividualRecipient()); + builder.setGroup(NOTIFICATION_GROUP); + builder.setDeleteIntent(notificationState.getMarkAsReadIntent(context, 0, SUMMARY_NOTIFICATION_ID)); + + long timestamp = firstItem.getTimestamp(); + if (timestamp != 0) builder.setWhen(timestamp); + + builder.addActions(notificationState.getMarkAsReadIntent(context, 0, SUMMARY_NOTIFICATION_ID)); + + ListIterator iterator = notifications.listIterator(notifications.size()); + + while(iterator.hasPrevious()) { + NotificationItem item = iterator.previous(); + builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText()); + } + + if (signal) { + builder.setAlarms(notificationState.getRingtone(context), notificationState.getVibrate(context)); + builder.setTicker(firstItem.getIndividualRecipient(), + firstItem.getText()); + } + + return builder; + } + + private void notify(Context context, int notificationId, AbstractNotificationBuilder notificationBuilder, boolean signal) { + if (signal) { + playNotificationSound(notificationBuilder.getRingtone(), notificationBuilder.getVibrate()); + } + NotificationManagerCompat.from(context).notify(notificationId, notificationBuilder.build()); + } + + private void sendInChatNotification(int chatId) { + if (!Prefs.isInChatNotifications(appContext) || + ServiceUtil.getAudioManager(appContext).getRingerMode() != AudioManager.RINGER_MODE_NORMAL) + { + return; + } + + if(Prefs.isChatMuted(appContext, chatId)) { + Log.d(TAG, "chat muted"); + return; + } + + if (soundInLoaded) { + soundPool.play(soundIn, 1.0f, 1.0f, 1, 0, 1.0f); + } + } + + void addMessageToNotificationState(ApplicationDcContext dcContext, int chatId, int msgId) { + if (Prefs.isChatMuted(appContext, chatId)) { + return; + } + + DcMsg record = dcContext.getMsg(msgId); + if (record.isInfo()) { + return; + } + + int id = record.getId(); + CharSequence body = record.getDisplayBody(); + DcMsg dcMsg = dcContext.getMsg(msgId); + Recipient chatRecipient = new Recipient(appContext, dcContext.getChat(dcMsg.getChatId()), null); + Recipient individualRecipient = new Recipient(appContext, null, dcContext.getContact(dcMsg.getFromId())); + SlideDeck slideDeck = new SlideDeck(dcContext.context, record); + long timestamp = record.getTimestamp(); + + + if(slideDeck.getSlides().isEmpty()) + slideDeck = null; + + // TODO: if message content should be hidden on screen lock, do it here. + if (record.hasFile() && TextUtils.isEmpty(body)) { + String summaryText = record.getSummarytext(100); + if (summaryText.isEmpty()) { + body = SpanUtil.italic(appContext.getString(R.string.notify_media_message)); + } else { + body = SpanUtil.italic(summaryText); + } + } else if (record.hasFile() && !record.isMediaPending()) { + String message = appContext.getString(R.string.notify_media_message_with_text, body); + int italicLength = message.length() - body.length(); + body = SpanUtil.italic(message, italicLength); + } + + notificationState.addNotification(new NotificationItem(id, chatRecipient, individualRecipient, chatId, body, timestamp, slideDeck)); + } } + diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifierApi23.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifierApi23.java new file mode 100644 index 000000000..2461392f9 --- /dev/null +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifierApi23.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011 Whisper Systems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.thoughtcrime.securesms.notifications; + +import android.annotation.TargetApi; +import android.app.NotificationManager; +import android.content.Context; +import android.service.notification.StatusBarNotification; +import android.util.Log; + +import org.thoughtcrime.securesms.util.ServiceUtil; + + +/** + * Handles posting system notifications for new messages. + * + */ + +@TargetApi(23) +class MessageNotifierApi23 extends MessageNotifier { + + MessageNotifierApi23(Context context) { + super(context); + } + + void cancelActiveNotifications() { + super.cancelActiveNotifications(); + NotificationManager notifications = ServiceUtil.getNotificationManager(appContext); + try { + + StatusBarNotification[] activeNotifications = notifications.getActiveNotifications(); + for (StatusBarNotification activeNotification : activeNotifications) { + notifications.cancel(activeNotification.getId()); + } + } catch (Throwable e) { + // XXX Appears to be a ROM bug, see #6043 + Log.w(TAG, e); + notifications.cancelAll(); + } + } + + } diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifierCompat.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifierCompat.java index f2dd4abc9..610319668 100644 --- a/src/org/thoughtcrime/securesms/notifications/MessageNotifierCompat.java +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifierCompat.java @@ -20,7 +20,7 @@ public class MessageNotifierCompat { if (Build.VERSION.SDK_INT < 23) { instance = new MessageNotifierPreApi23(context); } else { - instance = new MessageNotifier(context); + instance = new MessageNotifierApi23(context); } } diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifierPreApi23.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifierPreApi23.java index 8e6b37760..eb63ce5a9 100644 --- a/src/org/thoughtcrime/securesms/notifications/MessageNotifierPreApi23.java +++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifierPreApi23.java @@ -10,7 +10,7 @@ import java.util.List; import static org.thoughtcrime.securesms.notifications.MessageNotifierCompat.SUMMARY_NOTIFICATION_ID; -public class MessageNotifierPreApi23 extends MessageNotifier { +class MessageNotifierPreApi23 extends MessageNotifier { MessageNotifierPreApi23(Context context) { super(context); @@ -39,7 +39,7 @@ public class MessageNotifierPreApi23 extends MessageNotifier { void sendNotifications(int chatId, int messageId, boolean signal) { ApplicationDcContext dcContext = DcHelper.getContext(appContext); if (signal = isSignalAllowed(signal)) { - lastAudibleNotification = System.currentTimeMillis();; + lastAudibleNotification = System.currentTimeMillis(); } addMessageToNotificationState(dcContext, chatId, messageId);