Loading core/java/android/content/Context.java +9 −0 Original line number Original line Diff line number Diff line Loading @@ -242,6 +242,7 @@ public abstract class Context { BIND_IMPORTANT, BIND_IMPORTANT, BIND_ADJUST_WITH_ACTIVITY, BIND_ADJUST_WITH_ACTIVITY, BIND_NOT_PERCEPTIBLE, BIND_NOT_PERCEPTIBLE, BIND_DENY_ACTIVITY_STARTS, BIND_INCLUDE_CAPABILITIES BIND_INCLUDE_CAPABILITIES }) }) @Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE) Loading Loading @@ -355,6 +356,14 @@ public abstract class Context { /*********** Public flags above this line ***********/ /*********** Public flags above this line ***********/ /*********** Hidden flags below this line ***********/ /*********** Hidden flags below this line ***********/ /** * Flag for {@link #bindService}: If binding from an app that is visible, the bound service is * allowed to start an activity from background. Add a flag so that this behavior can be opted * out. * @hide */ public static final int BIND_DENY_ACTIVITY_STARTS = 0X000004000; /** /** * Flag for {@link #bindService}: This flag is intended to be used only by the system to adjust * Flag for {@link #bindService}: This flag is intended to be used only by the system to adjust * the scheduling policy for IMEs (and any other out-of-process user-visible components that * the scheduling policy for IMEs (and any other out-of-process user-visible components that Loading core/java/com/android/internal/content/FileSystemProvider.java +92 −76 Original line number Original line Diff line number Diff line Loading @@ -63,13 +63,13 @@ import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Arrays; import java.util.LinkedList; import java.util.LinkedList; import java.util.List; import java.util.List; 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.function.Predicate; 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 @@ -87,6 +87,8 @@ public abstract class FileSystemProvider extends DocumentsProvider { DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER, DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER, DocumentsContract.QUERY_ARG_MIME_TYPES); DocumentsContract.QUERY_ARG_MIME_TYPES); private static final int MAX_RESULTS_NUMBER = 23; private static String joinNewline(String... args) { private static String joinNewline(String... args) { return TextUtils.join("\n", args); return TextUtils.join("\n", args); } } Loading Loading @@ -394,56 +396,53 @@ public abstract class FileSystemProvider extends DocumentsProvider { } } /** /** * This method is similar to * WARNING: this method should really be {@code final}, but for the backward compatibility it's * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns * not; new classes that extend {@link FileSystemProvider} should override * all children documents including hidden directories/files. * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method. * * <p> * In a scoped storage world, access to "Android/data" style directories are hidden for privacy * reasons. This method may show privacy sensitive data, so its usage should only be in * restricted modes. * * @param parentDocumentId the directory to return children for. * @param projection list of {@link Document} columns to put into the * cursor. If {@code null} all supported columns should be * included. * @param sortOrder how to order the rows, formatted as an SQL * {@code ORDER BY} clause (excluding the ORDER BY itself). * Passing {@code null} will use the default sort order, which * may be unordered. This ordering is a hint that can be used to * prioritize how data is fetched from the network, but UI may * always enforce a specific ordering * @throws FileNotFoundException when parent document doesn't exist or query fails */ */ protected Cursor queryChildDocumentsShowAll( @Override String parentDocumentId, String[] projection, String sortOrder) public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder) throws FileNotFoundException { throws FileNotFoundException { return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true); return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false); } } /** * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it * could return <b>all</b> content of the directory, <b>including restricted (hidden) * directories and files</b>. * <p> * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and * {@code Android/obb/} on the external storage) are hidden for privacy reasons. * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care. */ @Override @Override public Cursor queryChildDocuments( public final Cursor queryChildDocumentsForManage(String documentId, String[] projection, String parentDocumentId, String[] projection, String sortOrder) String sortOrder) throws FileNotFoundException { throws FileNotFoundException { return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true); // Access to some directories is hidden for privacy reasons. return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow); } } private Cursor queryChildDocuments( protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder, String parentDocumentId, String[] projection, String sortOrder, boolean includeHidden) throws FileNotFoundException { @NonNull Predicate<File> filter) throws FileNotFoundException { final File parent = getFileForDocId(documentId); final File parent = getFileForDocId(parentDocumentId); final MatrixCursor result = new DirectoryCursor( final MatrixCursor result = new DirectoryCursor( resolveProjection(projection), parentDocumentId, parent); resolveProjection(projection), documentId, parent); if (parent.isDirectory()) { for (File file : FileUtils.listFilesOrEmpty(parent)) { if (!parent.isDirectory()) { if (filter.test(file)) { Log.w(TAG, '"' + documentId + "\" is not a directory"); includeFile(result, null, file); return result; } } if (!includeHidden && shouldHideDocument(documentId)) { Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden"); return result; } } } else { Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory"); for (File file : FileUtils.listFilesOrEmpty(parent)) { if (!includeHidden && shouldHideDocument(file)) continue; includeFile(result, null, file); } } return result; return result; } } Loading @@ -465,23 +464,29 @@ public abstract class FileSystemProvider extends DocumentsProvider { * * * @see ContentResolver#EXTRA_HONORED_ARGS * @see ContentResolver#EXTRA_HONORED_ARGS */ */ protected final Cursor querySearchDocuments( protected final Cursor querySearchDocuments(File folder, String[] projection, File folder, String[] projection, Set<String> exclusion, Bundle queryArgs) Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException { throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveProjection(projection)); final MatrixCursor result = new MatrixCursor(resolveProjection(projection)); final LinkedList<File> pending = new LinkedList<>(); pending.add(folder); // We'll be a running a BFS here. while (!pending.isEmpty() && result.getCount() < 24) { final Queue<File> pending = new ArrayDeque<>(); final File file = pending.removeFirst(); pending.offer(folder); if (shouldHide(file)) continue; while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) { final File file = pending.poll(); // Skip hidden documents (both files and directories) if (shouldHideDocument(file)) continue; if (file.isDirectory()) { if (file.isDirectory()) { for (File child : FileUtils.listFilesOrEmpty(file)) { for (File child : FileUtils.listFilesOrEmpty(file)) { pending.add(child); pending.offer(child); } } } } if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file, queryArgs)) { if (exclusion.contains(file.getAbsolutePath())) continue; if (matchSearchQueryArguments(file, queryArgs)) { includeFile(result, null, file); includeFile(result, null, file); } } } } Loading Loading @@ -600,26 +605,23 @@ 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); int flags = 0; int flags = 0; if (file.canWrite()) { if (file.canWrite()) { if (mimeType.equals(Document.MIME_TYPE_DIR)) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_RENAME; flags |= Document.FLAG_SUPPORTS_RENAME; flags |= Document.FLAG_SUPPORTS_MOVE; flags |= Document.FLAG_SUPPORTS_MOVE; if (isDir) { if (shouldBlockFromTree(docId)) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; } } else { } else { flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_RENAME; flags |= Document.FLAG_SUPPORTS_MOVE; } } } } if (isDir && shouldBlockDirectoryFromTree(docId)) { flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; } if (mimeType.startsWith("image/")) { if (mimeType.startsWith("image/")) { flags |= Document.FLAG_SUPPORTS_THUMBNAIL; flags |= Document.FLAG_SUPPORTS_THUMBNAIL; } } Loading Loading @@ -652,22 +654,36 @@ public abstract class FileSystemProvider extends DocumentsProvider { return row; return row; } } private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile( "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$"); /** /** * In a scoped storage world, access to "Android/data" style directories are * Some providers may want to restrict access to certain directories and files, * hidden for privacy reasons. * 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 shouldHide(@NonNull File file) { protected boolean shouldHideDocument(@NonNull String documentId) return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches()); throws FileNotFoundException { return false; } } private boolean shouldShow(@NonNull File file) { /** return !shouldHide(file); * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of * a {@link String} {@code documentId}. * * @see #shouldHideDocument(String) */ protected final boolean shouldHideDocument(@NonNull File document) throws FileNotFoundException { return shouldHideDocument(getDocIdForFile(document)); } } protected boolean shouldBlockFromTree(@NonNull String docId) { /** * @return if the directory that should be blocked from being selected when the user launches * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent. * * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE */ protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) throws FileNotFoundException { return false; return false; } } Loading core/proto/android/server/activitymanagerservice.proto +1 −0 Original line number Original line Diff line number Diff line Loading @@ -524,6 +524,7 @@ message ConnectionRecordProto { DEAD = 15; DEAD = 15; NOT_PERCEPTIBLE = 16; NOT_PERCEPTIBLE = 16; INCLUDE_CAPABILITIES = 17; INCLUDE_CAPABILITIES = 17; DENY_ACTIVITY_STARTS = 18; } } repeated Flag flags = 3; repeated Flag flags = 3; optional string service_name = 4; optional string service_name = 4; Loading packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +109 −53 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.externalstorage; package com.android.externalstorage; import static java.util.regex.Pattern.CASE_INSENSITIVE; 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 @@ -61,9 +63,22 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.PrintWriter; import java.util.Collections; import java.util.Collections; import java.util.List; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Objects; import java.util.UUID; import java.util.UUID; import java.util.regex.Pattern; /** * Presents content of the shared (a.k.a. "external") storage. * <p> * Starting with Android 11 (R), restricts access to the certain sections of the shared storage: * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in * the DocumentsUI by default. * See <a href="https://developer.android.com/about/versions/11/privacy/storage"> * Storage updates in Android 11</a>. * <p> * Documents ID format: {@code root:path/to/file}. */ public class ExternalStorageProvider extends FileSystemProvider { public class ExternalStorageProvider extends FileSystemProvider { private static final String TAG = "ExternalStorage"; private static final String TAG = "ExternalStorage"; Loading @@ -74,7 +89,12 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final Uri BASE_URI = private static final Uri BASE_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); // docId format: root:path/to/file /** * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and * {@code /Android/sandbox/} along with all their subdirectories and content. */ private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES = Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE); private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, Loading Loading @@ -276,70 +296,91 @@ public class ExternalStorageProvider extends FileSystemProvider { return projection != null ? projection : DEFAULT_ROOT_PROJECTION; return projection != null ? projection : DEFAULT_ROOT_PROJECTION; } } /** * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the * integrated shared ("external") storage along with all their content and subdirectories as * hidden. */ @Override @Override public Cursor queryChildDocumentsForManage( protected boolean shouldHideDocument(@NonNull String documentId) { String parentDocId, String[] projection, String sortOrder) // Don't need to hide anything on USB drives. throws FileNotFoundException { if (isOnRemovableUsbStorage(documentId)) { return queryChildDocumentsShowAll(parentDocId, projection, sortOrder); return false; } final String path = getPathFromDocId(documentId); return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches(); } } /** /** * Check that the directory is the root of storage or blocked file from tree. * Check that the directory is the root of storage or blocked file from tree. * <p> * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear * the UI, but the user <b>WILL NOT</b> be able to select them. * * * @param docId the docId of the directory to be checked * @param documentId the docId of the directory to be checked * @return true, should be blocked from tree. Otherwise, false. * @return true, should be blocked from tree. Otherwise, false. * * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE */ */ @Override @Override protected boolean shouldBlockFromTree(@NonNull String docId) { protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) try { throws FileNotFoundException { final File dir = getFileForDocId(docId, false /* visible */); final File dir = getFileForDocId(documentId, false); // The file is null or it is not a directory // the file is null or it is not a directory if (dir == null || !dir.isDirectory()) { if (dir == null || !dir.isDirectory()) { return false; return false; } } // Allow all directories on USB, including the root. // Allow all directories on USB, including the root. try { if (isOnRemovableUsbStorage(documentId)) { RootInfo rootInfo = getRootFromDocId(docId); if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { return false; return false; } } } catch (FileNotFoundException e) { Log.e(TAG, "Failed to determine rootInfo for docId"); } final String path = getPathFromDocId(docId); // Get canonical(!) path. Note that this path will have neither leading nor training "/". // This the root's path will be just an empty string. final String path = getPathFromDocId(documentId); // Block the root of the storage // Block the root of the storage if (path.isEmpty()) { if (path.isEmpty()) { return true; return true; } } // Block Download folder from tree // Block /Download/ and /Android/ folders from the tree. if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(), if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) || path.toLowerCase())) { equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) { return true; return true; } } if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(), // This shouldn't really make a difference, but just in case - let's block hidden path.toLowerCase())) { // directories as well. if (shouldHideDocument(documentId)) { return true; return true; } } return false; return false; } catch (IOException e) { throw new IllegalArgumentException( "Failed to determine if " + docId + " should block from tree " + ": " + e); } } private boolean isOnRemovableUsbStorage(@NonNull String documentId) { final RootInfo rootInfo; try { rootInfo = getRootFromDocId(documentId); } catch (FileNotFoundException e) { Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"'); return false; } } return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0; } @NonNull @Override @Override protected String getDocIdForFile(File file) throws FileNotFoundException { protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException { return getDocIdForFileMaybeCreate(file, false); return getDocIdForFileMaybeCreate(file, false); } } private String getDocIdForFileMaybeCreate(File file, boolean createNewDir) @NonNull private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir) throws FileNotFoundException { throws FileNotFoundException { String path = file.getAbsolutePath(); String path = file.getAbsolutePath(); Loading Loading @@ -409,26 +450,30 @@ public class ExternalStorageProvider extends FileSystemProvider { private File getFileForDocId(String docId, boolean visible, boolean mustExist) private File getFileForDocId(String docId, boolean visible, boolean mustExist) throws FileNotFoundException { throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); RootInfo root = getRootFromDocId(docId); return buildFile(root, docId, visible, mustExist); return buildFile(root, docId, mustExist); } } private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException { throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); RootInfo root = getRootFromDocId(docId); return Pair.create(root, buildFile(root, docId, visible, true)); return Pair.create(root, buildFile(root, docId, /* mustExist */ true)); } } @VisibleForTesting @VisibleForTesting static String getPathFromDocId(String docId) { static String getPathFromDocId(String docId) { final int splitIndex = docId.indexOf(':', 1); final int splitIndex = docId.indexOf(':', 1); final String path = docId.substring(splitIndex + 1); final String docIdPath = docId.substring(splitIndex + 1); if (path.isEmpty()) { // Canonicalize path and strip the leading "/" return path; final String path; try { path = new File(docIdPath).getCanonicalPath().substring(1); } catch (IOException e) { Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"'); return ""; } } // remove trailing "/" // Remove the trailing "/" as well. if (path.charAt(path.length() - 1) == '/') { if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') { return path.substring(0, path.length() - 1); return path.substring(0, path.length() - 1); } else { } else { return path; return path; Loading @@ -450,7 +495,7 @@ public class ExternalStorageProvider extends FileSystemProvider { return root; return root; } } private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist) private File buildFile(RootInfo root, String docId, boolean mustExist) throws FileNotFoundException { throws FileNotFoundException { final int splitIndex = docId.indexOf(':', 1); final int splitIndex = docId.indexOf(':', 1); final String path = docId.substring(splitIndex + 1); final String path = docId.substring(splitIndex + 1); Loading Loading @@ -529,7 +574,7 @@ public class ExternalStorageProvider extends FileSystemProvider { @Override @Override public Path findDocumentPath(@Nullable String parentDocId, String childDocId) public Path findDocumentPath(@Nullable String parentDocId, String childDocId) throws FileNotFoundException { throws FileNotFoundException { final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false); final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId); final RootInfo root = resolvedDocId.first; final RootInfo root = resolvedDocId.first; File child = resolvedDocId.second; File child = resolvedDocId.second; Loading Loading @@ -633,6 +678,13 @@ public class ExternalStorageProvider extends FileSystemProvider { } } } } /** * Print the state into the given stream. * Gets invoked when you run: * <pre> * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider * </pre> */ @Override @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); Loading Loading @@ -716,4 +768,8 @@ public class ExternalStorageProvider extends FileSystemProvider { } } return bundle; return bundle; } } private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) { return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT)); } } } packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +5 −0 Original line number Original line Diff line number Diff line Loading @@ -220,6 +220,11 @@ public class TileLifecycleManager extends BroadcastReceiver implements handlePendingMessages(); handlePendingMessages(); } } @Override public void onNullBinding(ComponentName name) { setBindService(false); } @Override @Override public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(ComponentName name) { if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); Loading Loading
core/java/android/content/Context.java +9 −0 Original line number Original line Diff line number Diff line Loading @@ -242,6 +242,7 @@ public abstract class Context { BIND_IMPORTANT, BIND_IMPORTANT, BIND_ADJUST_WITH_ACTIVITY, BIND_ADJUST_WITH_ACTIVITY, BIND_NOT_PERCEPTIBLE, BIND_NOT_PERCEPTIBLE, BIND_DENY_ACTIVITY_STARTS, BIND_INCLUDE_CAPABILITIES BIND_INCLUDE_CAPABILITIES }) }) @Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE) Loading Loading @@ -355,6 +356,14 @@ public abstract class Context { /*********** Public flags above this line ***********/ /*********** Public flags above this line ***********/ /*********** Hidden flags below this line ***********/ /*********** Hidden flags below this line ***********/ /** * Flag for {@link #bindService}: If binding from an app that is visible, the bound service is * allowed to start an activity from background. Add a flag so that this behavior can be opted * out. * @hide */ public static final int BIND_DENY_ACTIVITY_STARTS = 0X000004000; /** /** * Flag for {@link #bindService}: This flag is intended to be used only by the system to adjust * Flag for {@link #bindService}: This flag is intended to be used only by the system to adjust * the scheduling policy for IMEs (and any other out-of-process user-visible components that * the scheduling policy for IMEs (and any other out-of-process user-visible components that Loading
core/java/com/android/internal/content/FileSystemProvider.java +92 −76 Original line number Original line Diff line number Diff line Loading @@ -63,13 +63,13 @@ import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayDeque; import java.util.Arrays; import java.util.Arrays; import java.util.LinkedList; import java.util.LinkedList; import java.util.List; import java.util.List; 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.function.Predicate; 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 @@ -87,6 +87,8 @@ public abstract class FileSystemProvider extends DocumentsProvider { DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER, DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER, DocumentsContract.QUERY_ARG_MIME_TYPES); DocumentsContract.QUERY_ARG_MIME_TYPES); private static final int MAX_RESULTS_NUMBER = 23; private static String joinNewline(String... args) { private static String joinNewline(String... args) { return TextUtils.join("\n", args); return TextUtils.join("\n", args); } } Loading Loading @@ -394,56 +396,53 @@ public abstract class FileSystemProvider extends DocumentsProvider { } } /** /** * This method is similar to * WARNING: this method should really be {@code final}, but for the backward compatibility it's * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns * not; new classes that extend {@link FileSystemProvider} should override * all children documents including hidden directories/files. * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method. * * <p> * In a scoped storage world, access to "Android/data" style directories are hidden for privacy * reasons. This method may show privacy sensitive data, so its usage should only be in * restricted modes. * * @param parentDocumentId the directory to return children for. * @param projection list of {@link Document} columns to put into the * cursor. If {@code null} all supported columns should be * included. * @param sortOrder how to order the rows, formatted as an SQL * {@code ORDER BY} clause (excluding the ORDER BY itself). * Passing {@code null} will use the default sort order, which * may be unordered. This ordering is a hint that can be used to * prioritize how data is fetched from the network, but UI may * always enforce a specific ordering * @throws FileNotFoundException when parent document doesn't exist or query fails */ */ protected Cursor queryChildDocumentsShowAll( @Override String parentDocumentId, String[] projection, String sortOrder) public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder) throws FileNotFoundException { throws FileNotFoundException { return queryChildDocuments(parentDocumentId, projection, sortOrder, File -> true); return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ false); } } /** * This method is similar to {@link #queryChildDocuments(String, String[], String)}, however, it * could return <b>all</b> content of the directory, <b>including restricted (hidden) * directories and files</b>. * <p> * In the scoped storage world, some directories and files (e.g. {@code Android/data/} and * {@code Android/obb/} on the external storage) are hidden for privacy reasons. * Hence, this method may reveal privacy-sensitive data, thus should be used with extra care. */ @Override @Override public Cursor queryChildDocuments( public final Cursor queryChildDocumentsForManage(String documentId, String[] projection, String parentDocumentId, String[] projection, String sortOrder) String sortOrder) throws FileNotFoundException { throws FileNotFoundException { return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true); // Access to some directories is hidden for privacy reasons. return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow); } } private Cursor queryChildDocuments( protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder, String parentDocumentId, String[] projection, String sortOrder, boolean includeHidden) throws FileNotFoundException { @NonNull Predicate<File> filter) throws FileNotFoundException { final File parent = getFileForDocId(documentId); final File parent = getFileForDocId(parentDocumentId); final MatrixCursor result = new DirectoryCursor( final MatrixCursor result = new DirectoryCursor( resolveProjection(projection), parentDocumentId, parent); resolveProjection(projection), documentId, parent); if (parent.isDirectory()) { for (File file : FileUtils.listFilesOrEmpty(parent)) { if (!parent.isDirectory()) { if (filter.test(file)) { Log.w(TAG, '"' + documentId + "\" is not a directory"); includeFile(result, null, file); return result; } } if (!includeHidden && shouldHideDocument(documentId)) { Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden"); return result; } } } else { Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory"); for (File file : FileUtils.listFilesOrEmpty(parent)) { if (!includeHidden && shouldHideDocument(file)) continue; includeFile(result, null, file); } } return result; return result; } } Loading @@ -465,23 +464,29 @@ public abstract class FileSystemProvider extends DocumentsProvider { * * * @see ContentResolver#EXTRA_HONORED_ARGS * @see ContentResolver#EXTRA_HONORED_ARGS */ */ protected final Cursor querySearchDocuments( protected final Cursor querySearchDocuments(File folder, String[] projection, File folder, String[] projection, Set<String> exclusion, Bundle queryArgs) Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException { throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveProjection(projection)); final MatrixCursor result = new MatrixCursor(resolveProjection(projection)); final LinkedList<File> pending = new LinkedList<>(); pending.add(folder); // We'll be a running a BFS here. while (!pending.isEmpty() && result.getCount() < 24) { final Queue<File> pending = new ArrayDeque<>(); final File file = pending.removeFirst(); pending.offer(folder); if (shouldHide(file)) continue; while (!pending.isEmpty() && result.getCount() < MAX_RESULTS_NUMBER) { final File file = pending.poll(); // Skip hidden documents (both files and directories) if (shouldHideDocument(file)) continue; if (file.isDirectory()) { if (file.isDirectory()) { for (File child : FileUtils.listFilesOrEmpty(file)) { for (File child : FileUtils.listFilesOrEmpty(file)) { pending.add(child); pending.offer(child); } } } } if (!exclusion.contains(file.getAbsolutePath()) && matchSearchQueryArguments(file, queryArgs)) { if (exclusion.contains(file.getAbsolutePath())) continue; if (matchSearchQueryArguments(file, queryArgs)) { includeFile(result, null, file); includeFile(result, null, file); } } } } Loading Loading @@ -600,26 +605,23 @@ 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); int flags = 0; int flags = 0; if (file.canWrite()) { if (file.canWrite()) { if (mimeType.equals(Document.MIME_TYPE_DIR)) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_RENAME; flags |= Document.FLAG_SUPPORTS_RENAME; flags |= Document.FLAG_SUPPORTS_MOVE; flags |= Document.FLAG_SUPPORTS_MOVE; if (isDir) { if (shouldBlockFromTree(docId)) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; } } else { } else { flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_RENAME; flags |= Document.FLAG_SUPPORTS_MOVE; } } } } if (isDir && shouldBlockDirectoryFromTree(docId)) { flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; } if (mimeType.startsWith("image/")) { if (mimeType.startsWith("image/")) { flags |= Document.FLAG_SUPPORTS_THUMBNAIL; flags |= Document.FLAG_SUPPORTS_THUMBNAIL; } } Loading Loading @@ -652,22 +654,36 @@ public abstract class FileSystemProvider extends DocumentsProvider { return row; return row; } } private static final Pattern PATTERN_HIDDEN_PATH = Pattern.compile( "(?i)^/storage/[^/]+/(?:[0-9]+/)?Android/(?:data|obb|sandbox)$"); /** /** * In a scoped storage world, access to "Android/data" style directories are * Some providers may want to restrict access to certain directories and files, * hidden for privacy reasons. * 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 shouldHide(@NonNull File file) { protected boolean shouldHideDocument(@NonNull String documentId) return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches()); throws FileNotFoundException { return false; } } private boolean shouldShow(@NonNull File file) { /** return !shouldHide(file); * A variant of the {@link #shouldHideDocument(String)} that takes a {@link File} instead of * a {@link String} {@code documentId}. * * @see #shouldHideDocument(String) */ protected final boolean shouldHideDocument(@NonNull File document) throws FileNotFoundException { return shouldHideDocument(getDocIdForFile(document)); } } protected boolean shouldBlockFromTree(@NonNull String docId) { /** * @return if the directory that should be blocked from being selected when the user launches * an {@link Intent#ACTION_OPEN_DOCUMENT_TREE} intent. * * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE */ protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) throws FileNotFoundException { return false; return false; } } Loading
core/proto/android/server/activitymanagerservice.proto +1 −0 Original line number Original line Diff line number Diff line Loading @@ -524,6 +524,7 @@ message ConnectionRecordProto { DEAD = 15; DEAD = 15; NOT_PERCEPTIBLE = 16; NOT_PERCEPTIBLE = 16; INCLUDE_CAPABILITIES = 17; INCLUDE_CAPABILITIES = 17; DENY_ACTIVITY_STARTS = 18; } } repeated Flag flags = 3; repeated Flag flags = 3; optional string service_name = 4; optional string service_name = 4; Loading
packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +109 −53 Original line number Original line Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.externalstorage; package com.android.externalstorage; import static java.util.regex.Pattern.CASE_INSENSITIVE; 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 @@ -61,9 +63,22 @@ import java.io.IOException; import java.io.PrintWriter; import java.io.PrintWriter; import java.util.Collections; import java.util.Collections; import java.util.List; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Objects; import java.util.UUID; import java.util.UUID; import java.util.regex.Pattern; /** * Presents content of the shared (a.k.a. "external") storage. * <p> * Starting with Android 11 (R), restricts access to the certain sections of the shared storage: * {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/}, that will be hidden in * the DocumentsUI by default. * See <a href="https://developer.android.com/about/versions/11/privacy/storage"> * Storage updates in Android 11</a>. * <p> * Documents ID format: {@code root:path/to/file}. */ public class ExternalStorageProvider extends FileSystemProvider { public class ExternalStorageProvider extends FileSystemProvider { private static final String TAG = "ExternalStorage"; private static final String TAG = "ExternalStorage"; Loading @@ -74,7 +89,12 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final Uri BASE_URI = private static final Uri BASE_URI = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build(); // docId format: root:path/to/file /** * Regex for detecting {@code /Android/data/}, {@code /Android/obb/} and * {@code /Android/sandbox/} along with all their subdirectories and content. */ private static final Pattern PATTERN_RESTRICTED_ANDROID_SUBTREES = Pattern.compile("^Android/(?:data|obb|sandbox)(?:/.+)?", CASE_INSENSITIVE); private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, Loading Loading @@ -276,70 +296,91 @@ public class ExternalStorageProvider extends FileSystemProvider { return projection != null ? projection : DEFAULT_ROOT_PROJECTION; return projection != null ? projection : DEFAULT_ROOT_PROJECTION; } } /** * Mark {@code Android/data/}, {@code Android/obb/} and {@code Android/sandbox/} on the * integrated shared ("external") storage along with all their content and subdirectories as * hidden. */ @Override @Override public Cursor queryChildDocumentsForManage( protected boolean shouldHideDocument(@NonNull String documentId) { String parentDocId, String[] projection, String sortOrder) // Don't need to hide anything on USB drives. throws FileNotFoundException { if (isOnRemovableUsbStorage(documentId)) { return queryChildDocumentsShowAll(parentDocId, projection, sortOrder); return false; } final String path = getPathFromDocId(documentId); return PATTERN_RESTRICTED_ANDROID_SUBTREES.matcher(path).matches(); } } /** /** * Check that the directory is the root of storage or blocked file from tree. * Check that the directory is the root of storage or blocked file from tree. * <p> * Note, that this is different from hidden documents: blocked documents <b>WILL</b> appear * the UI, but the user <b>WILL NOT</b> be able to select them. * * * @param docId the docId of the directory to be checked * @param documentId the docId of the directory to be checked * @return true, should be blocked from tree. Otherwise, false. * @return true, should be blocked from tree. Otherwise, false. * * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE */ */ @Override @Override protected boolean shouldBlockFromTree(@NonNull String docId) { protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) try { throws FileNotFoundException { final File dir = getFileForDocId(docId, false /* visible */); final File dir = getFileForDocId(documentId, false); // The file is null or it is not a directory // the file is null or it is not a directory if (dir == null || !dir.isDirectory()) { if (dir == null || !dir.isDirectory()) { return false; return false; } } // Allow all directories on USB, including the root. // Allow all directories on USB, including the root. try { if (isOnRemovableUsbStorage(documentId)) { RootInfo rootInfo = getRootFromDocId(docId); if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { return false; return false; } } } catch (FileNotFoundException e) { Log.e(TAG, "Failed to determine rootInfo for docId"); } final String path = getPathFromDocId(docId); // Get canonical(!) path. Note that this path will have neither leading nor training "/". // This the root's path will be just an empty string. final String path = getPathFromDocId(documentId); // Block the root of the storage // Block the root of the storage if (path.isEmpty()) { if (path.isEmpty()) { return true; return true; } } // Block Download folder from tree // Block /Download/ and /Android/ folders from the tree. if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(), if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) || path.toLowerCase())) { equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) { return true; return true; } } if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(), // This shouldn't really make a difference, but just in case - let's block hidden path.toLowerCase())) { // directories as well. if (shouldHideDocument(documentId)) { return true; return true; } } return false; return false; } catch (IOException e) { throw new IllegalArgumentException( "Failed to determine if " + docId + " should block from tree " + ": " + e); } } private boolean isOnRemovableUsbStorage(@NonNull String documentId) { final RootInfo rootInfo; try { rootInfo = getRootFromDocId(documentId); } catch (FileNotFoundException e) { Log.e(TAG, "Failed to determine rootInfo for docId\"" + documentId + '"'); return false; } } return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0; } @NonNull @Override @Override protected String getDocIdForFile(File file) throws FileNotFoundException { protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException { return getDocIdForFileMaybeCreate(file, false); return getDocIdForFileMaybeCreate(file, false); } } private String getDocIdForFileMaybeCreate(File file, boolean createNewDir) @NonNull private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir) throws FileNotFoundException { throws FileNotFoundException { String path = file.getAbsolutePath(); String path = file.getAbsolutePath(); Loading Loading @@ -409,26 +450,30 @@ public class ExternalStorageProvider extends FileSystemProvider { private File getFileForDocId(String docId, boolean visible, boolean mustExist) private File getFileForDocId(String docId, boolean visible, boolean mustExist) throws FileNotFoundException { throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); RootInfo root = getRootFromDocId(docId); return buildFile(root, docId, visible, mustExist); return buildFile(root, docId, mustExist); } } private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException { throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); RootInfo root = getRootFromDocId(docId); return Pair.create(root, buildFile(root, docId, visible, true)); return Pair.create(root, buildFile(root, docId, /* mustExist */ true)); } } @VisibleForTesting @VisibleForTesting static String getPathFromDocId(String docId) { static String getPathFromDocId(String docId) { final int splitIndex = docId.indexOf(':', 1); final int splitIndex = docId.indexOf(':', 1); final String path = docId.substring(splitIndex + 1); final String docIdPath = docId.substring(splitIndex + 1); if (path.isEmpty()) { // Canonicalize path and strip the leading "/" return path; final String path; try { path = new File(docIdPath).getCanonicalPath().substring(1); } catch (IOException e) { Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"'); return ""; } } // remove trailing "/" // Remove the trailing "/" as well. if (path.charAt(path.length() - 1) == '/') { if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') { return path.substring(0, path.length() - 1); return path.substring(0, path.length() - 1); } else { } else { return path; return path; Loading @@ -450,7 +495,7 @@ public class ExternalStorageProvider extends FileSystemProvider { return root; return root; } } private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist) private File buildFile(RootInfo root, String docId, boolean mustExist) throws FileNotFoundException { throws FileNotFoundException { final int splitIndex = docId.indexOf(':', 1); final int splitIndex = docId.indexOf(':', 1); final String path = docId.substring(splitIndex + 1); final String path = docId.substring(splitIndex + 1); Loading Loading @@ -529,7 +574,7 @@ public class ExternalStorageProvider extends FileSystemProvider { @Override @Override public Path findDocumentPath(@Nullable String parentDocId, String childDocId) public Path findDocumentPath(@Nullable String parentDocId, String childDocId) throws FileNotFoundException { throws FileNotFoundException { final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false); final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId); final RootInfo root = resolvedDocId.first; final RootInfo root = resolvedDocId.first; File child = resolvedDocId.second; File child = resolvedDocId.second; Loading Loading @@ -633,6 +678,13 @@ public class ExternalStorageProvider extends FileSystemProvider { } } } } /** * Print the state into the given stream. * Gets invoked when you run: * <pre> * adb shell dumpsys activity provider com.android.externalstorage/.ExternalStorageProvider * </pre> */ @Override @Override public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); Loading Loading @@ -716,4 +768,8 @@ public class ExternalStorageProvider extends FileSystemProvider { } } return bundle; return bundle; } } private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) { return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT)); } } }
packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +5 −0 Original line number Original line Diff line number Diff line Loading @@ -220,6 +220,11 @@ public class TileLifecycleManager extends BroadcastReceiver implements handlePendingMessages(); handlePendingMessages(); } } @Override public void onNullBinding(ComponentName name) { setBindService(false); } @Override @Override public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(ComponentName name) { if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); Loading