2014-11-08 19:35:58 +00:00
|
|
|
package org.thoughtcrime.securesms.jobs;
|
|
|
|
|
|
|
|
import android.content.Context;
|
2019-04-17 14:21:30 +00:00
|
|
|
import android.graphics.Bitmap;
|
2017-08-22 17:44:04 +00:00
|
|
|
import android.support.annotation.NonNull;
|
2018-12-08 02:31:39 +00:00
|
|
|
import android.support.annotation.Nullable;
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
|
|
|
import com.annimon.stream.Stream;
|
2014-11-08 19:35:58 +00:00
|
|
|
|
2017-02-18 04:27:11 +00:00
|
|
|
import org.greenrobot.eventbus.EventBus;
|
2017-01-06 17:19:58 +00:00
|
|
|
import org.thoughtcrime.securesms.ApplicationContext;
|
|
|
|
import org.thoughtcrime.securesms.TextSecureExpiredException;
|
2015-10-13 01:25:05 +00:00
|
|
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
2018-04-27 00:03:54 +00:00
|
|
|
import org.thoughtcrime.securesms.contactshare.Contact;
|
|
|
|
import org.thoughtcrime.securesms.contactshare.ContactModelMapper;
|
2017-08-25 19:00:52 +00:00
|
|
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
2017-07-26 16:59:15 +00:00
|
|
|
import org.thoughtcrime.securesms.database.Address;
|
2014-11-08 19:35:58 +00:00
|
|
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
2015-11-02 22:32:02 +00:00
|
|
|
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
2019-03-28 15:56:35 +00:00
|
|
|
import org.thoughtcrime.securesms.jobmanager.Job;
|
|
|
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
2018-08-01 15:09:24 +00:00
|
|
|
import org.thoughtcrime.securesms.logging.Log;
|
2018-02-07 22:01:37 +00:00
|
|
|
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader;
|
|
|
|
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
2014-12-12 09:03:24 +00:00
|
|
|
import org.thoughtcrime.securesms.mms.PartAuthority;
|
2014-11-08 19:35:58 +00:00
|
|
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
2017-08-01 15:56:00 +00:00
|
|
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
2018-12-08 02:31:39 +00:00
|
|
|
import org.thoughtcrime.securesms.util.Base64;
|
2018-02-07 22:01:37 +00:00
|
|
|
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
|
|
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
2019-04-17 14:21:30 +00:00
|
|
|
import org.thoughtcrime.securesms.util.Hex;
|
2018-02-07 22:01:37 +00:00
|
|
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
2017-01-06 17:19:58 +00:00
|
|
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
2018-12-08 02:31:39 +00:00
|
|
|
import org.thoughtcrime.securesms.util.Util;
|
2016-03-23 17:34:41 +00:00
|
|
|
import org.whispersystems.libsignal.util.guava.Optional;
|
2019-01-14 07:30:54 +00:00
|
|
|
import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair;
|
2016-03-23 17:34:41 +00:00
|
|
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
|
2018-12-08 02:31:39 +00:00
|
|
|
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer;
|
2018-02-07 22:01:37 +00:00
|
|
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage;
|
2019-01-15 08:41:05 +00:00
|
|
|
import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage.Preview;
|
2019-01-14 07:30:54 +00:00
|
|
|
import org.whispersystems.signalservice.api.messages.multidevice.SentTranscriptMessage;
|
|
|
|
import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage;
|
2018-04-27 00:03:54 +00:00
|
|
|
import org.whispersystems.signalservice.api.messages.shared.SharedContact;
|
2016-03-23 17:34:41 +00:00
|
|
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
2014-11-08 19:35:58 +00:00
|
|
|
|
2018-02-07 22:01:37 +00:00
|
|
|
import java.io.ByteArrayInputStream;
|
2014-12-12 09:03:24 +00:00
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
2019-01-14 07:30:54 +00:00
|
|
|
import java.util.Collections;
|
2014-11-08 19:35:58 +00:00
|
|
|
import java.util.LinkedList;
|
|
|
|
import java.util.List;
|
2018-06-20 02:22:39 +00:00
|
|
|
import java.util.concurrent.TimeUnit;
|
2014-11-08 19:35:58 +00:00
|
|
|
|
2015-01-12 04:27:34 +00:00
|
|
|
public abstract class PushSendJob extends SendJob {
|
2014-11-08 19:35:58 +00:00
|
|
|
|
2018-11-22 01:26:06 +00:00
|
|
|
private static final String TAG = PushSendJob.class.getSimpleName();
|
|
|
|
private static final long CERTIFICATE_EXPIRATION_BUFFER = TimeUnit.DAYS.toMillis(1);
|
2014-11-08 19:35:58 +00:00
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
protected PushSendJob(Job.Parameters parameters) {
|
|
|
|
super(parameters);
|
2014-11-08 19:35:58 +00:00
|
|
|
}
|
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
protected static Job.Parameters constructParameters(Address destination) {
|
|
|
|
return new Parameters.Builder()
|
|
|
|
.setQueue(destination.serialize())
|
|
|
|
.addConstraint(NetworkConstraint.KEY)
|
|
|
|
.setLifespan(TimeUnit.DAYS.toMillis(1))
|
2020-05-19 03:57:47 +00:00
|
|
|
.setMaxAttempts(1)
|
2019-03-28 15:56:35 +00:00
|
|
|
.build();
|
2014-11-08 19:35:58 +00:00
|
|
|
}
|
|
|
|
|
2017-01-06 17:19:58 +00:00
|
|
|
@Override
|
2018-11-15 20:05:08 +00:00
|
|
|
protected final void onSend() throws Exception {
|
2017-01-06 17:19:58 +00:00
|
|
|
if (TextSecurePreferences.getSignedPreKeyFailureCount(context) > 5) {
|
|
|
|
ApplicationContext.getInstance(context)
|
|
|
|
.getJobManager()
|
2019-03-28 15:56:35 +00:00
|
|
|
.add(new RotateSignedPreKeyJob());
|
2017-01-06 17:19:58 +00:00
|
|
|
|
|
|
|
throw new TextSecureExpiredException("Too many signed prekey rotation failures");
|
|
|
|
}
|
|
|
|
|
2018-02-02 03:22:48 +00:00
|
|
|
onPushSend();
|
2017-01-06 17:19:58 +00:00
|
|
|
}
|
|
|
|
|
2018-06-11 16:37:01 +00:00
|
|
|
@Override
|
2018-06-20 20:56:05 +00:00
|
|
|
public void onRetry() {
|
|
|
|
super.onRetry();
|
2018-08-02 13:50:36 +00:00
|
|
|
Log.i(TAG, "onRetry()");
|
2018-06-20 20:56:05 +00:00
|
|
|
|
2019-03-28 15:56:35 +00:00
|
|
|
if (getRunAttempt() > 1) {
|
2018-08-02 13:50:36 +00:00
|
|
|
Log.i(TAG, "Scheduling service outage detection job.");
|
2019-03-28 15:56:35 +00:00
|
|
|
ApplicationContext.getInstance(context).getJobManager().add(new ServiceOutageDetectionJob());
|
2018-06-20 20:56:05 +00:00
|
|
|
}
|
2018-06-11 16:37:01 +00:00
|
|
|
}
|
|
|
|
|
2017-08-22 17:44:04 +00:00
|
|
|
protected Optional<byte[]> getProfileKey(@NonNull Recipient recipient) {
|
2017-08-25 19:00:52 +00:00
|
|
|
if (!recipient.resolve().isSystemContact() && !recipient.resolve().isProfileSharing()) {
|
|
|
|
return Optional.absent();
|
2017-08-15 01:11:13 +00:00
|
|
|
}
|
2017-08-25 19:00:52 +00:00
|
|
|
|
|
|
|
return Optional.of(ProfileKeyUtil.getProfileKey(context));
|
2017-08-15 01:11:13 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 16:59:15 +00:00
|
|
|
protected SignalServiceAddress getPushAddress(Address address) {
|
2017-08-07 21:24:53 +00:00
|
|
|
String relay = null;
|
2017-07-26 16:59:15 +00:00
|
|
|
return new SignalServiceAddress(address.toPhoneString(), Optional.fromNullable(relay));
|
2014-11-08 19:35:58 +00:00
|
|
|
}
|
|
|
|
|
2018-01-25 03:17:44 +00:00
|
|
|
protected List<SignalServiceAttachment> getAttachmentsFor(List<Attachment> parts) {
|
2016-03-23 17:34:41 +00:00
|
|
|
List<SignalServiceAttachment> attachments = new LinkedList<>();
|
2014-11-08 19:35:58 +00:00
|
|
|
|
2015-10-13 01:25:05 +00:00
|
|
|
for (final Attachment attachment : parts) {
|
2018-04-27 00:03:54 +00:00
|
|
|
SignalServiceAttachment converted = getAttachmentFor(attachment);
|
|
|
|
if (converted != null) {
|
|
|
|
attachments.add(converted);
|
2014-11-08 19:35:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return attachments;
|
|
|
|
}
|
|
|
|
|
2018-04-27 00:03:54 +00:00
|
|
|
protected SignalServiceAttachment getAttachmentFor(Attachment attachment) {
|
|
|
|
try {
|
|
|
|
if (attachment.getDataUri() == null || attachment.getSize() == 0) throw new IOException("Assertion failed, outgoing attachment has no data!");
|
|
|
|
InputStream is = PartAuthority.getAttachmentStream(context, attachment.getDataUri());
|
|
|
|
return SignalServiceAttachment.newStreamBuilder()
|
|
|
|
.withStream(is)
|
|
|
|
.withContentType(attachment.getContentType())
|
|
|
|
.withLength(attachment.getSize())
|
|
|
|
.withFileName(attachment.getFileName())
|
|
|
|
.withVoiceNote(attachment.isVoiceNote())
|
|
|
|
.withWidth(attachment.getWidth())
|
|
|
|
.withHeight(attachment.getHeight())
|
2018-11-09 07:33:37 +00:00
|
|
|
.withCaption(attachment.getCaption())
|
2018-04-27 00:03:54 +00:00
|
|
|
.withListener((total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress)))
|
|
|
|
.build();
|
|
|
|
} catch (IOException ioe) {
|
|
|
|
Log.w(TAG, "Couldn't open attachment", ioe);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-01-15 20:43:38 +00:00
|
|
|
protected @NonNull List<SignalServiceAttachment> getAttachmentPointersFor(List<Attachment> attachments) {
|
2018-12-08 02:31:39 +00:00
|
|
|
return Stream.of(attachments).map(this::getAttachmentPointerFor).filter(a -> a != null).toList();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected @Nullable SignalServiceAttachment getAttachmentPointerFor(Attachment attachment) {
|
|
|
|
if (TextUtils.isEmpty(attachment.getLocation())) {
|
|
|
|
Log.w(TAG, "empty content id");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (TextUtils.isEmpty(attachment.getKey())) {
|
|
|
|
Log.w(TAG, "empty encrypted key");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
long id = Long.parseLong(attachment.getLocation());
|
|
|
|
byte[] key = Base64.decode(attachment.getKey());
|
|
|
|
|
|
|
|
return new SignalServiceAttachmentPointer(id,
|
|
|
|
attachment.getContentType(),
|
|
|
|
key,
|
|
|
|
Optional.of(Util.toIntExact(attachment.getSize())),
|
|
|
|
Optional.absent(),
|
|
|
|
attachment.getWidth(),
|
|
|
|
attachment.getHeight(),
|
|
|
|
Optional.fromNullable(attachment.getDigest()),
|
|
|
|
Optional.fromNullable(attachment.getFileName()),
|
|
|
|
attachment.isVoiceNote(),
|
2019-10-16 23:39:59 +00:00
|
|
|
Optional.fromNullable(attachment.getCaption()), attachment.getUrl());
|
2018-12-08 02:31:39 +00:00
|
|
|
} catch (IOException | ArithmeticException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected static void notifyMediaMessageDeliveryFailed(Context context, long messageId) {
|
2017-08-01 15:56:00 +00:00
|
|
|
long threadId = DatabaseFactory.getMmsDatabase(context).getThreadIdForMessage(messageId);
|
|
|
|
Recipient recipient = DatabaseFactory.getThreadDatabase(context).getRecipientForThreadId(threadId);
|
2014-11-08 19:35:58 +00:00
|
|
|
|
2017-08-01 15:56:00 +00:00
|
|
|
if (threadId != -1 && recipient != null) {
|
|
|
|
MessageNotifier.notifyMessageDeliveryFailed(context, recipient, threadId);
|
2015-04-14 17:01:33 +00:00
|
|
|
}
|
2014-11-08 19:35:58 +00:00
|
|
|
}
|
2017-01-06 17:19:58 +00:00
|
|
|
|
2018-02-07 22:01:37 +00:00
|
|
|
protected Optional<SignalServiceDataMessage.Quote> getQuoteFor(OutgoingMediaMessage message) {
|
|
|
|
if (message.getOutgoingQuote() == null) return Optional.absent();
|
|
|
|
|
2018-04-02 23:17:32 +00:00
|
|
|
long quoteId = message.getOutgoingQuote().getId();
|
|
|
|
String quoteBody = message.getOutgoingQuote().getText();
|
|
|
|
Address quoteAuthor = message.getOutgoingQuote().getAuthor();
|
|
|
|
List<SignalServiceDataMessage.Quote.QuotedAttachment> quoteAttachments = new LinkedList<>();
|
2018-02-07 22:01:37 +00:00
|
|
|
|
|
|
|
for (Attachment attachment : message.getOutgoingQuote().getAttachments()) {
|
2018-04-02 23:17:32 +00:00
|
|
|
BitmapUtil.ScaleResult thumbnailData = null;
|
|
|
|
SignalServiceAttachment thumbnail = null;
|
2019-04-17 14:21:30 +00:00
|
|
|
String thumbnailType = MediaUtil.IMAGE_JPEG;
|
2018-02-07 22:01:37 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
if (MediaUtil.isImageType(attachment.getContentType()) && attachment.getDataUri() != null) {
|
2019-04-17 14:21:30 +00:00
|
|
|
Bitmap.CompressFormat format = BitmapUtil.getCompressFormatForContentType(attachment.getContentType());
|
|
|
|
|
|
|
|
thumbnailData = BitmapUtil.createScaledBytes(context, new DecryptableStreamUriLoader.DecryptableUri(attachment.getDataUri()), 100, 100, 500 * 1024, format);
|
|
|
|
thumbnailType = attachment.getContentType();
|
2018-02-07 22:01:37 +00:00
|
|
|
} else if (MediaUtil.isVideoType(attachment.getContentType()) && attachment.getThumbnailUri() != null) {
|
2018-04-02 23:17:32 +00:00
|
|
|
thumbnailData = BitmapUtil.createScaledBytes(context, new DecryptableStreamUriLoader.DecryptableUri(attachment.getThumbnailUri()), 100, 100, 500 * 1024);
|
2018-02-07 22:01:37 +00:00
|
|
|
}
|
|
|
|
|
2018-04-02 23:17:32 +00:00
|
|
|
if (thumbnailData != null) {
|
|
|
|
thumbnail = SignalServiceAttachment.newStreamBuilder()
|
2019-04-17 14:21:30 +00:00
|
|
|
.withContentType(thumbnailType)
|
2018-04-02 23:17:32 +00:00
|
|
|
.withWidth(thumbnailData.getWidth())
|
|
|
|
.withHeight(thumbnailData.getHeight())
|
|
|
|
.withLength(thumbnailData.getBitmap().length)
|
|
|
|
.withStream(new ByteArrayInputStream(thumbnailData.getBitmap()))
|
|
|
|
.build();
|
2018-02-07 22:01:37 +00:00
|
|
|
}
|
|
|
|
|
2018-04-02 23:17:32 +00:00
|
|
|
quoteAttachments.add(new SignalServiceDataMessage.Quote.QuotedAttachment(attachment.getContentType(),
|
|
|
|
attachment.getFileName(),
|
|
|
|
thumbnail));
|
2018-02-07 22:01:37 +00:00
|
|
|
} catch (BitmapDecodingException e) {
|
|
|
|
Log.w(TAG, e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return Optional.of(new SignalServiceDataMessage.Quote(quoteId, new SignalServiceAddress(quoteAuthor.serialize()), quoteBody, quoteAttachments));
|
|
|
|
}
|
|
|
|
|
2019-04-17 14:21:30 +00:00
|
|
|
protected Optional<SignalServiceDataMessage.Sticker> getStickerFor(OutgoingMediaMessage message) {
|
|
|
|
Attachment stickerAttachment = Stream.of(message.getAttachments()).filter(Attachment::isSticker).findFirst().orElse(null);
|
|
|
|
|
|
|
|
if (stickerAttachment == null) {
|
|
|
|
return Optional.absent();
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
byte[] packId = Hex.fromStringCondensed(stickerAttachment.getSticker().getPackId());
|
|
|
|
byte[] packKey = Hex.fromStringCondensed(stickerAttachment.getSticker().getPackKey());
|
|
|
|
int stickerId = stickerAttachment.getSticker().getStickerId();
|
|
|
|
SignalServiceAttachment attachment = getAttachmentPointerFor(stickerAttachment);
|
|
|
|
|
|
|
|
return Optional.of(new SignalServiceDataMessage.Sticker(packId, packKey, stickerId, attachment));
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.w(TAG, "Failed to decode sticker id/key", e);
|
|
|
|
return Optional.absent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-27 00:03:54 +00:00
|
|
|
List<SharedContact> getSharedContactsFor(OutgoingMediaMessage mediaMessage) {
|
|
|
|
List<SharedContact> sharedContacts = new LinkedList<>();
|
|
|
|
|
|
|
|
for (Contact contact : mediaMessage.getSharedContacts()) {
|
|
|
|
SharedContact.Builder builder = ContactModelMapper.localToRemoteBuilder(contact);
|
|
|
|
SharedContact.Avatar avatar = null;
|
|
|
|
|
|
|
|
if (contact.getAvatar() != null && contact.getAvatar().getAttachment() != null) {
|
|
|
|
avatar = SharedContact.Avatar.newBuilder().withAttachment(getAttachmentFor(contact.getAvatarAttachment()))
|
|
|
|
.withProfileFlag(contact.getAvatar().isProfile())
|
|
|
|
.build();
|
|
|
|
}
|
|
|
|
|
|
|
|
builder.setAvatar(avatar);
|
|
|
|
sharedContacts.add(builder.build());
|
|
|
|
}
|
|
|
|
|
|
|
|
return sharedContacts;
|
|
|
|
}
|
2018-02-07 22:01:37 +00:00
|
|
|
|
2019-01-15 08:41:05 +00:00
|
|
|
List<Preview> getPreviewsFor(OutgoingMediaMessage mediaMessage) {
|
|
|
|
return Stream.of(mediaMessage.getLinkPreviews()).map(lp -> {
|
|
|
|
SignalServiceAttachment attachment = lp.getThumbnail().isPresent() ? getAttachmentPointerFor(lp.getThumbnail().get()) : null;
|
|
|
|
return new Preview(lp.getUrl(), lp.getTitle(), Optional.fromNullable(attachment));
|
|
|
|
}).toList();
|
|
|
|
}
|
|
|
|
|
2018-11-22 01:26:06 +00:00
|
|
|
protected void rotateSenderCertificateIfNecessary() throws IOException {
|
2020-01-28 01:27:12 +00:00
|
|
|
// Loki - We don't need verification on sender certificates
|
2018-11-22 01:26:06 +00:00
|
|
|
}
|
|
|
|
|
2019-01-14 07:30:54 +00:00
|
|
|
protected SignalServiceSyncMessage buildSelfSendSyncMessage(@NonNull Context context, @NonNull SignalServiceDataMessage message, Optional<UnidentifiedAccessPair> syncAccess) {
|
2019-11-13 01:28:17 +00:00
|
|
|
String primary = TextSecurePreferences.getMasterHexEncodedPublicKey(context);
|
|
|
|
String localNumber = primary != null ? primary : TextSecurePreferences.getLocalNumber(context);
|
2019-01-14 07:30:54 +00:00
|
|
|
SentTranscriptMessage transcript = new SentTranscriptMessage(localNumber,
|
|
|
|
message.getTimestamp(),
|
|
|
|
message,
|
|
|
|
message.getExpiresInSeconds(),
|
|
|
|
Collections.singletonMap(localNumber, syncAccess.isPresent()));
|
|
|
|
return SignalServiceSyncMessage.forSentTranscript(transcript);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-02-02 03:22:48 +00:00
|
|
|
protected abstract void onPushSend() throws Exception;
|
2014-11-08 19:35:58 +00:00
|
|
|
}
|