Bladeren bron

修复Android10以下导出异常问题以及优化导出文件名

zk 1 jaar geleden
bovenliggende
commit
d807fbece0

+ 3 - 3
app/src/main/java/com/datarecovery/master/module/audiorecover/AudioRecoverViewModel.java

@@ -281,7 +281,7 @@ public class AudioRecoverViewModel extends BaseViewModel {
         showLoadingEvent.setValue(true);
         RxJavaUtil.doInBackground(() -> {
             for (FilesSearch.DocumentFile item : list) {
-                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_AUDIO, item.newInputStream(), FileUtil.getCreateFileName(item.getName(), ""));
+                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_AUDIO, item.newInputStream(), FileUtil.getCreateFileName(item.getName()));
                 item.setCheck(false);
             }
             return true;
@@ -293,8 +293,8 @@ public class AudioRecoverViewModel extends BaseViewModel {
             selectedList.setValue(list);
         }, throwable -> {
             showLoadingEvent.setValue(false);
-            ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);
-            BuglyHelper.postCatchedException(FileUtil.getFileListFailException(list, "音频导出失败"));
+            ToastUtil.show(throwable.getMessage(), ToastUtil.LENGTH_SHORT);
+            BuglyHelper.postCatchedException(throwable);
         });
     }
 

+ 3 - 3
app/src/main/java/com/datarecovery/master/module/filerecover/FileRecoverViewModel.java

@@ -319,7 +319,7 @@ public class FileRecoverViewModel extends BaseViewModel {
         showLoadingEvent.setValue(true);
         RxJavaUtil.doInBackground(() -> {
             for (FilesSearch.DocumentFile item : list) {
-                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_FILES, item.newInputStream(), FileUtil.getCreateFileName(item.getName(), ""));
+                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_FILES, item.newInputStream(), FileUtil.getCreateFileName(item.getName()));
                 item.setCheck(false);
             }
             return true;
@@ -331,8 +331,8 @@ public class FileRecoverViewModel extends BaseViewModel {
             selectedList.setValue(list);
         }, throwable -> {
             showLoadingEvent.setValue(false);
-            ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);
-            BuglyHelper.postCatchedException(FileUtil.getFileListFailException(list, "文件导出失败"));
+            ToastUtil.show(throwable.getMessage(), ToastUtil.LENGTH_SHORT);
+            BuglyHelper.postCatchedException(throwable);
         });
     }
 

+ 5 - 5
app/src/main/java/com/datarecovery/master/module/imgrecover/ImageRecoverViewModel.java

@@ -510,10 +510,10 @@ public class ImageRecoverViewModel extends BaseViewModel {
                     }
 
                     @Override
-                    public void onError(@NonNull Throwable e) {
+                    public void onError(@NonNull Throwable throwable) {
                         showLoadingEvent.setValue(false);
-                        ToastUtil.show(R.string.delete_fail, ToastUtil.LENGTH_SHORT);
-                        BuglyHelper.postCatchedException(FileUtil.getFileListFailException(list, "图片清除失败"));
+                        ToastUtil.show(throwable.getMessage(), ToastUtil.LENGTH_SHORT);
+                        BuglyHelper.postCatchedException(throwable);
                     }
                 });
     }
@@ -575,8 +575,8 @@ public class ImageRecoverViewModel extends BaseViewModel {
             selectedList.setValue(list);
         }, throwable -> {
             showLoadingEvent.setValue(false);
-            ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);
-            BuglyHelper.postCatchedException(FileUtil.getFileListFailException(list, "图片导出失败"));
+            ToastUtil.show(throwable.getMessage(), ToastUtil.LENGTH_SHORT);
+            BuglyHelper.postCatchedException(throwable);
         });
     }
 

+ 3 - 10
app/src/main/java/com/datarecovery/master/module/preview/PreviewViewModel.java

@@ -260,21 +260,14 @@ public class PreviewViewModel extends BaseViewModel {
                         if (currentFileFile == null) {
                             return false;
                         }
-                        MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_AUDIO, currentFileFile.newInputStream(), FileUtil.getCreateFileName(currentFileFile.getName(), ""));
+                        MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_AUDIO, currentFileFile.newInputStream(), FileUtil.getCreateFileName(currentFileFile.getName()));
                     }
                     return true;
                 }
                 , o -> ToastUtil.show(R.string.export_success, ToastUtil.LENGTH_SHORT)
                 , throwable -> {
-                    ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);
-                    ArrayList<Object> failList = new ArrayList<>();
-                    if (type == PreviewActivity.TYPE_IMG) {
-                        failList.add(currentImageFile.getValue());
-                        BuglyHelper.postCatchedException(FileUtil.getFileListFailException(failList, "图片导出失败"));
-                    } else if (type == PreviewActivity.TYPE_AUDIO) {
-                        failList.add(currentFileFile);
-                        BuglyHelper.postCatchedException(FileUtil.getFileListFailException(failList, "音频导出失败"));
-                    }
+                    ToastUtil.show(throwable.getMessage(), ToastUtil.LENGTH_SHORT);
+                    BuglyHelper.postCatchedException(throwable);
                 }
         );
 

+ 3 - 3
app/src/main/java/com/datarecovery/master/module/videorecover/VideoRecoverViewModel.java

@@ -251,7 +251,7 @@ public class VideoRecoverViewModel extends BaseViewModel {
         showLoadingEvent.setValue(true);
         RxJavaUtil.doInBackground(() -> {
             for (FilesSearch.DocumentFile item : list) {
-                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_VIDEO, item.newInputStream(), FileUtil.getCreateFileName(item.getName(), ""));
+                MediaStoreHelper.saveToSharedStorage(MediaStoreHelper.TYPE_VIDEO, item.newInputStream(), FileUtil.getCreateFileName(item.getName()));
                 item.setCheck(false);
             }
             return true;
@@ -263,8 +263,8 @@ public class VideoRecoverViewModel extends BaseViewModel {
             selectedList.setValue(list);
         }, throwable -> {
             showLoadingEvent.setValue(false);
-            ToastUtil.show(R.string.export_fail, ToastUtil.LENGTH_SHORT);
-            BuglyHelper.postCatchedException(FileUtil.getFileListFailException(list, "视频导出失败"));
+            ToastUtil.show(throwable.getMessage(), ToastUtil.LENGTH_SHORT);
+            BuglyHelper.postCatchedException(throwable);
         });
     }
 

+ 9 - 7
app/src/main/java/com/datarecovery/master/utils/FileUtil.java

@@ -73,16 +73,18 @@ public class FileUtil {
     }
 
 
-    public static String getCreateFileName(String fileName, String defaultSuffix) {
+    public static String getCreateFileName(String fileName) {
         String uuid = UUID.randomUUID().toString();
-        if (fileName == null) {
-            return uuid + defaultSuffix;
+        String time = DateUtil.formatNormalDate("yyyyMMddHHmmssSSS", System.currentTimeMillis());
+        if (fileName == null || fileName.isEmpty()) {
+            return uuid + "_" + time;
         }
-        String[] split = fileName.split("\\.");
-        if (split.length <= 1) {
-            return uuid + defaultSuffix;
+        int lastDot = fileName.lastIndexOf(".");
+        if (lastDot == -1) {
+            return uuid + "_" + time;
+        } else {
+            return fileName.substring(0, lastDot) + "_" + time + fileName.substring(lastDot);
         }
-        return uuid + "." + split[split.length - 1];
     }
 
     @SuppressLint("UseCompatLoadingForDrawables")

+ 102 - 6
app/src/main/java/com/datarecovery/master/utils/MediaStoreHelper.java

@@ -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();
+    }
 }