summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java79
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java103
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java60
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java388
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java148
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java149
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java99
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt4
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt86
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt29
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt88
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt88
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt149
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt44
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestRunningTaskInfoBuilder.java)18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java69
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java83
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java109
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java104
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java243
-rw-r--r--libs/androidfw/Idmap.cpp5
-rw-r--r--libs/hwui/Properties.cpp15
-rw-r--r--libs/hwui/Properties.h2
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp43
-rw-r--r--libs/hwui/renderthread/VulkanManager.h23
-rw-r--r--libs/hwui/tests/common/TestUtils.h46
-rw-r--r--libs/services/include/android/os/DropBoxManager.h2
-rw-r--r--libs/services/src/os/DropBoxManager.cpp31
-rw-r--r--libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java2
50 files changed, 2291 insertions, 397 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 4ef489ffd480..cb54021d7a23 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -111,6 +111,7 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
}
mDisplayAreasInfo.put(displayId, displayAreaInfo);
+ mLeashes.put(displayId, leash);
ArrayList<RootTaskDisplayAreaListener> listeners = mListeners.get(displayId);
if (listeners != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
index f213af752d55..52648d915f2c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
@@ -16,7 +16,7 @@
package com.android.wm.shell;
-import android.util.Slog;
+import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.common.ShellExecutor;
@@ -24,10 +24,10 @@ import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.splitscreen.SplitScreen;
import java.io.PrintWriter;
import java.util.Optional;
-import java.util.concurrent.TimeUnit;
/**
* An entry point into the shell for dumping shell internal state and running adb commands.
@@ -38,6 +38,7 @@ public final class ShellCommandHandlerImpl {
private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+ private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<Pip> mPipOptional;
private final Optional<OneHanded> mOneHandedOptional;
private final Optional<HideDisplayCutout> mHideDisplayCutout;
@@ -49,19 +50,21 @@ public final class ShellCommandHandlerImpl {
public static ShellCommandHandler create(
ShellTaskOrganizer shellTaskOrganizer,
Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<SplitScreen> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<OneHanded> oneHandedOptional,
Optional<HideDisplayCutout> hideDisplayCutout,
Optional<AppPairs> appPairsOptional,
ShellExecutor mainExecutor) {
return new ShellCommandHandlerImpl(shellTaskOrganizer, legacySplitScreenOptional,
- pipOptional, oneHandedOptional, hideDisplayCutout, appPairsOptional,
- mainExecutor).mImpl;
+ splitScreenOptional, pipOptional, oneHandedOptional, hideDisplayCutout,
+ appPairsOptional, mainExecutor).mImpl;
}
private ShellCommandHandlerImpl(
ShellTaskOrganizer shellTaskOrganizer,
Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<SplitScreen> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<OneHanded> oneHandedOptional,
Optional<HideDisplayCutout> hideDisplayCutout,
@@ -69,6 +72,7 @@ public final class ShellCommandHandlerImpl {
ShellExecutor mainExecutor) {
mShellTaskOrganizer = shellTaskOrganizer;
mLegacySplitScreenOptional = legacySplitScreenOptional;
+ mSplitScreenOptional = splitScreenOptional;
mPipOptional = pipOptional;
mOneHandedOptional = oneHandedOptional;
mHideDisplayCutout = hideDisplayCutout;
@@ -88,6 +92,9 @@ public final class ShellCommandHandlerImpl {
pw.println();
pw.println();
mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, ""));
+ pw.println();
+ pw.println();
+ mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw, ""));
}
@@ -102,6 +109,14 @@ public final class ShellCommandHandlerImpl {
return runPair(args, pw);
case "unpair":
return runUnpair(args, pw);
+ case "moveToSideStage":
+ return runMoveToSideStage(args, pw);
+ case "removeFromSideStage":
+ return runRemoveFromSideStage(args, pw);
+ case "setSideStagePosition":
+ return runSetSideStagePosition(args, pw);
+ case "setSideStageVisibility":
+ return runSetSideStageVisibility(args, pw);
case "help":
return runHelp(pw);
default:
@@ -109,7 +124,6 @@ public final class ShellCommandHandlerImpl {
}
}
-
private boolean runPair(String[] args, PrintWriter pw) {
if (args.length < 4) {
// First two arguments are "WMShell" and command name.
@@ -133,6 +147,53 @@ public final class ShellCommandHandlerImpl {
return true;
}
+ private boolean runMoveToSideStage(String[] args, PrintWriter pw) {
+ if (args.length < 3) {
+ // First arguments are "WMShell" and command name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = new Integer(args[2]);
+ final int sideStagePosition = args.length > 3
+ ? new Integer(args[3]) : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+ mSplitScreenOptional.ifPresent(split -> split.moveToSideStage(taskId, sideStagePosition));
+ return true;
+ }
+
+ private boolean runRemoveFromSideStage(String[] args, PrintWriter pw) {
+ if (args.length < 3) {
+ // First arguments are "WMShell" and command name.
+ pw.println("Error: task id should be provided as arguments");
+ return false;
+ }
+ final int taskId = new Integer(args[2]);
+ mSplitScreenOptional.ifPresent(split -> split.removeFromSideStage(taskId));
+ return true;
+ }
+
+ private boolean runSetSideStagePosition(String[] args, PrintWriter pw) {
+ if (args.length < 3) {
+ // First arguments are "WMShell" and command name.
+ pw.println("Error: side stage position should be provided as arguments");
+ return false;
+ }
+ final int position = new Integer(args[2]);
+ mSplitScreenOptional.ifPresent(split -> split.setSideStagePosition(position));
+ return true;
+ }
+
+ private boolean runSetSideStageVisibility(String[] args, PrintWriter pw) {
+ if (args.length < 3) {
+ // First arguments are "WMShell" and command name.
+ pw.println("Error: side stage position should be provided as arguments");
+ return false;
+ }
+ final Boolean visible = new Boolean(args[2]);
+
+ mSplitScreenOptional.ifPresent(split -> split.setSideStageVisibility(visible));
+ return true;
+ }
+
private boolean runHelp(PrintWriter pw) {
pw.println("Window Manager Shell commands:");
pw.println(" help");
@@ -142,6 +203,14 @@ public final class ShellCommandHandlerImpl {
pw.println(" pair <taskId1> <taskId2>");
pw.println(" unpair <taskId>");
pw.println(" Pairs/unpairs tasks with given ids.");
+ pw.println(" moveToSideStage <taskId> <SideStagePosition>");
+ pw.println(" Move a task with given id in split-screen mode.");
+ pw.println(" removeFromSideStage <taskId>");
+ pw.println(" Remove a task with given id in split-screen mode.");
+ pw.println(" setSideStagePosition <SideStagePosition>");
+ pw.println(" Sets the position of the side-stage.");
+ pw.println(" setSideStageVisibility <true/false>");
+ pw.println(" Show/hide side-stage.");
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
index 0cee0a21bde3..b43203dbfd77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
@@ -24,6 +24,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
+import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
@@ -38,6 +39,7 @@ public class ShellInitImpl {
private final DragAndDropController mDragAndDropController;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final Optional<LegacySplitScreen> mLegacySplitScreenOptional;
+ private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<AppPairs> mAppPairsOptional;
private final FullscreenTaskListener mFullscreenTaskListener;
private final ShellExecutor mMainExecutor;
@@ -49,6 +51,7 @@ public class ShellInitImpl {
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<SplitScreen> splitScreenOptional,
Optional<AppPairs> appPairsOptional,
FullscreenTaskListener fullscreenTaskListener,
Transitions transitions,
@@ -57,6 +60,7 @@ public class ShellInitImpl {
dragAndDropController,
shellTaskOrganizer,
legacySplitScreenOptional,
+ splitScreenOptional,
appPairsOptional,
fullscreenTaskListener,
transitions,
@@ -67,6 +71,7 @@ public class ShellInitImpl {
DragAndDropController dragAndDropController,
ShellTaskOrganizer shellTaskOrganizer,
Optional<LegacySplitScreen> legacySplitScreenOptional,
+ Optional<SplitScreen> splitScreenOptional,
Optional<AppPairs> appPairsOptional,
FullscreenTaskListener fullscreenTaskListener,
Transitions transitions,
@@ -75,6 +80,7 @@ public class ShellInitImpl {
mDragAndDropController = dragAndDropController;
mShellTaskOrganizer = shellTaskOrganizer;
mLegacySplitScreenOptional = legacySplitScreenOptional;
+ mSplitScreenOptional = splitScreenOptional;
mAppPairsOptional = appPairsOptional;
mFullscreenTaskListener = fullscreenTaskListener;
mTransitions = transitions;
@@ -91,6 +97,7 @@ public class ShellInitImpl {
mShellTaskOrganizer.registerOrganizer();
mAppPairsOptional.ifPresent(AppPairs::onOrganizerRegistered);
+ mSplitScreenOptional.ifPresent(SplitScreen::onOrganizerRegistered);
// Bind the splitscreen impl to the drag drop controller
mDragAndDropController.initialize(mLegacySplitScreenOptional);
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 faa4a0ed2294..c9b38d00c0ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -262,12 +262,6 @@ public class ShellTaskOrganizer extends TaskOrganizer {
synchronized (mLock) {
ProtoLog.v(WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId);
final TaskAppearedInfo data = mTasks.get(taskInfo.taskId);
- if (data == null) {
- // TODO(b/171749427): It means onTaskInfoChanged send before onTaskAppeared or
- // after onTaskVanished, it should be fixed in controller side.
- return;
- }
-
final TaskListener oldListener = getTaskListener(data.getTaskInfo());
final TaskListener newListener = getTaskListener(taskInfo);
mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, data.getLeash()));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
index 563de06fe40c..bab5140e2f52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
@@ -94,7 +94,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan
mTaskInfo1 = task1;
mTaskInfo2 = task2;
- mSplitLayout = new SplitLayout(
+ mSplitLayout = new SplitLayout(TAG + "SplitDivider",
mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
mRootTaskInfo.configuration, this /* layoutChangeListener */,
b -> b.setParent(mRootTaskLeash));
@@ -248,13 +248,13 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan
final Rect bounds1 = layout.getBounds1();
final Rect bounds2 = layout.getBounds2();
mSyncQueue.runInSync(t -> t
- // Ignores the original surface bounds so that the app could fill up the gap
- // between each surface with corresponding background while resizing.
- .setWindowCrop(mTaskLeash1, bounds1.width(), bounds1.height())
- .setWindowCrop(mTaskLeash2, bounds2.width(), bounds2.height())
.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
.setPosition(mTaskLeash1, bounds1.left, bounds1.top)
- .setPosition(mTaskLeash2, bounds2.left, bounds2.top));
+ .setPosition(mTaskLeash2, bounds2.left, bounds2.top)
+ // Sets crop to prevent visible region of tasks overlap with each other when
+ // re-positioning surfaces while resizing.
+ .setWindowCrop(mTaskLeash1, bounds1.width(), bounds1.height())
+ .setWindowCrop(mTaskLeash2, bounds2.width(), bounds2.height()));
}
@Override
@@ -271,10 +271,11 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.LayoutChan
mSyncQueue.runInSync(t -> t
// Resets layer of divider bar to make sure it is always on top.
.setLayer(dividerLeash, Integer.MAX_VALUE)
- .setWindowCrop(mTaskLeash1, bounds1.width(), bounds1.height())
- .setWindowCrop(mTaskLeash2, bounds2.width(), bounds2.height())
.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
.setPosition(mTaskLeash1, bounds1.left, bounds1.top)
- .setPosition(mTaskLeash2, bounds2.left, bounds2.top));
+ .setPosition(mTaskLeash2, bounds2.left, bounds2.top)
+ // Resets crop to apply new surface bounds directly.
+ .setWindowCrop(mTaskLeash1, null)
+ .setWindowCrop(mTaskLeash2, null));
}
}
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 291e9bdad69b..9721d302a98a 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
@@ -55,14 +55,15 @@ public class SplitLayout {
private Context mContext;
private DividerSnapAlgorithm mDividerSnapAlgorithm;
private int mDividePosition;
+ private boolean mInitialized = false;
- public SplitLayout(Context context, Configuration configuration,
+ public SplitLayout(String windowName, Context context, Configuration configuration,
LayoutChangeListener layoutChangeListener,
SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks) {
mContext = context.createConfigurationContext(configuration);
mLayoutChangeListener = layoutChangeListener;
mSplitWindowManager = new SplitWindowManager(
- mContext, configuration, parentContainerCallbacks);
+ windowName, mContext, configuration, parentContainerCallbacks);
mDividerWindowWidth = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.docked_stack_divider_thickness);
@@ -72,8 +73,7 @@ public class SplitLayout {
mRootBounds.set(configuration.windowConfiguration.getBounds());
mDividerSnapAlgorithm = getSnapAlgorithm(context.getResources(), mRootBounds);
- mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
- updateBounds(mDividePosition);
+ resetDividerPosition();
}
/** Gets bounds of the primary split. */
@@ -112,8 +112,13 @@ public class SplitLayout {
mSplitWindowManager.setConfiguration(configuration);
mRootBounds.set(rootBounds);
mDividerSnapAlgorithm = getSnapAlgorithm(mContext.getResources(), mRootBounds);
- mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
- updateBounds(mDividePosition);
+ resetDividerPosition();
+
+ // Don't inflate divider bar if it is not initialized.
+ if (!mInitialized) {
+ return false;
+ }
+
release();
init();
return true;
@@ -141,11 +146,15 @@ public class SplitLayout {
/** Inflates {@link DividerView} on the root surface. */
public void init() {
+ if (mInitialized) return;
+ mInitialized = true;
mSplitWindowManager.init(this);
}
/** Releases the surface holding the current {@link DividerView}. */
public void release() {
+ if (!mInitialized) return;
+ mInitialized = false;
mSplitWindowManager.release();
}
@@ -166,6 +175,12 @@ public class SplitLayout {
mSplitWindowManager.setResizingSplits(false);
}
+ /** Resets divider position. */
+ public void resetDividerPosition() {
+ mDividePosition = mDividerSnapAlgorithm.getMiddleTarget().position;
+ updateBounds(mDividePosition);
+ }
+
/**
* Sets new divide position and updates bounds correspondingly. Notifies listener if the new
* target indicates dismissing split.
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 29116bd6f956..7f9c34f5df7a 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
@@ -51,22 +51,23 @@ import com.android.wm.shell.R;
*/
public final class SplitWindowManager extends WindowlessWindowManager {
private static final String TAG = SplitWindowManager.class.getSimpleName();
- private static final String DIVIDER_WINDOW_TITLE = "SplitDivider";
private final ParentContainerCallbacks mParentContainerCallbacks;
private Context mContext;
private SurfaceControlViewHost mViewHost;
private boolean mResizingSplits;
+ private final String mWindowName;
public interface ParentContainerCallbacks {
void attachToParentSurface(SurfaceControl.Builder b);
}
- public SplitWindowManager(Context context, Configuration config,
+ public SplitWindowManager(String windowName, Context context, Configuration config,
ParentContainerCallbacks parentContainerCallbacks) {
super(config, null /* rootSurface */, null /* hostInputToken */);
mContext = context.createConfigurationContext(config);
mParentContainerCallbacks = parentContainerCallbacks;
+ mWindowName = windowName;
}
@Override
@@ -106,7 +107,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
| FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.token = new Binder();
- lp.setTitle(DIVIDER_WINDOW_TITLE);
+ lp.setTitle(mWindowName);
lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
mViewHost.setView(dividerView, lp);
dividerView.setup(splitLayout, mViewHost);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
index 0396e1d11398..94c6f018b6ac 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
@@ -18,7 +18,10 @@ package com.android.wm.shell.legacysplitscreen;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
@@ -52,6 +55,17 @@ class WindowManagerProxy {
private static final String TAG = "WindowManagerProxy";
private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
+ private static final int[] CONTROLLED_ACTIVITY_TYPES = {
+ ACTIVITY_TYPE_STANDARD,
+ ACTIVITY_TYPE_HOME,
+ ACTIVITY_TYPE_RECENTS,
+ ACTIVITY_TYPE_UNDEFINED
+ };
+ private static final int[] CONTROLLED_WINDOWING_MODES = {
+ WINDOWING_MODE_FULLSCREEN,
+ WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
+ WINDOWING_MODE_UNDEFINED
+ };
@GuardedBy("mDockedRect")
private final Rect mDockedRect = new Rect();
@@ -171,8 +185,9 @@ class WindowManagerProxy {
// Set launchtile first so that any stack created after
// getAllRootTaskInfos and before reparent (even if unlikely) are placed
// correctly.
- mTaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token);
WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setLaunchRoot(tiles.mSecondary.token, CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES);
final boolean isHomeResizable = buildEnterSplit(wct, tiles, layout);
applySyncTransaction(wct);
return isHomeResizable;
@@ -231,12 +246,12 @@ class WindowManagerProxy {
/** @see #buildDismissSplit */
void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
boolean dismissOrMaximize) {
- // Set launch root first so that any task created after getChildContainers and
- // before reparent (pretty unlikely) are put into fullscreen.
- mTaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
// TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
// plus specific APIs to clean this up.
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Set launch root first so that any task created after getChildContainers and
+ // before reparent (pretty unlikely) are put into fullscreen.
+ wct.setLaunchRoot(tiles.mSecondary.token, null, null);
buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
applySyncTransaction(wct);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
index a7c34fd4465a..d96d4d0a6a3c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java
@@ -138,7 +138,7 @@ public class PipMediaController {
public void onActivityPinned() {
// Once we enter PiP, try to find the active media controller for the top most activity
resolveActiveMediaController(mMediaSessionManager.getActiveSessionsForUser(null,
- UserHandle.USER_CURRENT));
+ UserHandle.CURRENT));
}
/**
@@ -245,7 +245,7 @@ public class PipMediaController {
public void registerSessionListenerForCurrentUser() {
mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener);
mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null,
- UserHandle.USER_CURRENT, null);
+ UserHandle.CURRENT, null);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 519ce3611ce7..e706f76faca1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -152,8 +152,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
public void onPipAnimationStart(PipAnimationController.PipTransitionAnimator animator) {
final int direction = animator.getTransitionDirection();
if (direction == TRANSITION_DIRECTION_TO_PIP) {
- InteractionJankMonitor.getInstance().begin(
- InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP, 2000);
+ // TODO (b//169221267): Add jank listener for transactions without buffer updates.
+ //InteractionJankMonitor.getInstance().begin(
+ // InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP, 2000);
}
sendOnPipTransitionStarted(direction);
}
@@ -166,8 +167,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
animator.getAnimationType());
sendOnPipTransitionFinished(direction);
if (direction == TRANSITION_DIRECTION_TO_PIP) {
- InteractionJankMonitor.getInstance().end(
- InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
+ // TODO (b//169221267): Add jank listener for transactions without buffer updates.
+ //InteractionJankMonitor.getInstance().end(
+ // InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
index de3bb2950c0a..f8b4dd9bc621 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUiEventLogger.java
@@ -41,12 +41,12 @@ public class PipUiEventLogger {
}
public void setTaskInfo(TaskInfo taskInfo) {
- if (taskInfo == null) {
- mPackageName = null;
- mPackageUid = INVALID_PACKAGE_UID;
- } else {
+ if (taskInfo != null && taskInfo.topActivity != null) {
mPackageName = taskInfo.topActivity.getPackageName();
mPackageUid = getUid(mPackageName, taskInfo.userId);
+ } else {
+ mPackageName = null;
+ mPackageUid = INVALID_PACKAGE_UID;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
new file mode 100644
index 000000000000..552eba4ed5b3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.graphics.Rect;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Main stage for split-screen mode. When split-screen is active all standard activity types launch
+ * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
+ * @see StageCoordinator
+ */
+class MainStage extends StageTaskListener {
+ private static final String TAG = MainStage.class.getSimpleName();
+
+ private boolean mIsActive = false;
+
+ private static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
+ private static final int[] CONTROLLED_WINDOWING_MODES =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
+ private static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
+
+ MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue) {
+ super(taskOrganizer, displayId, callbacks, syncQueue);
+ }
+
+ boolean isActive() {
+ return mIsActive;
+ }
+
+ void activate(Rect rootBounds, WindowContainerTransaction wct) {
+ if (mIsActive) return;
+
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setHidden(rootToken, false)
+ .setBounds(rootToken, rootBounds)
+ .setLaunchRoot(
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES)
+ .reparentTasks(
+ null /* currentParent */,
+ rootToken,
+ CONTROLLED_WINDOWING_MODES,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */)
+ // Moving the root task to top after the child tasks were repareted , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true /* onTop */);
+
+ mIsActive = true;
+ }
+
+ void deactivate(WindowContainerTransaction wct) {
+ if (!mIsActive) return;
+ mIsActive = false;
+
+ if (mRootTaskInfo == null) return;
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setHidden(rootToken, true)
+ .setLaunchRoot(
+ rootToken,
+ null,
+ null)
+ .reparentTasks(
+ rootToken,
+ null /* newParent */,
+ CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ CONTROLLED_ACTIVITY_TYPES,
+ true /* onTop */)
+ .reorder(rootToken, false /* onTop */);
+ }
+
+ void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds)
+ .setWindowingMode(mRootTaskInfo.token, windowingMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
new file mode 100644
index 000000000000..5645c19d5c46
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import android.app.ActivityManager;
+import android.graphics.Rect;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
+ * here. All other task are launch in the {@link MainStage}.
+ * @see StageCoordinator
+ */
+class SideStage extends StageTaskListener {
+ private static final String TAG = SideStage.class.getSimpleName();
+
+ SideStage(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue) {
+ super(taskOrganizer, displayId, callbacks, syncQueue);
+ }
+
+ void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
+ WindowContainerTransaction wct) {
+ final WindowContainerToken rootToken = mRootTaskInfo.token;
+ wct.setHidden(rootToken, false)
+ .setBounds(rootToken, rootBounds)
+ .reparent(task.token, rootToken, true /* onTop*/)
+ // Moving the root task to top after the child tasks were repareted , or the root
+ // task cannot be visible and focused.
+ .reorder(rootToken, true);
+ }
+
+ boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
+ final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
+ if (task == null) return false;
+
+ wct.setHidden(mRootTaskInfo.token, true)
+ .reorder(mRootTaskInfo.token, false)
+ .reparent(task.token, newParent, false /* onTop */);
+ return true;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
new file mode 100644
index 000000000000..08c2856d6792
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import android.annotation.IntDef;
+import android.app.ActivityManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.common.annotations.ExternalThread;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface to engage split-screen feature.
+ */
+@ExternalThread
+public interface SplitScreen {
+ /**
+ * Specifies that the side-stage is positioned at the top half of the screen if
+ * in portrait mode or at the left half of the screen if in landscape mode.
+ */
+ int SIDE_STAGE_POSITION_TOP_OR_LEFT = 0;
+
+ /**
+ * Specifies that the side-stage is positioned at the bottom half of the screen if
+ * in portrait mode or at the right half of the screen if in landscape mode.
+ */
+ int SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT = 1;
+
+ @IntDef(prefix = { "SIDE_STAGE_POSITION_" }, value = {
+ SIDE_STAGE_POSITION_TOP_OR_LEFT,
+ SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT
+ })
+ @interface SideStagePosition {}
+
+ /** @return {@code true} if split-screen is currently visible. */
+ boolean isSplitScreenVisible();
+ /** Moves a task in the side-stage of split-screen. */
+ boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition);
+ /** Moves a task in the side-stage of split-screen. */
+ boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SideStagePosition int sideStagePosition);
+ /** Removes a task from the side-stage of split-screen. */
+ boolean removeFromSideStage(int taskId);
+ /** Sets the position of the side-stage. */
+ void setSideStagePosition(@SideStagePosition int sideStagePosition);
+ /** Hides the side-stage if it is currently visible. */
+ void setSideStageVisibility(boolean visible);
+ /** Dumps current status of split-screen. */
+ void dump(@NonNull PrintWriter pw, String prefix);
+ /** Called when the shell organizer has been registered. */
+ void onOrganizerRegistered();
+}
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
new file mode 100644
index 000000000000..55cfea5b6da3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.ActivityManager;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import java.io.PrintWriter;
+
+/**
+ * Class manages split-screen multitasking mode and implements the main interface
+ * {@link SplitScreen}.
+ * @see StageCoordinator
+ */
+public class SplitScreenController implements SplitScreen {
+ private static final String TAG = SplitScreenController.class.getSimpleName();
+
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final SyncTransactionQueue mSyncQueue;
+ private final Context mContext;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private StageCoordinator mStageCoordinator;
+
+ public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+ SyncTransactionQueue syncQueue, Context context,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer) {
+ mTaskOrganizer = shellTaskOrganizer;
+ mSyncQueue = syncQueue;
+ mContext = context;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ }
+
+ @Override
+ public void onOrganizerRegistered() {
+ if (mStageCoordinator == null) {
+ // TODO: Multi-display
+ mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mRootTDAOrganizer, mTaskOrganizer);
+ }
+ }
+
+ @Override
+ public boolean isSplitScreenVisible() {
+ return mStageCoordinator.isSplitScreenVisible();
+ }
+
+ @Override
+ public boolean moveToSideStage(int taskId, @SideStagePosition int sideStagePosition) {
+ final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
+ return task != null && moveToSideStage(task, sideStagePosition);
+ }
+
+ @Override
+ public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SideStagePosition int sideStagePosition) {
+ return mStageCoordinator.moveToSideStage(task, sideStagePosition);
+ }
+
+ @Override
+ public boolean removeFromSideStage(int taskId) {
+ return mStageCoordinator.removeFromSideStage(taskId);
+ }
+
+ @Override
+ public void setSideStagePosition(@SideStagePosition int sideStagePosition) {
+ mStageCoordinator.setSideStagePosition(sideStagePosition);
+ }
+
+ @Override
+ public void setSideStageVisibility(boolean visible) {
+ mStageCoordinator.setSideStageVisibility(visible);
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + TAG);
+ if (mStageCoordinator != null) {
+ mStageCoordinator.dump(pw, prefix);
+ }
+ }
+
+}
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
new file mode 100644
index 000000000000..0c6edf10ba94
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_TOP_OR_LEFT;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.split.SplitLayout;
+
+import java.io.PrintWriter;
+
+/**
+ * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
+ * {@link SideStage} stages.
+ * Some high-level rules:
+ * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at
+ * least one child task.
+ * - The {@link MainStage} should only have children if the coordinator is active.
+ * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
+ * and {@link SideStage} are visible.
+ * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible.
+ * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
+ * {@link #onStageHasChildrenChanged(StageListenerImpl).}
+ */
+class StageCoordinator implements SplitLayout.LayoutChangeListener,
+ RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener {
+
+ private static final String TAG = StageCoordinator.class.getSimpleName();
+
+ private final MainStage mMainStage;
+ private final StageListenerImpl mMainStageListener = new StageListenerImpl();
+ private final SideStage mSideStage;
+ private final StageListenerImpl mSideStageListener = new StageListenerImpl();
+ private @SplitScreen.SideStagePosition int mSideStagePosition =
+ SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+
+ private final int mDisplayId;
+ private SplitLayout mSplitLayout;
+ private boolean mDividerVisible;
+ private final SyncTransactionQueue mSyncQueue;
+ private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private DisplayAreaInfo mDisplayAreaInfo;
+ private final Context mContext;
+
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mMainStage = new MainStage(mTaskOrganizer, mDisplayId, mMainStageListener, mSyncQueue);
+ mSideStage = new SideStage(mTaskOrganizer, mDisplayId, mSideStageListener, mSyncQueue);
+ mRootTDAOrganizer.registerListener(displayId, this);
+ }
+
+ @VisibleForTesting
+ StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ MainStage mainStage, SideStage sideStage) {
+ mContext = context;
+ mDisplayId = displayId;
+ mSyncQueue = syncQueue;
+ mRootTDAOrganizer = rootTDAOrganizer;
+ mTaskOrganizer = taskOrganizer;
+ mMainStage = mainStage;
+ mSideStage = sideStage;
+ mRootTDAOrganizer.registerListener(displayId, this);
+ }
+
+ boolean isSplitScreenVisible() {
+ return mSideStageListener.mVisible && mMainStageListener.mVisible;
+ }
+
+ boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
+ @SplitScreen.SideStagePosition int sideStagePosition) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSideStagePosition = sideStagePosition;
+ mMainStage.activate(getMainStageBounds(), wct);
+ mSideStage.addTask(task, getSideStageBounds(), wct);
+ mTaskOrganizer.applyTransaction(wct);
+ return true;
+ }
+
+ boolean removeFromSideStage(int taskId) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+
+ /**
+ * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
+ * {@link SideStage} no longer has children.
+ */
+ final boolean result = mSideStage.removeTask(taskId,
+ mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null,
+ wct);
+ mTaskOrganizer.applyTransaction(wct);
+ return result;
+ }
+
+ void setSideStagePosition(@SplitScreen.SideStagePosition int sideStagePosition) {
+ mSideStagePosition = sideStagePosition;
+ if (mSideStageListener.mVisible) {
+ onStageVisibilityChanged(mSideStageListener);
+ }
+ }
+
+ void setSideStageVisibility(boolean visible) {
+ if (!mSideStageListener.mVisible == visible) return;
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSideStage.setVisibility(visible, wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ private void onStageRootTaskVanished(StageListenerImpl stageListener) {
+ if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ // Deactivate the main stage if it no longer has a root task.
+ mMainStage.deactivate(wct);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ private void onStageVisibilityChanged(StageListenerImpl stageListener) {
+ final boolean sideStageVisible = mSideStageListener.mVisible;
+ final boolean mainStageVisible = mMainStageListener.mVisible;
+ // Divider is only visible if both the main stage and side stages are visible
+ final boolean dividerVisible = sideStageVisible && mainStageVisible;
+
+ if (mDividerVisible != dividerVisible) {
+ mDividerVisible = dividerVisible;
+ if (mDividerVisible) {
+ mSplitLayout.init();
+ } else {
+ mSplitLayout.release();
+ }
+ }
+
+ if (mainStageVisible) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (sideStageVisible) {
+ // The main stage configuration should to follow split layout when side stage is
+ // visible.
+ mMainStage.updateConfiguration(
+ WINDOWING_MODE_MULTI_WINDOW, getMainStageBounds(), wct);
+ } else {
+ // We want the main stage configuration to be fullscreen when the side stage isn't
+ // visible.
+ mMainStage.updateConfiguration(WINDOWING_MODE_FULLSCREEN, null, wct);
+ }
+ // TODO: Change to `mSyncQueue.queue(wct)` once BLAST is stable.
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ mSyncQueue.runInSync(t -> {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ final SurfaceControl sideStageLeash = mSideStage.mRootLeash;
+ final SurfaceControl mainStageLeash = mMainStage.mRootLeash;
+
+ if (dividerLeash != null) {
+ if (mDividerVisible) {
+ t.show(dividerLeash)
+ .setLayer(dividerLeash, Integer.MAX_VALUE)
+ .setPosition(dividerLeash,
+ mSplitLayout.getDividerBounds().left,
+ mSplitLayout.getDividerBounds().top);
+ } else {
+ t.hide(dividerLeash);
+ }
+ }
+
+ if (sideStageVisible) {
+ final Rect sideStageBounds = getSideStageBounds();
+ t.show(sideStageLeash)
+ .setPosition(sideStageLeash,
+ sideStageBounds.left, sideStageBounds.top)
+ .setWindowCrop(sideStageLeash,
+ sideStageBounds.width(), sideStageBounds.height());
+ } else {
+ t.hide(sideStageLeash);
+ }
+
+ if (mainStageVisible) {
+ final Rect mainStageBounds = getMainStageBounds();
+ t.show(mainStageLeash);
+ if (sideStageVisible) {
+ t.setPosition(mainStageLeash, mainStageBounds.left, mainStageBounds.top)
+ .setWindowCrop(mainStageLeash,
+ mainStageBounds.width(), mainStageBounds.height());
+ } else {
+ // Clear window crop and position if side stage isn't visible.
+ t.setPosition(mainStageLeash, 0, 0)
+ .setWindowCrop(mainStageLeash, null);
+ }
+ } else {
+ t.hide(mainStageLeash);
+ }
+ });
+ }
+
+ private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
+ if (stageListener == mSideStageListener) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (mSideStageListener.mHasChildren) {
+ // Make sure the main stage is active.
+ mMainStage.activate(getMainStageBounds(), wct);
+ } else {
+ // The side stage no long has children so we can deactivate the main stage.
+ mMainStage.deactivate(wct);
+ }
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+
+ @Override
+ public void onSnappedToDismiss(boolean snappedToEnd) {
+ // TODO: What to do...what to do...
+ mSplitLayout.resetDividerPosition();
+ onBoundsChanged(mSplitLayout);
+ }
+
+ @Override
+ public void onBoundsChanging(SplitLayout layout) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) return;
+ final Rect mainStageBounds = getMainStageBounds();
+ final Rect sideStageBounds = getSideStageBounds();
+
+ mSyncQueue.runInSync(t -> t
+ .setPosition(dividerLeash,
+ mSplitLayout.getDividerBounds().left, mSplitLayout.getDividerBounds().top)
+ .setPosition(mMainStage.mRootLeash, mainStageBounds.left, mainStageBounds.top)
+ .setPosition(mSideStage.mRootLeash, sideStageBounds.left, sideStageBounds.top)
+ // Sets crop to prevent visible region of tasks overlap with each other when
+ // re-positioning surfaces while resizing.
+ .setWindowCrop(mMainStage.mRootLeash,
+ mainStageBounds.width(), mainStageBounds.height())
+ .setWindowCrop(mSideStage.mRootLeash,
+ sideStageBounds.width(), sideStageBounds.height()));
+
+ }
+
+ @Override
+ public void onBoundsChanged(SplitLayout layout) {
+ final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
+ if (dividerLeash == null) return;
+ final Rect mainStageBounds = getMainStageBounds();
+ final Rect sideStageBounds = getSideStageBounds();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mMainStage.setBounds(mainStageBounds, wct);
+ mSideStage.setBounds(sideStageBounds, wct);
+ mTaskOrganizer.applyTransaction(wct);
+
+ mSyncQueue.runInSync(t -> t
+ // Resets layer of divider bar to make sure it is always on top.
+ .setLayer(dividerLeash, Integer.MAX_VALUE)
+ .setPosition(dividerLeash,
+ mSplitLayout.getDividerBounds().left, mSplitLayout.getDividerBounds().top)
+ .setPosition(mMainStage.mRootLeash,
+ mainStageBounds.left, mainStageBounds.top)
+ .setPosition(mSideStage.mRootLeash,
+ sideStageBounds.left, sideStageBounds.top)
+ // Resets crop to apply new surface bounds directly.
+ .setWindowCrop(mMainStage.mRootLeash, null)
+ .setWindowCrop(mSideStage.mRootLeash, null));
+ }
+
+ @Override
+ public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout == null) {
+ mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
+ mDisplayAreaInfo.configuration, this,
+ b -> mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b));
+ }
+ }
+
+ @Override
+ public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
+ throw new IllegalStateException("Well that was unexpected...");
+ }
+
+ @Override
+ public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
+ mDisplayAreaInfo = displayAreaInfo;
+ if (mSplitLayout != null
+ && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)) {
+ onBoundsChanged(mSplitLayout);
+ }
+ }
+
+ private Rect getSideStageBounds() {
+ return mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
+ }
+
+ private Rect getMainStageBounds() {
+ return mSideStagePosition == SIDE_STAGE_POSITION_TOP_OR_LEFT
+ ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
+ }
+
+ @Override
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ final String childPrefix = innerPrefix + " ";
+ pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
+ pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
+ pw.println(innerPrefix + "MainStage");
+ pw.println(childPrefix + "isActive=" + mMainStage.isActive());
+ mMainStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "SideStage");
+ mSideStageListener.dump(pw, childPrefix);
+ pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout);
+ }
+
+ class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
+ boolean mHasRootTask = false;
+ boolean mVisible = false;
+ boolean mHasChildren = false;
+
+ @Override
+ public void onRootTaskAppeared() {
+ mHasRootTask = true;
+ }
+
+ @Override
+ public void onStatusChanged(boolean visible, boolean hasChildren) {
+ if (!mHasRootTask) return;
+
+ if (mHasChildren != hasChildren) {
+ mHasChildren = hasChildren;
+ StageCoordinator.this.onStageHasChildrenChanged(this);
+ }
+ if (mVisible != visible) {
+ mVisible = visible;
+ StageCoordinator.this.onStageVisibilityChanged(this);
+ }
+ }
+
+ @Override
+ public void onRootTaskVanished() {
+ reset();
+ StageCoordinator.this.onStageRootTaskVanished(this);
+ }
+
+ private void reset() {
+ mHasRootTask = false;
+ mVisible = false;
+ mHasChildren = false;
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ pw.println(prefix + "mHasRootTask=" + mHasRootTask);
+ pw.println(prefix + "mVisible=" + mVisible);
+ pw.println(prefix + "mHasChildren=" + mHasChildren);
+ }
+ }
+}
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
new file mode 100644
index 000000000000..30f2701151d7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import android.annotation.CallSuper;
+import android.app.ActivityManager;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.transition.Transitions;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class that handle common task org. related for split-screen stages.
+ * Note that this class and its sub-class do not directly perform hierarchy operations.
+ * They only serve to hold a collection of tasks and provide APIs like
+ * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
+ * to perform operations in-sync with other containers.
+ * @see StageCoordinator
+ */
+class StageTaskListener implements ShellTaskOrganizer.TaskListener {
+ private static final String TAG = StageTaskListener.class.getSimpleName();
+
+ /** Callback interface for listening to changes in a split-screen stage. */
+ public interface StageListenerCallbacks {
+ void onRootTaskAppeared();
+ void onStatusChanged(boolean visible, boolean hasChildren);
+ void onRootTaskVanished();
+ }
+ private final StageListenerCallbacks mCallbacks;
+ private final SyncTransactionQueue mSyncQueue;
+
+ protected ActivityManager.RunningTaskInfo mRootTaskInfo;
+ protected SurfaceControl mRootLeash;
+ protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
+ private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
+
+ StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
+ StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue) {
+ mCallbacks = callbacks;
+ mSyncQueue = syncQueue;
+ taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
+ if (!taskInfo.hasParentTask()) {
+ mCallbacks.onRootTaskAppeared();
+ mRootLeash = leash;
+ mRootTaskInfo = taskInfo;
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ mChildrenLeashes.put(taskInfo.taskId, leash);
+ mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
+ } else {
+ throw new IllegalArgumentException("Unknown task: " + taskInfo);
+ }
+ sendStatusChanged();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ if (mRootTaskInfo.taskId == taskInfo.taskId) {
+ mRootTaskInfo = taskInfo;
+ } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
+ updateChildTaskSurface(
+ taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
+ } else {
+ throw new IllegalArgumentException("Unknown task: " + taskInfo);
+ }
+ sendStatusChanged();
+ }
+
+ @Override
+ @CallSuper
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ final int taskId = taskInfo.taskId;
+ if (mRootTaskInfo.taskId == taskId) {
+ mCallbacks.onRootTaskVanished();
+ mRootTaskInfo = null;
+ } else if (mChildrenTaskInfo.contains(taskId)) {
+ mChildrenTaskInfo.remove(taskId);
+ mChildrenLeashes.remove(taskId);
+ sendStatusChanged();
+ } else {
+ throw new IllegalArgumentException("Unknown task: " + taskInfo);
+ }
+ }
+
+ void setBounds(Rect bounds, WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, bounds);
+ }
+
+ void setVisibility(boolean visible, WindowContainerTransaction wct) {
+ wct.reorder(mRootTaskInfo.token, visible /* onTop */);
+ }
+
+ private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl leash, boolean firstAppeared) {
+ final Point taskPositionInParent = taskInfo.positionInParent;
+ mSyncQueue.runInSync(t -> {
+ t.setWindowCrop(leash, null);
+ t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
+ if (firstAppeared && !Transitions.ENABLE_SHELL_TRANSITIONS) {
+ t.setAlpha(leash, 1f);
+ t.setMatrix(leash, 1, 0, 0, 1);
+ t.show(leash);
+ }
+ });
+ }
+
+ private void sendStatusChanged() {
+ mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
+ }
+
+ @Override
+ @CallSuper
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
new file mode 100644
index 000000000000..4cd2c504c83e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.view.WindowManager.TRANSIT_CLOSE;
+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.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.os.IBinder;
+import android.util.ArrayMap;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+import java.util.ArrayList;
+
+/** The default handler that handles anything not already handled. */
+public class DefaultTransitionHandler implements Transitions.TransitionHandler {
+ private final TransactionPool mTransactionPool;
+ private final ShellExecutor mMainExecutor;
+ private final ShellExecutor mAnimExecutor;
+
+ /** Keeps track of the currently-running animations associated with each transition. */
+ private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>();
+
+ DefaultTransitionHandler(@NonNull TransactionPool transactionPool,
+ @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
+ mTransactionPool = transactionPool;
+ mMainExecutor = mainExecutor;
+ mAnimExecutor = animExecutor;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
+ if (mAnimations.containsKey(transition)) {
+ throw new IllegalStateException("Got a duplicate startAnimation call for "
+ + transition);
+ }
+ final ArrayList<Animator> animations = new ArrayList<>();
+ mAnimations.put(transition, animations);
+ final boolean isOpening = Transitions.isOpeningType(info.getType());
+
+ final Runnable onAnimFinish = () -> {
+ if (!animations.isEmpty()) return;
+ mAnimations.remove(transition);
+ finishCallback.run();
+ };
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+
+ // Don't animate anything with an animating parent
+ if (change.getParent() != null) continue;
+
+ final int mode = change.getMode();
+ if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
+ if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
+ // This received a transferred starting window, so don't animate
+ continue;
+ }
+ // fade in
+ startExampleAnimation(
+ animations, change.getLeash(), true /* show */, onAnimFinish);
+ } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
+ // fade out
+ startExampleAnimation(
+ animations, change.getLeash(), false /* show */, onAnimFinish);
+ }
+ }
+ t.apply();
+ // run finish now in-case there are no animations
+ onAnimFinish.run();
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(int type, @NonNull IBinder transition,
+ @Nullable ActivityManager.RunningTaskInfo triggerTask) {
+ return null;
+ }
+
+ // TODO(shell-transitions): real animations
+ private void startExampleAnimation(@NonNull ArrayList<Animator> animations,
+ @NonNull SurfaceControl leash, boolean show, @NonNull Runnable finishCallback) {
+ final float end = show ? 1.f : 0.f;
+ final float start = 1.f - end;
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ final ValueAnimator va = ValueAnimator.ofFloat(start, end);
+ va.setDuration(500);
+ va.addUpdateListener(animation -> {
+ float fraction = animation.getAnimatedFraction();
+ transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
+ transaction.apply();
+ });
+ final Runnable finisher = () -> {
+ transaction.setAlpha(leash, end);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ mMainExecutor.execute(() -> {
+ animations.remove(va);
+ finishCallback.run();
+ });
+ };
+ va.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animation) { }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ finisher.run();
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) { }
+ });
+ animations.add(va);
+ mAnimExecutor.execute(va::start);
+ }
+}
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 7ce71b0a1158..3b2ac70007e4 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
@@ -23,8 +23,6 @@ import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
-import android.animation.Animator;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -41,6 +39,7 @@ import android.window.WindowOrganizer;
import androidx.annotation.BinderThread;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
@@ -48,6 +47,7 @@ import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.ArrayList;
+import java.util.Arrays;
/** Plays transition animations */
public class Transitions {
@@ -58,7 +58,6 @@ public class Transitions {
SystemProperties.getBoolean("persist.debug.shell_transit", false);
private final WindowOrganizer mOrganizer;
- private final TransactionPool mTransactionPool;
private final ShellExecutor mMainExecutor;
private final ShellExecutor mAnimExecutor;
private final TransitionPlayerImpl mPlayerImpl;
@@ -67,7 +66,6 @@ public class Transitions {
private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>();
private static final class ActiveTransition {
- ArrayList<Animator> mAnimations = null;
TransitionHandler mFirstHandler = null;
}
@@ -77,10 +75,11 @@ public class Transitions {
public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool,
@NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) {
mOrganizer = organizer;
- mTransactionPool = pool;
mMainExecutor = mainExecutor;
mAnimExecutor = animExecutor;
mPlayerImpl = new TransitionPlayerImpl();
+ // The very last handler (0 in the list) should be the default one.
+ mHandlers.add(new DefaultTransitionHandler(pool, mainExecutor, animExecutor));
}
/** Register this transition handler with Core */
@@ -104,47 +103,10 @@ public class Transitions {
return mAnimExecutor;
}
- // TODO(shell-transitions): real animations
- private void startExampleAnimation(@NonNull IBinder transition, @NonNull SurfaceControl leash,
- boolean show) {
- final float end = show ? 1.f : 0.f;
- final float start = 1.f - end;
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- final ValueAnimator va = ValueAnimator.ofFloat(start, end);
- va.setDuration(500);
- va.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
- transaction.apply();
- });
- final Runnable finisher = () -> {
- transaction.setAlpha(leash, end);
- transaction.apply();
- mTransactionPool.release(transaction);
- mMainExecutor.execute(() -> {
- mActiveTransitions.get(transition).mAnimations.remove(va);
- onFinish(transition);
- });
- };
- va.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) { }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) { }
- });
- mActiveTransitions.get(transition).mAnimations.add(va);
- mAnimExecutor.execute(va::start);
+ /** Only use this in tests. This is used to avoid running animations during tests. */
+ @VisibleForTesting
+ void replaceDefaultHandlerForTest(TransitionHandler handler) {
+ mHandlers.set(0, handler);
}
/** @return true if the transition was triggered by opening something vs closing something */
@@ -217,18 +179,16 @@ public class Transitions {
}
}
- private void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
+ @VisibleForTesting
+ void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
transitionToken, info);
final ActiveTransition active = mActiveTransitions.get(transitionToken);
if (active == null) {
throw new IllegalStateException("Got transitionReady for non-active transition "
- + transitionToken + ". expecting one of " + mActiveTransitions.keySet());
- }
- if (active.mAnimations != null) {
- throw new IllegalStateException("Got a duplicate onTransitionReady call for "
- + transitionToken);
+ + transitionToken + ". expecting one of "
+ + Arrays.toString(mActiveTransitions.keySet().toArray()));
}
if (!info.getRootLeash().isValid()) {
// Invalid root-leash implies that the transition is empty/no-op, so just do
@@ -253,44 +213,21 @@ public class Transitions {
return;
}
}
-
- // No handler chose to perform this animation, so fall-back to the
- // default animation handling.
- final boolean isOpening = isOpeningType(info.getType());
- active.mAnimations = new ArrayList<>(); // Play fade animations
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
-
- // Don't animate anything with an animating parent
- if (change.getParent() != null) continue;
-
- final int mode = info.getChanges().get(i).getMode();
- if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
- if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) {
- // This received a transferred starting window, so don't animate
- continue;
- }
- // fade in
- startExampleAnimation(transitionToken, change.getLeash(), true /* show */);
- } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
- // fade out
- startExampleAnimation(transitionToken, change.getLeash(), false /* show */);
- }
- }
- t.apply();
- onFinish(transitionToken);
+ throw new IllegalStateException(
+ "This shouldn't happen, maybe the default handler is broken.");
}
private void onFinish(IBinder transition) {
- final ActiveTransition active = mActiveTransitions.get(transition);
- if (active.mAnimations != null && !active.mAnimations.isEmpty()) return;
+ if (!mActiveTransitions.containsKey(transition)) {
+ throw new IllegalStateException("Trying to finish an already-finished transition.");
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animations finished, notifying core %s", transition);
mActiveTransitions.remove(transition);
mOrganizer.finishTransition(transition, null, null);
}
- private void requestStartTransition(int type, @NonNull IBinder transitionToken,
+ void requestStartTransition(int type, @NonNull IBinder transitionToken,
@Nullable ActivityManager.RunningTaskInfo triggerTask) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: type=%d %s",
type, transitionToken);
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 5ab1c390a92b..94c1f59d957e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -29,7 +29,7 @@ fun LayersAssertion.appPairsDividerIsVisible(
enabled: Boolean = bugId == 0
) {
end("appPairsDividerIsVisible", bugId, enabled) {
- this.showsLayer(FlickerTestBase.SPLIT_DIVIDER)
+ this.showsLayer(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
}
}
@@ -39,7 +39,19 @@ fun LayersAssertion.appPairsDividerIsInvisible(
enabled: Boolean = bugId == 0
) {
end("appPairsDividerIsInVisible", bugId, enabled) {
- this.hasNotLayer(FlickerTestBase.SPLIT_DIVIDER)
+ this.hasNotLayer(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
+ }
+}
+
+@JvmOverloads
+fun LayersAssertion.appPairsDividerBecomesVisible(
+ bugId: Int = 0,
+ enabled: Boolean = bugId == 0
+) {
+ all("dividerLayerBecomesVisible") {
+ this.hidesLayer(FlickerTestBase.DOCKED_STACK_DIVIDER)
+ .then()
+ .showsLayer(FlickerTestBase.DOCKED_STACK_DIVIDER)
}
}
@@ -97,7 +109,7 @@ fun LayersAssertion.appPairsPrimaryBoundsIsVisible(
end("PrimaryAppBounds", bugId, enabled) {
val entry = this.trace.entries.firstOrNull()
?: throw IllegalStateException("Trace is empty")
- val dividerRegion = entry.getVisibleBounds(FlickerTestBase.SPLIT_DIVIDER)
+ val dividerRegion = entry.getVisibleBounds(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
this.hasVisibleRegion(primaryLayerName, getPrimaryRegion(dividerRegion, rotation))
}
}
@@ -112,7 +124,7 @@ fun LayersAssertion.appPairsSecondaryBoundsIsVisible(
end("SecondaryAppBounds", bugId, enabled) {
val entry = this.trace.entries.firstOrNull()
?: throw IllegalStateException("Trace is empty")
- val dividerRegion = entry.getVisibleBounds(FlickerTestBase.SPLIT_DIVIDER)
+ val dividerRegion = entry.getVisibleBounds(FlickerTestBase.APP_PAIR_SPLIT_DIVIDER)
this.hasVisibleRegion(secondaryLayerName, getSecondaryRegion(dividerRegion, rotation))
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
index 7809be04de96..24e5fef32c0a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/FlickerTestBase.kt
@@ -130,7 +130,7 @@ abstract class FlickerTestBase {
const val NAVIGATION_BAR_WINDOW_TITLE = "NavigationBar"
const val STATUS_BAR_WINDOW_TITLE = "StatusBar"
const val DOCKED_STACK_DIVIDER = "DockedStackDivider"
- const val SPLIT_DIVIDER = "SplitDivider"
+ const val APP_PAIR_SPLIT_DIVIDER = "AppPairSplitDivider"
const val IMAGE_WALLPAPER = "ImageWallpaper"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
index 22b1eb7037fc..cb9fabd3d5ba 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTest.kt
@@ -107,7 +107,7 @@ class AppPairsTest(
end("appsEndingBounds", enabled = false) {
val entry = this.trace.entries.firstOrNull()
?: throw IllegalStateException("Trace is empty")
- val dividerRegion = entry.getVisibleBounds(SPLIT_DIVIDER)
+ val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
this.hasVisibleRegion(primaryApp.defaultWindowName,
appPairsHelper.getPrimaryBounds(dividerRegion))
.and()
@@ -151,7 +151,7 @@ class AppPairsTest(
start("appsStartingBounds", enabled = false) {
val entry = this.trace.entries.firstOrNull()
?: throw IllegalStateException("Trace is empty")
- val dividerRegion = entry.getVisibleBounds(SPLIT_DIVIDER)
+ val dividerRegion = entry.getVisibleBounds(APP_PAIR_SPLIT_DIVIDER)
this.hasVisibleRegion(primaryApp.defaultWindowName,
appPairsHelper.getPrimaryBounds(dividerRegion))
.and()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
index b33fa55c2be1..85bf4a1f8c25 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterLegacySplitScreenTest.kt
@@ -21,7 +21,6 @@ import android.view.Surface
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.dsl.runWithFlicker
-import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.canSplitScreen
import com.android.server.wm.flicker.helpers.exitSplitScreen
import com.android.server.wm.flicker.helpers.isInSplitScreen
@@ -35,6 +34,7 @@ import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
+
import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisible
import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisible
import org.junit.Assert
@@ -59,8 +59,6 @@ class EnterLegacySplitScreenTest(
rotationName: String,
rotation: Int
) : SplitScreenTestBase(rotationName, rotation) {
- private val letterBox = "Letterbox"
-
private val splitScreenSetup: FlickerBuilder
get() = FlickerBuilder(instrumentation).apply {
val testLaunchActivity = "launch_splitScreen_test_activity"
@@ -91,7 +89,6 @@ class EnterLegacySplitScreenTest(
windowManagerTrace {
navBarWindowIsAlwaysVisible()
statusBarWindowIsAlwaysVisible()
- visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(launcherPackageName))
}
}
}
@@ -114,7 +111,8 @@ class EnterLegacySplitScreenTest(
rotation, splitScreenApp.defaultWindowName, 169271943)
dockedStackDividerBecomesVisible()
visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName, splitScreenApp.defaultWindowName)
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ LIVE_WALLPAPER_PACKAGE_NAME)
)
}
windowManagerTrace {
@@ -148,7 +146,7 @@ class EnterLegacySplitScreenTest(
rotation, secondaryApp.defaultWindowName, 169271943)
dockedStackDividerBecomesVisible()
visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName, splitScreenApp.defaultWindowName,
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
secondaryApp.defaultWindowName)
)
}
@@ -157,6 +155,7 @@ class EnterLegacySplitScreenTest(
showsAppWindow(splitScreenApp.defaultWindowName)
.and().showsAppWindow(secondaryApp.defaultWindowName)
}
+ visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(LAUNCHER_PACKAGE_NAME))
}
}
}
@@ -181,85 +180,14 @@ class EnterLegacySplitScreenTest(
layersTrace {
dockedStackDividerIsInvisible()
visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName, nonResizeableApp.defaultWindowName)
+ listOf(LAUNCHER_PACKAGE_NAME, nonResizeableApp.defaultWindowName)
)
}
windowManagerTrace {
end {
hidesAppWindow(nonResizeableApp.defaultWindowName)
}
- }
- }
- }
- }
-
- @Test
- fun testNonResizeableWhenAlreadyInSplitScreenPrimary() {
- val testTag = "testNonResizeableWhenAlreadyInSplitScreenPrimary"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- nonResizeableApp.launchViaIntent()
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- nonResizeableApp.reopenAppFromOverview()
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- end("appsEndingBounds", enabled = false) {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
- }
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName, splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName, letterBox)
- )
- }
- windowManagerTrace {
- end {
- showsAppWindow(nonResizeableApp.defaultWindowName)
- hidesAppWindow(splitScreenApp.defaultWindowName)
- }
- }
- }
- }
- }
-
- @Test
- fun testNonResizeableWhenAlreadyInSplitScreenSecondary() {
- val testTag = "testNonResizeableWhenAlreadyInSplitScreenSecondary"
- runWithFlicker(splitScreenSetup) {
- withTestName { testTag }
- repeat {
- TEST_REPETITIONS
- }
- transitions {
- splitScreenApp.launchViaIntent()
- uiDevice.launchSplitScreen()
- uiDevice.pressBack()
- nonResizeableApp.launchViaIntent()
- }
- assertions {
- layersTrace {
- dockedStackDividerIsInvisible()
- end("appsEndingBounds", enabled = false) {
- val displayBounds = WindowUtils.getDisplayBounds(rotation)
- this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
- }
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName, splitScreenApp.defaultWindowName,
- nonResizeableApp.defaultWindowName, letterBox)
- )
- }
- windowManagerTrace {
- end {
- showsAppWindow(nonResizeableApp.defaultWindowName)
- hidesAppWindow(splitScreenApp.defaultWindowName)
- }
+ visibleWindowsShownMoreThanOneConsecutiveEntry(listOf(LAUNCHER_PACKAGE_NAME))
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
index 573ffc6c3299..9586fd139eb5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottomTest.kt
@@ -22,16 +22,20 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.Flicker
import com.android.server.wm.flicker.FlickerTestRunner
import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.helpers.StandardAppHelper
import com.android.server.wm.flicker.endRotation
import com.android.server.wm.flicker.helpers.buildTestTag
import com.android.server.wm.flicker.helpers.exitSplitScreen
import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
import com.android.server.wm.flicker.helpers.isInSplitScreen
import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+
import com.android.server.wm.flicker.repetitions
+import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.testapp.Components
import org.junit.FixMethodOrder
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
@@ -53,23 +57,24 @@ class ExitLegacySplitScreenFromBottomTest(
@JvmStatic
fun getParams(): Collection<Array<Any>> {
val instrumentation = InstrumentationRegistry.getInstrumentation()
- val testApp = StandardAppHelper(instrumentation,
- "com.android.wm.shell.flicker.testapp", "SimpleApp")
+ val splitScreenApp = SplitScreenHelper(instrumentation,
+ TEST_APP_SPLITSCREEN_PRIMARY_LABEL,
+ Components.SplitScreenActivity())
- // b/161435597 causes the test not to work on 90 degrees
- return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
+ // TODO(b/162923992) Use of multiple segments of flicker spec for testing
+ return FlickerTestRunnerFactory(instrumentation,
+ listOf(Surface.ROTATION_0, Surface.ROTATION_90))
.buildTest { configuration ->
withTestName {
- buildTestTag("exitSplitScreenFromBottom", testApp,
+ buildTestTag("exitSplitScreenFromBottom", splitScreenApp,
configuration)
}
repeat { configuration.repetitions }
setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
eachRun {
- testApp.open()
+ device.wakeUpAndGoToHomeScreen()
+ device.openQuickStepAndClearRecentAppsFromOverview()
+ splitScreenApp.launchViaIntent()
device.launchSplitScreen()
device.waitForIdle()
this.setRotation(configuration.endRotation)
@@ -77,12 +82,10 @@ class ExitLegacySplitScreenFromBottomTest(
}
teardown {
eachRun {
- testApp.exit()
- }
- test {
if (device.isInSplitScreen()) {
device.exitSplitScreen()
}
+ splitScreenApp.exit()
}
}
transitions {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
index c51c73a9e248..84bfe9451e0a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenTest.kt
@@ -82,7 +82,7 @@ class ExitLegacySplitScreenTest(
}
layersTrace {
visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName))
+ listOf(LAUNCHER_PACKAGE_NAME))
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
new file mode 100644
index 000000000000..e9d3eb7f475d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableDismissInLegacySplitScreenTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:NonResizableDismissInLegacySplitScreenTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableDismissInLegacySplitScreenTest(
+ rotationName: String,
+ rotation: Int
+) : SplitScreenTestBase(rotationName, rotation) {
+
+ @Test
+ fun testNonResizableDismissInLegacySplitScreenTest() {
+ val testTag = "testNonResizableDismissInLegacySplitScreenTest"
+
+ runWithFlicker(transitionSetup) {
+ withTestName { testTag }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ nonResizeableApp.launchViaIntent()
+ splitScreenApp.launchViaIntent()
+ device.launchSplitScreen()
+ nonResizeableApp.reopenAppFromOverview()
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsInvisible()
+ end("appsEndingBounds", enabled = false) {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
+ }
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ nonResizeableApp.defaultWindowName, LETTER_BOX_NAME)
+ )
+ }
+ windowManagerTrace {
+ end {
+ showsAppWindow(nonResizeableApp.defaultWindowName)
+ hidesAppWindow(splitScreenApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
new file mode 100644
index 000000000000..b5a36f5a31d4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/NonResizableLaunchInLegacySplitScreenTest.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.legacysplitscreen
+
+import android.view.Surface
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.dockedStackDividerIsInvisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test open app to split screen.
+ * To run this test: `atest WMShellFlickerTests:NonResizableLaunchInLegacySplitScreenTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NonResizableLaunchInLegacySplitScreenTest(
+ rotationName: String,
+ rotation: Int
+) : SplitScreenTestBase(rotationName, rotation) {
+
+ @Test
+ fun testNonResizableLaunchInLegacySplitScreenTest() {
+ val testTag = "NonResizableLaunchInLegacySplitScreenTest"
+
+ runWithFlicker(transitionSetup) {
+ withTestName { testTag }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ nonResizeableApp.launchViaIntent()
+ splitScreenApp.launchViaIntent()
+ device.launchSplitScreen()
+ nonResizeableApp.reopenAppFromOverview()
+ }
+ assertions {
+ layersTrace {
+ dockedStackDividerIsInvisible()
+ end("appsEndingBounds", enabled = false) {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ this.hasVisibleRegion(nonResizeableApp.defaultWindowName, displayBounds)
+ }
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME, splitScreenApp.defaultWindowName,
+ nonResizeableApp.defaultWindowName, LETTER_BOX_NAME)
+ )
+ }
+ windowManagerTrace {
+ end {
+ showsAppWindow(nonResizeableApp.defaultWindowName)
+ hidesAppWindow(splitScreenApp.defaultWindowName)
+ }
+ }
+ }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
index af038698de80..90577ef19c1a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreenTest.kt
@@ -17,37 +17,22 @@
package com.android.wm.shell.flicker.legacysplitscreen
import android.platform.test.annotations.Presubmit
-import android.support.test.launcherhelper.LauncherStrategyFactory
import android.view.Surface
import androidx.test.filters.RequiresDevice
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.Flicker
-import com.android.server.wm.flicker.FlickerTestRunner
-import com.android.server.wm.flicker.FlickerTestRunnerFactory
-import com.android.server.wm.flicker.helpers.StandardAppHelper
-import com.android.server.wm.flicker.endRotation
-import com.android.server.wm.flicker.focusChanges
-import com.android.server.wm.flicker.helpers.buildTestTag
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.isInSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
import com.android.server.wm.flicker.appWindowBecomesVisible
-import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.visibleLayersShownMoreThanOneConsecutiveEntry
import com.android.server.wm.flicker.layerBecomesVisible
-import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.focusChanges
+import com.android.server.wm.flicker.helpers.launchSplitScreen
+import com.android.server.wm.flicker.dsl.runWithFlicker
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
import com.android.server.wm.flicker.noUncoveredRegions
-import com.android.server.wm.flicker.repetitions
-import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
+import com.android.server.wm.flicker.visibleWindowsShownMoreThanOneConsecutiveEntry
+import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
import org.junit.FixMethodOrder
+import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
@@ -56,85 +41,59 @@ import org.junit.runners.Parameterized
* Test open app to split screen.
* To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreenTest`
*/
-@Presubmit
+// TODO: Add back to pre-submit when stable.
+//@Presubmit
@RequiresDevice
@RunWith(Parameterized::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class OpenAppToLegacySplitScreenTest(
- testName: String,
- flickerSpec: Flicker
-) : FlickerTestRunner(testName, flickerSpec) {
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<Array<Any>> {
- val instrumentation = InstrumentationRegistry.getInstrumentation()
- val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
- .launcherStrategy.supportedLauncherPackage
- val testApp = StandardAppHelper(instrumentation,
- "com.android.wm.shell.flicker.testapp", "SimpleApp")
-
- // b/161435597 causes the test not to work on 90 degrees
- return FlickerTestRunnerFactory(instrumentation, listOf(Surface.ROTATION_0))
- .buildTest { configuration ->
- withTestName {
- buildTestTag("appToSplitScreen", testApp, configuration)
- }
- repeat { configuration.repetitions }
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview()
- }
- eachRun {
- testApp.open()
- device.pressHome()
- this.setRotation(configuration.endRotation)
- }
- }
- teardown {
- eachRun {
- if (device.isInSplitScreen()) {
- device.exitSplitScreen()
- }
- }
- test {
- testApp.exit()
- }
- }
- transitions {
- device.launchSplitScreen()
- }
- assertions {
- windowManagerTrace {
- navBarWindowIsAlwaysVisible()
- statusBarWindowIsAlwaysVisible()
- visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- appWindowBecomesVisible(testApp.getPackage())
- }
+ rotationName: String,
+ rotation: Int
+) : SplitScreenTestBase(rotationName, rotation) {
+ @Test
+ fun OpenAppToLegacySplitScreenTest() {
+ val testTag = "OpenAppToLegacySplitScreenTest"
- layersTrace {
- navBarLayerIsAlwaysVisible(bugId = 140855415)
- statusBarLayerIsAlwaysVisible()
- noUncoveredRegions(configuration.endRotation, enabled = false)
- navBarLayerRotatesAndScales(configuration.endRotation,
- bugId = 140855415)
- statusBarLayerRotatesScales(configuration.endRotation)
- visibleLayersShownMoreThanOneConsecutiveEntry(
- listOf(launcherPackageName))
+ runWithFlicker(transitionSetup) {
+ withTestName { testTag }
+ repeat { SplitScreenHelper.TEST_REPETITIONS }
+ transitions {
+ splitScreenApp.launchViaIntent()
+ device.pressHome()
+ this.setRotation(rotation)
+ device.launchSplitScreen()
+ }
+ assertions {
+ windowManagerTrace {
+ visibleWindowsShownMoreThanOneConsecutiveEntry()
+ appWindowBecomesVisible(splitScreenApp.getPackage())
+ }
- dockedStackDividerBecomesVisible()
- layerBecomesVisible(testApp.getPackage())
- }
+ layersTrace {
+ navBarLayerIsAlwaysVisible(bugId = 140855415)
+ noUncoveredRegions(rotation, enabled = false)
+ statusBarLayerIsAlwaysVisible(bugId = 140855415)
+ visibleLayersShownMoreThanOneConsecutiveEntry(
+ listOf(LAUNCHER_PACKAGE_NAME))
+ appPairsDividerBecomesVisible()
+ layerBecomesVisible(splitScreenApp.getPackage())
+ }
- eventLog {
- focusChanges(testApp.`package`,
- "recents_animation_input_consumer", "NexusLauncherActivity",
- bugId = 151179149)
- }
- }
+ eventLog {
+ focusChanges(splitScreenApp.`package`,
+ "recents_animation_input_consumer", "NexusLauncherActivity",
+ bugId = 151179149)
}
+ }
+ }
+ }
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<Array<Any>> {
+ val supportedRotations = intArrayOf(Surface.ROTATION_0, Surface.ROTATION_90)
+ return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) }
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
index a536ec8e2e0b..2b94c5f3fee9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/SplitScreenTestBase.kt
@@ -17,6 +17,15 @@
package com.android.wm.shell.flicker.legacysplitscreen
import android.support.test.launcherhelper.LauncherStrategyFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.exitSplitScreen
+import com.android.server.wm.flicker.helpers.isInSplitScreen
+import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
+import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
+import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible
+import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible
import com.android.wm.shell.flicker.NonRotationTestBase
import com.android.wm.shell.flicker.TEST_APP_NONRESIZEABLE_LABEL
import com.android.wm.shell.flicker.TEST_APP_SPLITSCREEN_PRIMARY_LABEL
@@ -37,6 +46,39 @@ abstract class SplitScreenTestBase(
protected val nonResizeableApp = SplitScreenHelper(instrumentation,
TEST_APP_NONRESIZEABLE_LABEL,
Components.NonResizeableActivity())
- protected val launcherPackageName = LauncherStrategyFactory.getInstance(instrumentation)
+
+ protected val LAUNCHER_PACKAGE_NAME = LauncherStrategyFactory.getInstance(instrumentation)
.launcherStrategy.supportedLauncherPackage
+ protected val LIVE_WALLPAPER_PACKAGE_NAME =
+ "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2"
+ protected val LETTER_BOX_NAME = "Letterbox"
+
+ protected val transitionSetup: FlickerBuilder
+ get() = FlickerBuilder(instrumentation).apply {
+ setup {
+ eachRun {
+ uiDevice.wakeUpAndGoToHomeScreen()
+ uiDevice.openQuickStepAndClearRecentAppsFromOverview()
+ }
+ }
+ teardown {
+ eachRun {
+ if (uiDevice.isInSplitScreen()) {
+ uiDevice.exitSplitScreen()
+ }
+ splitScreenApp.exit()
+ nonResizeableApp.exit()
+ }
+ }
+ assertions {
+ layersTrace {
+ navBarLayerIsAlwaysVisible()
+ statusBarLayerIsAlwaysVisible()
+ }
+ windowManagerTrace {
+ navBarWindowIsAlwaysVisible()
+ statusBarWindowIsAlwaysVisible()
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 76d3a6aaee22..1f58a8546796 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -14,27 +14,37 @@
* limitations under the License.
*/
-package com.android.wm.shell.apppairs;
+package com.android.wm.shell;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import android.app.ActivityManager;
import android.graphics.Rect;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
-public class TestRunningTaskInfoBuilder {
+public final class TestRunningTaskInfoBuilder {
static int sNextTaskId = 500;
private Rect mBounds = new Rect(0, 0, 100, 100);
private WindowContainerToken mToken =
new WindowContainerToken(new IWindowContainerToken.Default());
+ private int mParentTaskId = INVALID_TASK_ID;
- TestRunningTaskInfoBuilder setBounds(Rect bounds) {
+ public TestRunningTaskInfoBuilder setBounds(Rect bounds) {
mBounds.set(bounds);
return this;
}
- ActivityManager.RunningTaskInfo build() {
+ public TestRunningTaskInfoBuilder setParentTaskId(int taskId) {
+ mParentTaskId = taskId;
+ return this;
+ }
+
+ public ActivityManager.RunningTaskInfo build() {
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+ info.parentTaskId = INVALID_TASK_ID;
info.taskId = sNextTaskId++;
+ info.parentTaskId = mParentTaskId;
info.configuration.windowConfiguration.setBounds(mBounds);
info.token = mToken;
info.isResizeable = true;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
new file mode 100644
index 000000000000..9eb13fb1c5e7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestShellExecutor.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import android.os.Looper;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+import java.util.ArrayList;
+
+/**
+ * Really basic test executor. It just gathers all events in a blob. The only option is to
+ * execute everything at once. If better control over delayed execution is needed, please add it.
+ */
+public class TestShellExecutor implements ShellExecutor {
+ final ArrayList<Runnable> mRunnables = new ArrayList<>();
+
+ @Override
+ public void execute(Runnable runnable) {
+ mRunnables.add(runnable);
+ }
+
+ @Override
+ public void executeDelayed(Runnable r, long delayMillis) {
+ mRunnables.add(r);
+ }
+
+ @Override
+ public void removeCallbacks(Runnable r) {
+ mRunnables.remove(r);
+ }
+
+ @Override
+ public boolean hasCallback(Runnable r) {
+ return !mRunnables.isEmpty();
+ }
+
+ @Override
+ public Looper getLooper() {
+ return null;
+ }
+
+ public void flushAll() {
+ for (int i = mRunnables.size() - 1; i >= 0; --i) {
+ mRunnables.get(i).run();
+ }
+ mRunnables.clear();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
index 8dbc1d56bcc2..d21183e10ed9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
index fada694a4c07..505c153eff9c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
@@ -32,6 +32,7 @@ import androidx.test.filters.SmallTest;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.SyncTransactionQueue;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java
index 080f2075344a..1ee7fff44892 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java
@@ -18,6 +18,8 @@ package com.android.wm.shell.apppairs;
import android.app.ActivityManager;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+
public class TestAppPairsPool extends AppPairsPool{
TestAppPairsPool(AppPairsController controller) {
super(controller);
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 cd468167e372..0d1c6f93d8e3 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
@@ -55,6 +55,7 @@ public class SplitLayoutTests extends ShellTestCase {
public void setup() {
MockitoAnnotations.initMocks(this);
mSplitLayout = new SplitLayout(
+ "TestSplitLayout",
mContext,
getConfiguration(false),
mLayoutChangeListener,
@@ -64,6 +65,7 @@ public class SplitLayoutTests extends ShellTestCase {
@Test
@UiThreadTest
public void testUpdateConfiguration() {
+ mSplitLayout.init();
assertThat(mSplitLayout.updateConfiguration(getConfiguration(false))).isFalse();
assertThat(mSplitLayout.updateConfiguration(getConfiguration(true))).isTrue();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
index c17056353136..698315a77d8e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -49,7 +49,7 @@ public class SplitWindowManagerTests extends ShellTestCase {
MockitoAnnotations.initMocks(this);
final Configuration configuration = new Configuration();
configuration.setToDefaults();
- mSplitWindowManager = new SplitWindowManager(mContext, configuration,
+ mSplitWindowManager = new SplitWindowManager("TestSplitDivider", mContext, configuration,
b -> b.setParent(mSurfaceControl));
when(mSplitLayout.getDividerBounds()).thenReturn(
new Rect(0, 0, configuration.windowConfiguration.getBounds().width(),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
new file mode 100644
index 000000000000..702e8945de01
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.ActivityManager;
+import android.view.SurfaceControl;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+/** Tests for {@link MainStage} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MainStageTests {
+ @Mock private ShellTaskOrganizer mTaskOrganizer;
+ @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
+ @Mock private SyncTransactionQueue mSyncQueue;
+ @Mock private ActivityManager.RunningTaskInfo mRootTaskInfo;
+ @Mock private SurfaceControl mRootLeash;
+ @Spy private WindowContainerTransaction mWct;
+ private MainStage mMainStage;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
+ mMainStage = new MainStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue);
+ mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
+ }
+
+ @Test
+ public void testActiveDeactivate() {
+ mMainStage.activate(mRootTaskInfo.configuration.windowConfiguration.getBounds(), mWct);
+ assertThat(mMainStage.isActive()).isTrue();
+
+ mMainStage.deactivate(mWct);
+ assertThat(mMainStage.isActive()).isFalse();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
new file mode 100644
index 000000000000..01888b758bf6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.view.SurfaceControl;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+/** Tests for {@link SideStage} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SideStageTests {
+ @Mock private ShellTaskOrganizer mTaskOrganizer;
+ @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
+ @Mock private SyncTransactionQueue mSyncQueue;
+ @Mock private ActivityManager.RunningTaskInfo mRootTask;
+ @Mock private SurfaceControl mRootLeash;
+ @Spy private WindowContainerTransaction mWct;
+ private SideStage mSideStage;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mRootTask = new TestRunningTaskInfoBuilder().build();
+ mSideStage = new SideStage(mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks, mSyncQueue);
+ mSideStage.onTaskAppeared(mRootTask, mRootLeash);
+ }
+
+ @Test
+ public void testAddTask() {
+ final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+
+ mSideStage.addTask(task, mRootTask.configuration.windowConfiguration.getBounds(), mWct);
+
+ verify(mWct).reparent(eq(task.token), eq(mRootTask.token), eq(true));
+ }
+
+ @Test
+ public void testRemoveTask() {
+ final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ assertThat(mSideStage.removeTask(task.taskId, null, mWct)).isFalse();
+
+ mSideStage.mChildrenTaskInfo.put(task.taskId, task);
+ assertThat(mSideStage.removeTask(task.taskId, null, mWct)).isTrue();
+ verify(mWct).reparent(eq(task.token), isNull(), eq(false));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
new file mode 100644
index 000000000000..168e0df35fe1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER;
+
+import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.window.DisplayAreaInfo;
+import android.window.IWindowContainerToken;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link StageCoordinator} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StageCoordinatorTests extends ShellTestCase {
+ @Mock private ShellTaskOrganizer mTaskOrganizer;
+ @Mock private SyncTransactionQueue mSyncQueue;
+ @Mock private RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ @Mock private MainStage mMainStage;
+ @Mock private SideStage mSideStage;
+ private StageCoordinator mStageCoordinator;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mStageCoordinator = new TestStageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
+ mRootTDAOrganizer, mTaskOrganizer, mMainStage, mSideStage);
+ }
+
+ @Test
+ public void testMoveToSideStage() {
+ final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+
+ mStageCoordinator.moveToSideStage(task, SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT);
+
+ verify(mMainStage).activate(any(Rect.class), any(WindowContainerTransaction.class));
+ verify(mSideStage).addTask(eq(task), any(Rect.class),
+ any(WindowContainerTransaction.class));
+ }
+
+ @Test
+ public void testRemoveFromSideStage() {
+ final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+
+ doReturn(false).when(mMainStage).isActive();
+ mStageCoordinator.removeFromSideStage(task.taskId);
+
+ verify(mSideStage).removeTask(
+ eq(task.taskId), any(), any(WindowContainerTransaction.class));
+ }
+
+ private static class TestStageCoordinator extends StageCoordinator {
+ final DisplayAreaInfo mDisplayAreaInfo;
+
+ TestStageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
+ RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
+ MainStage mainStage, SideStage sideStage) {
+ super(context, displayId, syncQueue, rootTDAOrganizer, taskOrganizer, mainStage,
+ sideStage);
+
+ // Prepare default TaskDisplayArea for testing.
+ mDisplayAreaInfo = new DisplayAreaInfo(
+ new WindowContainerToken(new IWindowContainerToken.Default()),
+ DEFAULT_DISPLAY,
+ FEATURE_DEFAULT_TASK_CONTAINER);
+ this.onDisplayAreaAppeared(mDisplayAreaInfo);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
new file mode 100644
index 000000000000..c66e0730422c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.view.SurfaceControl;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Tests for {@link StageTaskListener} */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class StageTaskListenerTests {
+ @Mock private ShellTaskOrganizer mTaskOrganizer;
+ @Mock private StageTaskListener.StageListenerCallbacks mCallbacks;
+ @Mock private SyncTransactionQueue mSyncQueue;
+ private ActivityManager.RunningTaskInfo mRootTask;
+ private StageTaskListener mStageTaskListener;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mStageTaskListener = new StageTaskListener(
+ mTaskOrganizer,
+ DEFAULT_DISPLAY,
+ mCallbacks,
+ mSyncQueue);
+ mRootTask = new TestRunningTaskInfoBuilder().build();
+ mRootTask.parentTaskId = INVALID_TASK_ID;
+ mStageTaskListener.onTaskAppeared(mRootTask, new SurfaceControl());
+ }
+
+ @Test
+ public void testRootTaskAppeared() {
+ assertThat(mStageTaskListener.mRootTaskInfo.taskId).isEqualTo(mRootTask.taskId);
+ verify(mCallbacks).onRootTaskAppeared();
+ verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(false));
+ }
+
+ @Test
+ public void testChildTaskAppeared() {
+ final ActivityManager.RunningTaskInfo childTask =
+ new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
+
+ mStageTaskListener.onTaskAppeared(childTask, new SurfaceControl());
+
+ assertThat(mStageTaskListener.mChildrenTaskInfo.contains(childTask.taskId)).isTrue();
+ verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testUnknownTaskVanished() {
+ final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
+ mStageTaskListener.onTaskVanished(task);
+ }
+
+ @Test
+ public void testTaskVanished() {
+ final ActivityManager.RunningTaskInfo childTask =
+ new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
+ mStageTaskListener.mRootTaskInfo = mRootTask;
+ mStageTaskListener.mChildrenTaskInfo.put(childTask.taskId, childTask);
+
+ mStageTaskListener.onTaskVanished(childTask);
+ verify(mCallbacks, times(2)).onStatusChanged(eq(mRootTask.isVisible), eq(false));
+
+ mStageTaskListener.onTaskVanished(mRootTask);
+ verify(mCallbacks).onRootTaskVanished();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
new file mode 100644
index 000000000000..c46e59ad396a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.WindowContainerTransaction;
+import android.window.WindowOrganizer;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+/**
+ * Tests for the shell transitions.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ShellTransitionTests {
+
+ private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class);
+ private final TransactionPool mTransactionPool = mock(TransactionPool.class);
+ private final TestShellExecutor mMainExecutor = new TestShellExecutor();
+ private final ShellExecutor mAnimExecutor = new TestShellExecutor();
+ private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler();
+
+ @Before
+ public void setUp() {
+ doAnswer(invocation -> invocation.getArguments()[1])
+ .when(mOrganizer).startTransition(anyInt(), any(), any());
+ }
+
+ @Test
+ public void testBasicTransitionFlow() {
+ Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
+ mAnimExecutor);
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ IBinder transitToken = new Binder();
+ transitions.requestStartTransition(TRANSIT_OPEN, transitToken, null /* trigger */);
+ verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), any());
+ TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+ transitions.onTransitionReady(transitToken, info, mock(SurfaceControl.Transaction.class));
+ assertEquals(1, mDefaultHandler.activeCount());
+ mDefaultHandler.finishAll();
+ mMainExecutor.flushAll();
+ verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any());
+ }
+
+ @Test
+ public void testNonDefaultHandler() {
+ Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor,
+ mAnimExecutor);
+ transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+ final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
+ // Make a test handler that only responds to multi-window triggers AND only animates
+ // Change transitions.
+ TestTransitionHandler testHandler = new TestTransitionHandler() {
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
+ for (TransitionInfo.Change chg : info.getChanges()) {
+ if (chg.getMode() == TRANSIT_CHANGE) {
+ return super.startAnimation(transition, info, t, finishCallback);
+ }
+ }
+ return false;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(int type, @NonNull IBinder transition,
+ @Nullable RunningTaskInfo triggerTask) {
+ return (triggerTask != null
+ && triggerTask.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW)
+ ? handlerWCT : null;
+ }
+ };
+ transitions.addHandler(testHandler);
+
+ IBinder transitToken = new Binder();
+ TransitionInfo open = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+
+ // Make a request that will be rejected by the testhandler.
+ transitions.requestStartTransition(TRANSIT_OPEN, transitToken, null /* trigger */);
+ verify(mOrganizer, times(1)).startTransition(eq(TRANSIT_OPEN), eq(transitToken), isNull());
+ transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class));
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(0, testHandler.activeCount());
+ mDefaultHandler.finishAll();
+ mMainExecutor.flushAll();
+
+ // Make a request that will be handled by testhandler but not animated by it.
+ RunningTaskInfo mwTaskInfo =
+ createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
+ transitions.requestStartTransition(TRANSIT_OPEN, transitToken, mwTaskInfo);
+ verify(mOrganizer, times(1)).startTransition(
+ eq(TRANSIT_OPEN), eq(transitToken), eq(handlerWCT));
+ transitions.onTransitionReady(transitToken, open, mock(SurfaceControl.Transaction.class));
+ assertEquals(1, mDefaultHandler.activeCount());
+ assertEquals(0, testHandler.activeCount());
+ mDefaultHandler.finishAll();
+ mMainExecutor.flushAll();
+
+ // Make a request that will be handled AND animated by testhandler.
+ // Add an aggressive handler (doesn't handle but always animates) on top to make sure that
+ // the test handler gets first shot at animating since it claimed to handle it.
+ TestTransitionHandler topHandler = new TestTransitionHandler();
+ transitions.addHandler(topHandler);
+ transitions.requestStartTransition(TRANSIT_CHANGE, transitToken, mwTaskInfo);
+ verify(mOrganizer, times(1)).startTransition(
+ eq(TRANSIT_OPEN), eq(transitToken), eq(handlerWCT));
+ TransitionInfo change = new TransitionInfoBuilder(TRANSIT_CHANGE)
+ .addChange(TRANSIT_CHANGE).build();
+ transitions.onTransitionReady(transitToken, change, mock(SurfaceControl.Transaction.class));
+ assertEquals(0, mDefaultHandler.activeCount());
+ assertEquals(1, testHandler.activeCount());
+ assertEquals(0, topHandler.activeCount());
+ testHandler.finishAll();
+ mMainExecutor.flushAll();
+ }
+
+ class TransitionInfoBuilder {
+ final TransitionInfo mInfo;
+
+ TransitionInfoBuilder(@WindowManager.TransitionType int type) {
+ mInfo = new TransitionInfo(type, 0 /* flags */);
+ mInfo.setRootLeash(createMockSurface(true /* valid */), 0, 0);
+ }
+
+ TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode,
+ RunningTaskInfo taskInfo) {
+ final TransitionInfo.Change change =
+ new TransitionInfo.Change(null /* token */, null /* leash */);
+ change.setMode(mode);
+ change.setTaskInfo(taskInfo);
+ mInfo.addChange(change);
+ return this;
+ }
+
+ TransitionInfoBuilder addChange(@WindowManager.TransitionType int mode) {
+ return addChange(mode, null /* taskInfo */);
+ }
+
+ TransitionInfo build() {
+ return mInfo;
+ }
+ }
+
+ class TestTransitionHandler implements Transitions.TransitionHandler {
+ final ArrayList<Runnable> mFinishes = new ArrayList<>();
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull Runnable finishCallback) {
+ mFinishes.add(finishCallback);
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(int type, @NonNull IBinder transition,
+ @Nullable RunningTaskInfo triggerTask) {
+ return null;
+ }
+
+ void finishAll() {
+ for (int i = mFinishes.size() - 1; i >= 0; --i) {
+ mFinishes.get(i).run();
+ }
+ mFinishes.clear();
+ }
+
+ int activeCount() {
+ return mFinishes.size();
+ }
+ }
+
+ private static SurfaceControl createMockSurface(boolean valid) {
+ SurfaceControl sc = mock(SurfaceControl.class);
+ doReturn(valid).when(sc).isValid();
+ return sc;
+ }
+
+ private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, int activityType) {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ taskInfo.configuration.windowConfiguration.setActivityType(activityType);
+ return taskInfo;
+ }
+
+}
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 73e040c42826..adb383f95d40 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -326,6 +326,11 @@ std::unique_ptr<const LoadedIdmap> LoadedIdmap::Load(const StringPiece& idmap_pa
}
}
+ if (data_size != 0) {
+ LOG(ERROR) << "idmap parsed with " << data_size << "bytes remaining";
+ return {};
+ }
+
// Can't use make_unique because LoadedIdmap constructor is private.
return std::unique_ptr<LoadedIdmap>(
new LoadedIdmap(idmap_path.to_string(), header, data_header, target_entries,
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index ba44d056dda3..65f4e8c8ecec 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -15,7 +15,9 @@
*/
#include "Properties.h"
+
#include "Debug.h"
+#include "log/log_main.h"
#ifdef __ANDROID__
#include "HWUIProperties.sysprop.h"
#endif
@@ -190,15 +192,12 @@ RenderPipelineType Properties::getRenderPipelineType() {
return sRenderPipelineType;
}
-void Properties::overrideRenderPipelineType(RenderPipelineType type) {
+void Properties::overrideRenderPipelineType(RenderPipelineType type, bool inUnitTest) {
// If we're doing actual rendering then we can't change the renderer after it's been set.
- // Unit tests can freely change this as often as it wants, though, as there's no actual
- // GL rendering happening
- if (sRenderPipelineType != RenderPipelineType::NotInitialized) {
- LOG_ALWAYS_FATAL_IF(sRenderPipelineType != type,
- "Trying to change pipeline but it's already set");
- return;
- }
+ // Unit tests can freely change this as often as it wants.
+ LOG_ALWAYS_FATAL_IF(sRenderPipelineType != RenderPipelineType::NotInitialized &&
+ sRenderPipelineType != type && !inUnitTest,
+ "Trying to change pipeline but it's already set.");
sRenderPipelineType = type;
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 85a0f4aa7809..1639143ef87c 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -238,7 +238,7 @@ public:
static bool enableRTAnimations;
// Used for testing only to change the render pipeline.
- static void overrideRenderPipelineType(RenderPipelineType);
+ static void overrideRenderPipelineType(RenderPipelineType, bool inUnitTest = false);
static bool runningInEmulator;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 1333b92037c3..3e7ce368f55d 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -27,6 +27,8 @@
#include <vk/GrVkExtensions.h>
#include <vk/GrVkTypes.h>
+#include <cstring>
+
#include "Properties.h"
#include "RenderThread.h"
#include "renderstate/RenderState.h"
@@ -53,6 +55,19 @@ static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& fe
}
}
+GrVkGetProc VulkanManager::sSkiaGetProp = [](const char* proc_name, VkInstance instance,
+ VkDevice device) {
+ if (device != VK_NULL_HANDLE) {
+ if (strcmp("vkQueueSubmit", proc_name) == 0) {
+ return (PFN_vkVoidFunction)VulkanManager::interceptedVkQueueSubmit;
+ } else if (strcmp("vkQueueWaitIdle", proc_name) == 0) {
+ return (PFN_vkVoidFunction)VulkanManager::interceptedVkQueueWaitIdle;
+ }
+ return vkGetDeviceProcAddr(device, proc_name);
+ }
+ return vkGetInstanceProcAddr(instance, proc_name);
+};
+
#define GET_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F)
#define GET_INST_PROC(F) m##F = (PFN_vk##F)vkGetInstanceProcAddr(mInstance, "vk" #F)
#define GET_DEV_PROC(F) m##F = (PFN_vk##F)vkGetDeviceProcAddr(mDevice, "vk" #F)
@@ -83,7 +98,6 @@ VulkanManager::~VulkanManager() {
}
mGraphicsQueue = VK_NULL_HANDLE;
- mAHBUploadQueue = VK_NULL_HANDLE;
mDevice = VK_NULL_HANDLE;
mPhysicalDevice = VK_NULL_HANDLE;
mInstance = VK_NULL_HANDLE;
@@ -185,7 +199,6 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
for (uint32_t i = 0; i < queueCount; i++) {
if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
mGraphicsQueueIndex = i;
- LOG_ALWAYS_FATAL_IF(queueProps[i].queueCount < 2);
break;
}
}
@@ -210,14 +223,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
LOG_ALWAYS_FATAL_IF(!hasKHRSwapchainExtension);
}
- auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
- if (device != VK_NULL_HANDLE) {
- return vkGetDeviceProcAddr(device, proc_name);
- }
- return vkGetInstanceProcAddr(instance, proc_name);
- };
-
- grExtensions.init(getProc, mInstance, mPhysicalDevice, mInstanceExtensions.size(),
+ grExtensions.init(sSkiaGetProp, mInstance, mPhysicalDevice, mInstanceExtensions.size(),
mInstanceExtensions.data(), mDeviceExtensions.size(),
mDeviceExtensions.data());
@@ -289,7 +295,7 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe
queueNextPtr, // pNext
0, // VkDeviceQueueCreateFlags
mGraphicsQueueIndex, // queueFamilyIndex
- 2, // queueCount
+ 1, // queueCount
queuePriorities, // pQueuePriorities
};
@@ -344,7 +350,6 @@ void VulkanManager::initialize() {
this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
- mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
if (Properties::enablePartialUpdates && Properties::useBufferAge) {
mSwapBehavior = SwapBehavior::BufferAge;
@@ -353,24 +358,17 @@ void VulkanManager::initialize() {
sk_sp<GrDirectContext> VulkanManager::createContext(const GrContextOptions& options,
ContextType contextType) {
- auto getProc = [](const char* proc_name, VkInstance instance, VkDevice device) {
- if (device != VK_NULL_HANDLE) {
- return vkGetDeviceProcAddr(device, proc_name);
- }
- return vkGetInstanceProcAddr(instance, proc_name);
- };
GrVkBackendContext backendContext;
backendContext.fInstance = mInstance;
backendContext.fPhysicalDevice = mPhysicalDevice;
backendContext.fDevice = mDevice;
- backendContext.fQueue = (contextType == ContextType::kRenderThread) ? mGraphicsQueue
- : mAHBUploadQueue;
+ backendContext.fQueue = mGraphicsQueue;
backendContext.fGraphicsQueueIndex = mGraphicsQueueIndex;
backendContext.fMaxAPIVersion = mAPIVersion;
backendContext.fVkExtensions = &mExtensions;
backendContext.fDeviceFeatures2 = &mPhysicalDeviceFeatures2;
- backendContext.fGetProc = std::move(getProc);
+ backendContext.fGetProc = sSkiaGetProp;
return GrDirectContext::MakeVulkan(backendContext, options);
}
@@ -530,6 +528,8 @@ void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect)
ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
} else {
ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed");
+
+ std::lock_guard<std::mutex> lock(mGraphicsQueueMutex);
mQueueWaitIdle(mGraphicsQueue);
}
destroy_semaphore(destroyInfo);
@@ -540,6 +540,7 @@ void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect)
void VulkanManager::destroySurface(VulkanSurface* surface) {
// Make sure all submit commands have finished before starting to destroy objects.
if (VK_NULL_HANDLE != mGraphicsQueue) {
+ std::lock_guard<std::mutex> lock(mGraphicsQueueMutex);
mQueueWaitIdle(mGraphicsQueue);
}
mDeviceWaitIdle(mDevice);
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 7a77466303cd..121afc90cfe5 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -17,6 +17,10 @@
#ifndef VULKANMANAGER_H
#define VULKANMANAGER_H
+#include <functional>
+#include <mutex>
+
+#include "vulkan/vulkan_core.h"
#if !defined(VK_USE_PLATFORM_ANDROID_KHR)
#define VK_USE_PLATFORM_ANDROID_KHR
#endif
@@ -161,8 +165,25 @@ private:
VkDevice mDevice = VK_NULL_HANDLE;
uint32_t mGraphicsQueueIndex;
+
+ std::mutex mGraphicsQueueMutex;
VkQueue mGraphicsQueue = VK_NULL_HANDLE;
- VkQueue mAHBUploadQueue = VK_NULL_HANDLE;
+
+ static VKAPI_ATTR VkResult interceptedVkQueueSubmit(VkQueue queue, uint32_t submitCount,
+ const VkSubmitInfo* pSubmits,
+ VkFence fence) {
+ sp<VulkanManager> manager = VulkanManager::getInstance();
+ std::lock_guard<std::mutex> lock(manager->mGraphicsQueueMutex);
+ return manager->mQueueSubmit(queue, submitCount, pSubmits, fence);
+ }
+
+ static VKAPI_ATTR VkResult interceptedVkQueueWaitIdle(VkQueue queue) {
+ sp<VulkanManager> manager = VulkanManager::getInstance();
+ std::lock_guard<std::mutex> lock(manager->mGraphicsQueueMutex);
+ return manager->mQueueWaitIdle(queue);
+ }
+
+ static GrVkGetProc sSkiaGetProp;
// Variables saved to populate VkFunctorInitParams.
static const uint32_t mAPIVersion = VK_MAKE_VERSION(1, 1, 0);
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index c1d8b761514b..771c3452bf84 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -50,12 +50,12 @@ namespace uirenderer {
ADD_FAILURE() << "ClipState not a rect"; \
}
-#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
- TEST(test_case_name, test_name##_##pipeline) { \
- RenderPipelineType oldType = Properties::getRenderPipelineType(); \
- Properties::overrideRenderPipelineType(RenderPipelineType::pipeline); \
- functionCall; \
- Properties::overrideRenderPipelineType(oldType); \
+#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
+ TEST(test_case_name, test_name##_##pipeline) { \
+ RenderPipelineType oldType = Properties::getRenderPipelineType(); \
+ Properties::overrideRenderPipelineType(RenderPipelineType::pipeline, true); \
+ functionCall; \
+ Properties::overrideRenderPipelineType(oldType, true); \
};
#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \
@@ -67,29 +67,27 @@ namespace uirenderer {
* Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
* (for e.g. accessing its RenderState)
*/
-#define RENDERTHREAD_TEST(test_case_name, test_name) \
- class test_case_name##_##test_name##_RenderThreadTest { \
- public: \
- static void doTheThing(renderthread::RenderThread& renderThread); \
- }; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
- /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
- /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
- void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
+#define RENDERTHREAD_TEST(test_case_name, test_name) \
+ class test_case_name##_##test_name##_RenderThreadTest { \
+ public: \
+ static void doTheThing(renderthread::RenderThread& renderThread); \
+ }; \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \
+ void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
renderthread::RenderThread& renderThread)
/**
* Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes
*/
-#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \
- class test_case_name##_##test_name##_RenderThreadTest { \
- public: \
- static void doTheThing(renderthread::RenderThread& renderThread); \
- }; \
- INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
- /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
- /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */ \
- void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
+#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name) \
+ class test_case_name##_##test_name##_RenderThreadTest { \
+ public: \
+ static void doTheThing(renderthread::RenderThread& renderThread); \
+ }; \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL); \
+ INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); \
+ void test_case_name##_##test_name##_RenderThreadTest::doTheThing( \
renderthread::RenderThread& renderThread)
/**
diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h
index 07472435d8a3..5689286f0b32 100644
--- a/libs/services/include/android/os/DropBoxManager.h
+++ b/libs/services/include/android/os/DropBoxManager.h
@@ -93,8 +93,6 @@ private:
enum {
HAS_BYTE_ARRAY = 8
};
-
- Status add(const Entry& entry);
};
}} // namespace android::os
diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp
index 429f996bd65e..3716e019f69a 100644
--- a/libs/services/src/os/DropBoxManager.cpp
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -18,7 +18,9 @@
#include <android/os/DropBoxManager.h>
+#include <android-base/unique_fd.h>
#include <binder/IServiceManager.h>
+#include <binder/ParcelFileDescriptor.h>
#include <com/android/internal/os/IDropBoxManagerService.h>
#include <cutils/log.h>
@@ -178,18 +180,24 @@ DropBoxManager::~DropBoxManager()
Status
DropBoxManager::addText(const String16& tag, const string& text)
{
- Entry entry(tag, IS_TEXT);
- entry.mData.assign(text.c_str(), text.c_str() + text.size());
- return add(entry);
+ return addData(tag, reinterpret_cast<uint8_t const*>(text.c_str()), text.size(), IS_TEXT);
}
Status
DropBoxManager::addData(const String16& tag, uint8_t const* data,
size_t size, int flags)
{
- Entry entry(tag, flags);
- entry.mData.assign(data, data+size);
- return add(entry);
+ sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>(
+ defaultServiceManager()->getService(android::String16("dropbox")));
+ if (service == NULL) {
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service");
+ }
+ ALOGD("About to call service->add()");
+ vector<uint8_t> dataArg;
+ dataArg.assign(data, data + size);
+ Status status = service->addData(tag, dataArg, flags);
+ ALOGD("service->add returned %s", status.toString8().string());
+ return status;
}
Status
@@ -213,20 +221,15 @@ DropBoxManager::addFile(const String16& tag, int fd, int flags)
ALOGW("DropboxManager: %s", message.c_str());
return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, message.c_str());
}
- Entry entry(tag, flags, fd);
- return add(entry);
-}
-
-Status
-DropBoxManager::add(const Entry& entry)
-{
sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>(
defaultServiceManager()->getService(android::String16("dropbox")));
if (service == NULL) {
return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service");
}
ALOGD("About to call service->add()");
- Status status = service->add(entry);
+ android::base::unique_fd uniqueFd(fd);
+ android::os::ParcelFileDescriptor parcelFd(std::move(uniqueFd));
+ Status status = service->addFile(tag, parcelFd, flags);
ALOGD("service->add returned %s", status.toString8().string());
return status;
}
diff --git a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
index bf0cef01ac7b..18cfce528205 100644
--- a/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
+++ b/libs/usb/tests/AccessoryChat/src/com/android/accessorychat/AccessoryChat.java
@@ -83,7 +83,7 @@ public class AccessoryChat extends Activity implements Runnable, TextView.OnEdit
super.onCreate(savedInstanceState);
mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
- mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
+ mPermissionIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE_UNAUDITED);
IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
registerReceiver(mUsbReceiver, filter);