From e9a1037439a2f0a54be251227332806de00d7f80 Mon Sep 17 00:00:00 2001 From: Chris Li Date: Tue, 2 Aug 2022 16:08:37 +0800 Subject: [PATCH] Apply TaskFragmentOrganizer changes in one WCT (1/2) Before, when receive TaskFragment transaction, we apply changes in multiple WindowContainerTransactions. Now, update to apply all changes in one WCT for the whole TaskFragment transaction. Will update the TestAPI in the followup cl. Bug: 240519866 Test: pass existing Change-Id: I2e15b187bee95193874d76bc6963d3e01eba3910 --- .../android/window/TaskFragmentOrganizer.java | 23 ++- .../JetpackTaskFragmentOrganizer.java | 58 ++++--- .../extensions/embedding/SplitController.java | 154 +++++++++++------- .../extensions/embedding/SplitPresenter.java | 37 +---- .../embedding/TaskFragmentContainer.java | 15 +- .../JetpackTaskFragmentOrganizerTest.java | 4 +- .../embedding/SplitControllerTest.java | 136 +++++++++------- .../embedding/SplitPresenterTest.java | 7 +- .../embedding/TaskFragmentContainerTest.java | 36 ++-- .../TaskFragmentOrganizerControllerTest.java | 16 +- 10 files changed, 271 insertions(+), 215 deletions(-) diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java index 4eb7c4f04807b..5372071f7f0e0 100644 --- a/core/java/android/window/TaskFragmentOrganizer.java +++ b/core/java/android/window/TaskFragmentOrganizer.java @@ -147,13 +147,25 @@ public class TaskFragmentOrganizer extends WindowOrganizer { } } - /** Called when a TaskFragment is created and organized by this organizer. */ + /** + * Called when a TaskFragment is created and organized by this organizer. + * + * @param taskFragmentInfo Info of the TaskFragment that is created. + */ public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) {} - /** Called when the status of an organized TaskFragment is changed. */ + /** + * Called when the status of an organized TaskFragment is changed. + * + * @param taskFragmentInfo Info of the TaskFragment that is changed. + */ public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) {} - /** Called when an organized TaskFragment is removed. */ + /** + * Called when an organized TaskFragment is removed. + * + * @param taskFragmentInfo Info of the TaskFragment that is removed. + */ public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) {} /** @@ -176,6 +188,9 @@ public class TaskFragmentOrganizer extends WindowOrganizer { * For case like screen size change, it will trigger onTaskFragmentParentInfoChanged with new * Task bounds, but may not trigger onTaskFragmentInfoChanged because there can be an override * bounds. + * + * @param taskId Id of the parent Task that is changed. + * @param parentConfig Config of the parent Task. * @hide */ public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) { @@ -226,7 +241,7 @@ public class TaskFragmentOrganizer extends WindowOrganizer { /** * Called when an Activity is reparented to the Task with organized TaskFragment. For example, * when an Activity enters and then exits Picture-in-picture, it will be reparented back to its - * orginial Task. In this case, we need to notify the organizer so that it can check if the + * original Task. In this case, we need to notify the organizer so that it can check if the * Activity matches any split rule. * * @param taskId The Task that the activity is reparented to. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java index a53130d7de27a..9fb7d1988772f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java @@ -62,13 +62,18 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { * Callback that notifies the controller about changes to task fragments. */ interface TaskFragmentCallback { - void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo); - void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo); - void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo); - void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig); - void onActivityReparentedToTask(int taskId, @NonNull Intent activityIntent, - @NonNull IBinder activityToken); - void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType); + void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo); + void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo); + void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo); + void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, + int taskId, @NonNull Configuration parentConfig); + void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, + int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken); + void onTaskFragmentError(@NonNull WindowContainerTransaction wct, + @Nullable TaskFragmentInfo taskFragmentInfo, int opType); } /** @@ -158,27 +163,16 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { updateWindowingMode(wct, fragmentToken, WINDOWING_MODE_UNDEFINED); } - /** - * Expands an existing TaskFragment to fill parent. - * @param fragmentToken token of an existing TaskFragment. - */ - void expandTaskFragment(@NonNull IBinder fragmentToken) { - WindowContainerTransaction wct = new WindowContainerTransaction(); - expandTaskFragment(wct, fragmentToken); - applyTransaction(wct); - } - /** * Expands an Activity to fill parent by moving it to a new TaskFragment. * @param fragmentToken token to create new TaskFragment with. * @param activity activity to move to the fill-parent TaskFragment. */ - void expandActivity(@NonNull IBinder fragmentToken, @NonNull Activity activity) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); + void expandActivity(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken, + @NonNull Activity activity) { createTaskFragmentAndReparentActivity( wct, fragmentToken, activity.getActivityToken(), new Rect(), WINDOWING_MODE_UNDEFINED, activity); - applyTransaction(wct); } /** @@ -278,43 +272,55 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer { @Override public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); - mCallback.onTaskFragmentAppeared(taskFragmentInfo); + mCallback.onTaskFragmentAppeared(wct, taskFragmentInfo); + applyTransaction(wct); } @Override public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); - mCallback.onTaskFragmentInfoChanged(taskFragmentInfo); + mCallback.onTaskFragmentInfoChanged(wct, taskFragmentInfo); + applyTransaction(wct); } @Override public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); mFragmentInfos.remove(taskFragmentInfo.getFragmentToken()); - mCallback.onTaskFragmentVanished(taskFragmentInfo); + mCallback.onTaskFragmentVanished(wct, taskFragmentInfo); + applyTransaction(wct); } @Override public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) { - mCallback.onTaskFragmentParentInfoChanged(taskId, parentConfig); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mCallback.onTaskFragmentParentInfoChanged(wct, taskId, parentConfig); + applyTransaction(wct); } @Override public void onActivityReparentedToTask(int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { - mCallback.onActivityReparentedToTask(taskId, activityIntent, activityToken); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + mCallback.onActivityReparentedToTask(wct, taskId, activityIntent, activityToken); + applyTransaction(wct); } @Override public void onTaskFragmentError(@NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo, int opType, @NonNull Throwable exception) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); if (taskFragmentInfo != null) { final IBinder fragmentToken = taskFragmentInfo.getFragmentToken(); mFragmentInfos.put(fragmentToken, taskFragmentInfo); } - mCallback.onTaskFragmentError(taskFragmentInfo, opType); + mCallback.onTaskFragmentError(wct, taskFragmentInfo, opType); + applyTransaction(wct); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 3fee3f52ab257..0597809f8e36f 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -145,35 +145,36 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void onTaskFragmentAppeared(@NonNull TaskFragmentInfo taskFragmentInfo) { + public void onTaskFragmentAppeared(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo) { synchronized (mLock) { TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container == null) { return; } - container.setInfo(taskFragmentInfo); + container.setInfo(wct, taskFragmentInfo); if (container.isFinished()) { - mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); + mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else { // Update with the latest Task configuration. - mPresenter.updateContainer(container); + updateContainer(wct, container); } updateCallbackIfNecessary(); } } @Override - public void onTaskFragmentInfoChanged(@NonNull TaskFragmentInfo taskFragmentInfo) { + public void onTaskFragmentInfoChanged(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo) { synchronized (mLock) { TaskFragmentContainer container = getContainer(taskFragmentInfo.getFragmentToken()); if (container == null) { return; } - final WindowContainerTransaction wct = new WindowContainerTransaction(); final boolean wasInPip = isInPictureInPicture(container); - container.setInfo(taskFragmentInfo); + container.setInfo(wct, taskFragmentInfo); final boolean isInPip = isInPictureInPicture(container); // Check if there are no running activities - consider the container empty if there are // no non-finishing activities left. @@ -183,15 +184,15 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Instead, the original split should be cleanup, and the dependent may be // expanded to fullscreen. cleanupForEnterPip(wct, container); - mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct); + mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else if (taskFragmentInfo.isTaskClearedForReuse()) { // Do not finish the dependents if this TaskFragment was cleared due to // launching activity in the Task. - mPresenter.cleanupContainer(container, false /* shouldFinishDependent */, wct); + mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); } else if (!container.isWaitingActivityAppear()) { // Do not finish the container before the expected activity appear until // timeout. - mPresenter.cleanupContainer(container, true /* shouldFinishDependent */, wct); + mPresenter.cleanupContainer(wct, container, true /* shouldFinishDependent */); } } else if (wasInPip && isInPip) { // No update until exit PIP. @@ -208,13 +209,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // needed. updateContainer(wct, container); } - mPresenter.applyTransaction(wct); updateCallbackIfNecessary(); } } @Override - public void onTaskFragmentVanished(@NonNull TaskFragmentInfo taskFragmentInfo) { + public void onTaskFragmentVanished(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentInfo taskFragmentInfo) { synchronized (mLock) { final TaskFragmentContainer container = getContainer( taskFragmentInfo.getFragmentToken()); @@ -225,9 +226,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer newTopContainer = getTopActiveContainer( container.getTaskId()); if (newTopContainer != null) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); updateContainer(wct, newTopContainer); - mPresenter.applyTransaction(wct); } updateCallbackIfNecessary(); } @@ -236,7 +235,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void onTaskFragmentParentInfoChanged(int taskId, @NonNull Configuration parentConfig) { + public void onTaskFragmentParentInfoChanged(@NonNull WindowContainerTransaction wct, + int taskId, @NonNull Configuration parentConfig) { synchronized (mLock) { onTaskConfigurationChanged(taskId, parentConfig); if (isInPictureInPicture(parentConfig)) { @@ -256,7 +256,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen final TaskFragmentContainer container = containers.get(i); // Wait until onTaskFragmentAppeared to update new container. if (!container.isFinished() && !container.isWaitingActivityAppear()) { - mPresenter.updateContainer(container); + updateContainer(wct, container); } } updateCallbackIfNecessary(); @@ -264,7 +264,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @Override - public void onActivityReparentedToTask(int taskId, @NonNull Intent activityIntent, + public void onActivityReparentedToTask(@NonNull WindowContainerTransaction wct, + int taskId, @NonNull Intent activityIntent, @NonNull IBinder activityToken) { synchronized (mLock) { // If the activity belongs to the current app process, we treat it as a new activity @@ -275,10 +276,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // launching to top. We allow split as primary for activity reparent because the // activity may be split as primary before it is reparented out. In that case, we // want to show it as primary again when it is reparented back. - if (!resolveActivityToContainer(activity, true /* isOnReparent */)) { + if (!resolveActivityToContainer(wct, activity, true /* isOnReparent */)) { // When there is no embedding rule matched, try to place it in the top container // like a normal launch. - placeActivityInTopContainer(activity); + placeActivityInTopContainer(wct, activity); } updateCallbackIfNecessary(); return; @@ -293,7 +294,6 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // If the activity belongs to a different app process, we treat it as starting new // intent, since both actions might result in a new activity that should appear in an // organized TaskFragment. - final WindowContainerTransaction wct = new WindowContainerTransaction(); TaskFragmentContainer targetContainer = resolveStartActivityIntent(wct, taskId, activityIntent, null /* launchingActivity */); if (targetContainer == null) { @@ -306,14 +306,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), activityToken); - mPresenter.applyTransaction(wct); // Because the activity does not belong to the organizer process, we wait until // onTaskFragmentAppeared to trigger updateCallbackIfNecessary(). } } @Override - public void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType) { + public void onTaskFragmentError(@NonNull WindowContainerTransaction wct, + @Nullable TaskFragmentInfo taskFragmentInfo, int opType) { synchronized (mLock) { switch (opType) { case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT: @@ -329,10 +329,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } // Update the latest taskFragmentInfo and perform necessary clean-up - container.setInfo(taskFragmentInfo); + container.setInfo(wct, taskFragmentInfo); container.clearPendingAppearedActivities(); if (container.isEmpty()) { - mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); + mPresenter.cleanupContainer(wct, container, + false /* shouldFinishDependent */); } break; } @@ -343,7 +344,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } } - /** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */ + /** Called on receiving {@link #onTaskFragmentVanished} for cleanup. */ private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) { for (int i = mTaskContainers.size() - 1; i >= 0; i--) { final TaskContainer taskContainer = mTaskContainers.valueAt(i); @@ -422,10 +423,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @VisibleForTesting - void onActivityCreated(@NonNull Activity launchedActivity) { + @GuardedBy("mLock") + void onActivityCreated(@NonNull WindowContainerTransaction wct, + @NonNull Activity launchedActivity) { // TODO(b/229680885): we don't support launching into primary yet because we want to always // launch the new activity on top. - resolveActivityToContainer(launchedActivity, false /* isOnReparent */); + resolveActivityToContainer(wct, launchedActivity, false /* isOnReparent */); updateCallbackIfNecessary(); } @@ -440,7 +443,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen */ @VisibleForTesting @GuardedBy("mLock") - boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) { + boolean resolveActivityToContainer(@NonNull WindowContainerTransaction wct, + @NonNull Activity activity, boolean isOnReparent) { if (isInPictureInPicture(activity) || activity.isFinishing()) { // We don't embed activity when it is in PIP, or finishing. Return true since we don't // want any extra handling. @@ -472,12 +476,12 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // 1. Whether the new launched activity should always expand. if (shouldExpand(activity, null /* intent */)) { - expandActivity(activity); + expandActivity(wct, activity); return true; } // 2. Whether the new launched activity should launch a placeholder. - if (launchPlaceholderIfNecessary(activity, !isOnReparent)) { + if (launchPlaceholderIfNecessary(wct, activity, !isOnReparent)) { return true; } @@ -492,11 +496,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Can't find any activity below. return false; } - if (putActivitiesIntoSplitIfNecessary(activityBelow, activity)) { + if (putActivitiesIntoSplitIfNecessary(wct, activityBelow, activity)) { // Have split rule of [ activityBelow | launchedActivity ]. return true; } - if (isOnReparent && putActivitiesIntoSplitIfNecessary(activity, activityBelow)) { + if (isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, activityBelow)) { // Have split rule of [ launchedActivity | activityBelow]. return true; } @@ -519,19 +523,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Can't find the top activity on the other split TaskFragment. return false; } - if (putActivitiesIntoSplitIfNecessary(otherTopActivity, activity)) { + if (putActivitiesIntoSplitIfNecessary(wct, otherTopActivity, activity)) { // Have split rule of [ otherTopActivity | launchedActivity ]. return true; } // Have split rule of [ launchedActivity | otherTopActivity]. - return isOnReparent && putActivitiesIntoSplitIfNecessary(activity, otherTopActivity); + return isOnReparent && putActivitiesIntoSplitIfNecessary(wct, activity, otherTopActivity); } /** * Places the given activity to the top most TaskFragment in the task if there is any. */ @VisibleForTesting - void placeActivityInTopContainer(@NonNull Activity activity) { + void placeActivityInTopContainer(@NonNull WindowContainerTransaction wct, + @NonNull Activity activity) { if (getContainerWithActivity(activity) != null) { // The activity has already been put in a TaskFragment. This is likely to be done by // the server when the activity is started. @@ -547,20 +552,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return; } targetContainer.addPendingAppearedActivity(activity); - final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reparentActivityToTaskFragment(targetContainer.getTaskFragmentToken(), activity.getActivityToken()); - mPresenter.applyTransaction(wct); } /** * Starts an activity to side of the launchingActivity with the provided split config. */ - private void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent intent, + @GuardedBy("mLock") + private void startActivityToSide(@NonNull WindowContainerTransaction wct, + @NonNull Activity launchingActivity, @NonNull Intent intent, @Nullable Bundle options, @NonNull SplitRule sideRule, @Nullable Consumer failureCallback, boolean isPlaceholder) { try { - mPresenter.startActivityToSide(launchingActivity, intent, options, sideRule, + mPresenter.startActivityToSide(wct, launchingActivity, intent, options, sideRule, isPlaceholder); } catch (Exception e) { if (failureCallback != null) { @@ -573,15 +578,17 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Expands the given activity by either expanding the TaskFragment it is currently in or putting * it into a new expanded TaskFragment. */ - private void expandActivity(@NonNull Activity activity) { + @GuardedBy("mLock") + private void expandActivity(@NonNull WindowContainerTransaction wct, + @NonNull Activity activity) { final TaskFragmentContainer container = getContainerWithActivity(activity); if (shouldContainerBeExpanded(container)) { // Make sure that the existing container is expanded. - mPresenter.expandTaskFragment(container.getTaskFragmentToken()); + mPresenter.expandTaskFragment(wct, container.getTaskFragmentToken()); } else { // Put activity into a new expanded container. final TaskFragmentContainer newContainer = newContainer(activity, getTaskId(activity)); - mPresenter.expandActivity(newContainer.getTaskFragmentToken(), activity); + mPresenter.expandActivity(wct, newContainer.getTaskFragmentToken(), activity); } } @@ -667,8 +674,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * and returns {@code true}. Otherwise, returns {@code false}. */ @GuardedBy("mLock") - private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity, - @NonNull Activity secondaryActivity) { + private boolean putActivitiesIntoSplitIfNecessary(@NonNull WindowContainerTransaction wct, + @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity) { final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity); if (splitRule == null) { return false; @@ -686,23 +693,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return true; } secondaryContainer.addPendingAppearedActivity(secondaryActivity); - final WindowContainerTransaction wct = new WindowContainerTransaction(); if (mPresenter.expandSplitContainerIfNeeded(wct, splitContainer, primaryActivity, secondaryActivity, null /* secondaryIntent */) != RESULT_EXPAND_FAILED_NO_TF_INFO) { wct.reparentActivityToTaskFragment( secondaryContainer.getTaskFragmentToken(), secondaryActivity.getActivityToken()); - mPresenter.applyTransaction(wct); return true; } } // Create new split pair. - mPresenter.createNewSplitContainer(primaryActivity, secondaryActivity, splitRule); + mPresenter.createNewSplitContainer(wct, primaryActivity, secondaryActivity, splitRule); return true; } - private void onActivityConfigurationChanged(@NonNull Activity activity) { + @GuardedBy("mLock") + private void onActivityConfigurationChanged(@NonNull WindowContainerTransaction wct, + @NonNull Activity activity) { if (activity.isFinishing()) { // Do nothing if the activity is currently finishing. return; @@ -721,7 +728,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } // Check if activity requires a placeholder - launchPlaceholderIfNecessary(activity, false /* isOnCreated */); + launchPlaceholderIfNecessary(wct, activity, false /* isOnCreated */); } @VisibleForTesting @@ -741,7 +748,22 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * creation. */ void onTaskFragmentAppearEmptyTimeout(@NonNull TaskFragmentContainer container) { - mPresenter.cleanupContainer(container, false /* shouldFinishDependent */); + synchronized (mLock) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + onTaskFragmentAppearEmptyTimeout(wct, container); + mPresenter.applyTransaction(wct); + } + } + + /** + * Called when we have been waiting too long for the TaskFragment to become non-empty after + * creation. + */ + void onTaskFragmentAppearEmptyTimeout(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentContainer container) { + synchronized (mLock) { + mPresenter.cleanupContainer(wct, container, false /* shouldFinishDependent */); + } } /** @@ -971,6 +993,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } /** Cleanups all the dependencies when the TaskFragment is entering PIP. */ + @GuardedBy("mLock") private void cleanupForEnterPip(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { final TaskContainer taskContainer = container.getTaskContainer(); @@ -1084,9 +1107,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen * Updates the presentation of the container. If the container is part of the split or should * have a placeholder, it will also update the other part of the split. */ + @GuardedBy("mLock") void updateContainer(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentContainer container) { - if (launchPlaceholderIfNecessary(container)) { + if (launchPlaceholderIfNecessary(wct, container)) { // Placeholder was launched, the positions will be updated when the activity is added // to the secondary container. return; @@ -1111,7 +1135,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // Skip position update - one or both containers are finished. return; } - if (dismissPlaceholderIfNecessary(splitContainer)) { + if (dismissPlaceholderIfNecessary(wct, splitContainer)) { // Placeholder was finished, the positions will be updated when its container is emptied return; } @@ -1173,16 +1197,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen /** * Checks if the container requires a placeholder and launches it if necessary. */ - private boolean launchPlaceholderIfNecessary(@NonNull TaskFragmentContainer container) { + @GuardedBy("mLock") + private boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentContainer container) { final Activity topActivity = container.getTopNonFinishingActivity(); if (topActivity == null) { return false; } - return launchPlaceholderIfNecessary(topActivity, false /* isOnCreated */); + return launchPlaceholderIfNecessary(wct, topActivity, false /* isOnCreated */); } - boolean launchPlaceholderIfNecessary(@NonNull Activity activity, boolean isOnCreated) { + @GuardedBy("mLock") + boolean launchPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, + @NonNull Activity activity, boolean isOnCreated) { if (activity.isFinishing()) { return false; } @@ -1216,7 +1244,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // TODO(b/190433398): Handle failed request final Bundle options = getPlaceholderOptions(activity, isOnCreated); - startActivityToSide(activity, placeholderRule.getPlaceholderIntent(), options, + startActivityToSide(wct, activity, placeholderRule.getPlaceholderIntent(), options, placeholderRule, null /* failureCallback */, true /* isPlaceholder */); return true; } @@ -1243,7 +1271,9 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen } @VisibleForTesting - boolean dismissPlaceholderIfNecessary(@NonNull SplitContainer splitContainer) { + @GuardedBy("mLock") + boolean dismissPlaceholderIfNecessary(@NonNull WindowContainerTransaction wct, + @NonNull SplitContainer splitContainer) { if (!splitContainer.isPlaceholderContainer()) { return false; } @@ -1257,7 +1287,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return false; } - mPresenter.cleanupContainer(splitContainer.getSecondaryContainer(), + mPresenter.cleanupContainer(wct, splitContainer.getSecondaryContainer(), false /* shouldFinishDependent */); return true; } @@ -1560,14 +1590,18 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen // that we don't launch it if an activity itself already requested something to be // launched to side. synchronized (mLock) { - SplitController.this.onActivityCreated(activity); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + SplitController.this.onActivityCreated(wct, activity); + mPresenter.applyTransaction(wct); } } @Override public void onActivityConfigurationChanged(@NonNull Activity activity) { synchronized (mLock) { - SplitController.this.onActivityConfigurationChanged(activity); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + SplitController.this.onActivityConfigurationChanged(wct, activity); + mPresenter.applyTransaction(wct); } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java index bec832bc9ba29..2b069d72e46ff 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java @@ -108,31 +108,12 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { registerOrganizer(); } - /** - * Updates the presentation of the provided container. - */ - void updateContainer(@NonNull TaskFragmentContainer container) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - mController.updateContainer(wct, container); - applyTransaction(wct); - } - /** * Deletes the specified container and all other associated and dependent containers in the same * transaction. */ - void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - cleanupContainer(container, shouldFinishDependent, wct); - applyTransaction(wct); - } - - /** - * Deletes the specified container and all other associated and dependent containers in the same - * transaction. - */ - void cleanupContainer(@NonNull TaskFragmentContainer container, boolean shouldFinishDependent, - @NonNull WindowContainerTransaction wct) { + void cleanupContainer(@NonNull WindowContainerTransaction wct, + @NonNull TaskFragmentContainer container, boolean shouldFinishDependent) { container.finish(shouldFinishDependent, this, wct, mController); final TaskFragmentContainer newTopContainer = mController.getTopActiveContainer( @@ -190,10 +171,9 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * created and the activity will be re-parented to it. * @param rule The split rule to be applied to the container. */ - void createNewSplitContainer(@NonNull Activity primaryActivity, - @NonNull Activity secondaryActivity, @NonNull SplitPairRule rule) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - + void createNewSplitContainer(@NonNull WindowContainerTransaction wct, + @NonNull Activity primaryActivity, @NonNull Activity secondaryActivity, + @NonNull SplitPairRule rule) { final Rect parentBounds = getParentContainerBounds(primaryActivity); final Pair minDimensionsPair = getActivitiesMinDimensionsPair(primaryActivity, secondaryActivity); @@ -219,8 +199,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { minDimensionsPair); mController.registerSplit(wct, primaryContainer, primaryActivity, secondaryContainer, rule); - - applyTransaction(wct); } /** @@ -262,7 +240,8 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { * @param rule The split rule to be applied to the container. * @param isPlaceholder Whether the launch is a placeholder. */ - void startActivityToSide(@NonNull Activity launchingActivity, @NonNull Intent activityIntent, + void startActivityToSide(@NonNull WindowContainerTransaction wct, + @NonNull Activity launchingActivity, @NonNull Intent activityIntent, @Nullable Bundle activityOptions, @NonNull SplitRule rule, boolean isPlaceholder) { final Rect parentBounds = getParentContainerBounds(launchingActivity); final Pair minDimensionsPair = getActivityIntentMinDimensionsPair( @@ -284,7 +263,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { launchingActivity, taskId); final int windowingMode = mController.getTaskContainer(taskId) .getWindowingModeForSplitTaskFragment(primaryRectBounds); - final WindowContainerTransaction wct = new WindowContainerTransaction(); mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer, rule); startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds, @@ -294,7 +272,6 @@ class SplitPresenter extends JetpackTaskFragmentOrganizer { // When placeholder is launched in split, we should keep the focus on the primary. wct.requestFocusOnTaskFragment(primaryContainer.getTaskFragmentToken()); } - applyTransaction(wct); } /** diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index ae208f489e85e..11c0db3206463 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -251,19 +251,22 @@ class TaskFragmentContainer { return mInfo; } - void setInfo(@NonNull TaskFragmentInfo info) { + void setInfo(@NonNull WindowContainerTransaction wct, @NonNull TaskFragmentInfo info) { if (!mIsFinished && mInfo == null && info.isEmpty()) { // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no // pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if // it is still empty after timeout. - mAppearEmptyTimeout = () -> { - mAppearEmptyTimeout = null; - mController.onTaskFragmentAppearEmptyTimeout(this); - }; if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) { + mAppearEmptyTimeout = () -> { + mAppearEmptyTimeout = null; + // Call without the pass-in wct when timeout. We need to applyWct directly + // in this case. + mController.onTaskFragmentAppearEmptyTimeout(this); + }; mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS); } else { - mAppearEmptyTimeout.run(); + mAppearEmptyTimeout = null; + mController.onTaskFragmentAppearEmptyTimeout(wct, this); } } else if (mAppearEmptyTimeout != null && !info.isEmpty()) { mController.getHandler().removeCallbacks(mAppearEmptyTimeout); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java index 4d2595275f201..21cf7a6272a7c 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java @@ -56,6 +56,8 @@ import java.util.ArrayList; * Build/Install/Run: * atest WMJetpackUnitTests:JetpackTaskFragmentOrganizerTest */ +// Suppress GuardedBy warning on unit tests +@SuppressWarnings("GuardedBy") @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) @@ -119,7 +121,7 @@ public class JetpackTaskFragmentOrganizerTest { new Intent(), taskContainer, mSplitController); final TaskFragmentInfo info = createMockInfo(container); mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info); - container.setInfo(info); + container.setInfo(mTransaction, info); mOrganizer.expandTaskFragment(mTransaction, container.getTaskFragmentToken()); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java index 711a536a1cc39..07758d24ad94e 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java @@ -89,6 +89,8 @@ import java.util.List; * Build/Install/Run: * atest WMJetpackUnitTests:SplitControllerTest */ +// Suppress GuardedBy warning on unit tests +@SuppressWarnings("GuardedBy") @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) @@ -158,14 +160,14 @@ public class SplitControllerTest { final TaskFragmentInfo info = mock(TaskFragmentInfo.class); doReturn(new ArrayList<>()).when(info).getActivities(); doReturn(true).when(info).isEmpty(); - tf1.setInfo(info); + tf1.setInfo(mTransaction, info); assertWithMessage("Must return tf because we are waiting for tf1 to become non-empty after" + " creation.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isEqualTo(tf1); doReturn(false).when(info).isEmpty(); - tf1.setInfo(info); + tf1.setInfo(mTransaction, info); assertWithMessage("Must return null because tf1 becomes empty.") .that(mSplitController.getTopActiveContainer(TASK_ID)).isNull(); @@ -177,7 +179,7 @@ public class SplitControllerTest { doReturn(tf.getTaskFragmentToken()).when(mInfo).getFragmentToken(); // The TaskFragment has been removed in the server, we only need to cleanup the reference. - mSplitController.onTaskFragmentVanished(mInfo); + mSplitController.onTaskFragmentVanished(mTransaction, mInfo); verify(mSplitPresenter, never()).deleteTaskFragment(any(), any()); verify(mSplitController).removeContainer(tf); @@ -187,9 +189,10 @@ public class SplitControllerTest { @Test public void testOnTaskFragmentAppearEmptyTimeout() { final TaskFragmentContainer tf = mSplitController.newContainer(mActivity, TASK_ID); - mSplitController.onTaskFragmentAppearEmptyTimeout(tf); + mSplitController.onTaskFragmentAppearEmptyTimeout(mTransaction, tf); - verify(mSplitPresenter).cleanupContainer(tf, false /* shouldFinishDependent */); + verify(mSplitPresenter).cleanupContainer(mTransaction, tf, + false /* shouldFinishDependent */); } @Test @@ -229,8 +232,8 @@ public class SplitControllerTest { spyOn(tf); doReturn(mActivity).when(tf).getTopNonFinishingActivity(); doReturn(true).when(tf).isEmpty(); - doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mActivity, - false /* isOnCreated */); + doReturn(true).when(mSplitController).launchPlaceholderIfNecessary(mTransaction, + mActivity, false /* isOnCreated */); doNothing().when(mSplitPresenter).updateSplitContainer(any(), any(), any()); mSplitController.updateContainer(mTransaction, tf); @@ -250,7 +253,7 @@ public class SplitControllerTest { mSplitController.updateContainer(mTransaction, tf); - verify(mSplitController, never()).dismissPlaceholderIfNecessary(any()); + verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any()); // Verify if tf is not in the top splitContainer, final SplitContainer splitContainer = mock(SplitContainer.class); @@ -264,7 +267,7 @@ public class SplitControllerTest { mSplitController.updateContainer(mTransaction, tf); - verify(mSplitController, never()).dismissPlaceholderIfNecessary(any()); + verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any()); // Verify if one or both containers in the top SplitContainer are finished, // dismissPlaceholder() won't be called. @@ -273,12 +276,12 @@ public class SplitControllerTest { mSplitController.updateContainer(mTransaction, tf); - verify(mSplitController, never()).dismissPlaceholderIfNecessary(any()); + verify(mSplitController, never()).dismissPlaceholderIfNecessary(any(), any()); // Verify if placeholder should be dismissed, updateSplitContainer() won't be called. doReturn(false).when(tf).isFinished(); doReturn(true).when(mSplitController) - .dismissPlaceholderIfNecessary(splitContainer); + .dismissPlaceholderIfNecessary(mTransaction, splitContainer); mSplitController.updateContainer(mTransaction, tf); @@ -286,7 +289,7 @@ public class SplitControllerTest { // Verify if the top active split is updated if both of its containers are not finished. doReturn(false).when(mSplitController) - .dismissPlaceholderIfNecessary(splitContainer); + .dismissPlaceholderIfNecessary(mTransaction, splitContainer); mSplitController.updateContainer(mTransaction, tf); @@ -315,21 +318,23 @@ public class SplitControllerTest { @Test public void testOnActivityCreated() { - mSplitController.onActivityCreated(mActivity); + mSplitController.onActivityCreated(mTransaction, mActivity); // Disallow to split as primary because we want the new launch to be always on top. - verify(mSplitController).resolveActivityToContainer(mActivity, false /* isOnReparent */); + verify(mSplitController).resolveActivityToContainer(mTransaction, mActivity, + false /* isOnReparent */); } @Test public void testOnActivityReparentedToTask_sameProcess() { - mSplitController.onActivityReparentedToTask(TASK_ID, new Intent(), + mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, new Intent(), mActivity.getActivityToken()); // Treated as on activity created, but allow to split as primary. - verify(mSplitController).resolveActivityToContainer(mActivity, true /* isOnReparent */); + verify(mSplitController).resolveActivityToContainer(mTransaction, + mActivity, true /* isOnReparent */); // Try to place the activity to the top TaskFragment when there is no matched rule. - verify(mSplitController).placeActivityInTopContainer(mActivity); + verify(mSplitController).placeActivityInTopContainer(mTransaction, mActivity); } @Test @@ -339,10 +344,10 @@ public class SplitControllerTest { final IBinder activityToken = new Binder(); final Intent intent = new Intent(); - mSplitController.onActivityReparentedToTask(TASK_ID, intent, activityToken); + mSplitController.onActivityReparentedToTask(mTransaction, TASK_ID, intent, activityToken); // Treated as starting new intent - verify(mSplitController, never()).resolveActivityToContainer(any(), anyBoolean()); + verify(mSplitController, never()).resolveActivityToContainer(any(), any(), anyBoolean()); verify(mSplitController).resolveStartActivityIntent(any(), eq(TASK_ID), eq(intent), isNull()); } @@ -504,26 +509,29 @@ public class SplitControllerTest { @Test public void testPlaceActivityInTopContainer() { - mSplitController.placeActivityInTopContainer(mActivity); + mSplitController.placeActivityInTopContainer(mTransaction, mActivity); - verify(mSplitPresenter, never()).applyTransaction(any()); + verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any()); - mSplitController.newContainer(new Intent(), mActivity, TASK_ID); - mSplitController.placeActivityInTopContainer(mActivity); + // Place in the top container if there is no other rule matched. + final TaskFragmentContainer topContainer = mSplitController + .newContainer(new Intent(), mActivity, TASK_ID); + mSplitController.placeActivityInTopContainer(mTransaction, mActivity); - verify(mSplitPresenter).applyTransaction(any()); + verify(mTransaction).reparentActivityToTaskFragment(topContainer.getTaskFragmentToken(), + mActivity.getActivityToken()); // Not reparent if activity is in a TaskFragment. - clearInvocations(mSplitPresenter); + clearInvocations(mTransaction); mSplitController.newContainer(mActivity, TASK_ID); - mSplitController.placeActivityInTopContainer(mActivity); + mSplitController.placeActivityInTopContainer(mTransaction, mActivity); - verify(mSplitPresenter, never()).applyTransaction(any()); + verify(mTransaction, never()).reparentActivityToTaskFragment(any(), any()); } @Test public void testResolveActivityToContainer_noRuleMatched() { - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertFalse(result); @@ -535,7 +543,7 @@ public class SplitControllerTest { setupExpandRule(mActivity); // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it. - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); final TaskFragmentContainer container = mSplitController.getContainerWithActivity( mActivity); @@ -543,7 +551,8 @@ public class SplitControllerTest { assertTrue(result); assertNotNull(container); verify(mSplitController).newContainer(mActivity, TASK_ID); - verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity); + verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(), + mActivity); } @Test @@ -552,11 +561,11 @@ public class SplitControllerTest { // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it. final TaskFragmentContainer container = mSplitController.newContainer(mActivity, TASK_ID); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); - verify(mSplitPresenter).expandTaskFragment(container.getTaskFragmentToken()); + verify(mSplitPresenter).expandTaskFragment(mTransaction, container.getTaskFragmentToken()); } @Test @@ -566,14 +575,15 @@ public class SplitControllerTest { // When the activity is not in any TaskFragment, create a new expanded TaskFragment for it. final Activity activity = createMockActivity(); addSplitTaskFragments(activity, mActivity); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); final TaskFragmentContainer container = mSplitController.getContainerWithActivity( mActivity); assertTrue(result); assertNotNull(container); - verify(mSplitPresenter).expandActivity(container.getTaskFragmentToken(), mActivity); + verify(mSplitPresenter).expandActivity(mTransaction, container.getTaskFragmentToken(), + mActivity); } @Test @@ -583,11 +593,11 @@ public class SplitControllerTest { (SplitPlaceholderRule) mSplitController.getSplitRules().get(0); // Launch placeholder if the activity is not in any TaskFragment. - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); - verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT, + verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), placeholderRule, true /* isPlaceholder */); } @@ -600,11 +610,11 @@ public class SplitControllerTest { final Activity activity = createMockActivity(); mSplitController.newContainer(mActivity, TASK_ID); mSplitController.newContainer(activity, TASK_ID); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertFalse(result); - verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), + verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), anyBoolean()); } @@ -616,11 +626,11 @@ public class SplitControllerTest { // Launch placeholder if the activity is in the topmost expanded TaskFragment. mSplitController.newContainer(mActivity, TASK_ID); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); - verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT, + verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), placeholderRule, true /* isPlaceholder */); } @@ -632,11 +642,11 @@ public class SplitControllerTest { // Don't launch placeholder if the activity is in primary split. final Activity secondaryActivity = createMockActivity(); addSplitTaskFragments(mActivity, secondaryActivity); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertFalse(result); - verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), + verify(mSplitPresenter, never()).startActivityToSide(any(), any(), any(), any(), any(), anyBoolean()); } @@ -649,11 +659,11 @@ public class SplitControllerTest { // Launch placeholder if the activity is in secondary split. final Activity primaryActivity = createMockActivity(); addSplitTaskFragments(primaryActivity, mActivity); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); - verify(mSplitPresenter).startActivityToSide(mActivity, PLACEHOLDER_INTENT, + verify(mSplitPresenter).startActivityToSide(mTransaction, mActivity, PLACEHOLDER_INTENT, mSplitController.getPlaceholderOptions(mActivity, true /* isOnCreated */), placeholderRule, true /* isPlaceholder */); } @@ -676,7 +686,7 @@ public class SplitControllerTest { secondaryContainer, splitRule); clearInvocations(mSplitController); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); @@ -705,7 +715,7 @@ public class SplitControllerTest { final Activity launchedActivity = createMockActivity(); primaryContainer.addPendingAppearedActivity(launchedActivity); - assertFalse(mSplitController.resolveActivityToContainer(launchedActivity, + assertFalse(mSplitController.resolveActivityToContainer(mTransaction, launchedActivity, false /* isOnReparent */)); } @@ -717,7 +727,7 @@ public class SplitControllerTest { // Activity is already in secondary split, no need to create new split. addSplitTaskFragments(primaryActivity, mActivity); clearInvocations(mSplitController); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); @@ -735,7 +745,7 @@ public class SplitControllerTest { addSplitTaskFragments(primaryActivity, secondaryActivity); mSplitController.getContainerWithActivity(secondaryActivity) .addPendingAppearedActivity(mActivity); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertFalse(result); @@ -760,7 +770,7 @@ public class SplitControllerTest { mActivity, secondaryContainer, placeholderRule); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); @@ -774,7 +784,7 @@ public class SplitControllerTest { final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, TASK_ID); container.addPendingAppearedActivity(mActivity); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); @@ -790,14 +800,15 @@ public class SplitControllerTest { final TaskFragmentContainer container = mSplitController.newContainer(activityBelow, TASK_ID); container.addPendingAppearedActivity(mActivity); - boolean result = mSplitController.resolveActivityToContainer(mActivity, + boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertFalse(result); assertEquals(container, mSplitController.getContainerWithActivity(mActivity)); // Allow to split as primary. - result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */); + result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, + true /* isOnReparent */); assertTrue(result); assertSplitPair(mActivity, activityBelow); @@ -815,7 +826,7 @@ public class SplitControllerTest { final TaskFragmentContainer secondaryContainer = mSplitController.getContainerWithActivity( activityBelow); secondaryContainer.addPendingAppearedActivity(mActivity); - final boolean result = mSplitController.resolveActivityToContainer(mActivity, + final boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); final TaskFragmentContainer container = mSplitController.getContainerWithActivity( mActivity); @@ -836,14 +847,15 @@ public class SplitControllerTest { final TaskFragmentContainer primaryContainer = mSplitController.getContainerWithActivity( primaryActivity); primaryContainer.addPendingAppearedActivity(mActivity); - boolean result = mSplitController.resolveActivityToContainer(mActivity, + boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertFalse(result); assertEquals(primaryContainer, mSplitController.getContainerWithActivity(mActivity)); - result = mSplitController.resolveActivityToContainer(mActivity, true /* isOnReparent */); + result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, + true /* isOnReparent */); assertTrue(result); assertSplitPair(mActivity, primaryActivity); @@ -861,7 +873,7 @@ public class SplitControllerTest { container.addPendingAppearedActivity(mActivity); // Allow to split as primary. - boolean result = mSplitController.resolveActivityToContainer(mActivity, + boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, true /* isOnReparent */); assertTrue(result); @@ -879,15 +891,13 @@ public class SplitControllerTest { TASK_ID); container.addPendingAppearedActivity(mActivity); - boolean result = mSplitController.resolveActivityToContainer(mActivity, + boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */); } - // Suppress GuardedBy warning on unit tests - @SuppressWarnings("GuardedBy") @Test public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() { final Activity primaryActivity = createMockActivity(); @@ -899,14 +909,14 @@ public class SplitControllerTest { doReturn(secondaryActivity).when(mSplitController).findActivityBelow(eq(mActivity)); clearInvocations(mSplitPresenter); - boolean result = mSplitController.resolveActivityToContainer(mActivity, + boolean result = mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */); assertTrue(result); assertSplitPair(primaryActivity, mActivity, true /* matchParentBounds */); assertEquals(mSplitController.getContainerWithActivity(secondaryActivity), mSplitController.getContainerWithActivity(mActivity)); - verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any()); + verify(mSplitPresenter, never()).createNewSplitContainer(any(), any(), any(), any()); } @Test @@ -914,7 +924,7 @@ public class SplitControllerTest { doReturn(new Binder()).when(mSplitController).getInitialTaskFragmentToken(mActivity); // No need to handle when the new launched activity is in an unknown TaskFragment. - assertTrue(mSplitController.resolveActivityToContainer(mActivity, + assertTrue(mSplitController.resolveActivityToContainer(mTransaction, mActivity, false /* isOnReparent */)); } @@ -993,7 +1003,7 @@ public class SplitControllerTest { private void setupTaskFragmentInfo(@NonNull TaskFragmentContainer container, @NonNull Activity activity) { final TaskFragmentInfo info = createMockTaskFragmentInfo(container, activity); - container.setInfo(info); + container.setInfo(mTransaction, info); mSplitPresenter.mFragmentInfos.put(container.getTaskFragmentToken(), info); } diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java index d79319666c017..3fdf8e5d4c4dd 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java @@ -78,6 +78,8 @@ import org.mockito.MockitoAnnotations; * Build/Install/Run: * atest WMJetpackUnitTests:SplitPresenterTest */ +// Suppress GuardedBy warning on unit tests +@SuppressWarnings("GuardedBy") @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) @@ -226,8 +228,9 @@ public class SplitPresenterTest { mTransaction, splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); - primaryTf.setInfo(createMockTaskFragmentInfo(primaryTf, mActivity)); - secondaryTf.setInfo(createMockTaskFragmentInfo(secondaryTf, secondaryActivity)); + primaryTf.setInfo(mTransaction, createMockTaskFragmentInfo(primaryTf, mActivity)); + secondaryTf.setInfo(mTransaction, + createMockTaskFragmentInfo(secondaryTf, secondaryActivity)); assertEquals(RESULT_EXPANDED, mPresenter.expandSplitContainerIfNeeded(mTransaction, splitContainer, mActivity, secondaryActivity, null /* secondaryIntent */)); diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java index 44c7e6c611dee..6cbecff81be55 100644 --- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java +++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java @@ -19,6 +19,8 @@ package androidx.window.extensions.embedding; import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID; import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static org.junit.Assert.assertEquals; @@ -36,7 +38,6 @@ import static org.mockito.Mockito.never; import android.app.Activity; import android.content.Intent; import android.os.Binder; -import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; import android.window.TaskFragmentInfo; @@ -62,25 +63,27 @@ import java.util.List; * Build/Install/Run: * atest WMJetpackUnitTests:TaskFragmentContainerTest */ +// Suppress GuardedBy warning on unit tests +@SuppressWarnings("GuardedBy") @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class TaskFragmentContainerTest { @Mock private SplitPresenter mPresenter; - @Mock private SplitController mController; @Mock private TaskFragmentInfo mInfo; @Mock - private Handler mHandler; + private WindowContainerTransaction mTransaction; private Activity mActivity; private Intent mIntent; @Before public void setup() { MockitoAnnotations.initMocks(this); - doReturn(mHandler).when(mController).getHandler(); + mController = new SplitController(); + spyOn(mController); mActivity = createMockActivity(); mIntent = new Intent(); } @@ -123,7 +126,7 @@ public class TaskFragmentContainerTest { // Remove all references after the container has appeared in server. doReturn(new ArrayList<>()).when(mInfo).getActivities(); - container.setInfo(mInfo); + container.setInfo(mTransaction, mInfo); container.finish(true /* shouldFinishDependent */, mPresenter, wct, mController); verify(mActivity, never()).finish(); @@ -137,7 +140,7 @@ public class TaskFragmentContainerTest { final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity, null /* pendingAppearedIntent */, taskContainer, mController); final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity); - container0.setInfo(info); + container0.setInfo(mTransaction, info); // Request to reparent the activity to a new TaskFragment. final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity, null /* pendingAppearedIntent */, taskContainer, mController); @@ -163,7 +166,7 @@ public class TaskFragmentContainerTest { final TaskFragmentInfo info0 = createMockTaskFragmentInfo(pendingActivityContainer, mActivity); - pendingActivityContainer.setInfo(info0); + pendingActivityContainer.setInfo(mTransaction, info0); assertTrue(pendingActivityContainer.mPendingAppearedActivities.isEmpty()); @@ -175,7 +178,7 @@ public class TaskFragmentContainerTest { final TaskFragmentInfo info1 = createMockTaskFragmentInfo(pendingIntentContainer, mActivity); - pendingIntentContainer.setInfo(info1); + pendingIntentContainer.setInfo(mTransaction, info1); assertNull(pendingIntentContainer.getPendingAppearedIntent()); } @@ -191,18 +194,19 @@ public class TaskFragmentContainerTest { final TaskFragmentInfo info = mock(TaskFragmentInfo.class); doReturn(new ArrayList<>()).when(info).getActivities(); doReturn(true).when(info).isEmpty(); - container.setInfo(info); + container.setInfo(mTransaction, info); assertTrue(container.isWaitingActivityAppear()); doReturn(false).when(info).isEmpty(); - container.setInfo(info); + container.setInfo(mTransaction, info); assertFalse(container.isWaitingActivityAppear()); } @Test public void testAppearEmptyTimeout() { + doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any()); final TaskContainer taskContainer = new TaskContainer(TASK_ID); final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */, mIntent, taskContainer, mController); @@ -213,20 +217,20 @@ public class TaskFragmentContainerTest { final TaskFragmentInfo info = mock(TaskFragmentInfo.class); container.mInfo = null; doReturn(true).when(info).isEmpty(); - container.setInfo(info); + container.setInfo(mTransaction, info); assertNotNull(container.mAppearEmptyTimeout); // Not set if it is not appeared empty. doReturn(new ArrayList<>()).when(info).getActivities(); doReturn(false).when(info).isEmpty(); - container.setInfo(info); + container.setInfo(mTransaction, info); assertNull(container.mAppearEmptyTimeout); // Remove timeout after the container becomes non-empty. doReturn(false).when(info).isEmpty(); - container.setInfo(info); + container.setInfo(mTransaction, info); assertNull(container.mAppearEmptyTimeout); @@ -234,7 +238,7 @@ public class TaskFragmentContainerTest { container.mInfo = null; container.setPendingAppearedIntent(mIntent); doReturn(true).when(info).isEmpty(); - container.setInfo(info); + container.setInfo(mTransaction, info); container.mAppearEmptyTimeout.run(); assertNull(container.mAppearEmptyTimeout); @@ -260,7 +264,7 @@ public class TaskFragmentContainerTest { final List runningActivities = Lists.newArrayList(activity0.getActivityToken(), activity1.getActivityToken()); doReturn(runningActivities).when(mInfo).getActivities(); - container.setInfo(mInfo); + container.setInfo(mTransaction, mInfo); activities = container.collectNonFinishingActivities(); assertEquals(3, activities.size()); @@ -295,7 +299,7 @@ public class TaskFragmentContainerTest { final Activity activity = createMockActivity(); final List runningActivities = Lists.newArrayList(activity.getActivityToken()); doReturn(runningActivities).when(mInfo).getActivities(); - container.setInfo(mInfo); + container.setInfo(mTransaction, mInfo); assertEquals(activity, container.getBottomMostActivity()); } 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 9253b5e8af6f2..8332cb4209efa 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java @@ -204,7 +204,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mTaskFragment); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentInfoChanged(mTaskFragmentInfo); + verify(mOrganizer).onTaskFragmentInfoChanged(eq(mTaskFragmentInfo)); } @Test @@ -231,7 +231,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mOrganizer, never()).onTaskFragmentAppeared(any()); verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any()); - verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo); + verify(mOrganizer).onTaskFragmentVanished(eq(mTaskFragmentInfo)); // Not trigger onTaskFragmentInfoChanged. // Call onTaskFragmentAppeared before calling onTaskFragmentInfoChanged. @@ -247,7 +247,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { verify(mOrganizer, never()).onTaskFragmentAppeared(any()); verify(mOrganizer, never()).onTaskFragmentInfoChanged(any()); verify(mOrganizer, never()).onTaskFragmentParentInfoChanged(anyInt(), any()); - verify(mOrganizer).onTaskFragmentVanished(mTaskFragmentInfo); + verify(mOrganizer).onTaskFragmentVanished(eq(mTaskFragmentInfo)); } @Test @@ -298,7 +298,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mErrorToken, null /* taskFragment */, -1 /* opType */, exception); mController.dispatchPendingEvents(); - verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(null), eq(-1), eq(exception)); + verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(null), eq(-1), + eq(exception)); } @Test @@ -324,8 +325,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.onActivityReparentedToTask(activity); mController.dispatchPendingEvents(); - verify(mOrganizer).onActivityReparentedToTask(task.mTaskId, activity.intent, - activity.token); + verify(mOrganizer).onActivityReparentedToTask(eq(task.mTaskId), eq(activity.intent), + eq(activity.token)); // Notify organizer if there is any embedded in the Task. final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm) @@ -340,7 +341,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase { mController.dispatchPendingEvents(); verify(mOrganizer, times(2)) - .onActivityReparentedToTask(task.mTaskId, activity.intent, activity.token); + .onActivityReparentedToTask(eq(task.mTaskId), eq(activity.intent), + eq(activity.token)); } @Test