Handle the uncontrolled task in split-screen

Exit the split-screen in case users put the uncontrolled task into
split-screen. This CL do below change to support handling this task.
   1. If no controlling activity appeared, dismiss the split screen
      and show a toast to inform users.
   2. Reparent all tasks out of split root when dismissing instead of
      just reparent controlling tasks.
   3. Disable the split-item from launcher for tasks that are
      uncontrolled.

Bug: 230461945
Bug: 208646963
Bug: 238032411
Test: atest MultiWindowTests
      atest WMShellUnitTests
Change-Id: Iab59561e9d532b10dab8f5063047c4b68b73eb87
This commit is contained in:
Jeff Chang
2022-07-04 17:40:11 +08:00
parent c2df8ddade
commit 89f86c1813
9 changed files with 65 additions and 29 deletions

View File

@@ -348,8 +348,10 @@ public final class WindowContainerTransaction implements Parcelable {
* @param currentParent of the tasks to perform the operation no.
* {@code null} will perform the operation on the display.
* @param newParent for the tasks. {@code null} will perform the operation on the display.
* @param windowingModes of the tasks to reparent.
* @param activityTypes of the tasks to reparent.
* @param windowingModes of the tasks to reparent. {@code null} ignore this attribute when
* perform the operation.
* @param activityTypes of the tasks to reparent. {@code null} ignore this attribute when
* perform the operation.
* @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
* the bottom.
*/

View File

@@ -15,6 +15,11 @@
*/
package com.android.wm.shell.common.split;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import android.annotation.IntDef;
/** Helper utility class of methods and constants that are available to be imported in Launcher. */
@@ -44,4 +49,10 @@ public class SplitScreenConstants {
})
public @interface SplitPosition {
}
public static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
public static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
public static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
}

View File

@@ -16,6 +16,9 @@
package com.android.wm.shell.splitscreen;
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 android.content.Context;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -74,10 +77,10 @@ class MainStage extends StageTaskListener {
if (mRootTaskInfo == null) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
wct.reparentTasks(
rootToken,
null /* newParent */,
CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
CONTROLLED_ACTIVITY_TYPES,
toTop);
rootToken,
null /* newParent */,
null /* windowingModes */,
null /* activityTypes */,
toTop);
}
}

View File

@@ -47,8 +47,8 @@ class SideStage extends StageTaskListener {
wct.reparentTasks(
mRootTaskInfo.token,
null /* newParent */,
CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
CONTROLLED_ACTIVITY_TYPES,
null /* windowingModes */,
null /* activityTypes */,
toTop);
return true;
}

View File

@@ -26,6 +26,8 @@ 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;
@@ -64,6 +66,7 @@ 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;
@@ -227,6 +230,12 @@ 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);
}
@@ -463,11 +472,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return Objects.equals(launchingActivity, pairedActivity);
}
if (mFocusingTaskInfo != null
// TODO (b/238032411): have an API to determine whether an activity is valid for
// split screen or not.
&& mFocusingTaskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
&& mFocusingTaskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
if (mFocusingTaskInfo != null && isValidToEnterSplitScreen(mFocusingTaskInfo)) {
return Objects.equals(mFocusingTaskInfo.baseIntent.getComponent(), launchingActivity);
}

View File

@@ -87,6 +87,7 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
import android.widget.Toast;
import android.window.DisplayAreaInfo;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
@@ -98,6 +99,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -1920,10 +1922,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onNoLongerSupportMultiWindow() {
if (mMainStage.isActive()) {
final Toast splitUnsupportedToast = Toast.makeText(mContext,
R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
final boolean isMainStage = mMainStageListener == this;
if (!ENABLE_SHELL_TRANSITIONS) {
StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
splitUnsupportedToast.show();
return;
}
@@ -1932,6 +1937,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareExitSplitScreen(stageType, wct);
mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
splitUnsupportedToast.show();
}
}

View File

@@ -17,12 +17,12 @@
package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.annotation.CallSuper;
@@ -40,6 +40,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SurfaceUtils;
@@ -62,12 +63,6 @@ import java.util.function.Predicate;
class StageTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = StageTaskListener.class.getSimpleName();
protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
protected static final int[] CONTROLLED_WINDOWING_MODES =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
{WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
/** Callback interface for listening to changes in a split-screen stage. */
public interface StageListenerCallbacks {
void onRootTaskAppeared();
@@ -212,12 +207,15 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
mRootTaskInfo = taskInfo;
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
if (!taskInfo.supportsMultiWindow) {
// Leave split screen if the task no longer supports multi window.
if (!taskInfo.supportsMultiWindow
|| !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
|| !ArrayUtils.contains(CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
taskInfo.getWindowingMode())) {
// Leave split screen if the task no longer supports multi window or have
// uncontrolled task.
mCallbacks.onNoLongerSupportMultiWindow();
return;
}
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
taskInfo.isVisible);

View File

@@ -18,6 +18,9 @@ package com.android.systemui.shared.recents.model;
import static android.view.Display.DEFAULT_DISPLAY;
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 android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.app.TaskInfo;
@@ -31,6 +34,8 @@ import android.view.ViewDebug;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
import java.util.Objects;
@@ -242,8 +247,10 @@ public class Task {
ActivityManager.TaskDescription td = taskInfo.taskDescription;
return new Task(taskKey,
td != null ? td.getPrimaryColor() : 0,
td != null ? td.getBackgroundColor() : 0,
taskInfo.supportsMultiWindow, isLocked, td, taskInfo.topActivity);
td != null ? td.getBackgroundColor() : 0, taskInfo.supportsMultiWindow
&& ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
&& ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode()),
isLocked, td, taskInfo.topActivity);
}
public Task(TaskKey key) {

View File

@@ -1235,8 +1235,12 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
+ " task=" + task);
return false;
}
if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())
|| !ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) {
if (!ArrayUtils.isEmpty(hop.getActivityTypes())
&& !ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())) {
return false;
}
if (!ArrayUtils.isEmpty(hop.getWindowingModes())
&& !ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) {
return false;
}
if (isLockTaskModeViolation(finalNewParent, task, isInLockTaskMode)) {