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