From efe9df6ff20ba8d8aff0f12591bad0063e70a055 Mon Sep 17 00:00:00 2001 From: Riddle Hsu Date: Fri, 23 Jul 2021 19:22:27 -0600 Subject: [PATCH] Associate starting window in task fragment with base task The 2 cases when a fullscreen activity is launching with starting window (step a), and it launches embedded activity before it is drawn (step b): (assume X,Y are 2 different activities) 1. a. Task - X (Fullscreen) - starting window b. Task - TaskFragment - X (Left side) - TaskFragment - Y (Right side) - starting window 2. a. Task - X (Fullscreen) - starting window b. Task - TaskFragment X (Left side) - TaskFragment - Task - Y (Right side) - starting window The starting window will be transferred to the top activity (so it won't be covered by other embedded activity according to z-order) of the base task with keeping the configuration of base task. Another case is to bring the base task to front with snapshot starting window. That will find all visible activities and share the starting data to them. Then the starting window can be removed until all visible embedded activities are drawn. Bug: 189385912 Test: ActivityRecordTests#testStartingWindowInTaskFragment Change-Id: Ia4c0f4142d49f9e6ac9c39980b1157c35069ec4e --- .../com/android/server/wm/ActivityRecord.java | 108 +++++++++++++----- .../com/android/server/wm/StartingData.java | 6 + .../core/java/com/android/server/wm/Task.java | 40 +++---- .../com/android/server/wm/TaskFragment.java | 22 ++++ .../android/server/wm/WindowContainer.java | 23 ++++ .../server/wm/WindowManagerService.java | 5 +- .../com/android/server/wm/WindowState.java | 2 +- .../server/wm/ActivityRecordTests.java | 56 ++++++++- 8 files changed, 203 insertions(+), 59 deletions(-) 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();