diff options
Diffstat (limited to 'libs')
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); |