From 0e5ebc1c7bfafecd9e263e8107306c659808c3b4 Mon Sep 17 00:00:00 2001 From: wilsonshih Date: Thu, 20 Apr 2023 03:54:31 +0000 Subject: [PATCH] Fix stage stay empty if close transition request before task vanished The close transition will not contain trigger task if there are multiple activities in the task, and only finish the top activity. So the StageCooridinator won't prepare pending transition for it. But the whole activities might be finished during collecting, so when transition ready, the task should be closed and dismiss split screen if the task is the last child in the stage. When above case happen, prepare the dismiss transition at startAnimation. Bug: 278060807 Test: manual, Launch app info, entering split with another task, then press back to dismiss app info. Verify another task should become fullscreen. Test: manual, entering split, create a new task in one of stage, verify nothing break. Change-Id: I61c23511723d93f7a99eb2b2b08ab0b7dc118dcb --- .../shell/splitscreen/StageCoordinator.java | 69 ++++++++++++++++++- 1 file changed, 66 insertions(+), 3 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index e4f27240b2cb8..ceef373182fac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -89,6 +89,9 @@ import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.IntArray; import android.util.Log; import android.util.Slog; import android.view.Choreographer; @@ -2390,6 +2393,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!mMainStage.isActive()) return false; mSplitLayout.setFreezeDividerWindow(false); + final StageChangeRecord record = new StageChangeRecord(); for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); if (change.getMode() == TRANSIT_CHANGE @@ -2405,20 +2409,28 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (!stage.containsTask(taskInfo.taskId)) { Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called" + " with " + taskInfo.taskId + " before startAnimation()."); + record.addRecord(stage, true, taskInfo.taskId); } } else if (isClosingType(change.getMode())) { if (stage.containsTask(taskInfo.taskId)) { + record.addRecord(stage, false, taskInfo.taskId); Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called" + " with " + taskInfo.taskId + " before startAnimation()."); } } } - if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { + // If the size of dismissStages > 0, the task is closed without prepare pending + // transition, which could happen if all activities were finished after finish top + // activity in a task, so the trigger task is null when handleRequest. + final ArraySet dismissStages = record.getShouldDismissedStage(); + if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0 + || dismissStages.size() > 0) { Log.e(TAG, "Somehow removed the last task in a stage outside of a proper " + "transition."); final WindowContainerTransaction wct = new WindowContainerTransaction(); - final int dismissTop = mMainStage.getChildCount() == 0 - ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + final int dismissTop = (dismissStages.size() == 1 + && getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN) + || mMainStage.getChildCount() == 0 ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN; prepareExitSplitScreen(dismissTop, wct); mSplitTransitions.startDismissTransition(wct, this, dismissTop, EXIT_REASON_UNKNOWN); @@ -2445,6 +2457,57 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, finishCallback); } + static class StageChangeRecord { + static class StageChange { + final StageTaskListener mStageTaskListener; + final IntArray mAddedTaskId = new IntArray(); + final IntArray mRemovedTaskId = new IntArray(); + StageChange(StageTaskListener stage) { + mStageTaskListener = stage; + } + + boolean shouldDismissStage() { + if (mAddedTaskId.size() > 0 || mRemovedTaskId.size() == 0) { + return false; + } + int removeChildTaskCount = 0; + for (int i = mRemovedTaskId.size() - 1; i >= 0; --i) { + if (mStageTaskListener.containsTask(mRemovedTaskId.get(i))) { + ++removeChildTaskCount; + } + } + return removeChildTaskCount == mStageTaskListener.getChildCount(); + } + } + private final ArrayMap mChanges = new ArrayMap<>(); + + void addRecord(StageTaskListener stage, boolean open, int taskId) { + final StageChange next; + if (!mChanges.containsKey(stage)) { + next = new StageChange(stage); + mChanges.put(stage, next); + } else { + next = mChanges.get(stage); + } + if (open) { + next.mAddedTaskId.add(taskId); + } else { + next.mRemovedTaskId.add(taskId); + } + } + + ArraySet getShouldDismissedStage() { + final ArraySet dismissTarget = new ArraySet<>(); + for (int i = mChanges.size() - 1; i >= 0; --i) { + final StageChange change = mChanges.valueAt(i); + if (change.shouldDismissStage()) { + dismissTarget.add(change.mStageTaskListener); + } + } + return dismissTarget; + } + } + /** Starts the pending transition animation. */ public boolean startPendingAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,