summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java216
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java130
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java277
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java147
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt130
24 files changed, 812 insertions, 492 deletions
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index df5f921f3a62..c6197c8a730b 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -111,4 +111,8 @@
<!-- Whether to dim a split-screen task when the other is the IME target -->
<bool name="config_dimNonImeAttachedSide">true</bool>
+
+ <!-- Components support to launch multiple instances into split-screen -->
+ <string-array name="config_componentsSupportMultiInstancesSplit">
+ </string-array>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 43679364b443..e58e785850fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -16,14 +16,12 @@
package com.android.wm.shell;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
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.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
@@ -48,7 +46,6 @@ import android.window.StartingWindowInfo;
import android.window.StartingWindowRemovalInfo;
import android.window.TaskAppearedInfo;
import android.window.TaskOrganizer;
-import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
@@ -567,6 +564,22 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
}
}
+ /**
+ * Return list of {@link RunningTaskInfo}s for the given display.
+ *
+ * @return filtered list of tasks or empty list
+ */
+ public ArrayList<RunningTaskInfo> getRunningTasks(int displayId) {
+ ArrayList<RunningTaskInfo> result = new ArrayList<>();
+ for (int i = 0; i < mTasks.size(); i++) {
+ RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
+ if (taskInfo.displayId == displayId) {
+ result.add(taskInfo);
+ }
+ }
+ return result;
+ }
+
/** Gets running task by taskId. Returns {@code null} if no such task observed. */
@Nullable
public RunningTaskInfo getRunningTaskInfo(int taskId) {
@@ -693,57 +706,6 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
taskListener.reparentChildSurfaceToTask(taskId, sc, t);
}
- /**
- * Create a {@link WindowContainerTransaction} to clear task bounds.
- *
- * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
- * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- *
- * @param displayId display id for tasks that will have bounds cleared
- * @return {@link WindowContainerTransaction} with pending operations to clear bounds
- */
- public WindowContainerTransaction prepareClearBoundsForStandardTasks(int displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks: displayId=%d", displayId);
- WindowContainerTransaction wct = new WindowContainerTransaction();
- for (int i = 0; i < mTasks.size(); i++) {
- RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if ((taskInfo.displayId == displayId) && (taskInfo.getActivityType()
- == WindowConfiguration.ACTIVITY_TYPE_STANDARD)) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
- taskInfo.token, taskInfo);
- wct.setBounds(taskInfo.token, null);
- }
- }
- return wct;
- }
-
- /**
- * Create a {@link WindowContainerTransaction} to clear task level freeform setting.
- *
- * Only affects tasks that have {@link RunningTaskInfo#getActivityType()} set to
- * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
- *
- * @param displayId display id for tasks that will have windowing mode reset to {@link
- * WindowConfiguration#WINDOWING_MODE_UNDEFINED}
- * @return {@link WindowContainerTransaction} with pending operations to clear windowing mode
- */
- public WindowContainerTransaction prepareClearFreeformForStandardTasks(int displayId) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks: displayId=%d", displayId);
- WindowContainerTransaction wct = new WindowContainerTransaction();
- for (int i = 0; i < mTasks.size(); i++) {
- RunningTaskInfo taskInfo = mTasks.valueAt(i).getTaskInfo();
- if (taskInfo.displayId == displayId
- && taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
- && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
- "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
- taskInfo);
- wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- }
- }
- return wct;
- }
-
private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info,
int event) {
ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS
new file mode 100644
index 000000000000..1e0f9bc6322f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/OWNERS
@@ -0,0 +1,5 @@
+# WM shell sub-module back navigation owners
+# Bug component: 1152663
+shanh@google.com
+arthurhung@google.com
+wilsonshih@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 8bc16bcc9d9d..1474754fc7f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -46,8 +46,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.policy.DividerSnapAlgorithm;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
/**
* Divider for multi window splits.
@@ -364,8 +366,11 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
mViewHost.relayout(lp);
}
- void setInteractive(boolean interactive) {
+ void setInteractive(boolean interactive, String from) {
if (interactive == mInteractive) return;
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
+ from);
mInteractive = interactive;
releaseTouching();
mHandle.setVisibility(mInteractive ? View.VISIBLE : View.INVISIBLE);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 74f8bf9ac863..5b7ed278e843 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -48,6 +48,7 @@ import androidx.annotation.NonNull;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
+import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.SurfaceUtils;
import java.util.function.Consumer;
@@ -74,10 +75,14 @@ public class SplitDecorManager extends WindowlessWindowManager {
private boolean mShown;
private boolean mIsResizing;
- private Rect mBounds = new Rect();
+ private final Rect mBounds = new Rect();
+ private final Rect mResizingBounds = new Rect();
+ private final Rect mTempRect = new Rect();
private ValueAnimator mFadeAnimator;
private int mIconSize;
+ private int mOffsetX;
+ private int mOffsetY;
public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
SurfaceSession surfaceSession) {
@@ -158,7 +163,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
- Rect sideBounds, SurfaceControl.Transaction t) {
+ Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY) {
if (mResizingIconView == null) {
return;
}
@@ -167,6 +172,9 @@ public class SplitDecorManager extends WindowlessWindowManager {
mIsResizing = true;
mBounds.set(newBounds);
}
+ mResizingBounds.set(newBounds);
+ mOffsetX = offsetX;
+ mOffsetY = offsetY;
final boolean show =
newBounds.width() > mBounds.width() || newBounds.height() > mBounds.height();
@@ -221,11 +229,37 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Stops showing resizing hint. */
public void onResized(SurfaceControl.Transaction t) {
+ if (!mShown && mIsResizing) {
+ mTempRect.set(mResizingBounds);
+ mTempRect.offsetTo(-mOffsetX, -mOffsetY);
+ final SurfaceControl screenshot = ScreenshotUtils.takeScreenshot(t,
+ mHostLeash, mTempRect, Integer.MAX_VALUE - 1);
+
+ final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
+ final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
+ va.addUpdateListener(valueAnimator -> {
+ final float progress = (float) valueAnimator.getAnimatedValue();
+ animT.setAlpha(screenshot, progress);
+ animT.apply();
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
+ animT.remove(screenshot);
+ animT.apply();
+ animT.close();
+ }
+ });
+ va.start();
+ }
+
if (mResizingIconView == null) {
return;
}
mIsResizing = false;
+ mOffsetX = 0;
+ mOffsetY = 0;
if (mFadeAnimator != null && mFadeAnimator.isRunning()) {
if (!mShown) {
// If fade-out animation is running, just add release callback to it.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 295a2e3c4244..3de1045bfbda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -448,7 +448,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
*/
void updateDivideBounds(int position) {
updateBounds(position);
- mSplitLayoutHandler.onLayoutSizeChanging(this);
+ mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
+ mSurfaceEffectPolicy.mParallaxOffset.y);
}
void setDividePosition(int position, boolean applyLayoutChange) {
@@ -600,7 +601,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
animator.start();
}
- /** Swich both surface position with animation. */
+ /** Switch both surface position with animation. */
public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
SurfaceControl leash2, Consumer<Rect> finishCallback) {
final boolean isLandscape = isLandscape();
@@ -811,7 +812,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
* @see #applySurfaceChanges(SurfaceControl.Transaction, SurfaceControl, SurfaceControl,
* SurfaceControl, SurfaceControl, boolean)
*/
- void onLayoutSizeChanging(SplitLayout layout);
+ void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY);
/**
* Calls when finish resizing the split bounds.
@@ -1092,7 +1093,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// ImePositionProcessor#onImeVisibilityChanged directly in DividerView is not enough
// because DividerView won't receive onImeVisibilityChanged callback after it being
// re-inflated.
- mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus);
+ mSplitWindowManager.setInteractive(!mImeShown || !mHasImeFocus,
+ "onImeStartPositioning");
return needOffset ? IME_ANIMATION_NO_ALPHA : 0;
}
@@ -1118,7 +1120,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// Restore the split layout when wm-shell is not controlling IME insets anymore.
if (!controlling && mImeShown) {
reset();
- mSplitWindowManager.setInteractive(true);
+ mSplitWindowManager.setInteractive(true, "onImeControlTargetChanged");
mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 864b9a7528b0..060ac56cae96 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -166,9 +166,9 @@ public final class SplitWindowManager extends WindowlessWindowManager {
}
}
- void setInteractive(boolean interactive) {
+ void setInteractive(boolean interactive, String from) {
if (mDividerView == null) return;
- mDividerView.setInteractive(interactive);
+ mDividerView.setInteractive(interactive, from);
}
View getDividerView() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index 34ff6d814c8d..abc4024bc290 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -16,8 +16,11 @@
package com.android.wm.shell.desktopmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -151,21 +154,18 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
int displayId = mContext.getDisplayId();
+ ArrayList<RunningTaskInfo> runningTasks = mShellTaskOrganizer.getRunningTasks(displayId);
+
WindowContainerTransaction wct = new WindowContainerTransaction();
- // Reset freeform windowing mode that is set per task level (tasks should inherit
- // container value)
- wct.merge(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(displayId),
- true /* transfer */);
- int targetWindowingMode;
+ // Reset freeform windowing mode that is set per task level so tasks inherit it
+ clearFreeformForStandardTasks(runningTasks, wct);
if (active) {
- targetWindowingMode = WINDOWING_MODE_FREEFORM;
+ moveHomeBehindVisibleTasks(runningTasks, wct);
+ setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FREEFORM, wct);
} else {
- targetWindowingMode = WINDOWING_MODE_FULLSCREEN;
- // Clear any resized bounds
- wct.merge(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(displayId),
- true /* transfer */);
+ clearBoundsForStandardTasks(runningTasks, wct);
+ setDisplayAreaWindowingMode(displayId, WINDOWING_MODE_FULLSCREEN, wct);
}
- prepareWindowingModeChange(wct, displayId, targetWindowingMode);
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.startTransition(TRANSIT_CHANGE, wct, null);
} else {
@@ -173,17 +173,69 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
}
}
- private void prepareWindowingModeChange(WindowContainerTransaction wct,
- int displayId, @WindowConfiguration.WindowingMode int windowingMode) {
- DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer
- .getDisplayAreaInfo(displayId);
+ private WindowContainerTransaction clearBoundsForStandardTasks(
+ ArrayList<RunningTaskInfo> runningTasks, WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearBoundsForTasks");
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "clearing bounds for token=%s taskInfo=%s",
+ taskInfo.token, taskInfo);
+ wct.setBounds(taskInfo.token, null);
+ }
+ }
+ return wct;
+ }
+
+ private void clearFreeformForStandardTasks(ArrayList<RunningTaskInfo> runningTasks,
+ WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "prepareClearFreeformForTasks");
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE,
+ "clearing windowing mode for token=%s taskInfo=%s", taskInfo.token,
+ taskInfo);
+ wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ }
+ }
+ }
+
+ private void moveHomeBehindVisibleTasks(ArrayList<RunningTaskInfo> runningTasks,
+ WindowContainerTransaction wct) {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks");
+ RunningTaskInfo homeTask = null;
+ ArrayList<RunningTaskInfo> visibleTasks = new ArrayList<>();
+ for (RunningTaskInfo taskInfo : runningTasks) {
+ if (taskInfo.getActivityType() == ACTIVITY_TYPE_HOME) {
+ homeTask = taskInfo;
+ } else if (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
+ && taskInfo.isVisible()) {
+ visibleTasks.add(taskInfo);
+ }
+ }
+ if (homeTask == null) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: home task not found");
+ } else {
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveHomeBehindVisibleTasks: visible tasks %d",
+ visibleTasks.size());
+ wct.reorder(homeTask.getToken(), true /* onTop */);
+ for (RunningTaskInfo task : visibleTasks) {
+ wct.reorder(task.getToken(), true /* onTop */);
+ }
+ }
+ }
+
+ private void setDisplayAreaWindowingMode(int displayId,
+ @WindowConfiguration.WindowingMode int windowingMode, WindowContainerTransaction wct) {
+ DisplayAreaInfo displayAreaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ displayId);
if (displayAreaInfo == null) {
ProtoLog.e(WM_SHELL_DESKTOP_MODE,
"unable to update windowing mode for display %d display not found", displayId);
return;
}
- ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+ ProtoLog.v(WM_SHELL_DESKTOP_MODE,
"setWindowingMode: displayId=%d current wmMode=%d new wmMode=%d", displayId,
displayAreaInfo.configuration.windowConfiguration.getWindowingMode(),
windowingMode);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index f4888fbb2bb9..168f6d79a390 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -98,9 +98,11 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
switch (change.getMode()) {
case WindowManager.TRANSIT_OPEN:
- case WindowManager.TRANSIT_TO_FRONT:
onOpenTransitionReady(change, startT, finishT);
break;
+ case WindowManager.TRANSIT_TO_FRONT:
+ onToFrontTransitionReady(change, startT, finishT);
+ break;
case WindowManager.TRANSIT_CLOSE: {
taskInfoList.add(change.getTaskInfo());
onCloseTransitionReady(change, startT, finishT);
@@ -138,6 +140,21 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
change.getTaskInfo(), startT, finishT);
}
+ private void onToFrontTransitionReady(
+ TransitionInfo.Change change,
+ SurfaceControl.Transaction startT,
+ SurfaceControl.Transaction finishT) {
+ boolean exists = mWindowDecorViewModel.setupWindowDecorationForTransition(
+ change.getTaskInfo(),
+ startT,
+ finishT);
+ if (!exists) {
+ // Window caption does not exist, create it
+ mWindowDecorViewModel.createWindowDecoration(
+ change.getTaskInfo(), change.getLeash(), startT, finishT);
+ }
+ }
+
@Override
public void onTransitionStarting(@NonNull IBinder transition) {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index c52ed249c2ca..75f9a4c33af9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -42,8 +42,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
Consts.TAG_WM_SHELL),
WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
- WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
- Consts.TAG_WM_SHELL),
+ WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM_SPLIT_SCREEN),
WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
WM_SHELL_DESKTOP_MODE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
@@ -110,6 +110,7 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
private static class Consts {
private static final String TAG_WM_SHELL = "WindowManagerShell";
private static final String TAG_WM_STARTING_WINDOW = "ShellStartingWindow";
+ private static final String TAG_WM_SPLIT_SCREEN = "ShellSplitScreen";
private static final boolean ENABLE_DEBUG = true;
private static final boolean ENABLE_LOG_TO_PROTO_DEBUG = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
index eb08d0ecbd06..5533ad56d17c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl
@@ -86,8 +86,8 @@ interface ISplitScreen {
/**
* Starts a pair of intent and task in one transition.
*/
- oneway void startIntentAndTask(in PendingIntent pendingIntent, in Intent fillInIntent,
- in Bundle options1, int taskId, in Bundle options2, int sidePosition, float splitRatio,
+ oneway void startIntentAndTask(in PendingIntent pendingIntent, in Bundle options1, int taskId,
+ in Bundle options2, int sidePosition, float splitRatio,
in RemoteTransition remoteTransition, in InstanceId instanceId) = 16;
/**
@@ -108,9 +108,8 @@ interface ISplitScreen {
* Starts a pair of intent and task using legacy transition system.
*/
oneway void startIntentAndTaskWithLegacyTransition(in PendingIntent pendingIntent,
- in Intent fillInIntent, in Bundle options1, int taskId, in Bundle options2,
- int splitPosition, float splitRatio, in RemoteAnimationAdapter adapter,
- in InstanceId instanceId) = 12;
+ in Bundle options1, int taskId, in Bundle options2, int splitPosition, float splitRatio,
+ in RemoteAnimationAdapter adapter, in InstanceId instanceId) = 12;
/**
* Starts a pair of shortcut and task using legacy transition system.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index c6a2b8312ebd..cdc8cdd2c28d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -18,6 +18,7 @@ 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.ActivityTaskManager.INVALID_TASK_ID;
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;
@@ -32,6 +33,8 @@ import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
@@ -60,13 +63,12 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-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.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -166,8 +168,11 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
+ private final String[] mMultiInstancesComponents;
+
+ @VisibleForTesting
+ StageCoordinator mStageCoordinator;
- private StageCoordinator mStageCoordinator;
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mGoingToRecentsTasksLayer;
@@ -210,6 +215,51 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
if (ActivityTaskManager.supportsSplitScreenMultiWindow(context)) {
shellInit.addInitCallback(this::onInit, this);
}
+
+ // TODO(255224696): Remove the config once having a way for client apps to opt-in
+ // multi-instances split.
+ mMultiInstancesComponents = mContext.getResources()
+ .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
+ }
+
+ @VisibleForTesting
+ SplitScreenController(Context context,
+ ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
+ ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer,
+ DisplayController displayController,
+ DisplayImeController displayImeController,
+ DisplayInsetsController displayInsetsController,
+ DragAndDropController dragAndDropController,
+ Transitions transitions,
+ TransactionPool transactionPool,
+ IconProvider iconProvider,
+ RecentTasksController recentTasks,
+ ShellExecutor mainExecutor,
+ StageCoordinator stageCoordinator) {
+ mShellCommandHandler = shellCommandHandler;
+ mShellController = shellController;
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mMainExecutor = mainExecutor;
+ mDisplayController = displayController;
+ mDisplayImeController = displayImeController;
+ mDisplayInsetsController = displayInsetsController;
+ mDragAndDropController = dragAndDropController;
+ mTransitions = transitions;
+ mTransactionPool = transactionPool;
+ mIconProvider = iconProvider;
+ mRecentTasksOptional = Optional.of(recentTasks);
+ mStageCoordinator = stageCoordinator;
+ mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
+ shellInit.addInitCallback(this::onInit, this);
+ mMultiInstancesComponents = mContext.getResources()
+ .getStringArray(R.array.config_componentsSupportMultiInstancesSplit);
}
public SplitScreen asSplitScreen() {
@@ -471,72 +521,116 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
startIntent(intent, fillInIntent, position, options);
}
+ private void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
+ @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
+ @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
+ InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+ && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
+ }
+
+ private void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ Intent fillInIntent = null;
+ if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
+ && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+ fillInIntent = new Intent();
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ }
+ mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId);
+ }
+
@Override
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
- if (fillInIntent == null) {
- fillInIntent = new Intent();
- }
- // Flag this as a no-user-action launch to prevent sending user leaving event to the
- // current top activity since it's going to be put into another side of the split. This
- // prevents the current top activity from going into pip mode due to user leaving event.
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the current
+ // top activity since it's going to be put into another side of the split. This prevents the
+ // current top activity from going into pip mode due to user leaving event.
+ if (fillInIntent == null) fillInIntent = new Intent();
fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
- // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
- // split and there is no reusable background task.
- if (shouldAddMultipleTaskFlag(intent.getIntent(), position)) {
- final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional.isPresent()
- ? mRecentTasksOptional.get().findTaskInBackground(
- intent.getIntent().getComponent())
- : null;
- if (taskInfo != null) {
- startTask(taskInfo.taskId, position, options);
+ if (launchSameComponentAdjacently(intent, position, INVALID_TASK_ID)) {
+ final ComponentName launching = intent.getIntent().getComponent();
+ if (supportMultiInstancesSplit(launching)) {
+ // To prevent accumulating large number of instances in the background, reuse task
+ // in the background with priority.
+ final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional
+ .map(recentTasks -> recentTasks.findTaskInBackground(launching))
+ .orElse(null);
+ if (taskInfo != null) {
+ startTask(taskInfo.taskId, position, options);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+ "Start task in background");
+ return;
+ }
+
+ // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of
+ // the split and there is no reusable background task.
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ } else if (isSplitScreenVisible()) {
+ mStageCoordinator.switchSplitPosition("startIntent");
return;
}
- fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
}
- if (!ENABLE_SHELL_TRANSITIONS) {
- mStageCoordinator.startIntentLegacy(intent, fillInIntent, position, options);
- return;
- }
mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
/** Returns {@code true} if it's launching the same component on both sides of the split. */
- @VisibleForTesting
- boolean shouldAddMultipleTaskFlag(@Nullable Intent startIntent, @SplitPosition int position) {
- if (startIntent == null) {
- return false;
- }
+ private boolean launchSameComponentAdjacently(@Nullable PendingIntent pendingIntent,
+ @SplitPosition int position, int taskId) {
+ if (pendingIntent == null || pendingIntent.getIntent() == null) return false;
+
+ final ComponentName launchingActivity = pendingIntent.getIntent().getComponent();
+ if (launchingActivity == null) return false;
- final ComponentName launchingActivity = startIntent.getComponent();
- if (launchingActivity == null) {
+ if (taskId != INVALID_TASK_ID) {
+ final ActivityManager.RunningTaskInfo taskInfo =
+ mTaskOrganizer.getRunningTaskInfo(taskId);
+ if (taskInfo != null) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ }
return false;
}
- if (isSplitScreenVisible()) {
- // To prevent users from constantly dropping the same app to the same side resulting in
- // a large number of instances in the background.
- final ActivityManager.RunningTaskInfo targetTaskInfo = getTaskInfo(position);
- final ComponentName targetActivity = targetTaskInfo != null
- ? targetTaskInfo.baseIntent.getComponent() : null;
- if (Objects.equals(launchingActivity, targetActivity)) {
- return false;
+ if (!isSplitScreenVisible()) {
+ // Split screen is not yet activated, check if the current top running task is valid to
+ // split together.
+ final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
+ if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
}
-
- // Allow users to start a new instance the same to adjacent side.
- final ActivityManager.RunningTaskInfo pairedTaskInfo =
- getTaskInfo(SplitLayout.reversePosition(position));
- final ComponentName pairedActivity = pairedTaskInfo != null
- ? pairedTaskInfo.baseIntent.getComponent() : null;
- return Objects.equals(launchingActivity, pairedActivity);
+ return false;
}
- final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
- if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
- return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ // Compare to the adjacent side of the split to determine if this is launching the same
+ // component adjacently.
+ final ActivityManager.RunningTaskInfo pairedTaskInfo =
+ getTaskInfo(SplitLayout.reversePosition(position));
+ final ComponentName pairedActivity = pairedTaskInfo != null
+ ? pairedTaskInfo.baseIntent.getComponent() : null;
+ return Objects.equals(launchingActivity, pairedActivity);
+ }
+
+ @VisibleForTesting
+ /** Returns {@code true} if the component supports multi-instances split. */
+ boolean supportMultiInstancesSplit(@Nullable ComponentName launching) {
+ if (launching == null) return false;
+
+ final String componentName = launching.flattenToString();
+ for (int i = 0; i < mMultiInstancesComponents.length; i++) {
+ if (mMultiInstancesComponents[i].equals(componentName)) {
+ return true;
+ }
}
return false;
@@ -839,14 +933,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Override
public void startIntentAndTaskWithLegacyTransition(PendingIntent pendingIntent,
- Intent fillInIntent, Bundle options1, int taskId, Bundle options2,
- int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
- InstanceId instanceId) {
+ Bundle options1, int taskId, Bundle options2, int splitPosition, float splitRatio,
+ RemoteAnimationAdapter adapter, InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController,
"startIntentAndTaskWithLegacyTransition", (controller) ->
- controller.mStageCoordinator.startIntentAndTaskWithLegacyTransition(
- pendingIntent, fillInIntent, options1, taskId, options2,
- splitPosition, splitRatio, adapter, instanceId));
+ controller.startIntentAndTaskWithLegacyTransition(pendingIntent,
+ options1, taskId, options2, splitPosition, splitRatio, adapter,
+ instanceId));
}
@Override
@@ -872,14 +965,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
- public void startIntentAndTask(PendingIntent pendingIntent, Intent fillInIntent,
- @Nullable Bundle options1, int taskId, @Nullable Bundle options2,
- @SplitPosition int splitPosition, float splitRatio,
- @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
+ public void startIntentAndTask(PendingIntent pendingIntent, @Nullable Bundle options1,
+ int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
+ float splitRatio, @Nullable RemoteTransition remoteTransition,
+ InstanceId instanceId) {
executeRemoteCallWithTaskPermission(mController, "startIntentAndTask",
- (controller) -> controller.mStageCoordinator.startIntentAndTask(pendingIntent,
- fillInIntent, options1, taskId, options2, splitPosition, splitRatio,
- remoteTransition, instanceId));
+ (controller) -> controller.startIntentAndTask(pendingIntent, options1, taskId,
+ options2, splitPosition, splitRatio, remoteTransition, instanceId));
}
@Override
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 15a11334307b..c2ab7ef7e7bf 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
@@ -24,7 +24,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -428,6 +427,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Launches an activity into split. */
void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
@Nullable Bundle options) {
+ if (!ENABLE_SHELL_TRANSITIONS) {
+ startIntentLegacy(intent, fillInIntent, position, options);
+ return;
+ }
+
final WindowContainerTransaction wct = new WindowContainerTransaction();
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -441,13 +445,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
prepareEnterSplitScreen(wct, null /* taskInfo */, position);
mSplitTransitions.startEnterTransition(transitType, wct, null, this,
- aborted -> {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if (aborted && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePositionAnimated(
- SplitLayout.reversePosition(mSideStagePosition));
- }
- } /* consumedCallback */,
+ null /* consumedCallback */,
(finishWct, finishT) -> {
if (!evictWct.isEmpty()) {
finishWct.merge(evictWct, true);
@@ -457,7 +455,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
/** Launches an activity into split by legacy transition. */
void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options) {
+ @SplitPosition int position, @Nullable Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
prepareEvictChildTasks(position, evictWct);
@@ -473,12 +471,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
mSplitUnsupportedToast.show();
- } else {
- // Switch the split position if launching as MULTIPLE_TASK failed.
- if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
- setSideStagePosition(SplitLayout.reversePosition(
- getSideStagePosition()), null);
- }
}
// Do nothing when the animation was cancelled.
@@ -771,9 +763,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStage.evictInvisibleChildren(wct);
}
- Bundle resolveStartStage(@StageType int stage,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
- @androidx.annotation.Nullable WindowContainerTransaction wct) {
+ Bundle resolveStartStage(@StageType int stage, @SplitPosition int position,
+ @Nullable Bundle options, @Nullable WindowContainerTransaction wct) {
switch (stage) {
case STAGE_TYPE_UNDEFINED: {
if (position != SPLIT_POSITION_UNDEFINED) {
@@ -844,9 +835,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
: mMainStage.getTopVisibleChildTaskId();
}
- void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
- if (mSideStagePosition == sideStagePosition) return;
- SurfaceControl.Transaction t = mTransactionPool.acquire();
+ void switchSplitPosition(String reason) {
+ final SurfaceControl.Transaction t = mTransactionPool.acquire();
mTempRect1.setEmpty();
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
@@ -886,6 +876,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
va.start();
});
});
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Switch split position: %s", reason);
+ mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
+ getSideStagePosition(), mSideStage.getTopChildTaskUid(),
+ mSplitLayout.isLandscape());
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -1097,7 +1092,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
activityTaskManagerService.setFocusedTask(getTaskId(stageToFocus));
} catch (RemoteException | NullPointerException e) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Unable to update focus on the chosen stage, %s", TAG, e);
+ "Unable to update focus on the chosen stage: %s", e.getMessage());
}
}
@@ -1434,14 +1429,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Request to %s divider bar from %s.", TAG,
+ "Request to %s divider bar from %s.",
(visible ? "show" : "hide"), Debug.getCaller());
// Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard
// dismissing animation.
if (visible && mKeyguardShowing) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Defer showing divider bar due to keyguard showing.", TAG);
+ " Defer showing divider bar due to keyguard showing.");
return;
}
@@ -1450,7 +1445,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (mIsDividerRemoteAnimating) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Skip animating divider bar due to it's remote animating.", TAG);
+ " Skip animating divider bar due to it's remote animating.");
return;
}
@@ -1465,12 +1460,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
if (dividerLeash == null) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Skip animating divider bar due to divider leash not ready.", TAG);
+ " Skip animating divider bar due to divider leash not ready.");
return;
}
if (mIsDividerRemoteAnimating) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
- "%s: Skip animating divider bar due to it's remote animating.", TAG);
+ " Skip animating divider bar due to it's remote animating.");
return;
}
@@ -1617,10 +1612,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onDoubleTappedDivider() {
- setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition));
- mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
+ switchSplitPosition("double tap");
}
@Override
@@ -1633,14 +1625,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onLayoutSizeChanging(SplitLayout layout) {
+ public void onLayoutSizeChanging(SplitLayout layout, int offsetX, int offsetY) {
final SurfaceControl.Transaction t = mTransactionPool.acquire();
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
- mMainStage.onResizing(mTempRect1, mTempRect2, t);
- mSideStage.onResizing(mTempRect2, mTempRect1, t);
+ mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY);
+ mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY);
t.apply();
mTransactionPool.release(t);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 6b90eabe3bd2..acad5d93eab4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -288,9 +288,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
- void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t) {
+ void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
+ int offsetY) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
- mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t);
+ mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
+ offsetY);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 2830fa967011..857decf65567 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -24,7 +24,6 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
-import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -333,9 +332,12 @@ public class Transitions implements RemoteCallable<Transitions> {
boolean isOpening = isOpeningType(info.getType());
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if ((change.getFlags() & TransitionInfo.FLAG_IS_SYSTEM_WINDOW) != 0) {
+ if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
// Currently system windows are controlled by WindowState, so don't change their
- // surfaces. Otherwise their window tokens could be hidden unexpectedly.
+ // surfaces. Otherwise their surfaces could be hidden or cropped unexpectedly.
+ // This includes Wallpaper (always z-ordered at bottom) and IME (associated with
+ // app), because there may not be a transition associated with their visibility
+ // changes, and currently they don't need transition animation.
continue;
}
final SurfaceControl leash = change.getLeash();
@@ -372,16 +374,7 @@ public class Transitions implements RemoteCallable<Transitions> {
finishT.setAlpha(leash, 1.f);
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
- // Wallpaper/IME are anomalies: their visibility is tied to other WindowStates.
- // As a result, we actually can't hide their WindowTokens because there may not be a
- // transition associated with them becoming visible again. Fortunately, since
- // wallpapers are always z-ordered to the back, we don't have to worry about it
- // flickering to the front during reparenting. Similarly, the IME is reparented to
- // the associated app, so its visibility is coupled. So, an explicit hide is not
- // needed visually anyways.
- if ((change.getFlags() & (FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD)) == 0) {
- finishT.hide(leash);
- }
+ finishT.hide(leash);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 36dd8edaa8b7..ca15f0002fac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -105,6 +105,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
if (!shouldShowWindowDecor(taskInfo)) return false;
+ CaptionWindowDecoration oldDecoration = mWindowDecorByTaskId.get(taskInfo.taskId);
+ if (oldDecoration != null) {
+ // close the old decoration if it exists to avoid two window decorations being added
+ oldDecoration.close();
+ }
final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
mContext,
mDisplayController,
@@ -141,23 +146,25 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
}
@Override
- public void setupWindowDecorationForTransition(
+ public boolean setupWindowDecorationForTransition(
RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT) {
final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
- if (decoration == null) return;
+ if (decoration == null) return false;
decoration.relayout(taskInfo, startT, finishT);
+ return true;
}
@Override
- public void destroyWindowDecoration(RunningTaskInfo taskInfo) {
+ public boolean destroyWindowDecoration(RunningTaskInfo taskInfo) {
final CaptionWindowDecoration decoration =
mWindowDecorByTaskId.removeReturnOld(taskInfo.taskId);
- if (decoration == null) return;
+ if (decoration == null) return false;
decoration.close();
+ return true;
}
private class CaptionTouchEventListener implements
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index f0f2db7ded80..a49a300995e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -40,6 +40,9 @@ class TaskPositioner implements DragResizeCallback {
private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mResizeStartPoint = new PointF();
private final Rect mResizeTaskBounds = new Rect();
+ // Whether the |dragResizing| hint should be sent with the next bounds change WCT.
+ // Used to optimized fluid resizing of freeform tasks.
+ private boolean mPendingDragResizeHint = false;
private int mCtrlType;
private DragStartListener mDragStartListener;
@@ -53,6 +56,12 @@ class TaskPositioner implements DragResizeCallback {
@Override
public void onDragResizeStart(int ctrlType, float x, float y) {
+ if (ctrlType != CTRL_TYPE_UNDEFINED) {
+ // The task is being resized, send the |dragResizing| hint to core with the first
+ // bounds-change wct.
+ mPendingDragResizeHint = true;
+ }
+
mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType;
@@ -63,19 +72,31 @@ class TaskPositioner implements DragResizeCallback {
@Override
public void onDragResizeMove(float x, float y) {
- changeBounds(x, y);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (changeBounds(wct, x, y)) {
+ if (mPendingDragResizeHint) {
+ // This is the first bounds change since drag resize operation started.
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
+ mPendingDragResizeHint = false;
+ }
+ mTaskOrganizer.applyTransaction(wct);
+ }
}
@Override
public void onDragResizeEnd(float x, float y) {
- changeBounds(x, y);
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
+ changeBounds(wct, x, y);
+ mTaskOrganizer.applyTransaction(wct);
mCtrlType = 0;
mTaskBoundsAtDragStart.setEmpty();
mResizeStartPoint.set(0, 0);
+ mPendingDragResizeHint = false;
}
- private void changeBounds(float x, float y) {
+ private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
float deltaX = x - mResizeStartPoint.x;
mResizeTaskBounds.set(mTaskBoundsAtDragStart);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
@@ -96,10 +117,10 @@ class TaskPositioner implements DragResizeCallback {
}
if (!mResizeTaskBounds.isEmpty()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
- mTaskOrganizer.applyTransaction(wct);
+ return true;
}
+ return false;
}
interface DragStartListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
index d7f71c8235f1..2ce4d04377a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -44,7 +44,7 @@ public interface WindowDecorViewModel {
* @param taskSurface the surface of the task
* @param startT the start transaction to be applied before the transition
* @param finishT the finish transaction to restore states after the transition
- * @return the window decoration object
+ * @return {@code true} if window decoration was created, {@code false} otherwise
*/
boolean createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
@@ -66,8 +66,9 @@ public interface WindowDecorViewModel {
*
* @param startT the start transaction to be applied before the transition
* @param finishT the finish transaction to restore states after the transition
+ * @return {@code true} if window decoration exists, {@code false} otherwise
*/
- void setupWindowDecorationForTransition(
+ boolean setupWindowDecorationForTransition(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl.Transaction startT,
SurfaceControl.Transaction finishT);
@@ -76,6 +77,7 @@ public interface WindowDecorViewModel {
* Destroys the window decoration of the give task.
*
* @param taskInfo the info of the task
+ * @return {@code true} if window decoration was destroyed, {@code false} otherwise
*/
- void destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
+ boolean destroyWindowDecoration(ActivityManager.RunningTaskInfo taskInfo);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index 7cbace5af48f..081c8ae91bdb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -16,13 +16,9 @@
package com.android.wm.shell;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
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 android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -34,8 +30,6 @@ import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIO
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -44,11 +38,9 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
-import android.app.WindowConfiguration;
import android.content.LocusId;
import android.content.pm.ParceledListSlice;
import android.os.Binder;
@@ -61,8 +53,6 @@ import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransaction.Change;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -638,130 +628,10 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
verify(mTaskOrganizerController).restartTaskTopActivityProcessIfVisible(task1.token);
}
- @Test
- public void testPrepareClearBoundsForStandardTasks() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
- mOrganizer.onTaskAppeared(task2, null);
-
- MockToken otherDisplayToken = new MockToken();
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_UNDEFINED,
- otherDisplayToken);
- otherDisplayTask.displayId = 2;
- mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
- assertEquals(wct.getChanges().size(), 2);
- Change boundsChange1 = wct.getChanges().get(token1.binder());
- assertNotNull(boundsChange1);
- assertNotEquals(
- (boundsChange1.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
- assertTrue(boundsChange1.getConfiguration().windowConfiguration.getBounds().isEmpty());
-
- Change boundsChange2 = wct.getChanges().get(token2.binder());
- assertNotNull(boundsChange2);
- assertNotEquals(
- (boundsChange2.getWindowSetMask() & WindowConfiguration.WINDOW_CONFIG_BOUNDS), 0);
- assertTrue(boundsChange2.getConfiguration().windowConfiguration.getBounds().isEmpty());
- }
-
- @Test
- public void testPrepareClearBoundsForStandardTasks_onlyClearActivityTypeStandard() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_UNDEFINED, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_UNDEFINED, token2);
- task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- mOrganizer.onTaskAppeared(task2, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearBoundsForStandardTasks(1);
-
- // Only clear bounds for task1
- assertEquals(1, wct.getChanges().size());
- assertNotNull(wct.getChanges().get(token1.binder()));
- }
-
- @Test
- public void testPrepareClearFreeformForStandardTasks() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_MULTI_WINDOW, token2);
- mOrganizer.onTaskAppeared(task2, null);
-
- MockToken otherDisplayToken = new MockToken();
- RunningTaskInfo otherDisplayTask = createTaskInfo(3, WINDOWING_MODE_FREEFORM,
- otherDisplayToken);
- otherDisplayTask.displayId = 2;
- mOrganizer.onTaskAppeared(otherDisplayTask, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
- // Only task with freeform windowing mode and the right display should be updated
- assertEquals(wct.getChanges().size(), 1);
- Change wmModeChange1 = wct.getChanges().get(token1.binder());
- assertNotNull(wmModeChange1);
- assertEquals(wmModeChange1.getWindowingMode(), WINDOWING_MODE_UNDEFINED);
- }
-
- @Test
- public void testPrepareClearFreeformForStandardTasks_onlyClearActivityTypeStandard() {
- MockToken token1 = new MockToken();
- RunningTaskInfo task1 = createTaskInfo(1, WINDOWING_MODE_FREEFORM, token1);
- mOrganizer.onTaskAppeared(task1, null);
-
- MockToken token2 = new MockToken();
- RunningTaskInfo task2 = createTaskInfo(2, WINDOWING_MODE_FREEFORM, token2);
- task2.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_HOME);
- mOrganizer.onTaskAppeared(task2, null);
-
- WindowContainerTransaction wct = mOrganizer.prepareClearFreeformForStandardTasks(1);
-
- // Only clear freeform for task1
- assertEquals(1, wct.getChanges().size());
- assertNotNull(wct.getChanges().get(token1.binder()));
- }
-
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
return taskInfo;
}
-
- private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, MockToken token) {
- RunningTaskInfo taskInfo = createTaskInfo(taskId, windowingMode);
- taskInfo.displayId = 1;
- taskInfo.token = token.token();
- taskInfo.configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
- return taskInfo;
- }
-
- private static class MockToken {
- private final WindowContainerToken mToken;
- private final IBinder mBinder;
-
- MockToken() {
- mToken = mock(WindowContainerToken.class);
- mBinder = mock(IBinder.class);
- when(mToken.asBinder()).thenReturn(mBinder);
- }
-
- WindowContainerToken token() {
- return mToken;
- }
-
- IBinder binder() {
- return mBinder;
- }
- }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS
new file mode 100644
index 000000000000..1e0f9bc6322f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/OWNERS
@@ -0,0 +1,5 @@
+# WM shell sub-module back navigation owners
+# Bug component: 1152663
+shanh@google.com
+arthurhung@google.com
+wilsonshih@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 5332476d5130..3d779481d361 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -105,7 +105,8 @@ public class SplitLayoutTests extends ShellTestCase {
@Test
public void testUpdateDivideBounds() {
mSplitLayout.updateDivideBounds(anyInt());
- verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class));
+ verify(mSplitLayoutHandler).onLayoutSizeChanging(any(SplitLayout.class), anyInt(),
+ anyInt());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 79b520c734c8..89bafcb6b2f4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -16,10 +16,13 @@
package com.android.wm.shell.desktopmode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
@@ -30,13 +33,14 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -68,6 +72,9 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
+import java.util.ArrayList;
+import java.util.Arrays;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class DesktopModeControllerTest extends ShellTestCase {
@@ -83,9 +90,7 @@ public class DesktopModeControllerTest extends ShellTestCase {
@Mock
private Handler mMockHandler;
@Mock
- private Transitions mMockTransitions;
- private TestShellExecutor mExecutor;
-
+ private Transitions mTransitions;
private DesktopModeController mController;
private DesktopModeTaskRepository mDesktopModeTaskRepository;
private ShellInit mShellInit;
@@ -97,20 +102,19 @@ public class DesktopModeControllerTest extends ShellTestCase {
when(DesktopModeStatus.isActive(any())).thenReturn(true);
mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
- mExecutor = new TestShellExecutor();
mDesktopModeTaskRepository = new DesktopModeTaskRepository();
mController = new DesktopModeController(mContext, mShellInit, mShellController,
- mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mMockTransitions,
- mDesktopModeTaskRepository, mMockHandler, mExecutor);
+ mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+ mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(anyInt())).thenReturn(
- new WindowContainerTransaction());
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
mShellInit.init();
clearInvocations(mShellTaskOrganizer);
clearInvocations(mRootTaskDisplayAreaOrganizer);
+ clearInvocations(mTransitions);
}
@After
@@ -124,113 +128,133 @@ public class DesktopModeControllerTest extends ShellTestCase {
}
@Test
- public void testDesktopModeEnabled_taskWmClearedDisplaySetToFreeform() {
- // Create a fake WCT to simulate setting task windowing mode to undefined
- WindowContainerTransaction taskWct = new WindowContainerTransaction();
- MockToken taskMockToken = new MockToken();
- taskWct.setWindowingMode(taskMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskWct);
-
- // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
- MockToken displayMockToken = new MockToken();
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
+ public void testDesktopModeEnabled_rootTdaSetToFreeform() {
+ DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
- // The test
mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 1 change: Root TDA windowing mode
+ assertThat(wct.getChanges().size()).isEqualTo(1);
+ // Verify WCT has a change for setting windowing mode to freeform
+ Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+ }
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+ @Test
+ public void testDesktopModeDisabled_rootTdaSetToFullscreen() {
+ DisplayAreaInfo displayAreaInfo = createMockDisplayArea();
- // WCT should have 2 changes - clear task wm mode and set display wm mode
- WindowContainerTransaction wct = arg.getValue();
- assertThat(wct.getChanges()).hasSize(2);
+ mController.updateDesktopModeActive(false);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 1 change: Root TDA windowing mode
+ assertThat(wct.getChanges().size()).isEqualTo(1);
+ // Verify WCT has a change for setting windowing mode to fullscreen
+ Change change = wct.getChanges().get(displayAreaInfo.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ }
- // Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmModeChange = wct.getChanges().get(taskMockToken.binder());
- assertThat(taskWmModeChange).isNotNull();
- assertThat(taskWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
+ @Test
+ public void testDesktopModeEnabled_windowingModeCleared() {
+ createMockDisplayArea();
+ RunningTaskInfo freeformTask = createFreeformTask();
+ RunningTaskInfo fullscreenTask = createFullscreenTask();
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(freeformTask, fullscreenTask, homeTask)));
- // Verify executed WCT has a change for setting display windowing mode to freeform
- Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(displayWmModeChange).isNotNull();
- assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM);
+ mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 2 changes: Root TDA windowing mode and 1 task
+ assertThat(wct.getChanges().size()).isEqualTo(2);
+ // No changes for tasks that are not standard or freeform
+ assertThat(wct.getChanges().get(fullscreenTask.token.asBinder())).isNull();
+ assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+ // Standard freeform task has windowing mode cleared
+ Change change = wct.getChanges().get(freeformTask.token.asBinder());
+ assertThat(change).isNotNull();
+ assertThat(change.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
}
@Test
- public void testDesktopModeDisabled_taskWmAndBoundsClearedDisplaySetToFullscreen() {
- // Create a fake WCT to simulate setting task windowing mode to undefined
- WindowContainerTransaction taskWmWct = new WindowContainerTransaction();
- MockToken taskWmMockToken = new MockToken();
- taskWmWct.setWindowingMode(taskWmMockToken.token(), WINDOWING_MODE_UNDEFINED);
- when(mShellTaskOrganizer.prepareClearFreeformForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskWmWct);
-
- // Create a fake WCT to simulate clearing task bounds
- WindowContainerTransaction taskBoundsWct = new WindowContainerTransaction();
- MockToken taskBoundsMockToken = new MockToken();
- taskBoundsWct.setBounds(taskBoundsMockToken.token(), null);
- when(mShellTaskOrganizer.prepareClearBoundsForStandardTasks(
- mContext.getDisplayId())).thenReturn(taskBoundsWct);
-
- // Create a fake DisplayAreaInfo to check if windowing mode change is set correctly
- MockToken displayMockToken = new MockToken();
- DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(displayMockToken.mToken,
- mContext.getDisplayId(), 0);
- when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
- .thenReturn(displayAreaInfo);
+ public void testDesktopModeDisabled_windowingModeAndBoundsCleared() {
+ createMockDisplayArea();
+ RunningTaskInfo freeformTask = createFreeformTask();
+ RunningTaskInfo fullscreenTask = createFullscreenTask();
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(freeformTask, fullscreenTask, homeTask)));
- // The test
mController.updateDesktopModeActive(false);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // 3 changes: Root TDA windowing mode and 2 tasks
+ assertThat(wct.getChanges().size()).isEqualTo(3);
+ // No changes to home task
+ assertThat(wct.getChanges().get(homeTask.token.asBinder())).isNull();
+ // Standard tasks have bounds cleared
+ assertThatBoundsCleared(wct.getChanges().get(freeformTask.token.asBinder()));
+ assertThatBoundsCleared(wct.getChanges().get(fullscreenTask.token.asBinder()));
+ // Freeform standard tasks have windowing mode cleared
+ assertThat(wct.getChanges().get(
+ freeformTask.token.asBinder()).getWindowingMode()).isEqualTo(
+ WINDOWING_MODE_UNDEFINED);
+ }
- ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
- WindowContainerTransaction.class);
- verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
-
- // WCT should have 3 changes - clear task wm mode and bounds and set display wm mode
- WindowContainerTransaction wct = arg.getValue();
- assertThat(wct.getChanges()).hasSize(3);
-
- // Verify executed WCT has a change for setting task windowing mode to undefined
- Change taskWmMode = wct.getChanges().get(taskWmMockToken.binder());
- assertThat(taskWmMode).isNotNull();
- assertThat(taskWmMode.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED);
-
- // Verify executed WCT has a change for clearing task bounds
- Change bounds = wct.getChanges().get(taskBoundsMockToken.binder());
- assertThat(bounds).isNotNull();
- assertThat(bounds.getWindowSetMask() & WINDOW_CONFIG_BOUNDS).isNotEqualTo(0);
- assertThat(bounds.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
-
- // Verify executed WCT has a change for setting display windowing mode to fullscreen
- Change displayWmModeChange = wct.getChanges().get(displayAreaInfo.token.asBinder());
- assertThat(displayWmModeChange).isNotNull();
- assertThat(displayWmModeChange.getWindowingMode()).isEqualTo(WINDOWING_MODE_FULLSCREEN);
+ @Test
+ public void testDesktopModeEnabled_homeTaskBehindVisibleTask() {
+ createMockDisplayArea();
+ RunningTaskInfo fullscreenTask1 = createFullscreenTask();
+ fullscreenTask1.isVisible = true;
+ RunningTaskInfo fullscreenTask2 = createFullscreenTask();
+ fullscreenTask2.isVisible = false;
+ RunningTaskInfo homeTask = createHomeTask();
+ when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>(
+ Arrays.asList(fullscreenTask1, fullscreenTask2, homeTask)));
+
+ mController.updateDesktopModeActive(true);
+ WindowContainerTransaction wct = getDesktopModeSwitchTransaction();
+
+ // Check that there are hierarchy changes for home task and visible task
+ assertThat(wct.getHierarchyOps()).hasSize(2);
+ // First show home task
+ WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+ assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
+
+ // Then visible task on top of it
+ WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+ assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+ assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
}
@Test
public void testShowDesktopApps() {
// Set up two active tasks on desktop
- mDesktopModeTaskRepository.addActiveTask(1);
- mDesktopModeTaskRepository.addActiveTask(2);
- MockToken token1 = new MockToken();
- MockToken token2 = new MockToken();
- ActivityManager.RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder().setToken(
- token1.token()).setLastActiveTime(100).build();
- ActivityManager.RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder().setToken(
- token2.token()).setLastActiveTime(200).build();
- when(mShellTaskOrganizer.getRunningTaskInfo(1)).thenReturn(taskInfo1);
- when(mShellTaskOrganizer.getRunningTaskInfo(2)).thenReturn(taskInfo2);
+ RunningTaskInfo freeformTask1 = createFreeformTask();
+ freeformTask1.lastActiveTime = 100;
+ RunningTaskInfo freeformTask2 = createFreeformTask();
+ freeformTask2.lastActiveTime = 200;
+ mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+ mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+ when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
+ freeformTask1);
+ when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
+ freeformTask2);
// Run show desktop apps logic
mController.showDesktopApps();
ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
WindowContainerTransaction.class);
- verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), wctCaptor.capture(), any());
+ } else {
+ verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
+ }
WindowContainerTransaction wct = wctCaptor.getValue();
// Check wct has reorder calls
@@ -239,12 +263,12 @@ public class DesktopModeControllerTest extends ShellTestCase {
// Task 2 has activity later, must be first
WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op1.getContainer()).isEqualTo(token2.binder());
+ assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder());
// Task 1 should be second
- WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(0);
+ WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
- assertThat(op2.getContainer()).isEqualTo(token2.binder());
+ assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder());
}
@Test
@@ -266,7 +290,7 @@ public class DesktopModeControllerTest extends ShellTestCase {
@Test
public void testHandleTransitionRequest_notFreeform_returnsNull() {
- ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+ RunningTaskInfo trigger = new RunningTaskInfo();
trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
WindowContainerTransaction wct = mController.handleRequest(
new Binder(),
@@ -276,7 +300,7 @@ public class DesktopModeControllerTest extends ShellTestCase {
@Test
public void testHandleTransitionRequest_returnsWct() {
- ActivityManager.RunningTaskInfo trigger = new ActivityManager.RunningTaskInfo();
+ RunningTaskInfo trigger = new RunningTaskInfo();
trigger.token = new MockToken().mToken;
trigger.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
WindowContainerTransaction wct = mController.handleRequest(
@@ -285,6 +309,57 @@ public class DesktopModeControllerTest extends ShellTestCase {
assertThat(wct).isNotNull();
}
+ private DisplayAreaInfo createMockDisplayArea() {
+ DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
+ mContext.getDisplayId(), 0);
+ when(mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(mContext.getDisplayId()))
+ .thenReturn(displayAreaInfo);
+ return displayAreaInfo;
+ }
+
+ private RunningTaskInfo createFreeformTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private RunningTaskInfo createFullscreenTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_STANDARD)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private RunningTaskInfo createHomeTask() {
+ return new TestRunningTaskInfoBuilder()
+ .setToken(new MockToken().token())
+ .setActivityType(ACTIVITY_TYPE_HOME)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .setLastActiveTime(100)
+ .build();
+ }
+
+ private WindowContainerTransaction getDesktopModeSwitchTransaction() {
+ ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+ WindowContainerTransaction.class);
+ if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+ verify(mTransitions).startTransition(eq(TRANSIT_CHANGE), arg.capture(), any());
+ } else {
+ verify(mRootTaskDisplayAreaOrganizer).applyTransaction(arg.capture());
+ }
+ return arg.getValue();
+ }
+
+ private void assertThatBoundsCleared(Change change) {
+ assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
+ assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
+ }
+
private static class MockToken {
private final WindowContainerToken mToken;
private final IBinder mBinder;
@@ -298,9 +373,5 @@ public class DesktopModeControllerTest extends ShellTestCase {
WindowContainerToken token() {
return mToken;
}
-
- IBinder binder() {
- return mBinder;
- }
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index d01f3d310fc3..38b75f81171f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -16,18 +16,24 @@
package com.android.wm.shell.splitscreen;
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
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.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
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 org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -35,6 +41,8 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -65,11 +73,11 @@ import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Tests for {@link SplitScreenController}
*/
@@ -91,18 +99,21 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Mock Transitions mTransitions;
@Mock TransactionPool mTransactionPool;
@Mock IconProvider mIconProvider;
- @Mock Optional<RecentTasksController> mRecentTasks;
+ @Mock StageCoordinator mStageCoordinator;
+ @Mock RecentTasksController mRecentTasks;
+ @Captor ArgumentCaptor<Intent> mIntentCaptor;
private SplitScreenController mSplitScreenController;
@Before
public void setup() {
+ assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext));
MockitoAnnotations.initMocks(this);
mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit,
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks, mMainExecutor));
+ mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
}
@Test
@@ -148,58 +159,100 @@ public class SplitScreenControllerTests extends ShellTestCase {
}
@Test
- public void testShouldAddMultipleTaskFlag_notInSplitScreen() {
- doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
- doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
+ public void testStartIntent_appendsNoUserActionFlag() {
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
- // Verify launching the same activity returns true.
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_NO_USER_ACTION,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_NO_USER_ACTION);
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_appendsMultipleTaskFag() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into focus task
ActivityManager.RunningTaskInfo focusTaskInfo =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
-
- // Verify launching different activity returns false.
- Intent diffIntent = createStartIntent("diffActivity");
- focusTaskInfo =
- createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
- doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).startIntent(eq(pendingIntent), mIntentCaptor.capture(),
+ eq(SPLIT_POSITION_TOP_OR_LEFT), isNull());
+ assertEquals(FLAG_ACTIVITY_MULTIPLE_TASK,
+ mIntentCaptor.getValue().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK);
}
@Test
- public void testShouldAddMultipleTaskFlag_inSplitScreen() {
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundBeforeSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into focus task
+ ActivityManager.RunningTaskInfo focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(focusTaskInfo).when(mStageCoordinator).getFocusingTaskInfo();
+ doReturn(true).when(mStageCoordinator).isValidToEnterSplitScreen(any());
+ // Put the same component into a task in the background
+ ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
+ doReturn(sameTaskInfo).when(mRecentTasks).findTaskInBackground(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
+
+ @Test
+ public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() {
+ doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any());
+ doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any());
+ Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+ ActivityManager.RunningTaskInfo sameTaskInfo =
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+ // Put the same component into a task in the background
+ doReturn(new ActivityManager.RecentTaskInfo()).when(mRecentTasks)
+ .findTaskInBackground(any());
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT),
+ isNull());
+ }
+
+ @Test
+ public void startIntent_multiInstancesNotSupported_switchesPositionAfterSplitActivated() {
+ doReturn(false).when(mSplitScreenController).supportMultiInstancesSplit(any());
Intent startIntent = createStartIntent("startActivity");
+ PendingIntent pendingIntent =
+ PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE);
+ // Put the same component into another side of the split
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
ActivityManager.RunningTaskInfo sameTaskInfo =
createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
- Intent diffIntent = createStartIntent("diffActivity");
- ActivityManager.RunningTaskInfo differentTaskInfo =
- createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
-
- // Verify launching the same activity return false.
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
-
- // Verify launching the same activity as adjacent returns true.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(sameTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertTrue(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
-
- // Verify launching different activity from adjacent returns false.
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_TOP_OR_LEFT);
- doReturn(differentTaskInfo).when(mSplitScreenController)
- .getTaskInfo(SPLIT_POSITION_BOTTOM_OR_RIGHT);
- assertFalse(mSplitScreenController.shouldAddMultipleTaskFlag(
- startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ doReturn(sameTaskInfo).when(mSplitScreenController).getTaskInfo(
+ SPLIT_POSITION_BOTTOM_OR_RIGHT);
+
+ mSplitScreenController.startIntent(pendingIntent, null, SPLIT_POSITION_TOP_OR_LEFT, null);
+
+ verify(mStageCoordinator).switchSplitPosition(anyString());
}
private Intent createStartIntent(String activityName) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
new file mode 100644
index 000000000000..ac10ddb0116a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
@@ -0,0 +1,130 @@
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.window.WindowContainerToken
+import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [TaskPositioner].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:TaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class TaskPositionerTest : ShellTestCase() {
+
+ @Mock
+ private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+ @Mock
+ private lateinit var mockWindowDecoration: WindowDecoration<*>
+ @Mock
+ private lateinit var mockDragStartListener: TaskPositioner.DragStartListener
+
+ @Mock
+ private lateinit var taskToken: WindowContainerToken
+ @Mock
+ private lateinit var taskBinder: IBinder
+
+ private lateinit var taskPositioner: TaskPositioner
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ taskPositioner = TaskPositioner(
+ mockShellTaskOrganizer,
+ mockWindowDecoration,
+ mockDragStartListener
+ )
+ `when`(taskToken.asBinder()).thenReturn(taskBinder)
+ mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ }
+ }
+
+ @Test
+ fun testDragResize_move_skipsDragResizingFlag() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_UNDEFINED, // Move
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Move the task 10px to the right.
+ val newX = STARTING_BOUNDS.left.toFloat() + 10
+ val newY = STARTING_BOUNDS.top.toFloat()
+ taskPositioner.onDragResizeMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragResizeEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_setsDragResizingFlag() {
+ taskPositioner.onDragResizeStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ // Resize the task by 10px to the right.
+ val newX = STARTING_BOUNDS.right.toFloat() + 10
+ val newY = STARTING_BOUNDS.top.toFloat()
+ taskPositioner.onDragResizeMove(
+ newX,
+ newY
+ )
+
+ taskPositioner.onDragResizeEnd(newX, newY)
+
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ change.dragResizing
+ }
+ })
+ verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+ !change.dragResizing
+ }
+ })
+ }
+
+ companion object {
+ private const val TASK_ID = 5
+ private val STARTING_BOUNDS = Rect(0, 0, 100, 100)
+ }
+}