diff options
15 files changed, 377 insertions, 204 deletions
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 9d9cc470504a..71185d053dbe 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -250,6 +250,7 @@ public abstract class Context { BIND_IMPORTANT, BIND_ADJUST_WITH_ACTIVITY, BIND_NOT_PERCEPTIBLE, + BIND_DENY_ACTIVITY_STARTS, BIND_INCLUDE_CAPABILITIES }) @Retention(RetentionPolicy.SOURCE) @@ -371,6 +372,14 @@ public abstract class Context { /*********** 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 only intended to be used by the system to * indicate that a service binding is not considered as real package component usage and should * not generate a {@link android.app.usage.UsageEvents.Event#APP_COMPONENT_USED} event in usage diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java index a60b31078a86..be944d6410e8 100644 --- a/core/java/com/android/internal/content/FileSystemProvider.java +++ b/core/java/com/android/internal/content/FileSystemProvider.java @@ -62,14 +62,14 @@ import java.nio.file.FileVisitor; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayDeque; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.Locale; +import java.util.Queue; import java.util.Set; 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 @@ -87,6 +87,8 @@ public abstract class FileSystemProvider extends DocumentsProvider { DocumentsContract.QUERY_ARG_LAST_MODIFIED_AFTER, DocumentsContract.QUERY_ARG_MIME_TYPES); + private static final int MAX_RESULTS_NUMBER = 23; + private static String joinNewline(String... args) { return TextUtils.join("\n", args); } @@ -373,56 +375,53 @@ public abstract class FileSystemProvider extends DocumentsProvider { } /** - * This method is similar to - * {@link DocumentsProvider#queryChildDocuments(String, String[], String)}. This method returns - * all children documents including hidden directories/files. - * - * <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 + * WARNING: this method should really be {@code final}, but for the backward compatibility it's + * not; new classes that extend {@link FileSystemProvider} should override + * {@link #queryChildDocuments(String, String[], String, boolean)}, not this method. */ - protected Cursor queryChildDocumentsShowAll( - String parentDocumentId, String[] projection, String sortOrder) + @Override + public Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder) 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 - public Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder) - throws FileNotFoundException { - // Access to some directories is hidden for privacy reasons. - return queryChildDocuments(parentDocumentId, projection, sortOrder, this::shouldShow); + public final Cursor queryChildDocumentsForManage(String documentId, String[] projection, + String sortOrder) throws FileNotFoundException { + return queryChildDocuments(documentId, projection, sortOrder, /* includeHidden */ true); } - private Cursor queryChildDocuments( - String parentDocumentId, String[] projection, String sortOrder, - @NonNull Predicate<File> filter) throws FileNotFoundException { - final File parent = getFileForDocId(parentDocumentId); + protected Cursor queryChildDocuments(String documentId, String[] projection, String sortOrder, + boolean includeHidden) throws FileNotFoundException { + final File parent = getFileForDocId(documentId); final MatrixCursor result = new DirectoryCursor( - resolveProjection(projection), parentDocumentId, parent); - if (parent.isDirectory()) { - for (File file : FileUtils.listFilesOrEmpty(parent)) { - if (filter.test(file)) { - includeFile(result, null, file); - } - } - } else { - Log.w(TAG, "parentDocumentId '" + parentDocumentId + "' is not Directory"); + resolveProjection(projection), documentId, parent); + + if (!parent.isDirectory()) { + Log.w(TAG, '"' + documentId + "\" is not a directory"); + return result; + } + + if (!includeHidden && shouldHideDocument(documentId)) { + Log.w(TAG, "Queried directory \"" + documentId + "\" is hidden"); + return result; } + + for (File file : FileUtils.listFilesOrEmpty(parent)) { + if (!includeHidden && shouldHideDocument(file)) continue; + + includeFile(result, null, file); + } + return result; } @@ -444,23 +443,29 @@ public abstract class FileSystemProvider extends DocumentsProvider { * * @see ContentResolver#EXTRA_HONORED_ARGS */ - protected final Cursor querySearchDocuments( - File folder, String[] projection, Set<String> exclusion, Bundle queryArgs) - throws FileNotFoundException { + protected final Cursor querySearchDocuments(File folder, String[] projection, + Set<String> exclusion, Bundle queryArgs) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveProjection(projection)); - final LinkedList<File> pending = new LinkedList<>(); - pending.add(folder); - while (!pending.isEmpty() && result.getCount() < 24) { - final File file = pending.removeFirst(); - if (shouldHide(file)) continue; + + // We'll be a running a BFS here. + final Queue<File> pending = new ArrayDeque<>(); + pending.offer(folder); + + 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()) { 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); } } @@ -604,26 +609,23 @@ public abstract class FileSystemProvider extends DocumentsProvider { final int flagIndex = ArrayUtils.indexOf(columns, Document.COLUMN_FLAGS); if (flagIndex != -1) { + final boolean isDir = mimeType.equals(Document.MIME_TYPE_DIR); int flags = 0; if (file.canWrite()) { - if (mimeType.equals(Document.MIME_TYPE_DIR)) { + flags |= Document.FLAG_SUPPORTS_DELETE; + flags |= Document.FLAG_SUPPORTS_RENAME; + flags |= Document.FLAG_SUPPORTS_MOVE; + if (isDir) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; - flags |= Document.FLAG_SUPPORTS_DELETE; - flags |= Document.FLAG_SUPPORTS_RENAME; - flags |= Document.FLAG_SUPPORTS_MOVE; - - if (shouldBlockFromTree(docId)) { - flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE; - } - } else { 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/")) { flags |= Document.FLAG_SUPPORTS_THUMBNAIL; } @@ -656,22 +658,36 @@ public abstract class FileSystemProvider extends DocumentsProvider { 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 - * hidden for privacy reasons. + * 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 shouldHide(@NonNull File file) { - return (PATTERN_HIDDEN_PATH.matcher(file.getAbsolutePath()).matches()); + protected boolean shouldHideDocument(@NonNull String documentId) + 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; } diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index 17dc4589d402..24d2c90b8215 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -524,6 +524,7 @@ message ConnectionRecordProto { DEAD = 15; NOT_PERCEPTIBLE = 16; INCLUDE_CAPABILITIES = 17; + DENY_ACTIVITY_STARTS = 18; } repeated Flag flags = 3; optional string service_name = 4; diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 801b490406cc..992de22f4265 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,6 +16,8 @@ package com.android.externalstorage; +import static java.util.regex.Pattern.CASE_INSENSITIVE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.usage.StorageStatsManager; @@ -61,9 +63,22 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Objects; 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 { private static final String TAG = "ExternalStorage"; @@ -74,7 +89,12 @@ public class ExternalStorageProvider extends FileSystemProvider { private static final Uri BASE_URI = 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[] { Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, @@ -276,70 +296,91 @@ public class ExternalStorageProvider extends FileSystemProvider { 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 - public Cursor queryChildDocumentsForManage( - String parentDocId, String[] projection, String sortOrder) - throws FileNotFoundException { - return queryChildDocumentsShowAll(parentDocId, projection, sortOrder); + protected boolean shouldHideDocument(@NonNull String documentId) { + // Don't need to hide anything on USB drives. + if (isOnRemovableUsbStorage(documentId)) { + 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. + * <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. + * + * @see Document#FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE */ @Override - protected boolean shouldBlockFromTree(@NonNull String docId) { - try { - final File dir = getFileForDocId(docId, false /* visible */); + protected boolean shouldBlockDirectoryFromTree(@NonNull String documentId) + throws FileNotFoundException { + final File dir = getFileForDocId(documentId, false); + // The file is null or it is not a directory + if (dir == null || !dir.isDirectory()) { + return false; + } - // the file is null or it is not a directory - if (dir == null || !dir.isDirectory()) { - return false; - } + // Allow all directories on USB, including the root. + if (isOnRemovableUsbStorage(documentId)) { + return false; + } - // Allow all directories on USB, including the root. - try { - RootInfo rootInfo = getRootFromDocId(docId); - if ((rootInfo.flags & Root.FLAG_REMOVABLE_USB) == Root.FLAG_REMOVABLE_USB) { - return false; - } - } catch (FileNotFoundException e) { - Log.e(TAG, "Failed to determine rootInfo for 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); - final String path = getPathFromDocId(docId); + // Block the root of the storage + if (path.isEmpty()) { + return true; + } - // Block the root of the storage - if (path.isEmpty()) { - return true; - } + // Block /Download/ and /Android/ folders from the tree. + if (equalIgnoringCase(path, Environment.DIRECTORY_DOWNLOADS) || + equalIgnoringCase(path, Environment.DIRECTORY_ANDROID)) { + return true; + } - // Block Download folder from tree - if (TextUtils.equals(Environment.DIRECTORY_DOWNLOADS.toLowerCase(), - path.toLowerCase())) { - return true; - } + // This shouldn't really make a difference, but just in case - let's block hidden + // directories as well. + if (shouldHideDocument(documentId)) { + return true; + } - if (TextUtils.equals(Environment.DIRECTORY_ANDROID.toLowerCase(), - path.toLowerCase())) { - return true; - } + return false; + } + 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; - } catch (IOException e) { - throw new IllegalArgumentException( - "Failed to determine if " + docId + " should block from tree " + ": " + e); } + + return (rootInfo.flags & Root.FLAG_REMOVABLE_USB) != 0; } + @NonNull @Override - protected String getDocIdForFile(File file) throws FileNotFoundException { + protected String getDocIdForFile(@NonNull File file) throws FileNotFoundException { return getDocIdForFileMaybeCreate(file, false); } - private String getDocIdForFileMaybeCreate(File file, boolean createNewDir) + @NonNull + private String getDocIdForFileMaybeCreate(@NonNull File file, boolean createNewDir) throws FileNotFoundException { String path = file.getAbsolutePath(); @@ -409,26 +450,30 @@ public class ExternalStorageProvider extends FileSystemProvider { private File getFileForDocId(String docId, boolean visible, boolean mustExist) throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); - return buildFile(root, docId, visible, mustExist); + return buildFile(root, docId, mustExist); } - private Pair<RootInfo, File> resolveDocId(String docId, boolean visible) - throws FileNotFoundException { + private Pair<RootInfo, File> resolveDocId(String docId) throws FileNotFoundException { RootInfo root = getRootFromDocId(docId); - return Pair.create(root, buildFile(root, docId, visible, true)); + return Pair.create(root, buildFile(root, docId, /* mustExist */ true)); } @VisibleForTesting static String getPathFromDocId(String docId) { final int splitIndex = docId.indexOf(':', 1); - final String path = docId.substring(splitIndex + 1); + final String docIdPath = docId.substring(splitIndex + 1); - if (path.isEmpty()) { - return path; + // Canonicalize path and strip the leading "/" + final String path; + try { + path = new File(docIdPath).getCanonicalPath().substring(1); + } catch (IOException e) { + Log.w(TAG, "Could not canonicalize \"" + docIdPath + '"'); + return ""; } - // remove trailing "/" - if (path.charAt(path.length() - 1) == '/') { + // Remove the trailing "/" as well. + if (!path.isEmpty() && path.charAt(path.length() - 1) == '/') { return path.substring(0, path.length() - 1); } else { return path; @@ -450,7 +495,7 @@ public class ExternalStorageProvider extends FileSystemProvider { return root; } - private File buildFile(RootInfo root, String docId, boolean visible, boolean mustExist) + private File buildFile(RootInfo root, String docId, boolean mustExist) throws FileNotFoundException { final int splitIndex = docId.indexOf(':', 1); final String path = docId.substring(splitIndex + 1); @@ -529,7 +574,7 @@ public class ExternalStorageProvider extends FileSystemProvider { @Override public Path findDocumentPath(@Nullable String parentDocId, String childDocId) throws FileNotFoundException { - final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId, false); + final Pair<RootInfo, File> resolvedDocId = resolveDocId(childDocId); final RootInfo root = resolvedDocId.first; File child = resolvedDocId.second; @@ -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 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ", 160); @@ -716,4 +768,8 @@ public class ExternalStorageProvider extends FileSystemProvider { } return bundle; } + + private static boolean equalIgnoringCase(@NonNull String a, @NonNull String b) { + return TextUtils.equals(a.toLowerCase(Locale.ROOT), b.toLowerCase(Locale.ROOT)); + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java index ab7b54d98285..beadd821957b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java +++ b/packages/SettingsLib/src/com/android/settingslib/dream/DreamBackend.java @@ -351,7 +351,17 @@ public class DreamBackend { if (cn != null && cn.indexOf('/') < 0) { cn = resolveInfo.serviceInfo.packageName + "/" + cn; } - return cn == null ? null : ComponentName.unflattenFromString(cn); + // Ensure that the component is from the same package as the dream service. If not, + // treat the component as invalid and return null instead. + final ComponentName result = cn != null ? ComponentName.unflattenFromString(cn) : null; + if (result != null + && !result.getPackageName().equals(resolveInfo.serviceInfo.packageName)) { + Log.w(TAG, + "Inconsistent package name in component: " + result.getPackageName() + + ", should be: " + resolveInfo.serviceInfo.packageName); + return null; + } + return result; } private static void logd(String msg, Object... args) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java index d262412d5182..6e64b1a593c3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java @@ -233,6 +233,11 @@ public class TileLifecycleManager extends BroadcastReceiver implements } @Override + public void onNullBinding(ComponentName name) { + setBindService(false); + } + + @Override public void onServiceDisconnected(ComponentName name) { if (DEBUG) Log.d(TAG, "onServiceDisconnected " + name); handleDeath(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java index 0a428654f14a..7323a4a40ab9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java @@ -22,13 +22,16 @@ import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; +import android.content.ServiceConnection; import android.content.pm.PackageInfo; import android.content.pm.ServiceInfo; import android.net.Uri; @@ -51,7 +54,7 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; +import org.mockito.ArgumentCaptor; @SmallTest @RunWith(AndroidJUnit4.class) @@ -59,10 +62,10 @@ public class TileLifecycleManagerTest extends SysuiTestCase { private static final int TEST_FAIL_TIMEOUT = 5000; private final PackageManagerAdapter mMockPackageManagerAdapter = - Mockito.mock(PackageManagerAdapter.class); + mock(PackageManagerAdapter.class); private final BroadcastDispatcher mMockBroadcastDispatcher = - Mockito.mock(BroadcastDispatcher.class); - private final IQSTileService.Stub mMockTileService = Mockito.mock(IQSTileService.Stub.class); + mock(BroadcastDispatcher.class); + private final IQSTileService.Stub mMockTileService = mock(IQSTileService.Stub.class); private ComponentName mTileServiceComponentName; private Intent mTileServiceIntent; private UserHandle mUser; @@ -87,7 +90,7 @@ public class TileLifecycleManagerTest extends SysuiTestCase { mThread.start(); mHandler = Handler.createAsync(mThread.getLooper()); mStateManager = new TileLifecycleManager(mHandler, mContext, - Mockito.mock(IQSService.class), new Tile(), + mock(IQSService.class), new Tile(), mTileServiceIntent, mUser, mMockPackageManagerAdapter, @@ -247,4 +250,26 @@ public class TileLifecycleManagerTest extends SysuiTestCase { public void testToggleableTile() throws Exception { assertTrue(mStateManager.isToggleableTile()); } + + @Test + public void testNullBindingCallsUnbind() { + Context mockContext = mock(Context.class); + // Binding has to succeed + when(mockContext.bindServiceAsUser(any(), any(), anyInt(), any())).thenReturn(true); + TileLifecycleManager manager = new TileLifecycleManager(mHandler, mockContext, + mock(IQSService.class), + new Tile(), + mTileServiceIntent, + mUser, + mMockPackageManagerAdapter, + mMockBroadcastDispatcher); + + manager.setBindService(true); + + ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class); + verify(mockContext).bindServiceAsUser(any(), captor.capture(), anyInt(), any()); + + captor.getValue().onNullBinding(mTileServiceComponentName); + verify(mockContext).unbindService(captor.getValue()); + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 123bb6c856f3..43423e25376a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -3663,6 +3663,22 @@ public class ActivityManagerService extends IActivityManager.Stub throw new SecurityException(msg); } + final int callingUid = Binder.getCallingUid(); + final int callingPid = Binder.getCallingPid(); + + ProcessRecord proc; + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(callingPid); + } + if (callingUid >= FIRST_APPLICATION_UID + && (proc == null || !proc.info.isSystemApp())) { + final String msg = "Permission Denial: killAllBackgroundProcesses() from pid=" + + callingPid + ", uid=" + callingUid + " is not allowed"; + Slog.w(TAG, msg); + // Silently return to avoid existing apps from crashing. + return; + } + final long callingId = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -3703,22 +3719,6 @@ public class ActivityManagerService extends IActivityManager.Stub throw new SecurityException(msg); } - final int callingUid = Binder.getCallingUid(); - final int callingPid = Binder.getCallingPid(); - - ProcessRecord proc; - synchronized (mPidsSelfLocked) { - proc = mPidsSelfLocked.get(callingPid); - } - if (callingUid >= FIRST_APPLICATION_UID - && (proc == null || !proc.info.isSystemApp())) { - final String msg = "Permission Denial: killAllBackgroundProcesses() from pid=" - + callingPid + ", uid=" + callingUid + " is not allowed"; - Slog.w(TAG, msg); - // Silently return to avoid existing apps from crashing. - return; - } - final long callingId = Binder.clearCallingIdentity(); try { synchronized (this) { @@ -5006,7 +5006,20 @@ public class ActivityManagerService extends IActivityManager.Stub intent = new Intent(Intent.ACTION_MAIN); } try { - target.send(code, intent, resolvedType, allowlistToken, null, + if (allowlistToken != null) { + final int callingUid = Binder.getCallingUid(); + final String packageName; + final long token = Binder.clearCallingIdentity(); + try { + packageName = AppGlobals.getPackageManager().getNameForUid(callingUid); + } finally { + Binder.restoreCallingIdentity(token); + } + Slog.wtf(TAG, "Send a non-null allowlistToken to a non-PI target." + + " Calling package: " + packageName + "; intent: " + intent + + "; options: " + options); + } + target.send(code, intent, resolvedType, null, null, requiredPermission, options); } catch (RemoteException e) { } diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java index 916127126117..2e2ac4ad1543 100644 --- a/services/core/java/com/android/server/am/ConnectionRecord.java +++ b/services/core/java/com/android/server/am/ConnectionRecord.java @@ -68,6 +68,7 @@ final class ConnectionRecord { Context.BIND_NOT_VISIBLE, Context.BIND_NOT_PERCEPTIBLE, Context.BIND_INCLUDE_CAPABILITIES, + Context.BIND_DENY_ACTIVITY_STARTS, }; private static final int[] BIND_PROTO_ENUMS = new int[] { ConnectionRecordProto.AUTO_CREATE, @@ -87,6 +88,7 @@ final class ConnectionRecord { ConnectionRecordProto.NOT_VISIBLE, ConnectionRecordProto.NOT_PERCEPTIBLE, ConnectionRecordProto.INCLUDE_CAPABILITIES, + ConnectionRecordProto.DENY_ACTIVITY_STARTS, }; void dump(PrintWriter pw, String prefix) { @@ -226,6 +228,9 @@ final class ConnectionRecord { if ((flags & Context.BIND_NOT_PERCEPTIBLE) != 0) { sb.append("!PRCP "); } + if ((flags & Context.BIND_DENY_ACTIVITY_STARTS) != 0) { + sb.append("BALFD "); + } if ((flags & Context.BIND_INCLUDE_CAPABILITIES) != 0) { sb.append("CAPS "); } diff --git a/services/core/java/com/android/server/am/ProcessServiceRecord.java b/services/core/java/com/android/server/am/ProcessServiceRecord.java index 8f77b87f5308..1f689b3ce928 100644 --- a/services/core/java/com/android/server/am/ProcessServiceRecord.java +++ b/services/core/java/com/android/server/am/ProcessServiceRecord.java @@ -23,6 +23,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import com.android.internal.annotations.GuardedBy; +import com.android.server.wm.WindowProcessController; import java.io.PrintWriter; import java.util.ArrayList; @@ -323,19 +324,21 @@ final class ProcessServiceRecord { return mConnections.size(); } - void addBoundClientUid(int clientUid) { + void addBoundClientUid(int clientUid, String clientPackageName, int bindFlags) { mBoundClientUids.add(clientUid); - mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids); + mApp.getWindowProcessController() + .addBoundClientUid(clientUid, clientPackageName, bindFlags); } void updateBoundClientUids() { + clearBoundClientUids(); if (mServices.isEmpty()) { - clearBoundClientUids(); return; } // grab a set of clientUids of all mConnections of all services final ArraySet<Integer> boundClientUids = new ArraySet<>(); final int serviceCount = mServices.size(); + WindowProcessController controller = mApp.getWindowProcessController(); for (int j = 0; j < serviceCount; j++) { final ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns = mServices.valueAt(j).getConnections(); @@ -343,12 +346,13 @@ final class ProcessServiceRecord { for (int conni = 0; conni < size; conni++) { ArrayList<ConnectionRecord> c = conns.valueAt(conni); for (int i = 0; i < c.size(); i++) { - boundClientUids.add(c.get(i).clientUid); + ConnectionRecord cr = c.get(i); + boundClientUids.add(cr.clientUid); + controller.addBoundClientUid(cr.clientUid, cr.clientPackageName, cr.flags); } } } mBoundClientUids = boundClientUids; - mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids); } void addBoundClientUidsOfNewService(ServiceRecord sr) { @@ -359,15 +363,18 @@ final class ProcessServiceRecord { for (int conni = conns.size() - 1; conni >= 0; conni--) { ArrayList<ConnectionRecord> c = conns.valueAt(conni); for (int i = 0; i < c.size(); i++) { - mBoundClientUids.add(c.get(i).clientUid); + ConnectionRecord cr = c.get(i); + mBoundClientUids.add(cr.clientUid); + mApp.getWindowProcessController() + .addBoundClientUid(cr.clientUid, cr.clientPackageName, cr.flags); + } } - mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids); } void clearBoundClientUids() { mBoundClientUids.clear(); - mApp.getWindowProcessController().setBoundClientUids(mBoundClientUids); + mApp.getWindowProcessController().clearBoundClientUids(); } @GuardedBy("mService") diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index 804e442bc8de..b19abb40355f 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -683,7 +683,7 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // if we have a process attached, add bound client uid of this connection to it if (app != null) { - app.mServices.addBoundClientUid(c.clientUid); + app.mServices.addBoundClientUid(c.clientUid, c.clientPackageName, c.flags); } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index badad36565d4..d112864435c5 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -2995,7 +2995,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub if (!mContext.bindServiceAsUser(intent, newConn, Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE - | Context.BIND_INCLUDE_CAPABILITIES, + | Context.BIND_INCLUDE_CAPABILITIES + | Context.BIND_DENY_ACTIVITY_STARTS, new UserHandle(serviceUserId))) { String msg = "Unable to bind service: " + componentName; diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index bcb5e0db15e8..298f5f1b2151 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1325,29 +1325,38 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final long origId = Binder.clearCallingIdentity(); // TODO(b/64750076): Check if calling pid should really be -1. - final int res = getActivityStartController() - .obtainStarter(intent, "startNextMatchingActivity") - .setCaller(r.app.getThread()) - .setResolvedType(r.resolvedType) - .setActivityInfo(aInfo) - .setResultTo(resultTo != null ? resultTo.appToken : null) - .setResultWho(resultWho) - .setRequestCode(requestCode) - .setCallingPid(-1) - .setCallingUid(r.launchedFromUid) - .setCallingPackage(r.launchedFromPackage) - .setCallingFeatureId(r.launchedFromFeatureId) - .setRealCallingPid(-1) - .setRealCallingUid(r.launchedFromUid) - .setActivityOptions(options) - .execute(); - Binder.restoreCallingIdentity(origId); - - r.finishing = wasFinishing; - if (res != ActivityManager.START_SUCCESS) { - return false; + try { + if (options == null) { + options = new SafeActivityOptions(ActivityOptions.makeBasic()); + } + // Fixes b/230492947 + // Prevents background activity launch through #startNextMatchingActivity + // An activity going into the background could still go back to the foreground + // if the intent used matches both: + // - the activity in the background + // - a second activity. + options.getOptions(r).setAvoidMoveToFront(); + final int res = getActivityStartController() + .obtainStarter(intent, "startNextMatchingActivity") + .setCaller(r.app.getThread()) + .setResolvedType(r.resolvedType) + .setActivityInfo(aInfo) + .setResultTo(resultTo != null ? resultTo.appToken : null) + .setResultWho(resultWho) + .setRequestCode(requestCode) + .setCallingPid(-1) + .setCallingUid(r.launchedFromUid) + .setCallingPackage(r.launchedFromPackage) + .setCallingFeatureId(r.launchedFromFeatureId) + .setRealCallingPid(-1) + .setRealCallingUid(r.launchedFromUid) + .setActivityOptions(options) + .execute(); + r.finishing = wasFinishing; + return res == ActivityManager.START_SUCCESS; + } finally { + Binder.restoreCallingIdentity(origId); } - return true; } } diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java index 71a10df34d30..440cc267a8d6 100644 --- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java +++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java @@ -23,11 +23,11 @@ import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.SystemClock; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.IntArray; import android.util.Slog; @@ -59,9 +59,11 @@ class BackgroundLaunchProcessController { @GuardedBy("this") private @Nullable ArrayMap<Binder, IBinder> mBackgroundActivityStartTokens; - /** Set of UIDs of clients currently bound to this process. */ + /** Set of UIDs of clients currently bound to this process and opt in to allow this process to + * launch background activity. + */ @GuardedBy("this") - private @Nullable IntArray mBoundClientUids; + private @Nullable IntArray mBalOptInBoundClientUids; BackgroundLaunchProcessController(@NonNull IntPredicate uidHasActiveVisibleWindowPredicate, @Nullable BackgroundActivityStartCallback callback) { @@ -166,9 +168,9 @@ class BackgroundLaunchProcessController { private boolean isBoundByForegroundUid() { synchronized (this) { - if (mBoundClientUids != null) { - for (int i = mBoundClientUids.size() - 1; i >= 0; i--) { - if (mUidHasActiveVisibleWindowPredicate.test(mBoundClientUids.get(i))) { + if (mBalOptInBoundClientUids != null) { + for (int i = mBalOptInBoundClientUids.size() - 1; i >= 0; i--) { + if (mUidHasActiveVisibleWindowPredicate.test(mBalOptInBoundClientUids.get(i))) { return true; } } @@ -177,19 +179,23 @@ class BackgroundLaunchProcessController { return false; } - void setBoundClientUids(ArraySet<Integer> boundClientUids) { + void clearBalOptInBoundClientUids() { synchronized (this) { - if (boundClientUids == null || boundClientUids.isEmpty()) { - mBoundClientUids = null; - return; - } - if (mBoundClientUids == null) { - mBoundClientUids = new IntArray(); + if (mBalOptInBoundClientUids == null) { + mBalOptInBoundClientUids = new IntArray(); } else { - mBoundClientUids.clear(); + mBalOptInBoundClientUids.clear(); + } + } + } + + void addBoundClientUid(int clientUid, String clientPackageName, int bindFlags) { + if ((bindFlags & Context.BIND_DENY_ACTIVITY_STARTS) == 0) { + if (mBalOptInBoundClientUids == null) { + mBalOptInBoundClientUids = new IntArray(); } - for (int i = boundClientUids.size() - 1; i >= 0; i--) { - mBoundClientUids.add(boundClientUids.valueAt(i)); + if (mBalOptInBoundClientUids.indexOf(clientUid) == -1) { + mBalOptInBoundClientUids.add(clientUid); } } } @@ -255,10 +261,10 @@ class BackgroundLaunchProcessController { pw.println(mBackgroundActivityStartTokens.valueAt(i)); } } - if (mBoundClientUids != null && mBoundClientUids.size() > 0) { + if (mBalOptInBoundClientUids != null && mBalOptInBoundClientUids.size() > 0) { pw.print(prefix); pw.print("BoundClientUids:"); - pw.println(Arrays.toString(mBoundClientUids.toArray())); + pw.println(Arrays.toString(mBalOptInBoundClientUids.toArray())); } } } diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 1364c72e6275..e39b02d5f396 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -532,8 +532,18 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return mBgLaunchController.canCloseSystemDialogsByToken(mUid); } - public void setBoundClientUids(ArraySet<Integer> boundClientUids) { - mBgLaunchController.setBoundClientUids(boundClientUids); + /** + * Clear all bound client Uids. + */ + public void clearBoundClientUids() { + mBgLaunchController.clearBalOptInBoundClientUids(); + } + + /** + * Add bound client Uid. + */ + public void addBoundClientUid(int clientUid, String clientPackageName, int bindFlags) { + mBgLaunchController.addBoundClientUid(clientUid, clientPackageName, bindFlags); } /** |