Fix mediastore access for Android Q.
This commit is contained in:
parent
3163e09b98
commit
dc64a186d5
14 changed files with 247 additions and 200 deletions
|
@ -36,7 +36,8 @@
|
|||
<uses-permission android:name="android.permission.WRITE_SMS"/>
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28" />
|
||||
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ import org.thoughtcrime.securesms.util.DateUtils;
|
|||
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
@ -384,21 +385,30 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
|
|||
|
||||
if (mediaItem != null) {
|
||||
SaveAttachmentTask.showWarningDialog(this, (dialogInterface, i) -> {
|
||||
if (StorageUtil.canWriteToMediaStore()) {
|
||||
performSavetoDisk(mediaItem);
|
||||
return;
|
||||
}
|
||||
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
||||
.onAnyDenied(() -> Toast.makeText(this, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
|
||||
.onAllGranted(() -> {
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this);
|
||||
long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis();
|
||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null));
|
||||
performSavetoDisk(mediaItem);
|
||||
})
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void performSavetoDisk(@NonNull MediaItem mediaItem) {
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(MediaPreviewActivity.this);
|
||||
long saveDate = (mediaItem.date > 0) ? mediaItem.date : System.currentTimeMillis();
|
||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null));
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void deleteMedia() {
|
||||
MediaItem mediaItem = getCurrentMediaItem();
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.components;
|
|||
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
@ -106,7 +107,7 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo
|
|||
public void onBindItemViewHolder(RecentPhotoViewHolder viewHolder, @NonNull Cursor cursor) {
|
||||
viewHolder.imageView.setImageDrawable(null);
|
||||
|
||||
String path = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATA));
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns._ID));
|
||||
long dateTaken = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_TAKEN));
|
||||
long dateModified = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.DATE_MODIFIED));
|
||||
String mimeType = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.ImageColumns.MIME_TYPE));
|
||||
|
@ -116,7 +117,7 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo
|
|||
int width = cursor.getInt(cursor.getColumnIndexOrThrow(getWidthColumn(orientation)));
|
||||
int height = cursor.getInt(cursor.getColumnIndexOrThrow(getHeightColumn(orientation)));
|
||||
|
||||
final Uri uri = Uri.fromFile(new File(path));
|
||||
final Uri uri = ContentUris.withAppendedId(RecentPhotosLoader.BASE_URL, rowId);
|
||||
|
||||
Key signature = new MediaStoreSignature(mimeType, dateModified, orientation);
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -10,7 +8,6 @@ import android.widget.FrameLayout;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
@ -19,6 +16,7 @@ import org.thoughtcrime.securesms.R;
|
|||
import org.thoughtcrime.securesms.components.InputAwareLayout;
|
||||
import org.thoughtcrime.securesms.mediasend.Media;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
@ -84,7 +82,7 @@ public class AttachmentKeyboard extends FrameLayout implements InputAwareLayout.
|
|||
}
|
||||
|
||||
public void onMediaChanged(@NonNull List<Media> media) {
|
||||
if (ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||
if (StorageUtil.canReadFromMediaStore()) {
|
||||
mediaAdapter.setMedia(media);
|
||||
permissionButton.setVisibility(GONE);
|
||||
permissionText.setVisibility(GONE);
|
||||
|
|
|
@ -1034,6 +1034,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
|||
Permissions.with(this)
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.onAllGranted(() -> viewModel.onAttachmentKeyboardOpen())
|
||||
.withPermanentDenialDialog(getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
package org.thoughtcrime.securesms.conversation;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
|
@ -102,6 +103,7 @@ import org.thoughtcrime.securesms.mms.GlideApp;
|
|||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.profiles.UnknownSenderView;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.reactions.ReactionsBottomSheetDialogFragment;
|
||||
|
@ -123,6 +125,7 @@ import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
|
|||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
@ -854,26 +857,40 @@ public class ConversationFragment extends LoggingFragment {
|
|||
throw new AssertionError("Cannot save a view-once message.");
|
||||
}
|
||||
|
||||
SaveAttachmentTask.showWarningDialog(getActivity(), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
List<SaveAttachmentTask.Attachment> attachments = Stream.of(message.getSlideDeck().getSlides())
|
||||
.filter(s -> s.getUri() != null && (s.hasImage() || s.hasVideo() || s.hasAudio() || s.hasDocument()))
|
||||
.map(s -> new SaveAttachmentTask.Attachment(s.getUri(), s.getContentType(), message.getDateReceived(), s.getFileName().orNull()))
|
||||
.toList();
|
||||
if (!Util.isEmpty(attachments)) {
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity());
|
||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[0]));
|
||||
return;
|
||||
}
|
||||
|
||||
Log.w(TAG, "No slide with attachable media found, failing nicely.");
|
||||
Toast.makeText(getActivity(),
|
||||
getResources().getQuantityString(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card, 1),
|
||||
Toast.LENGTH_LONG).show();
|
||||
SaveAttachmentTask.showWarningDialog(getActivity(), (dialog, which) -> {
|
||||
if (StorageUtil.canWriteToMediaStore()) {
|
||||
performSave(message);
|
||||
return;
|
||||
}
|
||||
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
||||
.onAnyDenied(() -> Toast.makeText(requireContext(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
|
||||
.onAllGranted(() -> performSave(message))
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
||||
private void performSave(final MediaMmsMessageRecord message) {
|
||||
List<SaveAttachmentTask.Attachment> attachments = Stream.of(message.getSlideDeck().getSlides())
|
||||
.filter(s -> s.getUri() != null && (s.hasImage() || s.hasVideo() || s.hasAudio() || s.hasDocument()))
|
||||
.map(s -> new SaveAttachmentTask.Attachment(s.getUri(), s.getContentType(), message.getDateReceived(), s.getFileName().orNull()))
|
||||
.toList();
|
||||
|
||||
if (!Util.isEmpty(attachments)) {
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity());
|
||||
saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[0]));
|
||||
return;
|
||||
}
|
||||
|
||||
Log.w(TAG, "No slide with attachable media found, failing nicely.");
|
||||
Toast.makeText(getActivity(),
|
||||
getResources().getQuantityString(R.plurals.ConversationFragment_error_while_saving_attachments_to_sd_card, 1),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void clearHeaderIfNotTyping(ConversationAdapter adapter) {
|
||||
if (adapter.getHeaderView() != typingView) {
|
||||
adapter.setHeaderView(null);
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.Manifest;
|
|||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import androidx.loader.content.CursorLoader;
|
||||
|
||||
|
@ -15,7 +16,7 @@ public class RecentPhotosLoader extends CursorLoader {
|
|||
public static Uri BASE_URL = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
|
||||
private static final String[] PROJECTION = new String[] {
|
||||
MediaStore.Images.ImageColumns.DATA,
|
||||
MediaStore.Images.ImageColumns._ID,
|
||||
MediaStore.Images.ImageColumns.DATE_TAKEN,
|
||||
MediaStore.Images.ImageColumns.DATE_MODIFIED,
|
||||
MediaStore.Images.ImageColumns.ORIENTATION,
|
||||
|
@ -26,7 +27,8 @@ public class RecentPhotosLoader extends CursorLoader {
|
|||
MediaStore.Images.ImageColumns.HEIGHT
|
||||
};
|
||||
|
||||
private static final String SELECTION = MediaStore.Images.Media.DATA + " NOT NULL";
|
||||
private static final String SELECTION = Build.VERSION.SDK_INT > 28 ? MediaStore.Images.Media.IS_PENDING + " != 1"
|
||||
: MediaStore.Images.Media.DATA + " IS NULL";
|
||||
|
||||
private final Context context;
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.thoughtcrime.securesms.database.MediaDatabase;
|
|||
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.task.ProgressDialogAsyncTask;
|
||||
|
||||
import java.util.Collection;
|
||||
|
@ -32,43 +33,18 @@ final class MediaActions {
|
|||
{
|
||||
Context context = fragment.requireContext();
|
||||
|
||||
if (StorageUtil.canWriteToMediaStore()) {
|
||||
performSaveToDisk(context, mediaRecords, postExecute);
|
||||
return;
|
||||
}
|
||||
|
||||
SaveAttachmentTask.showWarningDialog(context, (dialogInterface, which) -> Permissions.with(fragment)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(fragment.getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
||||
.onAnyDenied(() -> Toast.makeText(context, R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
|
||||
.onAllGranted(() ->
|
||||
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 (MediaDatabase.MediaRecord mediaRecord : mediaRecords) {
|
||||
if (mediaRecord.getAttachment().getUri() != null) {
|
||||
attachments.add(new SaveAttachmentTask.Attachment(mediaRecord.getAttachment().getUri(),
|
||||
mediaRecord.getContentType(),
|
||||
mediaRecord.getDate(),
|
||||
mediaRecord.getAttachment().getFileName()));
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
).execute(), mediaRecords.size());
|
||||
.onAllGranted(() -> performSaveToDisk(context, mediaRecords, postExecute))
|
||||
.execute(), mediaRecords.size());
|
||||
}
|
||||
|
||||
static void handleDeleteMedia(@NonNull Context context,
|
||||
|
@ -111,4 +87,37 @@ final class MediaActions {
|
|||
builder.setNegativeButton(android.R.string.cancel, null);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
private static void performSaveToDisk(@NonNull Context context, @NonNull Collection<MediaDatabase.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 (MediaDatabase.MediaRecord mediaRecord : mediaRecords) {
|
||||
if (mediaRecord.getAttachment().getUri() != null) {
|
||||
attachments.add(new SaveAttachmentTask.Attachment(mediaRecord.getAttachment().getUri(),
|
||||
mediaRecord.getContentType(),
|
||||
mediaRecord.getDate(),
|
||||
mediaRecord.getAttachment().getFileName()));
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package org.thoughtcrime.securesms.mediasend;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.MediaStore.Images;
|
||||
import android.provider.MediaStore.Video;
|
||||
import android.provider.OpenableColumns;
|
||||
|
@ -20,13 +21,12 @@ import com.annimon.stream.Stream;
|
|||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -84,7 +84,7 @@ public class MediaRepository {
|
|||
|
||||
@WorkerThread
|
||||
private @NonNull List<MediaFolder> getFolders(@NonNull Context context) {
|
||||
if (!Permissions.hasAll(context, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
if (!StorageUtil.canReadFromMediaStore()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
@ -132,20 +132,19 @@ public class MediaRepository {
|
|||
|
||||
@WorkerThread
|
||||
private @NonNull FolderResult getFolders(@NonNull Context context, @NonNull Uri contentUri) {
|
||||
String cameraPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath() + File.separator + "Camera";
|
||||
String cameraBucketId = null;
|
||||
Uri globalThumbnail = null;
|
||||
long thumbnailTimestamp = 0;
|
||||
Map<String, FolderData> folders = new HashMap<>();
|
||||
|
||||
String[] projection = new String[] { Images.Media.DATA, Images.Media.BUCKET_ID, Images.Media.BUCKET_DISPLAY_NAME, Images.Media.DATE_MODIFIED };
|
||||
String selection = Images.Media.DATA + " NOT NULL";
|
||||
String[] projection = new String[] { Images.Media._ID, Images.Media.BUCKET_ID, Images.Media.BUCKET_DISPLAY_NAME, Images.Media.DATE_MODIFIED };
|
||||
String selection = isNotPending();
|
||||
String sortBy = Images.Media.BUCKET_DISPLAY_NAME + " COLLATE NOCASE ASC, " + Images.Media.DATE_MODIFIED + " DESC";
|
||||
|
||||
try (Cursor cursor = context.getContentResolver().query(contentUri, projection, selection, null, sortBy)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String path = cursor.getString(cursor.getColumnIndexOrThrow(projection[0]));
|
||||
Uri thumbnail = Uri.fromFile(new File(path));
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(projection[0]));
|
||||
Uri thumbnail = ContentUris.withAppendedId(contentUri, rowId);
|
||||
String bucketId = cursor.getString(cursor.getColumnIndexOrThrow(projection[1]));
|
||||
String title = cursor.getString(cursor.getColumnIndexOrThrow(projection[2]));
|
||||
long timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(projection[3]));
|
||||
|
@ -154,7 +153,7 @@ public class MediaRepository {
|
|||
folder.incrementCount();
|
||||
folders.put(bucketId, folder);
|
||||
|
||||
if (cameraBucketId == null && path.startsWith(cameraPath)) {
|
||||
if (cameraBucketId == null && "Camera".equals(title)) {
|
||||
cameraBucketId = bucketId;
|
||||
}
|
||||
|
||||
|
@ -170,7 +169,7 @@ public class MediaRepository {
|
|||
|
||||
@WorkerThread
|
||||
private @NonNull List<Media> getMediaInBucket(@NonNull Context context, @NonNull String bucketId) {
|
||||
if (!Permissions.hasAll(context, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
if (!StorageUtil.canReadFromMediaStore()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
|
@ -188,27 +187,27 @@ public class MediaRepository {
|
|||
@WorkerThread
|
||||
private @NonNull List<Media> getMediaInBucket(@NonNull Context context, @NonNull String bucketId, @NonNull Uri contentUri, boolean isImage) {
|
||||
List<Media> media = new LinkedList<>();
|
||||
String selection = Images.Media.BUCKET_ID + " = ? AND " + Images.Media.DATA + " NOT NULL";
|
||||
String selection = Images.Media.BUCKET_ID + " = ? AND " + isNotPending();
|
||||
String[] selectionArgs = new String[] { bucketId };
|
||||
String sortBy = Images.Media.DATE_MODIFIED + " DESC";
|
||||
|
||||
String[] projection;
|
||||
|
||||
if (isImage) {
|
||||
projection = new String[]{Images.Media.DATA, Images.Media.MIME_TYPE, Images.Media.DATE_MODIFIED, Images.Media.ORIENTATION, Images.Media.WIDTH, Images.Media.HEIGHT, Images.Media.SIZE};
|
||||
projection = new String[]{Images.Media._ID, Images.Media.MIME_TYPE, Images.Media.DATE_MODIFIED, Images.Media.ORIENTATION, Images.Media.WIDTH, Images.Media.HEIGHT, Images.Media.SIZE};
|
||||
} else {
|
||||
projection = new String[]{Images.Media.DATA, Images.Media.MIME_TYPE, Images.Media.DATE_MODIFIED, Images.Media.WIDTH, Images.Media.HEIGHT, Images.Media.SIZE, Video.Media.DURATION};
|
||||
projection = new String[]{Images.Media._ID, Images.Media.MIME_TYPE, Images.Media.DATE_MODIFIED, Images.Media.WIDTH, Images.Media.HEIGHT, Images.Media.SIZE, Video.Media.DURATION};
|
||||
}
|
||||
|
||||
if (Media.ALL_MEDIA_BUCKET_ID.equals(bucketId)) {
|
||||
selection = Images.Media.DATA + " NOT NULL";
|
||||
selection = isNotPending();
|
||||
selectionArgs = null;
|
||||
}
|
||||
|
||||
try (Cursor cursor = context.getContentResolver().query(contentUri, projection, selection, selectionArgs, sortBy)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String path = cursor.getString(cursor.getColumnIndexOrThrow(projection[0]));
|
||||
Uri uri = Uri.fromFile(new File(path));
|
||||
long rowId = cursor.getLong(cursor.getColumnIndexOrThrow(projection[0]));
|
||||
Uri uri = ContentUris.withAppendedId(contentUri, rowId);
|
||||
String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(Images.Media.MIME_TYPE));
|
||||
long date = cursor.getLong(cursor.getColumnIndexOrThrow(Images.Media.DATE_MODIFIED));
|
||||
int orientation = isImage ? cursor.getInt(cursor.getColumnIndexOrThrow(Images.Media.ORIENTATION)) : 0;
|
||||
|
@ -224,12 +223,12 @@ public class MediaRepository {
|
|||
return media;
|
||||
}
|
||||
|
||||
private @NonNull String isNotPending() {
|
||||
return Build.VERSION.SDK_INT <= 28 ? Images.Media.DATA + " NOT NULL" : MediaStore.MediaColumns.IS_PENDING + " != 1";
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private List<Media> getPopulatedMedia(@NonNull Context context, @NonNull List<Media> media) {
|
||||
if (!Permissions.hasAll(context, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
return media;
|
||||
}
|
||||
|
||||
return Stream.of(media).map(m -> {
|
||||
try {
|
||||
if (isPopulated(m)) {
|
||||
|
@ -265,10 +264,6 @@ public class MediaRepository {
|
|||
|
||||
@WorkerThread
|
||||
private Optional<Media> getMostRecentItem(@NonNull Context context) {
|
||||
if (!Permissions.hasAll(context, Manifest.permission.READ_EXTERNAL_STORAGE)) {
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
List<Media> media = getMediaInBucket(context, Media.ALL_MEDIA_BUCKET_ID, Images.Media.EXTERNAL_CONTENT_URI, true);
|
||||
return media.size() > 0 ? Optional.of(media.get(0)) : Optional.absent();
|
||||
}
|
||||
|
|
|
@ -367,36 +367,21 @@ public class AttachmentManager {
|
|||
}
|
||||
|
||||
public static void selectDocument(Activity activity, int requestCode) {
|
||||
Permissions.with(activity)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
||||
.onAllGranted(() -> selectMediaType(activity, "*/*", null, requestCode))
|
||||
.execute();
|
||||
selectMediaType(activity, "*/*", null, requestCode);
|
||||
}
|
||||
|
||||
public static void selectGallery(Activity activity, int requestCode, @NonNull Recipient recipient, @NonNull CharSequence body, @NonNull TransportOption transport) {
|
||||
Permissions.with(activity)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.request(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
||||
.onAllGranted(() -> selectMediaType(activity, "image/*", new String[] {"image/*", "video/*"}, requestCode))
|
||||
.onAllGranted(() -> activity.startActivityForResult(MediaSendActivity.buildGalleryIntent(activity, recipient, body, transport), requestCode))
|
||||
.execute();
|
||||
}
|
||||
|
||||
public static void selectAudio(Activity activity, int requestCode) {
|
||||
Permissions.with(activity)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_external_storage_permission_in_order_to_attach_photos_videos_or_audio))
|
||||
.onAllGranted(() -> selectMediaType(activity, "audio/*", null, requestCode))
|
||||
.execute();
|
||||
}
|
||||
|
||||
public static void selectContactInfo(Activity activity, int requestCode) {
|
||||
Permissions.with(activity)
|
||||
.request(Manifest.permission.WRITE_CONTACTS)
|
||||
.request(Manifest.permission.READ_CONTACTS)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_contacts_permission_in_order_to_attach_contact_information))
|
||||
.onAllGranted(() -> {
|
||||
|
@ -430,29 +415,6 @@ public class AttachmentManager {
|
|||
return captureUri;
|
||||
}
|
||||
|
||||
public void capturePhoto(Activity activity, int requestCode) {
|
||||
Permissions.with(activity)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(activity.getString(R.string.AttachmentManager_signal_requires_the_camera_permission_in_order_to_take_photos_but_it_has_been_permanently_denied))
|
||||
.onAllGranted(() -> {
|
||||
try {
|
||||
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (captureIntent.resolveActivity(activity.getPackageManager()) != null) {
|
||||
if (captureUri == null) {
|
||||
captureUri = DeprecatedPersistentBlobProvider.getInstance(context).createForExternal(context, MediaUtil.IMAGE_JPEG);
|
||||
}
|
||||
Log.d(TAG, "captureUri path is " + captureUri.getPath());
|
||||
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, captureUri);
|
||||
activity.startActivityForResult(captureIntent, requestCode);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
}
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
private static void selectMediaType(Activity activity, @NonNull String type, @Nullable String[] extraMimeType, int requestCode) {
|
||||
final Intent intent = new Intent();
|
||||
intent.setType(type);
|
||||
|
|
|
@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.scribbles.widget.VerticalSlideColorPicker;
|
|||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
@ -412,29 +413,17 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||
@Override
|
||||
public void onSave() {
|
||||
SaveAttachmentTask.showWarningDialog(requireContext(), (dialogInterface, i) -> {
|
||||
if (StorageUtil.canWriteToMediaStore()) {
|
||||
performSaveToDisk();
|
||||
return;
|
||||
}
|
||||
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.MediaPreviewActivity_signal_needs_the_storage_permission_in_order_to_write_to_external_storage_but_it_has_been_permanently_denied))
|
||||
.onAnyDenied(() -> Toast.makeText(requireContext(), R.string.MediaPreviewActivity_unable_to_write_to_external_storage_without_permission, Toast.LENGTH_LONG).show())
|
||||
.onAllGranted(() -> {
|
||||
SimpleTask.run(() -> {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
Bitmap image = imageEditorView.getModel().render(requireContext());
|
||||
|
||||
image.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
|
||||
|
||||
return BlobProvider.getInstance()
|
||||
.forData(outputStream.toByteArray())
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleUseInMemory();
|
||||
|
||||
}, uri -> {
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(requireContext());
|
||||
SaveAttachmentTask.Attachment attachment = new SaveAttachmentTask.Attachment(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), null);
|
||||
saveTask.executeOnExecutor(SignalExecutors.BOUNDED, attachment);
|
||||
});
|
||||
})
|
||||
.onAllGranted(this::performSaveToDisk)
|
||||
.execute();
|
||||
});
|
||||
}
|
||||
|
@ -469,6 +458,25 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
|||
controller.onDoneEditing();
|
||||
}
|
||||
|
||||
private void performSaveToDisk() {
|
||||
SimpleTask.run(() -> {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
Bitmap image = imageEditorView.getModel().render(requireContext());
|
||||
|
||||
image.compress(Bitmap.CompressFormat.JPEG, 80, outputStream);
|
||||
|
||||
return BlobProvider.getInstance()
|
||||
.forData(outputStream.toByteArray())
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleUseInMemory();
|
||||
|
||||
}, uri -> {
|
||||
SaveAttachmentTask saveTask = new SaveAttachmentTask(requireContext());
|
||||
SaveAttachmentTask.Attachment attachment = new SaveAttachmentTask.Attachment(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), null);
|
||||
saveTask.executeOnExecutor(SignalExecutors.BOUNDED, attachment);
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshUniqueColors() {
|
||||
imageEditorHud.setColorPalette(imageEditorView.getModel().getUniqueColorsIgnoringAlpha());
|
||||
}
|
||||
|
|
|
@ -244,12 +244,12 @@ public class BackupUtil {
|
|||
|
||||
private final long timestamp;
|
||||
private final long size;
|
||||
private final Uri uri;
|
||||
private final Uri uri;
|
||||
|
||||
BackupInfo(long timestamp, long size, Uri uri) {
|
||||
this.timestamp = timestamp;
|
||||
this.size = size;
|
||||
this.uri = uri;
|
||||
this.size = size;
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.media.MediaScannerConnection;
|
||||
|
@ -7,6 +8,10 @@ import android.net.Uri;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.TextUtils;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.Toast;
|
||||
|
@ -59,7 +64,7 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
|
|||
Context context = contextReference.get();
|
||||
String directory = null;
|
||||
|
||||
if (!StorageUtil.canWriteInSignalStorageDir()) {
|
||||
if (!StorageUtil.canWriteToMediaStore()) {
|
||||
return new Pair<>(WRITE_ACCESS_FAILURE, null);
|
||||
}
|
||||
|
||||
|
@ -76,14 +81,13 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
|
|||
|
||||
if (attachments.length > 1) return new Pair<>(SUCCESS, null);
|
||||
else return new Pair<>(SUCCESS, directory);
|
||||
} catch (NoExternalStorageException|IOException ioe) {
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, ioe);
|
||||
return new Pair<>(FAILURE, null);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable String saveAttachment(Context context, Attachment attachment)
|
||||
throws NoExternalStorageException, IOException
|
||||
private @Nullable String saveAttachment(Context context, Attachment attachment) throws IOException
|
||||
{
|
||||
String contentType = MediaUtil.getCorrectedMimeType(attachment.contentType);
|
||||
String fileName = attachment.fileName;
|
||||
|
@ -91,40 +95,46 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
|
|||
if (fileName == null) fileName = generateOutputFileName(contentType, attachment.date);
|
||||
fileName = sanitizeOutputFileName(fileName);
|
||||
|
||||
File outputDirectory = createOutputDirectoryFromContentType(contentType);
|
||||
File mediaFile = createOutputFile(outputDirectory, fileName);
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.uri);
|
||||
Uri outputUri = getMediaStoreContentUriForType(contentType);
|
||||
Uri mediaUri = createOutputUri(outputUri, fileName);
|
||||
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
try (InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.uri)) {
|
||||
|
||||
if (inputStream == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (outputUri.equals(StorageUtil.getLegacyDownloadUri())) {
|
||||
try (OutputStream outputStream = new FileOutputStream(mediaUri.getPath())) {
|
||||
Util.copy(inputStream, outputStream);
|
||||
MediaScannerConnection.scanFile(context, new String[]{mediaUri.getPath()}, new String[]{contentType}, null);
|
||||
}
|
||||
}
|
||||
|
||||
try (OutputStream outputStream = context.getContentResolver().openOutputStream(mediaUri)) {
|
||||
Util.copy(inputStream, outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
OutputStream outputStream = new FileOutputStream(mediaFile);
|
||||
Util.copy(inputStream, outputStream);
|
||||
if (Build.VERSION.SDK_INT > 28) {
|
||||
ContentValues updatePendingValues = new ContentValues();
|
||||
updatePendingValues.put(MediaStore.MediaColumns.IS_PENDING, 0);
|
||||
getContext().getContentResolver().update(mediaUri, updatePendingValues, null, null);
|
||||
}
|
||||
|
||||
MediaScannerConnection.scanFile(context, new String[]{mediaFile.getAbsolutePath()},
|
||||
new String[]{contentType}, null);
|
||||
|
||||
return outputDirectory.getName();
|
||||
return outputUri.getLastPathSegment();
|
||||
}
|
||||
|
||||
private File createOutputDirectoryFromContentType(@NonNull String contentType)
|
||||
throws NoExternalStorageException
|
||||
{
|
||||
File outputDirectory;
|
||||
|
||||
private @NonNull Uri getMediaStoreContentUriForType(@NonNull String contentType) {
|
||||
if (contentType.startsWith("video/")) {
|
||||
outputDirectory = StorageUtil.getVideoDir();
|
||||
return StorageUtil.getVideoUri();
|
||||
} else if (contentType.startsWith("audio/")) {
|
||||
outputDirectory = StorageUtil.getAudioDir();
|
||||
return StorageUtil.getAudioUri();
|
||||
} else if (contentType.startsWith("image/")) {
|
||||
outputDirectory = StorageUtil.getImageDir();
|
||||
return StorageUtil.getImageUri();
|
||||
} else {
|
||||
outputDirectory = StorageUtil.getDownloadDir();
|
||||
return StorageUtil.getDownloadUri();
|
||||
}
|
||||
|
||||
if (!outputDirectory.mkdirs()) Log.w(TAG, "mkdirs() returned false, attempting to continue");
|
||||
return outputDirectory;
|
||||
}
|
||||
|
||||
private String generateOutputFileName(@NonNull String contentType, long timestamp) {
|
||||
|
@ -142,25 +152,34 @@ public class SaveAttachmentTask extends ProgressDialogAsyncTask<SaveAttachmentTa
|
|||
return new File(fileName).getName();
|
||||
}
|
||||
|
||||
private File createOutputFile(@NonNull File outputDirectory, @NonNull String fileName)
|
||||
throws IOException
|
||||
{
|
||||
String[] fileParts = getFileNameParts(fileName);
|
||||
String base = fileParts[0];
|
||||
String extension = fileParts[1];
|
||||
private Uri createOutputUri(@NonNull Uri outputUri, @NonNull String fileName) throws IOException {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
|
||||
|
||||
File outputFile = new File(outputDirectory, base + "." + extension);
|
||||
|
||||
int i = 0;
|
||||
while (outputFile.exists()) {
|
||||
outputFile = new File(outputDirectory, base + "-" + (++i) + "." + extension);
|
||||
if (Build.VERSION.SDK_INT > 28) {
|
||||
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
|
||||
}
|
||||
|
||||
if (outputFile.isHidden()) {
|
||||
throw new IOException("Specified name would not be visible");
|
||||
if (Build.VERSION.SDK_INT <= 28 && outputUri.equals(StorageUtil.getLegacyDownloadUri())) {
|
||||
String[] fileParts = getFileNameParts(fileName);
|
||||
String base = fileParts[0];
|
||||
String extension = fileParts[1];
|
||||
File outputDirectory = new File(outputUri.getPath());
|
||||
File outputFile = new File(outputDirectory, base + "." + extension);
|
||||
|
||||
int i = 0;
|
||||
while (outputFile.exists()) {
|
||||
outputFile = new File(outputDirectory, base + "-" + (++i) + "." + extension);
|
||||
}
|
||||
|
||||
if (outputFile.isHidden()) {
|
||||
throw new IOException("Specified name would not be visible");
|
||||
}
|
||||
|
||||
return Uri.fromFile(outputFile);
|
||||
}
|
||||
|
||||
return outputFile;
|
||||
return getContext().getContentResolver().insert(outputUri, contentValues);
|
||||
}
|
||||
|
||||
private String[] getFileNameParts(String fileName) {
|
||||
|
|
|
@ -1,12 +1,19 @@
|
|||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
|
@ -68,20 +75,37 @@ public class StorageUtil {
|
|||
return getSignalStorageDir();
|
||||
}
|
||||
|
||||
public static File getVideoDir() throws NoExternalStorageException {
|
||||
return new File(getSignalStorageDir(), Environment.DIRECTORY_MOVIES);
|
||||
public static boolean canWriteToMediaStore() {
|
||||
return Build.VERSION.SDK_INT > 28 ||
|
||||
Permissions.hasAll(ApplicationDependencies.getApplication(), Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
public static File getAudioDir() throws NoExternalStorageException {
|
||||
return new File(getSignalStorageDir(), Environment.DIRECTORY_MUSIC);
|
||||
public static boolean canReadFromMediaStore() {
|
||||
return Permissions.hasAll(ApplicationDependencies.getApplication(), Manifest.permission.READ_EXTERNAL_STORAGE);
|
||||
}
|
||||
|
||||
public static File getImageDir() throws NoExternalStorageException {
|
||||
return new File(getSignalStorageDir(), Environment.DIRECTORY_PICTURES);
|
||||
public static @NonNull Uri getVideoUri() {
|
||||
return MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
public static File getDownloadDir() throws NoExternalStorageException {
|
||||
return new File(getSignalStorageDir(), Environment.DIRECTORY_DOWNLOADS);
|
||||
public static @NonNull Uri getAudioUri() {
|
||||
return MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
public static @NonNull Uri getImageUri() {
|
||||
return MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
|
||||
public static @NonNull Uri getDownloadUri() {
|
||||
if (Build.VERSION.SDK_INT > 28) {
|
||||
return MediaStore.Downloads.EXTERNAL_CONTENT_URI;
|
||||
} else {
|
||||
return getLegacyDownloadUri();
|
||||
}
|
||||
}
|
||||
|
||||
public static @NonNull Uri getLegacyDownloadUri() {
|
||||
return Uri.fromFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
|
||||
}
|
||||
|
||||
public static @Nullable String getCleanFileName(@Nullable String fileName) {
|
||||
|
|
Loading…
Add table
Reference in a new issue