diff options
Diffstat (limited to 'libs')
64 files changed, 1246 insertions, 1107 deletions
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java index dd86a1a0edbb..83619efefd3b 100644 --- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java +++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java @@ -90,9 +90,6 @@ public class DesktopModeStatus { /** The maximum override density allowed for tasks inside the desktop. */ private static final int DESKTOP_DENSITY_MAX = 1000; - /** The number of [WindowDecorViewHost] instances to warm up on system start. */ - private static final int WINDOW_DECOR_PRE_WARM_SIZE = 2; - /** * Sysprop declaring whether to enters desktop mode by default when the windowing mode of the * display's root TaskDisplayArea is set to WINDOWING_MODE_FREEFORM. @@ -115,14 +112,6 @@ public class DesktopModeStatus { private static final String MAX_TASK_LIMIT_SYS_PROP = "persist.wm.debug.desktop_max_task_limit"; /** - * Sysprop declaring the number of [WindowDecorViewHost] instances to warm up on system start. - * - * <p>If it is not defined, then [WINDOW_DECOR_PRE_WARM_SIZE] is used. - */ - private static final String WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP = - "persist.wm.debug.desktop_window_decor_pre_warm_size"; - - /** * Return {@code true} if veiled resizing is active. If false, fluid resizing is used. */ public static boolean isVeiledResizeEnabled() { @@ -162,12 +151,6 @@ public class DesktopModeStatus { context.getResources().getInteger(R.integer.config_maxDesktopWindowingActiveTasks)); } - /** The number of [WindowDecorViewHost] instances to warm up on system start. */ - public static int getWindowDecorPreWarmSize() { - return SystemProperties.getInt(WINDOW_DECOR_PRE_WARM_SIZE_SYS_PROP, - WINDOW_DECOR_PRE_WARM_SIZE); - } - /** * Return {@code true} if the current device supports desktop mode. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index f478b4446cbe..3e5adf395cdd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -117,6 +117,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ private static final long MAX_ANIMATION_DURATION = 2000; private final LatencyTracker mLatencyTracker; + @ShellMainThread private final Handler mHandler; /** True when a back gesture is ongoing */ private boolean mBackGestureStarted = false; @@ -218,7 +219,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull BackAnimationBackground backAnimationBackground, ShellBackAnimationRegistry shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler, - Transitions transitions) { + Transitions transitions, + @ShellMainThread Handler handler) { this( shellInit, shellController, @@ -230,7 +232,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont backAnimationBackground, shellBackAnimationRegistry, shellCommandHandler, - transitions); + transitions, + handler); } @VisibleForTesting @@ -245,7 +248,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont @NonNull BackAnimationBackground backAnimationBackground, ShellBackAnimationRegistry shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler, - Transitions transitions) { + Transitions transitions, + @NonNull @ShellMainThread Handler handler) { mShellController = shellController; mShellExecutor = shellExecutor; mActivityTaskManager = activityTaskManager; @@ -263,6 +267,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont mTransitions = transitions; mBackTransitionHandler = new BackTransitionHandler(); mTransitions.addHandler(mBackTransitionHandler); + mHandler = handler; updateTouchableArea(); } @@ -399,7 +404,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } } - private static class IBackAnimationImpl extends IBackAnimation.Stub + private class IBackAnimationImpl extends IBackAnimation.Stub implements ExternalInterfaceBinder { private BackAnimationController mController; @@ -417,7 +422,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont callback, runner, controller.mContext, - CUJ_PREDICTIVE_BACK_HOME))); + CUJ_PREDICTIVE_BACK_HOME, + mHandler))); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java index e24df0bdc05d..9ca9b730fb06 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java @@ -20,6 +20,7 @@ import static android.view.WindowManager.TRANSIT_OLD_UNSET; import android.annotation.NonNull; import android.content.Context; +import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.view.IRemoteAnimationFinishedCallback; @@ -31,6 +32,7 @@ import android.window.IOnBackInvokedCallback; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.Cuj.CujType; import com.android.internal.jank.InteractionJankMonitor; +import com.android.wm.shell.shared.annotations.ShellMainThread; /** * Used to register the animation callback and runner, it will trigger result if gesture was finish @@ -45,6 +47,8 @@ public class BackAnimationRunner { private final IRemoteAnimationRunner mRunner; private final @CujType int mCujType; private final Context mContext; + @ShellMainThread + private final Handler mHandler; // Whether we are waiting to receive onAnimationStart private boolean mWaitingAnimation; @@ -56,18 +60,35 @@ public class BackAnimationRunner { @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner, @NonNull Context context, - @CujType int cujType) { + @CujType int cujType, + @ShellMainThread Handler handler) { mCallback = callback; mRunner = runner; mCujType = cujType; mContext = context; + mHandler = handler; } public BackAnimationRunner( @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner, - @NonNull Context context) { - this(callback, runner, context, NO_CUJ); + @NonNull Context context, + @ShellMainThread Handler handler + ) { + this(callback, runner, context, NO_CUJ, handler); + } + + /** + * @deprecated Use {@link BackAnimationRunner} constructor providing an handler for the ui + * thread of the animation. + */ + @Deprecated + public BackAnimationRunner( + @NonNull IOnBackInvokedCallback callback, + @NonNull IRemoteAnimationRunner runner, + @NonNull Context context + ) { + this(callback, runner, context, NO_CUJ, context.getMainThreadHandler()); } /** Returns the registered animation runner */ @@ -100,7 +121,7 @@ public class BackAnimationRunner { mWaitingAnimation = false; if (shouldMonitorCUJ(apps)) { interactionJankMonitor.begin( - apps[0].leash, mContext, mCujType); + apps[0].leash, mContext, mHandler, mCujType); } try { getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index 32e809a23a91..37339307f5b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -26,6 +26,7 @@ import android.graphics.Matrix import android.graphics.PointF import android.graphics.Rect import android.graphics.RectF +import android.os.Handler import android.os.RemoteException import android.util.TimeUtils import android.view.Choreographer @@ -53,6 +54,7 @@ import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.shared.animation.Interpolators +import com.android.wm.shell.shared.annotations.ShellMainThread import kotlin.math.abs import kotlin.math.max import kotlin.math.min @@ -61,7 +63,8 @@ abstract class CrossActivityBackAnimation( private val context: Context, private val background: BackAnimationBackground, private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, - protected val transaction: SurfaceControl.Transaction + protected val transaction: SurfaceControl.Transaction, + @ShellMainThread handler: Handler, ) : ShellBackAnimation() { protected val startClosingRect = RectF() @@ -80,7 +83,13 @@ abstract class CrossActivityBackAnimation( private var statusbarHeight = SystemBarUtils.getStatusBarHeight(context) private val backAnimationRunner = - BackAnimationRunner(Callback(), Runner(), context, Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY) + BackAnimationRunner( + Callback(), + Runner(), + context, + Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY, + handler, + ) private val initialTouchPos = PointF() private val transformMatrix = Matrix() private val tmpFloat9 = FloatArray(9) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java index 3fccecab4319..7a569799ab84 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java @@ -34,6 +34,7 @@ import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; +import android.os.Handler; import android.os.RemoteException; import android.view.Choreographer; import android.view.IRemoteAnimationFinishedCallback; @@ -52,6 +53,7 @@ import com.android.internal.policy.SystemBarUtils; import com.android.internal.protolog.ProtoLog; import com.android.wm.shell.R; import com.android.wm.shell.shared.animation.Interpolators; +import com.android.wm.shell.shared.annotations.ShellMainThread; import javax.inject.Inject; @@ -113,9 +115,10 @@ public class CrossTaskBackAnimation extends ShellBackAnimation { private float mVerticalMargin; @Inject - public CrossTaskBackAnimation(Context context, BackAnimationBackground background) { + public CrossTaskBackAnimation(Context context, BackAnimationBackground background, + @ShellMainThread Handler handler) { mBackAnimationRunner = new BackAnimationRunner( - new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK); + new Callback(), new Runner(), context, CUJ_PREDICTIVE_BACK_CROSS_TASK, handler); mBackground = background; mContext = context; loadResources(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt index b02f97bf7784..2f7666b21882 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.back import android.content.Context import android.graphics.Rect import android.graphics.RectF +import android.os.Handler import android.util.MathUtils import android.view.SurfaceControl import android.view.animation.Animation @@ -30,6 +31,7 @@ import com.android.internal.policy.TransitionAnimation import com.android.internal.protolog.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.shared.annotations.ShellMainThread import javax.inject.Inject import kotlin.math.max import kotlin.math.min @@ -40,13 +42,15 @@ class CustomCrossActivityBackAnimation( background: BackAnimationBackground, rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, transaction: SurfaceControl.Transaction, - private val customAnimationLoader: CustomAnimationLoader + private val customAnimationLoader: CustomAnimationLoader, + @ShellMainThread handler: Handler, ) : CrossActivityBackAnimation( context, background, rootTaskDisplayAreaOrganizer, - transaction + transaction, + handler ) { private var enterAnimation: Animation? = null @@ -59,7 +63,8 @@ class CustomCrossActivityBackAnimation( constructor( context: Context, background: BackAnimationBackground, - rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + @ShellMainThread handler: Handler, ) : this( context, background, @@ -67,7 +72,8 @@ class CustomCrossActivityBackAnimation( SurfaceControl.Transaction(), CustomAnimationLoader( TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation") - ) + ), + handler, ) override fun preparePreCommitClosingRectMovement(swipeEdge: Int) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt index 66d8a5f2eeb9..eecd7694009d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt @@ -16,11 +16,13 @@ package com.android.wm.shell.back import android.content.Context +import android.os.Handler import android.view.SurfaceControl import android.window.BackEvent import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.shared.animation.Interpolators +import com.android.wm.shell.shared.annotations.ShellMainThread import javax.inject.Inject import kotlin.math.max @@ -30,13 +32,15 @@ class DefaultCrossActivityBackAnimation constructor( context: Context, background: BackAnimationBackground, - rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer + rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, + @ShellMainThread handler: Handler, ) : CrossActivityBackAnimation( context, background, rootTaskDisplayAreaOrganizer, - SurfaceControl.Transaction() + SurfaceControl.Transaction(), + handler ) { private val postCommitInterpolator = Interpolators.EMPHASIZED diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index c545d73734f0..af4a0c55f28d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1241,8 +1241,9 @@ public class BubbleController implements ConfigurationChangeListener, mBubbleData.dismissBubbleWithKey( bubbleKey, Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, timestamp); } - if (selectedBubbleKey != null && !selectedBubbleKey.equals(bubbleKey)) { - // We did not remove the selected bubble. Expand it again + if (mBubbleData.hasBubbles()) { + // We still have bubbles, if we dragged an individual bubble to dismiss we were expanded + // so re-expand to whatever is selected. showExpandedViewForBubbleBar(); } } @@ -2007,7 +2008,7 @@ public class BubbleController implements ConfigurationChangeListener, @Override public void selectionChanged(BubbleViewProvider selectedBubble) { // Only need to update the layer view if we're currently expanded for selection changes. - if (mLayerView != null && isStackExpanded()) { + if (mLayerView != null && mLayerView.isExpanded()) { mLayerView.showExpandedView(selectedBubble); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java index 1c9c195cf718..1367b7e24bc7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java @@ -186,6 +186,10 @@ public class BubbleBarLayerView extends FrameLayout if (expandedView == null) { return; } + if (mExpandedBubble != null && mIsExpanded && b.getKey().equals(mExpandedBubble.getKey())) { + // Already showing this bubble, skip animating + return; + } if (mExpandedBubble != null && !b.getKey().equals(mExpandedBubble.getKey())) { removeView(mExpandedView); mExpandedView = null; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index b8aa1b189f7e..4b55fd0f5527 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -48,6 +48,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; +import android.os.Handler; import android.view.Display; import android.view.InsetsSourceControl; import android.view.InsetsState; @@ -75,6 +76,7 @@ import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget; import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.shared.animation.Interpolators; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition; import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition; import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition; @@ -116,6 +118,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange new PathInterpolator(0.2f, 0f, 0f, 1f); private static final Interpolator GROW_INTERPOLATOR = new PathInterpolator(0.45f, 0f, 0.5f, 1f); + @ShellMainThread + private final Handler mHandler; private int mDividerWindowWidth; private int mDividerInsets; @@ -166,7 +170,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange SplitLayoutHandler splitLayoutHandler, SplitWindowManager.ParentContainerCallbacks parentContainerCallbacks, DisplayController displayController, DisplayImeController displayImeController, - ShellTaskOrganizer taskOrganizer, int parallaxType) { + ShellTaskOrganizer taskOrganizer, int parallaxType, @ShellMainThread Handler handler) { + mHandler = handler; mContext = context.createConfigurationContext(configuration); mOrientation = configuration.orientation; mRotation = configuration.windowConfiguration.getRotation(); @@ -598,7 +603,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } void onStartDragging() { - mInteractionJankMonitor.begin(getDividerLeash(), mContext, CUJ_SPLIT_SCREEN_RESIZE); + mInteractionJankMonitor.begin(getDividerLeash(), mContext, mHandler, + CUJ_SPLIT_SCREEN_RESIZE); } void onDraggingCancelled() { @@ -756,7 +762,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange @Override public void onAnimationStart(Animator animation) { mInteractionJankMonitor.begin(getDividerLeash(), - mContext, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); + mContext, mHandler, CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 4adea233b734..722fe1f59388 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -444,7 +444,9 @@ public abstract class WMShellBaseModule { BackAnimationBackground backAnimationBackground, Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry, ShellCommandHandler shellCommandHandler, - Transitions transitions) { + Transitions transitions, + @ShellMainThread Handler handler + ) { if (BackAnimationController.IS_ENABLED) { return shellBackAnimationRegistry.map( (animations) -> @@ -457,7 +459,8 @@ public abstract class WMShellBaseModule { backAnimationBackground, animations, shellCommandHandler, - transitions)); + transitions, + handler)); } return Optional.empty(); } 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 05a70d8ae197..2f4d77baae97 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 @@ -116,8 +116,6 @@ 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.PooledWindowDecorViewHostSupplier; -import com.android.wm.shell.windowdecor.viewhost.ReusableWindowDecorViewHost; import com.android.wm.shell.windowdecor.viewhost.WindowDecorViewHostSupplier; import dagger.Binds; @@ -355,7 +353,8 @@ public abstract class WMShellModule { @ShellMainThread ShellExecutor mainExecutor, @ShellAnimationThread ShellExecutor animExecutor, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { return new FreeformTaskTransitionHandler( shellInit, transitions, @@ -365,7 +364,8 @@ public abstract class WMShellModule { mainExecutor, animExecutor, desktopModeTaskRepository, - interactionJankMonitor); + interactionJankMonitor, + handler); } @WMSingleton @@ -382,19 +382,8 @@ public abstract class WMShellModule { @WMSingleton @Provides static WindowDecorViewHostSupplier provideWindowDecorViewHostSupplier( - @NonNull Context context, - @ShellMainThread @NonNull CoroutineScope mainScope, - @NonNull ShellInit shellInit) { - if (DesktopModeStatus.canEnterDesktopMode(context) - && Flags.enableDesktopWindowingScvhCache()) { - final int maxPoolSize = DesktopModeStatus.getMaxTaskLimit(context); - final int preWarmSize = DesktopModeStatus.getWindowDecorPreWarmSize(); - return new PooledWindowDecorViewHostSupplier( - context, mainScope, shellInit, - ReusableWindowDecorViewHost.DefaultFactory.INSTANCE, maxPoolSize, preWarmSize); - } else { - return new DefaultWindowDecorViewHostSupplier(mainScope); - } + @ShellMainThread @NonNull CoroutineScope mainScope) { + return new DefaultWindowDecorViewHostSupplier(mainScope); } // @@ -617,6 +606,7 @@ public abstract class WMShellModule { RecentsTransitionHandler recentsTransitionHandler, MultiInstanceHelper multiInstanceHelper, @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler mainHandler, Optional<DesktopTasksLimiter> desktopTasksLimiter, Optional<RecentTasksController> recentTasksController, InteractionJankMonitor interactionJankMonitor) { @@ -629,7 +619,7 @@ public abstract class WMShellModule { dragToDesktopTransitionHandler, desktopModeTaskRepository, desktopModeLoggerTransitionObserver, launchAdjacentController, recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter, - recentTasksController.orElse(null), interactionJankMonitor); + recentTasksController.orElse(null), interactionJankMonitor, mainHandler); } @WMSingleton @@ -639,7 +629,8 @@ public abstract class WMShellModule { Transitions transitions, @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository, ShellTaskOrganizer shellTaskOrganizer, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { int maxTaskLimit = DesktopModeStatus.getMaxTaskLimit(context); if (!DesktopModeStatus.canEnterDesktopMode(context) || !ENABLE_DESKTOP_WINDOWING_TASK_LIMIT.isEnabled(context) @@ -653,7 +644,8 @@ public abstract class WMShellModule { shellTaskOrganizer, maxTaskLimit, interactionJankMonitor, - context) + context, + handler) ); } @@ -700,9 +692,10 @@ public abstract class WMShellModule { static ExitDesktopTaskTransitionHandler provideExitDesktopTaskTransitionHandler( Transitions transitions, Context context, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { return new ExitDesktopTaskTransitionHandler( - transitions, context, interactionJankMonitor); + transitions, context, interactionJankMonitor, handler); } @WMSingleton diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java index 037fbb235bd4..3a4764d45f2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java @@ -101,7 +101,8 @@ public abstract class Pip1Module { DisplayInsetsController displayInsetsController, TabletopModeController pipTabletopController, Optional<OneHandedController> oneHandedController, - @ShellMainThread ShellExecutor mainExecutor) { + @ShellMainThread ShellExecutor mainExecutor, + @ShellMainThread Handler handler) { return Optional.ofNullable(PipController.create( context, shellInit, shellCommandHandler, shellController, displayController, pipAnimationController, pipAppOpsListener, @@ -111,7 +112,7 @@ public abstract class Pip1Module { pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, displayInsetsController, pipTabletopController, oneHandedController, - mainExecutor)); + mainExecutor, handler)); } // Handler is used by Icon.loadDrawableAsync diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index e1d14e5e6209..b8ebbcdbfb9d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -35,6 +35,7 @@ import android.graphics.PointF import android.graphics.Rect import android.graphics.Region import android.os.Binder +import android.os.Handler import android.os.IBinder import android.os.SystemProperties import android.util.Size @@ -136,7 +137,8 @@ class DesktopTasksController( @ShellMainThread private val mainExecutor: ShellExecutor, private val desktopTasksLimiter: Optional<DesktopTasksLimiter>, private val recentTasksController: RecentTasksController?, - private val interactionJankMonitor: InteractionJankMonitor + private val interactionJankMonitor: InteractionJankMonitor, + @ShellMainThread private val handler: Handler, ) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler, @@ -392,7 +394,7 @@ class DesktopTasksController( taskSurface: SurfaceControl, ) { logV("startDragToDesktop taskId=%d", taskInfo.taskId) - interactionJankMonitor.begin(taskSurface, context, + interactionJankMonitor.begin(taskSurface, context, handler, CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD) dragToDesktopTransitionHandler.startDragToDesktopTransition( taskInfo.taskId, @@ -796,7 +798,7 @@ class DesktopTasksController( releaseVisualIndicator() if (!taskInfo.isResizeable && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(context)) { interactionJankMonitor.begin( - taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable" + taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_non_resizable" ) // reposition non-resizable app back to its original position before being dragged @@ -809,7 +811,7 @@ class DesktopTasksController( ) } else { interactionJankMonitor.begin( - taskSurface, context, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable" + taskSurface, context, handler, CUJ_DESKTOP_MODE_SNAP_RESIZE, "drag_resizable" ) snapToHalfScreen(taskInfo, taskSurface, currentDragBounds, position) } @@ -1608,7 +1610,7 @@ class DesktopTasksController( when (indicatorType) { IndicatorType.TO_DESKTOP_INDICATOR -> { // Start a new jank interaction for the drag release to desktop window animation. - interactionJankMonitor.begin(taskSurface, context, + interactionJankMonitor.begin(taskSurface, context, handler, CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_RELEASE, "to_desktop") finalizeDragToDesktop(taskInfo) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt index 597637d3fbfc..dae37f4926f5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.content.Context +import android.os.Handler import android.os.IBinder import android.view.SurfaceControl import android.view.WindowManager.TRANSIT_TO_BACK @@ -29,6 +30,7 @@ import com.android.internal.jank.InteractionJankMonitor import com.android.internal.protolog.ProtoLog import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.shared.annotations.ShellMainThread import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TransitionObserver @@ -45,7 +47,8 @@ class DesktopTasksLimiter ( private val shellTaskOrganizer: ShellTaskOrganizer, private val maxTasksLimit: Int, private val interactionJankMonitor: InteractionJankMonitor, - private val context: Context + private val context: Context, + @ShellMainThread private val handler: Handler, ) { private val minimizeTransitionObserver = MinimizeTransitionObserver() @VisibleForTesting @@ -125,7 +128,7 @@ class DesktopTasksLimiter ( if (mActiveTaskDetails != null && mActiveTaskDetails.transitionInfo != null) { // Begin minimize window CUJ instrumentation. interactionJankMonitor.begin( - mActiveTaskDetails.transitionInfo?.rootLeash, context, + mActiveTaskDetails.transitionInfo?.rootLeash, context, handler, CUJ_DESKTOP_MODE_MINIMIZE_WINDOW ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java index e87be520cc91..dedd44f3950a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java @@ -29,6 +29,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; +import android.os.Handler; import android.os.IBinder; import android.util.DisplayMetrics; import android.view.SurfaceControl; @@ -44,6 +45,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.Cuj; import com.android.internal.jank.InteractionJankMonitor; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; import com.android.wm.shell.transition.Transitions; @@ -63,6 +65,8 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH private final Context mContext; private final Transitions mTransitions; private final InteractionJankMonitor mInteractionJankMonitor; + @ShellMainThread + private final Handler mHandler; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback; private final Supplier<SurfaceControl.Transaction> mTransactionSupplier; @@ -71,20 +75,24 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH public ExitDesktopTaskTransitionHandler( Transitions transitions, Context context, - InteractionJankMonitor interactionJankMonitor - ) { - this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor); + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler + ) { + this(transitions, SurfaceControl.Transaction::new, context, interactionJankMonitor, + handler); } private ExitDesktopTaskTransitionHandler( Transitions transitions, Supplier<SurfaceControl.Transaction> supplier, Context context, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { mTransitions = transitions; mTransactionSupplier = supplier; mContext = context; mInteractionJankMonitor = interactionJankMonitor; + mHandler = handler; } /** @@ -154,7 +162,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH final SurfaceControl sc = change.getLeash(); final Rect endBounds = change.getEndAbsBounds(); mInteractionJankMonitor - .begin(sc, mContext, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE); + .begin(sc, mContext, mHandler, Cuj.CUJ_DESKTOP_MODE_EXIT_MODE); // Hide the first (fullscreen) frame because the animation will start from the freeform // size. startT.hide(sc) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java index 832e2d2bc77b..517e20910f6d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java @@ -28,6 +28,7 @@ import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; import android.graphics.Rect; +import android.os.Handler; import android.os.IBinder; import android.util.ArrayMap; import android.view.SurfaceControl; @@ -43,6 +44,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.windowdecor.WindowDecorViewModel; @@ -65,6 +67,8 @@ public class FreeformTaskTransitionHandler private final InteractionJankMonitor mInteractionJankMonitor; private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; + @ShellMainThread + private final Handler mHandler; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); @@ -79,7 +83,8 @@ public class FreeformTaskTransitionHandler ShellExecutor mainExecutor, ShellExecutor animExecutor, DesktopModeTaskRepository desktopModeTaskRepository, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { mTransitions = transitions; mContext = context; mWindowDecorViewModel = windowDecorViewModel; @@ -88,6 +93,7 @@ public class FreeformTaskTransitionHandler mInteractionJankMonitor = interactionJankMonitor; mMainExecutor = mainExecutor; mAnimExecutor = animExecutor; + mHandler = handler; if (Transitions.ENABLE_SHELL_TRANSITIONS) { shellInit.addInitCallback(this::onInit, this); } @@ -267,7 +273,7 @@ public class FreeformTaskTransitionHandler change.getTaskInfo().displayId) == 1) { // Starting the jank trace if closing the last window in desktop mode. mInteractionJankMonitor.begin( - sc, mContext, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE); + sc, mContext, mHandler, CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE); } animator.addListener( new AnimatorListenerAdapter() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 1827923aad90..15472ebc149b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -55,6 +55,7 @@ import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerCallback; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.shared.annotations.ExternalThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -203,7 +204,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, DisplayController displayController, DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener, InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger, - ShellExecutor mainExecutor, Handler mainHandler) { + ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) { OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil(); OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context); OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor); @@ -217,7 +218,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>, mainExecutor); OneHandedDisplayAreaOrganizer organizer = new OneHandedDisplayAreaOrganizer( context, displayLayout, settingsUtil, animationController, tutorialHandler, - jankMonitor, mainExecutor); + jankMonitor, mainExecutor, mainHandler); OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger); return new OneHandedController(context, shellInit, shellCommandHandler, shellController, displayController, organizer, touchHandler, tutorialHandler, settingsUtil, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index d157ca837608..95e633d0b5ec 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -23,6 +23,7 @@ import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSI import android.content.Context; import android.graphics.Rect; +import android.os.Handler; import android.os.SystemProperties; import android.text.TextUtils; import android.util.ArrayMap; @@ -42,6 +43,7 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.shared.annotations.ShellMainThread; import java.io.PrintWriter; import java.util.ArrayList; @@ -70,6 +72,8 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { private final OneHandedSettingsUtil mOneHandedSettingsUtil; private final InteractionJankMonitor mJankMonitor; private final Context mContext; + @ShellMainThread + private final Handler mHandler; private boolean mIsReady; private float mLastVisualOffset = 0; @@ -136,9 +140,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { OneHandedAnimationController animationController, OneHandedTutorialHandler tutorialHandler, InteractionJankMonitor jankMonitor, - ShellExecutor mainExecutor) { + ShellExecutor mainExecutor, + @ShellMainThread Handler handler) { super(mainExecutor); mContext = context; + mHandler = handler; setDisplayLayout(displayLayout); mOneHandedSettingsUtil = oneHandedSettingsUtil; mAnimationController = animationController; @@ -333,7 +339,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { getDisplayAreaTokenMap().entrySet().iterator().next(); final InteractionJankMonitor.Configuration.Builder builder = InteractionJankMonitor.Configuration.Builder.withSurface( - cujType, mContext, firstEntry.getValue()); + cujType, mContext, firstEntry.getValue(), mHandler); if (!TextUtils.isEmpty(tag)) { builder.setTag(tag); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index deb7691f2d4d..af6844262771 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -44,6 +44,7 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; +import android.os.Handler; import android.os.RemoteException; import android.os.SystemProperties; import android.util.Pair; @@ -93,6 +94,7 @@ import com.android.wm.shell.pip.PipTaskOrganizer; import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.protolog.ShellProtoLogGroup; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.sysui.ConfigurationChangeListener; import com.android.wm.shell.sysui.KeyguardChangeListener; import com.android.wm.shell.sysui.ShellCommandHandler; @@ -146,6 +148,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb private Optional<OneHandedController> mOneHandedController; private final ShellCommandHandler mShellCommandHandler; private final ShellController mShellController; + @ShellMainThread + private final Handler mHandler; protected final PipImpl mImpl; private final Rect mTmpInsetBounds = new Rect(); @@ -405,7 +409,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb DisplayInsetsController displayInsetsController, TabletopModeController pipTabletopController, Optional<OneHandedController> oneHandedController, - ShellExecutor mainExecutor) { + ShellExecutor mainExecutor, + Handler handler) { if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) { ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Device doesn't support Pip feature", TAG); @@ -418,7 +423,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, - displayInsetsController, pipTabletopController, oneHandedController, mainExecutor) + displayInsetsController, pipTabletopController, oneHandedController, mainExecutor, + handler) .mImpl; } @@ -446,11 +452,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb DisplayInsetsController displayInsetsController, TabletopModeController tabletopModeController, Optional<OneHandedController> oneHandedController, - ShellExecutor mainExecutor + ShellExecutor mainExecutor, + @ShellMainThread Handler handler ) { mContext = context; mShellCommandHandler = shellCommandHandler; mShellController = shellController; + mHandler = handler; mImpl = new PipImpl(); mWindowManagerShellWrapper = windowManagerShellWrapper; mDisplayController = displayController; @@ -1047,7 +1055,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb // Begin InteractionJankMonitor with PIP transition CUJs final InteractionJankMonitor.Configuration.Builder builder = InteractionJankMonitor.Configuration.Builder.withSurface( - CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl()) + CUJ_PIP_TRANSITION, mContext, mPipTaskOrganizer.getSurfaceControl(), + mHandler) .setTag(getTransitionTag(direction)) .setTimeout(2000); InteractionJankMonitor.getInstance().begin(builder); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 4ba84eeb2e6d..e8eb10c984af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -1654,7 +1654,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext, mRootTaskInfo.configuration, this, mParentContainerCallbacks, mDisplayController, mDisplayImeController, mTaskOrganizer, - PARALLAX_ALIGN_CENTER /* parallaxType */); + PARALLAX_ALIGN_CENTER /* parallaxType */, mMainHandler); mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout); } @@ -2456,6 +2456,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, final StageChangeRecord record = new StageChangeRecord(); final int transitType = info.getType(); TransitionInfo.Change pipChange = null; + int closingSplitTaskId = -1; for (int iC = 0; iC < info.getChanges().size(); ++iC) { final TransitionInfo.Change change = info.getChanges().get(iC); if (change.getMode() == TRANSIT_CHANGE @@ -2516,21 +2517,31 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, + " with " + taskInfo.taskId + " before startAnimation()."); } } + if (isClosingType(change.getMode()) && + getStageOfTask(change.getTaskInfo().taskId) != STAGE_TYPE_UNDEFINED) { + // If either one of the 2 stages is closing we're assuming we'll break split + closingSplitTaskId = change.getTaskInfo().taskId; + } } if (pipChange != null) { TransitionInfo.Change pipReplacingChange = getPipReplacingChange(info, pipChange, mMainStage.mRootTaskInfo.taskId, mSideStage.mRootTaskInfo.taskId, getSplitItemStage(pipChange.getLastParent())); - if (pipReplacingChange != null) { + boolean keepSplitWithPip = pipReplacingChange != null && closingSplitTaskId == -1; + if (keepSplitWithPip) { // Set an enter transition for when startAnimation gets called again mSplitTransitions.setEnterTransition(transition, /*remoteTransition*/ null, TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, /*resizeAnim*/ false); + } else { + int finalClosingTaskId = closingSplitTaskId; + mRecentTasks.ifPresent(recentTasks -> + recentTasks.removeSplitPair(finalClosingTaskId)); + logExit(EXIT_REASON_FULLSCREEN_REQUEST); } mMixedHandler.animatePendingEnterPipFromSplit(transition, info, - startTransaction, finishTransaction, finishCallback, - pipReplacingChange != null); + startTransaction, finishTransaction, finishCallback, keepSplitWithPip); notifySplitAnimationFinished(); return true; } @@ -2821,8 +2832,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } callbackWct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token, false); mWindowDecorViewModel.ifPresent(viewModel -> { - viewModel.onTaskInfoChanged(finalMainChild.getTaskInfo()); - viewModel.onTaskInfoChanged(finalSideChild.getTaskInfo()); + if (finalMainChild != null) { + viewModel.onTaskInfoChanged(finalMainChild.getTaskInfo()); + } + if (finalSideChild != null) { + viewModel.onTaskInfoChanged(finalSideChild.getTaskInfo()); + } }); mPausingTasks.clear(); }); 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 b14283f878a3..caac2f6bb03e 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 @@ -111,6 +111,7 @@ import com.android.wm.shell.desktopmode.DesktopTasksLimiter; import com.android.wm.shell.desktopmode.DesktopWallpaperActivity; import com.android.wm.shell.freeform.FreeformTaskTransitionStarter; import com.android.wm.shell.shared.annotations.ShellBackgroundThread; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.shared.desktopmode.DesktopModeFlags; import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource; @@ -154,7 +155,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private final ShellTaskOrganizer mTaskOrganizer; private final ShellController mShellController; private final Context mContext; - private final Handler mMainHandler; + private final @ShellMainThread Handler mMainHandler; private final @ShellBackgroundThread ShellExecutor mBgExecutor; private final Choreographer mMainChoreographer; private final DisplayController mDisplayController; @@ -214,7 +215,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { public DesktopModeWindowDecorViewModel( Context context, ShellExecutor shellExecutor, - Handler mainHandler, + @ShellMainThread Handler mainHandler, Choreographer mainChoreographer, @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, @@ -270,7 +271,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { DesktopModeWindowDecorViewModel( Context context, ShellExecutor shellExecutor, - Handler mainHandler, + @ShellMainThread Handler mainHandler, Choreographer mainChoreographer, @ShellBackgroundThread ShellExecutor bgExecutor, ShellInit shellInit, @@ -495,7 +496,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } mInteractionJankMonitor.begin( - decoration.mTaskSurface, mContext, Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source); + decoration.mTaskSurface, mContext, mMainHandler, + Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, source); mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo); decoration.closeHandleMenu(); decoration.closeMaximizeMenu(); @@ -512,7 +514,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { Toast.makeText(mContext, R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show(); } else { - mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, + mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler, Cuj.CUJ_DESKTOP_MODE_SNAP_RESIZE, "maximize_menu_resizable"); mDesktopTasksController.snapToHalfScreen( decoration.mTaskInfo, @@ -548,7 +550,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } final WindowContainerTransaction wct = new WindowContainerTransaction(); - mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, + mInteractionJankMonitor.begin(decoration.mTaskSurface, mContext, mMainHandler, CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU); // App sometimes draws before the insets from WindowDecoration#relayout have // been added, so they must be added here @@ -860,6 +862,15 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { handleCaptionThroughStatusBar(e, decoration); final boolean wasDragging = mIsDragging; updateDragStatus(e.getActionMasked()); + final boolean upOrCancel = e.getActionMasked() == ACTION_UP + || e.getActionMasked() == ACTION_CANCEL; + if (wasDragging && upOrCancel) { + // When finishing a drag the event will be consumed, which means the pressed + // state of the App Handle must be manually reset to scale its drawable back to + // its original shape. This is necessary for drag gestures of the Handle that + // result in a cancellation (dragging back to the top). + v.setPressed(false); + } // Only prevent onClick from receiving this event if it's a drag. return wasDragging; } @@ -1180,8 +1191,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { : SPLIT_POSITION_TOP_OR_LEFT; final RunningTaskInfo oppositeTaskInfo = mSplitScreenController.getTaskInfo(oppositePosition); - mWindowDecorByTaskId.get(oppositeTaskInfo.taskId) - .disposeStatusBarInputLayer(); + if (oppositeTaskInfo != null) { + mWindowDecorByTaskId.get(oppositeTaskInfo.taskId) + .disposeStatusBarInputLayer(); + } } } mMoveToDesktopAnimator = null; @@ -1378,7 +1391,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragStartListener, mTransitions, mInteractionJankMonitor, - mTransactionFactory); + mTransactionFactory, + mMainHandler); windowDecoration.setTaskDragResizer(taskPositioner); final DesktopModeTouchEventListener touchEventListener = @@ -1602,7 +1616,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { DragPositioningCallbackUtility.DragStartListener dragStartListener, Transitions transitions, InteractionJankMonitor interactionJankMonitor, - Supplier<SurfaceControl.Transaction> transactionFactory) { + Supplier<SurfaceControl.Transaction> transactionFactory, + Handler handler) { final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled() ? new VeiledResizeTaskPositioner( taskOrganizer, @@ -1610,7 +1625,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { displayController, dragStartListener, transitions, - interactionJankMonitor) + interactionJankMonitor, + handler) : new FluidResizeTaskPositioner( taskOrganizer, transitions, 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 b1fc55f604d2..16036bee75b3 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 @@ -24,6 +24,8 @@ import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; import static android.view.MotionEvent.ACTION_CANCEL; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION; +import static android.window.flags.DesktopModeFlags.ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS; import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT; import static com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON; @@ -604,13 +606,13 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin // their custom content. relayoutParams.mInputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_SPY; } else { - if (Flags.enableCaptionCompatInsetForceConsumption()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION.isEnabled()) { // Force-consume the caption bar insets when the app tries to hide the caption. // This improves app compatibility of immersive apps. relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING; } } - if (Flags.enableCaptionCompatInsetForceConsumptionAlways()) { + if (ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS.isEnabled()) { // Always force-consume the caption bar insets for maximum app compatibility, // including non-immersive apps that just don't handle caption insets properly. relayoutParams.mInsetSourceFlags |= FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 599815530f63..6f3f41191485 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -24,6 +24,7 @@ import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; +import android.os.Handler; import android.os.IBinder; import android.view.Surface; import android.view.SurfaceControl; @@ -37,6 +38,7 @@ import androidx.annotation.Nullable; import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.shared.annotations.ShellMainThread; import com.android.wm.shell.transition.Transitions; import java.util.function.Supplier; @@ -63,14 +65,17 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T private int mCtrlType; private boolean mIsResizingOrAnimatingResize; @Surface.Rotation private int mRotation; + @ShellMainThread + private final Handler mHandler; public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, - Transitions transitions, InteractionJankMonitor interactionJankMonitor) { + Transitions transitions, InteractionJankMonitor interactionJankMonitor, + @ShellMainThread Handler handler) { this(taskOrganizer, windowDecoration, displayController, dragStartListener, - SurfaceControl.Transaction::new, transitions, interactionJankMonitor); + SurfaceControl.Transaction::new, transitions, interactionJankMonitor, handler); } public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, @@ -78,7 +83,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T DisplayController displayController, DragPositioningCallbackUtility.DragStartListener dragStartListener, Supplier<SurfaceControl.Transaction> supplier, Transitions transitions, - InteractionJankMonitor interactionJankMonitor) { + InteractionJankMonitor interactionJankMonitor, @ShellMainThread Handler handler) { mDesktopWindowDecoration = windowDecoration; mTaskOrganizer = taskOrganizer; mDisplayController = displayController; @@ -86,6 +91,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T mTransactionSupplier = supplier; mTransitions = transitions; mInteractionJankMonitor = interactionJankMonitor; + mHandler = handler; } @Override @@ -97,7 +103,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T if (isResizing()) { // Capture CUJ for re-sizing window in DW mode. mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface, - mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_RESIZE_WINDOW); + mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_RESIZE_WINDOW); if (!mDesktopWindowDecoration.mTaskInfo.isFocused) { WindowContainerTransaction wct = new WindowContainerTransaction(); wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true); @@ -131,7 +137,7 @@ public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.T } else if (mCtrlType == CTRL_TYPE_UNDEFINED) { // Begin window drag CUJ instrumentation only when drag position moves. mInteractionJankMonitor.begin(mDesktopWindowDecoration.mTaskSurface, - mDesktopWindowDecoration.mContext, CUJ_DESKTOP_MODE_DRAG_WINDOW); + mDesktopWindowDecoration.mContext, mHandler, CUJ_DESKTOP_MODE_DRAG_WINDOW); final SurfaceControl.Transaction t = mTransactionSupplier.get(); DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration, mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); 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 index 5156e47cfd13..139e6790b744 100644 --- 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 @@ -19,33 +19,51 @@ 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 [SurfaceControlViewHostAdapter]. + * A default implementation of [WindowDecorViewHost] backed by a [SurfaceControlViewHost]. * - * It supports asynchronously updating the view hierarchy using [updateViewAsync], in which + * 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( - context: Context, + private val context: Context, @ShellMainThread private val mainScope: CoroutineScope, - display: Display, - @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter = - SurfaceControlViewHostAdapter(context, display) + 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() = viewHostAdapter.rootSurface + get() = rootSurface override fun updateView( view: View, @@ -74,7 +92,8 @@ class DefaultWindowDecorViewHost( override fun release(t: SurfaceControl.Transaction) { clearCurrentUpdateJob() - viewHostAdapter.release(t) + viewHost?.release() + t.remove(rootSurface) } private fun updateViewHost( @@ -83,15 +102,45 @@ class DefaultWindowDecorViewHost( configuration: Configuration, onDrawTransaction: SurfaceControl.Transaction? ) { - viewHostAdapter.prepareViewHost(configuration) + 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 { - viewHostAdapter.applyTransactionOnDraw(it) + 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() } - viewHostAdapter.updateView(view, attrs) + 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/PooledWindowDecorViewHostSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt deleted file mode 100644 index b04188fa82a8..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.os.Trace -import android.util.Pools -import android.view.Display -import android.view.SurfaceControl -import com.android.wm.shell.shared.annotations.ShellMainThread -import com.android.wm.shell.sysui.ShellInit -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch - -/** - * A [WindowDecorViewHostSupplier] backed by a pool to allow recycling view hosts which may be - * expensive to recreate for each new/updated window decoration. - * - * Callers can obtain [ReusableWindowDecorViewHost] using [acquire], which will return a pooled - * object if available, or create a new instance and return it if needed. When done using a - * [ReusableWindowDecorViewHost], it must be released using [release] to allow it to be sent back - * into the pool and reused later on. - * - * This class also supports pre-warming [ReusableWindowDecorViewHost] instances, which will be put - * into the pool immediately after creation. - */ -class PooledWindowDecorViewHostSupplier( - private val context: Context, - @ShellMainThread private val mainScope: CoroutineScope, - shellInit: ShellInit, - private val viewHostFactory: ReusableWindowDecorViewHost.Factory = - ReusableWindowDecorViewHost.DefaultFactory, - maxPoolSize: Int, - private val preWarmSize: Int, -) : WindowDecorViewHostSupplier<ReusableWindowDecorViewHost> { - - private val pool: Pools.Pool<ReusableWindowDecorViewHost> = Pools.SynchronizedPool(maxPoolSize) - private var nextDecorViewHostId = 0 - - init { - require(preWarmSize <= maxPoolSize) { "Pre-warm size should not exceed pool size" } - shellInit.addInitCallback(this::onShellInit, this) - } - - private fun onShellInit() { - if (preWarmSize <= 0) { - return - } - preWarmViewHosts(preWarmSize) - } - - private fun preWarmViewHosts(preWarmSize: Int) { - mainScope.launch { - // Applying isn't needed, as the surface was never actually shown. - val t = SurfaceControl.Transaction() - repeat(preWarmSize) { - val warmedViewHost = create(context, context.display).apply { - warmUp() - } - // Put the warmed view host in the pool by releasing it. - release(warmedViewHost, t) - } - } - } - - override fun acquire(context: Context, display: Display): ReusableWindowDecorViewHost { - val reusedDecorViewHost = pool.acquire() - if (reusedDecorViewHost != null) { - return reusedDecorViewHost - } - Trace.beginSection("WindowDecorViewHostPool#acquire-new") - val newDecorViewHost = create(context, display) - Trace.endSection() - return newDecorViewHost - } - - override fun release(viewHost: ReusableWindowDecorViewHost, t: SurfaceControl.Transaction) { - val cached = pool.release(viewHost) - if (!cached) { - viewHost.release(t) - } - } - - private fun create(context: Context, display: Display): ReusableWindowDecorViewHost { - return viewHostFactory.create( - context, - mainScope, - display, - nextDecorViewHostId++ - ) - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt deleted file mode 100644 index 64536d1a7897..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt +++ /dev/null @@ -1,161 +0,0 @@ -/* - * 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.graphics.PixelFormat -import android.os.Trace -import android.view.Display -import android.view.SurfaceControl -import android.view.SurfaceControlViewHost -import android.view.View -import android.view.WindowManager -import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE -import android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH -import android.view.WindowManager.LayoutParams.TYPE_APPLICATION -import android.widget.FrameLayout -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 - -/** - * An implementation of [WindowDecorViewHost] that supports: - * 1) Replacing the root [View], meaning [WindowDecorViewHost.updateView] maybe be - * called with different [View] instances. This is useful when reusing [WindowDecorViewHost]s - * instances for vastly different view hierarchies, such as Desktop Windowing's App Handles and - * App Headers. - * 2) Pre-warming of the underlying [SurfaceControlViewHost]s. Useful because their creation and - * first root view assignment are expensive, which is undesirable in latency-sensitive code - * paths like during a shell transition. - */ -class ReusableWindowDecorViewHost( - private val context: Context, - @ShellMainThread private val mainScope: CoroutineScope, - display: Display, - val id: Int, - @VisibleForTesting val viewHostAdapter: SurfaceControlViewHostAdapter = - SurfaceControlViewHostAdapter(context, display) -) : WindowDecorViewHost, Warmable { - - @VisibleForTesting - val rootView = FrameLayout(context) - - private var currentUpdateJob: Job? = null - - override val surfaceControl: SurfaceControl - get() = viewHostAdapter.rootSurface - - override fun warmUp() { - if (viewHostAdapter.isInitialized()) { - // Already warmed up. - return - } - Trace.beginSection("$TAG#warmUp") - viewHostAdapter.prepareViewHost(context.resources.configuration) - viewHostAdapter.updateView( - rootView, - WindowManager.LayoutParams( - 0 /* width*/, - 0 /* height */, - TYPE_APPLICATION, - FLAG_NOT_FOCUSABLE or FLAG_SPLIT_TOUCH, - PixelFormat.TRANSPARENT - ).apply { - setTitle("View root of $TAG#$id") - setTrustedOverlay() - } - ) - Trace.endSection() - } - - override fun updateView( - view: View, - attrs: WindowManager.LayoutParams, - configuration: Configuration, - onDrawTransaction: SurfaceControl.Transaction? - ) { - clearCurrentUpdateJob() - updateViewHost(view, attrs, configuration, onDrawTransaction) - } - - override fun updateViewAsync( - view: View, - attrs: WindowManager.LayoutParams, - configuration: Configuration - ) { - clearCurrentUpdateJob() - currentUpdateJob = mainScope.launch { - updateViewHost(view, attrs, configuration, onDrawTransaction = null) - } - } - - override fun release(t: SurfaceControl.Transaction) { - clearCurrentUpdateJob() - viewHostAdapter.release(t) - } - - private fun updateViewHost( - view: View, - attrs: WindowManager.LayoutParams, - configuration: Configuration, - onDrawTransaction: SurfaceControl.Transaction? - ) { - viewHostAdapter.prepareViewHost(configuration) - onDrawTransaction?.let { - viewHostAdapter.applyTransactionOnDraw(it) - } - rootView.removeAllViews() - rootView.addView(view) - viewHostAdapter.updateView(rootView, attrs) - } - - private fun clearCurrentUpdateJob() { - currentUpdateJob?.cancel() - currentUpdateJob = null - } - - interface Factory { - fun create( - context: Context, - @ShellMainThread mainScope: CoroutineScope, - display: Display, - id: Int - ): ReusableWindowDecorViewHost - } - - object DefaultFactory : Factory { - override fun create( - context: Context, - @ShellMainThread mainScope: CoroutineScope, - display: Display, - id: Int - ): ReusableWindowDecorViewHost { - return ReusableWindowDecorViewHost( - context, - mainScope, - display, - id - ) - } - } - - companion object { - private const val TAG = "ReusableWindowDecorViewHost" - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt deleted file mode 100644 index a54c9ba67cf8..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.AttachedSurfaceControl -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 -typealias SurfaceControlViewHostFactory = - (Context, Display, WindowlessWindowManager, String) -> SurfaceControlViewHost - -/** - * Adapter for a [SurfaceControlViewHost] and its backing [SurfaceControl]. - * - * 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], only - * its properties and binding may be changed, its children views may be added, removed or changed - * and its [WindowManager.LayoutParams] may be changed. - */ -class SurfaceControlViewHostAdapter( - private val context: Context, - private val display: Display, - private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory = { c, d, wwm, s -> - SurfaceControlViewHost(c, d, wwm, s) - } -) { - val rootSurface: SurfaceControl = SurfaceControl.Builder() - .setName("SurfaceControlViewHostAdapter surface") - .setContainerLayer() - .setCallsite("SurfaceControlViewHostAdapter#init") - .build() - - private var wwm: WindowlessWindowManager? = null - @VisibleForTesting - var viewHost: SurfaceControlViewHost? = null - - /** Initialize the [SurfaceControlViewHost] if needed. */ - fun prepareViewHost(configuration: Configuration) { - if (wwm == null) { - wwm = WindowlessWindowManager(configuration, rootSurface, null) - } - requireWindowlessWindowManager().setConfiguration(configuration) - if (viewHost == null) { - viewHost = surfaceControlViewHostFactory.invoke( - context, - display, - requireWindowlessWindowManager(), - "SurfaceControlViewHostAdapter#prepareViewHost" - ) - } - } - - /** - * Request to apply the transaction atomically with the next draw of the view hierarchy. - * See [AttachedSurfaceControl.applyTransactionOnDraw]. - */ - fun applyTransactionOnDraw(t: SurfaceControl.Transaction) { - requireViewHost().rootSurfaceControl.applyTransactionOnDraw(t) - } - - /** Update the view hierarchy of the view host. */ - fun updateView(view: View, attrs: WindowManager.LayoutParams) { - if (requireViewHost().view == null) { - Trace.beginSection("SurfaceControlViewHostAdapter#updateView-setView") - requireViewHost().setView(view, attrs) - Trace.endSection() - } else { - check(requireViewHost().view == view) { "Changing view is not allowed" } - Trace.beginSection("SurfaceControlViewHostAdapter#updateView-relayout") - requireViewHost().relayout(attrs) - Trace.endSection() - } - } - - /** Release the view host and remove the backing surface. */ - fun release(t: SurfaceControl.Transaction) { - viewHost?.release() - t.remove(rootSurface) - } - - /** Whether the view host has had a view hierarchy set. */ - fun isInitialized(): Boolean = viewHost?.view != 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/Warmable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt deleted file mode 100644 index 0df9bfa2ee78..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 - -/** - * An interface for an object that can be warmed up before it's needed. - */ -interface Warmable { - fun warmUp() -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index b53ea3837178..227060d15640 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -137,6 +137,8 @@ public class BackAnimationControllerTest extends ShellTestCase { private Transitions mTransitions; @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer; + @Mock + private Handler mHandler; private BackAnimationController mController; private TestableContentResolver mContentResolver; @@ -161,13 +163,14 @@ public class BackAnimationControllerTest extends ShellTestCase { mTestableLooper = TestableLooper.get(this); mShellInit = spy(new ShellInit(mShellExecutor)); mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext, - mAnimationBackground, mRootTaskDisplayAreaOrganizer); - mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground); + mAnimationBackground, mRootTaskDisplayAreaOrganizer, mHandler); + mCrossTaskBackAnimation = new CrossTaskBackAnimation(mContext, mAnimationBackground, + mHandler); mShellBackAnimationRegistry = new ShellBackAnimationRegistry(mDefaultCrossActivityBackAnimation, mCrossTaskBackAnimation, /* dialogCloseAnimation= */ null, new CustomCrossActivityBackAnimation(mContext, mAnimationBackground, - mRootTaskDisplayAreaOrganizer), + mRootTaskDisplayAreaOrganizer, mHandler), /* defaultBackToHomeAnimation= */ null); mController = new BackAnimationController( @@ -181,7 +184,8 @@ public class BackAnimationControllerTest extends ShellTestCase { mAnimationBackground, mShellBackAnimationRegistry, mShellCommandHandler, - mTransitions); + mTransitions, + mHandler); mShellInit.init(); mShellExecutor.flushAll(); mTouchableRegion = new Rect(0, 0, 100, 100); @@ -344,7 +348,8 @@ public class BackAnimationControllerTest extends ShellTestCase { mAnimationBackground, mShellBackAnimationRegistry, mShellCommandHandler, - mTransitions); + mTransitions, + mHandler); shellInit.init(); registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME); @@ -898,7 +903,8 @@ public class BackAnimationControllerTest extends ShellTestCase { new BackAnimationRunner( mAnimatorCallback, mBackAnimationRunner, - mContext)); + mContext, + mHandler)); } private void unregisterAnimation(int type) { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt index 080ad901c656..5b5ef6f48789 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt @@ -22,6 +22,7 @@ import android.app.WindowConfiguration import android.graphics.Color import android.graphics.Point import android.graphics.Rect +import android.os.Handler import android.os.RemoteException import android.testing.AndroidTestingRunner import android.testing.TestableLooper @@ -66,6 +67,7 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() { @Mock private lateinit var transitionAnimation: TransitionAnimation @Mock private lateinit var appCompatTaskInfo: AppCompatTaskInfo @Mock private lateinit var transaction: Transaction + @Mock private lateinit var handler: Handler private lateinit var customCrossActivityBackAnimation: CustomCrossActivityBackAnimation private lateinit var customAnimationLoader: CustomAnimationLoader @@ -80,7 +82,8 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() { backAnimationBackground, rootTaskDisplayAreaOrganizer, transaction, - customAnimationLoader + customAnimationLoader, + handler, ) whenever(transitionAnimation.loadAppTransitionAnimation(eq(PACKAGE_NAME), eq(OPEN_RES_ID))) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java index f5847cc27071..cf69704a0470 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.verify; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.Handler; import android.os.SystemClock; import android.provider.DeviceConfig; import android.view.InputDevice; @@ -55,6 +56,7 @@ public class DividerViewTest extends ShellTestCase { private @Mock DisplayController mDisplayController; private @Mock DisplayImeController mDisplayImeController; private @Mock ShellTaskOrganizer mTaskOrganizer; + private @Mock Handler mHandler; private SplitLayout mSplitLayout; private DividerView mDividerView; @@ -65,7 +67,7 @@ public class DividerViewTest extends ShellTestCase { Configuration configuration = getConfiguration(); mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration, mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController, - mTaskOrganizer, SplitLayout.PARALLAX_NONE); + mTaskOrganizer, SplitLayout.PARALLAX_NONE, mHandler); SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager", mContext, configuration, mCallbacks); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java index 82b3a7de521b..177e47a342f6 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager; import android.content.res.Configuration; import android.graphics.Rect; +import android.os.Handler; import android.window.WindowContainerTransaction; import androidx.test.annotation.UiThreadTest; @@ -65,6 +66,7 @@ public class SplitLayoutTests extends ShellTestCase { @Mock DisplayImeController mDisplayImeController; @Mock ShellTaskOrganizer mTaskOrganizer; @Mock WindowContainerTransaction mWct; + @Mock Handler mHandler; @Captor ArgumentCaptor<Runnable> mRunnableCaptor; private SplitLayout mSplitLayout; @@ -80,7 +82,8 @@ public class SplitLayoutTests extends ShellTestCase { mDisplayController, mDisplayImeController, mTaskOrganizer, - SplitLayout.PARALLAX_NONE)); + SplitLayout.PARALLAX_NONE, + mHandler)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index ccbcabc9b1f5..8f20841e76b3 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -40,6 +40,7 @@ import android.graphics.Point import android.graphics.PointF import android.graphics.Rect import android.os.Binder +import android.os.Handler import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule @@ -181,6 +182,7 @@ class DesktopTasksControllerTest : ShellTestCase() { private lateinit var mockInteractionJankMonitor: InteractionJankMonitor @Mock private lateinit var mockSurface: SurfaceControl @Mock private lateinit var taskbarDesktopTaskListener: TaskbarDesktopTaskListener + @Mock private lateinit var mockHandler: Handler private lateinit var mockitoSession: StaticMockitoSession private lateinit var controller: DesktopTasksController @@ -221,7 +223,8 @@ class DesktopTasksControllerTest : ShellTestCase() { shellTaskOrganizer, MAX_TASK_LIMIT, mockInteractionJankMonitor, - mContext) + mContext, + mockHandler) whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks } whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() } @@ -274,7 +277,9 @@ class DesktopTasksControllerTest : ShellTestCase() { shellExecutor, Optional.of(desktopTasksLimiter), recentTasksController, - mockInteractionJankMonitor) + mockInteractionJankMonitor, + mockHandler, + ) } @After diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt index 2d0e428c45cb..61d03cac035c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.desktopmode import android.app.ActivityManager.RunningTaskInfo import android.os.Binder +import android.os.Handler import android.platform.test.flag.junit.SetFlagsRule import android.testing.AndroidTestingRunner import android.view.Display.DEFAULT_DISPLAY @@ -70,6 +71,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { @Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer @Mock lateinit var transitions: Transitions @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock lateinit var handler: Handler private lateinit var mockitoSession: StaticMockitoSession private lateinit var desktopTasksLimiter: DesktopTasksLimiter @@ -85,7 +87,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { desktopTasksLimiter = DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT, - interactionJankMonitor, mContext) + interactionJankMonitor, mContext, handler) } @After @@ -97,7 +99,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun createDesktopTasksLimiter_withZeroLimit_shouldThrow() { assertFailsWith<IllegalArgumentException> { DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, 0, - interactionJankMonitor, mContext) + interactionJankMonitor, mContext, handler) } } @@ -105,7 +107,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun createDesktopTasksLimiter_withNegativeLimit_shouldThrow() { assertFailsWith<IllegalArgumentException> { DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, -5, - interactionJankMonitor, mContext) + interactionJankMonitor, mContext, handler) } } @@ -334,7 +336,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { fun getTaskToMinimizeIfNeeded_tasksAboveLimit_otherLimit_returnsBackTask() { desktopTasksLimiter = DesktopTasksLimiter(transitions, desktopTaskRepo, shellTaskOrganizer, MAX_TASK_LIMIT2, - interactionJankMonitor, mContext) + interactionJankMonitor, mContext, handler) val tasks = (1..MAX_TASK_LIMIT2 + 1).map { setUpFreeformTask() } val minimizedTask = desktopTasksLimiter.getTaskToMinimizeIfNeeded( @@ -375,6 +377,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { verify(interactionJankMonitor).begin( any(), eq(mContext), + eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) desktopTasksLimiter.getTransitionObserver().onTransitionFinished( @@ -403,7 +406,9 @@ class DesktopTasksLimiterTest : ShellTestCase() { verify(interactionJankMonitor).begin( any(), eq(mContext), - eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) + eq(handler), + eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW), + ) desktopTasksLimiter.getTransitionObserver().onTransitionFinished( transition, @@ -432,6 +437,7 @@ class DesktopTasksLimiterTest : ShellTestCase() { verify(interactionJankMonitor).begin( any(), eq(mContext), + eq(handler), eq(CUJ_DESKTOP_MODE_MINIMIZE_WINDOW)) desktopTasksLimiter.getTransitionObserver().onTransitionMerged( diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java index e0463b41ad20..fefa933c5208 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java @@ -34,6 +34,7 @@ import android.app.WindowConfiguration; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; +import android.os.Handler; import android.os.IBinder; import android.util.DisplayMetrics; import android.view.SurfaceControl; @@ -81,6 +82,8 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase { Transitions.TransitionFinishCallback mTransitionFinishCallback; @Mock ShellExecutor mExecutor; + @Mock + Handler mHandler; private Point mPoint; private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler; @@ -97,7 +100,7 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase { .thenReturn(getContext().getResources().getDisplayMetrics()); mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions, - mContext, mInteractionJankMonitor); + mContext, mInteractionJankMonitor, mHandler); mPoint = new Point(0, 0); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java index 9c7f7237871a..9146906b6385 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.graphics.Rect; import android.os.Binder; +import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Display; @@ -100,6 +101,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { OneHandedSettingsUtil mMockSettingsUitl; @Mock InteractionJankMonitor mJankMonitor; + @Mock + Handler mMockHandler; List<DisplayAreaAppearedInfo> mDisplayAreaAppearedInfoList = new ArrayList<>(); @@ -142,7 +145,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockAnimationController, mTutorialHandler, mJankMonitor, - mMockShellMainExecutor)); + mMockShellMainExecutor, + mMockHandler)); for (int i = 0; i < DISPLAYAREA_INFO_COUNT; i++) { mDisplayAreaAppearedInfoList.add(getDummyDisplayAreaInfo()); @@ -429,7 +433,8 @@ public class OneHandedDisplayAreaOrganizerTest extends OneHandedTestCase { mMockAnimationController, mTutorialHandler, mJankMonitor, - mMockShellMainExecutor)); + mMockShellMainExecutor, + mMockHandler)); assertThat(testSpiedDisplayAreaOrganizer.isReady()).isFalse(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index f3944d5ac352..96003515a485 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -40,6 +40,7 @@ import android.content.res.Configuration; import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -115,6 +116,7 @@ public class PipControllerTest extends ShellTestCase { @Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder; @Mock private DisplayInsetsController mMockDisplayInsetsController; @Mock private TabletopModeController mMockTabletopModeController; + @Mock private Handler mMockHandler; @Mock private DisplayLayout mMockDisplayLayout1; @Mock private DisplayLayout mMockDisplayLayout2; @@ -138,7 +140,7 @@ public class PipControllerTest extends ShellTestCase { mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockTaskStackListener, mMockPipParamsChangedForwarder, mMockDisplayInsetsController, mMockTabletopModeController, - mMockOneHandedController, mMockExecutor); + mMockOneHandedController, mMockExecutor, mMockHandler); mShellInit.init(); when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm); when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper); @@ -230,7 +232,7 @@ public class PipControllerTest extends ShellTestCase { mMockPipTransitionController, mMockWindowManagerShellWrapper, mMockTaskStackListener, mMockPipParamsChangedForwarder, mMockDisplayInsetsController, mMockTabletopModeController, - mMockOneHandedController, mMockExecutor)); + mMockOneHandedController, mMockExecutor, mMockHandler)); } @Test 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 a18fbf0891ef..85bc7cc287e6 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 @@ -247,7 +247,18 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() { whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout) whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS) whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor) - whenever(mockTaskPositionerFactory.create(any(), any(), any(), any(), any(), any(), any())) + whenever( + mockTaskPositionerFactory.create( + any(), + any(), + any(), + any(), + any(), + any(), + any(), + any() + ) + ) .thenReturn(mockTaskPositioner) doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index 7784af6b1111..ab41d9c80177 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -21,6 +21,7 @@ import android.content.Context import android.content.res.Resources import android.graphics.Point import android.graphics.Rect +import android.os.Handler import android.os.IBinder import android.testing.AndroidTestingRunner import android.view.Display @@ -107,6 +108,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { private lateinit var mockResources: Resources @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor + @Mock + private lateinit var mockHandler: Handler private lateinit var taskPositioner: VeiledResizeTaskPositioner @@ -155,7 +158,8 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { mockDragStartListener, mockTransactionFactory, mockTransitions, - mockInteractionJankMonitor + mockInteractionJankMonitor, + mockHandler, ) } 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 index 1b0b7d95e657..1b2ce9e4df36 100644 --- 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 @@ -18,6 +18,7 @@ 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 @@ -27,6 +28,7 @@ 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 @@ -57,8 +59,54 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { onDrawTransaction = null ) - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() - assertThat(windowDecorViewHost.view()).isEqualTo(view) + 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) @@ -77,7 +125,7 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { ) // No view host yet, since the coroutine hasn't run. - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse() + assertThat(windowDecorViewHost.viewHost).isNull() windowDecorViewHost.updateView( view = syncView, @@ -89,13 +137,14 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { // Would run coroutine if it hadn't been cancelled. advanceUntilIdle() - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() - assertThat(windowDecorViewHost.view()).isNotNull() + 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.view()).isEqualTo(syncView) - assertThat(windowDecorViewHost.view()!!.layoutParams.width).isEqualTo(syncAttrs.width) + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(syncView) + assertThat(windowDecorViewHost.viewHost!!.view!!.layoutParams.width) + .isEqualTo(syncAttrs.width) } @OptIn(ExperimentalCoroutinesApi::class) @@ -111,11 +160,11 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { configuration = context.resources.configuration, ) - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isFalse() + assertThat(windowDecorViewHost.viewHost).isNull() advanceUntilIdle() - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() + assertThat(windowDecorViewHost.viewHost).isNotNull() } @OptIn(ExperimentalCoroutinesApi::class) @@ -138,8 +187,9 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { advanceUntilIdle() - assertThat(windowDecorViewHost.viewHostAdapter.isInitialized()).isTrue() - assertThat(windowDecorViewHost.view()).isEqualTo(otherView) + assertThat(windowDecorViewHost.viewHost).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isNotNull() + assertThat(windowDecorViewHost.viewHost!!.view).isEqualTo(otherView) } @Test @@ -157,15 +207,16 @@ class DefaultWindowDecorViewHostTest : ShellTestCase() { val t = mock(SurfaceControl.Transaction::class.java) windowDecorViewHost.release(t) - verify(windowDecorViewHost.viewHostAdapter).release(t) + verify(windowDecorViewHost.viewHost!!).release() + verify(t).remove(windowDecorViewHost.surfaceControl) } private fun CoroutineScope.createDefaultViewHost() = DefaultWindowDecorViewHost( context = context, mainScope = this, display = context.display, - viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)), + surfaceControlViewHostFactory = { c, d, wwm, s -> + spy(SurfaceControlViewHost(c, d, wwm, s)) + } ) - - private fun DefaultWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt deleted file mode 100644 index a7e4213ad01d..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt +++ /dev/null @@ -1,181 +0,0 @@ -/* - * 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.view.SurfaceControl -import androidx.test.filters.SmallTest -import com.android.wm.shell.ShellTestCase -import com.android.wm.shell.TestShellExecutor -import com.android.wm.shell.sysui.ShellInit -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.Test -import org.junit.runner.RunWith -import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.kotlin.any -import org.mockito.kotlin.mock -import org.mockito.kotlin.never -import org.mockito.kotlin.verify -import org.mockito.kotlin.whenever - -/** - * Tests for [PooledWindowDecorViewHostSupplier]. - * - * Build/Install/Run: - * atest WMShellUnitTests:PooledWindowDecorViewHostSupplierTest - */ -@OptIn(ExperimentalCoroutinesApi::class) -@SmallTest -@RunWith(AndroidTestingRunner::class) -class PooledWindowDecorViewHostSupplierTest : ShellTestCase() { - - private val testExecutor = TestShellExecutor() - private val testShellInit = ShellInit(testExecutor) - @Mock - private lateinit var mockViewHostFactory: ReusableWindowDecorViewHost.Factory - - private lateinit var supplier: PooledWindowDecorViewHostSupplier - - @Test - fun setUp() { - MockitoAnnotations.initMocks(this) - } - - @Test - fun onInit_warmsAndPoolsViewHosts() = runTest { - supplier = createSupplier(maxPoolSize = 5, preWarmSize = 2) - val mockViewHost1 = mock<ReusableWindowDecorViewHost>() - val mockViewHost2 = mock<ReusableWindowDecorViewHost>() - whenever(mockViewHostFactory - .create(context, this, context.display, id = 0)) - .thenReturn(mockViewHost1) - whenever(mockViewHostFactory - .create(context, this, context.display, id = 1)) - .thenReturn(mockViewHost2) - - testExecutor.flushAll() - advanceUntilIdle() - - // Both were warmed up. - verify(mockViewHost1).warmUp() - verify(mockViewHost2).warmUp() - // Both were released, so re-acquiring them provides the same instance. - assertThat(mockViewHost2) - .isEqualTo(supplier.acquire(context, context.display)) - assertThat(mockViewHost1) - .isEqualTo(supplier.acquire(context, context.display)) - } - - @Test(expected = Throwable::class) - fun onInit_warmUpSizeExceedsPoolSize_throws() = runTest { - createSupplier(maxPoolSize = 3, preWarmSize = 4) - } - - @Test - fun acquire_poolHasInstances_reuses() = runTest { - supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0) - - // Prepare the pool with one instance. - val mockViewHost = mock<ReusableWindowDecorViewHost>() - supplier.release(mockViewHost, SurfaceControl.Transaction()) - - assertThat(mockViewHost) - .isEqualTo(supplier.acquire(context, context.display)) - verify(mockViewHostFactory, never()).create(any(), any(), any(), any()) - } - - @Test - fun acquire_pooledHasZeroInstances_creates() = runTest { - supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0) - - supplier.acquire(context, context.display) - - verify(mockViewHostFactory).create(context, this, context.display, id = 0) - } - - @Test - fun release_poolBelowLimit_caches() = runTest { - supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0) - - val mockViewHost = mock<ReusableWindowDecorViewHost>() - val mockT = mock<SurfaceControl.Transaction>() - supplier.release(mockViewHost, mockT) - - assertThat(mockViewHost) - .isEqualTo(supplier.acquire(context, context.display)) - } - - @Test - fun release_poolBelowLimit_doesNotReleaseViewHost() = runTest { - supplier = createSupplier(maxPoolSize = 5, preWarmSize = 0) - - val mockViewHost = mock<ReusableWindowDecorViewHost>() - val mockT = mock<SurfaceControl.Transaction>() - supplier.release(mockViewHost, mockT) - - verify(mockViewHost, never()).release(mockT) - } - - @Test - fun release_poolAtLimit_doesNotCache() = runTest { - supplier = createSupplier(maxPoolSize = 1, preWarmSize = 0) - val mockT = mock<SurfaceControl.Transaction>() - val mockViewHost = mock<ReusableWindowDecorViewHost>() - supplier.release(mockViewHost, mockT) // Maxes pool. - - val mockViewHost2 = mock<ReusableWindowDecorViewHost>() - supplier.release(mockViewHost2, mockT) // Beyond limit. - - assertThat(mockViewHost) - .isEqualTo(supplier.acquire(context, context.display)) - // Second one wasn't cached, so the acquired one should've been a new instance. - assertThat(mockViewHost2) - .isNotEqualTo(supplier.acquire(context, context.display)) - } - - @Test - fun release_poolAtLimit_releasesViewHost() = runTest { - supplier = createSupplier(maxPoolSize = 1, preWarmSize = 0) - val mockT = mock<SurfaceControl.Transaction>() - val mockViewHost = mock<ReusableWindowDecorViewHost>() - supplier.release(mockViewHost, mockT) // Maxes pool. - - val mockViewHost2 = mock<ReusableWindowDecorViewHost>() - supplier.release(mockViewHost2, mockT) // Beyond limit. - - // Second one doesn't fit, so it needs to be released. - verify(mockViewHost2).release(mockT) - } - - private fun CoroutineScope.createSupplier( - maxPoolSize: Int, - preWarmSize: Int - ) = PooledWindowDecorViewHostSupplier( - context, - this, - testShellInit, - mockViewHostFactory, - maxPoolSize, - preWarmSize - ).also { - testShellInit.init() - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt deleted file mode 100644 index de2444e34ca9..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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.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.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.mock -import org.mockito.kotlin.spy -import org.mockito.kotlin.verify - -/** - * Tests for [ReusableWindowDecorViewHost]. - * - * Build/Install/Run: - * atest WMShellUnitTests:ReusableWindowDecorViewHostTest - */ -@SmallTest -@TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) -class ReusableWindowDecorViewHostTest : ShellTestCase() { - - @Test - fun warmUp_addsRootView() = runTest { - val reusableVH = createReusableViewHost().apply { - warmUp() - } - - assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() - assertThat(reusableVH.view()).isEqualTo(reusableVH.rootView) - } - - @Test - fun update_differentView_replacesView() = runTest { - val view = View(context) - val lp = WindowManager.LayoutParams() - val reusableVH = createReusableViewHost() - reusableVH.updateView(view, lp, context.resources.configuration, null) - - assertThat(reusableVH.rootView.childCount).isEqualTo(1) - assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(view) - - val newView = View(context) - val newLp = WindowManager.LayoutParams() - reusableVH.updateView(newView, newLp, context.resources.configuration, null) - - assertThat(reusableVH.rootView.childCount).isEqualTo(1) - assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(newView) - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Test - fun updateView_clearsPendingAsyncJob() = runTest { - val reusableVH = createReusableViewHost() - val asyncView = View(context) - val syncView = View(context) - val asyncAttrs = WindowManager.LayoutParams(100, 100) - val syncAttrs = WindowManager.LayoutParams(200, 200) - - reusableVH.updateViewAsync( - view = asyncView, - attrs = asyncAttrs, - configuration = context.resources.configuration, - ) - - // No view host yet, since the coroutine hasn't run. - assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse() - - reusableVH.updateView( - view = syncView, - attrs = syncAttrs, - configuration = context.resources.configuration, - onDrawTransaction = null - ) - - // Would run coroutine if it hadn't been cancelled. - advanceUntilIdle() - - assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() - // 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(reusableVH.rootView.getChildAt(0)).isEqualTo(syncView) - assertThat(reusableVH.view()!!.layoutParams.width).isEqualTo(syncAttrs.width) - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Test - fun updateViewAsync() = runTest { - val reusableVH = createReusableViewHost() - val view = View(context) - val attrs = WindowManager.LayoutParams(100, 100) - - reusableVH.updateViewAsync( - view = view, - attrs = attrs, - configuration = context.resources.configuration, - ) - - assertThat(reusableVH.viewHostAdapter.isInitialized()).isFalse() - - advanceUntilIdle() - - assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() - } - - @OptIn(ExperimentalCoroutinesApi::class) - @Test - fun updateViewAsync_clearsPendingAsyncJob() = runTest { - val reusableVH = createReusableViewHost() - - val view = View(context) - reusableVH.updateViewAsync( - view = view, - attrs = WindowManager.LayoutParams(100, 100), - configuration = context.resources.configuration, - ) - val otherView = View(context) - reusableVH.updateViewAsync( - view = otherView, - attrs = WindowManager.LayoutParams(100, 100), - configuration = context.resources.configuration, - ) - - advanceUntilIdle() - - assertThat(reusableVH.viewHostAdapter.isInitialized()).isTrue() - assertThat(reusableVH.rootView.getChildAt(0)).isEqualTo(otherView) - } - - @Test - fun release() = runTest { - val reusableVH = createReusableViewHost() - - val view = View(context) - reusableVH.updateView( - view = view, - attrs = WindowManager.LayoutParams(100, 100), - configuration = context.resources.configuration, - onDrawTransaction = null - ) - - val t = mock(SurfaceControl.Transaction::class.java) - reusableVH.release(t) - - verify(reusableVH.viewHostAdapter).release(t) - } - - private fun CoroutineScope.createReusableViewHost() = ReusableWindowDecorViewHost( - context = context, - mainScope = this, - display = context.display, - id = 1, - viewHostAdapter = spy(SurfaceControlViewHostAdapter(context, context.display)), - ) - - private fun ReusableWindowDecorViewHost.view(): View? = viewHostAdapter.viewHost?.view -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt deleted file mode 100644 index d6c80a7fffc1..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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 org.junit.Assert.assertThrows -import org.junit.Before -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 [SurfaceControlViewHostAdapter]. - * - * Build/Install/Run: - * atest WMShellUnitTests:SurfaceControlViewHostAdapterTest - */ -@SmallTest -@TestableLooper.RunWithLooper -@RunWith(AndroidTestingRunner::class) -class SurfaceControlViewHostAdapterTest : ShellTestCase() { - - private lateinit var adapter: SurfaceControlViewHostAdapter - - @Before - fun setUp() { - adapter = SurfaceControlViewHostAdapter( - context, - context.display, - surfaceControlViewHostFactory = { c, d, wwm, s -> - spy(SurfaceControlViewHost(c, d, wwm, s)) - } - ) - } - - @Test - fun prepareViewHost() { - adapter.prepareViewHost(context.resources.configuration) - - assertThat(adapter.viewHost).isNotNull() - } - - @Test - fun prepareViewHost_alreadyCreated_skips() { - adapter.prepareViewHost(context.resources.configuration) - - val viewHost = adapter.viewHost!! - - adapter.prepareViewHost(context.resources.configuration) - - assertThat(adapter.viewHost).isEqualTo(viewHost) - } - - @Test - fun updateView_layoutInViewHost() { - val view = View(context) - adapter.prepareViewHost(context.resources.configuration) - - adapter.updateView( - view = view, - attrs = WindowManager.LayoutParams(100, 100) - ) - - assertThat(adapter.isInitialized()).isTrue() - assertThat(adapter.view()).isEqualTo(view) - } - - @Test - fun updateView_alreadyLaidOut_relayouts() { - val view = View(context) - adapter.prepareViewHost(context.resources.configuration) - adapter.updateView( - view = view, - attrs = WindowManager.LayoutParams(100, 100) - ) - - val otherParams = WindowManager.LayoutParams(200, 200) - adapter.updateView( - view = view, - attrs = otherParams - ) - - assertThat(adapter.view()).isEqualTo(view) - assertThat(adapter.view()!!.layoutParams.width).isEqualTo(otherParams.width) - } - - @Test - fun updateView_replacingView_throws() { - val view = View(context) - adapter.prepareViewHost(context.resources.configuration) - adapter.updateView( - view = view, - attrs = WindowManager.LayoutParams(100, 100) - ) - - val otherView = View(context) - assertThrows(Exception::class.java) { - adapter.updateView( - view = otherView, - attrs = WindowManager.LayoutParams(100, 100) - ) - } - } - - @Test - fun release() { - adapter.prepareViewHost(context.resources.configuration) - adapter.updateView( - view = View(context), - attrs = WindowManager.LayoutParams(100, 100) - ) - - val mockT = mock(SurfaceControl.Transaction::class.java) - adapter.release(mockT) - - verify(adapter.viewHost!!).release() - verify(mockT).remove(adapter.rootSurface) - } - - private fun SurfaceControlViewHostAdapter.view(): View? = viewHost?.view -} diff --git a/libs/appfunctions/Android.bp b/libs/appfunctions/Android.bp new file mode 100644 index 000000000000..09e2f423c3ba --- /dev/null +++ b/libs/appfunctions/Android.bp @@ -0,0 +1,31 @@ +// 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 { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +java_sdk_library { + name: "com.google.android.appfunctions.sidecar", + owner: "google", + srcs: ["java/**/*.java"], + api_packages: ["com.google.android.appfunctions.sidecar"], + dex_preopt: { + enabled: false, + }, + system_ext_specific: true, + no_dist: true, + unsafe_ignore_missing_latest_api: true, +} diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt new file mode 100644 index 000000000000..504e3290b0ae --- /dev/null +++ b/libs/appfunctions/api/current.txt @@ -0,0 +1,49 @@ +// Signature format: 2.0 +package com.google.android.appfunctions.sidecar { + + public final class AppFunctionManager { + ctor public AppFunctionManager(android.content.Context); + method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + } + + public abstract class AppFunctionService extends android.app.Service { + ctor public AppFunctionService(); + method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); + method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE"; + field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; + } + + public final class ExecuteAppFunctionRequest { + method @NonNull public android.os.Bundle getExtras(); + method @NonNull public String getFunctionIdentifier(); + method @NonNull public android.app.appsearch.GenericDocument getParameters(); + method @NonNull public String getTargetPackageName(); + } + + public static final class ExecuteAppFunctionRequest.Builder { + ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String); + method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest build(); + method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle); + method @NonNull public com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument); + } + + public final class ExecuteAppFunctionResponse { + method @Nullable public String getErrorMessage(); + method @NonNull public android.os.Bundle getExtras(); + method public int getResultCode(); + method @NonNull public android.app.appsearch.GenericDocument getResultDocument(); + method public boolean isSuccess(); + method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newFailure(int, @Nullable String, @Nullable android.os.Bundle); + method @NonNull public static com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse newSuccess(@NonNull android.app.appsearch.GenericDocument, @Nullable android.os.Bundle); + field public static final String PROPERTY_RETURN_VALUE = "returnValue"; + field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 + field public static final int RESULT_DENIED = 1; // 0x1 + field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 + field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 + field public static final int RESULT_OK = 0; // 0x0 + field public static final int RESULT_TIMED_OUT = 5; // 0x5 + } + +} + diff --git a/libs/appfunctions/api/removed.txt b/libs/appfunctions/api/removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/libs/appfunctions/api/removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/libs/appfunctions/api/system-current.txt b/libs/appfunctions/api/system-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/libs/appfunctions/api/system-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/libs/appfunctions/api/system-removed.txt b/libs/appfunctions/api/system-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/libs/appfunctions/api/system-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/libs/appfunctions/api/test-current.txt b/libs/appfunctions/api/test-current.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/libs/appfunctions/api/test-current.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/libs/appfunctions/api/test-removed.txt b/libs/appfunctions/api/test-removed.txt new file mode 100644 index 000000000000..d802177e249b --- /dev/null +++ b/libs/appfunctions/api/test-removed.txt @@ -0,0 +1 @@ +// Signature format: 2.0 diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java new file mode 100644 index 000000000000..b1dd4676a35e --- /dev/null +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java @@ -0,0 +1,82 @@ +/* + * 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.google.android.appfunctions.sidecar; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.content.Context; + +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + + +/** + * Provides app functions related functionalities. + * + * <p>App function is a specific piece of functionality that an app offers to the system. These + * functionalities can be integrated into various system features. + * + * <p>This class wraps {@link android.app.appfunctions.AppFunctionManager} functionalities and + * exposes it here as a sidecar library (avoiding direct dependency on the platform API). + */ +// TODO(b/357551503): Implement get and set enabled app function APIs. +// TODO(b/367329899): Add sidecar library to Android B builds. +public final class AppFunctionManager { + private final android.app.appfunctions.AppFunctionManager mManager; + private final Context mContext; + + /** + * Creates an instance. + * + * @param context A {@link Context}. + * @throws java.lang.IllegalStateException if the underlying {@link + * android.app.appfunctions.AppFunctionManager} is not found. + */ + public AppFunctionManager(Context context) { + mContext = Objects.requireNonNull(context); + mManager = context.getSystemService(android.app.appfunctions.AppFunctionManager.class); + if (mManager == null) { + throw new IllegalStateException( + "Underlying AppFunctionManager system service not found."); + } + } + + /** + * Executes the app function. + * + * <p>Proxies request and response to the underlying {@link + * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and + * response in the appropriate type required by the function. + */ + public void executeAppFunction( + @NonNull ExecuteAppFunctionRequest sidecarRequest, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<ExecuteAppFunctionResponse> callback) { + Objects.requireNonNull(sidecarRequest); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + + android.app.appfunctions.ExecuteAppFunctionRequest platformRequest = + SidecarConverter.getPlatformExecuteAppFunctionRequest(sidecarRequest); + mManager.executeAppFunction( + platformRequest, executor, (platformResponse) -> { + callback.accept(SidecarConverter.getSidecarExecuteAppFunctionResponse( + platformResponse)); + }); + } +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java new file mode 100644 index 000000000000..65959dfdf561 --- /dev/null +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java @@ -0,0 +1,114 @@ +/* + * 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.google.android.appfunctions.sidecar; + +import static android.Manifest.permission.BIND_APP_FUNCTION_SERVICE; + +import android.annotation.MainThread; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.Service; +import android.content.Intent; +import android.os.Binder; +import android.os.IBinder; + +import java.util.function.Consumer; + +/** + * Abstract base class to provide app functions to the system. + * + * <p>Include the following in the manifest: + * + * <pre> + * {@literal + * <service android:name=".YourService" + * android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"> + * <intent-filter> + * <action android:name="android.app.appfunctions.AppFunctionService" /> + * </intent-filter> + * </service> + * } + * </pre> + * + * <p>This class wraps {@link android.app.appfunctions.AppFunctionService} functionalities and + * exposes it here as a sidecar library (avoiding direct dependency on the platform API). + * + * @see AppFunctionManager + */ +public abstract class AppFunctionService extends Service { + /** + * The permission to only allow system access to the functions through {@link + * AppFunctionManagerService}. + */ + @NonNull + public static final String BIND_APP_FUNCTION_SERVICE = + "android.permission.BIND_APP_FUNCTION_SERVICE"; + + /** + * The {@link Intent} that must be declared as handled by the service. To be supported, the + * service must also require the {@link BIND_APP_FUNCTION_SERVICE} permission so that other + * applications can not abuse it. + */ + @NonNull + public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; + + private final Binder mBinder = + android.app.appfunctions.AppFunctionService.createBinder( + /* context= */ this, + /* onExecuteFunction= */ (platformRequest, callback) -> { + AppFunctionService.this.onExecuteFunction( + SidecarConverter.getSidecarExecuteAppFunctionRequest( + platformRequest), + (sidecarResponse) -> { + callback.accept( + SidecarConverter.getPlatformExecuteAppFunctionResponse( + sidecarResponse)); + }); + } + ); + + @NonNull + @Override + public final IBinder onBind(@Nullable Intent intent) { + return mBinder; + } + + /** + * Called by the system to execute a specific app function. + * + * <p>This method is triggered when the system requests your AppFunctionService to handle a + * particular function you have registered and made available. + * + * <p>To ensure proper routing of function requests, assign a unique identifier to each + * function. This identifier doesn't need to be globally unique, but it must be unique within + * your app. For example, a function to order food could be identified as "orderFood". In most + * cases this identifier should come from the ID automatically generated by the AppFunctions + * SDK. You can determine the specific function to invoke by calling {@link + * ExecuteAppFunctionRequest#getFunctionIdentifier()}. + * + * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker + * thread and dispatch the result with the given callback. You should always report back the + * result using the callback, no matter if the execution was successful or not. + * + * @param request The function execution request. + * @param callback A callback to report back the result. + */ + @MainThread + public abstract void onExecuteFunction( + @NonNull ExecuteAppFunctionRequest request, + @NonNull Consumer<ExecuteAppFunctionResponse> callback); +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java new file mode 100644 index 000000000000..fa6d2ff12313 --- /dev/null +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java @@ -0,0 +1,137 @@ +/* + * 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.google.android.appfunctions.sidecar; + +import android.annotation.NonNull; +import android.app.appsearch.GenericDocument; +import android.os.Bundle; + +import java.util.Objects; + +/** + * A request to execute an app function. + * + * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionRequest} without parcel + * functionality and exposes it here as a sidecar library (avoiding direct dependency on the + * platform API). + */ +public final class ExecuteAppFunctionRequest { + /** Returns the package name of the app that hosts the function. */ + @NonNull private final String mTargetPackageName; + + /** + * Returns the unique string identifier of the app function to be executed. TODO(b/357551503): + * Document how callers can get the available function identifiers. + */ + @NonNull private final String mFunctionIdentifier; + + /** Returns additional metadata relevant to this function execution request. */ + @NonNull private final Bundle mExtras; + + /** + * Returns the parameters required to invoke this function. Within this [GenericDocument], the + * property names are the names of the function parameters and the property values are the + * values of those parameters. + * + * <p>The document may have missing parameters. Developers are advised to implement defensive + * handling measures. + * + * <p>TODO(b/357551503): Document how function parameters can be obtained for function execution + */ + @NonNull private final GenericDocument mParameters; + + private ExecuteAppFunctionRequest( + @NonNull String targetPackageName, + @NonNull String functionIdentifier, + @NonNull Bundle extras, + @NonNull GenericDocument parameters) { + mTargetPackageName = Objects.requireNonNull(targetPackageName); + mFunctionIdentifier = Objects.requireNonNull(functionIdentifier); + mExtras = Objects.requireNonNull(extras); + mParameters = Objects.requireNonNull(parameters); + } + + /** Returns the package name of the app that hosts the function. */ + @NonNull + public String getTargetPackageName() { + return mTargetPackageName; + } + + /** Returns the unique string identifier of the app function to be executed. */ + @NonNull + public String getFunctionIdentifier() { + return mFunctionIdentifier; + } + + /** + * Returns the function parameters. The key is the parameter name, and the value is the + * parameter value. + * + * <p>The bundle may have missing parameters. Developers are advised to implement defensive + * handling measures. + */ + @NonNull + public GenericDocument getParameters() { + return mParameters; + } + + /** Returns the additional data relevant to this function execution. */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** Builder for {@link ExecuteAppFunctionRequest}. */ + public static final class Builder { + @NonNull private final String mTargetPackageName; + @NonNull private final String mFunctionIdentifier; + @NonNull private Bundle mExtras = Bundle.EMPTY; + + @NonNull + private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build(); + + public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) { + mTargetPackageName = Objects.requireNonNull(targetPackageName); + mFunctionIdentifier = Objects.requireNonNull(functionIdentifier); + } + + /** Sets the additional data relevant to this function execution. */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = Objects.requireNonNull(extras); + return this; + } + + /** Sets the function parameters. */ + @NonNull + public Builder setParameters(@NonNull GenericDocument parameters) { + Objects.requireNonNull(parameters); + mParameters = parameters; + return this; + } + + /** Builds the {@link ExecuteAppFunctionRequest}. */ + @NonNull + public ExecuteAppFunctionRequest build() { + return new ExecuteAppFunctionRequest( + mTargetPackageName, + mFunctionIdentifier, + mExtras, + mParameters); + } + } +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java new file mode 100644 index 000000000000..60c25fae58d1 --- /dev/null +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java @@ -0,0 +1,240 @@ +/* + * 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.google.android.appfunctions.sidecar; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.appsearch.GenericDocument; +import android.os.Bundle; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * The response to an app function execution. + * + * <p>This class copies {@link android.app.appfunctions.ExecuteAppFunctionResponse} without parcel + * functionality and exposes it here as a sidecar library (avoiding direct dependency on the + * platform API). + */ +public final class ExecuteAppFunctionResponse { + /** + * The name of the property that stores the function return value within the {@code + * resultDocument}. + * + * <p>See {@link GenericDocument#getProperty(String)} for more information. + * + * <p>If the function returns {@code void} or throws an error, the {@code resultDocument} will + * be empty {@link GenericDocument}. + * + * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will + * return {@code null}. + * + * <p>See {@link #getResultDocument} for more information on extracting the return value. + */ + public static final String PROPERTY_RETURN_VALUE = "returnValue"; + + /** The call was successful. */ + public static final int RESULT_OK = 0; + + /** The caller does not have the permission to execute an app function. */ + public static final int RESULT_DENIED = 1; + + /** An unknown error occurred while processing the call in the AppFunctionService. */ + public static final int RESULT_APP_UNKNOWN_ERROR = 2; + + /** + * An internal error occurred within AppFunctionManagerService. + * + * <p>This error may be considered similar to {@link IllegalStateException} + */ + public static final int RESULT_INTERNAL_ERROR = 3; + + /** + * The caller supplied invalid arguments to the call. + * + * <p>This error may be considered similar to {@link IllegalArgumentException}. + */ + public static final int RESULT_INVALID_ARGUMENT = 4; + + /** The operation was timed out. */ + public static final int RESULT_TIMED_OUT = 5; + + /** The result code of the app function execution. */ + @ResultCode private final int mResultCode; + + /** + * The error message associated with the result, if any. This is {@code null} if the result code + * is {@link #RESULT_OK}. + */ + @Nullable private final String mErrorMessage; + + /** + * Returns the return value of the executed function. + * + * <p>The return value is stored in a {@link GenericDocument} with the key {@link + * #PROPERTY_RETURN_VALUE}. + * + * <p>See {@link #getResultDocument} for more information on extracting the return value. + */ + @NonNull private final GenericDocument mResultDocument; + + /** Returns the additional metadata data relevant to this function execution response. */ + @NonNull private final Bundle mExtras; + + private ExecuteAppFunctionResponse( + @NonNull GenericDocument resultDocument, + @NonNull Bundle extras, + @ResultCode int resultCode, + @Nullable String errorMessage) { + mResultDocument = Objects.requireNonNull(resultDocument); + mExtras = Objects.requireNonNull(extras); + mResultCode = resultCode; + mErrorMessage = errorMessage; + } + + /** + * Returns result codes from throwable. + * + * @hide + */ + static @ResultCode int getResultCode(@NonNull Throwable t) { + if (t instanceof IllegalArgumentException) { + return ExecuteAppFunctionResponse.RESULT_INVALID_ARGUMENT; + } + return ExecuteAppFunctionResponse.RESULT_APP_UNKNOWN_ERROR; + } + + /** + * Returns a successful response. + * + * @param resultDocument The return value of the executed function. + * @param extras The additional metadata data relevant to this function execution response. + */ + @NonNull + public static ExecuteAppFunctionResponse newSuccess( + @NonNull GenericDocument resultDocument, @Nullable Bundle extras) { + Objects.requireNonNull(resultDocument); + Bundle actualExtras = getActualExtras(extras); + + return new ExecuteAppFunctionResponse( + resultDocument, actualExtras, RESULT_OK, /* errorMessage= */ null); + } + + /** + * Returns a failure response. + * + * @param resultCode The result code of the app function execution. + * @param extras The additional metadata data relevant to this function execution response. + * @param errorMessage The error message associated with the result, if any. + */ + @NonNull + public static ExecuteAppFunctionResponse newFailure( + @ResultCode int resultCode, @Nullable String errorMessage, @Nullable Bundle extras) { + if (resultCode == RESULT_OK) { + throw new IllegalArgumentException("resultCode must not be RESULT_OK"); + } + Bundle actualExtras = getActualExtras(extras); + GenericDocument emptyDocument = new GenericDocument.Builder<>("", "", "").build(); + return new ExecuteAppFunctionResponse( + emptyDocument, actualExtras, resultCode, errorMessage); + } + + private static Bundle getActualExtras(@Nullable Bundle extras) { + if (extras == null) { + return Bundle.EMPTY; + } + return extras; + } + + /** + * Returns a generic document containing the return value of the executed function. + * + * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value. + * + * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed + * function does not produce a return value. + * + * <p>Sample code for extracting the return value: + * + * <pre> + * GenericDocument resultDocument = response.getResultDocument(); + * Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE); + * if (returnValue != null) { + * // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString}, + * // {@link GenericDocument#getPropertyLong} etc. + * // Do something with the returnValue + * } + * </pre> + */ + @NonNull + public GenericDocument getResultDocument() { + return mResultDocument; + } + + /** Returns the extras of the app function execution. */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + + /** + * Returns {@code true} if {@link #getResultCode} equals {@link + * ExecuteAppFunctionResponse#RESULT_OK}. + */ + public boolean isSuccess() { + return getResultCode() == RESULT_OK; + } + + /** + * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}. + */ + @ResultCode + public int getResultCode() { + return mResultCode; + } + + /** + * Returns the error message associated with this result. + * + * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}. + */ + @Nullable + public String getErrorMessage() { + return mErrorMessage; + } + + /** + * Result codes. + * + * @hide + */ + @IntDef( + prefix = {"RESULT_"}, + value = { + RESULT_OK, + RESULT_DENIED, + RESULT_APP_UNKNOWN_ERROR, + RESULT_INTERNAL_ERROR, + RESULT_INVALID_ARGUMENT, + RESULT_TIMED_OUT, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ResultCode {} +} diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java new file mode 100644 index 000000000000..b1b05f79f33f --- /dev/null +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java @@ -0,0 +1,104 @@ +/* + * 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.google.android.appfunctions.sidecar; + +import android.annotation.NonNull; + +/** + * Utility class containing methods to convert Sidecar objects of AppFunctions API into the + * underlying platform classes. + * + * @hide + */ +public final class SidecarConverter { + private SidecarConverter() {} + + /** + * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} + * into platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} + * + * @hide + */ + @NonNull + public static android.app.appfunctions.ExecuteAppFunctionRequest + getPlatformExecuteAppFunctionRequest(@NonNull ExecuteAppFunctionRequest request) { + return new + android.app.appfunctions.ExecuteAppFunctionRequest.Builder( + request.getTargetPackageName(), + request.getFunctionIdentifier()) + .setExtras(request.getExtras()) + .setParameters(request.getParameters()) + .build(); + } + + /** + * Converts sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} + * into platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} + * + * @hide + */ + @NonNull + public static android.app.appfunctions.ExecuteAppFunctionResponse + getPlatformExecuteAppFunctionResponse(@NonNull ExecuteAppFunctionResponse response) { + if (response.isSuccess()) { + return android.app.appfunctions.ExecuteAppFunctionResponse.newSuccess( + response.getResultDocument(), response.getExtras()); + } else { + return android.app.appfunctions.ExecuteAppFunctionResponse.newFailure( + response.getResultCode(), + response.getErrorMessage(), + response.getExtras()); + } + } + + /** + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionRequest} + * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest} + * + * @hide + */ + @NonNull + public static ExecuteAppFunctionRequest getSidecarExecuteAppFunctionRequest( + @NonNull android.app.appfunctions.ExecuteAppFunctionRequest request) { + return new ExecuteAppFunctionRequest.Builder( + request.getTargetPackageName(), + request.getFunctionIdentifier()) + .setExtras(request.getExtras()) + .setParameters(request.getParameters()) + .build(); + } + + /** + * Converts platform's {@link android.app.appfunctions.ExecuteAppFunctionResponse} + * into sidecar's {@link com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse} + * + * @hide + */ + @NonNull + public static ExecuteAppFunctionResponse getSidecarExecuteAppFunctionResponse( + @NonNull android.app.appfunctions.ExecuteAppFunctionResponse response) { + if (response.isSuccess()) { + return ExecuteAppFunctionResponse.newSuccess( + response.getResultDocument(), response.getExtras()); + } else { + return ExecuteAppFunctionResponse.newFailure( + response.getResultCode(), + response.getErrorMessage(), + response.getExtras()); + } + } +} diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 185436160349..84bd45dfc012 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -264,6 +264,7 @@ Bitmap::Bitmap(void* address, size_t size, const SkImageInfo& info, size_t rowBy , mPixelStorageType(PixelStorageType::Heap) { mPixelStorage.heap.address = address; mPixelStorage.heap.size = size; + traceBitmapCreate(); } Bitmap::Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info) @@ -272,6 +273,7 @@ Bitmap::Bitmap(SkPixelRef& pixelRef, const SkImageInfo& info) , mPixelStorageType(PixelStorageType::WrappedPixelRef) { pixelRef.ref(); mPixelStorage.wrapped.pixelRef = &pixelRef; + traceBitmapCreate(); } Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info, size_t rowBytes) @@ -281,6 +283,7 @@ Bitmap::Bitmap(void* address, int fd, size_t mappedSize, const SkImageInfo& info mPixelStorage.ashmem.address = address; mPixelStorage.ashmem.fd = fd; mPixelStorage.ashmem.size = mappedSize; + traceBitmapCreate(); } #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration @@ -297,10 +300,12 @@ Bitmap::Bitmap(AHardwareBuffer* buffer, const SkImageInfo& info, size_t rowBytes setImmutable(); // HW bitmaps are always immutable mImage = SkImages::DeferredFromAHardwareBuffer(buffer, mInfo.alphaType(), mInfo.refColorSpace()); + traceBitmapCreate(); } #endif Bitmap::~Bitmap() { + traceBitmapDelete(); switch (mPixelStorageType) { case PixelStorageType::WrappedPixelRef: mPixelStorage.wrapped.pixelRef->unref(); @@ -572,4 +577,28 @@ void Bitmap::setGainmap(sp<uirenderer::Gainmap>&& gainmap) { mGainmap = std::move(gainmap); } +std::mutex Bitmap::mLock{}; +size_t Bitmap::mTotalBitmapBytes = 0; +size_t Bitmap::mTotalBitmapCount = 0; + +void Bitmap::traceBitmapCreate() { + if (ATRACE_ENABLED()) { + std::lock_guard lock{mLock}; + mTotalBitmapBytes += getAllocationByteCount(); + mTotalBitmapCount++; + ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes); + ATRACE_INT64("Bitmap Count", mTotalBitmapCount); + } +} + +void Bitmap::traceBitmapDelete() { + if (ATRACE_ENABLED()) { + std::lock_guard lock{mLock}; + mTotalBitmapBytes -= getAllocationByteCount(); + mTotalBitmapCount--; + ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes); + ATRACE_INT64("Bitmap Count", mTotalBitmapCount); + } +} + } // namespace android diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index dd344e2f5517..3d55d859ed5f 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -25,6 +25,7 @@ #include <cutils/compiler.h> #include <utils/StrongPointer.h> +#include <mutex> #include <optional> #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration @@ -227,6 +228,13 @@ private: } mPixelStorage; sk_sp<SkImage> mImage; // Cache is used only for HW Bitmaps with Skia pipeline. + + // for tracing total number and memory usage of bitmaps + static std::mutex mLock; + static size_t mTotalBitmapBytes; + static size_t mTotalBitmapCount; + void traceBitmapCreate(); + void traceBitmapDelete(); }; } // namespace android diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h index d7bf20130b71..e13e136550ca 100644 --- a/libs/hwui/hwui/DrawTextFunctor.h +++ b/libs/hwui/hwui/DrawTextFunctor.h @@ -73,6 +73,7 @@ static void simplifyPaint(int color, Paint* paint) { } paint->setStrokeJoin(SkPaint::kRound_Join); paint->setLooper(nullptr); + paint->setBlendMode(SkBlendMode::kSrcOver); } class DrawTextFunctor { diff --git a/libs/hwui/platform/host/thread/ThreadBase.h b/libs/hwui/platform/host/thread/ThreadBase.h index d709430cc9b6..b4e7bd34971a 100644 --- a/libs/hwui/platform/host/thread/ThreadBase.h +++ b/libs/hwui/platform/host/thread/ThreadBase.h @@ -48,7 +48,7 @@ protected: nsecs_t nextWakeup = mQueue.nextWakeup(lock); std::chrono::nanoseconds duration = std::chrono::nanoseconds::max(); if (nextWakeup < std::numeric_limits<nsecs_t>::max()) { - int timeout = nextWakeup - WorkQueue::clock::now(); + nsecs_t timeout = nextWakeup - WorkQueue::clock::now(); if (timeout < 0) timeout = 0; duration = std::chrono::nanoseconds(timeout); } diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index 1afef75bc741..d993b8715260 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -64,25 +64,6 @@ MouseCursorController::~MouseCursorController() { mLocked.pointerSprite.clear(); } -std::optional<FloatRect> MouseCursorController::getBounds() const { - std::scoped_lock lock(mLock); - - return getBoundsLocked(); -} - -std::optional<FloatRect> MouseCursorController::getBoundsLocked() const REQUIRES(mLock) { - if (!mLocked.viewport.isValid()) { - return {}; - } - - return FloatRect{ - static_cast<float>(mLocked.viewport.logicalLeft), - static_cast<float>(mLocked.viewport.logicalTop), - static_cast<float>(mLocked.viewport.logicalRight - 1), - static_cast<float>(mLocked.viewport.logicalBottom - 1), - }; -} - void MouseCursorController::move(float deltaX, float deltaY) { #if DEBUG_MOUSE_CURSOR_UPDATES ALOGD("Move pointer by deltaX=%0.3f, deltaY=%0.3f", deltaX, deltaY); @@ -105,11 +86,20 @@ void MouseCursorController::setPosition(float x, float y) { } void MouseCursorController::setPositionLocked(float x, float y) REQUIRES(mLock) { - const auto bounds = getBoundsLocked(); - if (!bounds) return; + const auto& v = mLocked.viewport; + if (!v.isValid()) return; - mLocked.pointerX = std::max(bounds->left, std::min(bounds->right, x)); - mLocked.pointerY = std::max(bounds->top, std::min(bounds->bottom, y)); + // The valid bounds for a mouse cursor. Since the right and bottom edges are considered outside + // the display, clip the bounds by one pixel instead of letting the cursor get arbitrarily + // close to the outside edge. + const FloatRect bounds{ + static_cast<float>(mLocked.viewport.logicalLeft), + static_cast<float>(mLocked.viewport.logicalTop), + static_cast<float>(mLocked.viewport.logicalRight - 1), + static_cast<float>(mLocked.viewport.logicalBottom - 1), + }; + mLocked.pointerX = std::max(bounds.left, std::min(bounds.right, x)); + mLocked.pointerY = std::max(bounds.top, std::min(bounds.bottom, y)); updatePointerLocked(); } @@ -216,9 +206,11 @@ void MouseCursorController::setDisplayViewport(const DisplayViewport& viewport, // Reset cursor position to center if size or display changed. if (oldViewport.displayId != viewport.displayId || oldDisplayWidth != newDisplayWidth || oldDisplayHeight != newDisplayHeight) { - if (const auto bounds = getBoundsLocked(); bounds) { - mLocked.pointerX = (bounds->left + bounds->right) * 0.5f; - mLocked.pointerY = (bounds->top + bounds->bottom) * 0.5f; + if (viewport.isValid()) { + // Use integer coordinates as the starting point for the cursor location. + // We usually expect display sizes to be even numbers, so the flooring is precautionary. + mLocked.pointerX = std::floor((viewport.logicalLeft + viewport.logicalRight) / 2); + mLocked.pointerY = std::floor((viewport.logicalTop + viewport.logicalBottom) / 2); // Reload icon resources for density may be changed. loadResourcesLocked(getAdditionalMouseResources); } else { diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index 860034141a0b..12b31a8c531a 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -43,7 +43,6 @@ public: MouseCursorController(PointerControllerContext& context); ~MouseCursorController(); - std::optional<FloatRect> getBounds() const; void move(float deltaX, float deltaY); void setPosition(float x, float y); FloatPoint getPosition() const; @@ -104,7 +103,6 @@ private: } mLocked GUARDED_BY(mLock); - std::optional<FloatRect> getBoundsLocked() const; void setPositionLocked(float x, float y); void updatePointerLocked(); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 5ae967bc369a..78d7d3a7051b 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -138,10 +138,6 @@ std::mutex& PointerController::getLock() const { return mDisplayInfoListener->mLock; } -std::optional<FloatRect> PointerController::getBounds() const { - return mCursorController.getBounds(); -} - void PointerController::move(float deltaX, float deltaY) { const ui::LogicalDisplayId displayId = mCursorController.getDisplayId(); vec2 transformed; diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 4d1e1d733cc1..ee8d1211341f 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -51,7 +51,6 @@ public: ~PointerController() override; - std::optional<FloatRect> getBounds() const override; void move(float deltaX, float deltaY) override; void setPosition(float x, float y) override; FloatPoint getPosition() const override; @@ -166,9 +165,6 @@ public: ~TouchPointerController() override; - std::optional<FloatRect> getBounds() const override { - LOG_ALWAYS_FATAL("Should not be called"); - } void move(float, float) override { LOG_ALWAYS_FATAL("Should not be called"); } |