diff options
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, |