Refactor notification thumbnails to reduce chances for ANR.
This commit is contained in:
parent
a9fc5622cd
commit
1e499fd12f
13 changed files with 213 additions and 72 deletions
|
@ -34,7 +34,7 @@ import org.thoughtcrime.securesms.database.MessageDatabase.SyncMessageId;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageId;
|
import org.thoughtcrime.securesms.database.model.MessageId;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
|
import org.thoughtcrime.securesms.notifications.v2.DefaultMessageNotifier;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.signal.core.util.CursorUtil;
|
import org.signal.core.util.CursorUtil;
|
||||||
|
@ -251,9 +251,9 @@ public class MmsSmsDatabase extends Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cursor getMessagesForNotificationState(Collection<MessageNotifierV2.StickyThread> stickyThreads) {
|
public Cursor getMessagesForNotificationState(Collection<DefaultMessageNotifier.StickyThread> stickyThreads) {
|
||||||
StringBuilder stickyQuery = new StringBuilder();
|
StringBuilder stickyQuery = new StringBuilder();
|
||||||
for (MessageNotifierV2.StickyThread stickyThread : stickyThreads) {
|
for (DefaultMessageNotifier.StickyThread stickyThread : stickyThreads) {
|
||||||
if (stickyQuery.length() > 0) {
|
if (stickyQuery.length() > 0) {
|
||||||
stickyQuery.append(" OR ");
|
stickyQuery.append(" OR ");
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import com.annimon.stream.Stream;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
|
import org.thoughtcrime.securesms.notifications.v2.DefaultMessageNotifier;
|
||||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
import org.thoughtcrime.securesms.util.BubbleUtil;
|
import org.thoughtcrime.securesms.util.BubbleUtil;
|
||||||
|
@ -47,7 +47,7 @@ public final class NotificationCancellationHelper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels all Message-Based notifications. Specifically, this is any notification that is not the
|
* Cancels all Message-Based notifications. Specifically, this is any notification that is not the
|
||||||
* summary notification assigned to the {@link MessageNotifierV2#NOTIFICATION_GROUP} group.
|
* summary notification assigned to the {@link DefaultMessageNotifier#NOTIFICATION_GROUP} group.
|
||||||
*
|
*
|
||||||
* We utilize our wrapped cancellation methods and a counter to make sure that we do not lose
|
* We utilize our wrapped cancellation methods and a counter to make sure that we do not lose
|
||||||
* bubble notifications that do not have unread messages in them.
|
* bubble notifications that do not have unread messages in them.
|
||||||
|
@ -113,7 +113,7 @@ public final class NotificationCancellationHelper {
|
||||||
@RequiresApi(23)
|
@RequiresApi(23)
|
||||||
private static boolean isSingleThreadNotification(@NonNull StatusBarNotification statusBarNotification) {
|
private static boolean isSingleThreadNotification(@NonNull StatusBarNotification statusBarNotification) {
|
||||||
return statusBarNotification.getId() != NotificationIds.MESSAGE_SUMMARY &&
|
return statusBarNotification.getId() != NotificationIds.MESSAGE_SUMMARY &&
|
||||||
Objects.equals(statusBarNotification.getNotification().getGroup(), MessageNotifierV2.NOTIFICATION_GROUP);
|
Objects.equals(statusBarNotification.getNotification().getGroup(), DefaultMessageNotifier.NOTIFICATION_GROUP);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -10,7 +10,7 @@ import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.signal.core.util.ExceptionUtil;
|
import org.signal.core.util.ExceptionUtil;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
|
import org.thoughtcrime.securesms.notifications.v2.DefaultMessageNotifier;
|
||||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.BubbleUtil;
|
import org.thoughtcrime.securesms.util.BubbleUtil;
|
||||||
|
@ -24,12 +24,12 @@ import java.util.Optional;
|
||||||
public class OptimizedMessageNotifier implements MessageNotifier {
|
public class OptimizedMessageNotifier implements MessageNotifier {
|
||||||
|
|
||||||
private final LeakyBucketLimiter limiter;
|
private final LeakyBucketLimiter limiter;
|
||||||
private final MessageNotifierV2 messageNotifierV2;
|
private final DefaultMessageNotifier defaultMessageNotifier;
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
public OptimizedMessageNotifier(@NonNull Application context) {
|
public OptimizedMessageNotifier(@NonNull Application context) {
|
||||||
this.limiter = new LeakyBucketLimiter(5, 1000, new Handler(SignalExecutors.getAndStartHandlerThread("signal-notifier").getLooper()));
|
this.limiter = new LeakyBucketLimiter(5, 1000, new Handler(SignalExecutors.getAndStartHandlerThread("signal-notifier").getLooper()));
|
||||||
this.messageNotifierV2 = new MessageNotifierV2(context);
|
this.defaultMessageNotifier = new DefaultMessageNotifier(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -119,6 +119,6 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessageNotifier getNotifier() {
|
private MessageNotifier getNotifier() {
|
||||||
return messageNotifierV2;
|
return defaultMessageNotifier;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.thoughtcrime.securesms.database.model.ParentStoryId;
|
||||||
import org.thoughtcrime.securesms.database.model.StoryType;
|
import org.thoughtcrime.securesms.database.model.StoryType;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||||
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
|
import org.thoughtcrime.securesms.notifications.v2.DefaultMessageNotifier;
|
||||||
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
import org.thoughtcrime.securesms.notifications.v2.ConversationId;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||||
|
@ -67,7 +67,7 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
final RecipientId recipientId = intent.getParcelableExtra(RECIPIENT_EXTRA);
|
final RecipientId recipientId = intent.getParcelableExtra(RECIPIENT_EXTRA);
|
||||||
final ReplyMethod replyMethod = (ReplyMethod) intent.getSerializableExtra(REPLY_METHOD);
|
final ReplyMethod replyMethod = (ReplyMethod) intent.getSerializableExtra(REPLY_METHOD);
|
||||||
final CharSequence responseText = remoteInput.getCharSequence(MessageNotifierV2.EXTRA_REMOTE_REPLY);
|
final CharSequence responseText = remoteInput.getCharSequence(DefaultMessageNotifier.EXTRA_REMOTE_REPLY);
|
||||||
final long groupStoryId = intent.getLongExtra(GROUP_STORY_ID_EXTRA, Long.MIN_VALUE);
|
final long groupStoryId = intent.getLongExtra(GROUP_STORY_ID_EXTRA, Long.MIN_VALUE);
|
||||||
|
|
||||||
if (recipientId == null) throw new AssertionError("No recipientId specified");
|
if (recipientId == null) throw new AssertionError("No recipientId specified");
|
||||||
|
|
|
@ -44,14 +44,14 @@ import kotlin.math.max
|
||||||
/**
|
/**
|
||||||
* MessageNotifier implementation using the new system for creating and showing notifications.
|
* MessageNotifier implementation using the new system for creating and showing notifications.
|
||||||
*/
|
*/
|
||||||
class MessageNotifierV2(context: Application) : MessageNotifier {
|
class DefaultMessageNotifier(context: Application) : MessageNotifier {
|
||||||
@Volatile private var visibleThread: ConversationId? = null
|
@Volatile private var visibleThread: ConversationId? = null
|
||||||
@Volatile private var lastDesktopActivityTimestamp: Long = -1
|
@Volatile private var lastDesktopActivityTimestamp: Long = -1
|
||||||
@Volatile private var lastAudibleNotification: Long = -1
|
@Volatile private var lastAudibleNotification: Long = -1
|
||||||
@Volatile private var lastScheduledReminder: Long = 0
|
@Volatile private var lastScheduledReminder: Long = 0
|
||||||
@Volatile private var previousLockedStatus: Boolean = KeyCachingService.isLocked(context)
|
@Volatile private var previousLockedStatus: Boolean = KeyCachingService.isLocked(context)
|
||||||
@Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy
|
@Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = SignalStore.settings().messageNotificationsPrivacy
|
||||||
@Volatile private var previousState: NotificationStateV2 = NotificationStateV2.EMPTY
|
@Volatile private var previousState: NotificationState = NotificationState.EMPTY
|
||||||
|
|
||||||
private val threadReminders: MutableMap<ConversationId, Reminder> = ConcurrentHashMap()
|
private val threadReminders: MutableMap<ConversationId, Reminder> = ConcurrentHashMap()
|
||||||
private val stickyThreads: MutableMap<ConversationId, StickyThread> = mutableMapOf()
|
private val stickyThreads: MutableMap<ConversationId, StickyThread> = mutableMapOf()
|
||||||
|
@ -132,7 +132,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
val notificationProfile: NotificationProfile? = NotificationProfiles.getActiveProfile(SignalDatabase.notificationProfiles.getProfiles())
|
val notificationProfile: NotificationProfile? = NotificationProfiles.getActiveProfile(SignalDatabase.notificationProfiles.getProfiles())
|
||||||
|
|
||||||
Log.internal().i(TAG, "sticky thread: $stickyThreads active profile: ${notificationProfile?.id ?: "none" }")
|
Log.internal().i(TAG, "sticky thread: $stickyThreads active profile: ${notificationProfile?.id ?: "none" }")
|
||||||
var state: NotificationStateV2 = NotificationStateProvider.constructNotificationState(stickyThreads, notificationProfile)
|
var state: NotificationState = NotificationStateProvider.constructNotificationState(stickyThreads, notificationProfile)
|
||||||
Log.internal().i(TAG, "state: $state")
|
Log.internal().i(TAG, "state: $state")
|
||||||
|
|
||||||
if (state.muteFilteredMessages.isNotEmpty()) {
|
if (state.muteFilteredMessages.isNotEmpty()) {
|
||||||
|
@ -208,13 +208,14 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
lastAudibleNotification = System.currentTimeMillis()
|
lastAudibleNotification = System.currentTimeMillis()
|
||||||
|
|
||||||
updateReminderTimestamps(context, alertOverrides, threadsThatAlerted)
|
updateReminderTimestamps(context, alertOverrides, threadsThatAlerted)
|
||||||
|
NotificationThumbnails.removeAllExcept(state.notificationItems)
|
||||||
|
|
||||||
ServiceUtil.getNotificationManager(context).cancelOrphanedNotifications(context, state, stickyThreads.map { it.value.notificationId }.toSet())
|
ServiceUtil.getNotificationManager(context).cancelOrphanedNotifications(context, state, stickyThreads.map { it.value.notificationId }.toSet())
|
||||||
updateBadge(context, state.messageCount)
|
updateBadge(context, state.messageCount)
|
||||||
|
|
||||||
val smsIds: MutableList<Long> = mutableListOf()
|
val smsIds: MutableList<Long> = mutableListOf()
|
||||||
val mmsIds: MutableList<Long> = mutableListOf()
|
val mmsIds: MutableList<Long> = mutableListOf()
|
||||||
for (item: NotificationItemV2 in state.notificationItems) {
|
for (item: NotificationItem in state.notificationItems) {
|
||||||
if (item.isMms) {
|
if (item.isMms) {
|
||||||
mmsIds.add(item.id)
|
mmsIds.add(item.id)
|
||||||
} else {
|
} else {
|
||||||
|
@ -301,7 +302,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG: String = Log.tag(MessageNotifierV2::class.java)
|
val TAG: String = Log.tag(DefaultMessageNotifier::class.java)
|
||||||
|
|
||||||
private val REMINDER_TIMEOUT: Long = TimeUnit.MINUTES.toMillis(2)
|
private val REMINDER_TIMEOUT: Long = TimeUnit.MINUTES.toMillis(2)
|
||||||
val MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(2)
|
val MIN_AUDIBLE_PERIOD_MILLIS = TimeUnit.SECONDS.toMillis(2)
|
||||||
|
@ -339,12 +340,12 @@ private fun NotificationManager.getDisplayedNotificationIds(): Result<Set<Int>>
|
||||||
return try {
|
return try {
|
||||||
Result.success(activeNotifications.filter { it.isMessageNotification() }.map { it.id }.toSet())
|
Result.success(activeNotifications.filter { it.isMessageNotification() }.map { it.id }.toSet())
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.w(MessageNotifierV2.TAG, e)
|
Log.w(DefaultMessageNotifier.TAG, e)
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NotificationManager.cancelOrphanedNotifications(context: Context, state: NotificationStateV2, stickyNotifications: Set<Int>) {
|
private fun NotificationManager.cancelOrphanedNotifications(context: Context, state: NotificationState, stickyNotifications: Set<Int>) {
|
||||||
if (Build.VERSION.SDK_INT < 24) {
|
if (Build.VERSION.SDK_INT < 24) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -354,13 +355,13 @@ private fun NotificationManager.cancelOrphanedNotifications(context: Context, st
|
||||||
.map { it.id }
|
.map { it.id }
|
||||||
.filterNot { state.notificationIds.contains(it) }
|
.filterNot { state.notificationIds.contains(it) }
|
||||||
.forEach { id ->
|
.forEach { id ->
|
||||||
Log.d(MessageNotifierV2.TAG, "Cancelling orphaned notification: $id")
|
Log.d(DefaultMessageNotifier.TAG, "Cancelling orphaned notification: $id")
|
||||||
NotificationCancellationHelper.cancel(context, id)
|
NotificationCancellationHelper.cancel(context, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCancellationHelper.cancelMessageSummaryIfSoleNotification(context)
|
NotificationCancellationHelper.cancelMessageSummaryIfSoleNotification(context)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
Log.w(MessageNotifierV2.TAG, e)
|
Log.w(DefaultMessageNotifier.TAG, e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
abstract fun setOnlyAlertOnce(onlyAlertOnce: Boolean)
|
abstract fun setOnlyAlertOnce(onlyAlertOnce: Boolean)
|
||||||
abstract fun setGroupSummary(isGroupSummary: Boolean)
|
abstract fun setGroupSummary(isGroupSummary: Boolean)
|
||||||
abstract fun setSubText(subText: String)
|
abstract fun setSubText(subText: String)
|
||||||
abstract fun addMarkAsReadActionActual(state: NotificationStateV2)
|
abstract fun addMarkAsReadActionActual(state: NotificationState)
|
||||||
abstract fun setPriority(priority: Int)
|
abstract fun setPriority(priority: Int)
|
||||||
abstract fun setAlarms(recipient: Recipient?)
|
abstract fun setAlarms(recipient: Recipient?)
|
||||||
abstract fun setTicker(ticker: CharSequence?)
|
abstract fun setTicker(ticker: CharSequence?)
|
||||||
|
@ -79,7 +79,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
protected abstract fun setWhen(timestamp: Long)
|
protected abstract fun setWhen(timestamp: Long)
|
||||||
protected abstract fun addActions(replyMethod: ReplyMethod, conversation: NotificationConversation)
|
protected abstract fun addActions(replyMethod: ReplyMethod, conversation: NotificationConversation)
|
||||||
protected abstract fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean)
|
protected abstract fun addMessagesActual(conversation: NotificationConversation, includeShortcut: Boolean)
|
||||||
protected abstract fun addMessagesActual(state: NotificationStateV2)
|
protected abstract fun addMessagesActual(state: NotificationState)
|
||||||
protected abstract fun setBubbleMetadataActual(conversation: NotificationConversation, bubbleState: BubbleUtil.BubbleState)
|
protected abstract fun setBubbleMetadataActual(conversation: NotificationConversation, bubbleState: BubbleUtil.BubbleState)
|
||||||
protected abstract fun setLights(@ColorInt color: Int, onTime: Int, offTime: Int)
|
protected abstract fun setLights(@ColorInt color: Int, onTime: Int, offTime: Int)
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setWhen(notificationItem: NotificationItemV2?) {
|
fun setWhen(notificationItem: NotificationItem?) {
|
||||||
if (notificationItem != null && notificationItem.timestamp != 0L) {
|
if (notificationItem != null && notificationItem.timestamp != 0L) {
|
||||||
setWhen(notificationItem.timestamp)
|
setWhen(notificationItem.timestamp)
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addMarkAsReadAction(state: NotificationStateV2) {
|
fun addMarkAsReadAction(state: NotificationState) {
|
||||||
if (privacy.isDisplayMessage && isNotLocked) {
|
if (privacy.isDisplayMessage && isNotLocked) {
|
||||||
addMarkAsReadActionActual(state)
|
addMarkAsReadActionActual(state)
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
addMessagesActual(conversation, privacy.isDisplayContact)
|
addMessagesActual(conversation, privacy.isDisplayContact)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addMessages(state: NotificationStateV2) {
|
fun addMessages(state: NotificationState) {
|
||||||
if (privacy.isDisplayNothing) {
|
if (privacy.isDisplayNothing) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -207,7 +207,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
val label: String = context.getString(replyMethod.toLongDescription())
|
val label: String = context.getString(replyMethod.toLongDescription())
|
||||||
val replyAction: NotificationCompat.Action = if (Build.VERSION.SDK_INT >= 24) {
|
val replyAction: NotificationCompat.Action = if (Build.VERSION.SDK_INT >= 24) {
|
||||||
NotificationCompat.Action.Builder(R.drawable.ic_reply_white_36dp, actionName, remoteReply)
|
NotificationCompat.Action.Builder(R.drawable.ic_reply_white_36dp, actionName, remoteReply)
|
||||||
.addRemoteInput(RemoteInput.Builder(MessageNotifierV2.EXTRA_REMOTE_REPLY).setLabel(label).build())
|
.addRemoteInput(RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
|
||||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
||||||
.setShowsUserInterface(false)
|
.setShowsUserInterface(false)
|
||||||
.build()
|
.build()
|
||||||
|
@ -216,7 +216,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
val wearableReplyAction = NotificationCompat.Action.Builder(R.drawable.ic_reply, actionName, remoteReply)
|
val wearableReplyAction = NotificationCompat.Action.Builder(R.drawable.ic_reply, actionName, remoteReply)
|
||||||
.addRemoteInput(RemoteInput.Builder(MessageNotifierV2.EXTRA_REMOTE_REPLY).setLabel(label).build())
|
.addRemoteInput(RemoteInput.Builder(DefaultMessageNotifier.EXTRA_REMOTE_REPLY).setLabel(label).build())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
builder.addAction(replyAction)
|
builder.addAction(replyAction)
|
||||||
|
@ -226,7 +226,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
builder.extend(extender)
|
builder.extend(extender)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addMarkAsReadActionActual(state: NotificationStateV2) {
|
override fun addMarkAsReadActionActual(state: NotificationState) {
|
||||||
val markAllAsReadAction = NotificationCompat.Action(R.drawable.check, context.getString(R.string.MessageNotifier_mark_all_as_read), state.getMarkAsReadIntent(context))
|
val markAllAsReadAction = NotificationCompat.Action(R.drawable.check, context.getString(R.string.MessageNotifier_mark_all_as_read), state.getMarkAsReadIntent(context))
|
||||||
builder.addAction(markAllAsReadAction)
|
builder.addAction(markAllAsReadAction)
|
||||||
builder.extend(NotificationCompat.WearableExtender().addAction(markAllAsReadAction))
|
builder.extend(NotificationCompat.WearableExtender().addAction(markAllAsReadAction))
|
||||||
|
@ -286,14 +286,14 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
builder.setStyle(messagingStyle)
|
builder.setStyle(messagingStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addMessagesActual(state: NotificationStateV2) {
|
override fun addMessagesActual(state: NotificationState) {
|
||||||
if (Build.VERSION.SDK_INT >= 24) {
|
if (Build.VERSION.SDK_INT >= 24) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val style: NotificationCompat.InboxStyle = NotificationCompat.InboxStyle()
|
val style: NotificationCompat.InboxStyle = NotificationCompat.InboxStyle()
|
||||||
|
|
||||||
for (notificationItem: NotificationItemV2 in state.notificationItems) {
|
for (notificationItem: NotificationItem in state.notificationItems) {
|
||||||
val line: CharSequence? = notificationItem.getInboxLine(context)
|
val line: CharSequence? = notificationItem.getInboxLine(context)
|
||||||
if (line != null) {
|
if (line != null) {
|
||||||
style.addLine(line)
|
style.addLine(line)
|
||||||
|
|
|
@ -36,9 +36,9 @@ import java.lang.NullPointerException
|
||||||
data class NotificationConversation(
|
data class NotificationConversation(
|
||||||
val recipient: Recipient,
|
val recipient: Recipient,
|
||||||
val thread: ConversationId,
|
val thread: ConversationId,
|
||||||
val notificationItems: List<NotificationItemV2>
|
val notificationItems: List<NotificationItem>
|
||||||
) {
|
) {
|
||||||
val mostRecentNotification: NotificationItemV2 = notificationItems.last()
|
val mostRecentNotification: NotificationItem = notificationItems.last()
|
||||||
val notificationId: Int = NotificationIds.getNotificationIdForThread(thread)
|
val notificationId: Int = NotificationIds.getNotificationIdForThread(thread)
|
||||||
val sortKey: Long = Long.MAX_VALUE - mostRecentNotification.timestamp
|
val sortKey: Long = Long.MAX_VALUE - mostRecentNotification.timestamp
|
||||||
val messageCount: Int = notificationItems.size
|
val messageCount: Int = notificationItems.size
|
||||||
|
|
|
@ -37,18 +37,18 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
*/
|
*/
|
||||||
object NotificationFactory {
|
object NotificationFactory {
|
||||||
|
|
||||||
val TAG = Log.tag(NotificationFactory::class.java)
|
val TAG: String = Log.tag(NotificationFactory::class.java)
|
||||||
|
|
||||||
fun notify(
|
fun notify(
|
||||||
context: Context,
|
context: Context,
|
||||||
state: NotificationStateV2,
|
state: NotificationState,
|
||||||
visibleThread: ConversationId?,
|
visibleThread: ConversationId?,
|
||||||
targetThread: ConversationId?,
|
targetThread: ConversationId?,
|
||||||
defaultBubbleState: BubbleUtil.BubbleState,
|
defaultBubbleState: BubbleUtil.BubbleState,
|
||||||
lastAudibleNotification: Long,
|
lastAudibleNotification: Long,
|
||||||
notificationConfigurationChanged: Boolean,
|
notificationConfigurationChanged: Boolean,
|
||||||
alertOverrides: Set<ConversationId>,
|
alertOverrides: Set<ConversationId>,
|
||||||
previousState: NotificationStateV2
|
previousState: NotificationState
|
||||||
): Set<ConversationId> {
|
): Set<ConversationId> {
|
||||||
if (state.isEmpty) {
|
if (state.isEmpty) {
|
||||||
Log.d(TAG, "State is empty, bailing")
|
Log.d(TAG, "State is empty, bailing")
|
||||||
|
@ -85,7 +85,7 @@ object NotificationFactory {
|
||||||
|
|
||||||
private fun notify19(
|
private fun notify19(
|
||||||
context: Context,
|
context: Context,
|
||||||
state: NotificationStateV2,
|
state: NotificationState,
|
||||||
visibleThread: ConversationId?,
|
visibleThread: ConversationId?,
|
||||||
targetThread: ConversationId?,
|
targetThread: ConversationId?,
|
||||||
defaultBubbleState: BubbleUtil.BubbleState,
|
defaultBubbleState: BubbleUtil.BubbleState,
|
||||||
|
@ -127,7 +127,7 @@ object NotificationFactory {
|
||||||
@TargetApi(24)
|
@TargetApi(24)
|
||||||
private fun notify24(
|
private fun notify24(
|
||||||
context: Context,
|
context: Context,
|
||||||
state: NotificationStateV2,
|
state: NotificationState,
|
||||||
visibleThread: ConversationId?,
|
visibleThread: ConversationId?,
|
||||||
targetThread: ConversationId?,
|
targetThread: ConversationId?,
|
||||||
defaultBubbleState: BubbleUtil.BubbleState,
|
defaultBubbleState: BubbleUtil.BubbleState,
|
||||||
|
@ -135,7 +135,7 @@ object NotificationFactory {
|
||||||
notificationConfigurationChanged: Boolean,
|
notificationConfigurationChanged: Boolean,
|
||||||
alertOverrides: Set<ConversationId>,
|
alertOverrides: Set<ConversationId>,
|
||||||
nonVisibleThreadCount: Int,
|
nonVisibleThreadCount: Int,
|
||||||
previousState: NotificationStateV2
|
previousState: NotificationState
|
||||||
): Set<ConversationId> {
|
): Set<ConversationId> {
|
||||||
val threadsThatNewlyAlerted: MutableSet<ConversationId> = mutableSetOf()
|
val threadsThatNewlyAlerted: MutableSet<ConversationId> = mutableSetOf()
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ object NotificationFactory {
|
||||||
setSmallIcon(R.drawable.ic_notification)
|
setSmallIcon(R.drawable.ic_notification)
|
||||||
setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
|
setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
|
||||||
setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||||
setGroup(MessageNotifierV2.NOTIFICATION_GROUP)
|
setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP)
|
||||||
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||||
setChannelId(conversation.getChannelId(context))
|
setChannelId(conversation.getChannelId(context))
|
||||||
setContentTitle(conversation.getContentTitle(context))
|
setContentTitle(conversation.getContentTitle(context))
|
||||||
|
@ -224,7 +224,7 @@ object NotificationFactory {
|
||||||
NotificationManagerCompat.from(context).safelyNotify(context, conversation.recipient, notificationId, builder.build())
|
NotificationManagerCompat.from(context).safelyNotify(context, conversation.recipient, notificationId, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun notifySummary(context: Context, state: NotificationStateV2) {
|
private fun notifySummary(context: Context, state: NotificationState) {
|
||||||
if (state.messageCount == 0) {
|
if (state.messageCount == 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -235,7 +235,7 @@ object NotificationFactory {
|
||||||
setSmallIcon(R.drawable.ic_notification)
|
setSmallIcon(R.drawable.ic_notification)
|
||||||
setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
|
setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
|
||||||
setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||||
setGroup(MessageNotifierV2.NOTIFICATION_GROUP)
|
setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP)
|
||||||
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||||
setChannelId(NotificationChannels.getMessagesChannel(context))
|
setChannelId(NotificationChannels.getMessagesChannel(context))
|
||||||
setContentTitle(context.getString(R.string.app_name))
|
setContentTitle(context.getString(R.string.app_name))
|
||||||
|
@ -263,7 +263,7 @@ object NotificationFactory {
|
||||||
private fun notifyInThread(context: Context, recipient: Recipient, lastAudibleNotification: Long) {
|
private fun notifyInThread(context: Context, recipient: Recipient, lastAudibleNotification: Long) {
|
||||||
if (!SignalStore.settings().isMessageNotificationsInChatSoundsEnabled ||
|
if (!SignalStore.settings().isMessageNotificationsInChatSoundsEnabled ||
|
||||||
ServiceUtil.getAudioManager(context).ringerMode != AudioManager.RINGER_MODE_NORMAL ||
|
ServiceUtil.getAudioManager(context).ringerMode != AudioManager.RINGER_MODE_NORMAL ||
|
||||||
(System.currentTimeMillis() - lastAudibleNotification) < MessageNotifierV2.MIN_AUDIBLE_PERIOD_MILLIS
|
(System.currentTimeMillis() - lastAudibleNotification) < DefaultMessageNotifier.MIN_AUDIBLE_PERIOD_MILLIS
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -378,7 +378,7 @@ object NotificationFactory {
|
||||||
setSmallIcon(R.drawable.ic_notification)
|
setSmallIcon(R.drawable.ic_notification)
|
||||||
setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
|
setColor(ContextCompat.getColor(context, R.color.core_ultramarine))
|
||||||
setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
setCategory(NotificationCompat.CATEGORY_MESSAGE)
|
||||||
setGroup(MessageNotifierV2.NOTIFICATION_GROUP)
|
setGroup(DefaultMessageNotifier.NOTIFICATION_GROUP)
|
||||||
setChannelId(conversation.getChannelId(context))
|
setChannelId(conversation.getChannelId(context))
|
||||||
setContentTitle(conversation.getContentTitle(context))
|
setContentTitle(conversation.getContentTitle(context))
|
||||||
setLargeIcon(conversation.getContactLargeIcon(context).toLargeBitmap(context))
|
setLargeIcon(conversation.getContactLargeIcon(context).toLargeBitmap(context))
|
||||||
|
|
|
@ -30,14 +30,14 @@ import org.thoughtcrime.securesms.util.hasSharedContact
|
||||||
import org.thoughtcrime.securesms.util.hasSticker
|
import org.thoughtcrime.securesms.util.hasSticker
|
||||||
import org.thoughtcrime.securesms.util.isMediaMessage
|
import org.thoughtcrime.securesms.util.isMediaMessage
|
||||||
|
|
||||||
private val TAG: String = Log.tag(NotificationItemV2::class.java)
|
private val TAG: String = Log.tag(NotificationItem::class.java)
|
||||||
private const val EMOJI_REPLACEMENT_STRING = "__EMOJI__"
|
private const val EMOJI_REPLACEMENT_STRING = "__EMOJI__"
|
||||||
private const val MAX_DISPLAY_LENGTH = 500
|
private const val MAX_DISPLAY_LENGTH = 500
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base for messaged-based notifications. Represents a single notification.
|
* Base for messaged-based notifications. Represents a single notification.
|
||||||
*/
|
*/
|
||||||
sealed class NotificationItemV2(val threadRecipient: Recipient, protected val record: MessageRecord) : Comparable<NotificationItemV2> {
|
sealed class NotificationItem(val threadRecipient: Recipient, protected val record: MessageRecord) : Comparable<NotificationItem> {
|
||||||
|
|
||||||
val id: Long = record.id
|
val id: Long = record.id
|
||||||
val thread = ConversationId.fromMessageRecord(record)
|
val thread = ConversationId.fromMessageRecord(record)
|
||||||
|
@ -104,7 +104,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: NotificationItemV2): Int {
|
override fun compareTo(other: NotificationItem): Int {
|
||||||
return timestamp.compareTo(other.timestamp)
|
return timestamp.compareTo(other.timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasSameContent(other: NotificationItemV2): Boolean {
|
open fun hasSameContent(other: NotificationItem): Boolean {
|
||||||
return timestamp == other.timestamp &&
|
return timestamp == other.timestamp &&
|
||||||
id == other.id &&
|
id == other.id &&
|
||||||
isMms == other.isMms &&
|
isMms == other.isMms &&
|
||||||
|
@ -162,17 +162,23 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ThumbnailInfo(val uri: Uri? = null, val contentType: String? = null)
|
data class ThumbnailInfo(val uri: Uri? = null, val contentType: String? = null) {
|
||||||
|
companion object {
|
||||||
|
val NONE = ThumbnailInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a notification associated with a new message.
|
* Represents a notification associated with a new message.
|
||||||
*/
|
*/
|
||||||
class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : NotificationItemV2(threadRecipient, record) {
|
class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : NotificationItem(threadRecipient, record) {
|
||||||
override val timestamp: Long = record.timestamp
|
override val timestamp: Long = record.timestamp
|
||||||
override val individualRecipient: Recipient = if (record.isOutgoing) Recipient.self() else record.individualRecipient.resolve()
|
override val individualRecipient: Recipient = if (record.isOutgoing) Recipient.self() else record.individualRecipient.resolve()
|
||||||
override val isNewNotification: Boolean = notifiedTimestamp == 0L
|
override val isNewNotification: Boolean = notifiedTimestamp == 0L
|
||||||
|
|
||||||
|
private var thumbnailInfo: ThumbnailInfo? = null
|
||||||
|
|
||||||
override fun getPrimaryTextActual(context: Context): CharSequence {
|
override fun getPrimaryTextActual(context: Context): CharSequence {
|
||||||
return if (KeyCachingService.isLocked(context)) {
|
return if (KeyCachingService.isLocked(context)) {
|
||||||
SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message))
|
SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message))
|
||||||
|
@ -221,14 +227,17 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getThumbnailInfo(context: Context): ThumbnailInfo {
|
override fun getThumbnailInfo(context: Context): ThumbnailInfo {
|
||||||
return if (SignalStore.settings().messageNotificationsPrivacy.isDisplayMessage && !KeyCachingService.isLocked(context)) {
|
if (thumbnailInfo == null) {
|
||||||
val thumbnailSlide: Slide? = slideDeck?.thumbnailSlide
|
thumbnailInfo = if (SignalStore.settings().messageNotificationsPrivacy.isDisplayMessage && !KeyCachingService.isLocked(context)) {
|
||||||
ThumbnailInfo(thumbnailSlide?.publicUri, thumbnailSlide?.contentType)
|
NotificationThumbnails.get(context, this)
|
||||||
} else {
|
} else {
|
||||||
ThumbnailInfo()
|
ThumbnailInfo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return thumbnailInfo!!
|
||||||
|
}
|
||||||
|
|
||||||
override fun canReply(context: Context): Boolean {
|
override fun canReply(context: Context): Boolean {
|
||||||
if (KeyCachingService.isLocked(context) ||
|
if (KeyCachingService.isLocked(context) ||
|
||||||
record.isRemoteDelete ||
|
record.isRemoteDelete ||
|
||||||
|
@ -246,6 +255,10 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun hasSameContent(other: NotificationItem): Boolean {
|
||||||
|
return super.hasSameContent(other) && thumbnailInfo == (other as? MessageNotification)?.thumbnailInfo
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "MessageNotification(timestamp=$timestamp, isNewNotification=$isNewNotification)"
|
return "MessageNotification(timestamp=$timestamp, isNewNotification=$isNewNotification)"
|
||||||
}
|
}
|
||||||
|
@ -254,7 +267,7 @@ class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : N
|
||||||
/**
|
/**
|
||||||
* Represents a notification associated with a new reaction.
|
* Represents a notification associated with a new reaction.
|
||||||
*/
|
*/
|
||||||
class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, val reaction: ReactionRecord) : NotificationItemV2(threadRecipient, record) {
|
class ReactionNotification(threadRecipient: Recipient, record: MessageRecord, val reaction: ReactionRecord) : NotificationItem(threadRecipient, record) {
|
||||||
override val timestamp: Long = reaction.dateReceived
|
override val timestamp: Long = reaction.dateReceived
|
||||||
override val individualRecipient: Recipient = Recipient.resolved(reaction.author)
|
override val individualRecipient: Recipient = Recipient.resolved(reaction.author)
|
||||||
override val isNewNotification: Boolean = timestamp > notifiedTimestamp
|
override val isNewNotification: Boolean = timestamp > notifiedTimestamp
|
|
@ -11,7 +11,7 @@ import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
/**
|
/**
|
||||||
* Hold all state for notifications for all conversations.
|
* Hold all state for notifications for all conversations.
|
||||||
*/
|
*/
|
||||||
data class NotificationStateV2(val conversations: List<NotificationConversation>, val muteFilteredMessages: List<FilteredMessage>, val profileFilteredMessages: List<FilteredMessage>) {
|
data class NotificationState(val conversations: List<NotificationConversation>, val muteFilteredMessages: List<FilteredMessage>, val profileFilteredMessages: List<FilteredMessage>) {
|
||||||
|
|
||||||
val threadCount: Int = conversations.size
|
val threadCount: Int = conversations.size
|
||||||
val isEmpty: Boolean = conversations.isEmpty()
|
val isEmpty: Boolean = conversations.isEmpty()
|
||||||
|
@ -22,7 +22,7 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val notificationItems: List<NotificationItemV2> by lazy {
|
val notificationItems: List<NotificationItem> by lazy {
|
||||||
conversations.map { it.notificationItems }
|
conversations.map { it.notificationItems }
|
||||||
.flatten()
|
.flatten()
|
||||||
.sorted()
|
.sorted()
|
||||||
|
@ -33,7 +33,7 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
|
||||||
.toSet()
|
.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
val mostRecentNotification: NotificationItemV2?
|
val mostRecentNotification: NotificationItem?
|
||||||
get() = notificationItems.lastOrNull()
|
get() = notificationItems.lastOrNull()
|
||||||
|
|
||||||
val mostRecentSender: Recipient?
|
val mostRecentSender: Recipient?
|
||||||
|
@ -88,6 +88,6 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
|
||||||
data class FilteredMessage(val id: Long, val isMms: Boolean)
|
data class FilteredMessage(val id: Long, val isMms: Boolean)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = NotificationStateV2(emptyList(), emptyList(), emptyList())
|
val EMPTY = NotificationState(emptyList(), emptyList(), emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -22,12 +22,12 @@ object NotificationStateProvider {
|
||||||
private val TAG = Log.tag(NotificationStateProvider::class.java)
|
private val TAG = Log.tag(NotificationStateProvider::class.java)
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun constructNotificationState(stickyThreads: Map<ConversationId, MessageNotifierV2.StickyThread>, notificationProfile: NotificationProfile?): NotificationStateV2 {
|
fun constructNotificationState(stickyThreads: Map<ConversationId, DefaultMessageNotifier.StickyThread>, notificationProfile: NotificationProfile?): NotificationState {
|
||||||
val messages: MutableList<NotificationMessage> = mutableListOf()
|
val messages: MutableList<NotificationMessage> = mutableListOf()
|
||||||
|
|
||||||
SignalDatabase.mmsSms.getMessagesForNotificationState(stickyThreads.values).use { unreadMessages ->
|
SignalDatabase.mmsSms.getMessagesForNotificationState(stickyThreads.values).use { unreadMessages ->
|
||||||
if (unreadMessages.count == 0) {
|
if (unreadMessages.count == 0) {
|
||||||
return NotificationStateV2.EMPTY
|
return NotificationState.EMPTY
|
||||||
}
|
}
|
||||||
|
|
||||||
MmsSmsDatabase.readerFor(unreadMessages).use { reader ->
|
MmsSmsDatabase.readerFor(unreadMessages).use { reader ->
|
||||||
|
@ -71,19 +71,19 @@ object NotificationStateProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
val conversations: MutableList<NotificationConversation> = mutableListOf()
|
val conversations: MutableList<NotificationConversation> = mutableListOf()
|
||||||
val muteFilteredMessages: MutableList<NotificationStateV2.FilteredMessage> = mutableListOf()
|
val muteFilteredMessages: MutableList<NotificationState.FilteredMessage> = mutableListOf()
|
||||||
val profileFilteredMessages: MutableList<NotificationStateV2.FilteredMessage> = mutableListOf()
|
val profileFilteredMessages: MutableList<NotificationState.FilteredMessage> = mutableListOf()
|
||||||
|
|
||||||
messages.groupBy { it.thread }
|
messages.groupBy { it.thread }
|
||||||
.forEach { (thread, threadMessages) ->
|
.forEach { (thread, threadMessages) ->
|
||||||
var notificationItems: MutableList<NotificationItemV2> = mutableListOf()
|
var notificationItems: MutableList<NotificationItem> = mutableListOf()
|
||||||
|
|
||||||
for (notification: NotificationMessage in threadMessages) {
|
for (notification: NotificationMessage in threadMessages) {
|
||||||
when (notification.includeMessage(notificationProfile)) {
|
when (notification.includeMessage(notificationProfile)) {
|
||||||
MessageInclusion.INCLUDE -> notificationItems.add(MessageNotification(notification.threadRecipient, notification.messageRecord))
|
MessageInclusion.INCLUDE -> notificationItems.add(MessageNotification(notification.threadRecipient, notification.messageRecord))
|
||||||
MessageInclusion.EXCLUDE -> Unit
|
MessageInclusion.EXCLUDE -> Unit
|
||||||
MessageInclusion.MUTE_FILTERED -> muteFilteredMessages += NotificationStateV2.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
MessageInclusion.MUTE_FILTERED -> muteFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||||
MessageInclusion.PROFILE_FILTERED -> profileFilteredMessages += NotificationStateV2.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
MessageInclusion.PROFILE_FILTERED -> profileFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notification.hasUnreadReactions) {
|
if (notification.hasUnreadReactions) {
|
||||||
|
@ -91,8 +91,8 @@ object NotificationStateProvider {
|
||||||
when (notification.includeReaction(it, notificationProfile)) {
|
when (notification.includeReaction(it, notificationProfile)) {
|
||||||
MessageInclusion.INCLUDE -> notificationItems.add(ReactionNotification(notification.threadRecipient, notification.messageRecord, it))
|
MessageInclusion.INCLUDE -> notificationItems.add(ReactionNotification(notification.threadRecipient, notification.messageRecord, it))
|
||||||
MessageInclusion.EXCLUDE -> Unit
|
MessageInclusion.EXCLUDE -> Unit
|
||||||
MessageInclusion.MUTE_FILTERED -> muteFilteredMessages += NotificationStateV2.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
MessageInclusion.MUTE_FILTERED -> muteFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||||
MessageInclusion.PROFILE_FILTERED -> profileFilteredMessages += NotificationStateV2.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
MessageInclusion.PROFILE_FILTERED -> profileFilteredMessages += NotificationState.FilteredMessage(notification.messageRecord.id, notification.messageRecord.isMms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ object NotificationStateProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotificationStateV2(conversations, muteFilteredMessages, profileFilteredMessages)
|
return NotificationState(conversations, muteFilteredMessages, profileFilteredMessages)
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class NotificationMessage(
|
private data class NotificationMessage(
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
package org.thoughtcrime.securesms.notifications.v2
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import org.signal.core.util.concurrent.SignalExecutors
|
||||||
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.thoughtcrime.securesms.database.model.MessageId
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
|
||||||
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
|
||||||
|
import org.thoughtcrime.securesms.mms.Slide
|
||||||
|
import org.thoughtcrime.securesms.providers.BlobProvider
|
||||||
|
import org.thoughtcrime.securesms.util.ImageCompressionUtil
|
||||||
|
import org.thoughtcrime.securesms.util.kb
|
||||||
|
import org.thoughtcrime.securesms.util.mb
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and caches attachment thumbnails solely for use by Notifications.
|
||||||
|
*
|
||||||
|
* Handles LRU cache on it's own due to needing to cleanup BlobProvider when oldest element is evicted.
|
||||||
|
*
|
||||||
|
* Previously the PartProvider was used and it would provide the entire, full-resolution image assets causing
|
||||||
|
* some OEMs to ANR during file reading.
|
||||||
|
*/
|
||||||
|
object NotificationThumbnails {
|
||||||
|
private val TAG = Log.tag(NotificationThumbnails::class.java)
|
||||||
|
|
||||||
|
private const val MAX_CACHE_SIZE = 16
|
||||||
|
private val TARGET_SIZE = 128.kb
|
||||||
|
private val SUPPORTED_SIZE_THRESHOLD = 1.mb
|
||||||
|
|
||||||
|
private val executor = SignalExecutors.BOUNDED_IO
|
||||||
|
|
||||||
|
private val thumbnailCache = LinkedHashMap<MessageId, CachedThumbnail>(MAX_CACHE_SIZE)
|
||||||
|
|
||||||
|
fun get(context: Context, notificationItem: NotificationItem): NotificationItem.ThumbnailInfo {
|
||||||
|
val thumbnailSlide: Slide? = notificationItem.slideDeck?.thumbnailSlide
|
||||||
|
|
||||||
|
if (thumbnailSlide == null || thumbnailSlide.uri == null) {
|
||||||
|
return NotificationItem.ThumbnailInfo.NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnailSlide.fileSize > SUPPORTED_SIZE_THRESHOLD) {
|
||||||
|
Log.i(TAG, "Source attachment too large for notification")
|
||||||
|
return NotificationItem.ThumbnailInfo.NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnailSlide.fileSize < TARGET_SIZE) {
|
||||||
|
return NotificationItem.ThumbnailInfo(thumbnailSlide.publicUri, thumbnailSlide.contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
val messageId = MessageId(notificationItem.id, notificationItem.isMms)
|
||||||
|
val thumbnail: CachedThumbnail? = synchronized(thumbnailCache) { thumbnailCache[messageId] }
|
||||||
|
|
||||||
|
if (thumbnail != null) {
|
||||||
|
return if (thumbnail != CachedThumbnail.PENDING) {
|
||||||
|
NotificationItem.ThumbnailInfo(thumbnail.uri, thumbnail.contentType)
|
||||||
|
} else {
|
||||||
|
NotificationItem.ThumbnailInfo.NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized(thumbnailCache) {
|
||||||
|
thumbnailCache[messageId] = CachedThumbnail.PENDING
|
||||||
|
}
|
||||||
|
|
||||||
|
executor.execute {
|
||||||
|
val uri = thumbnailSlide.uri
|
||||||
|
|
||||||
|
if (uri != null) {
|
||||||
|
val result = ImageCompressionUtil.compressWithinConstraints(
|
||||||
|
context,
|
||||||
|
thumbnailSlide.contentType,
|
||||||
|
DecryptableStreamUriLoader.DecryptableUri(uri),
|
||||||
|
1024,
|
||||||
|
TARGET_SIZE,
|
||||||
|
60
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
val thumbnailUri = BlobProvider
|
||||||
|
.getInstance()
|
||||||
|
.forData(result.data)
|
||||||
|
.withMimeType(result.mimeType)
|
||||||
|
.withFileName(result.hashCode().toString())
|
||||||
|
.createForSingleSessionInMemory()
|
||||||
|
|
||||||
|
synchronized(thumbnailCache) {
|
||||||
|
if (thumbnailCache.size >= MAX_CACHE_SIZE) {
|
||||||
|
thumbnailCache.remove(thumbnailCache.keys.first())?.uri?.let {
|
||||||
|
BlobProvider.getInstance().delete(context, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thumbnailCache[messageId] = CachedThumbnail(thumbnailUri, result.mimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplicationDependencies.getMessageNotifier().updateNotification(context, notificationItem.thread)
|
||||||
|
} else {
|
||||||
|
Log.i(TAG, "Unable to compress attachment thumbnail for $messageId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NotificationItem.ThumbnailInfo.NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeAllExcept(notificationItems: List<NotificationItem>) {
|
||||||
|
val currentMessages = notificationItems.map { MessageId(it.id, it.isMms) }
|
||||||
|
synchronized(thumbnailCache) {
|
||||||
|
thumbnailCache.keys.removeIf { !currentMessages.contains(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class CachedThumbnail(val uri: Uri?, val contentType: String?) {
|
||||||
|
companion object {
|
||||||
|
val PENDING = CachedThumbnail(null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package org.thoughtcrime.securesms.util
|
||||||
|
|
||||||
|
/** Kilobytes in bytes */
|
||||||
|
val Int.kb
|
||||||
|
get() = this * 1024
|
||||||
|
|
||||||
|
/** Megabytes in bytes. */
|
||||||
|
val Int.mb
|
||||||
|
get() = this * 1024 * 1024
|
Loading…
Add table
Reference in a new issue