diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index c96ffe761d902..10c3f786a2a45 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -330,8 +330,6 @@ import com.android.internal.policy.AttributeCache; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.internal.util.XmlUtils; -import com.android.internal.util.function.pooled.PooledFunction; -import com.android.internal.util.function.pooled.PooledLambda; import com.android.server.LocalServices; import com.android.server.am.AppTimeTracker; import com.android.server.am.PendingIntentRecord; @@ -746,6 +744,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean startingDisplayed; boolean startingMoved; + /** + * If it is non-null, it requires all activities who have the same starting data to be drawn + * to remove the starting window. + * TODO(b/189385912): Remove starting window related fields after migrating them to task. + */ + private StartingData mSharedStartingData; + boolean mHandleExitSplashScreen; @TransferSplashScreenState int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE; @@ -1085,6 +1090,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn); pw.print(" mIsExiting="); pw.println(mIsExiting); } + if (mSharedStartingData != null) { + pw.println(prefix + "mSharedStartingData=" + mSharedStartingData); + } if (mStartingWindow != null || mStartingSurface != null || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) { pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow); @@ -1379,9 +1387,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @Override - void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) { - final Task oldTask = oldParent != null ? ((TaskFragment) oldParent).getTask() : null; - final Task newTask = newParent != null ? ((TaskFragment) newParent).getTask() : null; + void onParentChanged(ConfigurationContainer rawNewParent, ConfigurationContainer rawOldParent) { + final TaskFragment oldParent = (TaskFragment) rawOldParent; + final TaskFragment newParent = (TaskFragment) rawNewParent; + final Task oldTask = oldParent != null ? oldParent.getTask() : null; + final Task newTask = newParent != null ? newParent.getTask() : null; this.task = newTask; super.onParentChanged(newParent, oldParent); @@ -1440,11 +1450,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A updateColorTransform(); if (oldParent != null) { - ((TaskFragment) oldParent).cleanUpActivityReferences(this); + oldParent.cleanUpActivityReferences(this); } if (newParent != null && isState(RESUMED)) { - ((TaskFragment) newParent).setResumedActivity(this, "onParentChanged"); + newParent.setResumedActivity(this, "onParentChanged"); + if (mStartingWindow != null && mStartingData != null + && mStartingData.mAssociatedTask == null && newParent.isEmbedded()) { + // The starting window should keep covering its task when the activity is + // reparented to a task fragment that may not fill the task bounds. + associateStartingDataWithTask(); + overrideConfigurationPropagation(mStartingWindow, task); + } } if (rootTask != null && rootTask.topRunningActivity() == this) { @@ -2005,7 +2022,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @VisibleForTesting boolean addStartingWindow(String pkg, int resolvedTheme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags, - IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning, + ActivityRecord from, boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean useEmpty) { // If the display is frozen, we won't do anything until the actual window is // displayed so there is no reason to put in the starting window. @@ -2064,7 +2081,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } applyStartingWindowTheme(pkg, resolvedTheme); - if (transferStartingWindow(transferFrom)) { + if (from != null && transferStartingWindow(from)) { return true; } @@ -2089,6 +2106,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Creating SnapshotStartingData"); mStartingData = new SnapshotStartingData(mWmService, snapshot, typeParams); + if (task.forAllLeafTaskFragments(TaskFragment::isEmbedded)) { + // Associate with the task so if this activity is resized by task fragment later, the + // starting window can keep the same bounds as the task. + associateStartingDataWithTask(); + } scheduleAddStartingWindow(); return true; } @@ -2337,6 +2359,23 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + void attachStartingWindow(@NonNull WindowState startingWindow) { + mStartingWindow = startingWindow; + if (mStartingData != null && mStartingData.mAssociatedTask != null) { + // Associate the configuration of starting window with the task. + overrideConfigurationPropagation(startingWindow, mStartingData.mAssociatedTask); + } + } + + private void associateStartingDataWithTask() { + mStartingData.mAssociatedTask = task; + task.forAllActivities(r -> { + if (r.mVisibleRequested && !r.firstWindowDrawn) { + r.mSharedStartingData = mStartingData; + } + }); + } + void removeStartingWindow() { if (transferSplashScreenIfNeeded()) { return; @@ -2346,6 +2385,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void removeStartingWindowAnimation(boolean prepareAnimation) { mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE; + if (mSharedStartingData != null) { + mSharedStartingData.mAssociatedTask.forAllActivities(r -> { + r.mSharedStartingData = null; + }); + } if (mStartingWindow == null) { if (mStartingData != null) { // Starting window has not been added yet, but it is scheduled to be added. @@ -3864,12 +3908,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - boolean transferStartingWindow(IBinder transferFrom) { - final ActivityRecord fromActivity = getDisplayContent().getActivityRecord(transferFrom); - if (fromActivity == null) { - return false; - } - + private boolean transferStartingWindow(@NonNull ActivityRecord fromActivity) { final WindowState tStartingWindow = fromActivity.mStartingWindow; if (tStartingWindow != null && fromActivity.mStartingSurface != null) { // In this case, the starting icon has already been displayed, so start @@ -3890,6 +3929,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Transfer the starting window over to the new token. mStartingData = fromActivity.mStartingData; + mSharedStartingData = fromActivity.mSharedStartingData; mStartingSurface = fromActivity.mStartingSurface; startingDisplayed = fromActivity.startingDisplayed; fromActivity.startingDisplayed = false; @@ -3952,6 +3992,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "Moving pending starting from %s to %s", fromActivity, this); mStartingData = fromActivity.mStartingData; + mSharedStartingData = fromActivity.mSharedStartingData; fromActivity.mStartingData = null; fromActivity.startingMoved = true; scheduleAddStartingWindow(); @@ -3970,16 +4011,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * immediately finishes after, so we have to transfer T to M. */ void transferStartingWindowFromHiddenAboveTokenIfNeeded() { - final PooledFunction p = PooledLambda.obtainFunction(ActivityRecord::transferStartingWindow, - this, PooledLambda.__(ActivityRecord.class)); - task.forAllActivities(p); - p.recycle(); - } - - private boolean transferStartingWindow(ActivityRecord fromActivity) { - if (fromActivity == this) return true; - - return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity.token); + task.forAllActivities(fromActivity -> { + if (fromActivity == this) return true; + return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity); + }); } void checkKeyguardFlagsChanged() { @@ -5160,6 +5195,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A void notifyAppStopped() { ProtoLog.v(WM_DEBUG_ADD_REMOVE, "notifyAppStopped: %s", this); mAppStopped = true; + firstWindowDrawn = false; // This is to fix the edge case that auto-enter-pip is finished in Launcher but app calls // setAutoEnterEnabled(false) and transitions to STOPPED state, see b/191930787. // Clear any surface transactions and content overlay in this case. @@ -5923,7 +5959,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) { + void onFirstWindowDrawn(WindowState win) { firstWindowDrawn = true; // stop tracking mSplashScreenStyleEmpty = true; @@ -5939,7 +5975,22 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // own stuff. win.cancelAnimation(); } - removeStartingWindow(); + + // Remove starting window directly if is in a pure task. Otherwise if it is associated with + // a task (e.g. nested task fragment), then remove only if all visible windows in the task + // are drawn. + final Task associatedTask = + mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null; + if (associatedTask == null) { + removeStartingWindow(); + } else if (associatedTask.getActivity( + r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) { + // The last drawn activity may not be the one that owns the starting window. + final ActivityRecord r = associatedTask.topActivityContainsStartingWindow(); + if (r != null) { + r.removeStartingWindow(); + } + } updateReportedVisibilityLocked(); } @@ -6510,8 +6561,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean scheduled = addStartingWindow(packageName, resolvedTheme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, - prev != null ? prev.appToken : null, - newTask || newSingleActivity, taskSwitch, isProcessRunning(), + prev, newTask || newSingleActivity, taskSwitch, isProcessRunning(), allowTaskSnapshot(), activityCreated, mSplashScreenStyleEmpty); if (DEBUG_STARTING_WINDOW_VERBOSE && scheduled) { Slog.d(TAG, "Scheduled starting window for " + this); diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java index c671e3835abcb..8b1befbefd990 100644 --- a/services/core/java/com/android/server/wm/StartingData.java +++ b/services/core/java/com/android/server/wm/StartingData.java @@ -32,6 +32,12 @@ public abstract class StartingData { */ boolean mIsTransitionForward; + /** + * Non-null if the starting window should cover the bounds of associated task. It is assigned + * when the parent activity of starting window may be put in a partial area of the task. + */ + Task mAssociatedTask; + protected StartingData(WindowManagerService service, int typeParams) { mService = service; mTypeParams = typeParams; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 5996083c09f44..969e0fd9d0fc2 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -628,7 +628,7 @@ class Task extends TaskFragment { IVoiceInteractionSession _voiceSession, IVoiceInteractor _voiceInteractor, boolean _createdByOrganizer, IBinder _launchCookie, boolean _deferTaskAppear, boolean _removeWithTaskOrganizer) { - super(atmService, null /* fragmentToken */, _createdByOrganizer); + super(atmService, null /* fragmentToken */, _createdByOrganizer, false /* isEmbedded */); mTaskId = _taskId; mUserId = _userId; @@ -707,13 +707,13 @@ class Task extends TaskFragment { return this; } - private void cleanUpResourcesForDestroy(ConfigurationContainer oldParent) { + private void cleanUpResourcesForDestroy(WindowContainer oldParent) { if (hasChild()) { return; } // This task is going away, so save the last state if necessary. - saveLaunchingStateIfNeeded(((WindowContainer) oldParent).getDisplayContent()); + saveLaunchingStateIfNeeded(oldParent.getDisplayContent()); // TODO: VI what about activity? final boolean isVoiceSession = voiceSession != null; @@ -1144,11 +1144,11 @@ class Task extends TaskFragment { } @Override - void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) { - final DisplayContent display = newParent != null - ? ((WindowContainer) newParent).getDisplayContent() : null; - final DisplayContent oldDisplay = oldParent != null - ? ((WindowContainer) oldParent).getDisplayContent() : null; + void onParentChanged(ConfigurationContainer rawNewParent, ConfigurationContainer rawOldParent) { + final WindowContainer newParent = (WindowContainer) rawNewParent; + final WindowContainer oldParent = (WindowContainer) rawOldParent; + final DisplayContent display = newParent != null ? newParent.getDisplayContent() : null; + final DisplayContent oldDisplay = oldParent != null ? oldParent.getDisplayContent() : null; mPrevDisplayId = (oldDisplay != null) ? oldDisplay.mDisplayId : INVALID_DISPLAY; @@ -1189,7 +1189,7 @@ class Task extends TaskFragment { } if (oldParent != null) { - final Task oldParentTask = ((WindowContainer) oldParent).asTask(); + final Task oldParentTask = oldParent.asTask(); if (oldParentTask != null) { final PooledConsumer c = PooledLambda.obtainConsumer( Task::cleanUpActivityReferences, oldParentTask, @@ -5164,21 +5164,17 @@ class Task extends TaskFragment { // "has the same starting icon" as the next one. This allows the // window manager to keep the previous window it had previously // created, if it still had one. - Task prevTask = r.getTask(); - ActivityRecord prev = prevTask.getActivity( - a -> a.mStartingData != null && a.okToShowLocked()); - if (prev != null) { - // We don't want to reuse the previous starting preview if: - // (1) The current activity is in a different task. - if (prev.getTask() != prevTask) { - prev = null; - } - // (2) The current activity is already displayed. - else if (prev.nowVisible) { - prev = null; - } + Task baseTask = r.getTask(); + if (baseTask.isEmbedded()) { + // If the task is embedded in a task fragment, there may have an existing + // starting window in the parent task. This allows the embedded activities + // to share the starting window and make sure that the window can have top + // z-order by transferring to the top activity. + baseTask = baseTask.getParent().asTaskFragment().getTask(); } + final ActivityRecord prev = baseTask.getActivity( + a -> a.mStartingData != null && a.okToShowLocked()); r.showStartingWindow(prev, newTask, isTaskSwitch, true /* startActivity */, sourceRecord); } diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 999df307cbf05..7cd0ee92b30aa 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -207,6 +207,9 @@ class TaskFragment extends WindowContainer { @VisibleForTesting boolean mCreatedByOrganizer; + /** Whether this TaskFragment is embedded in a task. */ + private final boolean mIsEmbedded; + /** Organizer that organizing this TaskFragment. */ @Nullable private ITaskFragmentOrganizer mTaskFragmentOrganizer; @@ -269,14 +272,21 @@ class TaskFragment extends WindowContainer { } } + /** Creates an embedded task fragment. */ TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken, boolean createdByOrganizer) { + this(atmService, fragmentToken, createdByOrganizer, true /* isEmbedded */); + } + + TaskFragment(ActivityTaskManagerService atmService, IBinder fragmentToken, + boolean createdByOrganizer, boolean isEmbedded) { super(atmService.mWindowManager); mAtmService = atmService; mTaskSupervisor = mAtmService.mTaskSupervisor; mRootWindowContainer = mAtmService.mRootWindowContainer; mCreatedByOrganizer = createdByOrganizer; + mIsEmbedded = isEmbedded; mTaskFragmentOrganizerController = mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController; mFragmentToken = fragmentToken; @@ -389,6 +399,18 @@ class TaskFragment extends WindowContainer { return this; } + /** Returns {@code true} if this is a container for embedded activities or tasks. */ + boolean isEmbedded() { + if (mIsEmbedded) { + return true; + } + final WindowContainer parent = getParent(); + if (parent != null) { + final TaskFragment taskFragment = parent.asTaskFragment(); + return taskFragment != null && taskFragment.isEmbedded(); + } + return false; + } /** * Simply check and give warning logs if this is not operated on leaf {@link TaskFragment}. diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index ffee0b795aec3..c48e9d1b4dea6 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3399,6 +3399,29 @@ class WindowContainer extends ConfigurationContainer< unregisterConfigurationChangeListener(listener); } + /** + * Forces the receiver container to always use the configuration of the supplier container as + * its requested override configuration. It allows to propagate configuration without changing + * the relationship between child and parent. + */ + static void overrideConfigurationPropagation(WindowContainer receiver, + WindowContainer supplier) { + final ConfigurationContainerListener listener = new ConfigurationContainerListener() { + @Override + public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) { + receiver.onRequestedOverrideConfigurationChanged(supplier.getConfiguration()); + } + }; + supplier.registerConfigurationChangeListener(listener); + receiver.registerWindowContainerListener(new WindowContainerListener() { + @Override + public void onRemoved() { + receiver.unregisterWindowContainerListener(this); + supplier.unregisterConfigurationChangeListener(listener); + } + }); + } + /** * Returns the {@link WindowManager.LayoutParams.WindowType}. */ diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index bbbbde902f6ca..36809b374525e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1769,9 +1769,8 @@ public class WindowManagerService extends IWindowManager.Stub final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty(); win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows); - final ActivityRecord tokenActivity = token.asActivityRecord(); - if (type == TYPE_APPLICATION_STARTING && tokenActivity != null) { - tokenActivity.mStartingWindow = win; + if (type == TYPE_APPLICATION_STARTING && activity != null) { + activity.attachStartingWindow(win); ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "addWindow: %s startingWindow=%s", activity, win); } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 18734a52bd06e..f9727e23ea8a0 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -4736,7 +4736,7 @@ class WindowState extends WindowContainer implements WindowManagerP final int drawState = mWinAnimator.mDrawState; if ((drawState == HAS_DRAWN || drawState == READY_TO_SHOW) && mActivityRecord != null) { if (mAttrs.type != TYPE_APPLICATION_STARTING) { - mActivityRecord.onFirstWindowDrawn(this, mWinAnimator); + mActivityRecord.onFirstWindowDrawn(this); } else { mActivityRecord.onStartingWindowDrawn(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 2c3586093717d..41f2246858b44 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -18,6 +18,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION; import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT; @@ -145,6 +146,7 @@ import org.junit.runner.RunWith; import org.mockito.invocation.InvocationOnMock; import java.util.ArrayList; +import java.util.function.BiConsumer; /** @@ -2506,7 +2508,7 @@ public class ActivityRecordTests extends WindowTestsBase { false, false); waitUntilHandlersIdle(); activity2.addStartingWindow(mPackageName, - android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1.appToken.asBinder(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1, true, true, false, true, false, false); waitUntilHandlersIdle(); assertNoStartingWindow(activity1); @@ -2523,7 +2525,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Surprise, ...! Transfer window in the middle of the creation flow. activity2.addStartingWindow(mPackageName, android.R.style.Theme, null, "Test", 0, 0, 0, 0, - activity1.appToken.asBinder(), true, true, false, + activity1, true, true, false, true, false, false); }); activity1.addStartingWindow(mPackageName, @@ -2544,7 +2546,7 @@ public class ActivityRecordTests extends WindowTestsBase { false, false); waitUntilHandlersIdle(); activity2.addStartingWindow(mPackageName, - android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1.appToken.asBinder(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity1, true, true, false, true, false, false); waitUntilHandlersIdle(); assertNoStartingWindow(activity1); @@ -2650,7 +2652,7 @@ public class ActivityRecordTests extends WindowTestsBase { // Make sure the fixed rotation transform linked to activity2 when adding starting window // on activity2. topActivity.addStartingWindow(mPackageName, - android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity.appToken.asBinder(), + android.R.style.Theme, null, "Test", 0, 0, 0, 0, activity, false, false, false, true, false, false); waitUntilHandlersIdle(); assertTrue(topActivity.hasFixedRotationTransform()); @@ -2681,6 +2683,52 @@ public class ActivityRecordTests extends WindowTestsBase { assertHasStartingWindow(activityBottom); } + @Test + public void testStartingWindowInTaskFragment() { + final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final WindowState startingWindow = createWindowState( + new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING), activity1); + activity1.addWindow(startingWindow); + activity1.attachStartingWindow(startingWindow); + activity1.mStartingData = mock(StartingData.class); + final Task task = activity1.getTask(); + final Rect taskBounds = task.getBounds(); + final int width = taskBounds.width(); + final int height = taskBounds.height(); + final BiConsumer fragmentSetup = (fragment, bounds) -> { + final Configuration config = fragment.getRequestedOverrideConfiguration(); + config.windowConfiguration.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + config.windowConfiguration.setBounds(bounds); + fragment.onRequestedOverrideConfigurationChanged(config); + }; + + final TaskFragment taskFragment1 = new TaskFragment( + mAtm, null /* fragmentToken */, false /* createdByOrganizer */); + fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height)); + task.addChild(taskFragment1, POSITION_TOP); + + final TaskFragment taskFragment2 = new TaskFragment( + mAtm, null /* fragmentToken */, false /* createdByOrganizer */); + fragmentSetup.accept(taskFragment2, new Rect(width / 2, 0, width, height)); + task.addChild(taskFragment2, POSITION_TOP); + final ActivityRecord activity2 = new ActivityBuilder(mAtm).build(); + activity2.mVisibleRequested = true; + taskFragment2.addChild(activity2); + activity1.reparent(taskFragment1, POSITION_TOP); + + assertEquals(task, activity1.mStartingData.mAssociatedTask); + assertEquals(taskFragment1.getBounds(), activity1.getBounds()); + // The activity was resized by task fragment, but starting window must still cover the task. + assertEquals(taskBounds, activity1.mStartingWindow.getBounds()); + + // The starting window is only removed when all embedded activities are drawn. + final WindowState activityWindow = mock(WindowState.class); + activity1.onFirstWindowDrawn(activityWindow); + assertNotNull(activity1.mStartingWindow); + activity2.onFirstWindowDrawn(activityWindow); + assertNull(activity1.mStartingWindow); + } + @Test public void testTransitionAnimationBounds() { removeGlobalMinSizeRestriction();