Use AttachmentSaver to save media overview files to device storage.

This commit is contained in:
Jeffrey Starke
2025-03-25 15:36:57 -04:00
committed by Cody Henthorne
parent 18328079c8
commit 0ef627b864
6 changed files with 36 additions and 532 deletions

View File

@@ -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<MediaTable.MediaRecord> mediaRecords,
@Nullable Runnable postExecute)
static Completable handleSaveMedia(@NonNull Fragment fragment,
@NonNull Collection<MediaTable.MediaRecord> 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<MediaTable.MediaRecord> mediaRecords, @Nullable Runnable postExecute) {
new ProgressDialogAsyncTask<Void, Void, List<SaveAttachmentTask.Attachment>>(context,
R.string.MediaOverviewActivity_collecting_attachments,
R.string.please_wait)
{
@Override
protected List<SaveAttachmentTask.Attachment> doInBackground(Void... params) {
List<SaveAttachmentTask.Attachment> 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<SaveAttachmentTask.Attachment> 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();
}
}

View File

@@ -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