From bdb950373a2eab9e5787d5c5c5fa0ac1d72f8dac Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 16 Sep 2019 16:11:07 +1000 Subject: [PATCH 1/4] Partially implement GIFs --- .../database/AttachmentDatabase.java | 7 +++++- .../securesms/jobs/PushDecryptJob.java | 17 +++++++++++++ .../linkpreview/LinkPreviewDomains.java | 3 ++- .../linkpreview/LinkPreviewRepository.java | 24 +++++++++++++++++++ 4 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 0827a2bd1a..92f5d10467 100644 --- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -739,7 +739,12 @@ public class AttachmentDatabase extends Database { if (thumbnailUri != null) { try (InputStream attachmentStream = PartAuthority.getAttachmentStream(context, thumbnailUri)) { - Pair dimens = BitmapUtil.getDimensions(attachmentStream); + Pair dimens; + if (attachment.getContentType().equals(MediaUtil.IMAGE_GIF)) { + dimens = new Pair<>(attachment.getWidth(), attachment.getHeight()); + } else { + dimens = BitmapUtil.getDimensions(attachmentStream); + } updateAttachmentThumbnail(attachmentId, PartAuthority.getAttachmentStream(context, thumbnailUri), (float) dimens.first / (float) dimens.second); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index e12ca79897..41fbee2b18 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -978,6 +978,23 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } })); } + } if (LinkPreviewUtil.isWhitelistedMediaUrl(body)) { + new LinkPreviewRepository(context).fetchGIF(context, body, attachmentOrNull -> Util.runOnMain(() -> { + if (attachmentOrNull.isPresent()) { + Attachment attachment = attachmentOrNull.get(); + try { + IncomingMediaMessage mediaMessage = new IncomingMediaMessage(Address.fromExternal(context, content.getSender()), message.getTimestamp(), -1, + message.getExpiresInSeconds() * 1000L, false, content.isNeedsReceipt(), message.getBody(), message.getGroupInfo(), Optional.of(new ArrayList<>()), + Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent()); + mediaMessage.getAttachments().add(attachment); + handleMediaMessage(content, mediaMessage, smsMessageId, messageServerIDOrNull); + } catch (Exception e) { + // TODO: Handle + } + } else { + // TODO: Handle + } + })); } else { Optional insertResult = database.insertMessageInbox(textMessage); diff --git a/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewDomains.java b/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewDomains.java index 6e99f84ac9..01f56ce798 100644 --- a/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewDomains.java +++ b/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewDomains.java @@ -33,6 +33,7 @@ public class LinkPreviewDomains { "redd.it", "imgur.com", "pinimg.com", - "giphy.com" + "giphy.com", + "tenor.com" )); } diff --git a/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java index 324ec905c0..0f03fbef77 100644 --- a/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java +++ b/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java @@ -8,6 +8,7 @@ import android.text.Html; import android.text.TextUtils; import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.request.FutureTarget; import org.thoughtcrime.securesms.ApplicationContext; @@ -150,6 +151,29 @@ public class LinkPreviewRepository implements InjectableType { return new CallRequestController(call); } + public @NonNull RequestController fetchGIF(@NonNull Context context, @NonNull String url, @NonNull Callback> callback) { + FutureTarget future = GlideApp.with(context).asGif().load(new ChunkedImageUrl(url)).skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE) + .centerInside().submit(1024, 1024); + RequestController controller = () -> future.cancel(false); + SignalExecutors.UNBOUNDED.execute(() -> { + try { + GifDrawable gif = future.get(); + byte[] bytes = new byte[gif.getBuffer().remaining()]; + gif.getBuffer().get(bytes); + Uri uri = BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory(); + Optional thumbnail = Optional.of(new UriAttachment(uri, uri, MediaUtil.IMAGE_GIF, AttachmentDatabase.TRANSFER_PROGRESS_DONE, + bytes.length, gif.getIntrinsicWidth(), gif.getIntrinsicHeight(), null, null, false, false, null, null)); + callback.onComplete(thumbnail); + } catch (CancellationException | ExecutionException | InterruptedException e) { + controller.cancel(); + callback.onComplete(Optional.absent()); + } finally { + future.cancel(false); + } + }); + return () -> future.cancel(true); + } + private @NonNull RequestController fetchThumbnail(@NonNull Context context, @NonNull String imageUrl, @NonNull Callback> callback) { FutureTarget bitmapFuture = GlideApp.with(context).asBitmap() .load(new ChunkedImageUrl(imageUrl)) From e5d82b325d3bd2f8d6ac9155fa6b73049ca195aa Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 17 Sep 2019 09:57:23 +1000 Subject: [PATCH 2/4] Fix GIFs --- .../securesms/linkpreview/LinkPreviewRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java b/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java index 0f03fbef77..6d6d0f4ba7 100644 --- a/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java +++ b/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewRepository.java @@ -11,6 +11,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.resource.gif.GifDrawable; import com.bumptech.glide.request.FutureTarget; +import com.bumptech.glide.util.ByteBufferUtil; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.UriAttachment; @@ -158,8 +159,7 @@ public class LinkPreviewRepository implements InjectableType { SignalExecutors.UNBOUNDED.execute(() -> { try { GifDrawable gif = future.get(); - byte[] bytes = new byte[gif.getBuffer().remaining()]; - gif.getBuffer().get(bytes); + byte[] bytes = ByteBufferUtil.toBytes(gif.getBuffer()); Uri uri = BlobProvider.getInstance().forData(bytes).createForSingleSessionInMemory(); Optional thumbnail = Optional.of(new UriAttachment(uri, uri, MediaUtil.IMAGE_GIF, AttachmentDatabase.TRANSFER_PROGRESS_DONE, bytes.length, gif.getIntrinsicWidth(), gif.getIntrinsicHeight(), null, null, false, false, null, null)); From df36d5e539be1cbcad9a03916c68e29d66c542fa Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 17 Sep 2019 10:36:34 +1000 Subject: [PATCH 3/4] Handle case where GIF fetch fails --- .../securesms/jobs/PushDecryptJob.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 41fbee2b18..286ceab5bb 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -946,14 +946,14 @@ public class PushDecryptJob extends BaseJob implements InjectableType { } else { notifyTypingStoppedFromIncomingMessage(recipient, content.getSender(), content.getSenderDevice()); - IncomingTextMessage textMessage = new IncomingTextMessage(Address.fromSerialized(content.getSender()), + IncomingTextMessage _textMessage = new IncomingTextMessage(Address.fromSerialized(content.getSender()), content.getSenderDevice(), message.getTimestamp(), body, message.getGroupInfo(), message.getExpiresInSeconds() * 1000L, content.isNeedsReceipt()); - textMessage = new IncomingEncryptedMessage(textMessage, body); + IncomingEncryptedMessage textMessage = new IncomingEncryptedMessage(_textMessage, body); List urls = LinkPreviewUtil.findWhitelistedUrls(body); int urlCount = urls.size(); @@ -992,28 +992,34 @@ public class PushDecryptJob extends BaseJob implements InjectableType { // TODO: Handle } } else { - // TODO: Handle + handleTextMessage(message, textMessage, smsMessageId, messageServerIDOrNull); } })); } else { - Optional insertResult = database.insertMessageInbox(textMessage); - - if (insertResult.isPresent()) threadId = insertResult.get().getThreadId(); - else threadId = null; - - if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get()); - - // Loki - Store message server ID - updateGroupChatMessageServerID(messageServerIDOrNull, insertResult); - - boolean isGroupMessage = message.getGroupInfo().isPresent(); - if (threadId != null && !isGroupMessage) { - MessageNotifier.updateNotification(context, threadId); - } + handleTextMessage(message, textMessage, smsMessageId, messageServerIDOrNull); } } } + private void handleTextMessage(@NonNull SignalServiceDataMessage message, @NonNull IncomingTextMessage textMessage, @NonNull Optional smsMessageId, @NonNull Optional messageServerIDOrNull) { + SmsDatabase database = DatabaseFactory.getSmsDatabase(context); + Optional insertResult = database.insertMessageInbox(textMessage); + + Long threadId; + if (insertResult.isPresent()) threadId = insertResult.get().getThreadId(); + else threadId = null; + + if (smsMessageId.isPresent()) database.deleteMessage(smsMessageId.get()); + + // Loki - Store message server ID + updateGroupChatMessageServerID(messageServerIDOrNull, insertResult); + + boolean isGroupMessage = message.getGroupInfo().isPresent(); + if (threadId != null && !isGroupMessage) { + MessageNotifier.updateNotification(context, threadId); + } + } + private void updateGroupChatMessageServerID(Optional messageServerIDOrNull, Optional insertResult) { if (insertResult.isPresent() && messageServerIDOrNull.isPresent()) { long messageID = insertResult.get().getMessageId(); From 74dfac6fd1b94077ceceaa3713a48c16a5beb9e6 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 17 Sep 2019 11:46:47 +1000 Subject: [PATCH 4/4] Implement GIF sending --- .../conversation/ConversationActivity.java | 12 ++-- .../securesms/jobs/PushGroupSendJob.java | 2 +- .../securesms/jobs/PushMediaSendJob.java | 2 +- .../linkpreview/LinkPreviewDomains.java | 3 +- .../securesms/sms/MessageSender.java | 55 ++++++++++++------- 5 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 88c5404192..8da4fd994e 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -152,6 +152,7 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; +import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.loki.FriendRequestViewDelegate; @@ -2180,11 +2181,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity long expiresIn = recipient.getExpireMessages() * 1000L; boolean initiating = threadId == -1; boolean needsSplit = !transport.isSms() && message.length() > transport.calculateCharacters(message).maxPrimaryMessageSize; - boolean isMediaMessage = attachmentManager.isAttachmentPresent() || - recipient.isGroupRecipient() || - recipient.getAddress().isEmail() || - inputPanel.getQuote().isPresent() || - linkPreviewViewModel.hasLinkPreview() || + boolean isMediaMessage = attachmentManager.isAttachmentPresent() || + recipient.isGroupRecipient() || + recipient.getAddress().isEmail() || + inputPanel.getQuote().isPresent() || + linkPreviewViewModel.hasLinkPreview() || + LinkPreviewUtil.isWhitelistedMediaUrl(message) || needsSplit; Log.i(TAG, "isManual Selection: " + sendButton.isManualSelection()); diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index 92c0827ccd..4d8b3e5775 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -95,7 +95,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType { OutgoingMediaMessage message = database.getOutgoingMessage(messageId); List attachments = new LinkedList<>(); - attachments.addAll(message.getAttachments()); + // attachments.addAll(message.getAttachments()); // attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList()); attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList()); diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index 3f384fff8f..dd39fcb69d 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -76,7 +76,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType { OutgoingMediaMessage message = database.getOutgoingMessage(messageId); List attachments = new LinkedList<>(); - attachments.addAll(message.getAttachments()); + // attachments.addAll(message.getAttachments()); // attachments.addAll(Stream.of(message.getLinkPreviews()).filter(p -> p.getThumbnail().isPresent()).map(p -> p.getThumbnail().get()).toList()); attachments.addAll(Stream.of(message.getSharedContacts()).filter(c -> c.getAvatar() != null).map(c -> c.getAvatar().getAttachment()).withoutNulls().toList()); diff --git a/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewDomains.java b/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewDomains.java index 01f56ce798..6e99f84ac9 100644 --- a/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewDomains.java +++ b/src/org/thoughtcrime/securesms/linkpreview/LinkPreviewDomains.java @@ -33,7 +33,6 @@ public class LinkPreviewDomains { "redd.it", "imgur.com", "pinimg.com", - "giphy.com", - "tenor.com" + "giphy.com" )); } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index df61b4b164..a33ac1d25e 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -39,6 +39,8 @@ import org.thoughtcrime.securesms.jobs.PushGroupSendJob; import org.thoughtcrime.securesms.jobs.PushMediaSendJob; import org.thoughtcrime.securesms.jobs.PushTextSendJob; import org.thoughtcrime.securesms.jobs.SmsSendJob; +import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository; +import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; @@ -46,6 +48,7 @@ import org.thoughtcrime.securesms.push.AccountManagerFactory; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.service.ExpiringMessageManager; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; import org.whispersystems.signalservice.api.push.ContactTokenDetails; @@ -93,28 +96,42 @@ public class MessageSender { final boolean forceSms, final SmsDatabase.InsertListener insertListener) { - try { - ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - long allocatedThreadId; + long allocatedThreadId; - if (threadId == -1) { - allocatedThreadId = threadDatabase.getThreadIdFor(message.getRecipient(), message.getDistributionType()); - } else { - allocatedThreadId = threadId; - } - - Recipient recipient = message.getRecipient(); - long messageId = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener); - - sendMediaMessage(context, recipient, forceSms, messageId, message.getExpiresIn()); - - return allocatedThreadId; - } catch (MmsException e) { - Log.w(TAG, e); - return threadId; + if (threadId == -1) { + allocatedThreadId = threadDatabase.getThreadIdFor(message.getRecipient(), message.getDistributionType()); + } else { + allocatedThreadId = threadId; } + + Recipient recipient = message.getRecipient(); + + if (message.getLinkPreviews().isEmpty() && message.getAttachments().isEmpty() && LinkPreviewUtil.isWhitelistedMediaUrl(message.getBody())) { + new LinkPreviewRepository(context).fetchGIF(context, message.getBody(), attachmentOrNull -> Util.runOnMain(() -> { + if (attachmentOrNull.isPresent()) { + Attachment attachment = attachmentOrNull.get(); + try { + message.getAttachments().add(attachment); + long messageId = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener); + sendMediaMessage(context, recipient, forceSms, messageId, message.getExpiresIn()); + } catch (Exception e) { + // TODO: Handle + } + } else { + try { + long messageId = database.insertMessageOutbox(message, allocatedThreadId, forceSms, insertListener); + sendMediaMessage(context, recipient, forceSms, messageId, message.getExpiresIn()); + } catch (MmsException e) { + // TODO: Handle + } + } + })); + } + + return allocatedThreadId; } public static void resendGroupMessage(Context context, MessageRecord messageRecord, Address filterAddress) {