diff options
6 files changed, 180 insertions, 56 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 84cd63424cd1..ec9babf09ef3 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -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; @@ -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; @@ -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 @@ -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); @@ -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; @@ -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); @@ -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, diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 1d328671876f..1b0c01816f73 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -140,6 +140,45 @@ class TaskFragment extends WindowContainer<WindowContainer> { 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. * * @see #mMinWidth @@ -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) { @@ -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; } /** @@ -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); } /** diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java index b4d1cf77919a..2546177ec367 100644 --- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java @@ -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; @@ -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; diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 22d6237411f3..64a5deb32fcb 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -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; @@ -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); @@ -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; @@ -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."); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index b9432753c17f..4ca14ddbd96f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -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; @@ -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; @@ -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; @@ -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; @@ -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 @@ -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) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java index 8202cd915527..ed8440027bdc 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.testing.Assert.assertThrows; @@ -531,7 +532,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mWindowOrganizerController.mLaunchTaskFragments .put(mFragmentToken, mTaskFragment); mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token); - doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity); + doReturn(EMBEDDING_ALLOWED).when(mTaskFragment).isAllowedToEmbedActivity(activity); clearInvocations(mAtm.mRootWindowContainer); mAtm.getWindowOrganizerController().applyTransaction(mTransaction); @@ -921,7 +922,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .setOrganizer(mOrganizer) .setBounds(mTaskFragBounds) .build(); - doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity); mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); clearInvocations(mAtm.mRootWindowContainer); @@ -956,7 +956,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { .setOrganizer(mOrganizer) .setBounds(mTaskFragBounds) .build(); - doReturn(true).when(mTaskFragment).isAllowedToEmbedActivity(activity); mWindowOrganizerController.mLaunchTaskFragments.put(oldFragToken, oldTaskFrag); mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment); clearInvocations(mAtm.mRootWindowContainer); |