kopia lustrzana https://github.com/ryukoposting/Signal-Android
Fix various notification display issues and properly support reply.
rodzic
5bbc4aea95
commit
a843619c5b
|
@ -31,6 +31,7 @@ import org.thoughtcrime.securesms.database.MessageDatabase.SyncMessageId;
|
||||||
import org.thoughtcrime.securesms.database.MessageDatabase.ThreadUpdate;
|
import org.thoughtcrime.securesms.database.MessageDatabase.ThreadUpdate;
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
|
import org.thoughtcrime.securesms.notifications.v2.MessageNotifierV2;
|
||||||
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.thoughtcrime.securesms.util.CursorUtil;
|
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||||
|
@ -218,6 +219,28 @@ public class MmsSmsDatabase extends Database {
|
||||||
return queryTables(PROJECTION, selection, order, null);
|
return queryTables(PROJECTION, selection, order, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Cursor getMessagesForNotificationState(Collection<MessageNotifierV2.StickyThread> stickyThreads) {
|
||||||
|
StringBuilder stickyQuery = new StringBuilder();
|
||||||
|
for (MessageNotifierV2.StickyThread stickyThread : stickyThreads) {
|
||||||
|
if (stickyQuery.length() > 0) {
|
||||||
|
stickyQuery.append(" OR ");
|
||||||
|
}
|
||||||
|
stickyQuery.append("(")
|
||||||
|
.append(MmsSmsColumns.THREAD_ID + " = ")
|
||||||
|
.append(stickyThread.getThreadId())
|
||||||
|
.append(" AND ")
|
||||||
|
.append(MmsSmsColumns.NORMALIZED_DATE_RECEIVED)
|
||||||
|
.append(" >= ")
|
||||||
|
.append(stickyThread.getEarliestTimestamp())
|
||||||
|
.append(")");
|
||||||
|
}
|
||||||
|
|
||||||
|
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||||
|
String selection = MmsSmsColumns.NOTIFIED + " = 0 AND (" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1" + (stickyQuery.length() > 0 ? " OR (" + stickyQuery.toString() + ")" : "") + ")";
|
||||||
|
|
||||||
|
return queryTables(PROJECTION, selection, order, null);
|
||||||
|
}
|
||||||
|
|
||||||
public int getUnreadCount(long threadId) {
|
public int getUnreadCount(long threadId) {
|
||||||
String selection = MmsSmsColumns.READ + " = 0 AND " + MmsSmsColumns.NOTIFIED + " = 0 AND " + MmsSmsColumns.THREAD_ID + " = " + threadId;
|
String selection = MmsSmsColumns.READ + " = 0 AND " + MmsSmsColumns.NOTIFIED + " = 0 AND " + MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||||
Cursor cursor = queryTables(PROJECTION, selection, null, null);
|
Cursor cursor = queryTables(PROJECTION, selection, null, null);
|
||||||
|
|
|
@ -749,6 +749,12 @@ public class DefaultMessageNotifier implements MessageNotifier {
|
||||||
alarmManager.cancel(pendingIntent);
|
alarmManager.cancel(pendingIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addStickyThread(long threadId, long earliestTimestamp) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeStickyThread(long threadId) {}
|
||||||
|
|
||||||
private static class DelayedNotification implements Runnable {
|
private static class DelayedNotification implements Runnable {
|
||||||
|
|
||||||
private static final long DELAY = TimeUnit.SECONDS.toMillis(5);
|
private static final long DELAY = TimeUnit.SECONDS.toMillis(5);
|
||||||
|
|
|
@ -4,8 +4,8 @@ package org.thoughtcrime.securesms.notifications;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
|
||||||
|
|
||||||
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
|
||||||
|
@ -13,30 +13,37 @@ public class DeleteNotificationReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
public static String DELETE_NOTIFICATION_ACTION = "org.thoughtcrime.securesms.DELETE_NOTIFICATION";
|
public static String DELETE_NOTIFICATION_ACTION = "org.thoughtcrime.securesms.DELETE_NOTIFICATION";
|
||||||
|
|
||||||
public static String EXTRA_IDS = "message_ids";
|
public static final String EXTRA_IDS = "message_ids";
|
||||||
public static String EXTRA_MMS = "is_mms";
|
public static final String EXTRA_MMS = "is_mms";
|
||||||
|
public static final String EXTRA_THREAD_IDS = "thread_ids";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(final Context context, Intent intent) {
|
public void onReceive(final Context context, Intent intent) {
|
||||||
if (DELETE_NOTIFICATION_ACTION.equals(intent.getAction())) {
|
if (DELETE_NOTIFICATION_ACTION.equals(intent.getAction())) {
|
||||||
ApplicationDependencies.getMessageNotifier().clearReminder(context);
|
MessageNotifier notifier = ApplicationDependencies.getMessageNotifier();
|
||||||
|
notifier.clearReminder(context);
|
||||||
|
|
||||||
final long[] ids = intent.getLongArrayExtra(EXTRA_IDS);
|
final long[] ids = intent.getLongArrayExtra(EXTRA_IDS);
|
||||||
final boolean[] mms = intent.getBooleanArrayExtra(EXTRA_MMS);
|
final boolean[] mms = intent.getBooleanArrayExtra(EXTRA_MMS);
|
||||||
|
final long[] threadIds = intent.getLongArrayExtra(EXTRA_THREAD_IDS);
|
||||||
|
|
||||||
|
if (threadIds != null) {
|
||||||
|
for (long threadId : threadIds) {
|
||||||
|
notifier.removeStickyThread(threadId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (ids == null || mms == null || ids.length != mms.length) return;
|
if (ids == null || mms == null || ids.length != mms.length) return;
|
||||||
|
|
||||||
new AsyncTask<Void, Void, Void>() {
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
@Override
|
for (int i = 0; i < ids.length; i++) {
|
||||||
protected Void doInBackground(Void... params) {
|
if (!mms[i]) {
|
||||||
for (int i=0;i<ids.length;i++) {
|
DatabaseFactory.getSmsDatabase(context).markAsNotified(ids[i]);
|
||||||
if (!mms[i]) DatabaseFactory.getSmsDatabase(context).markAsNotified(ids[i]);
|
} else {
|
||||||
else DatabaseFactory.getMmsDatabase(context).markAsNotified(ids[i]);
|
DatabaseFactory.getMmsDatabase(context).markAsNotified(ids[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import com.annimon.stream.Stream;
|
||||||
|
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MessageDatabase.ExpirationInfo;
|
import org.thoughtcrime.securesms.database.MessageDatabase.ExpirationInfo;
|
||||||
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
|
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
|
||||||
|
@ -43,6 +42,11 @@ public class MarkReadReceiver extends BroadcastReceiver {
|
||||||
final long[] threadIds = intent.getLongArrayExtra(THREAD_IDS_EXTRA);
|
final long[] threadIds = intent.getLongArrayExtra(THREAD_IDS_EXTRA);
|
||||||
|
|
||||||
if (threadIds != null) {
|
if (threadIds != null) {
|
||||||
|
MessageNotifier notifier = ApplicationDependencies.getMessageNotifier();
|
||||||
|
for (long threadId : threadIds) {
|
||||||
|
notifier.removeStickyThread(threadId);
|
||||||
|
}
|
||||||
|
|
||||||
NotificationCancellationHelper.cancelLegacy(context, intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1));
|
NotificationCancellationHelper.cancelLegacy(context, intent.getIntExtra(NOTIFICATION_ID_EXTRA, -1));
|
||||||
|
|
||||||
SignalExecutors.BOUNDED.execute(() -> {
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
|
|
|
@ -24,6 +24,8 @@ public interface MessageNotifier {
|
||||||
void updateNotification(@NonNull Context context, long threadId, boolean signal);
|
void updateNotification(@NonNull Context context, long threadId, boolean signal);
|
||||||
void updateNotification(@NonNull Context context, long threadId, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState);
|
void updateNotification(@NonNull Context context, long threadId, boolean signal, int reminderCount, @NonNull BubbleUtil.BubbleState defaultBubbleState);
|
||||||
void clearReminder(@NonNull Context context);
|
void clearReminder(@NonNull Context context);
|
||||||
|
void addStickyThread(long threadId, long earliestTimestamp);
|
||||||
|
void removeStickyThread(long threadId);
|
||||||
|
|
||||||
|
|
||||||
class ReminderReceiver extends BroadcastReceiver {
|
class ReminderReceiver extends BroadcastReceiver {
|
||||||
|
|
|
@ -19,7 +19,9 @@ import org.thoughtcrime.securesms.util.BubbleUtil;
|
||||||
import org.thoughtcrime.securesms.util.ConversationUtil;
|
import org.thoughtcrime.securesms.util.ConversationUtil;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Consolidates Notification Cancellation logic to one class.
|
* Consolidates Notification Cancellation logic to one class.
|
||||||
|
@ -36,6 +38,10 @@ public final class NotificationCancellationHelper {
|
||||||
|
|
||||||
private NotificationCancellationHelper() {}
|
private NotificationCancellationHelper() {}
|
||||||
|
|
||||||
|
public static void cancelAllMessageNotifications(@NonNull Context context) {
|
||||||
|
cancelAllMessageNotifications(context, Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 DefaultMessageNotifier#NOTIFICATION_GROUP} group.
|
* summary notification assigned to the {@link DefaultMessageNotifier#NOTIFICATION_GROUP} group.
|
||||||
|
@ -43,7 +49,7 @@ public final class NotificationCancellationHelper {
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
public static void cancelAllMessageNotifications(@NonNull Context context) {
|
public static void cancelAllMessageNotifications(@NonNull Context context, @NonNull Set<Integer> stickyNotifications) {
|
||||||
if (Build.VERSION.SDK_INT >= 23) {
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
try {
|
try {
|
||||||
NotificationManager notifications = ServiceUtil.getNotificationManager(context);
|
NotificationManager notifications = ServiceUtil.getNotificationManager(context);
|
||||||
|
@ -53,7 +59,7 @@ public final class NotificationCancellationHelper {
|
||||||
for (StatusBarNotification activeNotification : activeNotifications) {
|
for (StatusBarNotification activeNotification : activeNotifications) {
|
||||||
if (isSingleThreadNotification(activeNotification)) {
|
if (isSingleThreadNotification(activeNotification)) {
|
||||||
activeCount++;
|
activeCount++;
|
||||||
if (cancel(context, activeNotification.getId())) {
|
if (!stickyNotifications.contains(activeNotification.getId()) && cancel(context, activeNotification.getId())) {
|
||||||
activeCount--;
|
activeCount--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,6 +92,16 @@ public class OptimizedMessageNotifier implements MessageNotifier {
|
||||||
getNotifier().clearReminder(context);
|
getNotifier().clearReminder(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addStickyThread(long threadId, long earliestTimestamp) {
|
||||||
|
getNotifier().addStickyThread(threadId, earliestTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeStickyThread(long threadId) {
|
||||||
|
getNotifier().removeStickyThread(threadId);
|
||||||
|
}
|
||||||
|
|
||||||
private void runOnLimiter(@NonNull Runnable runnable) {
|
private void runOnLimiter(@NonNull Runnable runnable) {
|
||||||
Throwable prettyException = new Throwable();
|
Throwable prettyException = new Throwable();
|
||||||
limiter.run(() -> {
|
limiter.run(() -> {
|
||||||
|
|
|
@ -21,13 +21,11 @@ import android.annotation.SuppressLint;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.core.app.RemoteInput;
|
import androidx.core.app.RemoteInput;
|
||||||
|
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
|
import org.thoughtcrime.securesms.database.MessageDatabase.MarkedMessageInfo;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
@ -47,10 +45,10 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public class RemoteReplyReceiver extends BroadcastReceiver {
|
public class RemoteReplyReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
public static final String TAG = Log.tag(RemoteReplyReceiver.class);
|
|
||||||
public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.WEAR_REPLY";
|
public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.WEAR_REPLY";
|
||||||
public static final String RECIPIENT_EXTRA = "recipient_extra";
|
public static final String RECIPIENT_EXTRA = "recipient_extra";
|
||||||
public static final String REPLY_METHOD = "reply_method";
|
public static final String REPLY_METHOD = "reply_method";
|
||||||
|
public static final String EARLIEST_TIMESTAMP = "earliest_timestamp";
|
||||||
|
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
@Override
|
@Override
|
||||||
|
@ -109,6 +107,8 @@ public class RemoteReplyReceiver extends BroadcastReceiver {
|
||||||
throw new AssertionError("Unknown Reply method");
|
throw new AssertionError("Unknown Reply method");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplicationDependencies.getMessageNotifier().addStickyThread(threadId, intent.getLongExtra(EARLIEST_TIMESTAMP, System.currentTimeMillis()));
|
||||||
|
|
||||||
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(threadId, true);
|
List<MarkedMessageInfo> messageIds = DatabaseFactory.getThreadDatabase(context).setRead(threadId, true);
|
||||||
|
|
||||||
ApplicationDependencies.getMessageNotifier().updateNotification(context);
|
ApplicationDependencies.getMessageNotifier().updateNotification(context);
|
||||||
|
|
|
@ -49,11 +49,13 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
@Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = TextSecurePreferences.getNotificationPrivacy(context)
|
@Volatile private var previousPrivacyPreference: NotificationPrivacyPreference = TextSecurePreferences.getNotificationPrivacy(context)
|
||||||
|
|
||||||
private val threadReminders: MutableMap<Long, Reminder> = ConcurrentHashMap()
|
private val threadReminders: MutableMap<Long, Reminder> = ConcurrentHashMap()
|
||||||
|
private val stickyThreads: MutableMap<Long, StickyThread> = mutableMapOf()
|
||||||
|
|
||||||
private val executor = CancelableExecutor()
|
private val executor = CancelableExecutor()
|
||||||
|
|
||||||
override fun setVisibleThread(threadId: Long) {
|
override fun setVisibleThread(threadId: Long) {
|
||||||
visibleThread = threadId
|
visibleThread = threadId
|
||||||
|
stickyThreads.remove(threadId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getVisibleThread(): Long {
|
override fun getVisibleThread(): Long {
|
||||||
|
@ -112,24 +114,31 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val state: NotificationStateV2 = NotificationStateProvider.constructNotificationState(context)
|
|
||||||
|
|
||||||
Log.internal().i(TAG, state.toString())
|
|
||||||
|
|
||||||
if (state.isEmpty) {
|
|
||||||
Log.i(TAG, "State is empty, cancelling all notifications")
|
|
||||||
NotificationCancellationHelper.cancelAllMessageNotifications(context)
|
|
||||||
updateBadge(context, 0)
|
|
||||||
clearReminderInternal(context)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentLockStatus: Boolean = KeyCachingService.isLocked(context)
|
val currentLockStatus: Boolean = KeyCachingService.isLocked(context)
|
||||||
val currentPrivacyPreference: NotificationPrivacyPreference = TextSecurePreferences.getNotificationPrivacy(context)
|
val currentPrivacyPreference: NotificationPrivacyPreference = TextSecurePreferences.getNotificationPrivacy(context)
|
||||||
val notificationConfigurationChanged: Boolean = currentLockStatus != previousLockedStatus || currentPrivacyPreference != previousPrivacyPreference
|
val notificationConfigurationChanged: Boolean = currentLockStatus != previousLockedStatus || currentPrivacyPreference != previousPrivacyPreference
|
||||||
previousLockedStatus = currentLockStatus
|
previousLockedStatus = currentLockStatus
|
||||||
previousPrivacyPreference = currentPrivacyPreference
|
previousPrivacyPreference = currentPrivacyPreference
|
||||||
|
|
||||||
|
if (notificationConfigurationChanged) {
|
||||||
|
stickyThreads.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.internal().i(TAG, "sticky thread: $stickyThreads")
|
||||||
|
val state: NotificationStateV2 = NotificationStateProvider.constructNotificationState(context, stickyThreads)
|
||||||
|
Log.internal().i(TAG, "state: $state")
|
||||||
|
|
||||||
|
val retainStickyThreadIds: Set<Long> = state.getThreadsWithMostRecentNotificationFromSelf()
|
||||||
|
stickyThreads.keys.retainAll { retainStickyThreadIds.contains(it) }
|
||||||
|
|
||||||
|
if (state.isEmpty) {
|
||||||
|
Log.i(TAG, "State is empty, cancelling all notifications")
|
||||||
|
NotificationCancellationHelper.cancelAllMessageNotifications(context, stickyThreads.map { it.value.notificationId }.toSet())
|
||||||
|
updateBadge(context, 0)
|
||||||
|
clearReminderInternal(context)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val alertOverrides: Set<Long> = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys
|
val alertOverrides: Set<Long> = threadReminders.filter { (_, reminder) -> reminder.lastNotified < System.currentTimeMillis() - REMINDER_TIMEOUT }.keys
|
||||||
|
|
||||||
val threadsThatAlerted: Set<Long> = NotificationFactory.notify(
|
val threadsThatAlerted: Set<Long> = NotificationFactory.notify(
|
||||||
|
@ -147,7 +156,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
|
|
||||||
updateReminderTimestamps(context, alertOverrides, threadsThatAlerted)
|
updateReminderTimestamps(context, alertOverrides, threadsThatAlerted)
|
||||||
|
|
||||||
ServiceUtil.getNotificationManager(context).cancelOrphanedNotifications(context, state)
|
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()
|
||||||
|
@ -164,6 +173,18 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
Log.i(TAG, "threads: ${state.threadCount} messages: ${state.messageCount}")
|
Log.i(TAG, "threads: ${state.threadCount} messages: ${state.messageCount}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun clearReminder(context: Context) {
|
||||||
|
// Intentionally left blank
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addStickyThread(threadId: Long, earliestTimestamp: Long) {
|
||||||
|
stickyThreads[threadId] = StickyThread(threadId, NotificationIds.getNotificationIdForThread(threadId), earliestTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeStickyThread(threadId: Long) {
|
||||||
|
stickyThreads.remove(threadId)
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateReminderTimestamps(context: Context, alertOverrides: Set<Long>, threadsThatAlerted: Set<Long>) {
|
private fun updateReminderTimestamps(context: Context, alertOverrides: Set<Long>, threadsThatAlerted: Set<Long>) {
|
||||||
if (TextSecurePreferences.getRepeatAlertsCount(context) == 0) {
|
if (TextSecurePreferences.getRepeatAlertsCount(context) == 0) {
|
||||||
return
|
return
|
||||||
|
@ -216,10 +237,6 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
alarmManager?.cancel(pendingIntent)
|
alarmManager?.cancel(pendingIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearReminder(context: Context) {
|
|
||||||
// Intentionally left blank
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val TAG = Log.tag(MessageNotifierV2::class.java)
|
private val TAG = Log.tag(MessageNotifierV2::class.java)
|
||||||
private val REMINDER_TIMEOUT = TimeUnit.MINUTES.toMillis(2)
|
private val REMINDER_TIMEOUT = TimeUnit.MINUTES.toMillis(2)
|
||||||
|
@ -233,7 +250,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun NotificationManager.cancelOrphanedNotifications(context: Context, state: NotificationStateV2) {
|
private fun NotificationManager.cancelOrphanedNotifications(context: Context, state: NotificationStateV2, stickyNotifications: Set<Int>) {
|
||||||
if (Build.VERSION.SDK_INT < 23) {
|
if (Build.VERSION.SDK_INT < 23) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -244,7 +261,8 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
notification.id != KeyCachingService.SERVICE_RUNNING_ID &&
|
notification.id != KeyCachingService.SERVICE_RUNNING_ID &&
|
||||||
notification.id != IncomingMessageObserver.FOREGROUND_ID &&
|
notification.id != IncomingMessageObserver.FOREGROUND_ID &&
|
||||||
notification.id != NotificationIds.PENDING_MESSAGES &&
|
notification.id != NotificationIds.PENDING_MESSAGES &&
|
||||||
!CallNotificationBuilder.isWebRtcNotification(notification.id)
|
!CallNotificationBuilder.isWebRtcNotification(notification.id) &&
|
||||||
|
!stickyNotifications.contains(notification.id)
|
||||||
) {
|
) {
|
||||||
if (!state.notificationIds.contains(notification.id)) {
|
if (!state.notificationIds.contains(notification.id)) {
|
||||||
Log.d(TAG, "Cancelling orphaned notification: ${notification.id}")
|
Log.d(TAG, "Cancelling orphaned notification: ${notification.id}")
|
||||||
|
@ -258,6 +276,7 @@ class MessageNotifierV2(context: Application) : MessageNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class StickyThread(val threadId: Long, val notificationId: Int, val earliestTimestamp: Long)
|
||||||
private data class Reminder(val lastNotified: Long, val count: Int = 0)
|
private data class Reminder(val lastNotified: Long, val count: Int = 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.notifications.v2
|
||||||
import android.annotation.TargetApi
|
import android.annotation.TargetApi
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.app.Person
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
@ -15,7 +16,6 @@ import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.Person
|
|
||||||
import androidx.core.app.RemoteInput
|
import androidx.core.app.RemoteInput
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
|
@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.util.AvatarUtil
|
||||||
import org.thoughtcrime.securesms.util.BubbleUtil
|
import org.thoughtcrime.securesms.util.BubbleUtil
|
||||||
import org.thoughtcrime.securesms.util.ConversationUtil
|
import org.thoughtcrime.securesms.util.ConversationUtil
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
|
import androidx.core.app.Person as PersonCompat
|
||||||
|
|
||||||
private const val BIG_PICTURE_DIMEN = 500
|
private const val BIG_PICTURE_DIMEN = 500
|
||||||
|
|
||||||
|
@ -241,12 +242,18 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val messagingStyle: NotificationCompat.MessagingStyle = NotificationCompat.MessagingStyle(ConversationUtil.buildPersonCompat(context, Recipient.self()))
|
val self: PersonCompat = PersonCompat.Builder()
|
||||||
|
.setBot(false)
|
||||||
|
.setName(Recipient.self().getDisplayName(context))
|
||||||
|
.setIcon(Recipient.self().getContactDrawable(context).toLargeBitmap(context).toIconCompat())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val messagingStyle: NotificationCompat.MessagingStyle = NotificationCompat.MessagingStyle(self)
|
||||||
messagingStyle.conversationTitle = conversation.getConversationTitle(context)
|
messagingStyle.conversationTitle = conversation.getConversationTitle(context)
|
||||||
messagingStyle.isGroupConversation = conversation.isGroup
|
messagingStyle.isGroupConversation = conversation.isGroup
|
||||||
|
|
||||||
conversation.notificationItems.forEach { notificationItem ->
|
conversation.notificationItems.forEach { notificationItem ->
|
||||||
val personBuilder: Person.Builder = Person.Builder()
|
val personBuilder: PersonCompat.Builder = PersonCompat.Builder()
|
||||||
.setBot(false)
|
.setBot(false)
|
||||||
.setName(notificationItem.getPersonName(context))
|
.setName(notificationItem.getPersonName(context))
|
||||||
.setUri(notificationItem.getPersonUri(context))
|
.setUri(notificationItem.getPersonUri(context))
|
||||||
|
@ -400,6 +407,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
|
|
||||||
override fun setWhen(timestamp: Long) {
|
override fun setWhen(timestamp: Long) {
|
||||||
builder.setWhen(timestamp)
|
builder.setWhen(timestamp)
|
||||||
|
builder.setShowWhen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setGroupSummary(isGroupSummary: Boolean) {
|
override fun setGroupSummary(isGroupSummary: Boolean) {
|
||||||
|
@ -480,12 +488,18 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val messagingStyle: Notification.MessagingStyle = Notification.MessagingStyle(ConversationUtil.buildPerson(context, Recipient.self()))
|
val self: Person = Person.Builder()
|
||||||
|
.setBot(false)
|
||||||
|
.setName(Recipient.self().getDisplayName(context))
|
||||||
|
.setIcon(Recipient.self().getContactDrawable(context).toLargeBitmap(context).toIcon())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val messagingStyle: Notification.MessagingStyle = Notification.MessagingStyle(self)
|
||||||
messagingStyle.conversationTitle = conversation.getConversationTitle(context)
|
messagingStyle.conversationTitle = conversation.getConversationTitle(context)
|
||||||
messagingStyle.isGroupConversation = conversation.isGroup
|
messagingStyle.isGroupConversation = conversation.isGroup
|
||||||
|
|
||||||
conversation.notificationItems.forEach { notificationItem ->
|
conversation.notificationItems.forEach { notificationItem ->
|
||||||
val personBuilder: android.app.Person.Builder = android.app.Person.Builder()
|
val personBuilder: Person.Builder = Person.Builder()
|
||||||
.setBot(false)
|
.setBot(false)
|
||||||
.setName(notificationItem.getPersonName(context))
|
.setName(notificationItem.getPersonName(context))
|
||||||
.setUri(notificationItem.getPersonUri(context))
|
.setUri(notificationItem.getPersonUri(context))
|
||||||
|
@ -623,6 +637,7 @@ sealed class NotificationBuilder(protected val context: Context) {
|
||||||
|
|
||||||
override fun setWhen(timestamp: Long) {
|
override fun setWhen(timestamp: Long) {
|
||||||
builder.setWhen(timestamp)
|
builder.setWhen(timestamp)
|
||||||
|
builder.setShowWhen(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setGroupSummary(isGroupSummary: Boolean) {
|
override fun setGroupSummary(isGroupSummary: Boolean) {
|
||||||
|
|
|
@ -34,10 +34,8 @@ private const val LARGE_ICON_DIMEN = 250
|
||||||
class NotificationConversation(
|
class NotificationConversation(
|
||||||
val recipient: Recipient,
|
val recipient: Recipient,
|
||||||
val threadId: Long,
|
val threadId: Long,
|
||||||
unsortedNotificationItems: List<NotificationItemV2>
|
val notificationItems: List<NotificationItemV2>
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val notificationItems: List<NotificationItemV2> = unsortedNotificationItems.sorted()
|
|
||||||
val mostRecentNotification: NotificationItemV2 = notificationItems.last()
|
val mostRecentNotification: NotificationItemV2 = notificationItems.last()
|
||||||
val notificationId: Int = NotificationIds.getNotificationIdForThread(threadId)
|
val notificationId: Int = NotificationIds.getNotificationIdForThread(threadId)
|
||||||
val sortKey: Long = Long.MAX_VALUE - mostRecentNotification.timestamp
|
val sortKey: Long = Long.MAX_VALUE - mostRecentNotification.timestamp
|
||||||
|
@ -146,18 +144,18 @@ class NotificationConversation(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDeleteIntent(context: Context): PendingIntent? {
|
fun getDeleteIntent(context: Context): PendingIntent? {
|
||||||
var index = 0
|
|
||||||
val ids = LongArray(notificationItems.size)
|
val ids = LongArray(notificationItems.size)
|
||||||
val mms = BooleanArray(ids.size)
|
val mms = BooleanArray(ids.size)
|
||||||
notificationItems.forEach { notificationItem ->
|
notificationItems.forEachIndexed { index, notificationItem ->
|
||||||
ids[index] = notificationItem.id
|
ids[index] = notificationItem.id
|
||||||
mms[index++] = notificationItem.isMms
|
mms[index] = notificationItem.isMms
|
||||||
}
|
}
|
||||||
|
|
||||||
val intent = Intent(context, DeleteNotificationReceiver::class.java)
|
val intent = Intent(context, DeleteNotificationReceiver::class.java)
|
||||||
.setAction(DeleteNotificationReceiver.DELETE_NOTIFICATION_ACTION)
|
.setAction(DeleteNotificationReceiver.DELETE_NOTIFICATION_ACTION)
|
||||||
.putExtra(DeleteNotificationReceiver.EXTRA_IDS, ids)
|
.putExtra(DeleteNotificationReceiver.EXTRA_IDS, ids)
|
||||||
.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms)
|
.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms)
|
||||||
|
.putExtra(DeleteNotificationReceiver.EXTRA_THREAD_IDS, longArrayOf(threadId))
|
||||||
.makeUniqueToPreventMerging()
|
.makeUniqueToPreventMerging()
|
||||||
|
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
@ -185,6 +183,7 @@ class NotificationConversation(
|
||||||
.setAction(RemoteReplyReceiver.REPLY_ACTION)
|
.setAction(RemoteReplyReceiver.REPLY_ACTION)
|
||||||
.putExtra(RemoteReplyReceiver.RECIPIENT_EXTRA, recipient.id)
|
.putExtra(RemoteReplyReceiver.RECIPIENT_EXTRA, recipient.id)
|
||||||
.putExtra(RemoteReplyReceiver.REPLY_METHOD, replyMethod)
|
.putExtra(RemoteReplyReceiver.REPLY_METHOD, replyMethod)
|
||||||
|
.putExtra(RemoteReplyReceiver.EARLIEST_TIMESTAMP, notificationItems.first().timestamp)
|
||||||
.setPackage(context.packageName)
|
.setPackage(context.packageName)
|
||||||
.makeUniqueToPreventMerging()
|
.makeUniqueToPreventMerging()
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import org.thoughtcrime.securesms.R
|
import org.thoughtcrime.securesms.R
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto
|
import org.thoughtcrime.securesms.contacts.avatars.ContactPhoto
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
|
import org.thoughtcrime.securesms.contacts.avatars.FallbackContactPhoto
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.GeneratedContactPhoto
|
||||||
|
import org.thoughtcrime.securesms.contacts.avatars.ProfileContactPhoto
|
||||||
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp
|
import org.thoughtcrime.securesms.mms.GlideApp
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
|
@ -26,8 +28,8 @@ fun Drawable?.toLargeBitmap(context: Context): Bitmap? {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Recipient.getContactDrawable(context: Context): Drawable? {
|
fun Recipient.getContactDrawable(context: Context): Drawable? {
|
||||||
val contactPhoto: ContactPhoto? = contactPhoto
|
val contactPhoto: ContactPhoto? = if (isSelf) ProfileContactPhoto(this, profileAvatar) else contactPhoto
|
||||||
val fallbackContactPhoto: FallbackContactPhoto = fallbackContactPhoto
|
val fallbackContactPhoto: FallbackContactPhoto = if (isSelf) getFallback(context) else fallbackContactPhoto
|
||||||
return if (contactPhoto != null) {
|
return if (contactPhoto != null) {
|
||||||
try {
|
try {
|
||||||
GlideApp.with(context.applicationContext)
|
GlideApp.with(context.applicationContext)
|
||||||
|
@ -67,3 +69,7 @@ fun Uri.toBitmap(context: Context, dimension: Int): Bitmap {
|
||||||
fun Intent.makeUniqueToPreventMerging(): Intent {
|
fun Intent.makeUniqueToPreventMerging(): Intent {
|
||||||
return setData((Uri.parse("custom://" + System.currentTimeMillis())))
|
return setData((Uri.parse("custom://" + System.currentTimeMillis())))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Recipient.getFallback(context: Context): FallbackContactPhoto {
|
||||||
|
return GeneratedContactPhoto(getDisplayName(context), R.drawable.ic_profile_outline_40)
|
||||||
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ object NotificationFactory {
|
||||||
conversation = conversation,
|
conversation = conversation,
|
||||||
targetThreadId = targetThreadId,
|
targetThreadId = targetThreadId,
|
||||||
defaultBubbleState = defaultBubbleState,
|
defaultBubbleState = defaultBubbleState,
|
||||||
shouldAlert = conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId)
|
shouldAlert = (conversation.hasNewNotifications() || alertOverrides.contains(conversation.threadId)) && !conversation.mostRecentNotification.individualRecipient.isSelf
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,7 +150,7 @@ sealed class NotificationItemV2(val threadRecipient: Recipient, protected val re
|
||||||
*/
|
*/
|
||||||
class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : NotificationItemV2(threadRecipient, record) {
|
class MessageNotification(threadRecipient: Recipient, record: MessageRecord) : NotificationItemV2(threadRecipient, record) {
|
||||||
override val timestamp: Long = record.timestamp
|
override val timestamp: Long = record.timestamp
|
||||||
override val individualRecipient: Recipient = 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
|
||||||
|
|
||||||
override fun getPrimaryTextActual(context: Context): CharSequence {
|
override fun getPrimaryTextActual(context: Context): CharSequence {
|
||||||
|
|
|
@ -17,10 +17,10 @@ import org.thoughtcrime.securesms.util.CursorUtil
|
||||||
object NotificationStateProvider {
|
object NotificationStateProvider {
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
fun constructNotificationState(context: Context): NotificationStateV2 {
|
fun constructNotificationState(context: Context, stickyThreads: Map<Long, MessageNotifierV2.StickyThread>): NotificationStateV2 {
|
||||||
val messages: MutableList<NotificationMessage> = mutableListOf()
|
val messages: MutableList<NotificationMessage> = mutableListOf()
|
||||||
|
|
||||||
DatabaseFactory.getMmsSmsDatabase(context).unread.use { unreadMessages ->
|
DatabaseFactory.getMmsSmsDatabase(context).getMessagesForNotificationState(stickyThreads.values).use { unreadMessages ->
|
||||||
if (unreadMessages.count == 0) {
|
if (unreadMessages.count == 0) {
|
||||||
return NotificationStateV2.EMPTY
|
return NotificationStateV2.EMPTY
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ object NotificationStateProvider {
|
||||||
messageRecord = record,
|
messageRecord = record,
|
||||||
threadRecipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(record.threadId)?.resolve() ?: Recipient.UNKNOWN,
|
threadRecipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(record.threadId)?.resolve() ?: Recipient.UNKNOWN,
|
||||||
threadId = record.threadId,
|
threadId = record.threadId,
|
||||||
|
stickyThread = stickyThreads.containsKey(record.threadId),
|
||||||
isUnreadMessage = CursorUtil.requireInt(unreadMessages, MmsSmsColumns.READ) == 0,
|
isUnreadMessage = CursorUtil.requireInt(unreadMessages, MmsSmsColumns.READ) == 0,
|
||||||
hasUnreadReactions = CursorUtil.requireInt(unreadMessages, MmsSmsColumns.REACTIONS_UNREAD) == 1,
|
hasUnreadReactions = CursorUtil.requireInt(unreadMessages, MmsSmsColumns.REACTIONS_UNREAD) == 1,
|
||||||
lastReactionRead = CursorUtil.requireLong(unreadMessages, MmsSmsColumns.REACTIONS_LAST_SEEN)
|
lastReactionRead = CursorUtil.requireLong(unreadMessages, MmsSmsColumns.REACTIONS_LAST_SEEN)
|
||||||
|
@ -44,19 +45,25 @@ object NotificationStateProvider {
|
||||||
val conversations: MutableList<NotificationConversation> = mutableListOf()
|
val conversations: MutableList<NotificationConversation> = mutableListOf()
|
||||||
messages.groupBy { it.threadId }
|
messages.groupBy { it.threadId }
|
||||||
.forEach { (threadId, threadMessages) ->
|
.forEach { (threadId, threadMessages) ->
|
||||||
val notificationItems: MutableList<NotificationItemV2> = mutableListOf()
|
var notificationItems: MutableList<NotificationItemV2> = mutableListOf()
|
||||||
for (notification: NotificationMessage in threadMessages) {
|
|
||||||
|
|
||||||
|
for (notification: NotificationMessage in threadMessages) {
|
||||||
if (notification.includeMessage()) {
|
if (notification.includeMessage()) {
|
||||||
notificationItems += MessageNotification(notification.threadRecipient, notification.messageRecord)
|
notificationItems.add(MessageNotification(notification.threadRecipient, notification.messageRecord))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notification.hasUnreadReactions) {
|
if (notification.hasUnreadReactions) {
|
||||||
notification.messageRecord.reactions.filter { notification.includeReaction(it) }
|
notification.messageRecord.reactions.filter { notification.includeReaction(it) }
|
||||||
.forEach { notificationItems += ReactionNotification(notification.threadRecipient, notification.messageRecord, it) }
|
.forEach { notificationItems.add(ReactionNotification(notification.threadRecipient, notification.messageRecord, it)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
notificationItems.sort()
|
||||||
|
if (notificationItems.isNotEmpty() && stickyThreads.containsKey(threadId) && !notificationItems.last().individualRecipient.isSelf) {
|
||||||
|
val indexOfOldestNonSelfMessage: Int = notificationItems.indexOfLast { it.individualRecipient.isSelf } + 1
|
||||||
|
notificationItems = notificationItems.slice(indexOfOldestNonSelfMessage..notificationItems.lastIndex).toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
if (notificationItems.isNotEmpty()) {
|
if (notificationItems.isNotEmpty()) {
|
||||||
conversations += NotificationConversation(notificationItems[0].threadRecipient, threadId, notificationItems)
|
conversations += NotificationConversation(notificationItems[0].threadRecipient, threadId, notificationItems)
|
||||||
}
|
}
|
||||||
|
@ -69,14 +76,16 @@ object NotificationStateProvider {
|
||||||
val messageRecord: MessageRecord,
|
val messageRecord: MessageRecord,
|
||||||
val threadRecipient: Recipient,
|
val threadRecipient: Recipient,
|
||||||
val threadId: Long,
|
val threadId: Long,
|
||||||
|
val stickyThread: Boolean,
|
||||||
val isUnreadMessage: Boolean,
|
val isUnreadMessage: Boolean,
|
||||||
val hasUnreadReactions: Boolean,
|
val hasUnreadReactions: Boolean,
|
||||||
val lastReactionRead: Long
|
val lastReactionRead: Long
|
||||||
) {
|
) {
|
||||||
|
private val isUnreadIncoming: Boolean = isUnreadMessage && !messageRecord.isOutgoing
|
||||||
private val unknownOrNotMutedThread: Boolean = threadRecipient == Recipient.UNKNOWN || threadRecipient.isNotMuted
|
private val unknownOrNotMutedThread: Boolean = threadRecipient == Recipient.UNKNOWN || threadRecipient.isNotMuted
|
||||||
|
|
||||||
fun includeMessage(): Boolean {
|
fun includeMessage(): Boolean {
|
||||||
return isUnreadMessage && (unknownOrNotMutedThread || (threadRecipient.isAlwaysNotifyMentions && messageRecord.hasSelfMention()))
|
return (isUnreadIncoming || stickyThread) && (unknownOrNotMutedThread || (threadRecipient.isAlwaysNotifyMentions && messageRecord.hasSelfMention()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun includeReaction(reaction: ReactionRecord): Boolean {
|
fun includeReaction(reaction: ReactionRecord): Boolean {
|
||||||
|
|
|
@ -42,8 +42,10 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
|
||||||
fun getDeleteIntent(context: Context): PendingIntent? {
|
fun getDeleteIntent(context: Context): PendingIntent? {
|
||||||
val ids = LongArray(messageCount)
|
val ids = LongArray(messageCount)
|
||||||
val mms = BooleanArray(ids.size)
|
val mms = BooleanArray(ids.size)
|
||||||
|
val threadIds: MutableList<Long> = mutableListOf()
|
||||||
|
|
||||||
conversations.forEach { conversation ->
|
conversations.forEach { conversation ->
|
||||||
|
threadIds += conversation.threadId
|
||||||
conversation.notificationItems.forEachIndexed { index, notificationItem ->
|
conversation.notificationItems.forEachIndexed { index, notificationItem ->
|
||||||
ids[index] = notificationItem.id
|
ids[index] = notificationItem.id
|
||||||
mms[index] = notificationItem.isMms
|
mms[index] = notificationItem.isMms
|
||||||
|
@ -54,6 +56,7 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
|
||||||
.setAction(DeleteNotificationReceiver.DELETE_NOTIFICATION_ACTION)
|
.setAction(DeleteNotificationReceiver.DELETE_NOTIFICATION_ACTION)
|
||||||
.putExtra(DeleteNotificationReceiver.EXTRA_IDS, ids)
|
.putExtra(DeleteNotificationReceiver.EXTRA_IDS, ids)
|
||||||
.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms)
|
.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms)
|
||||||
|
.putExtra(DeleteNotificationReceiver.EXTRA_THREAD_IDS, threadIds.toLongArray())
|
||||||
.makeUniqueToPreventMerging()
|
.makeUniqueToPreventMerging()
|
||||||
|
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
@ -74,6 +77,12 @@ data class NotificationStateV2(val conversations: List<NotificationConversation>
|
||||||
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getThreadsWithMostRecentNotificationFromSelf(): Set<Long> {
|
||||||
|
return conversations.filter { it.mostRecentNotification.individualRecipient.isSelf }
|
||||||
|
.map { it.threadId }
|
||||||
|
.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = NotificationStateV2(emptyList())
|
val EMPTY = NotificationStateV2(emptyList())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,9 @@ package org.thoughtcrime.securesms.preferences.widgets;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
public class NotificationPrivacyPreference {
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public final class NotificationPrivacyPreference {
|
||||||
|
|
||||||
private final String preference;
|
private final String preference;
|
||||||
|
|
||||||
|
@ -26,4 +28,17 @@ public class NotificationPrivacyPreference {
|
||||||
public @NonNull String toString() {
|
public @NonNull String toString() {
|
||||||
return preference;
|
return preference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
final NotificationPrivacyPreference that = (NotificationPrivacyPreference) o;
|
||||||
|
return Objects.equals(preference, that.preference);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(preference);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Ładowanie…
Reference in New Issue