Support for images in notifications.

Closes #3859
Fixes #1858
// FREEBIE
This commit is contained in:
Moxie Marlinspike 2015-07-31 16:46:17 -07:00
parent 120cde9917
commit f8bb065ffd
4 changed files with 106 additions and 25 deletions

View File

@ -12,6 +12,7 @@ import org.thoughtcrime.securesms.database.PartDatabase;
import org.thoughtcrime.securesms.database.PartDatabase.PartId; import org.thoughtcrime.securesms.database.PartDatabase.PartId;
import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement; import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.jobqueue.JobParameters; import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement; import org.whispersystems.jobqueue.requirements.NetworkRequirement;
@ -66,6 +67,8 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
retrievePart(masterSecret, part, messageId); retrievePart(masterSecret, part, messageId);
Log.w(TAG, "Got part: " + part.getPartId()); Log.w(TAG, "Got part: " + part.getPartId());
} }
MessageNotifier.updateNotification(context, masterSecret);
} }
@Override @Override

View File

@ -42,13 +42,17 @@ import org.thoughtcrime.securesms.database.MmsSmsDatabase;
import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.database.PushDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientFactory; import org.thoughtcrime.securesms.recipients.RecipientFactory;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.service.KeyCachingService;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.SpanUtil; import org.thoughtcrime.securesms.util.SpanUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope; import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
import java.io.IOException; import java.io.IOException;
@ -187,12 +191,12 @@ public class MessageNotifier {
return; return;
} }
SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, TextSecurePreferences.getNotificationPrivacy(context)); SingleRecipientNotificationBuilder builder = new SingleRecipientNotificationBuilder(context, masterSecret, TextSecurePreferences.getNotificationPrivacy(context));
List<NotificationItem> notifications = notificationState.getNotifications(); List<NotificationItem> notifications = notificationState.getNotifications();
builder.setSender(notifications.get(0).getIndividualRecipient()); builder.setSender(notifications.get(0).getIndividualRecipient());
builder.setMessageCount(notificationState.getMessageCount()); builder.setMessageCount(notificationState.getMessageCount());
builder.setPrimaryMessageBody(notifications.get(0).getText()); builder.setPrimaryMessageBody(notifications.get(0).getText(), notifications.get(0).getSlideDeck());
builder.setContentIntent(notifications.get(0).getPendingIntent(context)); builder.setContentIntent(notifications.get(0).getPendingIntent(context));
long timestamp = notifications.get(0).getTimestamp(); long timestamp = notifications.get(0).getTimestamp();
@ -318,7 +322,7 @@ public class MessageNotifier {
body.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); body.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
if (!recipients.isMuted()) { if (!recipients.isMuted()) {
notificationState.addNotification(new NotificationItem(recipient, recipients, null, threadId, body, 0)); notificationState.addNotification(new NotificationItem(recipient, recipients, null, threadId, body, 0, null));
} }
} }
} finally { } finally {
@ -344,6 +348,7 @@ public class MessageNotifier {
long threadId = record.getThreadId(); long threadId = record.getThreadId();
CharSequence body = record.getDisplayBody(); CharSequence body = record.getDisplayBody();
Recipients threadRecipients = null; Recipients threadRecipients = null;
ListenableFutureTask<SlideDeck> slideDeck = null;
long timestamp; long timestamp;
if (record.isPush()) timestamp = record.getDateSent(); if (record.isPush()) timestamp = record.getDateSent();
@ -357,14 +362,16 @@ public class MessageNotifier {
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message)); body = SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message));
} else if (record.isMms() && TextUtils.isEmpty(body)) { } else if (record.isMms() && TextUtils.isEmpty(body)) {
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_media_message)); body = SpanUtil.italic(context.getString(R.string.MessageNotifier_media_message));
slideDeck = ((MediaMmsMessageRecord)record).getSlideDeckFuture();
} else if (record.isMms() && !record.isMmsNotification()) { } else if (record.isMms() && !record.isMmsNotification()) {
String message = context.getString(R.string.MessageNotifier_media_message_with_text, body); String message = context.getString(R.string.MessageNotifier_media_message_with_text, body);
int italicLength = message.length() - body.length(); int italicLength = message.length() - body.length();
body = SpanUtil.italic(message, italicLength); body = SpanUtil.italic(message, italicLength);
slideDeck = ((MediaMmsMessageRecord)record).getSlideDeckFuture();
} }
if (threadRecipients == null || !threadRecipients.isMuted()) { if (threadRecipients == null || !threadRecipients.isMuted()) {
notificationState.addNotification(new NotificationItem(recipient, recipients, threadRecipients, threadId, body, timestamp)); notificationState.addNotification(new NotificationItem(recipient, recipients, threadRecipients, threadId, body, timestamp, slideDeck));
} }
} }

View File

@ -4,10 +4,14 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.ConversationActivity; import org.thoughtcrime.securesms.ConversationActivity;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.Recipients; import org.thoughtcrime.securesms.recipients.Recipients;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
public class NotificationItem { public class NotificationItem {
@ -17,10 +21,12 @@ public class NotificationItem {
private final long threadId; private final long threadId;
private final CharSequence text; private final CharSequence text;
private final long timestamp; private final long timestamp;
private final ListenableFutureTask<SlideDeck> slideDeck;
public NotificationItem(Recipient individualRecipient, Recipients recipients, public NotificationItem(Recipient individualRecipient, Recipients recipients,
Recipients threadRecipients, long threadId, Recipients threadRecipients, long threadId,
CharSequence text, long timestamp) CharSequence text, long timestamp,
@Nullable ListenableFutureTask<SlideDeck> slideDeck)
{ {
this.individualRecipient = individualRecipient; this.individualRecipient = individualRecipient;
this.recipients = recipients; this.recipients = recipients;
@ -28,6 +34,7 @@ public class NotificationItem {
this.text = text; this.text = text;
this.threadId = threadId; this.threadId = threadId;
this.timestamp = timestamp; this.timestamp = timestamp;
this.slideDeck = slideDeck;
} }
public Recipients getRecipients() { public Recipients getRecipients() {
@ -50,6 +57,10 @@ public class NotificationItem {
return threadId; return threadId;
} }
public @Nullable ListenableFutureTask<SlideDeck> getSlideDeck() {
return slideDeck;
}
public PendingIntent getPendingIntent(Context context) { public PendingIntent getPendingIntent(Context context) {
Intent intent = new Intent(context, ConversationActivity.class); Intent intent = new Intent(context, ConversationActivity.class);
Recipients notifyRecipients = threadRecipients != null ? threadRecipients : recipients; Recipients notifyRecipients = threadRecipients != null ? threadRecipients : recipients;

View File

@ -6,28 +6,46 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Action; import android.support.v4.app.NotificationCompat.Action;
import android.support.v4.app.RemoteInput; import android.support.v4.app.RemoteInput;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.util.Log;
import com.bumptech.glide.Glide;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.preferences.NotificationPrivacyPreference; import org.thoughtcrime.securesms.preferences.NotificationPrivacyPreference;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.ListenableFutureTask;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException;
public class SingleRecipientNotificationBuilder extends AbstractNotificationBuilder { public class SingleRecipientNotificationBuilder extends AbstractNotificationBuilder {
private static final String TAG = SingleRecipientNotificationBuilder.class.getSimpleName();
private final List<CharSequence> messageBodies = new LinkedList<>(); private final List<CharSequence> messageBodies = new LinkedList<>();
public SingleRecipientNotificationBuilder(@NonNull Context context, @NonNull NotificationPrivacyPreference privacy) { private ListenableFutureTask<SlideDeck> slideDeck;
private final MasterSecret masterSecret;
public SingleRecipientNotificationBuilder(@NonNull Context context,
@Nullable MasterSecret masterSecret,
@NonNull NotificationPrivacyPreference privacy)
{
super(context, privacy); super(context, privacy);
this.masterSecret = masterSecret;
setSmallIcon(R.drawable.icon_notification); setSmallIcon(R.drawable.icon_notification);
setColor(context.getResources().getColor(R.color.textsecure_primary)); setColor(context.getResources().getColor(R.color.textsecure_primary));
@ -62,9 +80,10 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
setNumber(messageCount); setNumber(messageCount);
} }
public void setPrimaryMessageBody(CharSequence message) { public void setPrimaryMessageBody(CharSequence message, @Nullable ListenableFutureTask<SlideDeck> slideDeck) {
if (privacy.isDisplayMessage()) { if (privacy.isDisplayMessage()) {
setContentText(message); setContentText(message);
this.slideDeck = slideDeck;
} else { } else {
setContentText(context.getString(R.string.SingleRecipientNotificationBuilder_contents_hidden)); setContentText(context.getString(R.string.SingleRecipientNotificationBuilder_contents_hidden));
} }
@ -122,14 +141,14 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
@Override @Override
public Notification build() { public Notification build() {
if (privacy.isDisplayMessage()) { if (privacy.isDisplayMessage()) {
SpannableStringBuilder content = new SpannableStringBuilder(); if (messageBodies.size() == 1 && hasBigPictureSlide(slideDeck)) {
assert masterSecret != null;
for (CharSequence message : messageBodies) { setStyle(new NotificationCompat.BigPictureStyle()
content.append(message); .bigPicture(getBigPicture(masterSecret, slideDeck))
content.append('\n'); .setSummaryText(getBigText(messageBodies)));
} else {
setStyle(new NotificationCompat.BigTextStyle().bigText(getBigText(messageBodies)));
} }
setStyle(new NotificationCompat.BigTextStyle().bigText(content));
} }
return super.build(); return super.build();
@ -146,4 +165,45 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil
} }
} }
private boolean hasBigPictureSlide(@Nullable ListenableFutureTask<SlideDeck> slideDeck) {
try {
return masterSecret != null &&
slideDeck != null &&
Build.VERSION.SDK_INT >= 16 &&
slideDeck.get().getThumbnailSlide(context).hasImage() &&
!slideDeck.get().getThumbnailSlide(context).isInProgress() &&
slideDeck.get().getThumbnailSlide(context).getThumbnailUri() != null;
} catch (InterruptedException | ExecutionException e) {
Log.w(TAG, e);
return false;
}
}
private Bitmap getBigPicture(@NonNull MasterSecret masterSecret,
@NonNull ListenableFutureTask<SlideDeck> slideDeck)
{
try {
Uri uri = slideDeck.get().getThumbnailSlide(context).getThumbnailUri();
return Glide.with(context)
.load(new DecryptableStreamUriLoader.DecryptableUri(masterSecret, uri))
.asBitmap()
.into(500, 500)
.get();
} catch (InterruptedException | ExecutionException e) {
throw new AssertionError(e);
}
}
private CharSequence getBigText(List<CharSequence> messageBodies) {
SpannableStringBuilder content = new SpannableStringBuilder();
for (CharSequence message : messageBodies) {
content.append(message);
content.append('\n');
}
return content;
}
} }