summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java16
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHost.kt71
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplier.kt105
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHost.kt161
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapter.kt111
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewhost/Warmable.kt23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizerTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/DefaultWindowDecorViewHostTest.kt81
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/PooledWindowDecorViewHostSupplierTest.kt181
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/ReusableWindowDecorViewHostTest.kt182
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewhost/SurfaceControlViewHostAdapterTest.kt144
-rw-r--r--libs/appfunctions/Android.bp31
-rw-r--r--libs/appfunctions/api/current.txt49
-rw-r--r--libs/appfunctions/api/removed.txt1
-rw-r--r--libs/appfunctions/api/system-current.txt1
-rw-r--r--libs/appfunctions/api/system-removed.txt1
-rw-r--r--libs/appfunctions/api/test-current.txt1
-rw-r--r--libs/appfunctions/api/test-removed.txt1
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java82
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java114
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionRequest.java137
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/ExecuteAppFunctionResponse.java240
-rw-r--r--libs/appfunctions/java/com/google/android/appfunctions/sidecar/SidecarConverter.java104
-rw-r--r--libs/hwui/hwui/Bitmap.cpp29
-rw-r--r--libs/hwui/hwui/Bitmap.h8
-rw-r--r--libs/hwui/hwui/DrawTextFunctor.h1
-rw-r--r--libs/hwui/platform/host/thread/ThreadBase.h2
-rw-r--r--libs/input/MouseCursorController.cpp44
-rw-r--r--libs/input/MouseCursorController.h2
-rw-r--r--libs/input/PointerController.cpp4
-rw-r--r--libs/input/PointerController.h4
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");
}