diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentSaver.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentSaver.kt index b056d472a2..9061f46f82 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentSaver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/AttachmentSaver.kt @@ -21,6 +21,7 @@ import org.signal.core.util.orNull import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.ProgressCardDialogFragment import org.thoughtcrime.securesms.components.ProgressCardDialogFragmentArgs +import org.thoughtcrime.securesms.database.MediaTable import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.permissions.Permissions @@ -56,6 +57,20 @@ class AttachmentSaver(private val host: Host) { fun saveAttachmentsRx(attachments: Set): Completable = rxCompletable { saveAttachments(attachments) } + suspend fun saveAttachments(records: Collection) { + val attachments = records.mapNotNull { record -> + val uri = record.attachment?.uri + val contentType = record.contentType + if (uri != null && contentType != null) { + SaveAttachment(uri, contentType, record.date, record.attachment.fileName) + } else { + null + } + }.toSet() + saveAttachments(attachments) + } + + fun saveAttachmentsRx(records: Collection): Completable = rxCompletable { saveAttachments(records) } suspend fun saveAttachments(attachments: Set) { if (checkIsSaveWarningAccepted(attachmentCount = attachments.size) == SaveToStorageWarningResult.ACCEPTED) { if (checkCanWriteToMediaStore() == RequestPermissionResult.GRANTED) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaActions.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaActions.java index 28c765f011..6b9517d3a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaActions.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaActions.java @@ -1,56 +1,37 @@ package org.thoughtcrime.securesms.mediaoverview; -import android.Manifest; import android.content.Context; import android.content.res.Resources; -import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.attachments.AttachmentSaver; import org.thoughtcrime.securesms.database.MediaTable; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.jobs.MultiDeviceDeleteSyncJob; -import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.AttachmentUtil; -import org.thoughtcrime.securesms.util.SaveAttachmentTask; -import org.thoughtcrime.securesms.util.StorageUtil; import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; import java.util.Collection; import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; import java.util.Set; +import io.reactivex.rxjava3.core.Completable; + final class MediaActions { private MediaActions() { } - static void handleSaveMedia(@NonNull Fragment fragment, - @NonNull Collection mediaRecords, - @Nullable Runnable postExecute) + static Completable handleSaveMedia(@NonNull Fragment fragment, + @NonNull Collection mediaRecords) { - Context context = fragment.requireContext(); - - if (StorageUtil.canWriteToMediaStore()) { - performSaveToDisk(context, mediaRecords, postExecute); - return; - } - - SaveAttachmentTask.showWarningDialogIfNecessary(context, mediaRecords.size(), () -> Permissions.with(fragment) - .request(Manifest.permission.WRITE_EXTERNAL_STORAGE) - .ifNecessary() - .withPermanentDenialDialog(fragment.getString(R.string.AttachmentSaver__signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied)) - .onAnyDenied(() -> Toast.makeText(context, R.string.AttachmentSaver__unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show()) - .onAllGranted(() -> performSaveToDisk(context, mediaRecords, postExecute)) - .execute()); + return new AttachmentSaver(fragment).saveAttachmentsRx(mediaRecords); } static void handleDeleteMedia(@NonNull Context context, @@ -96,37 +77,4 @@ final class MediaActions { builder.setNegativeButton(android.R.string.cancel, null); builder.show(); } - - private static void performSaveToDisk(@NonNull Context context, @NonNull Collection mediaRecords, @Nullable Runnable postExecute) { - new ProgressDialogAsyncTask>(context, - R.string.MediaOverviewActivity_collecting_attachments, - R.string.please_wait) - { - @Override - protected List doInBackground(Void... params) { - List attachments = new LinkedList<>(); - - for (MediaTable.MediaRecord mediaRecord : mediaRecords) { - if (mediaRecord.getAttachment().getUri() != null) { - attachments.add(new SaveAttachmentTask.Attachment(mediaRecord.getAttachment().getUri(), - mediaRecord.getContentType(), - mediaRecord.getDate(), - mediaRecord.getAttachment().fileName)); - } - } - - return attachments; - } - - @Override - protected void onPostExecute(List attachments) { - super.onPostExecute(attachments); - SaveAttachmentTask saveTask = new SaveAttachmentTask(context, attachments.size()); - saveTask.executeOnExecutor(THREAD_POOL_EXECUTOR, - attachments.toArray(new SaveAttachmentTask.Attachment[0])); - - if (postExecute != null) postExecute.run(); - } - }.execute(); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java index 3cb58823d7..f1e3254cf5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediaoverview/MediaOverviewPageFragment.java @@ -46,14 +46,16 @@ import org.thoughtcrime.securesms.database.loaders.MediaLoader; import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity; import org.thoughtcrime.securesms.mms.PartAuthority; +import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.util.BottomOffsetDecoration; import org.thoughtcrime.securesms.util.MediaUtil; -import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; import java.util.Arrays; import java.util.Objects; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; + public final class MediaOverviewPageFragment extends Fragment implements MediaGalleryAllAdapter.ItemClickListener, MediaGalleryAllAdapter.AudioItemListener, @@ -359,9 +361,12 @@ public final class MediaOverviewPageFragment extends Fragment bottomActionBar.setItems(Arrays.asList( new ActionItem(R.drawable.symbol_save_android_24, getResources().getQuantityString(R.plurals.MediaOverviewActivity_save_plural, selectionCount), () -> { - MediaActions.handleSaveMedia(MediaOverviewPageFragment.this, - getListAdapter().getSelectedMedia(), - this::exitMultiSelect); + lifecycleDisposable.add( + MediaActions + .handleSaveMedia(MediaOverviewPageFragment.this, getListAdapter().getSelectedMedia()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(this::exitMultiSelect) + ); }), new ActionItem(R.drawable.symbol_check_circle_24, getString(R.string.MediaOverviewActivity_select_all), this::handleSelectAllMedia), new ActionItem(R.drawable.symbol_trash_24, getResources().getQuantityString(R.plurals.MediaOverviewActivity_delete_plural, selectionCount), this::handleDeleteSelectedMedia) @@ -400,6 +405,12 @@ public final class MediaOverviewPageFragment extends Fragment voiceNoteMediaController.getVoiceNotePlaybackState().removeObserver(observer); } + @SuppressWarnings("deprecation") + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults); + } + private class ActionModeCallback implements ActionMode.Callback { @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentTask.java b/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentTask.java deleted file mode 100644 index 2d807d6339..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentTask.java +++ /dev/null @@ -1,465 +0,0 @@ -package org.thoughtcrime.securesms.util; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.media.MediaScannerConnection; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.provider.MediaStore; -import android.webkit.MimeTypeMap; -import android.widget.CheckBox; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import org.signal.core.util.MapUtil; -import org.signal.core.util.StreamUtil; -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.util.Pair; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.mms.PartAuthority; -import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.ref.WeakReference; -import java.text.SimpleDateFormat; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.TimeUnit; - -/** - * @deprecated Use {@link SaveAttachmentUtil} instead. - */ -@Deprecated -public class SaveAttachmentTask extends ProgressDialogAsyncTask> { - private static final String TAG = Log.tag(SaveAttachmentTask.class); - - static final int SUCCESS = 0; - private static final int FAILURE = 1; - private static final int WRITE_ACCESS_FAILURE = 2; - - private final WeakReference contextReference; - - private final int attachmentCount; - - private final Map> batchOperationNameCache = new HashMap<>(); - - public SaveAttachmentTask(Context context) { - this(context, 1); - } - - public SaveAttachmentTask(Context context, int count) { - super(context, - context.getResources().getQuantityString(R.plurals.ConversationFragment_saving_n_attachments, count, count), - context.getResources().getQuantityString(R.plurals.ConversationFragment_saving_n_attachments_to_sd_card, count, count)); - this.contextReference = new WeakReference<>(context); - this.attachmentCount = count; - } - - @Override - protected Pair doInBackground(SaveAttachmentTask.Attachment... attachments) { - if (attachments == null || attachments.length == 0) { - throw new AssertionError("must pass in at least one attachment"); - } - - try { - Context context = contextReference.get(); - String directory = null; - - if (!StorageUtil.canWriteToMediaStore()) { - return new Pair<>(WRITE_ACCESS_FAILURE, null); - } - - if (context == null) { - return new Pair<>(FAILURE, null); - } - - for (Attachment attachment : attachments) { - if (attachment != null) { - directory = saveAttachment(context, attachment); - if (directory == null) { - return new Pair<>(FAILURE, null); - } - } - } - - if (attachments.length > 1) { - return new Pair<>(SUCCESS, null); - } else { - return new Pair<>(SUCCESS, directory); - } - } catch (IOException ioe) { - Log.w(TAG, ioe); - return new Pair<>(FAILURE, null); - } - } - - private @Nullable String saveAttachment(Context context, Attachment attachment) throws IOException - { - String contentType = Objects.requireNonNull(MediaUtil.getCorrectedMimeType(attachment.contentType)); - String fileName = attachment.fileName; - - if (fileName == null) { - fileName = generateOutputFileName(contentType, attachment.date); - } - - fileName = sanitizeOutputFileName(fileName); - - CreateMediaUriResult result = createMediaUri(getMediaStoreContentUriForType(contentType), contentType, fileName); - ContentValues updateValues = new ContentValues(); - - final Uri mediaUri = result.mediaUri; - - if (mediaUri == null) { - return null; - } - - try (InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.uri)) { - - if (inputStream == null) { - return null; - } - - if (Objects.equals(result.outputUri.getScheme(), ContentResolver.SCHEME_FILE)) { - try (OutputStream outputStream = new FileOutputStream(mediaUri.getPath())) { - StreamUtil.copy(inputStream, outputStream); - MediaScannerConnection.scanFile(context, new String[] { mediaUri.getPath() }, new String[] { contentType }, null); - } - } else { - try (OutputStream outputStream = context.getContentResolver().openOutputStream(mediaUri, "w")) { - long total = StreamUtil.copy(inputStream, outputStream); - if (total > 0) { - updateValues.put(MediaStore.MediaColumns.SIZE, total); - } - } - } - } - - if (Build.VERSION.SDK_INT > 28) { - updateValues.put(MediaStore.MediaColumns.IS_PENDING, 0); - } - - if (updateValues.size() > 0) { - getContext().getContentResolver().update(mediaUri, updateValues, null, null); - } - - return result.outputUri.getLastPathSegment(); - } - - private @NonNull Uri getMediaStoreContentUriForType(@NonNull String contentType) { - if (contentType.startsWith("video/")) { - return StorageUtil.getVideoUri(); - } else if (contentType.startsWith("audio/")) { - return StorageUtil.getAudioUri(); - } else if (contentType.startsWith("image/")) { - return StorageUtil.getImageUri(); - } else { - return StorageUtil.getDownloadUri(); - } - } - - private @Nullable File ensureExternalPath(@Nullable File path) { - if (path != null && path.exists()) { - return path; - } - - if (path == null) { - File documents = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS); - if (documents.exists() || documents.mkdirs()) { - return documents; - } else { - return null; - } - } - - if (path.mkdirs()) { - return path; - } else { - return null; - } - } - - /** - * Returns a path to a shared media (or documents) directory for the type of the file. - *

- * Note that this method attempts to create a directory if the path returned from - * Environment object does not exist yet. The attempt may fail in which case it attempts - * to return the default "Document" path. It finally returns null if it also fails. - * Otherwise it returns the absolute path to the directory. - * - * @param contentType a MIME type of a file - * @return an absolute path to a directory or null - */ - private @Nullable String getExternalPathForType(String contentType) { - File storage = null; - if (contentType.startsWith("video/")) { - storage = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES); - } else if (contentType.startsWith("audio/")) { - storage = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC); - } else if (contentType.startsWith("image/")) { - storage = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); - } - - storage = ensureExternalPath(storage); - if (storage == null) { - return null; - } - - return storage.getAbsolutePath(); - } - - private String generateOutputFileName(@NonNull String contentType, long timestamp) { - MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); - String extension = mimeTypeMap.getExtensionFromMimeType(contentType); - SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS"); - String base = "signal-" + dateFormatter.format(timestamp); - - if (extension == null) extension = "attach"; - - return base + "." + extension; - } - - private String sanitizeOutputFileName(@NonNull String fileName) { - return new File(fileName).getName(); - } - - private @NonNull CreateMediaUriResult createMediaUri(@NonNull Uri outputUri, @NonNull String contentType, @NonNull String fileName) - throws IOException - { - String[] fileParts = getFileNameParts(fileName); - String base = fileParts[0]; - String extension = fileParts[1]; - String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - - if (MediaUtil.isOctetStream(mimeType) && MediaUtil.isImageVideoOrAudioType(contentType)) { - Log.d(TAG, "MimeTypeMap returned octet stream for media, changing to provided content type [" + contentType + "] instead."); - mimeType = contentType; - } - - if (MediaUtil.isOctetStream(mimeType)) { - if (outputUri.equals(StorageUtil.getAudioUri())) { - mimeType = "audio/*"; - } else if (outputUri.equals(StorageUtil.getVideoUri())) { - mimeType = "video/*"; - } else if (outputUri.equals(StorageUtil.getImageUri())) { - mimeType = "image/*"; - } - } - - ContentValues contentValues = new ContentValues(); - contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName); - contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); - contentValues.put(MediaStore.MediaColumns.DATE_ADDED, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); - contentValues.put(MediaStore.MediaColumns.DATE_MODIFIED, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); - - if (Build.VERSION.SDK_INT > 28) { - int i = 0; - String displayName = fileName; - - while (pathInCache(outputUri, displayName) || displayNameTaken(outputUri, displayName)) { - displayName = base + "-" + (++i) + "." + extension; - } - - contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName); - contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1); - putInCache(outputUri, displayName); - } else if (Objects.equals(outputUri.getScheme(), ContentResolver.SCHEME_FILE)) { - File outputDirectory = new File(outputUri.getPath()); - File outputFile = new File(outputDirectory, base + "." + extension); - - int i = 0; - while (pathInCache(outputUri, outputFile.getPath()) || outputFile.exists()) { - outputFile = new File(outputDirectory, base + "-" + (++i) + "." + extension); - } - - if (outputFile.isHidden()) { - throw new IOException("Specified name would not be visible"); - } - - putInCache(outputUri, outputFile.getPath()); - return new CreateMediaUriResult(outputUri, Uri.fromFile(outputFile)); - } else { - String dir = getExternalPathForType(contentType); - if (dir == null) { - throw new IOException(String.format(Locale.US, "Path for type: %s was not available", contentType)); - } - - String outputFileName = fileName; - String dataPath = String.format("%s/%s", dir, outputFileName); - int i = 0; - while (pathInCache(outputUri, dataPath) || pathTaken(outputUri, dataPath)) { - Log.d(TAG, "The content exists. Rename and check again."); - outputFileName = base + "-" + (++i) + "." + extension; - dataPath = String.format("%s/%s", dir, outputFileName); - } - putInCache(outputUri, outputFileName); - contentValues.put(MediaStore.MediaColumns.DATA, dataPath); - } - - try { - return new CreateMediaUriResult(outputUri, getContext().getContentResolver().insert(outputUri, contentValues)); - } catch (RuntimeException e) { - if (e instanceof IllegalArgumentException || e.getCause() instanceof IllegalArgumentException) { - Log.w(TAG, "Unable to create uri in " + outputUri + " with mimeType [" + mimeType + "]"); - return new CreateMediaUriResult(StorageUtil.getDownloadUri(), getContext().getContentResolver().insert(StorageUtil.getDownloadUri(), contentValues)); - } else { - throw e; - } - } - } - - private void putInCache(@NonNull Uri outputUri, @NonNull String dataPath) { - Set pathSet = MapUtil.getOrDefault(batchOperationNameCache, outputUri, new HashSet<>()); - if (!pathSet.add(dataPath)) { - throw new IllegalStateException("Path already used in data set."); - } - - batchOperationNameCache.put(outputUri, pathSet); - } - - private boolean pathInCache(@NonNull Uri outputUri, @NonNull String dataPath) { - Set pathSet = batchOperationNameCache.get(outputUri); - if (pathSet == null) { - return false; - } - - return pathSet.contains(dataPath); - } - - private boolean pathTaken(@NonNull Uri outputUri, @NonNull String dataPath) throws IOException { - try (Cursor cursor = getContext().getContentResolver().query(outputUri, - new String[] { MediaStore.MediaColumns.DATA }, - MediaStore.MediaColumns.DATA + " = ?", - new String[] { dataPath }, - null)) - { - if (cursor == null) { - throw new IOException("Something is wrong with the filename to save"); - } - return cursor.moveToFirst(); - } - } - - private boolean displayNameTaken(@NonNull Uri outputUri, @NonNull String displayName) throws IOException { - try (Cursor cursor = getContext().getContentResolver().query(outputUri, - new String[] { MediaStore.MediaColumns.DISPLAY_NAME }, - MediaStore.MediaColumns.DISPLAY_NAME + " = ?", - new String[] { displayName }, - null)) - { - if (cursor == null) { - throw new IOException("Something is wrong with the displayName to save"); - } - - return cursor.moveToFirst(); - } - } - - private String[] getFileNameParts(String fileName) { - String[] result = new String[2]; - String[] tokens = fileName.split("\\.(?=[^\\.]+$)"); - - result[0] = tokens[0]; - - if (tokens.length > 1) { - result[1] = tokens[1]; - } else { - result[1] = ""; - } - - return result; - } - - @Override - protected void onPostExecute(final Pair result) { - super.onPostExecute(result); - final Context context = contextReference.get(); - if (context == null) return; - - switch (result.first()) { - case FAILURE: - Toast.makeText(context, - context.getResources().getQuantityText(R.plurals.SaveAttachment_error_while_saving_attachments_to_sd_card, attachmentCount), - Toast.LENGTH_LONG).show(); - break; - case SUCCESS: - Toast.makeText(context, - context.getResources().getQuantityText(R.plurals.SaveAttachment_saved_success, attachmentCount), - Toast.LENGTH_LONG).show(); - break; - case WRITE_ACCESS_FAILURE: - Toast.makeText(context, R.string.SaveAttachment_unable_to_write_to_sd_card_exclamation, Toast.LENGTH_LONG).show(); - break; - } - } - - public static class Attachment { - public Uri uri; - public String fileName; - public String contentType; - public long date; - - public Attachment( - @NonNull Uri uri, - @Nullable String contentType, - long date, - @Nullable String fileName - ) { - if (uri == null || contentType == null || date < 0) { - throw new AssertionError("uri, content type, and date must all be specified"); - } - this.uri = uri; - this.fileName = fileName; - this.contentType = contentType; - this.date = date; - } - } - - private static class CreateMediaUriResult { - final Uri outputUri; - final Uri mediaUri; - - private CreateMediaUriResult(Uri outputUri, Uri mediaUri) { - this.outputUri = outputUri; - this.mediaUri = mediaUri; - } - } - - public static void showWarningDialogIfNecessary(Context context, int count, Runnable onSave) { - if (SignalStore.uiHints().hasDismissedSaveStorageWarning()) { - onSave.run(); - } else { - new MaterialAlertDialogBuilder(context) - .setView(R.layout.dialog_save_attachment) - .setTitle(R.string.AttachmentSaver__save_to_phone) - .setCancelable(true) - .setMessage(context.getResources().getQuantityString(R.plurals.AttachmentSaver__this_media_will_be_saved, count, count)) - .setPositiveButton(R.string.save, ((dialog, i) -> { - CheckBox checkbox = ((AlertDialog) dialog).findViewById(R.id.checkbox); - if (checkbox.isChecked()) { - SignalStore.uiHints().markDismissedSaveStorageWarning(); - } - onSave.run(); - })) - .setNegativeButton(android.R.string.cancel, null) - .show(); - } - } -} - diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentUtil.kt index 33739cac5b..3ef9e0ede7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/SaveAttachmentUtil.kt @@ -40,10 +40,6 @@ import java.util.concurrent.TimeUnit */ private typealias BatchOperationNameCache = HashMap> -/** - * This is a rewrite of [SaveAttachmentTask] that does not handle displaying - * a progress dialog and is not backed by an async task. - */ object SaveAttachmentUtil { private val TAG = Log.tag(SaveAttachmentUtil::class.java) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e476d35dfd..751cc627cd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1683,7 +1683,6 @@ Deleting Deleting messages… - Collecting attachments… Sort by Newest Oldest