Apply MessageStyle and fix chronology issues.

master
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.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.core.text.HtmlCompat;
import android.text.SpannableStringBuilder;
import android.text.TextUtils;
@ -71,11 +70,9 @@ import org.thoughtcrime.securesms.webrtc.CallNotificationBuilder;
import org.whispersystems.signalservice.internal.util.Util;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
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) {
if (Build.VERSION.SDK_INT >= 23) {
try {
@ -209,7 +230,7 @@ public class MessageNotifier {
return;
}
updateNotification(context, false, 0);
updateNotification(context, -1, false, 0);
}
public static void updateNotification(@NonNull Context context, long threadId)
@ -226,7 +247,7 @@ public class MessageNotifier {
long threadId,
boolean signal)
{
boolean isVisible = visibleThread == threadId;
boolean isVisible = visibleThread == threadId;
ThreadDatabase threads = DatabaseFactory.getThreadDatabase(context);
Recipient recipients = DatabaseFactory.getThreadDatabase(context)
@ -246,11 +267,12 @@ public class MessageNotifier {
if (isVisible) {
sendInThreadNotification(context, threads.getRecipientForThreadId(threadId));
} else {
updateNotification(context, signal, 0);
updateNotification(context, threadId, signal, 0);
}
}
private static void updateNotification(@NonNull Context context,
long targetThread,
boolean signal,
int reminderCount)
{
@ -281,13 +303,22 @@ public class MessageNotifier {
if (notificationState.hasMultipleThreads()) {
if (Build.VERSION.SDK_INT >= 23) {
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 {
sendSingleThreadNotification(context, notificationState, signal, false);
if (isDisplayingSummaryNotification(context)) {
sendMultipleThreadNotification(context, notificationState, false);
}
}
cancelOrphanedNotifications(context, notificationState);
@ -302,9 +333,10 @@ public class MessageNotifier {
}
}
private static void sendSingleThreadNotification(@NonNull Context context,
@NonNull NotificationState notificationState,
boolean signal, boolean bundled)
private static void sendSingleThreadNotification(@NonNull Context context,
@NonNull NotificationState notificationState,
boolean signal,
boolean bundled)
{
Log.i(TAG, "sendSingleThreadNotification() signal: " + signal + " bundled: " + bundled);
@ -317,8 +349,13 @@ public class MessageNotifier {
SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context));
List<NotificationItem> notifications = notificationState.getNotifications();
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.setMessageCount(notificationState.getMessageCount());
@ -327,7 +364,7 @@ public class MessageNotifier {
builder.setContentIntent(notifications.get(0).getPendingIntent(context));
builder.setDeleteIntent(notificationState.getDeleteIntent(context));
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();
if (timestamp != 0) builder.setWhen(timestamp);
@ -348,7 +385,7 @@ public class MessageNotifier {
while(iterator.hasPrevious()) {
NotificationItem item = iterator.previous();
builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText());
builder.addMessageBody(item.getRecipient(), item.getIndividualRecipient(), item.getText(), item.getTimestamp());
}
if (signal) {
@ -357,9 +394,9 @@ public class MessageNotifier {
notifications.get(0).getText());
}
if (bundled) {
if (Build.VERSION.SDK_INT >= 23) {
builder.setGroup(NOTIFICATION_GROUP);
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY);
builder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
}
Notification notification = builder.build();
@ -378,10 +415,13 @@ public class MessageNotifier {
builder.setMessageCount(notificationState.getMessageCount(), notificationState.getThreadCount());
builder.setMostRecentSender(notifications.get(0).getIndividualRecipient());
builder.setGroup(NOTIFICATION_GROUP);
builder.setDeleteIntent(notificationState.getDeleteIntent(context));
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();
if (timestamp != 0) builder.setWhen(timestamp);
@ -599,7 +639,7 @@ public class MessageNotifier {
@Override
protected Void doInBackground(Void... params) {
int reminderCount = intent.getIntExtra("reminder_count", 0);
MessageNotifier.updateNotification(context, true, reminderCount + 1);
MessageNotifier.updateNotification(context, -1, true, reminderCount + 1);
return null;
}

View File

@ -13,9 +13,13 @@ import androidx.annotation.StringRes;
import androidx.appcompat.view.ContextThemeWrapper;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.Action;
import androidx.core.app.Person;
import androidx.core.app.RemoteInput;
import androidx.core.graphics.drawable.IconCompat;
import android.text.SpannableStringBuilder;
import com.annimon.stream.Stream;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
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 LARGE_ICON_DIMEN = 250;
private final List<CharSequence> messageBodies = new LinkedList<>();
private final List<NotificationCompat.MessagingStyle.Message> messages = new LinkedList<>();
private SlideDeck slideDeck;
private CharSequence contentTitle;
private CharSequence contentText;
private Recipient threadRecipient;
public SingleRecipientNotificationBuilder(@NonNull Context context, @NonNull NotificationPrivacyPreference privacy)
{
@ -76,25 +81,7 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
addPerson(recipient.getContactUri().toString());
}
ContactPhoto contactPhoto = recipient.getContactPhoto();
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)));
}
setLargeIcon(getContactDrawable(recipient));
} else {
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) {
setContentInfo(String.valueOf(messageCount));
setNumber(messageCount);
@ -139,10 +147,10 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
NotificationCompat.CarExtender.UnreadConversation.Builder unreadConversationBuilder =
new NotificationCompat.CarExtender.UnreadConversation.Builder(contentTitle.toString())
.addMessage(contentText.toString())
.setLatestTimestamp(timestamp)
.setReadPendingIntent(androidAutoHeardIntent)
.setReplyAction(androidAutoReplyIntent, remoteInput);
.addMessage(contentText.toString())
.setLatestTimestamp(timestamp)
.setReadPendingIntent(androidAutoHeardIntent)
.setReplyAction(androidAutoReplyIntent, remoteInput);
extend(new NotificationCompat.CarExtender().setUnreadConversation(unreadConversationBuilder.build()));
}
@ -202,19 +210,35 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
public void addMessageBody(@NonNull Recipient threadRecipient,
@NonNull Recipient individualRecipient,
@Nullable CharSequence messageBody)
@Nullable CharSequence messageBody,
long timestamp)
{
SpannableStringBuilder stringBuilder = new SpannableStringBuilder();
Person.Builder personBuilder = new Person.Builder()
.setKey(individualRecipient.getId().serialize())
.setBot(false);
if (privacy.isDisplayContact() && threadRecipient.isGroup()) {
stringBuilder.append(Util.getBoldedString(individualRecipient.toShortString(context) + ": "));
}
this.threadRecipient = threadRecipient;
if (privacy.isDisplayMessage()) {
messageBodies.add(stringBuilder.append(messageBody == null ? "" : messageBody));
if (privacy.isDisplayContact()) {
personBuilder.setName(individualRecipient.getDisplayName(context));
Bitmap bitmap = getLargeBitmap(getContactDrawable(individualRecipient));
if (bitmap != null) {
personBuilder.setIcon(IconCompat.createWithBitmap(bitmap));
}
} 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
@ -223,33 +247,68 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
Optional<Uri> largeIconUri = getLargeIconUri(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));
}
if (messageBodies.size() == 1 && bigPictureUri.isPresent()) {
if (messages.size() == 1 && bigPictureUri.isPresent()) {
setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(getNotificationPicture(bigPictureUri.get(), BIG_PICTURE_DIMEN))
.setSummaryText(getBigText(messageBodies)));
.setSummaryText(getBigText()));
} else {
setStyle(new NotificationCompat.BigTextStyle().bigText(getBigText(messageBodies)));
if (Build.VERSION.SDK_INT >= 24) {
applyMessageStyle();
} else {
applyLegacy();
}
}
}
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) {
if (drawable != null) {
int largeIconTargetSize = context.getResources().getDimensionPixelSize(R.dimen.contact_photo_target_size);
Bitmap recipientPhotoBitmap = BitmapUtil.createFromDrawable(drawable, largeIconTargetSize, largeIconTargetSize);
if (recipientPhotoBitmap != null) {
setLargeIcon(recipientPhotoBitmap);
}
setLargeIcon(getLargeBitmap(drawable));
}
}
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) {
if (slideDeck == null) {
return Optional.absent();
@ -302,12 +361,12 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
return super.setContentText(this.contentText);
}
private CharSequence getBigText(List<CharSequence> messageBodies) {
private CharSequence getBigText() {
SpannableStringBuilder content = new SpannableStringBuilder();
for (int i = 0; i < messageBodies.size(); i++) {
content.append(trimToDisplayLength(messageBodies.get(i)));
if (i < messageBodies.size() - 1) {
for (int i = 0; i < messages.size(); i++) {
content.append(getBigTextFor(messages.get(i)));
if (i < messages.size() - 1) {
content.append('\n');
}
}
@ -315,4 +374,14 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
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()));
}
}