diff --git a/src/org/thoughtcrime/securesms/components/TransferControlView.java b/src/org/thoughtcrime/securesms/components/TransferControlView.java index 1d67cb0351..ad85bfa381 100644 --- a/src/org/thoughtcrime/securesms/components/TransferControlView.java +++ b/src/org/thoughtcrime/securesms/components/TransferControlView.java @@ -2,14 +2,15 @@ package org.thoughtcrime.securesms.components; import android.animation.LayoutTransition; import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.content.ContextCompat; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; + import com.annimon.stream.Stream; import com.pnikosis.materialishprogress.ProgressWheel; @@ -28,7 +29,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -public class TransferControlView extends FrameLayout { +public final class TransferControlView extends FrameLayout { + + private static final int UPLOAD_TASK_WEIGHT = 1; + + /** + * A weighting compared to {@link #UPLOAD_TASK_WEIGHT} + */ + private static final int COMPRESSION_TASK_WEIGHT = 3; @Nullable private List slides; @Nullable private View current; @@ -37,7 +45,8 @@ public class TransferControlView extends FrameLayout { private final View downloadDetails; private final TextView downloadDetailsText; - private final Map downloadProgress; + private final Map networkProgress; + private final Map compresssionProgress; public TransferControlView(Context context) { this(context, null); @@ -56,7 +65,9 @@ public class TransferControlView extends FrameLayout { setVisibility(GONE); setLayoutTransition(new LayoutTransition()); - this.downloadProgress = new HashMap<>(); + this.networkProgress = new HashMap<>(); + this.compresssionProgress = new HashMap<>(); + this.progressWheel = ViewUtil.findById(this, R.id.progress_wheel); this.downloadDetails = ViewUtil.findById(this, R.id.download_details); this.downloadDetailsText = ViewUtil.findById(this, R.id.download_details_text); @@ -98,19 +109,20 @@ public class TransferControlView extends FrameLayout { this.slides = slides; if (!isUpdateToExistingSet(slides)) { - downloadProgress.clear(); - Stream.of(slides).forEach(s -> downloadProgress.put(s.asAttachment(), 0f)); + networkProgress.clear(); + compresssionProgress.clear(); + Stream.of(slides).forEach(s -> networkProgress.put(s.asAttachment(), 0f)); } for (Slide slide : slides) { if (slide.asAttachment().getTransferState() == AttachmentDatabase.TRANSFER_PROGRESS_DONE) { - downloadProgress.put(slide.asAttachment(), 1f); + networkProgress.put(slide.asAttachment(), 1f); } } switch (getTransferState(slides)) { case AttachmentDatabase.TRANSFER_PROGRESS_STARTED: - showProgressSpinner(calculateProgress(downloadProgress)); + showProgressSpinner(calculateProgress(networkProgress, compresssionProgress)); break; case AttachmentDatabase.TRANSFER_PROGRESS_PENDING: case AttachmentDatabase.TRANSFER_PROGRESS_FAILED: @@ -124,7 +136,7 @@ public class TransferControlView extends FrameLayout { } public void showProgressSpinner() { - showProgressSpinner(calculateProgress(downloadProgress)); + showProgressSpinner(calculateProgress(networkProgress, compresssionProgress)); } public void showProgressSpinner(float progress) { @@ -158,12 +170,12 @@ public class TransferControlView extends FrameLayout { } private boolean isUpdateToExistingSet(@NonNull List slides) { - if (slides.size() != downloadProgress.size()) { + if (slides.size() != networkProgress.size()) { return false; } for (Slide slide : slides) { - if (!downloadProgress.containsKey(slide.asAttachment())) { + if (!networkProgress.containsKey(slide.asAttachment())) { return false; } } @@ -207,19 +219,36 @@ public class TransferControlView extends FrameLayout { current = view; } - private float calculateProgress(@NonNull Map downloadProgress) { - float totalProgress = 0; - for (float progress : downloadProgress.values()) { - totalProgress += progress / downloadProgress.size(); + private static float calculateProgress(@NonNull Map uploadDownloadProgress, Map compresssionProgress) { + float totalDownloadProgress = 0; + float totalCompressionProgress = 0; + + for (float progress : uploadDownloadProgress.values()) { + totalDownloadProgress += progress; } - return totalProgress; + + for (float progress : compresssionProgress.values()) { + totalCompressionProgress += progress; + } + + float weightedProgress = UPLOAD_TASK_WEIGHT * totalDownloadProgress + COMPRESSION_TASK_WEIGHT * totalCompressionProgress; + float weightedTotal = UPLOAD_TASK_WEIGHT * uploadDownloadProgress.size() + COMPRESSION_TASK_WEIGHT * compresssionProgress.size(); + + return weightedProgress / weightedTotal; } @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) public void onEventAsync(final PartProgressEvent event) { - if (downloadProgress.containsKey(event.attachment)) { - downloadProgress.put(event.attachment, ((float) event.progress) / event.total); - progressWheel.setInstantProgress(calculateProgress(downloadProgress)); + if (networkProgress.containsKey(event.attachment)) { + float proportionCompleted = ((float) event.progress) / event.total; + + if (event.type == PartProgressEvent.Type.COMPRESSION) { + compresssionProgress.put(event.attachment, proportionCompleted); + } else { + networkProgress.put(event.attachment, proportionCompleted); + } + + progressWheel.setInstantProgress(calculateProgress(networkProgress, compresssionProgress)); } } } diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationItem.java b/src/org/thoughtcrime/securesms/conversation/ConversationItem.java index b6ae879d10..0cb49fe063 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -1340,9 +1340,9 @@ public class ConversationItem extends LinearLayout database.markAsOutbox(messageRecord.getId()); database.markAsForcedSms(messageRecord.getId()); - ApplicationContext.getInstance(context) - .getJobManager() - .add(new MmsSendJob(messageRecord.getId())); + MmsSendJob.enqueue(context, + ApplicationContext.getInstance(context).getJobManager(), + messageRecord.getId()); } else { SmsDatabase database = DatabaseFactory.getSmsDatabase(context); database.markAsInsecure(messageRecord.getId()); diff --git a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java index ee359994ca..fcedea0a50 100644 --- a/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/src/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -497,8 +497,8 @@ public class AttachmentDatabase extends Database { return insertedAttachments; } - public @NonNull DatabaseAttachment updateAttachmentData(@NonNull DatabaseAttachment databaseAttachment, - @NonNull MediaStream mediaStream) + public void updateAttachmentData(@NonNull DatabaseAttachment databaseAttachment, + @NonNull MediaStream mediaStream) throws MmsException { SQLiteDatabase database = databaseHelper.getWritableDatabase(); @@ -518,29 +518,8 @@ public class AttachmentDatabase extends Database { contentValues.put(DATA_RANDOM, dataInfo.random); database.update(TABLE_NAME, contentValues, PART_ID_WHERE, databaseAttachment.getAttachmentId().toStrings()); - - return new DatabaseAttachment(databaseAttachment.getAttachmentId(), - databaseAttachment.getMmsId(), - databaseAttachment.hasData(), - databaseAttachment.hasThumbnail(), - mediaStream.getMimeType(), - databaseAttachment.getTransferState(), - dataInfo.length, - databaseAttachment.getFileName(), - databaseAttachment.getLocation(), - databaseAttachment.getKey(), - databaseAttachment.getRelay(), - databaseAttachment.getDigest(), - databaseAttachment.getFastPreflightId(), - databaseAttachment.isVoiceNote(), - mediaStream.getWidth(), - mediaStream.getHeight(), - databaseAttachment.isQuote(), - databaseAttachment.getCaption(), - databaseAttachment.getSticker()); } - public void updateAttachmentFileName(@NonNull AttachmentId attachmentId, @Nullable String fileName) { diff --git a/src/org/thoughtcrime/securesms/events/PartProgressEvent.java b/src/org/thoughtcrime/securesms/events/PartProgressEvent.java index b824ec16cf..1c669d825d 100644 --- a/src/org/thoughtcrime/securesms/events/PartProgressEvent.java +++ b/src/org/thoughtcrime/securesms/events/PartProgressEvent.java @@ -1,18 +1,24 @@ package org.thoughtcrime.securesms.events; - import androidx.annotation.NonNull; import org.thoughtcrime.securesms.attachments.Attachment; -public class PartProgressEvent { +public final class PartProgressEvent { public final Attachment attachment; + public final Type type; public final long total; public final long progress; - public PartProgressEvent(@NonNull Attachment attachment, long total, long progress) { + public enum Type { + COMPRESSION, + NETWORK + } + + public PartProgressEvent(@NonNull Attachment attachment, @NonNull Type type, long total, long progress) { this.attachment = attachment; + this.type = type; this.total = total; this.progress = progress; } diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java new file mode 100644 index 0000000000..df80e72550 --- /dev/null +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentCompressionJob.java @@ -0,0 +1,231 @@ +package org.thoughtcrime.securesms.jobs; + +import android.content.Context; +import android.media.MediaDataSource; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import org.greenrobot.eventbus.EventBus; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.attachments.AttachmentId; +import org.thoughtcrime.securesms.attachments.DatabaseAttachment; +import org.thoughtcrime.securesms.database.AttachmentDatabase; +import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.events.PartProgressEvent; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; +import org.thoughtcrime.securesms.mms.MediaConstraints; +import org.thoughtcrime.securesms.mms.MediaStream; +import org.thoughtcrime.securesms.mms.MmsException; +import org.thoughtcrime.securesms.service.GenericForegroundService; +import org.thoughtcrime.securesms.service.NotificationController; +import org.thoughtcrime.securesms.transport.UndeliverableMessageException; +import org.thoughtcrime.securesms.util.BitmapDecodingException; +import org.thoughtcrime.securesms.util.BitmapUtil; +import org.thoughtcrime.securesms.util.MediaUtil; +import org.thoughtcrime.securesms.video.InMemoryTranscoder; +import org.thoughtcrime.securesms.video.VideoSizeException; +import org.thoughtcrime.securesms.video.VideoSourceException; +import org.thoughtcrime.securesms.video.videoconverter.EncodingException; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public final class AttachmentCompressionJob extends BaseJob { + + public static final String KEY = "AttachmentCompressionJob"; + + @SuppressWarnings("unused") + private static final String TAG = Log.tag(AttachmentCompressionJob.class); + + private static final String KEY_ROW_ID = "row_id"; + private static final String KEY_UNIQUE_ID = "unique_id"; + private static final String KEY_MMS = "mms"; + private static final String KEY_MMS_SUBSCRIPTION_ID = "mms_subscription_id"; + + private final AttachmentId attachmentId; + private final boolean mms; + private final int mmsSubscriptionId; + + public static AttachmentCompressionJob fromAttachment(@NonNull DatabaseAttachment databaseAttachment, + boolean mms, + int mmsSubscriptionId) + { + return new AttachmentCompressionJob(databaseAttachment.getAttachmentId(), + MediaUtil.isVideo(databaseAttachment) && MediaConstraints.isVideoTranscodeAvailable(), + mms, + mmsSubscriptionId); + } + + private AttachmentCompressionJob(@NonNull AttachmentId attachmentId, + boolean isVideoTranscode, + boolean mms, + int mmsSubscriptionId) + { + this(new Parameters.Builder() + .addConstraint(NetworkConstraint.KEY) + .setLifespan(TimeUnit.DAYS.toMillis(1)) + .setMaxAttempts(Parameters.UNLIMITED) + .setQueue(isVideoTranscode ? "VIDEO_TRANSCODE" : null) + .build(), + attachmentId, + mms, + mmsSubscriptionId); + } + + private AttachmentCompressionJob(@NonNull Parameters parameters, + @NonNull AttachmentId attachmentId, + boolean mms, + int mmsSubscriptionId) + { + super(parameters); + this.attachmentId = attachmentId; + this.mms = mms; + this.mmsSubscriptionId = mmsSubscriptionId; + } + + @Override + public @NonNull Data serialize() { + return new Data.Builder().putLong(KEY_ROW_ID, attachmentId.getRowId()) + .putLong(KEY_UNIQUE_ID, attachmentId.getUniqueId()) + .putBoolean(KEY_MMS, mms) + .putInt(KEY_MMS_SUBSCRIPTION_ID, mmsSubscriptionId) + .build(); + } + + @Override + public @NonNull String getFactoryKey() { + return KEY; + } + + @Override + public void onRun() throws Exception { + AttachmentDatabase database = DatabaseFactory.getAttachmentDatabase(context); + DatabaseAttachment databaseAttachment = database.getAttachment(attachmentId); + + if (databaseAttachment == null) { + throw new IllegalStateException("Cannot find the specified attachment."); + } + + MediaConstraints mediaConstraints = mms ? MediaConstraints.getMmsMediaConstraints(mmsSubscriptionId) + : MediaConstraints.getPushMediaConstraints(); + + scaleAndStripExif(database, mediaConstraints, databaseAttachment); + } + + @Override + public void onCanceled() { } + + @Override + protected boolean onShouldRetry(@NonNull Exception exception) { + return exception instanceof IOException; + } + + private void scaleAndStripExif(@NonNull AttachmentDatabase attachmentDatabase, + @NonNull MediaConstraints constraints, + @NonNull DatabaseAttachment attachment) + throws UndeliverableMessageException + { + try { + if (MediaUtil.isVideo(attachment) && MediaConstraints.isVideoTranscodeAvailable()) { + transcodeVideoIfNeededToDatabase(context, attachmentDatabase, attachment, constraints, EventBus.getDefault()); + } else if (constraints.isSatisfied(context, attachment)) { + if (MediaUtil.isJpeg(attachment)) { + MediaStream stripped = getResizedMedia(context, attachment, constraints); + attachmentDatabase.updateAttachmentData(attachment, stripped); + } + } else if (constraints.canResize(attachment)) { + MediaStream resized = getResizedMedia(context, attachment, constraints); + attachmentDatabase.updateAttachmentData(attachment, resized); + } else { + throw new UndeliverableMessageException("Size constraints could not be met!"); + } + } catch (IOException | MmsException e) { + throw new UndeliverableMessageException(e); + } + } + + @RequiresApi(26) + private static void transcodeVideoIfNeededToDatabase(@NonNull Context context, + @NonNull AttachmentDatabase attachmentDatabase, + @NonNull DatabaseAttachment attachment, + @NonNull MediaConstraints constraints, + @NonNull EventBus eventBus) + throws UndeliverableMessageException + { + try (NotificationController notification = GenericForegroundService.startForegroundTask(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) { + + notification.setIndeterminateProgress(); + + try (MediaDataSource dataSource = attachmentDatabase.mediaDataSourceFor(attachment.getAttachmentId())) { + + if (dataSource == null) { + throw new UndeliverableMessageException("Cannot get media data source for attachment."); + } + + try (InMemoryTranscoder transcoder = new InMemoryTranscoder(context, dataSource, constraints.getCompressedVideoMaxSize(context))) { + + if (transcoder.isTranscodeRequired()) { + + MediaStream mediaStream = transcoder.transcode(percent -> { + notification.setProgress(100, percent); + eventBus.postSticky(new PartProgressEvent(attachment, + PartProgressEvent.Type.COMPRESSION, + 100, + percent)); + }); + + attachmentDatabase.updateAttachmentData(attachment, mediaStream); + } + } + } + } catch (VideoSourceException | EncodingException e) { + if (attachment.getSize() > constraints.getVideoMaxSize(context)) { + throw new UndeliverableMessageException("Duration not found, attachment too large to skip transcode", e); + } else { + Log.w(TAG, "Problem with video source, but video small enough to skip transcode", e); + } + } catch (IOException | MmsException | VideoSizeException e) { + throw new UndeliverableMessageException("Failed to transcode", e); + } + } + + private static MediaStream getResizedMedia(@NonNull Context context, + @NonNull Attachment attachment, + @NonNull MediaConstraints constraints) + throws IOException + { + if (!constraints.canResize(attachment)) { + throw new UnsupportedOperationException("Cannot resize this content type"); + } + + try { + BitmapUtil.ScaleResult scaleResult = BitmapUtil.createScaledBytes(context, + new DecryptableStreamUriLoader.DecryptableUri(attachment.getDataUri()), + constraints); + + return new MediaStream(new ByteArrayInputStream(scaleResult.getBitmap()), + MediaUtil.IMAGE_JPEG, + scaleResult.getWidth(), + scaleResult.getHeight()); + } catch (BitmapDecodingException e) { + throw new IOException(e); + } + } + + public static final class Factory implements Job.Factory { + @Override + public @NonNull AttachmentCompressionJob create(@NonNull Parameters parameters, @NonNull Data data) { + return new AttachmentCompressionJob(parameters, + new AttachmentId(data.getLong(KEY_ROW_ID), data.getLong(KEY_UNIQUE_ID)), + data.getBoolean(KEY_MMS), + data.getInt(KEY_MMS_SUBSCRIPTION_ID)); + } + } +} diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java index 41acc6eaa4..bcf79d9783 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentDownloadJob.java @@ -162,7 +162,7 @@ public class AttachmentDownloadJob extends BaseJob { SignalServiceMessageReceiver messageReceiver = ApplicationDependencies.getSignalServiceMessageReceiver(); SignalServiceAttachmentPointer pointer = createAttachmentPointer(attachment); - InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress))); + InputStream stream = messageReceiver.retrieveAttachment(pointer, attachmentFile, MAX_ATTACHMENT_SIZE, (total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress))); database.insertAttachmentsForPlaceholder(messageId, attachmentId, stream); } catch (InvalidPartException | NonSuccessfulResponseCodeException | InvalidMessageException | MmsException e) { diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java index 89cce42c44..2f13df6738 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java @@ -17,12 +17,9 @@ import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.service.GenericForegroundService; import org.thoughtcrime.securesms.service.NotificationController; -import org.thoughtcrime.securesms.transport.UndeliverableMessageException; -import org.thoughtcrime.securesms.util.MediaUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; @@ -32,11 +29,17 @@ import java.io.IOException; import java.io.InputStream; import java.util.concurrent.TimeUnit; -public class AttachmentUploadJob extends BaseJob { +/** + * Uploads an attachment without alteration. + *

+ * Queue {@link AttachmentCompressionJob} before to compress. + */ +public final class AttachmentUploadJob extends BaseJob { - public static final String KEY = "AttachmentUploadJob"; + public static final String KEY = "AttachmentUploadJobV2"; - private static final String TAG = AttachmentUploadJob.class.getSimpleName(); + @SuppressWarnings("unused") + private static final String TAG = Log.tag(AttachmentUploadJob.class); private static final String KEY_ROW_ID = "row_id"; private static final String KEY_UNIQUE_ID = "unique_id"; @@ -46,26 +49,13 @@ public class AttachmentUploadJob extends BaseJob { */ private static final int FOREGROUND_LIMIT = 10 * 1024 * 1024; - /** - * The {@link PartProgressEvent} on the {@link EventBus} is shared between transcoding and uploading. - *

- * This number is the ratio that represents the transcoding effort, after which it will hand - * over to the to complete the progress. - */ - private static final double ENCODING_PROGRESS_RATIO = 0.75; + private final AttachmentId attachmentId; - private final AttachmentId attachmentId; - - public static AttachmentUploadJob fromAttachment(DatabaseAttachment databaseAttachment) { - return new AttachmentUploadJob(databaseAttachment.getAttachmentId(), MediaUtil.isVideo(databaseAttachment) && MediaConstraints.isVideoTranscodeAvailable()); - } - - private AttachmentUploadJob(AttachmentId attachmentId, boolean isVideoTranscode) { + public AttachmentUploadJob(AttachmentId attachmentId) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) .setMaxAttempts(Parameters.UNLIMITED) - .setQueue(isVideoTranscode ? "VIDEO_TRANSCODE" : null) .build(), attachmentId); } @@ -97,13 +87,8 @@ public class AttachmentUploadJob extends BaseJob { throw new InvalidAttachmentException("Cannot find the specified attachment."); } - MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); - Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment); - boolean videoTranscodeOccurred = databaseAttachment != scaledAttachment && MediaUtil.isVideo(scaledAttachment); - double progressStartPoint = videoTranscodeOccurred ? ENCODING_PROGRESS_RATIO : 0; - - try (NotificationController notification = getNotificationForAttachment(scaledAttachment)) { - SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment, notification, progressStartPoint); + try (NotificationController notification = getNotificationForAttachment(databaseAttachment)) { + SignalServiceAttachment localAttachment = getAttachmentFor(databaseAttachment, notification); SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker()); Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get(); @@ -127,14 +112,7 @@ public class AttachmentUploadJob extends BaseJob { return exception instanceof IOException; } - /** - * @param progressStartPoint A value from 0..1 that represents any progress already shown. - * The {@link PartProgressEvent} of this task will fit in the remaining - * 1 - progressStartPoint. - */ - private @NonNull SignalServiceAttachment getAttachmentFor(Attachment attachment, @Nullable NotificationController notification, double progressStartPoint) - throws InvalidAttachmentException - { + private @NonNull SignalServiceAttachment getAttachmentFor(Attachment attachment, @Nullable NotificationController notification) throws InvalidAttachmentException { 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()); @@ -148,8 +126,7 @@ public class AttachmentUploadJob extends BaseJob { .withHeight(attachment.getHeight()) .withCaption(attachment.getCaption()) .withListener((total, progress) -> { - long cumulativeProgress = (long) ((1.0 - progressStartPoint) * progress + total * progressStartPoint); - EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, cumulativeProgress)); + EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress)); if (notification != null) { notification.setProgress(total, progress); } @@ -160,25 +137,6 @@ public class AttachmentUploadJob extends BaseJob { } } - private Attachment scaleAndStripExif(@NonNull AttachmentDatabase attachmentDatabase, - @NonNull MediaConstraints constraints, - @NonNull DatabaseAttachment attachment) - throws UndeliverableMessageException - { - MediaResizer mediaResizer = new MediaResizer(context, constraints); - - MediaResizer.ProgressListener progressListener = (progress, total) -> { - PartProgressEvent event = new PartProgressEvent(attachment, - total, - (long) (progress * ENCODING_PROGRESS_RATIO)); - EventBus.getDefault().postSticky(event); - }; - - return mediaResizer.scaleAndStripExifToDatabase(attachmentDatabase, - attachment, - progressListener); - } - private class InvalidAttachmentException extends Exception { InvalidAttachmentException(String message) { super(message); diff --git a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index deb775c0c8..080da95cc2 100644 --- a/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/src/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -29,6 +29,7 @@ public final class JobManagerFactories { put(AttachmentCopyJob.KEY, new AttachmentCopyJob.Factory()); put(AttachmentDownloadJob.KEY, new AttachmentDownloadJob.Factory()); put(AttachmentUploadJob.KEY, new AttachmentUploadJob.Factory()); + put(AttachmentCompressionJob.KEY, new AttachmentCompressionJob.Factory()); put(AvatarDownloadJob.KEY, new AvatarDownloadJob.Factory()); put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory()); put(CreateSignedPreKeyJob.KEY, new CreateSignedPreKeyJob.Factory()); @@ -82,6 +83,8 @@ public final class JobManagerFactories { // Dead jobs put("PushContentReceiveJob", new FailingJob.Factory()); + put("AttachmentUploadJob", new FailingJob.Factory()); + put("MmsSendJob", new FailingJob.Factory()); }}; } diff --git a/src/org/thoughtcrime/securesms/jobs/MediaResizer.java b/src/org/thoughtcrime/securesms/jobs/MediaResizer.java deleted file mode 100644 index 7824df71eb..0000000000 --- a/src/org/thoughtcrime/securesms/jobs/MediaResizer.java +++ /dev/null @@ -1,154 +0,0 @@ -package org.thoughtcrime.securesms.jobs; - -import android.content.Context; -import android.media.MediaDataSource; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.attachments.Attachment; -import org.thoughtcrime.securesms.attachments.DatabaseAttachment; -import org.thoughtcrime.securesms.database.AttachmentDatabase; -import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader; -import org.thoughtcrime.securesms.mms.MediaConstraints; -import org.thoughtcrime.securesms.mms.MediaStream; -import org.thoughtcrime.securesms.mms.MmsException; -import org.thoughtcrime.securesms.service.GenericForegroundService; -import org.thoughtcrime.securesms.service.NotificationController; -import org.thoughtcrime.securesms.transport.UndeliverableMessageException; -import org.thoughtcrime.securesms.util.BitmapDecodingException; -import org.thoughtcrime.securesms.util.BitmapUtil; -import org.thoughtcrime.securesms.util.MediaUtil; -import org.thoughtcrime.securesms.video.InMemoryTranscoder; -import org.thoughtcrime.securesms.video.VideoSourceException; -import org.thoughtcrime.securesms.video.VideoSizeException; -import org.thoughtcrime.securesms.video.videoconverter.EncodingException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -final class MediaResizer { - - private static final String TAG = Log.tag(MediaResizer.class); - - @NonNull private final Context context; - @NonNull private final MediaConstraints constraints; - - MediaResizer(@NonNull Context context, - @NonNull MediaConstraints constraints) - { - this.context = context; - this.constraints = constraints; - } - - List scaleAndStripExifToDatabase(@NonNull AttachmentDatabase attachmentDatabase, - @NonNull List attachments) - throws UndeliverableMessageException - { - List results = new ArrayList<>(attachments.size()); - - for (Attachment attachment : attachments) { - results.add(scaleAndStripExifToDatabase(attachmentDatabase, (DatabaseAttachment) attachment, null)); - } - - return results; - } - - DatabaseAttachment scaleAndStripExifToDatabase(@NonNull AttachmentDatabase attachmentDatabase, - @NonNull DatabaseAttachment attachment, - @Nullable ProgressListener transcodeProgressListener) - throws UndeliverableMessageException - { - try { - if (MediaUtil.isVideo(attachment) && MediaConstraints.isVideoTranscodeAvailable()) { - return transcodeVideoIfNeededToDatabase(attachmentDatabase, attachment, transcodeProgressListener); - } else if (constraints.isSatisfied(context, attachment)) { - if (MediaUtil.isJpeg(attachment)) { - MediaStream stripped = getResizedMedia(context, attachment); - return attachmentDatabase.updateAttachmentData(attachment, stripped); - } else { - return attachment; - } - } else if (constraints.canResize(attachment)) { - MediaStream resized = getResizedMedia(context, attachment); - return attachmentDatabase.updateAttachmentData(attachment, resized); - } else { - throw new UndeliverableMessageException("Size constraints could not be met!"); - } - } catch (IOException | MmsException e) { - throw new UndeliverableMessageException(e); - } - } - - @RequiresApi(26) - private @NonNull DatabaseAttachment transcodeVideoIfNeededToDatabase(@NonNull AttachmentDatabase attachmentDatabase, - @NonNull DatabaseAttachment attachment, - @Nullable ProgressListener progressListener) - throws UndeliverableMessageException - { - try (NotificationController notification = GenericForegroundService.startForegroundTask(context, context.getString(R.string.AttachmentUploadJob_compressing_video_start))) { - - notification.setIndeterminateProgress(); - - try (MediaDataSource dataSource = attachmentDatabase.mediaDataSourceFor(attachment.getAttachmentId())) { - - if (dataSource == null) { - throw new UndeliverableMessageException("Cannot get media data source for attachment."); - } - - try (InMemoryTranscoder transcoder = new InMemoryTranscoder(context, dataSource, constraints.getCompressedVideoMaxSize(context))) { - - if (transcoder.isTranscodeRequired()) { - - MediaStream mediaStream = transcoder.transcode(percent -> { - notification.setProgress(100, percent); - - if (progressListener != null) { - progressListener.onProgress(percent, 100); - } - }); - - return attachmentDatabase.updateAttachmentData(attachment, mediaStream); - } else { - return attachment; - } - } - } - } catch (VideoSourceException | EncodingException e) { - if (attachment.getSize() > constraints.getVideoMaxSize(context)) { - throw new UndeliverableMessageException("Duration not found, attachment too large to skip transcode", e); - } else { - Log.w(TAG, "Duration not found, video small enough to skip transcode", e); - return attachment; - } - } catch (IOException | MmsException | VideoSizeException e) { - throw new UndeliverableMessageException("Failed to transcode", e); - } - } - - private MediaStream getResizedMedia(@NonNull Context context, @NonNull Attachment attachment) - throws IOException - { - if (!constraints.canResize(attachment)) { - throw new UnsupportedOperationException("Cannot resize this content type"); - } - - try { - // XXX - This is loading everything into memory! We want the send path to be stream-like. - BitmapUtil.ScaleResult scaleResult = BitmapUtil.createScaledBytes(context, new DecryptableStreamUriLoader.DecryptableUri(attachment.getDataUri()), constraints); - return new MediaStream(new ByteArrayInputStream(scaleResult.getBitmap()), MediaUtil.IMAGE_JPEG, scaleResult.getWidth(), scaleResult.getHeight()); - } catch (BitmapDecodingException e) { - throw new IOException(e); - } - } - - public interface ProgressListener { - - void onProgress(long progress, long total); - } -} diff --git a/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java b/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java index 82e511ca4c..1362f45957 100644 --- a/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/MmsSendJob.java @@ -1,16 +1,14 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; -import androidx.annotation.NonNull; import android.text.TextUtils; - -import org.thoughtcrime.securesms.jobmanager.Data; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.logging.Log; import android.webkit.MimeTypeMap; +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + import com.android.mms.dom.smil.parser.SmilXmlSerializer; +import com.annimon.stream.Stream; import com.google.android.mms.ContentType; import com.google.android.mms.InvalidHeaderValueException; import com.google.android.mms.pdu_alt.CharacterSets; @@ -25,11 +23,17 @@ import com.google.android.mms.smil.SmilHelper; import com.klinker.android.send_message.Utils; import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MmsDatabase; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.ThreadDatabase; +import org.thoughtcrime.securesms.jobmanager.Data; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobmanager.JobManager; +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.CompatMmsConnection; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.mms.MmsException; @@ -50,17 +54,17 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; -public class MmsSendJob extends SendJob { +public final class MmsSendJob extends SendJob { - public static final String KEY = "MmsSendJob"; + public static final String KEY = "MmsSendJobV2"; private static final String TAG = MmsSendJob.class.getSimpleName(); private static final String KEY_MESSAGE_ID = "message_id"; - private long messageId; + private final long messageId; - public MmsSendJob(long messageId) { + private MmsSendJob(long messageId) { this(new Job.Parameters.Builder() .setQueue("mms-operation") .addConstraint(NetworkConstraint.KEY) @@ -69,6 +73,29 @@ public class MmsSendJob extends SendJob { messageId); } + /** Enqueues compression jobs for attachments and finally the MMS send job. */ + @WorkerThread + public static void enqueue(@NonNull Context context, @NonNull JobManager jobManager, long messageId) { + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + OutgoingMediaMessage message; + + try { + message = database.getOutgoingMessage(messageId); + } catch (MmsException | NoSuchMessageException e) { + throw new AssertionError(e); + } + + List compressionJobs = Stream.of(message.getAttachments()) + .map(a -> (Job) AttachmentCompressionJob.fromAttachment((DatabaseAttachment) a, true, message.getSubscriptionId())) + .toList(); + + MmsSendJob sendJob = new MmsSendJob(messageId); + + jobManager.startChain(compressionJobs) + .then(sendJob) + .enqueue(); + } + private MmsSendJob(@NonNull Job.Parameters parameters, long messageId) { super(parameters); this.messageId = messageId; @@ -196,7 +223,7 @@ public class MmsSendJob extends SendJob { String lineNumber = getMyNumber(context); Address destination = message.getRecipient().getAddress(); MediaConstraints mediaConstraints = MediaConstraints.getMmsMediaConstraints(message.getSubscriptionId()); - List scaledAttachments = scaleAndStripExifFromAttachments(mediaConstraints, message.getAttachments()); + List scaledAttachments = message.getAttachments(); if (!TextUtils.isEmpty(lineNumber)) { req.setFrom(new EncodedStringValue(lineNumber)); diff --git a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index 4888e270b1..7dab395463 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -94,12 +94,15 @@ public class PushGroupSendJob extends PushSendJob { 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()); - List attachmentJobs = Stream.of(attachments).map(a -> AttachmentUploadJob.fromAttachment((DatabaseAttachment) a)).toList(); + List compressionJobs = Stream.of(attachments).map(a -> AttachmentCompressionJob.fromAttachment((DatabaseAttachment) a, false, -1)).toList(); + + List attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId())).toList(); if (attachmentJobs.isEmpty()) { jobManager.add(new PushGroupSendJob(messageId, destination, filterAddress)); } else { - jobManager.startChain(attachmentJobs) + jobManager.startChain(compressionJobs) + .then(attachmentJobs) .then(new PushGroupSendJob(messageId, destination, filterAddress)) .enqueue(); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java index cd5b990e55..0c1a9ec0a4 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushMediaSendJob.java @@ -81,12 +81,15 @@ public class PushMediaSendJob extends PushSendJob { 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()); - List attachmentJobs = Stream.of(attachments).map(a -> AttachmentUploadJob.fromAttachment((DatabaseAttachment) a)).toList(); + List compressionJobs = Stream.of(attachments).map(a -> AttachmentCompressionJob.fromAttachment((DatabaseAttachment) a, false, -1)).toList(); + + List attachmentJobs = Stream.of(attachments).map(a -> new AttachmentUploadJob(((DatabaseAttachment) a).getAttachmentId())).toList(); if (attachmentJobs.isEmpty()) { jobManager.add(new PushMediaSendJob(messageId, destination)); } else { - jobManager.startChain(attachmentJobs) + jobManager.startChain(compressionJobs) + .then(attachmentJobs) .then(new PushMediaSendJob(messageId, destination)) .enqueue(); } diff --git a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java index 85ebee790a..28d9ac10a7 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushSendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushSendJob.java @@ -135,7 +135,7 @@ public abstract class PushSendJob extends SendJob { .withWidth(attachment.getWidth()) .withHeight(attachment.getHeight()) .withCaption(attachment.getCaption()) - .withListener((total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress))) + .withListener((total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, PartProgressEvent.Type.NETWORK, total, progress))) .build(); } catch (IOException ioe) { Log.w(TAG, "Couldn't open attachment", ioe); diff --git a/src/org/thoughtcrime/securesms/jobs/SendJob.java b/src/org/thoughtcrime/securesms/jobs/SendJob.java index 1775da3a43..ae773ef7e2 100644 --- a/src/org/thoughtcrime/securesms/jobs/SendJob.java +++ b/src/org/thoughtcrime/securesms/jobs/SendJob.java @@ -46,14 +46,4 @@ public abstract class SendJob extends BaseJob { database.markAttachmentUploaded(messageId, attachment); } } - - protected List scaleAndStripExifFromAttachments(@NonNull MediaConstraints constraints, - @NonNull List attachments) - throws UndeliverableMessageException - { - MediaResizer mediaResizer = new MediaResizer(context, constraints); - AttachmentDatabase attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context); - - return mediaResizer.scaleAndStripExifToDatabase(attachmentDatabase, attachments); - } } diff --git a/src/org/thoughtcrime/securesms/sms/MessageSender.java b/src/org/thoughtcrime/securesms/sms/MessageSender.java index 97f865c555..10d8b122f7 100644 --- a/src/org/thoughtcrime/securesms/sms/MessageSender.java +++ b/src/org/thoughtcrime/securesms/sms/MessageSender.java @@ -16,39 +16,39 @@ */ package org.thoughtcrime.securesms.sms; -import android.app.Application; import android.content.Context; + import androidx.annotation.NonNull; import com.annimon.stream.Stream; -import org.thoughtcrime.securesms.attachments.AttachmentId; -import org.thoughtcrime.securesms.attachments.DatabaseAttachment; -import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; -import org.thoughtcrime.securesms.database.MmsSmsDatabase; -import org.thoughtcrime.securesms.database.NoSuchMessageException; -import org.thoughtcrime.securesms.database.model.SmsMessageRecord; -import org.thoughtcrime.securesms.jobmanager.Job; -import org.thoughtcrime.securesms.jobmanager.JobManager; -import org.thoughtcrime.securesms.jobs.AttachmentCopyJob; -import org.thoughtcrime.securesms.jobs.AttachmentUploadJob; -import org.thoughtcrime.securesms.logging.Log; - import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.attachments.Attachment; +import org.thoughtcrime.securesms.attachments.AttachmentId; +import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.database.Address; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.database.MessagingDatabase.SyncMessageId; import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.database.MmsSmsDatabase; +import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.MessageRecord; +import org.thoughtcrime.securesms.database.model.SmsMessageRecord; +import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobmanager.JobManager; +import org.thoughtcrime.securesms.jobs.AttachmentCopyJob; +import org.thoughtcrime.securesms.jobs.AttachmentCompressionJob; +import org.thoughtcrime.securesms.jobs.AttachmentUploadJob; import org.thoughtcrime.securesms.jobs.MmsSendJob; 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.logging.Log; import org.thoughtcrime.securesms.mms.MmsException; import org.thoughtcrime.securesms.mms.OutgoingMediaMessage; import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage; @@ -171,14 +171,17 @@ public class MessageSender { mmsDatabase.endTransaction(); } - List uploadJobs = new ArrayList<>(databaseAttachments.size()); - List copyJobs = new ArrayList<>(databaseAttachments.size()); - List messageJobs = new ArrayList<>(databaseAttachments.get(0).size()); + List compressionJobs = new ArrayList<>(databaseAttachments.size()); + List uploadJobs = new ArrayList<>(databaseAttachments.size()); + List copyJobs = new ArrayList<>(databaseAttachments.size()); + List messageJobs = new ArrayList<>(databaseAttachments.get(0).size()); for (List attachmentList : databaseAttachments) { DatabaseAttachment source = attachmentList.get(0); - uploadJobs.add(AttachmentUploadJob.fromAttachment(source)); + compressionJobs.add(AttachmentCompressionJob.fromAttachment(source, false, -1)); + + uploadJobs.add(new AttachmentUploadJob(source.getAttachmentId())); if (attachmentList.size() > 1) { AttachmentId sourceId = source.getAttachmentId(); @@ -209,7 +212,10 @@ public class MessageSender { copyJobs.size(), messageJobs.size())); - JobManager.Chain chain = ApplicationContext.getInstance(context).getJobManager().startChain(uploadJobs); + JobManager.Chain chain = ApplicationContext.getInstance(context) + .getJobManager() + .startChain(compressionJobs) + .then(uploadJobs); if (copyJobs.size() > 0) { chain = chain.then(copyJobs); @@ -289,7 +295,7 @@ public class MessageSender { private static void sendMms(Context context, long messageId) { JobManager jobManager = ApplicationContext.getInstance(context).getJobManager(); - jobManager.add(new MmsSendJob(messageId)); + MmsSendJob.enqueue(context, jobManager, messageId); } private static boolean isPushTextSend(Context context, Recipient recipient, boolean keyExchange) { diff --git a/src/org/thoughtcrime/securesms/video/InMemoryTranscoder.java b/src/org/thoughtcrime/securesms/video/InMemoryTranscoder.java index 63656c61d2..719d50a131 100644 --- a/src/org/thoughtcrime/securesms/video/InMemoryTranscoder.java +++ b/src/org/thoughtcrime/securesms/video/InMemoryTranscoder.java @@ -116,7 +116,7 @@ public final class InMemoryTranscoder implements Closeable { memoryFile = MemoryFileDescriptor.newMemoryFileDescriptor(context, "TRANSCODE", - memoryFileEstimate); + memoryFileEstimate); final long startTime = System.currentTimeMillis(); final FileDescriptor memoryFileFileDescriptor = memoryFile.getFileDescriptor(); diff --git a/test/unitTest/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.java b/test/unitTest/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.java new file mode 100644 index 0000000000..c6864d9341 --- /dev/null +++ b/test/unitTest/java/org/thoughtcrime/securesms/jobs/JobManagerFactoriesTest.java @@ -0,0 +1,35 @@ +package org.thoughtcrime.securesms.jobs; + +import android.app.Application; + +import org.junit.Test; +import org.thoughtcrime.securesms.jobmanager.Job; + +import java.util.Map; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +public final class JobManagerFactoriesTest { + + @Test + public void PushContentReceiveJob_is_retired() { + Map factories = JobManagerFactories.getJobFactories(mock(Application.class)); + + assertTrue(factories.get("PushContentReceiveJob") instanceof FailingJob.Factory); + } + + @Test + public void AttachmentUploadJob_is_retired() { + Map factories = JobManagerFactories.getJobFactories(mock(Application.class)); + + assertTrue(factories.get("AttachmentUploadJob") instanceof FailingJob.Factory); + } + + @Test + public void MmsSendJob_is_retired() { + Map factories = JobManagerFactories.getJobFactories(mock(Application.class)); + + assertTrue(factories.get("MmsSendJob") instanceof FailingJob.Factory); + } +}