Loading AconfigFlags.bp +1 −0 Original line number Original line Diff line number Diff line Loading @@ -132,6 +132,7 @@ aconfig_declarations_group { "libcore_exported_aconfig_flags_lib", "libcore_exported_aconfig_flags_lib", "libcore_readonly_aconfig_flags_lib", "libcore_readonly_aconfig_flags_lib", "libgui_flags_java_lib", "libgui_flags_java_lib", "mediaprovider_exported_aconfig_flag_lib", "networksecurity_exported_aconfig_flags_lib", "networksecurity_exported_aconfig_flags_lib", "power_flags_lib", "power_flags_lib", "sdk_sandbox_exported_flags_lib", "sdk_sandbox_exported_flags_lib", Loading core/java/com/android/internal/content/storage/FileSystemProvider.java +166 −7 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.internal.content; package com.android.internal.content; import static android.provider.Flags.enableDocumentsTrashApi; import static com.android.providers.media.flags.Flags.enableTrashAndRestoreByFilePathApi; import android.annotation.CallSuper; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; Loading Loading @@ -71,6 +75,8 @@ import java.util.Locale; import java.util.Queue; import java.util.Queue; import java.util.Set; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** /** * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local Loading @@ -92,6 +98,19 @@ public abstract class FileSystemProvider extends DocumentsProvider { private static final int DEFAULT_SEARCH_RESULT_LIMIT = 23; private static final int DEFAULT_SEARCH_RESULT_LIMIT = 23; private static final int MAX_SEARCH_RESULT_LIMIT = 1000; private static final int MAX_SEARCH_RESULT_LIMIT = 1000; /** * File prefix indicating that the file {@link MediaStore.MediaColumns#IS_TRASHED}. */ protected static final String PREFIX_TRASHED = "trashed"; /** * Default directory for trashed items */ protected static final String DIRECTORY_TRASH_STORAGE = ".trash-storage"; private static final Pattern PATTERN_EXPIRES_FILE = Pattern.compile( "(?i)^\\.(pending|trashed)-(\\d+)-([^/]+)$"); private static String joinNewline(String... args) { private static String joinNewline(String... args) { return TextUtils.join("\n", args); return TextUtils.join("\n", args); } } Loading Loading @@ -609,6 +628,117 @@ public abstract class FileSystemProvider extends DocumentsProvider { return DocumentsContract.openImageThumbnail(file); return DocumentsContract.openImageThumbnail(file); } } @Nullable @Override public String trashDocument(@NonNull String documentId) throws FileNotFoundException { if (!enableTrashAndRestoreByFilePathApi()) { throw new UnsupportedOperationException( "MediaStore feature for trash is not supported"); } File file = getFileForDocId(documentId); if (!file.exists()) { throw new FileNotFoundException("File does not exist for " + documentId); } String trashedPath = MediaStore.trashFile(getContext().getContentResolver(), file.getPath()); File trashedFile = new File(trashedPath); final String trashedDocId = getDocIdForFile(trashedFile); onDocIdChanged(documentId); onDocIdDeleted(documentId, /* shouldRevokeUriPermission */ true); onDocIdChanged(trashedDocId); return trashedDocId; } protected final Cursor queryTrashDocuments(File parent, String[] projection) throws FileNotFoundException { MatrixCursor result = new MatrixCursor(resolveProjection(projection)); includeTrashFiles(result, parent); // include MediaStore trashed files which are not in .trash-storage location includeMediaStoreTrashFiles(result); return result; } @Nullable @Override public String restoreDocumentFromTrash(@NonNull String documentId, @Nullable String targetId) throws FileNotFoundException { if (!enableTrashAndRestoreByFilePathApi()) { throw new UnsupportedOperationException( "MediaStore feature for trash is not supported"); } File file = getFileForDocId(documentId); if (!file.exists()) { throw new FileNotFoundException("File does not exist for " + documentId); } if (!isTrashFile(file)) { throw new IllegalArgumentException("DocumentId represents a non-trashed file"); } String targetPath = null; if (targetId != null) { File targetFile = getFileForDocId(targetId); if (targetFile != null) { targetPath = targetFile.getAbsolutePath(); } } String restoredPath = MediaStore.restoreFileFromTrash(getContext().getContentResolver(), file.getPath(), targetPath); File restoredFile = new File(restoredPath); final String restoredDocId = getDocIdForFile(restoredFile); onDocIdChanged(documentId); onDocIdChanged(restoredDocId); return restoredDocId; } private boolean isTrashFile(File file) { final Matcher matcher = PATTERN_EXPIRES_FILE.matcher(file.getName()); return matcher.matches() && matcher.group(1).equals(PREFIX_TRASHED); } private void includeTrashFiles(MatrixCursor result, File parent) throws FileNotFoundException { for (File file : parent.listFiles()) { if (isTrashFile(file)) { includeFile(result, null, file); continue; } if (file.isDirectory()) { includeTrashFiles(result, file); } } } private void includeMediaStoreTrashFiles(MatrixCursor result) throws FileNotFoundException { final Uri uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL); final Bundle queryArgs = new Bundle(); queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY); queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, MediaStore.MediaColumns.RELATIVE_PATH + " NOT LIKE ?"); queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, new String[]{DIRECTORY_TRASH_STORAGE + "/%"}); String[] projection = new String[]{MediaStore.Files.FileColumns.DATA}; try (Cursor cursor = getContext().getContentResolver().query(uri, projection, queryArgs, null)) { if (cursor != null) { while (cursor.moveToNext()) { final String data = cursor.getString( cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)); File file = new File(data); includeFile(result, null, file); } } } } protected RowBuilder includeFile(final MatrixCursor result, String docId, File file) protected RowBuilder includeFile(final MatrixCursor result, String docId, File file) throws FileNotFoundException { throws FileNotFoundException { final String[] columns = result.getColumnNames(); final String[] columns = result.getColumnNames(); Loading @@ -627,17 +757,29 @@ public abstract class FileSystemProvider extends DocumentsProvider { final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS); final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS); if (flagIndex != -1) { if (flagIndex != -1) { final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR); final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR); boolean isTrashedFile = isTrashFile(file); int flags = 0; int flags = 0; if (file.canWrite()) { if (file.canWrite()) { flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_DELETE; if (!isTrashedFile) { flags |= Document.FLAG_SUPPORTS_RENAME; flags |= Document.FLAG_SUPPORTS_RENAME; flags |= Document.FLAG_SUPPORTS_MOVE; flags |= Document.FLAG_SUPPORTS_MOVE; if (isDir) { if (isDir) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_DIR_SUPPORTS_CREATE; } else { } else { flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_WRITE; } } } } } if (enableDocumentsTrashApi()) { if (isTrashFile(file)) { flags |= Document.FLAG_SUPPORTS_RESTORE; } else if (isTrashSupported(file)) { flags |= Document.FLAG_SUPPORTS_TRASH; } } if (isDir && shouldBlockDirectoryFromTree(docId)) { if (isDir && shouldBlockDirectoryFromTree(docId)) { flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; Loading @@ -655,7 +797,13 @@ public abstract class FileSystemProvider extends DocumentsProvider { final int displayNameIndex = ArrayUtils.indexOf(columns, Document.COLUMN_DISPLAY_NAME); final int displayNameIndex = ArrayUtils.indexOf(columns, Document.COLUMN_DISPLAY_NAME); if (displayNameIndex != -1) { if (displayNameIndex != -1) { row.add(displayNameIndex, file.getName()); String name = file.getName(); final Matcher matcher = PATTERN_EXPIRES_FILE.matcher(name); if (matcher.matches() && matcher.group(1).equals(PREFIX_TRASHED)) { // .trashed-<timestamp>-<name> name = matcher.group(3); } row.add(displayNameIndex, name); } } final int lastModifiedIndex = ArrayUtils.indexOf(columns, Document.COLUMN_LAST_MODIFIED); final int lastModifiedIndex = ArrayUtils.indexOf(columns, Document.COLUMN_LAST_MODIFIED); Loading Loading @@ -686,6 +834,17 @@ public abstract class FileSystemProvider extends DocumentsProvider { return false; return false; } } /** * Some providers may want to restrict access to certain directories and files, * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for * privacy reasons. * Such providers should override this method. */ protected boolean isTrashSupported(@NonNull File document) throws FileNotFoundException { return false; } /** /** * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of * a {@link String} {@code documentId}. * a {@link String} {@code documentId}. Loading packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +30 −0 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.externalstorage; package com.android.externalstorage; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.app.usage.StorageStatsManager; import android.app.usage.StorageStatsManager; Loading Loading @@ -638,6 +639,35 @@ public class ExternalStorageProvider extends FileSystemProvider { return result; return result; } } @Override protected boolean isTrashSupported(File file) { try { String documentId = getDocIdForFile(file); // Trash not supported on USB devices if (isOnRemovableUsbStorage(documentId)) { return false; } final RootInfo root = getRootFromDocId(documentId); final String canonicalPath = getPathFromDocId(documentId); return !isRestrictedPath(root.rootId, canonicalPath); } catch (Exception e) { return false; } } @Nullable @Override public Cursor queryTrashDocuments(String[] projection) throws FileNotFoundException { if (!mRoots.containsKey(ROOT_ID_PRIMARY_EMULATED)) { return null; } RootInfo rootInfo = mRoots.get(ROOT_ID_PRIMARY_EMULATED); File trashDir = new File(rootInfo.path, DIRECTORY_TRASH_STORAGE); return queryTrashDocuments(trashDir, projection); } @Override @Override public Path findDocumentPath(@Nullable String parentDocId, String childDocId) public Path findDocumentPath(@Nullable String parentDocId, String childDocId) throws FileNotFoundException { throws FileNotFoundException { Loading packages/ExternalStorageProvider/tests/src/com/android/externalstorage/CleanupTemporaryFilesRule.java +4 −0 Original line number Original line Diff line number Diff line Loading @@ -50,6 +50,10 @@ public class CleanupTemporaryFilesRule implements TestRule { * @param directory the path to start from * @param directory the path to start from */ */ public static void removeFilesRecursively(File directory) { public static void removeFilesRecursively(File directory) { if (directory == null || !directory.exists() || directory.listFiles().length == 0) { return; } for (File childFile : directory.listFiles()) { for (File childFile : directory.listFiles()) { if (childFile.isDirectory()) { if (childFile.isDirectory()) { removeFilesRecursively(childFile); removeFilesRecursively(childFile); Loading packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java +271 −236 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
AconfigFlags.bp +1 −0 Original line number Original line Diff line number Diff line Loading @@ -132,6 +132,7 @@ aconfig_declarations_group { "libcore_exported_aconfig_flags_lib", "libcore_exported_aconfig_flags_lib", "libcore_readonly_aconfig_flags_lib", "libcore_readonly_aconfig_flags_lib", "libgui_flags_java_lib", "libgui_flags_java_lib", "mediaprovider_exported_aconfig_flag_lib", "networksecurity_exported_aconfig_flags_lib", "networksecurity_exported_aconfig_flags_lib", "power_flags_lib", "power_flags_lib", "sdk_sandbox_exported_flags_lib", "sdk_sandbox_exported_flags_lib", Loading
core/java/com/android/internal/content/storage/FileSystemProvider.java +166 −7 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,10 @@ package com.android.internal.content; package com.android.internal.content; import static android.provider.Flags.enableDocumentsTrashApi; import static com.android.providers.media.flags.Flags.enableTrashAndRestoreByFilePathApi; import android.annotation.CallSuper; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; Loading Loading @@ -71,6 +75,8 @@ import java.util.Locale; import java.util.Queue; import java.util.Queue; import java.util.Set; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; /** /** * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local * A helper class for {@link android.provider.DocumentsProvider} to perform file operations on local Loading @@ -92,6 +98,19 @@ public abstract class FileSystemProvider extends DocumentsProvider { private static final int DEFAULT_SEARCH_RESULT_LIMIT = 23; private static final int DEFAULT_SEARCH_RESULT_LIMIT = 23; private static final int MAX_SEARCH_RESULT_LIMIT = 1000; private static final int MAX_SEARCH_RESULT_LIMIT = 1000; /** * File prefix indicating that the file {@link MediaStore.MediaColumns#IS_TRASHED}. */ protected static final String PREFIX_TRASHED = "trashed"; /** * Default directory for trashed items */ protected static final String DIRECTORY_TRASH_STORAGE = ".trash-storage"; private static final Pattern PATTERN_EXPIRES_FILE = Pattern.compile( "(?i)^\\.(pending|trashed)-(\\d+)-([^/]+)$"); private static String joinNewline(String... args) { private static String joinNewline(String... args) { return TextUtils.join("\n", args); return TextUtils.join("\n", args); } } Loading Loading @@ -609,6 +628,117 @@ public abstract class FileSystemProvider extends DocumentsProvider { return DocumentsContract.openImageThumbnail(file); return DocumentsContract.openImageThumbnail(file); } } @Nullable @Override public String trashDocument(@NonNull String documentId) throws FileNotFoundException { if (!enableTrashAndRestoreByFilePathApi()) { throw new UnsupportedOperationException( "MediaStore feature for trash is not supported"); } File file = getFileForDocId(documentId); if (!file.exists()) { throw new FileNotFoundException("File does not exist for " + documentId); } String trashedPath = MediaStore.trashFile(getContext().getContentResolver(), file.getPath()); File trashedFile = new File(trashedPath); final String trashedDocId = getDocIdForFile(trashedFile); onDocIdChanged(documentId); onDocIdDeleted(documentId, /* shouldRevokeUriPermission */ true); onDocIdChanged(trashedDocId); return trashedDocId; } protected final Cursor queryTrashDocuments(File parent, String[] projection) throws FileNotFoundException { MatrixCursor result = new MatrixCursor(resolveProjection(projection)); includeTrashFiles(result, parent); // include MediaStore trashed files which are not in .trash-storage location includeMediaStoreTrashFiles(result); return result; } @Nullable @Override public String restoreDocumentFromTrash(@NonNull String documentId, @Nullable String targetId) throws FileNotFoundException { if (!enableTrashAndRestoreByFilePathApi()) { throw new UnsupportedOperationException( "MediaStore feature for trash is not supported"); } File file = getFileForDocId(documentId); if (!file.exists()) { throw new FileNotFoundException("File does not exist for " + documentId); } if (!isTrashFile(file)) { throw new IllegalArgumentException("DocumentId represents a non-trashed file"); } String targetPath = null; if (targetId != null) { File targetFile = getFileForDocId(targetId); if (targetFile != null) { targetPath = targetFile.getAbsolutePath(); } } String restoredPath = MediaStore.restoreFileFromTrash(getContext().getContentResolver(), file.getPath(), targetPath); File restoredFile = new File(restoredPath); final String restoredDocId = getDocIdForFile(restoredFile); onDocIdChanged(documentId); onDocIdChanged(restoredDocId); return restoredDocId; } private boolean isTrashFile(File file) { final Matcher matcher = PATTERN_EXPIRES_FILE.matcher(file.getName()); return matcher.matches() && matcher.group(1).equals(PREFIX_TRASHED); } private void includeTrashFiles(MatrixCursor result, File parent) throws FileNotFoundException { for (File file : parent.listFiles()) { if (isTrashFile(file)) { includeFile(result, null, file); continue; } if (file.isDirectory()) { includeTrashFiles(result, file); } } } private void includeMediaStoreTrashFiles(MatrixCursor result) throws FileNotFoundException { final Uri uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL); final Bundle queryArgs = new Bundle(); queryArgs.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY); queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, MediaStore.MediaColumns.RELATIVE_PATH + " NOT LIKE ?"); queryArgs.putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, new String[]{DIRECTORY_TRASH_STORAGE + "/%"}); String[] projection = new String[]{MediaStore.Files.FileColumns.DATA}; try (Cursor cursor = getContext().getContentResolver().query(uri, projection, queryArgs, null)) { if (cursor != null) { while (cursor.moveToNext()) { final String data = cursor.getString( cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA)); File file = new File(data); includeFile(result, null, file); } } } } protected RowBuilder includeFile(final MatrixCursor result, String docId, File file) protected RowBuilder includeFile(final MatrixCursor result, String docId, File file) throws FileNotFoundException { throws FileNotFoundException { final String[] columns = result.getColumnNames(); final String[] columns = result.getColumnNames(); Loading @@ -627,17 +757,29 @@ public abstract class FileSystemProvider extends DocumentsProvider { final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS); final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS); if (flagIndex != -1) { if (flagIndex != -1) { final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR); final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR); boolean isTrashedFile = isTrashFile(file); int flags = 0; int flags = 0; if (file.canWrite()) { if (file.canWrite()) { flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_DELETE; if (!isTrashedFile) { flags |= Document.FLAG_SUPPORTS_RENAME; flags |= Document.FLAG_SUPPORTS_RENAME; flags |= Document.FLAG_SUPPORTS_MOVE; flags |= Document.FLAG_SUPPORTS_MOVE; if (isDir) { if (isDir) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_DIR_SUPPORTS_CREATE; } else { } else { flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_WRITE; } } } } } if (enableDocumentsTrashApi()) { if (isTrashFile(file)) { flags |= Document.FLAG_SUPPORTS_RESTORE; } else if (isTrashSupported(file)) { flags |= Document.FLAG_SUPPORTS_TRASH; } } if (isDir && shouldBlockDirectoryFromTree(docId)) { if (isDir && shouldBlockDirectoryFromTree(docId)) { flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; Loading @@ -655,7 +797,13 @@ public abstract class FileSystemProvider extends DocumentsProvider { final int displayNameIndex = ArrayUtils.indexOf(columns, Document.COLUMN_DISPLAY_NAME); final int displayNameIndex = ArrayUtils.indexOf(columns, Document.COLUMN_DISPLAY_NAME); if (displayNameIndex != -1) { if (displayNameIndex != -1) { row.add(displayNameIndex, file.getName()); String name = file.getName(); final Matcher matcher = PATTERN_EXPIRES_FILE.matcher(name); if (matcher.matches() && matcher.group(1).equals(PREFIX_TRASHED)) { // .trashed-<timestamp>-<name> name = matcher.group(3); } row.add(displayNameIndex, name); } } final int lastModifiedIndex = ArrayUtils.indexOf(columns, Document.COLUMN_LAST_MODIFIED); final int lastModifiedIndex = ArrayUtils.indexOf(columns, Document.COLUMN_LAST_MODIFIED); Loading Loading @@ -686,6 +834,17 @@ public abstract class FileSystemProvider extends DocumentsProvider { return false; return false; } } /** * Some providers may want to restrict access to certain directories and files, * e.g. <i>"Android/data"</i> and <i>"Android/obb"</i> on the shared storage for * privacy reasons. * Such providers should override this method. */ protected boolean isTrashSupported(@NonNull File document) throws FileNotFoundException { return false; } /** /** * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of * a {@link String} {@code documentId}. * a {@link String} {@code documentId}. Loading
packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +30 −0 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.externalstorage; package com.android.externalstorage; import android.annotation.NonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.Nullable; import android.app.usage.StorageStatsManager; import android.app.usage.StorageStatsManager; Loading Loading @@ -638,6 +639,35 @@ public class ExternalStorageProvider extends FileSystemProvider { return result; return result; } } @Override protected boolean isTrashSupported(File file) { try { String documentId = getDocIdForFile(file); // Trash not supported on USB devices if (isOnRemovableUsbStorage(documentId)) { return false; } final RootInfo root = getRootFromDocId(documentId); final String canonicalPath = getPathFromDocId(documentId); return !isRestrictedPath(root.rootId, canonicalPath); } catch (Exception e) { return false; } } @Nullable @Override public Cursor queryTrashDocuments(String[] projection) throws FileNotFoundException { if (!mRoots.containsKey(ROOT_ID_PRIMARY_EMULATED)) { return null; } RootInfo rootInfo = mRoots.get(ROOT_ID_PRIMARY_EMULATED); File trashDir = new File(rootInfo.path, DIRECTORY_TRASH_STORAGE); return queryTrashDocuments(trashDir, projection); } @Override @Override public Path findDocumentPath(@Nullable String parentDocId, String childDocId) public Path findDocumentPath(@Nullable String parentDocId, String childDocId) throws FileNotFoundException { throws FileNotFoundException { Loading
packages/ExternalStorageProvider/tests/src/com/android/externalstorage/CleanupTemporaryFilesRule.java +4 −0 Original line number Original line Diff line number Diff line Loading @@ -50,6 +50,10 @@ public class CleanupTemporaryFilesRule implements TestRule { * @param directory the path to start from * @param directory the path to start from */ */ public static void removeFilesRecursively(File directory) { public static void removeFilesRecursively(File directory) { if (directory == null || !directory.exists() || directory.listFiles().length == 0) { return; } for (File childFile : directory.listFiles()) { for (File childFile : directory.listFiles()) { if (childFile.isDirectory()) { if (childFile.isDirectory()) { removeFilesRecursively(childFile); removeFilesRecursively(childFile); Loading
packages/ExternalStorageProvider/tests/src/com/android/externalstorage/ExternalStorageProviderTest.java +271 −236 File changed.Preview size limit exceeded, changes collapsed. Show changes