diff options
| author | 2024-09-12 15:51:57 +0000 | |
|---|---|---|
| committer | 2024-09-12 15:51:57 +0000 | |
| commit | a61bb061163fb97d09eb8d4d39397bb7bab4727f (patch) | |
| tree | 47a82b12a5093456bf32ef1158ebb41905da914a | |
| parent | 544ef065e5e17bed2ced952b4b6cdfcf2e540836 (diff) | |
| parent | 4c008874f9512e16d11095e799164ca943b0f58d (diff) | |
Merge "Add WindowDecorViewHost and WindowDecorViewHostSupplier" into main
14 files changed, 730 insertions, 322 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 7054c17cfeb0..8c7dcf295319 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -18,6 +18,7 @@ package com.android.wm.shell.dagger; import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.KeyguardManager; import android.content.Context; @@ -114,6 +115,8 @@ import com.android.wm.shell.unfold.qualifier.UnfoldTransition; import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel; import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel; import com.android.wm.shell.windowdecor.WindowDecorViewModel; +import com.android.wm.shell.windowdecor.viewhost.DefaultWindowDecorViewHostSupplier; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import dagger.Binds; import dagger.Lazy; @@ -244,7 +247,8 @@ public abstract class WMShellModule { AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, - Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler) { + Optional<DesktopActivityOrientationChangeHandler> desktopActivityOrientationHandler, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { if (DesktopModeStatus.canEnterDesktopMode(context)) { return new DesktopModeWindowDecorViewModel( context, @@ -268,7 +272,8 @@ public abstract class WMShellModule { assistContentRequester, multiInstanceHelper, desktopTasksLimiter, - desktopActivityOrientationHandler); + desktopActivityOrientationHandler, + windowDecorViewHostSupplier); } return new CaptionWindowDecorViewModel( context, @@ -282,7 +287,8 @@ public abstract class WMShellModule { displayController, rootTaskDisplayAreaOrganizer, syncQueue, - transitions); + transitions, + windowDecorViewHostSupplier); } @WMSingleton @@ -371,6 +377,13 @@ public abstract class WMShellModule { context, shellInit, transitions, windowDecorViewModel); } + @WMSingleton + @Provides + static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier( + @ShellMainThread @NonNull CoroutineScope mainScope) { + return new DefaultWindowDecorViewHostSupplier(mainScope); + } + // // One handed mode // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 11976aed9315..015139519f1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -62,6 +62,7 @@ import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; /** * View model for the window decoration with a caption and shadows. Works with @@ -83,6 +84,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private final Transitions mTransitions; private final Region mExclusionRegion = Region.obtain(); private final InputManager mInputManager; + private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier; private TaskOperations mTaskOperations; /** @@ -120,7 +122,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { DisplayController displayController, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, SyncTransactionQueue syncQueue, - Transitions transitions) { + Transitions transitions, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { mContext = context; mMainExecutor = shellExecutor; mMainHandler = mainHandler; @@ -132,6 +135,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer; mSyncQueue = syncQueue; mTransitions = transitions; + mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; if (!Transitions.ENABLE_SHELL_TRANSITIONS) { mTaskOperations = new TaskOperations(null, mContext, mSyncQueue); } @@ -295,7 +299,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mMainHandler, mBgExecutor, mMainChoreographer, - mSyncQueue); + mSyncQueue, + mWindowDecorViewHostSupplier); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); final FluidResizeTaskPositioner taskPositioner = diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 349ee0b1fcda..46fe68f44bed 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -56,6 +56,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; /** * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with @@ -88,8 +89,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL Handler handler, @ShellBackgroundThread ShellExecutor bgExecutor, Choreographer choreographer, - SyncTransactionQueue syncQueue) { - super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface); + SyncTransactionQueue syncQueue, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { + super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, + windowDecorViewHostSupplier); mHandler = handler; mBgExecutor = bgExecutor; mChoreographer = choreographer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index 0f8bd2866fa8..a0047da46d60 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -122,6 +122,7 @@ import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionReg import com.android.wm.shell.windowdecor.extension.InsetsStateKt; import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import kotlin.Unit; @@ -155,6 +156,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final InteractionJankMonitor mInteractionJankMonitor; private final MultiInstanceHelper mMultiInstanceHelper; private final Optional<DesktopTasksLimiter> mDesktopTasksLimiter; + private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier; private boolean mTransitionDragActive; private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>(); @@ -223,8 +225,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, Optional<DesktopTasksLimiter> desktopTasksLimiter, - Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler - ) { + Optional<DesktopActivityOrientationChangeHandler> activityOrientationChangeHandler, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { this( context, shellExecutor, @@ -244,6 +246,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { genericLinksParser, assistContentRequester, multiInstanceHelper, + windowDecorViewHostSupplier, new DesktopModeWindowDecoration.Factory(), new InputMonitorFactory(), SurfaceControl.Transaction::new, @@ -275,6 +278,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, MultiInstanceHelper multiInstanceHelper, + WindowDecorViewHostSupplier windowDecorViewHostSupplier, DesktopModeWindowDecoration.Factory desktopModeWindowDecorFactory, InputMonitorFactory inputMonitorFactory, Supplier<SurfaceControl.Transaction> transactionFactory, @@ -300,6 +304,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mMultiInstanceHelper = multiInstanceHelper; mShellCommandHandler = shellCommandHandler; mWindowManager = windowManager; + mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; mDesktopModeWindowDecorFactory = desktopModeWindowDecorFactory; mInputMonitorFactory = inputMonitorFactory; mTransactionFactory = transactionFactory; @@ -1284,7 +1289,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mRootTaskDisplayAreaOrganizer, mGenericLinksParser, mAssistContentRequester, - mMultiInstanceHelper); + mMultiInstanceHelper, + mWindowDecorViewHostSupplier); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); final TaskPositioner taskPositioner = mTaskPositionerFactory.create( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 142be91fe942..1409d3011395 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -35,7 +35,6 @@ import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResiz import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.WindowConfiguration.WindowingMode; import android.app.assist.AssistContent; @@ -92,6 +91,7 @@ import com.android.wm.shell.windowdecor.extension.TaskInfoKt; import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder; import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder; import com.android.wm.shell.windowdecor.viewholder.WindowDecorationViewHolder; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import kotlin.Unit; import kotlin.jvm.functions.Function0; @@ -134,12 +134,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin private DragPositioningCallback mDragPositioningCallback; private DragResizeInputListener mDragResizeListener; private DragDetector mDragDetector; - private Runnable mCurrentViewHostRunnable = null; private RelayoutParams mRelayoutParams = new RelayoutParams(); private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult = new WindowDecoration.RelayoutResult<>(); - private final Runnable mViewHostRunnable = - () -> updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mResult); private final Point mPositionInParent = new Point(); private HandleMenu mHandleMenu; @@ -190,14 +187,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, - MultiInstanceHelper multiInstanceHelper) { + MultiInstanceHelper multiInstanceHelper, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { this (context, userContext, displayController, splitScreenController, taskOrganizer, taskInfo, taskSurface, handler, bgExecutor, choreographer, syncQueue, rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper( context.getSystemService(WindowManager.class)), - new SurfaceControlViewHostFactory() {}, DefaultMaximizeMenuFactory.INSTANCE, + new SurfaceControlViewHostFactory() {}, windowDecorViewHostSupplier, + DefaultMaximizeMenuFactory.INSTANCE, DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper); } @@ -222,13 +221,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin Supplier<SurfaceControl> surfaceControlSupplier, WindowManagerWrapper windowManagerWrapper, SurfaceControlViewHostFactory surfaceControlViewHostFactory, + WindowDecorViewHostSupplier windowDecorViewHostSupplier, MaximizeMenuFactory maximizeMenuFactory, HandleMenuFactory handleMenuFactory, MultiInstanceHelper multiInstanceHelper) { super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, windowContainerTransactionSupplier, surfaceControlSupplier, - surfaceControlViewHostFactory); + surfaceControlViewHostFactory, windowDecorViewHostSupplier); mSplitScreenController = splitScreenController; mHandler = handler; mBgExecutor = bgExecutor; @@ -337,73 +337,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { Trace.beginSection("DesktopModeWindowDecoration#relayout"); - if (taskInfo.isFreeform()) { - // The Task is in Freeform mode -> show its header in sync since it's an integral part - // of the window itself - a delayed header might cause bad UX. - relayoutInSync(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskPositionAndCrop); - } else { - // The Task is outside Freeform mode -> allow the handle view to be delayed since the - // handle is just a small addition to the window. - relayoutWithDelayedViewHost(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskPositionAndCrop); - } - Trace.endSection(); - } - - /** Run the whole relayout phase immediately without delay. */ - private void relayoutInSync(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { - // Clear the current ViewHost runnable as we will update the ViewHost here - clearCurrentViewHostRunnable(); - updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, applyStartTransactionOnDraw, - shouldSetTaskPositionAndCrop); - if (mResult.mRootView != null) { - updateViewHost(mRelayoutParams, startT, mResult); - } - } - - /** - * Clear the current ViewHost runnable - to ensure it doesn't run once relayout params have been - * updated. - */ - private void clearCurrentViewHostRunnable() { - if (mCurrentViewHostRunnable != null) { - mHandler.removeCallbacks(mCurrentViewHostRunnable); - mCurrentViewHostRunnable = null; - } - } - - /** - * Relayout the window decoration but repost some of the work, to unblock the current callstack. - */ - private void relayoutWithDelayedViewHost(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { - if (applyStartTransactionOnDraw) { - throw new IllegalArgumentException( - "We cannot both sync viewhost ondraw and delay viewhost creation."); - } - // Clear the current ViewHost runnable as we will update the ViewHost here - clearCurrentViewHostRunnable(); - updateRelayoutParamsAndSurfaces(taskInfo, startT, finishT, - false /* applyStartTransactionOnDraw */, shouldSetTaskPositionAndCrop); - if (mResult.mRootView == null) { - // This means something blocks the window decor from showing, e.g. the task is hidden. - // Nothing is set up in this case including the decoration surface. - return; - } - // Store the current runnable so it can be removed if we start a new relayout. - mCurrentViewHostRunnable = mViewHostRunnable; - mHandler.post(mCurrentViewHostRunnable); - } - - @SuppressLint("MissingPermission") - private void updateRelayoutParamsAndSurfaces(ActivityManager.RunningTaskInfo taskInfo, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) { - Trace.beginSection("DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces"); if (Flags.enableDesktopWindowingAppToWeb()) { setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp); @@ -420,8 +353,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - Trace.beginSection("DesktopModeWindowDecoration#relayout-updateViewsAndSurfaces"); - updateViewsAndSurfaces(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); + Trace.beginSection("DesktopModeWindowDecoration#relayout-inner"); + relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); Trace.endSection(); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo @@ -433,7 +366,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // This means something blocks the window decor from showing, e.g. the task is hidden. // Nothing is set up in this case including the decoration surface. disposeStatusBarInputLayer(); - Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces + Trace.endSection(); // DesktopModeWindowDecoration#relayout return; } @@ -441,12 +374,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin disposeStatusBarInputLayer(); mWindowDecorViewHolder = createViewHolder(); } - Trace.beginSection("DesktopModeWindowDecoration#relayout-binding"); final Point position = new Point(); if (isAppHandle(mWindowDecorViewHolder)) { position.set(determineHandlePosition()); } + Trace.beginSection("DesktopModeWindowDecoration#relayout-bindData"); mWindowDecorViewHolder.bindData(mTaskInfo, position, mResult.mCaptionWidth, @@ -460,7 +393,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin } updateDragResizeListener(oldDecorationSurface); updateMaximizeMenu(startT); - Trace.endSection(); // DesktopModeWindowDecoration#updateRelayoutParamsAndSurfaces + Trace.endSection(); // DesktopModeWindowDecoration#relayout } private boolean isCaptionVisible() { @@ -638,6 +571,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin relayoutParams.mLayoutResId = captionLayoutId; relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode()); relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId); + // Allow the handle view to be delayed since the handle is just a small addition to the + // window, whereas the header cannot be delayed because it is expected to be visible from + // the first frame. + relayoutParams.mAsyncViewHost = isAppHandle; if (isAppHeader) { if (TaskInfoKt.isTransparentCaptionBarAppearance(taskInfo)) { @@ -1253,7 +1190,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId); disposeResizeVeil(); disposeStatusBarInputLayer(); - clearCurrentViewHostRunnable(); super.close(); } @@ -1364,7 +1300,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, AppToWebGenericLinksParser genericLinksParser, AssistContentRequester assistContentRequester, - MultiInstanceHelper multiInstanceHelper) { + MultiInstanceHelper multiInstanceHelper, + WindowDecorViewHostSupplier windowDecorViewHostSupplier) { return new DesktopModeWindowDecoration( context, userContext, @@ -1380,7 +1317,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin rootTaskDisplayAreaOrganizer, genericLinksParser, assistContentRequester, - multiInstanceHelper); + multiInstanceHelper, + windowDecorViewHostSupplier); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index c1a55b48a02a..369484558325 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -62,6 +62,8 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams.OccludingCaptionElement; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer; import com.android.wm.shell.windowdecor.extension.InsetsStateKt; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import java.util.ArrayList; import java.util.Arrays; @@ -116,6 +118,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier; final Supplier<WindowContainerTransaction> mWindowContainerTransactionSupplier; final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory; + @NonNull private final WindowDecorViewHostSupplier mWindowDecorViewHostSupplier; private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener = new DisplayController.OnDisplaysChangedListener() { @Override @@ -137,9 +140,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> Context mDecorWindowContext; SurfaceControl mDecorationContainerSurface; - SurfaceControl mCaptionContainerSurface; - private WindowlessWindowManager mCaptionWindowManager; - private SurfaceControlViewHost mViewHost; + private WindowDecorViewHost mDecorViewHost; private Configuration mWindowDecorConfig; TaskDragResizer mTaskDragResizer; boolean mIsCaptionVisible; @@ -158,11 +159,13 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> DisplayController displayController, ShellTaskOrganizer taskOrganizer, RunningTaskInfo taskInfo, - SurfaceControl taskSurface) { + SurfaceControl taskSurface, + @NonNull WindowDecorViewHostSupplier windowDecorViewHostSupplier) { this(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, SurfaceControl.Builder::new, SurfaceControl.Transaction::new, WindowContainerTransaction::new, SurfaceControl::new, - new SurfaceControlViewHostFactory() {}); + new SurfaceControlViewHostFactory() {}, + windowDecorViewHostSupplier); } WindowDecoration( @@ -176,7 +179,8 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, - SurfaceControlViewHostFactory surfaceControlViewHostFactory) { + SurfaceControlViewHostFactory surfaceControlViewHostFactory, + @NonNull WindowDecorViewHostSupplier windowDecorViewHostSupplier) { mContext = context; mUserContext = userContext; mDisplayController = displayController; @@ -187,6 +191,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier; mWindowContainerTransactionSupplier = windowContainerTransactionSupplier; mSurfaceControlViewHostFactory = surfaceControlViewHostFactory; + mWindowDecorViewHostSupplier = windowDecorViewHostSupplier; mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId); final InsetsState insetsState = mDisplayController.getInsetsState(mTaskInfo.displayId); mIsStatusBarVisible = insetsState != null @@ -212,15 +217,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> void relayout(RelayoutParams params, SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) { - updateViewsAndSurfaces(params, startT, finishT, wct, rootView, outResult); - if (outResult.mRootView != null) { - updateViewHost(params, startT, outResult); - } - } - - protected void updateViewsAndSurfaces(RelayoutParams params, - SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT, - WindowContainerTransaction wct, T rootView, RelayoutResult<T> outResult) { + Trace.beginSection("WindowDecoration#relayout"); outResult.reset(); if (params.mRunningTaskInfo != null) { mTaskInfo = params.mRunningTaskInfo; @@ -231,17 +228,21 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> if (!mTaskInfo.isVisible) { releaseViews(wct); finishT.hide(mTaskSurface); + Trace.endSection(); // WindowDecoration#relayout return; } - + Trace.beginSection("WindowDecoration#relayout-inflateIfNeeded"); inflateIfNeeded(params, wct, rootView, oldLayoutResId, outResult); - if (outResult.mRootView == null) { - // Didn't manage to create a root view, early out. + Trace.endSection(); + final boolean hasCaptionView = outResult.mRootView != null; + if (!hasCaptionView) { + Trace.endSection(); // WindowDecoration#relayout return; } - rootView = null; // Clear it just in case we use it accidentally + Trace.beginSection("WindowDecoration#relayout-updateCaptionVisibility"); updateCaptionVisibility(outResult.mRootView); + Trace.endSection(); final Rect taskBounds = mTaskInfo.getConfiguration().windowConfiguration.getBounds(); outResult.mWidth = taskBounds.width(); @@ -254,10 +255,23 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width(); outResult.mCaptionX = (outResult.mWidth - outResult.mCaptionWidth) / 2; + Trace.beginSection("WindowDecoration#relayout-acquire"); + if (mDecorViewHost == null) { + mDecorViewHost = mWindowDecorViewHostSupplier.acquire(mDecorWindowContext, mDisplay); + } + Trace.endSection(); + + final SurfaceControl captionSurface = mDecorViewHost.getSurfaceControl(); + Trace.beginSection("WindowDecoration#relayout-updateSurfacesAndInsets"); updateDecorationContainerSurface(startT, outResult); - updateCaptionContainerSurface(startT, outResult); + updateCaptionContainerSurface(captionSurface, startT, outResult); updateCaptionInsets(params, wct, outResult, taskBounds); updateTaskSurface(params, startT, finishT, outResult); + Trace.endSection(); + + outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); + updateViewHierarchy(params, outResult, startT); + Trace.endSection(); // WindowDecoration#relayout } private void inflateIfNeeded(RelayoutParams params, WindowContainerTransaction wct, @@ -305,6 +319,32 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> return (T) LayoutInflater.from(context).inflate(layoutResId, null); } + private void updateViewHierarchy(@NonNull RelayoutParams params, + @NonNull RelayoutResult<T> outResult, @NonNull SurfaceControl.Transaction startT) { + Trace.beginSection("WindowDecoration#updateViewHierarchy"); + final WindowManager.LayoutParams lp = + new WindowManager.LayoutParams( + outResult.mCaptionWidth, + outResult.mCaptionHeight, + TYPE_APPLICATION, + FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH, + PixelFormat.TRANSPARENT); + lp.setTitle("Caption of Task=" + mTaskInfo.taskId); + lp.setTrustedOverlay(); + lp.inputFeatures = params.mInputFeatures; + if (params.mAsyncViewHost) { + if (params.mApplyStartTransactionOnDraw) { + throw new IllegalArgumentException( + "We cannot both sync viewhost ondraw and delay viewhost creation."); + } + mDecorViewHost.updateViewAsync(outResult.mRootView, lp, mTaskInfo.getConfiguration()); + } else { + mDecorViewHost.updateView(outResult.mRootView, lp, mTaskInfo.getConfiguration(), + params.mApplyStartTransactionOnDraw ? startT : null); + } + Trace.endSection(); + } + private void updateDecorationContainerSurface( SurfaceControl.Transaction startT, RelayoutResult<T> outResult) { if (mDecorationContainerSurface == null) { @@ -325,23 +365,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> .show(mDecorationContainerSurface); } - private void updateCaptionContainerSurface( + private void updateCaptionContainerSurface(@NonNull SurfaceControl captionSurface, SurfaceControl.Transaction startT, RelayoutResult<T> outResult) { - if (mCaptionContainerSurface == null) { - final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); - mCaptionContainerSurface = builder - .setName("Caption container of Task=" + mTaskInfo.taskId) - .setContainerLayer() - .setParent(mDecorationContainerSurface) - .setCallsite("WindowDecoration.updateCaptionContainerSurface") - .build(); - } - - startT.setWindowCrop(mCaptionContainerSurface, outResult.mCaptionWidth, + startT.reparent(captionSurface, mDecorationContainerSurface) + .setWindowCrop(captionSurface, outResult.mCaptionWidth, outResult.mCaptionHeight) - .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */) - .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER) - .show(mCaptionContainerSurface); + .setPosition(captionSurface, outResult.mCaptionX, 0 /* y */) + .setLayer(captionSurface, CAPTION_LAYER_Z_ORDER) + .show(captionSurface); } private void updateCaptionInsets(RelayoutParams params, WindowContainerTransaction wct, @@ -435,64 +466,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } } - /** - * Updates a {@link SurfaceControlViewHost} to connect the window decoration surfaces with our - * View hierarchy. - * - * @param params parameters to use from the last relayout - * @param onDrawTransaction a transaction to apply in sync with #onDraw - * @param outResult results to use from the last relayout - * - */ - protected void updateViewHost(RelayoutParams params, - SurfaceControl.Transaction onDrawTransaction, RelayoutResult<T> outResult) { - Trace.beginSection("CaptionViewHostLayout"); - if (mCaptionWindowManager == null) { - // Put caption under a container surface because ViewRootImpl sets the destination frame - // of windowless window layers and BLASTBufferQueue#update() doesn't support offset. - mCaptionWindowManager = new WindowlessWindowManager( - mTaskInfo.getConfiguration(), mCaptionContainerSurface, - null /* hostInputToken */); - } - mCaptionWindowManager.setConfiguration(mTaskInfo.getConfiguration()); - final WindowManager.LayoutParams lp = - new WindowManager.LayoutParams( - outResult.mCaptionWidth, - outResult.mCaptionHeight, - TYPE_APPLICATION, - FLAG_NOT_FOCUSABLE | FLAG_SPLIT_TOUCH, - PixelFormat.TRANSPARENT); - lp.setTitle("Caption of Task=" + mTaskInfo.taskId); - lp.setTrustedOverlay(); - lp.inputFeatures = params.mInputFeatures; - if (mViewHost == null) { - Trace.beginSection("CaptionViewHostLayout-new"); - mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay, - mCaptionWindowManager); - if (params.mApplyStartTransactionOnDraw) { - if (onDrawTransaction == null) { - throw new IllegalArgumentException("Trying to sync a null Transaction"); - } - mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction); - } - outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); - mViewHost.setView(outResult.mRootView, lp); - Trace.endSection(); - } else { - Trace.beginSection("CaptionViewHostLayout-relayout"); - if (params.mApplyStartTransactionOnDraw) { - if (onDrawTransaction == null) { - throw new IllegalArgumentException("Trying to sync a null Transaction"); - } - mViewHost.getRootSurfaceControl().applyTransactionOnDraw(onDrawTransaction); - } - outResult.mRootView.setPadding(0, params.mCaptionTopPadding, 0, 0); - mViewHost.relayout(lp); - Trace.endSection(); - } - Trace.endSection(); // CaptionViewHostLayout - } - private Rect calculateBoundingRect(@NonNull OccludingCaptionElement element, int elementWidthPx, @NonNull Rect captionRect) { switch (element.mAlignment) { @@ -580,18 +553,11 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } void releaseViews(WindowContainerTransaction wct) { - if (mViewHost != null) { - mViewHost.release(); - mViewHost = null; - } - - mCaptionWindowManager = null; - final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get(); boolean released = false; - if (mCaptionContainerSurface != null) { - t.remove(mCaptionContainerSurface); - mCaptionContainerSurface = null; + if (mDecorViewHost != null) { + mWindowDecorViewHostSupplier.release(mDecorViewHost, t); + mDecorViewHost = null; released = true; } @@ -742,6 +708,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> boolean mApplyStartTransactionOnDraw; boolean mSetTaskPositionAndCrop; + boolean mAsyncViewHost; void reset() { mLayoutResId = Resources.ID_NULL; @@ -758,6 +725,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mApplyStartTransactionOnDraw = false; mSetTaskPositionAndCrop = false; + mAsyncViewHost = false; mWindowDecorConfig = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt new file mode 100644 index 000000000000..139e6790b744 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2024 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.windowdecor.viewhost + +import android.content.Context +import android.content.res.Configuration +import android.view.Display +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.View +import android.view.WindowManager +import android.view.WindowlessWindowManager +import androidx.tracing.Trace +import com.android.internal.annotations.VisibleForTesting +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +typealias SurfaceControlViewHostFactory = + (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost + +/** + * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost]. + * + * It does not support swapping the root view added to the VRI of the [SurfaceControlViewHost], and + * any attempts to do will throw, which means that once a [View] is added using [updateView] or + * [updateViewAsync], only its properties and binding may be changed, its children views may be + * added, removed or changed and its [WindowManager.LayoutParams] may be changed. + * It also supports asynchronously updating the view hierarchy using [updateViewAsync], in which + * case the update work will be posted on the [ShellMainThread] with no delay. + */ +class DefaultWindowDecorViewHost( + private val context: Context, + @ShellMainThread private val mainScope: CoroutineScope, + private val display: Display, + private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s -> + SurfaceControlViewHost(c, d, wwm, s) + } +) : WindowDecorViewHost { + + private val rootSurface: SurfaceControl = SurfaceControl.Builder() + .setName("DefaultWindowDecorViewHost surface") + .setContainerLayer() + .setCallsite("DefaultWindowDecorViewHost#init") + .build() + + private var wwm: WindowlessWindowManager? = null + @VisibleForTesting + var viewHost: SurfaceControlViewHost? = null + private var currentUpdateJob: Job? = null + + override val surfaceControl: SurfaceControl + get() = rootSurface + + override fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + onDrawTransaction: SurfaceControl.Transaction? + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateView") + clearCurrentUpdateJob() + updateViewHost(view, attrs, configuration, onDrawTransaction) + Trace.endSection() + } + + override fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewAsync") + clearCurrentUpdateJob() + currentUpdateJob = mainScope.launch { + updateViewHost(view, attrs, configuration, onDrawTransaction = null) + } + Trace.endSection() + } + + override fun release(t: SurfaceControl.Transaction) { + clearCurrentUpdateJob() + viewHost?.release() + t.remove(rootSurface) + } + + private fun updateViewHost( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + onDrawTransaction: SurfaceControl.Transaction? + ) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost") + if (wwm == null) { + wwm = WindowlessWindowManager(configuration, rootSurface, null) + } + requireWindowlessWindowManager().setConfiguration(configuration) + if (viewHost == null) { + viewHost = surfaceControlViewHostFactory.invoke( + context, + display, + requireWindowlessWindowManager(), + "DefaultWindowDecorViewHost#updateViewHost" + ) + } + onDrawTransaction?.let { + requireViewHost().rootSurfaceControl.applyTransactionOnDraw(it) + } + if (requireViewHost().view == null) { + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-setView") + requireViewHost().setView(view, attrs) + Trace.endSection() + } else { + check(requireViewHost().view == view) { "Changing view is not allowed" } + Trace.beginSection("DefaultWindowDecorViewHost#updateViewHost-relayout") + requireViewHost().relayout(attrs) + Trace.endSection() + } + Trace.endSection() + } + + private fun clearCurrentUpdateJob() { + currentUpdateJob?.cancel() + currentUpdateJob = null + } + + private fun requireWindowlessWindowManager(): WindowlessWindowManager { + return wwm ?: error("Expected non-null windowless window manager") + } + + private fun requireViewHost(): SurfaceControlViewHost { + return viewHost ?: error("Expected non-null view host") + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostSupplier.kt new file mode 100644 index 000000000000..9997e8f564d8 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostSupplier.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 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.windowdecor.viewhost + +import android.content.Context +import android.view.Display +import android.view.SurfaceControl +import com.android.wm.shell.shared.annotations.ShellMainThread +import kotlinx.coroutines.CoroutineScope + +/** + * A supplier of [DefaultWindowDecorViewHost]s. It creates a new one every time one is requested. + */ +class DefaultWindowDecorViewHostSupplier( + @ShellMainThread private val mainScope: CoroutineScope, +) : WindowDecorViewHostSupplier<DefaultWindowDecorViewHost> { + + override fun acquire(context: Context, display: Display): DefaultWindowDecorViewHost { + return DefaultWindowDecorViewHost(context, mainScope, display) + } + + override fun release(viewHost: DefaultWindowDecorViewHost, t: SurfaceControl.Transaction) { + viewHost.release(t) + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHost.kt new file mode 100644 index 000000000000..3fbaea8bd1bf --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHost.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 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.windowdecor.viewhost + +import android.content.res.Configuration +import android.view.SurfaceControl +import android.view.View +import android.view.WindowManager +import com.android.wm.shell.windowdecor.WindowDecoration + +/** + * An interface for a utility that hosts a [WindowDecoration]'s [View] hierarchy under a + * [SurfaceControl]. + */ +interface WindowDecorViewHost { + /** The surface where the underlying [View] hierarchy is being rendered. */ + val surfaceControl: SurfaceControl + + /** Synchronously update the view hierarchy of this view host. */ + fun updateView( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration, + onDrawTransaction: SurfaceControl.Transaction? + ) + + /** Asynchronously update the view hierarchy of this view host. */ + fun updateViewAsync( + view: View, + attrs: WindowManager.LayoutParams, + configuration: Configuration + ) + + /** Releases the underlying [View] hierarchy and removes the backing [SurfaceControl]. */ + fun release(t: SurfaceControl.Transaction) +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHostSupplier.kt new file mode 100644 index 000000000000..0e2358446d12 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHostSupplier.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 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.windowdecor.viewhost + +import android.content.Context +import android.view.Display +import android.view.SurfaceControl + +/** + * An interface for a supplier of [WindowDecorViewHost]s. + */ +interface WindowDecorViewHostSupplier<T : WindowDecorViewHost> { + /** Acquire a [WindowDecorViewHost]. */ + fun acquire(context: Context, display: Display): T + + /** + * Release a [WindowDecorViewHost] when it is no longer used. + * + * @param viewHost the [WindowDecorViewHost] to release + * @param t a transaction that may be used to remove any underlying backing [SurfaceControl] + * that are hosting this [WindowDecorViewHost]. The supplier is not expected to apply + * the transaction. It should be applied by the owner of this supplier. + */ + fun release(viewHost: T, t: SurfaceControl.Transaction) +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt index 2c805e8e77b3..a17d08d8fbfb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt @@ -98,6 +98,7 @@ import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeKeyguardChangeListener import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier import java.util.Optional import java.util.function.Consumer import java.util.function.Supplier @@ -181,6 +182,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { @Mock private lateinit var mockTaskPositionerFactory: DesktopModeWindowDecorViewModel.TaskPositionerFactory @Mock private lateinit var mockTaskPositioner: TaskPositioner + @Mock private lateinit var mockWindowDecorViewHostSupplier: WindowDecorViewHostSupplier<*> private lateinit var spyContext: TestableContext private val transactionFactory = Supplier<SurfaceControl.Transaction> { @@ -230,6 +232,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { mockGenericLinksParser, mockAssistContentRequester, mockMultiInstanceHelper, + mockWindowDecorViewHostSupplier, mockDesktopModeWindowDecorFactory, mockInputMonitorFactory, transactionFactory, @@ -1197,7 +1200,7 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever( mockDesktopModeWindowDecorFactory.create( any(), any(), any(), any(), any(), eq(task), any(), any(), any(), any(), any(), - any(), any(), any(), any()) + any(), any(), any(), any(), any()) ).thenReturn(decoration) decoration.mTaskInfo = task whenever(decoration.isFocused).thenReturn(task.isFocused) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java index b9e542a0e0c1..7b68ddf64fce 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java @@ -97,6 +97,8 @@ import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import kotlin.Unit; import kotlin.jvm.functions.Function0; @@ -162,6 +164,10 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { @Mock private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory; @Mock + private WindowDecorViewHostSupplier mMockWindowDecorViewHostSupplier; + @Mock + private WindowDecorViewHost mMockWindowDecorViewHost; + @Mock private TypedArray mMockRoundedCornersRadiusArray; @Mock private TestTouchEventListener mMockTouchEventListener; @@ -233,6 +239,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { any(), anyBoolean(), anyBoolean(), any(), anyInt(), anyInt(), anyInt())) .thenReturn(mMockHandleMenu); when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any())).thenReturn(false); + when(mMockWindowDecorViewHostSupplier.acquire(any(), eq(defaultDisplay))) + .thenReturn(mMockWindowDecorViewHost); + when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class)); } @After @@ -504,58 +513,55 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { } @Test - public void relayout_fullscreenTask_appliesTransactionImmediately() { + public void updateRelayoutParams_handle_requestsAsyncViewHostRendering() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); + // Make the task fullscreen so that its decoration is an App Handle. taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); - spyWindowDecor.relayout(taskInfo); + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false); - verify(mMockTransaction).apply(); - verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any()); + // App Handles don't need to be rendered in sync with the task animation, per UX. + assertThat(relayoutParams.mAsyncViewHost).isTrue(); } @Test - public void relayout_freeformTask_appliesTransactionOnDraw() { + public void updateRelayoutParams_header_requestsSyncViewHostRendering() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); + // Make the task freeform so that its decoration is an App Header. taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); - // Make non-resizable to avoid dealing with input-permissions (MONITOR_INPUT) - taskInfo.isResizeable = false; - - spyWindowDecor.relayout(taskInfo); - - verify(mMockTransaction, never()).apply(); - verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockTransaction); - } - - @Test - public void relayout_fullscreenTask_doesNotCreateViewHostImmediately() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + final RelayoutParams relayoutParams = new RelayoutParams(); - spyWindowDecor.relayout(taskInfo); + DesktopModeWindowDecoration.updateRelayoutParams( + relayoutParams, + mTestableContext, + taskInfo, + /* applyStartTransactionOnDraw= */ true, + /* shouldSetTaskPositionAndCrop */ false); - verify(mMockSurfaceControlViewHostFactory, never()).create(any(), any(), any()); + // App Headers must be rendered in sync with the task animation, so it cannot be delayed. + assertThat(relayoutParams.mAsyncViewHost).isFalse(); } @Test - public void relayout_fullscreenTask_postsViewHostCreation() { + public void relayout_fullscreenTask_appliesTransactionImmediately() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); spyWindowDecor.relayout(taskInfo); - verify(mMockHandler).post(runnableArgument.capture()); - runnableArgument.getValue().run(); - verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any()); + verify(mMockTransaction).apply(); + verify(mMockRootSurfaceControl, never()).applyTransactionOnDraw(any()); } @Test - public void relayout_freeformTask_createsViewHostImmediately() { + public void relayout_freeformTask_appliesTransactionOnDraw() { final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -564,36 +570,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { spyWindowDecor.relayout(taskInfo); - verify(mMockSurfaceControlViewHostFactory).create(any(), any(), any()); - verify(mMockHandler, never()).post(any()); - } - - @Test - public void relayout_removesExistingHandlerCallback() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo); - verify(mMockHandler).post(runnableArgument.capture()); - - spyWindowDecor.relayout(taskInfo); - - verify(mMockHandler).removeCallbacks(runnableArgument.getValue()); - } - - @Test - public void close_removesExistingHandlerCallback() { - final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true); - final DesktopModeWindowDecoration spyWindowDecor = spy(createWindowDecoration(taskInfo)); - taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - ArgumentCaptor<Runnable> runnableArgument = ArgumentCaptor.forClass(Runnable.class); - spyWindowDecor.relayout(taskInfo); - verify(mMockHandler).post(runnableArgument.capture()); - - spyWindowDecor.close(); - - verify(mMockHandler).removeCallbacks(runnableArgument.getValue()); + verify(mMockTransaction, never()).apply(); + verify(mMockWindowDecorViewHost).updateView(any(), any(), any(), eq(mMockTransaction)); } @Test @@ -924,7 +902,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase { mMockGenericLinksParser, mMockAssistContentRequester, SurfaceControl.Builder::new, mMockTransactionSupplier, WindowContainerTransaction::new, SurfaceControl::new, new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory, - maximizeMenuFactory, mMockHandleMenuFactory, mMockMultiInstanceHelper); + mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory, + mMockMultiInstanceHelper); windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener, mMockTouchEventListener); windowDecor.setExclusionRegionListener(mMockExclusionRegionListener); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index 2e117ac9f865..7252b32efc6b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -85,6 +85,8 @@ import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.tests.R; import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHost; +import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import org.junit.Before; import org.junit.Rule; @@ -128,6 +130,10 @@ public class WindowDecorationTests extends ShellTestCase { @Mock private SurfaceControlViewHost mMockSurfaceControlViewHost; @Mock + private WindowDecorViewHostSupplier mMockWindowDecorViewHostSupplier; + @Mock + private WindowDecorViewHost mMockWindowDecorViewHost; + @Mock private AttachedSurfaceControl mMockRootSurfaceControl; @Mock private TestView mMockView; @@ -167,6 +173,9 @@ public class WindowDecorationTests extends ShellTestCase { when(mMockSurfaceControlViewHost.getRootSurfaceControl()) .thenReturn(mMockRootSurfaceControl); when(mMockView.findViewById(anyInt())).thenReturn(mMockView); + when(mMockWindowDecorViewHostSupplier.acquire(any(), any())) + .thenReturn(mMockWindowDecorViewHost); + when(mMockWindowDecorViewHost.getSurfaceControl()).thenReturn(mock(SurfaceControl.class)); // Add status bar inset so that WindowDecoration does not think task is in immersive mode mInsetsState.getOrCreateSource(STATUS_BAR_INSET_SOURCE_ID, statusBars()).setVisible(true); @@ -230,10 +239,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder() .setDisplayId(Display.DEFAULT_DISPLAY) @@ -254,18 +259,18 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true); verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100); - verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); - verify(captionContainerSurfaceBuilder).setContainerLayer(); + final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl(); + verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface); verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); - verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); - - verify(mMockSurfaceControlViewHost) - .setView(same(mMockView), - argThat(lp -> lp.height == 64 - && lp.width == 300 - && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0)); + verify(mMockWindowDecorViewHost).updateView( + same(mMockView), + argThat(lp -> lp.height == 64 + && lp.width == 300 + && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0), + eq(taskInfo.configuration), + eq(null) /* onDrawTransaction */); verify(mMockView).setTaskFocusState(true); verify(mMockWindowContainerTransaction).addInsetsSource( eq(taskInfo.token), @@ -296,10 +301,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -322,7 +323,7 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.relayout(taskInfo); - verify(mMockSurfaceControlViewHost, never()).release(); + verify(mMockWindowDecorViewHost, never()).release(any()); verify(t, never()).apply(); verify(mMockWindowContainerTransaction, never()) .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt()); @@ -332,9 +333,8 @@ public class WindowDecorationTests extends ShellTestCase { taskInfo.isVisible = false; windowDecor.relayout(taskInfo); - final InOrder releaseOrder = inOrder(t2, mMockSurfaceControlViewHost); - releaseOrder.verify(mMockSurfaceControlViewHost).release(); - releaseOrder.verify(t2).remove(captionContainerSurface); + final InOrder releaseOrder = inOrder(t2, mMockWindowDecorViewHostSupplier); + releaseOrder.verify(mMockWindowDecorViewHostSupplier).release(mMockWindowDecorViewHost, t2); releaseOrder.verify(t2).remove(decorContainerSurface); releaseOrder.verify(t2).apply(); // Expect to remove two insets sources, the caption insets and the mandatory gesture insets. @@ -382,8 +382,8 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockDisplayController).removeDisplayWindowListener(same(listener)); assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView); - verify(mMockSurfaceControlViewHostFactory).create(any(), eq(mockDisplay), any()); - verify(mMockSurfaceControlViewHost).setView(same(mMockView), any()); + verify(mMockWindowDecorViewHostSupplier).acquire(any(), eq(mockDisplay)); + verify(mMockWindowDecorViewHost).updateView(same(mMockView), any(), any(), any()); } @Test @@ -396,10 +396,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -436,8 +432,7 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.mDecorWindowContext.getResources(), mRelayoutParams.mCaptionHeightId); verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, width, height); verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface); - verify(mMockSurfaceControlViewHostFactory, Mockito.times(2)) - .create(any(), eq(defaultDisplay), any()); + verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any()); } @Test @@ -450,10 +445,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -473,8 +464,8 @@ public class WindowDecorationTests extends ShellTestCase { windowDecor.relayout(taskInfo); - verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); - verify(captionContainerSurfaceBuilder).setContainerLayer(); + final SurfaceControl captionContainerSurface = mMockWindowDecorViewHost.getSurfaceControl(); + verify(mMockSurfaceControlStartT).reparent(captionContainerSurface, decorContainerSurface); // Width of the captionContainerSurface should match the width of TASK_BOUNDS verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); @@ -490,10 +481,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder captionContainerSurfaceBuilder = - createMockSurfaceControlBuilder(captionContainerSurface); - mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder); final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class); mMockSurfaceControlTransactions.add(t); @@ -511,9 +498,11 @@ public class WindowDecorationTests extends ShellTestCase { taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo); - windowDecor.relayout(taskInfo, true /* applyStartTransactionOnDraw */); + mRelayoutParams.mApplyStartTransactionOnDraw = true; + windowDecor.relayout(taskInfo); - verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); + verify(mMockWindowDecorViewHost).updateView(any(), any(), any(), + eq(mMockSurfaceControlStartT)); } @Test @@ -867,37 +856,52 @@ public class WindowDecorationTests extends ShellTestCase { } @Test - public void updateViewHost_applyTransactionOnDrawIsTrue_surfaceControlIsUpdated() { + public void relayout_applyTransactionOnDrawIsTrue_updatesViewWithDrawTransaction() { final TestWindowDecoration windowDecor = createWindowDecoration( - new TestRunningTaskInfoBuilder().build()); + new TestRunningTaskInfoBuilder() + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FREEFORM) + .build()); mRelayoutParams.mApplyStartTransactionOnDraw = true; mRelayoutResult.mRootView = mMockView; - windowDecor.updateViewHost(mRelayoutParams, mMockSurfaceControlStartT, mRelayoutResult); + windowDecor.relayout(windowDecor.mTaskInfo); - verify(mMockRootSurfaceControl).applyTransactionOnDraw(mMockSurfaceControlStartT); + verify(mMockWindowDecorViewHost) + .updateView(eq(mRelayoutResult.mRootView), any(), + eq(windowDecor.mTaskInfo.configuration), eq(mMockSurfaceControlStartT)); } @Test - public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsTrue_throwsException() { + public void relayout_applyTransactionOnDrawIsTrue_asyncViewHostRendering_throwsException() { final TestWindowDecoration windowDecor = createWindowDecoration( - new TestRunningTaskInfoBuilder().build()); + new TestRunningTaskInfoBuilder() + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build()); mRelayoutParams.mApplyStartTransactionOnDraw = true; + mRelayoutParams.mAsyncViewHost = true; mRelayoutResult.mRootView = mMockView; assertThrows(IllegalArgumentException.class, - () -> windowDecor.updateViewHost( - mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult)); + () -> windowDecor.relayout(windowDecor.mTaskInfo)); } @Test - public void updateViewHost_nullDrawTransaction_applyTransactionOnDrawIsFalse_doesNotThrow() { + public void relayout_asyncViewHostRendering() { final TestWindowDecoration windowDecor = createWindowDecoration( - new TestRunningTaskInfoBuilder().build()); - mRelayoutParams.mApplyStartTransactionOnDraw = false; + new TestRunningTaskInfoBuilder() + .setVisible(true) + .setWindowingMode(WINDOWING_MODE_FULLSCREEN) + .build()); + mRelayoutParams.mAsyncViewHost = true; mRelayoutResult.mRootView = mMockView; - windowDecor.updateViewHost(mRelayoutParams, null /* onDrawTransaction */, mRelayoutResult); + windowDecor.relayout(windowDecor.mTaskInfo); + + verify(mMockWindowDecorViewHost) + .updateViewAsync(eq(mRelayoutResult.mRootView), any(), + eq(windowDecor.mTaskInfo.configuration)); } @Test @@ -997,7 +1001,8 @@ public class WindowDecorationTests extends ShellTestCase { new MockObjectSupplier<>(mMockSurfaceControlTransactions, () -> mock(SurfaceControl.Transaction.class)), () -> mMockWindowContainerTransaction, () -> mMockTaskSurface, - mMockSurfaceControlViewHostFactory); + mMockSurfaceControlViewHostFactory, + mMockWindowDecorViewHostSupplier); } private class MockObjectSupplier<T> implements Supplier<T> { @@ -1037,16 +1042,20 @@ public class WindowDecorationTests extends ShellTestCase { Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier, Supplier<WindowContainerTransaction> windowContainerTransactionSupplier, Supplier<SurfaceControl> surfaceControlSupplier, - SurfaceControlViewHostFactory surfaceControlViewHostFactory) { + SurfaceControlViewHostFactory surfaceControlViewHostFactory, + @NonNull WindowDecorViewHostSupplier windowDecorViewHostSupplier) { super(context, userContext, displayController, taskOrganizer, taskInfo, taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier, windowContainerTransactionSupplier, surfaceControlSupplier, - surfaceControlViewHostFactory); + surfaceControlViewHostFactory, windowDecorViewHostSupplier); } @Override void relayout(ActivityManager.RunningTaskInfo taskInfo) { - relayout(taskInfo, false /* applyStartTransactionOnDraw */); + mRelayoutParams.mRunningTaskInfo = taskInfo; + mRelayoutParams.mLayoutResId = R.layout.caption_layout; + relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, + mMockWindowContainerTransaction, mMockView, mRelayoutResult); } @Override @@ -1067,15 +1076,6 @@ public class WindowDecorationTests extends ShellTestCase { return super.inflateLayout(context, layoutResId); } - void relayout(ActivityManager.RunningTaskInfo taskInfo, - boolean applyStartTransactionOnDraw) { - mRelayoutParams.mRunningTaskInfo = taskInfo; - mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw; - mRelayoutParams.mLayoutResId = R.layout.caption_layout; - relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT, - mMockWindowContainerTransaction, mMockView, mRelayoutResult); - } - private AdditionalViewContainer addTestViewContainer() { final Resources resources = mDecorWindowContext.getResources(); final int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt new file mode 100644 index 000000000000..1b2ce9e4df36 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2024 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.windowdecor.viewhost + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.SurfaceControl +import android.view.SurfaceControlViewHost +import android.view.View +import android.view.WindowManager +import androidx.test.filters.SmallTest +import com.android.wm.shell.ShellTestCase +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertThrows +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify + + +/** + * Tests for [DefaultWindowDecorViewHost]. + * + * Build/Install/Run: + * atest WMShellUnitTests:DefaultWindowDecorViewHostTest + */ +@SmallTest +@TestableLooper.RunWithLooper +@RunWith(AndroidTestingRunner::class) +class DefaultWindowDecorViewHostTest : ShellTestCase() { + + @Test + fun updateView_layoutInViewHost() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view) + } + + @Test + fun updateView_alreadyLaidOut_relayouts() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + val otherParams = WindowManager.LayoutParams(200, 200) + windowDecorViewHost.updateView( + view = view, + attrs = otherParams, + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(view) + assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width) + .isEqualTo(otherParams.width) + } + + @Test + fun updateView_replacingView_throws() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + val otherView = View(context) + assertThrows(Exception::class.java) { + windowDecorViewHost.updateView( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + } + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateView_clearsPendingAsyncJob() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val asyncView = View(context) + val syncView = View(context) + val asyncAttrs = WindowManager.LayoutParams(100, 100) + val syncAttrs = WindowManager.LayoutParams(200, 200) + + windowDecorViewHost.updateViewAsync( + view = asyncView, + attrs = asyncAttrs, + configuration = context.resources.configuration, + ) + + // No view host yet, since the coroutine hasn't run. + assertThat(windowDecorViewHost.viewHost).isNull() + + windowDecorViewHost.updateView( + view = syncView, + attrs = syncAttrs, + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + // Would run coroutine if it hadn't been cancelled. + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isNotNull() + // View host view/attrs should match the ones from the sync call, plus, since the + // sync/async were made with different views, if the job hadn't been cancelled there + // would've been an exception thrown as replacing views isn't allowed. + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView) + assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width) + .isEqualTo(syncAttrs.width) + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync() = runTest { + val windowDecorViewHost = createDefaultViewHost() + val view = View(context) + val attrs = WindowManager.LayoutParams(100, 100) + + windowDecorViewHost.updateViewAsync( + view = view, + attrs = attrs, + configuration = context.resources.configuration, + ) + + assertThat(windowDecorViewHost.viewHost).isNull() + + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHost).isNotNull() + } + + @OptIn(ExperimentalCoroutinesApi::class) + @Test + fun updateViewAsync_clearsPendingAsyncJob() = runTest { + val windowDecorViewHost = createDefaultViewHost() + + val view = View(context) + windowDecorViewHost.updateViewAsync( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + val otherView = View(context) + windowDecorViewHost.updateViewAsync( + view = otherView, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + ) + + advanceUntilIdle() + + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView) + } + + @Test + fun release() = runTest { + val windowDecorViewHost = createDefaultViewHost() + + val view = View(context) + windowDecorViewHost.updateView( + view = view, + attrs = WindowManager.LayoutParams(100, 100), + configuration = context.resources.configuration, + onDrawTransaction = null + ) + + val t = mock(SurfaceControl.Transaction::class.java) + windowDecorViewHost.release(t) + + verify(windowDecorViewHost.viewHost!!).release() + verify(t).remove(windowDecorViewHost.surfaceControl) + } + + private fun CoroutineScope.createDefaultViewHost() = DefaultWindowDecorViewHost( + context = context, + mainScope = this, + display = context.display, + surfaceControlViewHostFactory = { c, d, wwm, s -> + spy(SurfaceControlViewHost(c, d, wwm, s)) + } + ) +}
\ No newline at end of file |