Enhance error handling logic to restore split-screen

Unusual use cases result in the main split empty. This CL uses focus
listener to improve error handling logic which restores split screen
from unexpected poor status.

Bug: 235193477
Test: atest MultiWindowTests
      atest WMShellUnitTests
Change-Id: I636c33821699c9774f687a89fc31b8de2859df7e
This commit is contained in:
Jeff Chang
2022-07-13 18:19:43 +08:00
parent 9e73750458
commit a7931fdaa2
4 changed files with 56 additions and 32 deletions

View File

@@ -499,7 +499,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
|| (taskInfo.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME
&& taskInfo.isVisible);
final boolean focusTaskChanged = (mLastFocusedTaskInfo == null
|| mLastFocusedTaskInfo.taskId != taskInfo.taskId) && isFocusedOrHome;
|| mLastFocusedTaskInfo.taskId != taskInfo.taskId
|| mLastFocusedTaskInfo.getWindowingMode() != taskInfo.getWindowingMode())
&& isFocusedOrHome;
if (focusTaskChanged) {
for (int i = 0; i < mFocusListeners.size(); i++) {
mFocusListeners.valueAt(i).onFocusTaskChanged(taskInfo);

View File

@@ -18,16 +18,12 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -66,7 +62,6 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -104,7 +99,7 @@ import java.util.concurrent.Executor;
*/
// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
public class SplitScreenController implements DragAndDropPolicy.Starter,
RemoteCallable<SplitScreenController>, ShellTaskOrganizer.FocusListener {
RemoteCallable<SplitScreenController> {
private static final String TAG = SplitScreenController.class.getSimpleName();
static final int EXIT_REASON_UNKNOWN = 0;
@@ -152,8 +147,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mSplitTasksContainerLayer;
private ActivityManager.RunningTaskInfo mFocusingTaskInfo;
public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
@@ -175,7 +168,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
mTaskOrganizer.addFocusListener(this);
}
public SplitScreen asSplitScreen() {
@@ -192,11 +184,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mMainExecutor;
}
@Override
public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
mFocusingTaskInfo = taskInfo;
}
public void onOrganizerRegistered() {
if (mStageCoordinator == null) {
// TODO: Multi-display
@@ -215,6 +202,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator;
}
public ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
return mStageCoordinator.getFocusingTaskInfo();
}
public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
return mStageCoordinator.isValidToEnterSplitScreen(taskInfo);
}
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) {
@@ -230,12 +225,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
&& mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
}
public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
return taskInfo.supportsMultiWindow
&& ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
&& ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
}
public @SplitPosition int getSplitPosition(int taskId) {
return mStageCoordinator.getSplitPosition(taskId);
}
@@ -472,8 +461,9 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return Objects.equals(launchingActivity, pairedActivity);
}
if (mFocusingTaskInfo != null && isValidToEnterSplitScreen(mFocusingTaskInfo)) {
return Objects.equals(mFocusingTaskInfo.baseIntent.getComponent(), launchingActivity);
final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
}
return false;

View File

@@ -35,6 +35,8 @@ import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -98,6 +100,7 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -138,7 +141,7 @@ import java.util.Optional;
*/
public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
ShellTaskOrganizer.TaskListener {
ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.FocusListener {
private static final String TAG = StageCoordinator.class.getSimpleName();
@@ -176,6 +179,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
private ActivityManager.RunningTaskInfo mFocusingTaskInfo;
/**
* A single-top root task which the split divider attached to.
*/
@@ -254,6 +259,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayController.addDisplayWindowListener(this);
mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
mTaskOrganizer.addFocusListener(this);
}
@VisibleForTesting
@@ -1186,12 +1192,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED);
}
} else if (isSideStage && !mMainStage.isActive()) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSplitLayout.init();
prepareEnterSplitScreen(wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t ->
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
if (mFocusingTaskInfo != null && !isValidToEnterSplitScreen(mFocusingTaskInfo)) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSideStage.removeAllTasks(wct, true);
wct.reorder(mRootTaskInfo.token, false /* onTop */);
mTaskOrganizer.applyTransaction(wct);
Slog.i(TAG, "cancel entering split screen, reason = "
+ exitReasonToString(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW));
} else {
final WindowContainerTransaction wct = new WindowContainerTransaction();
mSplitLayout.init();
prepareEnterSplitScreen(wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t ->
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
}
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
@@ -1206,6 +1221,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
return taskInfo.supportsMultiWindow
&& ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
&& ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
}
ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
return mFocusingTaskInfo;
}
@Override
public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
mFocusingTaskInfo = taskInfo;
}
@Override
public void onSnappedToDismiss(boolean bottomOrRight) {
final boolean mainStageToTop =

View File

@@ -24,6 +24,7 @@ import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSIT
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -90,12 +91,13 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Test
public void testIsLaunchingAdjacently_notInSplitScreen() {
doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
// Verify launching the same activity returns true.
Intent startIntent = createStartIntent("startActivity");
ActivityManager.RunningTaskInfo focusTaskInfo =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
mSplitScreenController.onFocusTaskChanged(focusTaskInfo);
doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
assertTrue(mSplitScreenController.isLaunchingAdjacently(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
@@ -103,7 +105,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
Intent diffIntent = createStartIntent("diffActivity");
focusTaskInfo =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
mSplitScreenController.onFocusTaskChanged(focusTaskInfo);
doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
assertFalse(mSplitScreenController.isLaunchingAdjacently(
startIntent, SPLIT_POSITION_TOP_OR_LEFT));
}