|
|
@@ -10,13 +10,16 @@ import androidx.annotation.NonNull;
|
|
|
import androidx.annotation.RequiresApi;
|
|
|
import androidx.annotation.WorkerThread;
|
|
|
|
|
|
+import com.atmob.common.logging.AtmobLog;
|
|
|
import com.atmob.common.thread.ThreadPoolUtil;
|
|
|
import com.datarecovery.master.utils.FileUtil;
|
|
|
|
|
|
import java.io.File;
|
|
|
+import java.io.FileFilter;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
import java.util.HashMap;
|
|
|
+import java.util.Iterator;
|
|
|
import java.util.LinkedList;
|
|
|
import java.util.List;
|
|
|
import java.util.Queue;
|
|
|
@@ -40,11 +43,11 @@ public class XFileSearch {
|
|
|
* Android Q及以上:除了应用私有目录,可以通过文件路径访问,需要权限{@link android.Manifest.permission#MANAGE_EXTERNAL_STORAGE}
|
|
|
* 应用私有目录只能通过SAF访问,需要用户手动授权可以访问的目录
|
|
|
*/
|
|
|
- public static List<XFile> searchExternalStorage(Context context, FileFilter fileFilter) {
|
|
|
+ public static List<XFile> searchExternalStorage(Context context, List<String[]> specifyScanPaths, FileFilter fileFilter) {
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
|
- return searchExternalStorageOld(context, fileFilter);
|
|
|
+ return searchExternalStorageOld(context, specifyScanPaths, fileFilter);
|
|
|
} else {
|
|
|
- return searchExternalStorageNew(context, fileFilter);
|
|
|
+ return searchExternalStorageNew(context, specifyScanPaths, fileFilter);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -59,31 +62,45 @@ public class XFileSearch {
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
|
searchExternalStorageOldAsync(context, null, fileFilter, callback, cancellationSignal);
|
|
|
} else {
|
|
|
- searchExternalStorageNewAsync(context, fileFilter, callback, cancellationSignal);
|
|
|
+ searchExternalStorageNewAsync(context, null, null, fileFilter, callback, cancellationSignal);
|
|
|
}
|
|
|
});
|
|
|
return cancellationSignal;
|
|
|
}
|
|
|
|
|
|
- public static CancellationSignal searchExternalStorageAsync(Context context, FilePreScanDirectory filePreScanDirectory, FileFilter fileFilter, FileSearchCallback callback) {
|
|
|
+
|
|
|
+ public static CancellationSignal searchPreExternalStorageAsync(Context context, List<String[]> filePreScanDirectory, FileFilter fileFilter, FileSearchCallback callback) {
|
|
|
CancellationSignal cancellationSignal = new CancellationSignal();
|
|
|
ThreadPoolUtil.getInstance().execute(() -> {
|
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
|
- searchExternalStorageOldAsync(context, filePreScanDirectory, fileFilter, callback, cancellationSignal);
|
|
|
+ searchExternalStorageOldAsync(context, null, fileFilter, callback, cancellationSignal);
|
|
|
} else {
|
|
|
- searchExternalStorageNewAsync(context, fileFilter, callback, cancellationSignal);
|
|
|
+ searchExternalStorageNewAsync(context, filePreScanDirectory, null, fileFilter, callback, cancellationSignal);
|
|
|
}
|
|
|
});
|
|
|
return cancellationSignal;
|
|
|
}
|
|
|
|
|
|
- private static void searchExternalStorageNewAsync(Context context, FileFilter fileFilter,
|
|
|
+ public static CancellationSignal searchSpecifyExternalStorageAsync(Context context, List<String[]> specifyScanPaths, FileFilter fileFilter, FileSearchCallback callback) {
|
|
|
+ CancellationSignal cancellationSignal = new CancellationSignal();
|
|
|
+ ThreadPoolUtil.getInstance().execute(() -> {
|
|
|
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
|
|
+ searchExternalStorageOldAsync(context, specifyScanPaths, fileFilter, callback, cancellationSignal);
|
|
|
+ } else {
|
|
|
+ searchExternalStorageNewAsync(context, null, specifyScanPaths, fileFilter, callback, cancellationSignal);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return cancellationSignal;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private static void searchExternalStorageNewAsync(Context context, List<String[]> filePreScanDirectory, List<String[]> specifyScanPaths, FileFilter fileFilter,
|
|
|
FileSearchCallback callback,
|
|
|
CancellationSignal cancellationSignal) {
|
|
|
final int maxThreadCount = 5;
|
|
|
Lock lock = new ReentrantLock();
|
|
|
Condition condition = lock.newCondition();
|
|
|
- List<XFile> startFiles = getExternalStorageFilesNew(context, fileFilter);
|
|
|
+ List<XFile> startFiles = getExternalStorageFilesNew(context, filePreScanDirectory, specifyScanPaths, fileFilter);
|
|
|
ArrayList<XFile> tasks = new ArrayList<>();
|
|
|
for (XFile startFile : startFiles) {
|
|
|
try {
|
|
|
@@ -157,20 +174,20 @@ public class XFileSearch {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private static void searchExternalStorageOldAsync(Context context, FilePreScanDirectory filePreScanDirectory, FileFilter fileFilter,
|
|
|
+ private static void searchExternalStorageOldAsync(Context context, List<String[]> specifyScanPaths, FileFilter fileFilter,
|
|
|
FileSearchCallback callback,
|
|
|
CancellationSignal cancellationSignal) {
|
|
|
- List<XFile> startFiles = getExternalStorageFilesOld(context, fileFilter, filePreScanDirectory);
|
|
|
+ List<XFile> startFiles = getExternalStorageFilesOld(context, specifyScanPaths);
|
|
|
searchInternalAsync(startFiles, fileFilter, callback, cancellationSignal);
|
|
|
}
|
|
|
|
|
|
- private static List<XFile> searchExternalStorageNew(Context context, FileFilter fileFilter) {
|
|
|
- List<XFile> startFiles = getExternalStorageFilesNew(context, fileFilter);
|
|
|
+ private static List<XFile> searchExternalStorageNew(Context context, List<String[]> specifyScanPaths, FileFilter fileFilter) {
|
|
|
+ List<XFile> startFiles = getExternalStorageFilesNew(context, null, specifyScanPaths, fileFilter);
|
|
|
return searchInternal(startFiles, fileFilter);
|
|
|
}
|
|
|
|
|
|
- private static List<XFile> searchExternalStorageOld(Context context, FileFilter fileFilter) {
|
|
|
- List<XFile> startFiles = getExternalStorageFilesOld(context, fileFilter, null);
|
|
|
+ private static List<XFile> searchExternalStorageOld(Context context, List<String[]> specifyScanPaths, FileFilter fileFilter) {
|
|
|
+ List<XFile> startFiles = getExternalStorageFilesOld(context, specifyScanPaths);
|
|
|
return searchInternal(startFiles, fileFilter);
|
|
|
}
|
|
|
|
|
|
@@ -221,14 +238,14 @@ public class XFileSearch {
|
|
|
}
|
|
|
|
|
|
@NonNull
|
|
|
- private static List<XFile> getExternalStorageFilesNew(Context context, FileFilter fileFilter) {
|
|
|
+ private static List<XFile> getExternalStorageFilesNew(Context context, List<String[]> filePreScanDirectory, List<String[]> specifyScanPaths, FileFilter fileFilter) {
|
|
|
List<StorageVolume> readableStorageVolumes = XStorageManager.getReadableStorageVolumes(context);
|
|
|
List<XFile> startFiles = new ArrayList<>();
|
|
|
for (StorageVolume storageVolume : readableStorageVolumes) {
|
|
|
Uri treeUri = StorageVolumeUtil.getTreeUri(storageVolume);
|
|
|
- if (fileFilter != null && fileFilter.getScanPaths() != null && !fileFilter.getScanPaths().isEmpty()) {
|
|
|
- File directory = StorageVolumeUtil.getDirectory(storageVolume);
|
|
|
- for (String[] scanPath : fileFilter.getScanPaths()) {
|
|
|
+ File directory = StorageVolumeUtil.getDirectory(storageVolume);
|
|
|
+ if (specifyScanPaths != null && !specifyScanPaths.isEmpty()) {
|
|
|
+ for (String[] scanPath : specifyScanPaths) {
|
|
|
String filePath = FileUtil.getFilePath(scanPath);
|
|
|
if (filePath.contains("Android%2Fdata")) {
|
|
|
XSAFFile xsafFile = new XSAFFile(context, treeUri, scanPath);
|
|
|
@@ -252,6 +269,105 @@ public class XFileSearch {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ } else if (filePreScanDirectory != null && !filePreScanDirectory.isEmpty()) {
|
|
|
+ List<XFile> normalFiles = new ArrayList<>();
|
|
|
+ List<XFile> androidDataFiles = new ArrayList<>();
|
|
|
+ List<XFile> allFiles = new ArrayList<>();
|
|
|
+
|
|
|
+ for (String[] scanPath : filePreScanDirectory) {
|
|
|
+ String filePath = FileUtil.getFilePath(scanPath);
|
|
|
+ if (filePath.contains("Android%2Fdata")) {
|
|
|
+ XSAFFile xsafFile = new XSAFFile(context, treeUri, scanPath);
|
|
|
+ try {
|
|
|
+ if (xsafFile.exists()) {
|
|
|
+ allFiles.add(xsafFile);
|
|
|
+ androidDataFiles.add(xsafFile);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (directory != null) {
|
|
|
+ XPathFile xPathFile = new XPathFile(context, directory, filePath);
|
|
|
+ try {
|
|
|
+ if (xPathFile.exists()) {
|
|
|
+ allFiles.add(xPathFile);
|
|
|
+ normalFiles.add(xPathFile);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (directory != null) {
|
|
|
+ XPathFile androidEx = new XPathFile(context, directory);
|
|
|
+ XFile androidXSA = null;
|
|
|
+ XSAFFile androidDataXSA = new XSAFFile(context, treeUri, new String[]{"Android", "data"});
|
|
|
+ if (androidDataXSA.exists()) {
|
|
|
+ XFile[] xFiles = androidDataXSA.listFiles();
|
|
|
+ for (XFile x : xFiles) {
|
|
|
+ XFile tag = null;
|
|
|
+ for (XFile next : androidDataFiles) {
|
|
|
+ try {
|
|
|
+ if (x.getPath().equals(next.getPath())) {
|
|
|
+ tag = next;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (tag == null) {
|
|
|
+ allFiles.add(x);
|
|
|
+ } else {
|
|
|
+ androidDataFiles.remove(tag);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (androidEx.exists()) {
|
|
|
+ XFile[] xFiles = androidEx.listFiles();
|
|
|
+ for (XFile x : xFiles) {
|
|
|
+ try {
|
|
|
+ if ("Android".equals(x.getName())) {
|
|
|
+ androidXSA = x;
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ }
|
|
|
+ XFile tag = null;
|
|
|
+ for (XFile next : normalFiles) {
|
|
|
+ try {
|
|
|
+ if (x.getPath().equals(next.getPath())) {
|
|
|
+ tag = next;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (tag == null) {
|
|
|
+ allFiles.add(x);
|
|
|
+ } else {
|
|
|
+ normalFiles.remove(tag);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ if (androidXSA != null && androidXSA.exists()) {
|
|
|
+ XFile[] xFiles = androidXSA.listFiles();
|
|
|
+ for (XFile xFile : xFiles) {
|
|
|
+ try {
|
|
|
+ if (xFile.getPath().contains("Android%2Fdata") || xFile.getPath().contains("Android/data")) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ allFiles.add(xFile);
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception ignore) {
|
|
|
+ }
|
|
|
+ }
|
|
|
+ startFiles.addAll(allFiles);
|
|
|
} else {
|
|
|
XSAFFile xsafFile = new XSAFFile(context, treeUri, Arrays.asList("Android", "data"));
|
|
|
try {
|
|
|
@@ -261,8 +377,6 @@ public class XFileSearch {
|
|
|
} catch (Exception e) {
|
|
|
e.printStackTrace();
|
|
|
}
|
|
|
-
|
|
|
- File directory = StorageVolumeUtil.getDirectory(storageVolume);
|
|
|
if (directory != null) {
|
|
|
XPathFile xPathFile = new XPathFile(context, directory);
|
|
|
try {
|
|
|
@@ -279,7 +393,7 @@ public class XFileSearch {
|
|
|
}
|
|
|
|
|
|
@NonNull
|
|
|
- private static List<XFile> getExternalStorageFilesOld(Context context, FileFilter fileFilter, FilePreScanDirectory filePreScanDirectory) {
|
|
|
+ private static List<XFile> getExternalStorageFilesOld(Context context, List<String[]> specifyScanPaths) {
|
|
|
List<StorageVolume> readableStorageVolumes = XStorageManager.getReadableStorageVolumes(context);
|
|
|
List<XFile> startFiles = new ArrayList<>();
|
|
|
for (StorageVolume storageVolume : readableStorageVolumes) {
|
|
|
@@ -287,8 +401,8 @@ public class XFileSearch {
|
|
|
if (directory == null) {
|
|
|
continue;
|
|
|
}
|
|
|
- if (fileFilter != null && fileFilter.getScanPaths() != null && !fileFilter.getScanPaths().isEmpty()) {
|
|
|
- for (String[] searchPath : fileFilter.getScanPaths()) {
|
|
|
+ if (specifyScanPaths != null && !specifyScanPaths.isEmpty()) {
|
|
|
+ for (String[] searchPath : specifyScanPaths) {
|
|
|
try {
|
|
|
File file = new File(directory, FileUtil.getFilePath(searchPath));
|
|
|
if (file.exists()) {
|
|
|
@@ -298,38 +412,6 @@ public class XFileSearch {
|
|
|
|
|
|
}
|
|
|
}
|
|
|
- } else if (filePreScanDirectory != null && filePreScanDirectory.preScanPaths() != null && !filePreScanDirectory.preScanPaths().isEmpty()) {
|
|
|
- // 预扫描目录
|
|
|
- List<XFile> preFiles = new ArrayList<>();
|
|
|
- for (String searchPath : filePreScanDirectory.preScanPaths()) {
|
|
|
- try {
|
|
|
- File file = new File(directory, searchPath);
|
|
|
- if (file.exists()) {
|
|
|
- preFiles.add(new XPathFile(context, file));
|
|
|
- }
|
|
|
- } catch (Exception ignore) {
|
|
|
-
|
|
|
- }
|
|
|
- }
|
|
|
- XFile androidData = new XPathFile(context, directory);
|
|
|
- File androidDataFile = new File(directory, "Android%2Fdata");
|
|
|
- String androidDataFilePath = androidDataFile.getPath();
|
|
|
- List<XFile> rootFiles = new ArrayList<>();
|
|
|
- File[] files = androidDataFile.listFiles();
|
|
|
- if (files != null) {
|
|
|
- for (File file : files) {
|
|
|
- if (!androidDataFilePath.contains(file.getPath())) {
|
|
|
- rootFiles.add(new XPathFile(context, file));
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- try {
|
|
|
- if (androidDataFile.exists()) {
|
|
|
-
|
|
|
- }
|
|
|
- } catch (Exception ignore) {
|
|
|
-
|
|
|
- }
|
|
|
} else {
|
|
|
XFile androidData = new XPathFile(context, directory);
|
|
|
try {
|
|
|
@@ -554,15 +636,9 @@ public class XFileSearch {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public interface FilePreScanDirectory {
|
|
|
- //仅一级目录以及Android/data目录
|
|
|
- List<String> preScanPaths();
|
|
|
- }
|
|
|
|
|
|
public interface FileFilter {
|
|
|
|
|
|
- List<String[]> getScanPaths();
|
|
|
-
|
|
|
boolean acceptFile(XFile file);
|
|
|
|
|
|
boolean acceptDirectory(XFile file);
|