Loading services/core/java/com/android/server/wm/ActivityStarter.java +54 −39 Original line number Diff line number Diff line Loading @@ -78,6 +78,10 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY; import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_TOP; import android.annotation.NonNull; Loading Loading @@ -131,6 +135,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; import com.android.server.wm.LaunchParamsController.LaunchParams; import com.android.server.wm.TaskFragment.EmbeddingCheckResult; import java.io.PrintWriter; import java.text.DateFormat; Loading Loading @@ -2038,12 +2043,6 @@ class ActivityStarter { } } if (mInTaskFragment != null && !canEmbedActivity(mInTaskFragment, r, newTask, targetTask)) { Slog.e(TAG, "Permission denied: Cannot embed " + r + " to " + mInTaskFragment.getTask() + " targetTask= " + targetTask); return START_PERMISSION_DENIED; } // Do not start the activity if target display's DWPC does not allow it. // We can't return fatal error code here because it will crash the caller of // startActivity() if they don't catch the exception. We don't expect 3P apps to make Loading @@ -2070,19 +2069,21 @@ class ActivityStarter { } /** * Return {@code true} if an activity can be embedded to the TaskFragment. * Returns whether embedding of {@code starting} is allowed. * * @param taskFragment the TaskFragment for embedding. * @param starting the starting activity. * @param newTask whether the starting activity is going to be launched on a new task. * @param targetTask the target task for launching activity, which could be different from * the one who hosting the embedding. */ private boolean canEmbedActivity(@NonNull TaskFragment taskFragment, @NonNull ActivityRecord starting, boolean newTask, Task targetTask) { @VisibleForTesting @EmbeddingCheckResult static int canEmbedActivity(@NonNull TaskFragment taskFragment, @NonNull ActivityRecord starting, @NonNull Task targetTask) { final Task hostTask = taskFragment.getTask(); // Not allowed embedding a separate task or without host task. if (hostTask == null || newTask || targetTask != hostTask) { return false; if (hostTask == null || targetTask != hostTask) { return EMBEDDING_DISALLOWED_NEW_TASK; } return taskFragment.isAllowedToEmbedActivity(starting); Loading Loading @@ -2894,19 +2895,16 @@ class ActivityStarter { mIntentDelivered = true; } /** Places {@link #mStartActivity} in {@code task} or an embedded {@link TaskFragment}. */ private void addOrReparentStartingActivity(@NonNull Task task, String reason) { TaskFragment newParent = task; if (mInTaskFragment != null) { // TODO(b/234351413): remove remaining embedded Task logic. // mInTaskFragment is created and added to the leaf task by task fragment organizer's // request. If the task was resolved and different than mInTaskFragment, reparent the // task to mInTaskFragment for embedding. if (mInTaskFragment.getTask() != task) { if (shouldReparentInTaskFragment(task)) { task.reparent(mInTaskFragment, POSITION_TOP); } } else { int embeddingCheckResult = canEmbedActivity(mInTaskFragment, mStartActivity, task); if (embeddingCheckResult == EMBEDDING_ALLOWED) { newParent = mInTaskFragment; } else { // Start mStartActivity to task instead if it can't be embedded to mInTaskFragment. sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult); } } else { TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null; Loading @@ -2918,20 +2916,12 @@ class ActivityStarter { } } if (candidateTf != null && candidateTf.isEmbedded() && canEmbedActivity(candidateTf, mStartActivity, false /* newTask */, task)) { && canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) { // Use the embedded TaskFragment of the top activity as the new parent if the // activity can be embedded. newParent = candidateTf; } } // Start Activity to the Task if mStartActivity's min dimensions are not satisfied. if (newParent.isEmbedded() && newParent.smallerThanMinDimension(mStartActivity)) { reason += " - MinimumDimensionViolation"; mService.mWindowOrganizerController.sendMinimumDimensionViolation( newParent, mStartActivity.getMinDimensions(), mRequest.errorCallbackToken, reason); newParent = task; } if (mStartActivity.getTaskFragment() == null || mStartActivity.getTaskFragment() == newParent) { newParent.addChild(mStartActivity, POSITION_TOP); Loading @@ -2940,16 +2930,41 @@ class ActivityStarter { } } private boolean shouldReparentInTaskFragment(Task task) { // The task has not been embedded. We should reparent the task to TaskFragment. if (!task.isEmbedded()) { return true; /** * Notifies the client side that {@link #mStartActivity} cannot be embedded to * {@code taskFragment}. */ private void sendCanNotEmbedActivityError(TaskFragment taskFragment, @EmbeddingCheckResult int result) { final String errMsg; switch(result) { case EMBEDDING_DISALLOWED_NEW_TASK: { errMsg = "Cannot embed " + mStartActivity + " that launched on another task" + ",mLaunchMode=" + mLaunchMode + ",mLaunchFlag=" + Integer.toHexString(mLaunchFlags); break; } case EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION: { errMsg = "Cannot embed " + mStartActivity + ". TaskFragment's bounds:" + taskFragment.getBounds() + ", minimum dimensions:" + mStartActivity.getMinDimensions(); break; } case EMBEDDING_DISALLOWED_UNTRUSTED_HOST: { errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity; break; } default: errMsg = "Unhandled embed result:" + result; } if (taskFragment.isOrganized()) { mService.mWindowOrganizerController.sendTaskFragmentOperationFailure( taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken, new SecurityException(errMsg)); } else { // If the taskFragment is not organized, just dump error message as warning logs. Slog.w(TAG, errMsg); } WindowContainer<?> parent = task.getParent(); // If the Activity is going to launch on top of embedded Task in the same TaskFragment, // we don't need to reparent the Task. Otherwise, the embedded Task should reparent to // another TaskFragment. return parent.asTaskFragment() != mInTaskFragment; } private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance, Loading services/core/java/com/android/server/wm/TaskFragment.java +57 −10 Original line number Diff line number Diff line Loading @@ -139,6 +139,45 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** Set to false to disable the preview that is shown while a new activity is being started. */ static final boolean SHOW_APP_STARTING_PREVIEW = true; /** * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: * indicate that an Activity can be embedded successfully. */ static final int EMBEDDING_ALLOWED = 0; /** * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: * indicate that an Activity can't be embedded because either the Activity does not allow * untrusted embedding, and the embedding host app is not trusted. */ static final int EMBEDDING_DISALLOWED_UNTRUSTED_HOST = 1; /** * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: * indicate that an Activity can't be embedded because this taskFragment's bounds are * {@link #smallerThanMinDimension(ActivityRecord)}. */ static final int EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION = 2; /** * An embedding check result of * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: * indicate that an Activity can't be embedded because the Activity is started on a new task. */ static final int EMBEDDING_DISALLOWED_NEW_TASK = 3; /** * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}. */ @IntDef(prefix = {"EMBEDDING_"}, value = { EMBEDDING_ALLOWED, EMBEDDING_DISALLOWED_UNTRUSTED_HOST, EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION, EMBEDDING_DISALLOWED_NEW_TASK, }) @interface EmbeddingCheckResult {} /** * Indicate that the minimal width/height should use the default value. * Loading Loading @@ -520,20 +559,29 @@ class TaskFragment extends WindowContainer<WindowContainer> { return false; } boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) { @EmbeddingCheckResult int isAllowedToEmbedActivity(@NonNull ActivityRecord a) { return isAllowedToEmbedActivity(a, mTaskFragmentOrganizerUid); } /** * Checks if the organized task fragment is allowed to have the specified activity, which is * allowed if an activity allows embedding in untrusted mode, or if the trusted mode can be * enabled. * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) * allowed if an activity allows embedding in untrusted mode, if the trusted mode can be * enabled, or if the organized task fragment bounds are not * {@link #smallerThanMinDimension(ActivityRecord)}. * * @param uid uid of the TaskFragment organizer. * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) */ boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) { return isAllowedToEmbedActivityInUntrustedMode(a) || isAllowedToEmbedActivityInTrustedMode(a, uid); @EmbeddingCheckResult int isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) { if (!isAllowedToEmbedActivityInUntrustedMode(a) && !isAllowedToEmbedActivityInTrustedMode(a, uid)) { return EMBEDDING_DISALLOWED_UNTRUSTED_HOST; } else if (smallerThanMinDimension(a)) { return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; } return EMBEDDING_ALLOWED; } boolean smallerThanMinDimension(@NonNull ActivityRecord activity) { Loading @@ -550,9 +598,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { } final int minWidth = minDimensions.x; final int minHeight = minDimensions.y; final boolean smaller = taskFragBounds.width() < minWidth return taskFragBounds.width() < minWidth || taskFragBounds.height() < minHeight; return smaller; } /** Loading Loading @@ -609,7 +656,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { // The system is trusted to embed other apps securely and for all users. return UserHandle.getAppId(uid) == SYSTEM_UID // Activities from the same UID can be embedded freely by the host. || uid == a.getUid(); || a.isUid(uid); } /** Loading services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +2 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.window.TaskFragmentOrganizer.putExceptionInBundle; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; import android.annotation.IntDef; Loading Loading @@ -235,7 +236,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr + " is not in a task belong to the organizer app."); return; } if (!task.isAllowedToEmbedActivity(activity, mOrganizerUid)) { if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) { Slog.d(TAG, "Reparent activity=" + activity.token + " is not allowed to be embedded."); return; Loading services/core/java/com/android/server/wm/WindowOrganizerController.java +4 −3 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANI import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED; import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; Loading Loading @@ -756,7 +757,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } if (!parent.isAllowedToEmbedActivity(activity)) { if (parent.isAllowedToEmbedActivity(activity) != EMBEDDING_ALLOWED) { final Throwable exception = new SecurityException( "The task fragment is not trusted to embed the given activity."); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); Loading Loading @@ -988,7 +989,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } /** A helper method to send minimum dimension violation error to the client. */ void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions, private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions, IBinder errorCallbackToken, String reason) { if (taskFragment == null || taskFragment.getTaskFragmentOrganizer() == null) { return; Loading Loading @@ -1582,7 +1583,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // We are reparenting activities to a new embedded TaskFragment, this operation is only // allowed if the new parent is trusted by all reparent activities. final boolean isEmbeddingDisallowed = oldParent.forAllActivities(activity -> !newParentTF.isAllowedToEmbedActivity(activity)); newParentTF.isAllowedToEmbedActivity(activity) == EMBEDDING_ALLOWED); if (isEmbeddingDisallowed) { final Throwable exception = new SecurityException( "The new parent is not trusted to embed the activities."); Loading services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +61 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP; import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; Loading @@ -52,6 +53,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityStarter.canEmbedActivity; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; Loading @@ -59,6 +65,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; Loading Loading @@ -87,6 +94,7 @@ import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.service.voice.IVoiceInteractionSession; import android.util.Pair; import android.util.Size; import android.view.Gravity; import android.window.TaskFragmentOrganizerToken; Loading Loading @@ -1172,6 +1180,7 @@ public class ActivityStarterTests extends WindowTestsBase { null /* inTask */, taskFragment); assertFalse(taskFragment.hasChild()); assertNotNull("Target record must be started on Task.", targetRecord.getParent().asTask()); } @Test Loading Loading @@ -1342,6 +1351,58 @@ public class ActivityStarterTests extends WindowTestsBase { any()); } @Test public void testCanEmbedActivity() { final Size minDimensions = new Size(1000, 1000); final WindowLayout windowLayout = new WindowLayout(0, 0, 0, 0, 0, minDimensions.getWidth(), minDimensions.getHeight()); final ActivityRecord starting = new ActivityBuilder(mAtm) .setUid(UNIMPORTANT_UID) .setWindowLayout(windowLayout) .build(); // Task fragment hasn't attached to a task yet. Start activity to a new task. TaskFragment taskFragment = new TaskFragmentBuilder(mAtm).build(); final Task task = new TaskBuilder(mSupervisor).build(); assertEquals(EMBEDDING_DISALLOWED_NEW_TASK, canEmbedActivity(taskFragment, starting, task)); // Starting activity is going to be started on a task different from task fragment's parent // task. Start activity to a new task. task.addChild(taskFragment, POSITION_TOP); final Task newTask = new TaskBuilder(mSupervisor).build(); assertEquals(EMBEDDING_DISALLOWED_NEW_TASK, canEmbedActivity(taskFragment, starting, newTask)); // Make task fragment bounds exceed task bounds. final Rect taskBounds = task.getBounds(); taskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.right + 1, taskBounds.bottom + 1); assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST, canEmbedActivity(taskFragment, starting, task)); taskFragment.setBounds(taskBounds); starting.info.flags |= FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task)); starting.info.flags &= ~FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; // Set task fragment's uid as the same as starting activity's uid. taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class), UNIMPORTANT_UID, "test"); assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task)); // Make task fragment bounds smaller than starting activity's minimum dimensions taskFragment.setBounds(0, 0, minDimensions.getWidth() - 1, minDimensions.getHeight() - 1); assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION, canEmbedActivity(taskFragment, starting, task)); } private static void startActivityInner(ActivityStarter starter, ActivityRecord target, ActivityRecord source, ActivityOptions options, Task inTask, TaskFragment inTaskFragment) { Loading Loading
services/core/java/com/android/server/wm/ActivityStarter.java +54 −39 Original line number Diff line number Diff line Loading @@ -78,6 +78,10 @@ import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS; import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY; import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_TOP; import android.annotation.NonNull; Loading Loading @@ -131,6 +135,7 @@ import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; import com.android.server.wm.LaunchParamsController.LaunchParams; import com.android.server.wm.TaskFragment.EmbeddingCheckResult; import java.io.PrintWriter; import java.text.DateFormat; Loading Loading @@ -2038,12 +2043,6 @@ class ActivityStarter { } } if (mInTaskFragment != null && !canEmbedActivity(mInTaskFragment, r, newTask, targetTask)) { Slog.e(TAG, "Permission denied: Cannot embed " + r + " to " + mInTaskFragment.getTask() + " targetTask= " + targetTask); return START_PERMISSION_DENIED; } // Do not start the activity if target display's DWPC does not allow it. // We can't return fatal error code here because it will crash the caller of // startActivity() if they don't catch the exception. We don't expect 3P apps to make Loading @@ -2070,19 +2069,21 @@ class ActivityStarter { } /** * Return {@code true} if an activity can be embedded to the TaskFragment. * Returns whether embedding of {@code starting} is allowed. * * @param taskFragment the TaskFragment for embedding. * @param starting the starting activity. * @param newTask whether the starting activity is going to be launched on a new task. * @param targetTask the target task for launching activity, which could be different from * the one who hosting the embedding. */ private boolean canEmbedActivity(@NonNull TaskFragment taskFragment, @NonNull ActivityRecord starting, boolean newTask, Task targetTask) { @VisibleForTesting @EmbeddingCheckResult static int canEmbedActivity(@NonNull TaskFragment taskFragment, @NonNull ActivityRecord starting, @NonNull Task targetTask) { final Task hostTask = taskFragment.getTask(); // Not allowed embedding a separate task or without host task. if (hostTask == null || newTask || targetTask != hostTask) { return false; if (hostTask == null || targetTask != hostTask) { return EMBEDDING_DISALLOWED_NEW_TASK; } return taskFragment.isAllowedToEmbedActivity(starting); Loading Loading @@ -2894,19 +2895,16 @@ class ActivityStarter { mIntentDelivered = true; } /** Places {@link #mStartActivity} in {@code task} or an embedded {@link TaskFragment}. */ private void addOrReparentStartingActivity(@NonNull Task task, String reason) { TaskFragment newParent = task; if (mInTaskFragment != null) { // TODO(b/234351413): remove remaining embedded Task logic. // mInTaskFragment is created and added to the leaf task by task fragment organizer's // request. If the task was resolved and different than mInTaskFragment, reparent the // task to mInTaskFragment for embedding. if (mInTaskFragment.getTask() != task) { if (shouldReparentInTaskFragment(task)) { task.reparent(mInTaskFragment, POSITION_TOP); } } else { int embeddingCheckResult = canEmbedActivity(mInTaskFragment, mStartActivity, task); if (embeddingCheckResult == EMBEDDING_ALLOWED) { newParent = mInTaskFragment; } else { // Start mStartActivity to task instead if it can't be embedded to mInTaskFragment. sendCanNotEmbedActivityError(mInTaskFragment, embeddingCheckResult); } } else { TaskFragment candidateTf = mAddingToTaskFragment != null ? mAddingToTaskFragment : null; Loading @@ -2918,20 +2916,12 @@ class ActivityStarter { } } if (candidateTf != null && candidateTf.isEmbedded() && canEmbedActivity(candidateTf, mStartActivity, false /* newTask */, task)) { && canEmbedActivity(candidateTf, mStartActivity, task) == EMBEDDING_ALLOWED) { // Use the embedded TaskFragment of the top activity as the new parent if the // activity can be embedded. newParent = candidateTf; } } // Start Activity to the Task if mStartActivity's min dimensions are not satisfied. if (newParent.isEmbedded() && newParent.smallerThanMinDimension(mStartActivity)) { reason += " - MinimumDimensionViolation"; mService.mWindowOrganizerController.sendMinimumDimensionViolation( newParent, mStartActivity.getMinDimensions(), mRequest.errorCallbackToken, reason); newParent = task; } if (mStartActivity.getTaskFragment() == null || mStartActivity.getTaskFragment() == newParent) { newParent.addChild(mStartActivity, POSITION_TOP); Loading @@ -2940,16 +2930,41 @@ class ActivityStarter { } } private boolean shouldReparentInTaskFragment(Task task) { // The task has not been embedded. We should reparent the task to TaskFragment. if (!task.isEmbedded()) { return true; /** * Notifies the client side that {@link #mStartActivity} cannot be embedded to * {@code taskFragment}. */ private void sendCanNotEmbedActivityError(TaskFragment taskFragment, @EmbeddingCheckResult int result) { final String errMsg; switch(result) { case EMBEDDING_DISALLOWED_NEW_TASK: { errMsg = "Cannot embed " + mStartActivity + " that launched on another task" + ",mLaunchMode=" + mLaunchMode + ",mLaunchFlag=" + Integer.toHexString(mLaunchFlags); break; } case EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION: { errMsg = "Cannot embed " + mStartActivity + ". TaskFragment's bounds:" + taskFragment.getBounds() + ", minimum dimensions:" + mStartActivity.getMinDimensions(); break; } case EMBEDDING_DISALLOWED_UNTRUSTED_HOST: { errMsg = "The app:" + mCallingUid + "is not trusted to " + mStartActivity; break; } default: errMsg = "Unhandled embed result:" + result; } if (taskFragment.isOrganized()) { mService.mWindowOrganizerController.sendTaskFragmentOperationFailure( taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken, new SecurityException(errMsg)); } else { // If the taskFragment is not organized, just dump error message as warning logs. Slog.w(TAG, errMsg); } WindowContainer<?> parent = task.getParent(); // If the Activity is going to launch on top of embedded Task in the same TaskFragment, // we don't need to reparent the Task. Otherwise, the embedded Task should reparent to // another TaskFragment. return parent.asTaskFragment() != mInTaskFragment; } private int adjustLaunchFlagsToDocumentMode(ActivityRecord r, boolean launchSingleInstance, Loading
services/core/java/com/android/server/wm/TaskFragment.java +57 −10 Original line number Diff line number Diff line Loading @@ -139,6 +139,45 @@ class TaskFragment extends WindowContainer<WindowContainer> { /** Set to false to disable the preview that is shown while a new activity is being started. */ static final boolean SHOW_APP_STARTING_PREVIEW = true; /** * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: * indicate that an Activity can be embedded successfully. */ static final int EMBEDDING_ALLOWED = 0; /** * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: * indicate that an Activity can't be embedded because either the Activity does not allow * untrusted embedding, and the embedding host app is not trusted. */ static final int EMBEDDING_DISALLOWED_UNTRUSTED_HOST = 1; /** * An embedding check result of {@link #isAllowedToEmbedActivity(ActivityRecord)} or * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: * indicate that an Activity can't be embedded because this taskFragment's bounds are * {@link #smallerThanMinDimension(ActivityRecord)}. */ static final int EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION = 2; /** * An embedding check result of * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}: * indicate that an Activity can't be embedded because the Activity is started on a new task. */ static final int EMBEDDING_DISALLOWED_NEW_TASK = 3; /** * Embedding check results of {@link #isAllowedToEmbedActivity(ActivityRecord)} or * {@link ActivityStarter#canEmbedActivity(TaskFragment, ActivityRecord, Task)}. */ @IntDef(prefix = {"EMBEDDING_"}, value = { EMBEDDING_ALLOWED, EMBEDDING_DISALLOWED_UNTRUSTED_HOST, EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION, EMBEDDING_DISALLOWED_NEW_TASK, }) @interface EmbeddingCheckResult {} /** * Indicate that the minimal width/height should use the default value. * Loading Loading @@ -520,20 +559,29 @@ class TaskFragment extends WindowContainer<WindowContainer> { return false; } boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a) { @EmbeddingCheckResult int isAllowedToEmbedActivity(@NonNull ActivityRecord a) { return isAllowedToEmbedActivity(a, mTaskFragmentOrganizerUid); } /** * Checks if the organized task fragment is allowed to have the specified activity, which is * allowed if an activity allows embedding in untrusted mode, or if the trusted mode can be * enabled. * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) * allowed if an activity allows embedding in untrusted mode, if the trusted mode can be * enabled, or if the organized task fragment bounds are not * {@link #smallerThanMinDimension(ActivityRecord)}. * * @param uid uid of the TaskFragment organizer. * @see #isAllowedToEmbedActivityInTrustedMode(ActivityRecord) */ boolean isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) { return isAllowedToEmbedActivityInUntrustedMode(a) || isAllowedToEmbedActivityInTrustedMode(a, uid); @EmbeddingCheckResult int isAllowedToEmbedActivity(@NonNull ActivityRecord a, int uid) { if (!isAllowedToEmbedActivityInUntrustedMode(a) && !isAllowedToEmbedActivityInTrustedMode(a, uid)) { return EMBEDDING_DISALLOWED_UNTRUSTED_HOST; } else if (smallerThanMinDimension(a)) { return EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; } return EMBEDDING_ALLOWED; } boolean smallerThanMinDimension(@NonNull ActivityRecord activity) { Loading @@ -550,9 +598,8 @@ class TaskFragment extends WindowContainer<WindowContainer> { } final int minWidth = minDimensions.x; final int minHeight = minDimensions.y; final boolean smaller = taskFragBounds.width() < minWidth return taskFragBounds.width() < minWidth || taskFragBounds.height() < minHeight; return smaller; } /** Loading Loading @@ -609,7 +656,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { // The system is trusted to embed other apps securely and for all users. return UserHandle.getAppId(uid) == SYSTEM_UID // Activities from the same UID can be embedded freely by the host. || uid == a.getUid(); || a.isUid(uid); } /** Loading
services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +2 −1 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.window.TaskFragmentOrganizer.putExceptionInBundle; import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.WindowOrganizerController.configurationsAreEqualForOrganizer; import android.annotation.IntDef; Loading Loading @@ -235,7 +236,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr + " is not in a task belong to the organizer app."); return; } if (!task.isAllowedToEmbedActivity(activity, mOrganizerUid)) { if (task.isAllowedToEmbedActivity(activity, mOrganizerUid) != EMBEDDING_ALLOWED) { Slog.d(TAG, "Reparent activity=" + activity.token + " is not allowed to be embedded."); return; Loading
services/core/java/com/android/server/wm/WindowOrganizerController.java +4 −3 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANI import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED; import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; Loading Loading @@ -756,7 +757,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); break; } if (!parent.isAllowedToEmbedActivity(activity)) { if (parent.isAllowedToEmbedActivity(activity) != EMBEDDING_ALLOWED) { final Throwable exception = new SecurityException( "The task fragment is not trusted to embed the given activity."); sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception); Loading Loading @@ -988,7 +989,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } /** A helper method to send minimum dimension violation error to the client. */ void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions, private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions, IBinder errorCallbackToken, String reason) { if (taskFragment == null || taskFragment.getTaskFragmentOrganizer() == null) { return; Loading Loading @@ -1582,7 +1583,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub // We are reparenting activities to a new embedded TaskFragment, this operation is only // allowed if the new parent is trusted by all reparent activities. final boolean isEmbeddingDisallowed = oldParent.forAllActivities(activity -> !newParentTF.isAllowedToEmbedActivity(activity)); newParentTF.isAllowedToEmbedActivity(activity) == EMBEDDING_ALLOWED); if (isEmbeddingDisallowed) { final Throwable exception = new SecurityException( "The new parent is not trusted to embed the activities."); Loading
services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +61 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,7 @@ import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED; import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP; import static android.content.pm.ActivityInfo.FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; Loading @@ -52,6 +53,11 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.ActivityStarter.canEmbedActivity; import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_BOTTOM; import static com.android.server.wm.WindowContainer.POSITION_TOP; Loading @@ -59,6 +65,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; Loading Loading @@ -87,6 +94,7 @@ import android.os.RemoteException; import android.platform.test.annotations.Presubmit; import android.service.voice.IVoiceInteractionSession; import android.util.Pair; import android.util.Size; import android.view.Gravity; import android.window.TaskFragmentOrganizerToken; Loading Loading @@ -1172,6 +1180,7 @@ public class ActivityStarterTests extends WindowTestsBase { null /* inTask */, taskFragment); assertFalse(taskFragment.hasChild()); assertNotNull("Target record must be started on Task.", targetRecord.getParent().asTask()); } @Test Loading Loading @@ -1342,6 +1351,58 @@ public class ActivityStarterTests extends WindowTestsBase { any()); } @Test public void testCanEmbedActivity() { final Size minDimensions = new Size(1000, 1000); final WindowLayout windowLayout = new WindowLayout(0, 0, 0, 0, 0, minDimensions.getWidth(), minDimensions.getHeight()); final ActivityRecord starting = new ActivityBuilder(mAtm) .setUid(UNIMPORTANT_UID) .setWindowLayout(windowLayout) .build(); // Task fragment hasn't attached to a task yet. Start activity to a new task. TaskFragment taskFragment = new TaskFragmentBuilder(mAtm).build(); final Task task = new TaskBuilder(mSupervisor).build(); assertEquals(EMBEDDING_DISALLOWED_NEW_TASK, canEmbedActivity(taskFragment, starting, task)); // Starting activity is going to be started on a task different from task fragment's parent // task. Start activity to a new task. task.addChild(taskFragment, POSITION_TOP); final Task newTask = new TaskBuilder(mSupervisor).build(); assertEquals(EMBEDDING_DISALLOWED_NEW_TASK, canEmbedActivity(taskFragment, starting, newTask)); // Make task fragment bounds exceed task bounds. final Rect taskBounds = task.getBounds(); taskFragment.setBounds(taskBounds.left, taskBounds.top, taskBounds.right + 1, taskBounds.bottom + 1); assertEquals(EMBEDDING_DISALLOWED_UNTRUSTED_HOST, canEmbedActivity(taskFragment, starting, task)); taskFragment.setBounds(taskBounds); starting.info.flags |= FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task)); starting.info.flags &= ~FLAG_ALLOW_UNTRUSTED_ACTIVITY_EMBEDDING; // Set task fragment's uid as the same as starting activity's uid. taskFragment.setTaskFragmentOrganizer(mock(TaskFragmentOrganizerToken.class), UNIMPORTANT_UID, "test"); assertEquals(EMBEDDING_ALLOWED, canEmbedActivity(taskFragment, starting, task)); // Make task fragment bounds smaller than starting activity's minimum dimensions taskFragment.setBounds(0, 0, minDimensions.getWidth() - 1, minDimensions.getHeight() - 1); assertEquals(EMBEDDING_DISALLOWED_MIN_DIMENSION_VIOLATION, canEmbedActivity(taskFragment, starting, task)); } private static void startActivityInner(ActivityStarter starter, ActivityRecord target, ActivityRecord source, ActivityOptions options, Task inTask, TaskFragment inTaskFragment) { Loading