summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jorge Gil <jorgegil@google.com> 2024-08-20 01:12:14 +0000
committer Jorge Gil <jorgegil@google.com> 2024-09-12 03:54:55 +0000
commit4c008874f9512e16d11095e799164ca943b0f58d (patch)
tree42b5f30861a8e8a5f158a258846b7d712c8987a6
parent5ceda28d2d7eed5b43c133e5de8587958fcd486c (diff)
Add WindowDecorViewHost and WindowDecorViewHostSupplier
Creates an abstraction for creating, updating and releasing the surface and view hierarchy drawn by the WindowDecoration (aka the caption bar). Also provides a default implementation that supports rendering the view hierarchy synchronously and asynchronously. There's no behavioral effects in this change, it just moves the "async" ability to the base WindowDecoration class from the DesktopModeWindowDecoration, and it sets up the decorations to allow swapping the default implementation with a new (and flagged) WindowDecorViewHost that allows warming up the view host and a new WindowDecorViewHostSupplier implementation that allows caching/reusing WindowDecorViewHosts, which will be introduced in a follow up change. Bug: 360452034 Flag: EXEMPT refactor Test: m WMShellUnitTests Change-Id: Iae7589879d56d802c85b8c4a72a8dcc9b20d6afb
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java102
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java174
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt146
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostSupplier.kt38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHost.kt49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/WindowDecorViewHostSupplier.kt38
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java99
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java132
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt222
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 4af5b2c95cd5..260d5815506a 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) {
@@ -573,18 +546,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;
}
@@ -735,6 +701,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
boolean mApplyStartTransactionOnDraw;
boolean mSetTaskPositionAndCrop;
+ boolean mAsyncViewHost;
void reset() {
mLayoutResId = Resources.ID_NULL;
@@ -751,6 +718,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 6154391c5e97..da76d7fc82d4 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
@@ -980,7 +984,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> {
@@ -1020,16 +1025,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
@@ -1050,15 +1059,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