summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java405
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt490
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt16
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt8
9 files changed, 581 insertions, 394 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 84bb830af9a3..35475c7ee4ce 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
@@ -134,6 +134,7 @@ import com.android.wm.shell.recents.RecentsTransitionHandler;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellDesktopThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -750,6 +751,7 @@ public abstract class WMShellModule {
MultiInstanceHelper multiInstanceHelper,
@ShellMainThread ShellExecutor mainExecutor,
@ShellMainThread Handler mainHandler,
+ @ShellDesktopThread ShellExecutor desktopExecutor,
Optional<DesktopTasksLimiter> desktopTasksLimiter,
Optional<RecentTasksController> recentTasksController,
InteractionJankMonitor interactionJankMonitor,
@@ -789,6 +791,7 @@ public abstract class WMShellModule {
recentsTransitionHandler,
multiInstanceHelper,
mainExecutor,
+ desktopExecutor,
desktopTasksLimiter,
recentTasksController.orElse(null),
interactionJankMonitor,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 27aed17762ff..aecbf1a23cb2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -18,9 +18,6 @@ package com.android.wm.shell.desktopmode;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR;
@@ -28,37 +25,27 @@ import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.Indica
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.RectEvaluator;
-import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
-import android.graphics.drawable.LayerDrawable;
-import android.util.DisplayMetrics;
import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
-import android.view.animation.DecelerateInterpolator;
+import android.window.DesktopModeFlags;
import androidx.annotation.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
-import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.shared.annotations.ShellDesktopThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -115,37 +102,54 @@ public class DesktopModeVisualIndicator {
}
}
+ private final VisualIndicatorViewContainer mVisualIndicatorViewContainer;
+
private final Context mContext;
private final DisplayController mDisplayController;
- private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
private final ActivityManager.RunningTaskInfo mTaskInfo;
- private final SurfaceControl mTaskSurface;
- private final @Nullable BubbleDropTargetBoundsProvider mBubbleBoundsProvider;
- private SurfaceControl mLeash;
-
- private final SyncTransactionQueue mSyncQueue;
- private SurfaceControlViewHost mViewHost;
- private View mView;
private IndicatorType mCurrentType;
- private DragStartState mDragStartState;
- private boolean mIsReleased;
+ private final DragStartState mDragStartState;
- public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
+ public DesktopModeVisualIndicator(@ShellDesktopThread ShellExecutor desktopExecutor,
+ @ShellMainThread ShellExecutor mainExecutor,
+ SyncTransactionQueue syncQueue,
ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
Context context, SurfaceControl taskSurface,
RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer,
DragStartState dragStartState,
@Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider) {
- mSyncQueue = syncQueue;
+ SurfaceControl.Builder builder = new SurfaceControl.Builder();
+ taskDisplayAreaOrganizer.attachToDisplayArea(taskInfo.displayId, builder);
+ mVisualIndicatorViewContainer = new VisualIndicatorViewContainer(
+ DesktopModeFlags.ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX.isTrue()
+ ? desktopExecutor : mainExecutor,
+ mainExecutor, builder, syncQueue, bubbleBoundsProvider);
mTaskInfo = taskInfo;
mDisplayController = displayController;
mContext = context;
- mTaskSurface = taskSurface;
- mRootTdaOrganizer = taskDisplayAreaOrganizer;
- mBubbleBoundsProvider = bubbleBoundsProvider;
mCurrentType = NO_INDICATOR;
mDragStartState = dragStartState;
+ mVisualIndicatorViewContainer.createView(
+ mContext,
+ mDisplayController.getDisplay(mTaskInfo.displayId),
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId),
+ mTaskInfo,
+ taskSurface
+ );
+ }
+
+ /** Start the fade out animation, running the callback on the main thread once it is done. */
+ public void fadeOutIndicator(
+ @NonNull Runnable callback) {
+ mVisualIndicatorViewContainer.fadeOutIndicator(
+ mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType, callback
+ );
+ }
+
+ /** Release the visual indicator view and its viewhost. */
+ public void releaseVisualIndicator() {
+ mVisualIndicatorViewContainer.releaseVisualIndicator();
}
/**
@@ -202,7 +206,10 @@ public class DesktopModeVisualIndicator {
}
}
if (mDragStartState != DragStartState.DRAGGED_INTENT) {
- transitionIndicator(result);
+ mVisualIndicatorViewContainer.transitionIndicator(
+ mTaskInfo, mDisplayController, mCurrentType, result
+ );
+ mCurrentType = result;
}
return result;
}
@@ -283,338 +290,8 @@ public class DesktopModeVisualIndicator {
layout.width(), layout.height());
}
- /**
- * Create a fullscreen indicator with no animation
- */
- private void createView() {
- if (mIsReleased) return;
- final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- final Resources resources = mContext.getResources();
- final DisplayMetrics metrics = resources.getDisplayMetrics();
- final int screenWidth;
- final int screenHeight;
- if (Flags.enableBugFixesForSecondaryDisplay()) {
- final DisplayLayout displayLayout =
- mDisplayController.getDisplayLayout(mTaskInfo.displayId);
- screenWidth = displayLayout.width();
- screenHeight = displayLayout.height();
- } else {
- screenWidth = metrics.widthPixels;
- screenHeight = metrics.heightPixels;
- }
- mView = new View(mContext);
- final SurfaceControl.Builder builder = new SurfaceControl.Builder();
- mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder);
- mLeash = builder
- .setName("Desktop Mode Visual Indicator")
- .setContainerLayer()
- .setCallsite("DesktopModeVisualIndicator.createView")
- .build();
- t.show(mLeash);
- final WindowManager.LayoutParams lp =
- new WindowManager.LayoutParams(screenWidth, screenHeight, TYPE_APPLICATION,
- FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
- lp.setTitle("Desktop Mode Visual Indicator");
- lp.setTrustedOverlay();
- lp.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
- final WindowlessWindowManager windowManager = new WindowlessWindowManager(
- mTaskInfo.configuration, mLeash,
- /* hostInputToken= */ null);
- mViewHost = new SurfaceControlViewHost(mContext,
- mDisplayController.getDisplay(mTaskInfo.displayId), windowManager,
- "DesktopModeVisualIndicator");
- mViewHost.setView(mView, lp);
- // We want this indicator to be behind the dragged task, but in front of all others.
- t.setRelativeLayer(mLeash, mTaskSurface, -1);
-
- mSyncQueue.runInSync(transaction -> {
- transaction.merge(t);
- t.close();
- });
- }
-
@VisibleForTesting
Rect getIndicatorBounds() {
- return mView.getBackground().getBounds();
- }
-
- /**
- * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards.
- */
- private void fadeInIndicator(IndicatorType type) {
- mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background);
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator
- .fadeBoundsIn(mView, type,
- mDisplayController.getDisplayLayout(mTaskInfo.displayId),
- mBubbleBoundsProvider);
- animator.start();
- mCurrentType = type;
- }
-
- /**
- * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
- *
- * @param finishCallback called when animation ends or gets cancelled
- */
- void fadeOutIndicator(@Nullable Runnable finishCallback) {
- if (mCurrentType == NO_INDICATOR) {
- // In rare cases, fade out can be requested before the indicator has determined its
- // initial type and started animating in. In this case, no animator is needed.
- finishCallback.run();
- return;
- }
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator
- .fadeBoundsOut(mView, mCurrentType,
- mDisplayController.getDisplayLayout(mTaskInfo.displayId),
- mBubbleBoundsProvider);
- animator.start();
- if (finishCallback != null) {
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finishCallback.run();
- }
- });
- }
- mCurrentType = NO_INDICATOR;
- }
-
- /**
- * Takes existing indicator and animates it to bounds reflecting a new indicator type.
- */
- private void transitionIndicator(IndicatorType newType) {
- if (mCurrentType == newType) return;
- if (mView == null) {
- createView();
- }
- if (mCurrentType == NO_INDICATOR) {
- fadeInIndicator(newType);
- } else if (newType == NO_INDICATOR) {
- fadeOutIndicator(/* finishCallback= */ null);
- } else {
- final VisualIndicatorAnimator animator = VisualIndicatorAnimator.animateIndicatorType(
- mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId), mCurrentType,
- newType, mBubbleBoundsProvider);
- mCurrentType = newType;
- animator.start();
- }
- }
-
- /**
- * Release the indicator and its components when it is no longer needed.
- */
- public void releaseVisualIndicator(SurfaceControl.Transaction t) {
- mIsReleased = true;
- if (mViewHost == null) return;
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
- }
-
- if (mLeash != null) {
- t.remove(mLeash);
- mLeash = null;
- }
- }
-
- /**
- * Animator for Desktop Mode transitions which supports bounds and alpha animation.
- */
- private static class VisualIndicatorAnimator extends ValueAnimator {
- private static final int FULLSCREEN_INDICATOR_DURATION = 200;
- private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f;
- private static final float INDICATOR_FINAL_OPACITY = 0.35f;
- private static final int MAXIMUM_OPACITY = 255;
-
- /**
- * Determines how this animator will interact with the view's alpha:
- * Fade in, fade out, or no change to alpha
- */
- private enum AlphaAnimType {
- ALPHA_FADE_IN_ANIM, ALPHA_FADE_OUT_ANIM, ALPHA_NO_CHANGE_ANIM
- }
-
- private final View mView;
- private final Rect mStartBounds;
- private final Rect mEndBounds;
- private final RectEvaluator mRectEvaluator;
-
- private VisualIndicatorAnimator(View view, Rect startBounds,
- Rect endBounds) {
- mView = view;
- mStartBounds = new Rect(startBounds);
- mEndBounds = endBounds;
- setFloatValues(0, 1);
- mRectEvaluator = new RectEvaluator(new Rect());
- }
-
- private static VisualIndicatorAnimator fadeBoundsIn(
- @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout,
- @Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider) {
- final Rect endBounds = getIndicatorBounds(displayLayout, type, bubbleBoundsProvider);
- final Rect startBounds = getMinBounds(endBounds);
- view.getBackground().setBounds(startBounds);
-
- final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, endBounds);
- animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM);
- return animator;
- }
-
- private static VisualIndicatorAnimator fadeBoundsOut(
- @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout,
- @Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider) {
- final Rect startBounds = getIndicatorBounds(displayLayout, type, bubbleBoundsProvider);
- final Rect endBounds = getMinBounds(startBounds);
- view.getBackground().setBounds(startBounds);
-
- final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, endBounds);
- animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_OUT_ANIM);
- return animator;
- }
-
- /**
- * Create animator for visual indicator changing type (i.e., fullscreen to freeform,
- * freeform to split, etc.)
- *
- * @param view the view for this indicator
- * @param displayLayout information about the display the transitioning task is
- * currently on
- * @param origType the original indicator type
- * @param newType the new indicator type
- * @param bubbleBoundsProvider provides bounds for bubbles indicators
- */
- private static VisualIndicatorAnimator animateIndicatorType(@NonNull View view,
- @NonNull DisplayLayout displayLayout, IndicatorType origType, IndicatorType newType,
- @Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider) {
- final Rect startBounds = getIndicatorBounds(displayLayout, origType,
- bubbleBoundsProvider);
- final Rect endBounds = getIndicatorBounds(displayLayout, newType, bubbleBoundsProvider);
- final VisualIndicatorAnimator animator = new VisualIndicatorAnimator(
- view, startBounds, endBounds);
- animator.setInterpolator(new DecelerateInterpolator());
- setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_NO_CHANGE_ANIM);
- return animator;
- }
-
- /** Calculates the bounds the indicator should have when fully faded in. */
- private static Rect getIndicatorBounds(DisplayLayout layout, IndicatorType type,
- @Nullable BubbleDropTargetBoundsProvider bubbleBoundsProvider) {
- final Rect desktopStableBounds = new Rect();
- layout.getStableBounds(desktopStableBounds);
- final int padding = desktopStableBounds.top;
- switch (type) {
- case TO_FULLSCREEN_INDICATOR:
- desktopStableBounds.top += padding;
- desktopStableBounds.bottom -= padding;
- desktopStableBounds.left += padding;
- desktopStableBounds.right -= padding;
- return desktopStableBounds;
- case TO_DESKTOP_INDICATOR:
- final float adjustmentPercentage = 1f
- - DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
- return new Rect((int) (adjustmentPercentage * desktopStableBounds.width() / 2),
- (int) (adjustmentPercentage * desktopStableBounds.height() / 2),
- (int) (desktopStableBounds.width()
- - (adjustmentPercentage * desktopStableBounds.width() / 2)),
- (int) (desktopStableBounds.height()
- - (adjustmentPercentage * desktopStableBounds.height() / 2)));
- case TO_SPLIT_LEFT_INDICATOR:
- return new Rect(padding, padding,
- desktopStableBounds.width() / 2 - padding,
- desktopStableBounds.height());
- case TO_SPLIT_RIGHT_INDICATOR:
- return new Rect(desktopStableBounds.width() / 2 + padding, padding,
- desktopStableBounds.width() - padding,
- desktopStableBounds.height());
- case TO_BUBBLE_LEFT_INDICATOR:
- if (bubbleBoundsProvider == null) {
- return new Rect();
- }
- return bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(
- /* onLeft= */ true);
- case TO_BUBBLE_RIGHT_INDICATOR:
- if (bubbleBoundsProvider == null) {
- return new Rect();
- }
- return bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(
- /* onLeft= */ false);
- default:
- throw new IllegalArgumentException("Invalid indicator type provided.");
- }
- }
-
- /**
- * Add necessary listener for animation of indicator
- */
- private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator,
- AlphaAnimType animType) {
- animator.addUpdateListener(a -> {
- if (animator.mView != null) {
- animator.updateBounds(a.getAnimatedFraction(), animator.mView);
- if (animType == AlphaAnimType.ALPHA_FADE_IN_ANIM) {
- animator.updateIndicatorAlpha(a.getAnimatedFraction(), animator.mView);
- } else if (animType == AlphaAnimType.ALPHA_FADE_OUT_ANIM) {
- animator.updateIndicatorAlpha(1 - a.getAnimatedFraction(), animator.mView);
- }
- } else {
- animator.cancel();
- }
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- animator.mView.getBackground().setBounds(animator.mEndBounds);
- }
- });
- animator.setDuration(FULLSCREEN_INDICATOR_DURATION);
- }
-
- /**
- * Update bounds of view based on current animation fraction.
- * Use of delta is to animate bounds independently, in case we need to
- * run multiple animations simultaneously.
- *
- * @param fraction fraction to use, compared against previous fraction
- * @param view the view to update
- */
- private void updateBounds(float fraction, View view) {
- if (mStartBounds.equals(mEndBounds)) {
- return;
- }
- final Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds);
- view.getBackground().setBounds(currentBounds);
- }
-
- /**
- * Fade in the fullscreen indicator
- *
- * @param fraction current animation fraction
- */
- private void updateIndicatorAlpha(float fraction, View view) {
- final LayerDrawable drawable = (LayerDrawable) view.getBackground();
- drawable.findDrawableByLayerId(R.id.indicator_stroke)
- .setAlpha((int) (MAXIMUM_OPACITY * fraction));
- drawable.findDrawableByLayerId(R.id.indicator_solid)
- .setAlpha((int) (MAXIMUM_OPACITY * fraction * INDICATOR_FINAL_OPACITY));
- }
-
- /**
- * Return the minimum bounds of a visual indicator, to be used at the end of fading out
- * and the start of fading in.
- */
- private static Rect getMinBounds(Rect maxBounds) {
- return new Rect((int) (maxBounds.left
- + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width())),
- (int) (maxBounds.top
- + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height())),
- (int) (maxBounds.right
- - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width())),
- (int) (maxBounds.bottom
- - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height())));
- }
+ return mVisualIndicatorViewContainer.getIndicatorBounds();
}
}
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 3b2598450800..c42d8dde0093 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
@@ -116,6 +116,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener.RecentsTransi
import com.android.wm.shell.recents.RecentsTransitionStateListener.TRANSITION_STATE_NOT_RUNNING
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.annotations.ExternalThread
+import com.android.wm.shell.shared.annotations.ShellDesktopThread
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -176,6 +177,7 @@ class DesktopTasksController(
private val recentsTransitionHandler: RecentsTransitionHandler,
private val multiInstanceHelper: MultiInstanceHelper,
@ShellMainThread private val mainExecutor: ShellExecutor,
+ @ShellDesktopThread private val desktopExecutor: ShellExecutor,
private val desktopTasksLimiter: Optional<DesktopTasksLimiter>,
private val recentTasksController: RecentTasksController?,
private val interactionJankMonitor: InteractionJankMonitor,
@@ -202,26 +204,19 @@ class DesktopTasksController(
private var userId: Int
private val desktopModeShellCommandHandler: DesktopModeShellCommandHandler =
DesktopModeShellCommandHandler(this)
- private val mOnAnimationFinishedCallback =
- Consumer<SurfaceControl.Transaction> { t: SurfaceControl.Transaction ->
- visualIndicator?.releaseVisualIndicator(t)
- visualIndicator = null
- }
+ private val mOnAnimationFinishedCallback = { releaseVisualIndicator() }
private val dragToDesktopStateListener =
object : DragToDesktopStateListener {
- override fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction) {
- removeVisualIndicator(tx)
+ override fun onCommitToDesktopAnimationStart() {
+ removeVisualIndicator()
}
- override fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction) {
- removeVisualIndicator(tx)
+ override fun onCancelToDesktopAnimationEnd() {
+ removeVisualIndicator()
}
- private fun removeVisualIndicator(tx: SurfaceControl.Transaction) {
- visualIndicator?.fadeOutIndicator {
- visualIndicator?.releaseVisualIndicator(tx)
- visualIndicator = null
- }
+ private fun removeVisualIndicator() {
+ visualIndicator?.fadeOutIndicator { releaseVisualIndicator() }
}
}
@@ -1773,13 +1768,8 @@ class DesktopTasksController(
}
fun releaseVisualIndicator() {
- val t = SurfaceControl.Transaction()
- visualIndicator?.releaseVisualIndicator(t)
+ visualIndicator?.releaseVisualIndicator()
visualIndicator = null
- syncQueue.runInSync { transaction ->
- transaction.merge(t)
- t.close()
- }
}
override fun getContext(): Context = context
@@ -2757,6 +2747,8 @@ class DesktopTasksController(
val indicator =
visualIndicator
?: DesktopModeVisualIndicator(
+ desktopExecutor,
+ mainExecutor,
syncQueue,
taskInfo,
displayController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 1aa081340bf9..fc29498291da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -642,7 +642,7 @@ sealed class DragToDesktopTransitionHandler(
startPosition.y.toInt() + unscaledStartHeight,
)
- dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
+ dragToDesktopStateListener?.onCommitToDesktopAnimationStart()
// Accept the merge by applying the merging transaction (applied by #showResizeVeil)
// and finish callback. Show the veil and position the task at the first frame before
// starting the final animation.
@@ -773,7 +773,7 @@ sealed class DragToDesktopTransitionHandler(
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
- dragToDesktopStateListener?.onCancelToDesktopAnimationEnd(tx)
+ dragToDesktopStateListener?.onCancelToDesktopAnimationEnd()
// Start the cancel transition to restore order.
startCancelDragToDesktopTransition()
}
@@ -866,9 +866,9 @@ sealed class DragToDesktopTransitionHandler(
)
interface DragToDesktopStateListener {
- fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction)
+ fun onCommitToDesktopAnimationStart()
- fun onCancelToDesktopAnimationEnd(tx: SurfaceControl.Transaction)
+ fun onCancelToDesktopAnimationEnd()
}
sealed class TransitionState {
@@ -1081,7 +1081,7 @@ constructor(
val startBoundsWithOffset =
Rect(startBounds).apply { offset(startPosition.x.toInt(), startPosition.y.toInt()) }
- dragToDesktopStateListener?.onCommitToDesktopAnimationStart(startTransaction)
+ dragToDesktopStateListener?.onCommitToDesktopAnimationStart()
// Accept the merge by applying the merging transaction (applied by #showResizeVeil)
// and finish callback. Show the veil and position the task at the first frame before
// starting the final animation.
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 f7f87ed63003..5ae1fca73d4e 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
@@ -50,9 +50,11 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.transition.Transitions;
+import kotlin.Unit;
+import kotlin.jvm.functions.Function0;
+
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
import java.util.function.Supplier;
@@ -69,7 +71,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
@ShellMainThread
private final Handler mHandler;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
- private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
+ private Function0<Unit> mOnAnimationFinishedCallback;
private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
private Point mPosition;
@@ -106,7 +108,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
*/
public void startTransition(@NonNull DesktopModeTransitionSource transitionSource,
@NonNull WindowContainerTransaction wct, Point position,
- Consumer<SurfaceControl.Transaction> onAnimationEndCallback) {
+ Function0<Unit> onAnimationEndCallback) {
mPosition = position;
mOnAnimationFinishedCallback = onAnimationEndCallback;
final IBinder token = mTransitions.startTransition(getExitTransitionType(transitionSource),
@@ -192,7 +194,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
@Override
public void onAnimationEnd(Animator animation) {
if (mOnAnimationFinishedCallback != null) {
- mOnAnimationFinishedCallback.accept(finishT);
+ mOnAnimationFinishedCallback.invoke();
}
mInteractionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_EXIT_MODE);
mTransitions.getMainExecutor().execute(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
new file mode 100644
index 000000000000..9625e48d5bd0
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/VisualIndicatorViewContainer.kt
@@ -0,0 +1,490 @@
+/*
+ * Copyright (C) 2025 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.desktopmode
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.RectEvaluator
+import android.animation.ValueAnimator
+import android.app.ActivityManager
+import android.content.Context
+import android.graphics.PixelFormat
+import android.graphics.Rect
+import android.graphics.drawable.LayerDrawable
+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 android.view.animation.DecelerateInterpolator
+import com.android.internal.annotations.VisibleForTesting
+import com.android.window.flags.Flags
+import com.android.wm.shell.R
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
+import com.android.wm.shell.shared.annotations.ShellDesktopThread
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider
+import com.android.wm.shell.windowdecor.WindowDecoration.SurfaceControlViewHostFactory
+
+/**
+ * Container for the view / viewhost of the indicator, ensuring it is created / animated off the
+ * main thread.
+ */
+private class VisualIndicatorViewContainer
+@JvmOverloads
+constructor(
+ @ShellDesktopThread private val desktopExecutor: ShellExecutor,
+ @ShellMainThread private val mainExecutor: ShellExecutor,
+ private val indicatorBuilder: SurfaceControl.Builder,
+ private val syncQueue: SyncTransactionQueue,
+ private val surfaceControlViewHostFactory: SurfaceControlViewHostFactory =
+ object : SurfaceControlViewHostFactory {},
+ private val bubbleBoundsProvider: BubbleDropTargetBoundsProvider?,
+) {
+ @VisibleForTesting var indicatorView: View? = null
+ private var indicatorViewHost: SurfaceControlViewHost? = null
+ // Below variables and the SyncTransactionQueue are the only variables that should
+ // be accessed from shell main thread. Everything else should be used exclusively
+ // from the desktop thread.
+ private var indicatorLeash: SurfaceControl? = null
+ private var isReleased = false
+
+ /** Create a fullscreen indicator with no animation */
+ @ShellMainThread
+ fun createView(
+ context: Context,
+ display: Display,
+ layout: DisplayLayout,
+ taskInfo: ActivityManager.RunningTaskInfo,
+ taskSurface: SurfaceControl,
+ ) {
+ if (isReleased) return
+ desktopExecutor.execute {
+ val resources = context.resources
+ val metrics = resources.displayMetrics
+ val screenWidth: Int
+ val screenHeight: Int
+ if (Flags.enableBugFixesForSecondaryDisplay()) {
+ screenWidth = layout.width()
+ screenHeight = layout.height()
+ } else {
+ screenWidth = metrics.widthPixels
+ screenHeight = metrics.heightPixels
+ }
+ indicatorView = View(context)
+ val leash =
+ indicatorBuilder
+ .setName("Desktop Mode Visual Indicator")
+ .setContainerLayer()
+ .setCallsite("DesktopModeVisualIndicator.createView")
+ .build()
+ val lp =
+ WindowManager.LayoutParams(
+ screenWidth,
+ screenHeight,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT,
+ )
+ lp.title = "Desktop Mode Visual Indicator"
+ lp.setTrustedOverlay()
+ lp.inputFeatures =
+ lp.inputFeatures or WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL
+ val windowManager =
+ WindowlessWindowManager(
+ taskInfo.configuration,
+ leash,
+ /* hostInputTransferToken= */ null,
+ )
+ indicatorViewHost =
+ surfaceControlViewHostFactory.create(
+ context,
+ display,
+ windowManager,
+ "VisualIndicatorViewContainer",
+ )
+ indicatorView?.let { indicatorViewHost?.setView(it, lp) }
+ showIndicator(taskSurface, leash)
+ }
+ }
+
+ private fun showIndicator(taskSurface: SurfaceControl, leash: SurfaceControl) {
+ mainExecutor.execute {
+ indicatorLeash = leash
+ val t = SurfaceControl.Transaction()
+ t.show(indicatorLeash)
+ // We want this indicator to be behind the dragged task, but in front of all others.
+ t.setRelativeLayer(indicatorLeash, taskSurface, -1)
+ syncQueue.runInSync { transaction: SurfaceControl.Transaction ->
+ transaction.merge(t)
+ t.close()
+ }
+ }
+ }
+
+ @VisibleForTesting
+ fun getIndicatorBounds(): Rect {
+ return indicatorView?.background?.getBounds() ?: Rect()
+ }
+
+ /**
+ * Takes existing indicator and animates it to bounds reflecting a new indicator type. Should
+ * only be called from the main thread.
+ */
+ @ShellMainThread
+ fun transitionIndicator(
+ taskInfo: ActivityManager.RunningTaskInfo,
+ displayController: DisplayController,
+ currentType: IndicatorType,
+ newType: IndicatorType,
+ ) {
+ if (currentType == newType || isReleased) return
+ desktopExecutor.execute {
+ val layout =
+ displayController.getDisplayLayout(taskInfo.displayId)
+ ?: error("Expected to find DisplayLayout for taskId${taskInfo.taskId}.")
+ if (currentType == IndicatorType.NO_INDICATOR) {
+ fadeInIndicator(layout, newType)
+ } else if (newType == IndicatorType.NO_INDICATOR) {
+ fadeOutIndicator(layout, currentType, /* finishCallback= */ null)
+ } else {
+ val animStartType = IndicatorType.valueOf(currentType.name)
+ val animator =
+ indicatorView?.let {
+ VisualIndicatorAnimator.animateIndicatorType(
+ it,
+ layout,
+ animStartType,
+ newType,
+ bubbleBoundsProvider,
+ )
+ } ?: return@execute
+ animator.start()
+ }
+ }
+ }
+
+ /**
+ * Fade indicator in as provided type. Animator fades it in while expanding the bounds outwards.
+ */
+ @VisibleForTesting
+ fun fadeInIndicator(layout: DisplayLayout, type: IndicatorType) {
+ desktopExecutor.assertCurrentThread()
+ indicatorView?.let {
+ it.setBackgroundResource(R.drawable.desktop_windowing_transition_background)
+ val animator =
+ VisualIndicatorAnimator.fadeBoundsIn(it, type, layout, bubbleBoundsProvider)
+ animator.start()
+ }
+ }
+
+ /**
+ * Fade out indicator without fully releasing it. Animator fades it out while shrinking bounds.
+ *
+ * @param finishCallback called when animation ends or gets cancelled
+ */
+ fun fadeOutIndicator(
+ layout: DisplayLayout,
+ currentType: IndicatorType,
+ finishCallback: Runnable?,
+ ) {
+ if (currentType == IndicatorType.NO_INDICATOR) {
+ // In rare cases, fade out can be requested before the indicator has determined its
+ // initial type and started animating in. In this case, no animator is needed.
+ finishCallback?.run()
+ return
+ }
+ desktopExecutor.execute {
+ indicatorView?.let {
+ val animStartType = IndicatorType.valueOf(currentType.name)
+ val animator =
+ VisualIndicatorAnimator.fadeBoundsOut(
+ it,
+ animStartType,
+ layout,
+ bubbleBoundsProvider,
+ )
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ if (finishCallback != null) {
+ mainExecutor.execute(finishCallback)
+ }
+ }
+ }
+ )
+ animator.start()
+ }
+ }
+ }
+
+ /** Release the indicator and its components when it is no longer needed. */
+ @ShellMainThread
+ fun releaseVisualIndicator() {
+ if (isReleased) return
+ desktopExecutor.execute {
+ indicatorViewHost?.release()
+ indicatorViewHost = null
+ }
+ indicatorLeash?.let {
+ val tx = SurfaceControl.Transaction()
+ tx.remove(it)
+ indicatorLeash = null
+ syncQueue.runInSync { transaction: SurfaceControl.Transaction ->
+ transaction.merge(tx)
+ tx.close()
+ }
+ }
+ isReleased = true
+ }
+
+ /**
+ * Animator for Desktop Mode transitions which supports bounds and alpha animation. Functions
+ * should only be called from the desktop executor.
+ */
+ @VisibleForTesting
+ class VisualIndicatorAnimator(view: View, startBounds: Rect, endBounds: Rect) :
+ ValueAnimator() {
+ /**
+ * Determines how this animator will interact with the view's alpha: Fade in, fade out, or
+ * no change to alpha
+ */
+ private enum class AlphaAnimType {
+ ALPHA_FADE_IN_ANIM,
+ ALPHA_FADE_OUT_ANIM,
+ ALPHA_NO_CHANGE_ANIM,
+ }
+
+ private val indicatorView: View = view
+ @VisibleForTesting val indicatorStartBounds = Rect(startBounds)
+ @VisibleForTesting val indicatorEndBounds = endBounds
+ private val mRectEvaluator: RectEvaluator
+
+ init {
+ setFloatValues(0f, 1f)
+ mRectEvaluator = RectEvaluator(Rect())
+ }
+
+ /**
+ * Update bounds of view based on current animation fraction. Use of delta is to animate
+ * bounds independently, in case we need to run multiple animations simultaneously.
+ *
+ * @param fraction fraction to use, compared against previous fraction
+ * @param view the view to update
+ */
+ @ShellDesktopThread
+ private fun updateBounds(fraction: Float, view: View?) {
+ if (indicatorStartBounds == indicatorEndBounds) {
+ return
+ }
+ val currentBounds =
+ mRectEvaluator.evaluate(fraction, indicatorStartBounds, indicatorEndBounds)
+ view?.background?.bounds = currentBounds
+ }
+
+ /**
+ * Fade in the fullscreen indicator
+ *
+ * @param fraction current animation fraction
+ */
+ @ShellDesktopThread
+ private fun updateIndicatorAlpha(fraction: Float, view: View?) {
+ val drawable = view?.background as LayerDrawable
+ drawable.findDrawableByLayerId(R.id.indicator_stroke).alpha =
+ (MAXIMUM_OPACITY * fraction).toInt()
+ drawable.findDrawableByLayerId(R.id.indicator_solid).alpha =
+ (MAXIMUM_OPACITY * fraction * INDICATOR_FINAL_OPACITY).toInt()
+ }
+
+ companion object {
+ private const val FULLSCREEN_INDICATOR_DURATION = 200
+ private const val FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f
+ private const val INDICATOR_FINAL_OPACITY = 0.35f
+ private const val MAXIMUM_OPACITY = 255
+
+ @ShellDesktopThread
+ fun fadeBoundsIn(
+ view: View,
+ type: IndicatorType,
+ displayLayout: DisplayLayout,
+ bubbleBoundsProvider: BubbleDropTargetBoundsProvider?,
+ ): VisualIndicatorAnimator {
+ val endBounds = getIndicatorBounds(displayLayout, type, bubbleBoundsProvider)
+ val startBounds = getMinBounds(endBounds)
+ view.background.bounds = startBounds
+
+ val animator = VisualIndicatorAnimator(view, startBounds, endBounds)
+ animator.interpolator = DecelerateInterpolator()
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM)
+ return animator
+ }
+
+ @ShellDesktopThread
+ fun fadeBoundsOut(
+ view: View,
+ type: IndicatorType,
+ displayLayout: DisplayLayout,
+ bubbleBoundsProvider: BubbleDropTargetBoundsProvider?,
+ ): VisualIndicatorAnimator {
+ val startBounds = getIndicatorBounds(displayLayout, type, bubbleBoundsProvider)
+ val endBounds = getMinBounds(startBounds)
+ view.background.bounds = startBounds
+
+ val animator = VisualIndicatorAnimator(view, startBounds, endBounds)
+ animator.interpolator = DecelerateInterpolator()
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_OUT_ANIM)
+ return animator
+ }
+
+ /**
+ * Create animator for visual indicator changing type (i.e., fullscreen to freeform,
+ * freeform to split, etc.)
+ *
+ * @param view the view for this indicator
+ * @param displayLayout information about the display the transitioning task is
+ * currently on
+ * @param origType the original indicator type
+ * @param newType the new indicator type
+ * @param desktopExecutor: the executor for the ShellDesktopThread; should be the only
+ * thread this function runs on
+ */
+ @ShellDesktopThread
+ fun animateIndicatorType(
+ view: View,
+ displayLayout: DisplayLayout,
+ origType: IndicatorType,
+ newType: IndicatorType,
+ bubbleBoundsProvider: BubbleDropTargetBoundsProvider?,
+ ): VisualIndicatorAnimator {
+ val startBounds = getIndicatorBounds(displayLayout, origType, bubbleBoundsProvider)
+ val endBounds = getIndicatorBounds(displayLayout, newType, bubbleBoundsProvider)
+ val animator = VisualIndicatorAnimator(view, startBounds, endBounds)
+ animator.interpolator = DecelerateInterpolator()
+ setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_NO_CHANGE_ANIM)
+ return animator
+ }
+
+ /** Calculates the bounds the indicator should have when fully faded in. */
+ private fun getIndicatorBounds(
+ layout: DisplayLayout,
+ type: IndicatorType,
+ bubbleBoundsProvider: BubbleDropTargetBoundsProvider?,
+ ): Rect {
+ val desktopStableBounds = Rect()
+ layout.getStableBounds(desktopStableBounds)
+ val padding = desktopStableBounds.top
+ when (type) {
+ IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+ desktopStableBounds.top += padding
+ desktopStableBounds.bottom -= padding
+ desktopStableBounds.left += padding
+ desktopStableBounds.right -= padding
+ return desktopStableBounds
+ }
+
+ IndicatorType.TO_DESKTOP_INDICATOR -> {
+ val adjustmentPercentage =
+ (1f - DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE)
+ return Rect(
+ (adjustmentPercentage * desktopStableBounds.width() / 2).toInt(),
+ (adjustmentPercentage * desktopStableBounds.height() / 2).toInt(),
+ (desktopStableBounds.width() -
+ (adjustmentPercentage * desktopStableBounds.width() / 2))
+ .toInt(),
+ (desktopStableBounds.height() -
+ (adjustmentPercentage * desktopStableBounds.height() / 2))
+ .toInt(),
+ )
+ }
+
+ IndicatorType.TO_SPLIT_LEFT_INDICATOR ->
+ return Rect(
+ padding,
+ padding,
+ desktopStableBounds.width() / 2 - padding,
+ desktopStableBounds.height(),
+ )
+
+ IndicatorType.TO_SPLIT_RIGHT_INDICATOR ->
+ return Rect(
+ desktopStableBounds.width() / 2 + padding,
+ padding,
+ desktopStableBounds.width() - padding,
+ desktopStableBounds.height(),
+ )
+ IndicatorType.TO_BUBBLE_LEFT_INDICATOR ->
+ return bubbleBoundsProvider?.getBubbleBarExpandedViewDropTargetBounds(
+ /* onLeft= */ true
+ ) ?: Rect()
+ IndicatorType.TO_BUBBLE_RIGHT_INDICATOR ->
+ return bubbleBoundsProvider?.getBubbleBarExpandedViewDropTargetBounds(
+ /* onLeft= */ false
+ ) ?: Rect()
+ else -> throw IllegalArgumentException("Invalid indicator type provided.")
+ }
+ }
+
+ /** Add necessary listener for animation of indicator */
+ private fun setupIndicatorAnimation(
+ animator: VisualIndicatorAnimator,
+ animType: AlphaAnimType,
+ ) {
+ animator.addUpdateListener { a: ValueAnimator ->
+ animator.updateBounds(a.animatedFraction, animator.indicatorView)
+ if (animType == AlphaAnimType.ALPHA_FADE_IN_ANIM) {
+ animator.updateIndicatorAlpha(a.animatedFraction, animator.indicatorView)
+ } else if (animType == AlphaAnimType.ALPHA_FADE_OUT_ANIM) {
+ animator.updateIndicatorAlpha(
+ 1 - a.animatedFraction,
+ animator.indicatorView,
+ )
+ }
+ }
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ animator.indicatorView.background.bounds = animator.indicatorEndBounds
+ }
+ }
+ )
+ animator.setDuration(FULLSCREEN_INDICATOR_DURATION.toLong())
+ }
+
+ /**
+ * Return the minimum bounds of a visual indicator, to be used at the end of fading out
+ * and the start of fading in.
+ */
+ private fun getMinBounds(maxBounds: Rect): Rect {
+ return Rect(
+ (maxBounds.left + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width()))
+ .toInt(),
+ (maxBounds.top + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height()))
+ .toInt(),
+ (maxBounds.right - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width()))
+ .toInt(),
+ (maxBounds.bottom - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height()))
+ .toInt(),
+ )
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 76005c22972a..25dadfde274d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -911,7 +911,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
}
}
- @VisibleForTesting
public interface SurfaceControlViewHostFactory {
default SurfaceControlViewHost create(Context c, Display d, WindowlessWindowManager wmm) {
return new SurfaceControlViewHost(c, d, wmm, "WindowDecoration");
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 8f45cf4d08fc..20d50aa32f7f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -23,6 +23,7 @@ import android.graphics.Rect
import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
+import android.view.Display
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
import com.android.internal.policy.SystemBarUtils
@@ -30,6 +31,7 @@ import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.SyncTransactionQueue
@@ -60,18 +62,22 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
private lateinit var taskInfo: RunningTaskInfo
@Mock private lateinit var syncQueue: SyncTransactionQueue
@Mock private lateinit var displayController: DisplayController
+ @Mock private lateinit var display: Display
@Mock private lateinit var taskSurface: SurfaceControl
@Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var displayLayout: DisplayLayout
@Mock private lateinit var bubbleBoundsProvider: BubbleDropTargetBoundsProvider
private lateinit var visualIndicator: DesktopModeVisualIndicator
+ private val desktopExecutor = TestShellExecutor()
+ private val mainExecutor = TestShellExecutor()
@Before
fun setUp() {
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
+ whenever(displayController.getDisplay(anyInt())).thenReturn(display)
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
whenever(displayController.getDisplay(anyInt())).thenReturn(mContext.display)
whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(any()))
@@ -305,7 +311,11 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(/* onLeft= */ true))
.thenReturn(dropTargetBounds)
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
visualIndicator.updateIndicatorType(PointF(100f, 1500f))
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
animatorTestRule.advanceTimeBy(200)
@@ -322,7 +332,11 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
whenever(bubbleBoundsProvider.getBubbleBarExpandedViewDropTargetBounds(/* onLeft= */ false))
.thenReturn(dropTargetBounds)
createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
visualIndicator.updateIndicatorType(PointF(2300f, 1500f))
+ desktopExecutor.flushAll()
+ mainExecutor.flushAll()
animatorTestRule.advanceTimeBy(200)
@@ -332,6 +346,8 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
visualIndicator =
DesktopModeVisualIndicator(
+ desktopExecutor,
+ mainExecutor,
syncQueue,
taskInfo,
displayController,
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 be53272185b6..f6464b52efa0 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
@@ -29,6 +29,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
import android.content.ComponentName
+import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.content.pm.ActivityInfo.CONFIG_DENSITY
@@ -52,6 +53,7 @@ import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableContext
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.view.DragEvent
import android.view.Gravity
@@ -210,6 +212,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock lateinit var shellController: ShellController
@Mock lateinit var displayController: DisplayController
@Mock lateinit var displayLayout: DisplayLayout
+ @Mock lateinit var display: Display
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@Mock lateinit var syncQueue: SyncTransactionQueue
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@@ -258,6 +261,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Mock private lateinit var userProfileContexts: UserProfileContexts
@Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
@Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var mockDisplayContext: Context
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -270,6 +274,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
private lateinit var spyContext: TestableContext
private val shellExecutor = TestShellExecutor()
+ private val bgExecutor = TestShellExecutor()
// Mock running tasks are registered here so we can get the list from mock shell task organizer
private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -326,6 +331,8 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
whenever(enterDesktopTransitionHandler.moveToDesktop(any(), any())).thenAnswer { Binder() }
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayController.getDisplayContext(anyInt())).thenReturn(mockDisplayContext)
+ whenever(displayController.getDisplay(anyInt())).thenReturn(display)
whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
(i.arguments.first() as Rect).set(STABLE_BOUNDS)
}
@@ -408,6 +415,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
recentsTransitionHandler,
multiInstanceHelper,
shellExecutor,
+ bgExecutor,
Optional.of(desktopTasksLimiter),
recentTasksController,
mockInteractionJankMonitor,