Apply MessageStyle and fix chronology issues.

This commit is contained in:
Alex Hart 2019-12-19 11:42:10 -04:00 committed by Alan Evans
parent fe5fca8eaf
commit 3bd8aa8a86
2 changed files with 173 additions and 64 deletions

View File

@ -37,7 +37,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.core.text.HtmlCompat;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.TextUtils; import android.text.TextUtils;
@ -71,11 +70,9 @@ import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
import org.whispersystems.signalservice.internal.util.Util; import org.whispersystems.signalservice.internal.util.Util;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -170,6 +167,30 @@ public class MessageNotifier {
} }
} }
private static boolean isDisplayingSummaryNotification(@NonNull Context context) {
if (Build.VERSION.SDK_INT >= 23) {
try {
NotificationManager notificationManager = ServiceUtil.getNotificationManager(context);
StatusBarNotification[] activeNotifications = notificationManager.getActiveNotifications();
for (StatusBarNotification activeNotification : activeNotifications) {
if (activeNotification.getId() == SUMMARY_NOTIFICATION_ID) {
return true;
}
}
return false;
} catch (Throwable e) {
// XXX Android ROM Bug, see #6043
Log.w(TAG, e);
return false;
}
} else {
return false;
}
}
private static void cancelOrphanedNotifications(@NonNull Context context, NotificationState notificationState) { private static void cancelOrphanedNotifications(@NonNull Context context, NotificationState notificationState) {
if (Build.VERSION.SDK_INT >= 23) { if (Build.VERSION.SDK_INT >= 23) {
try { try {
@ -209,7 +230,7 @@ public class MessageNotifier {
return; return;
} }
updateNotification(context, false, 0); updateNotification(context, -1, false, 0);
} }
public static void updateNotification(@NonNull Context context, long threadId) public static void updateNotification(@NonNull Context context, long threadId)
@ -226,7 +247,7 @@ public class MessageNotifier {
long threadId, long threadId,
boolean signal) boolean signal)
{ {
boolean isVisible = visibleThread == threadId; boolean isVisible = visibleThread == threadId;
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context); ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
Recipient recipients = DatabaseFactory.getThreadDatabase(context) Recipient recipients = DatabaseFactory.getThreadDatabase(context)
@ -246,11 +267,12 @@ public class MessageNotifier {
if (isVisible) { if (isVisible) {
sendInThreadNotification(context, threads.getRecipientForThreadId(threadId)); sendInThreadNotification(context, threads.getRecipientForThreadId(threadId));
} else { } else {
updateNotification(context, signal, 0); updateNotification(context, threadId, signal, 0);
} }
} }
private static void updateNotification(@NonNull Context context, private static void updateNotification(@NonNull Context context,
long targetThread,
boolean signal, boolean signal,
int reminderCount) int reminderCount)
{ {
@ -281,13 +303,22 @@ public class MessageNotifier {
if (notificationState.hasMultipleThreads()) { if (notificationState.hasMultipleThreads()) {
if (Build.VERSION.SDK_INT >= 23) { if (Build.VERSION.SDK_INT >= 23) {
for (long threadId : notificationState.getThreads()) { for (long threadId : notificationState.getThreads()) {
sendSingleThreadNotification(context, new NotificationState(notificationState.getNotificationsForThread(threadId)), false, true); if (targetThread < 1 || targetThread == threadId) {
sendSingleThreadNotification(context,
new NotificationState(notificationState.getNotificationsForThread(threadId)),
signal && (threadId == targetThread),
true);
}
} }
} }
sendMultipleThreadNotification(context, notificationState, signal); sendMultipleThreadNotification(context, notificationState, signal && (Build.VERSION.SDK_INT < 23));
} else { } else {
sendSingleThreadNotification(context, notificationState, signal, false); sendSingleThreadNotification(context, notificationState, signal, false);
if (isDisplayingSummaryNotification(context)) {
sendMultipleThreadNotification(context, notificationState, false);
}
} }
cancelOrphanedNotifications(context, notificationState); cancelOrphanedNotifications(context, notificationState);
@ -302,9 +333,10 @@ public class MessageNotifier {
} }
} }
private static void sendSingleThreadNotification(@NonNull Context context, private static void sendSingleThreadNotification(@NonNull Context context,
@NonNull NotificationState notificationState, @NonNull NotificationState notificationState,
boolean signal, boolean bundled) boolean signal,
boolean bundled)
{ {
Log.i(TAG, "sendSingleThreadNotification() signal: " + signal + " bundled: " + bundled); Log.i(TAG, "sendSingleThreadNotification() signal: " + signal + " bundled: " + bundled);
@ -317,8 +349,13 @@ public class MessageNotifier {
SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context)); SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context));
List<NotificationItem> notifications = notificationState.getNotifications(); List<NotificationItem> notifications = notificationState.getNotifications();
Recipient recipient = notifications.get(0).getRecipient(); Recipient recipient = notifications.get(0).getRecipient();
int notificationId = (int) (SUMMARY_NOTIFICATION_ID + (bundled ? notifications.get(0).getThreadId() : 0)); int notificationId;
if (Build.VERSION.SDK_INT >= 23) {
notificationId = (int) (SUMMARY_NOTIFICATION_ID + notifications.get(0).getThreadId());
} else {
notificationId = SUMMARY_NOTIFICATION_ID;
}
builder.setThread(notifications.get(0).getRecipient()); builder.setThread(notifications.get(0).getRecipient());
builder.setMessageCount(notificationState.getMessageCount()); builder.setMessageCount(notificationState.getMessageCount());
@ -327,7 +364,7 @@ public class MessageNotifier {
builder.setContentIntent(notifications.get(0).getPendingIntent(context)); builder.setContentIntent(notifications.get(0).getPendingIntent(context));
builder.setDeleteIntent(notificationState.getDeleteIntent(context)); builder.setDeleteIntent(notificationState.getDeleteIntent(context));
builder.setOnlyAlertOnce(!signal); builder.setOnlyAlertOnce(!signal);
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY); builder.setSortKey(String.valueOf(Long.MAX_VALUE - notifications.get(0).getTimestamp()));
long timestamp = notifications.get(0).getTimestamp(); long timestamp = notifications.get(0).getTimestamp();
if (timestamp != 0) builder.setWhen(timestamp); if (timestamp != 0) builder.setWhen(timestamp);
@ -348,7 +385,7 @@ public class MessageNotifier {
while(iterator.hasPrevious()) { while(iterator.hasPrevious()) {
NotificationItem item = iterator.previous(); NotificationItem item = iterator.previous();
builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText()); builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText(), item.getTimestamp());
} }
if (signal) { if (signal) {
@ -357,9 +394,9 @@ public class MessageNotifier {
notifications.get(0).getText()); notifications.get(0).getText());
} }
if (bundled) { if (Build.VERSION.SDK_INT >= 23) {
builder.setGroup(NOTIFICATION_GROUP); builder.setGroup(NOTIFICATION_GROUP);
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY); builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
} }
Notification notification = builder.build(); Notification notification = builder.build();
@ -378,10 +415,13 @@ public class MessageNotifier {
builder.setMessageCount(notificationState.getMessageCount(), notificationState.getThreadCount()); builder.setMessageCount(notificationState.getMessageCount(), notificationState.getThreadCount());
builder.setMostRecentSender(notifications.get(0).getIndividualRecipient()); builder.setMostRecentSender(notifications.get(0).getIndividualRecipient());
builder.setGroup(NOTIFICATION_GROUP);
builder.setDeleteIntent(notificationState.getDeleteIntent(context)); builder.setDeleteIntent(notificationState.getDeleteIntent(context));
builder.setOnlyAlertOnce(!signal); builder.setOnlyAlertOnce(!signal);
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
if (Build.VERSION.SDK_INT >= 23) {
builder.setGroup(NOTIFICATION_GROUP);
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
}
long timestamp = notifications.get(0).getTimestamp(); long timestamp = notifications.get(0).getTimestamp();
if (timestamp != 0) builder.setWhen(timestamp); if (timestamp != 0) builder.setWhen(timestamp);
@ -599,7 +639,7 @@ public class MessageNotifier {
@Override @Override
protected Void doInBackground(Void... params) { protected Void doInBackground(Void... params) {
int reminderCount = intent.getIntExtra("reminder_count", 0); int reminderCount = intent.getIntExtra("reminder_count", 0);
MessageNotifier.updateNotification(context, true, reminderCount + 1); MessageNotifier.updateNotification(context, -1, true, reminderCount + 1);
return null; return null;
} }

View File

@ -13,9 +13,13 @@ import androidx.annotation.StringRes;
import androidx.appcompat.view.ContextThemeWrapper; import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.Action; import androidx.core.app.NotificationCompat.Action;
import androidx.core.app.Person;
import androidx.core.app.RemoteInput; import androidx.core.app.RemoteInput;
import androidx.core.graphics.drawable.IconCompat;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import com.annimon.stream.Stream;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
@ -46,11 +50,12 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
private static final int BIG_PICTURE_DIMEN = 500; private static final int BIG_PICTURE_DIMEN = 500;
private static final int LARGE_ICON_DIMEN = 250; private static final int LARGE_ICON_DIMEN = 250;
private final List<CharSequence> messageBodies = new LinkedList<>(); private final List<NotificationCompat.MessagingStyle.Message> messages = new LinkedList<>();
private SlideDeck slideDeck; private SlideDeck slideDeck;
private CharSequence contentTitle; private CharSequence contentTitle;
private CharSequence contentText; private CharSequence contentText;
private Recipient threadRecipient;
public SingleRecipientNotificationBuilder(@NonNull Context context, @NonNull NotificationPrivacyPreference privacy) public SingleRecipientNotificationBuilder(@NonNull Context context, @NonNull NotificationPrivacyPreference privacy)
{ {
@ -76,25 +81,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
addPerson(recipient.getContactUri().toString()); addPerson(recipient.getContactUri().toString());
} }
ContactPhoto contactPhoto = recipient.getContactPhoto(); setLargeIcon(getContactDrawable(recipient));
FallbackContactPhoto fallbackContactPhoto = recipient.getFallbackContactPhoto();
if (contactPhoto != null) {
try {
setLargeIcon(GlideApp.with(context.getApplicationContext())
.load(contactPhoto)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.circleCrop()
.submit(context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height))
.get());
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e);
setLargeIcon(fallbackContactPhoto.asDrawable(context, recipient.getColor().toConversationColor(context)));
}
} else {
setLargeIcon(fallbackContactPhoto.asDrawable(context, recipient.getColor().toConversationColor(context)));
}
} else { } else {
setContentTitle(context.getString(R.string.SingleRecipientNotificationBuilder_signal)); setContentTitle(context.getString(R.string.SingleRecipientNotificationBuilder_signal));
@ -102,6 +89,27 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
} }
} }
private Drawable getContactDrawable(@NonNull Recipient recipient) {
ContactPhoto contactPhoto = recipient.getContactPhoto();
FallbackContactPhoto fallbackContactPhoto = recipient.getFallbackContactPhoto();
if (contactPhoto != null) {
try {
return GlideApp.with(context.getApplicationContext())
.load(contactPhoto)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.circleCrop()
.submit(context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width),
context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height))
.get();
} catch (InterruptedException | ExecutionException e) {
return fallbackContactPhoto.asDrawable(context, recipient.getColor().toConversationColor(context));
}
} else {
return fallbackContactPhoto.asDrawable(context, recipient.getColor().toConversationColor(context));
}
}
public void setMessageCount(int messageCount) { public void setMessageCount(int messageCount) {
setContentInfo(String.valueOf(messageCount)); setContentInfo(String.valueOf(messageCount));
setNumber(messageCount); setNumber(messageCount);
@ -139,10 +147,10 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
NotificationCompat.CarExtender.UnreadConversation.Builder unreadConversationBuilder = NotificationCompat.CarExtender.UnreadConversation.Builder unreadConversationBuilder =
new NotificationCompat.CarExtender.UnreadConversation.Builder(contentTitle.toString()) new NotificationCompat.CarExtender.UnreadConversation.Builder(contentTitle.toString())
.addMessage(contentText.toString()) .addMessage(contentText.toString())
.setLatestTimestamp(timestamp) .setLatestTimestamp(timestamp)
.setReadPendingIntent(androidAutoHeardIntent) .setReadPendingIntent(androidAutoHeardIntent)
.setReplyAction(androidAutoReplyIntent, remoteInput); .setReplyAction(androidAutoReplyIntent, remoteInput);
extend(new NotificationCompat.CarExtender().setUnreadConversation(unreadConversationBuilder.build())); extend(new NotificationCompat.CarExtender().setUnreadConversation(unreadConversationBuilder.build()));
} }
@ -202,19 +210,35 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
public void addMessageBody(@NonNull Recipient threadRecipient, public void addMessageBody(@NonNull Recipient threadRecipient,
@NonNull Recipient individualRecipient, @NonNull Recipient individualRecipient,
@Nullable CharSequence messageBody) @Nullable CharSequence messageBody,
long timestamp)
{ {
SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
Person.Builder personBuilder = new Person.Builder()
.setKey(individualRecipient.getId().serialize())
.setBot(false);
if (privacy.isDisplayContact() && threadRecipient.isGroup()) { this.threadRecipient = threadRecipient;
stringBuilder.append(Util.getBoldedString(individualRecipient.toShortString(context) + ": "));
}
if (privacy.isDisplayMessage()) { if (privacy.isDisplayContact()) {
messageBodies.add(stringBuilder.append(messageBody == null ? "" : messageBody)); personBuilder.setName(individualRecipient.getDisplayName(context));
Bitmap bitmap = getLargeBitmap(getContactDrawable(individualRecipient));
if (bitmap != null) {
personBuilder.setIcon(IconCompat.createWithBitmap(bitmap));
}
} else { } else {
messageBodies.add(stringBuilder.append(context.getString(R.string.SingleRecipientNotificationBuilder_new_message))); personBuilder.setName("");
} }
final CharSequence text;
if (privacy.isDisplayMessage()) {
text = messageBody == null ? "" : messageBody;
} else {
text = stringBuilder.append(context.getString(R.string.SingleRecipientNotificationBuilder_new_message));
}
messages.add(new NotificationCompat.MessagingStyle.Message(text, timestamp, personBuilder.build()));
} }
@Override @Override
@ -223,33 +247,68 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
Optional<Uri> largeIconUri = getLargeIconUri(slideDeck); Optional<Uri> largeIconUri = getLargeIconUri(slideDeck);
Optional<Uri> bigPictureUri = getBigPictureUri(slideDeck); Optional<Uri> bigPictureUri = getBigPictureUri(slideDeck);
if (messageBodies.size() == 1 && largeIconUri.isPresent()) { if (messages.size() == 1 && largeIconUri.isPresent()) {
setLargeIcon(getNotificationPicture(largeIconUri.get(), LARGE_ICON_DIMEN)); setLargeIcon(getNotificationPicture(largeIconUri.get(), LARGE_ICON_DIMEN));
} }
if (messageBodies.size() == 1 && bigPictureUri.isPresent()) { if (messages.size() == 1 && bigPictureUri.isPresent()) {
setStyle(new NotificationCompat.BigPictureStyle() setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(getNotificationPicture(bigPictureUri.get(), BIG_PICTURE_DIMEN)) .bigPicture(getNotificationPicture(bigPictureUri.get(), BIG_PICTURE_DIMEN))
.setSummaryText(getBigText(messageBodies))); .setSummaryText(getBigText()));
} else { } else {
setStyle(new NotificationCompat.BigTextStyle().bigText(getBigText(messageBodies))); if (Build.VERSION.SDK_INT >= 24) {
applyMessageStyle();
} else {
applyLegacy();
}
} }
} }
return super.build(); return super.build();
} }
private void applyMessageStyle() {
NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle(
new Person.Builder()
.setBot(false)
.setName(Recipient.self().getDisplayName(context))
.setKey(Recipient.self().getId().serialize())
.build());
if (threadRecipient.isGroup()) {
if (privacy.isDisplayContact()) {
messagingStyle.setConversationTitle(threadRecipient.getDisplayName(context));
} else {
messagingStyle.setConversationTitle(context.getString(R.string.SingleRecipientNotificationBuilder_signal));
}
messagingStyle.setGroupConversation(true);
}
Stream.of(messages).forEach(messagingStyle::addMessage);
setStyle(messagingStyle);
}
private void applyLegacy() {
setStyle(new NotificationCompat.BigTextStyle().bigText(getBigText()));
}
private void setLargeIcon(@Nullable Drawable drawable) { private void setLargeIcon(@Nullable Drawable drawable) {
if (drawable != null) { if (drawable != null) {
int largeIconTargetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size); setLargeIcon(getLargeBitmap(drawable));
Bitmap recipientPhotoBitmap = BitmapUtil.createFromDrawable(drawable, largeIconTargetSize, largeIconTargetSize);
if (recipientPhotoBitmap != null) {
setLargeIcon(recipientPhotoBitmap);
}
} }
} }
private @Nullable Bitmap getLargeBitmap(@Nullable Drawable drawable) {
if (drawable != null) {
int largeIconTargetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
return BitmapUtil.createFromDrawable(drawable, largeIconTargetSize, largeIconTargetSize);
}
return null;
}
private static Optional<Uri> getLargeIconUri(@Nullable SlideDeck slideDeck) { private static Optional<Uri> getLargeIconUri(@Nullable SlideDeck slideDeck) {
if (slideDeck == null) { if (slideDeck == null) {
return Optional.absent(); return Optional.absent();
@ -302,12 +361,12 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
return super.setContentText(this.contentText); return super.setContentText(this.contentText);
} }
private CharSequence getBigText(List<CharSequence> messageBodies) { private CharSequence getBigText() {
SpannableStringBuilder content = new SpannableStringBuilder(); SpannableStringBuilder content = new SpannableStringBuilder();
for (int i = 0; i < messageBodies.size(); i++) { for (int i = 0; i < messages.size(); i++) {
content.append(trimToDisplayLength(messageBodies.get(i))); content.append(getBigTextFor(messages.get(i)));
if (i < messageBodies.size() - 1) { if (i < messages.size() - 1) {
content.append('\n'); content.append('\n');
} }
} }
@ -315,4 +374,14 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
return content; return content;
} }
private CharSequence getBigTextFor(NotificationCompat.MessagingStyle.Message message) {
SpannableStringBuilder content = new SpannableStringBuilder();
if (message.getPerson() != null && message.getPerson().getName() != null && threadRecipient.isGroup()) {
content.append(Util.getBoldedString(message.getPerson().getName().toString())).append(": ");
}
return trimToDisplayLength(content.append(message.getText()));
}
} }