|
|
@@ -1,16 +1,20 @@
|
|
|
package com.datarecovery.master.utils;
|
|
|
|
|
|
+import android.Manifest;
|
|
|
import android.content.ContentResolver;
|
|
|
import android.content.ContentValues;
|
|
|
import android.content.Context;
|
|
|
+import android.content.Intent;
|
|
|
import android.net.Uri;
|
|
|
import android.os.Build;
|
|
|
+import android.os.Environment;
|
|
|
import android.os.ParcelFileDescriptor;
|
|
|
import android.provider.MediaStore;
|
|
|
|
|
|
import androidx.annotation.IntDef;
|
|
|
import androidx.annotation.RequiresApi;
|
|
|
|
|
|
+import com.atmob.common.permission.PermissionUtil;
|
|
|
import com.atmob.common.runtime.ContextUtil;
|
|
|
|
|
|
import java.io.File;
|
|
|
@@ -20,9 +24,6 @@ import java.io.InputStream;
|
|
|
import java.lang.annotation.Retention;
|
|
|
import java.lang.annotation.RetentionPolicy;
|
|
|
|
|
|
-/**
|
|
|
- * 用于保存文件到公共存储空间
|
|
|
- */
|
|
|
public class MediaStoreHelper {
|
|
|
|
|
|
public static final int TYPE_IMAGE = 1;
|
|
|
@@ -37,10 +38,11 @@ public class MediaStoreHelper {
|
|
|
|
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
|
@IntDef({TYPE_IMAGE, TYPE_VIDEO, TYPE_AUDIO, TYPE_FILES, TYPE_DOWNLOADS})
|
|
|
- @interface MediaType {
|
|
|
+ public @interface MediaType {
|
|
|
}
|
|
|
|
|
|
public static void saveToSharedStorage(@MediaType int mediaType, File file, String fileName) throws Exception {
|
|
|
+ assertPermission();
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
|
saveToSharedStorage(mediaType, java.nio.file.Files.newInputStream(file.toPath()), fileName);
|
|
|
} else {
|
|
|
@@ -48,6 +50,14 @@ public class MediaStoreHelper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private static void assertPermission() {
|
|
|
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
|
+ if (!PermissionUtil.hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
|
|
|
+ throw new IllegalStateException("Missing WRITE_EXTERNAL_STORAGE permission.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public static void saveToSharedStorage(@MediaType int mediaType, InputStream inputStream, String fileName) throws Exception {
|
|
|
// Add a media item that other apps don't see until the item is
|
|
|
// fully written to the media store.
|
|
|
@@ -68,7 +78,14 @@ public class MediaStoreHelper {
|
|
|
mediaDetails.put(targetMedia.isPending(), 1);
|
|
|
}
|
|
|
|
|
|
- Uri mediaContentUri = resolver.insert(mediaCollection, mediaDetails);
|
|
|
+ Uri mediaContentUri;
|
|
|
+ try {
|
|
|
+ mediaContentUri = resolver.insert(mediaCollection, mediaDetails);
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ // downgrade to the old way.
|
|
|
+ saveToExternalStorage(targetMedia, inputStream, fileName);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
if (mediaContentUri == null) {
|
|
|
throw new IllegalStateException("Failed to create new media item.");
|
|
|
@@ -100,6 +117,51 @@ public class MediaStoreHelper {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private static void saveToExternalStorage(Media media, InputStream inputStream, String fileName) throws Exception {
|
|
|
+ File externalDir = media.getExternalDir();
|
|
|
+ if (!Environment.isExternalStorageEmulated(externalDir)) {
|
|
|
+ throw new IllegalStateException("External storage is not emulated.");
|
|
|
+ }
|
|
|
+ if (!externalDir.exists() && !externalDir.mkdirs()) {
|
|
|
+ throw new IllegalStateException("Failed to create external storage directory: " + externalDir);
|
|
|
+ }
|
|
|
+ File targetFile = new File(externalDir, fileName);
|
|
|
+ targetFile = createNotRepeatingFile(targetFile, 1);
|
|
|
+ try (FileOutputStream fos = new FileOutputStream(targetFile)) {
|
|
|
+ byte[] buf = new byte[8192];
|
|
|
+ int len;
|
|
|
+ while ((len = inputStream.read(buf)) > 0) {
|
|
|
+ fos.write(buf, 0, len);
|
|
|
+ }
|
|
|
+ fos.flush();
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new IllegalStateException("Failed to save media file to external storage.", e);
|
|
|
+ }
|
|
|
+ // scan the file to make it visible to other apps.
|
|
|
+ sendMediaScanBroadcast(targetFile);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void sendMediaScanBroadcast(File targetFile) {
|
|
|
+ Context applicationContext = ContextUtil.getContext().getApplicationContext();
|
|
|
+ applicationContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(targetFile)));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static File createNotRepeatingFile(File targetFile, int index) throws Exception {
|
|
|
+ if (targetFile.exists()) {
|
|
|
+ String name = targetFile.getName();
|
|
|
+ int dotIndex = name.lastIndexOf('.');
|
|
|
+ String prefix = name.substring(0, dotIndex);
|
|
|
+ String suffix = name.substring(dotIndex);
|
|
|
+ targetFile = new File(targetFile.getParentFile(), prefix + "(" + index + ")" + suffix);
|
|
|
+ return createNotRepeatingFile(targetFile, index + 1);
|
|
|
+ } else {
|
|
|
+ if (!targetFile.createNewFile()) {
|
|
|
+ throw new IllegalStateException("Failed to create new file: " + targetFile);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return targetFile;
|
|
|
+ }
|
|
|
+
|
|
|
private static Media getTargetMedia(int mediaType) {
|
|
|
switch (mediaType) {
|
|
|
case TYPE_IMAGE:
|
|
|
@@ -136,10 +198,16 @@ public class MediaStoreHelper {
|
|
|
return MediaStore.Images.Media.DISPLAY_NAME;
|
|
|
}
|
|
|
|
|
|
+ @RequiresApi(api = Build.VERSION_CODES.Q)
|
|
|
@Override
|
|
|
public String isPending() {
|
|
|
return MediaStore.Images.Media.IS_PENDING;
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public File getExternalDir() {
|
|
|
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private static class Video implements Media {
|
|
|
@@ -159,10 +227,16 @@ public class MediaStoreHelper {
|
|
|
return MediaStore.Video.Media.DISPLAY_NAME;
|
|
|
}
|
|
|
|
|
|
+ @RequiresApi(api = Build.VERSION_CODES.Q)
|
|
|
@Override
|
|
|
public String isPending() {
|
|
|
return MediaStore.Video.Media.IS_PENDING;
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public File getExternalDir() {
|
|
|
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private static class Audio implements Media {
|
|
|
@@ -182,10 +256,16 @@ public class MediaStoreHelper {
|
|
|
return MediaStore.Audio.Media.DISPLAY_NAME;
|
|
|
}
|
|
|
|
|
|
+ @RequiresApi(api = Build.VERSION_CODES.Q)
|
|
|
@Override
|
|
|
public String isPending() {
|
|
|
return MediaStore.Audio.Media.IS_PENDING;
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public File getExternalDir() {
|
|
|
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private static class Files implements Media {
|
|
|
@@ -204,10 +284,16 @@ public class MediaStoreHelper {
|
|
|
return MediaStore.Files.FileColumns.DISPLAY_NAME;
|
|
|
}
|
|
|
|
|
|
+ @RequiresApi(api = Build.VERSION_CODES.Q)
|
|
|
@Override
|
|
|
public String isPending() {
|
|
|
return MediaStore.Files.FileColumns.IS_PENDING;
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public File getExternalDir() {
|
|
|
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private static class Downloads implements Media {
|
|
|
@@ -223,17 +309,27 @@ public class MediaStoreHelper {
|
|
|
return MediaStore.Downloads.DISPLAY_NAME;
|
|
|
}
|
|
|
|
|
|
+ @RequiresApi(api = Build.VERSION_CODES.Q)
|
|
|
@Override
|
|
|
public String isPending() {
|
|
|
return MediaStore.Downloads.IS_PENDING;
|
|
|
}
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public File getExternalDir() {
|
|
|
+ return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- private interface Media {
|
|
|
+ private interface Media extends MediaDir {
|
|
|
Uri getMediaCollection();
|
|
|
|
|
|
String displayName();
|
|
|
|
|
|
String isPending();
|
|
|
}
|
|
|
+
|
|
|
+ private interface MediaDir {
|
|
|
+ File getExternalDir();
|
|
|
+ }
|
|
|
}
|