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 015d5c1705e7f..fb0a91f178025 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 @@ -16,10 +16,13 @@ package com.android.wm.shell.desktopmode; +import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.RectEvaluator; import android.animation.ValueAnimator; +import android.annotation.NonNull; import android.app.ActivityManager; import android.content.Context; import android.content.res.Resources; @@ -32,12 +35,12 @@ import android.view.View; import android.view.WindowManager; import android.view.WindowlessWindowManager; import android.view.animation.DecelerateInterpolator; -import android.widget.ImageView; import com.android.wm.shell.R; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; /** @@ -56,6 +59,9 @@ public class DesktopModeVisualIndicator { private final SyncTransactionQueue mSyncQueue; private SurfaceControlViewHost mViewHost; + private View mView; + private boolean mIsFullscreen; + public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue, ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController, Context context, SurfaceControl taskSurface, ShellTaskOrganizer taskOrganizer, @@ -67,21 +73,19 @@ public class DesktopModeVisualIndicator { mTaskSurface = taskSurface; mTaskOrganizer = taskOrganizer; mRootTdaOrganizer = taskDisplayAreaOrganizer; + createView(); } /** - * Create and animate the indicator for the exit desktop mode transition. + * Create a fullscreen indicator with no animation */ - public void createFullscreenIndicator() { + private void createView() { final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); final Resources resources = mContext.getResources(); final DisplayMetrics metrics = resources.getDisplayMetrics(); final int screenWidth = metrics.widthPixels; final int screenHeight = metrics.heightPixels; - final int padding = mDisplayController - .getDisplayLayout(mTaskInfo.displayId).stableInsets().top; - final ImageView v = new ImageView(mContext); - v.setImageResource(R.drawable.desktop_windowing_transition_background); + mView = new View(mContext); final SurfaceControl.Builder builder = new SurfaceControl.Builder(); mRootTdaOrganizer.attachToDisplayArea(mTaskInfo.displayId, builder); mLeash = builder @@ -101,7 +105,7 @@ public class DesktopModeVisualIndicator { mViewHost = new SurfaceControlViewHost(mContext, mDisplayController.getDisplay(mTaskInfo.displayId), windowManager, "FullscreenVisualIndicator"); - mViewHost.setView(v, lp); + 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); @@ -109,17 +113,56 @@ public class DesktopModeVisualIndicator { transaction.merge(t); t.close(); }); - final Rect startBounds = new Rect(padding, padding, - screenWidth - padding, screenHeight - padding); - final VisualIndicatorAnimator animator = VisualIndicatorAnimator.fullscreenIndicator(v, - startBounds); + } + + /** + * Create fullscreen indicator and fades it in. + */ + public void createFullscreenIndicator() { + mIsFullscreen = true; + mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background); + final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFullscreenAnimator( + mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); + animator.start(); + } + + /** + * Create a fullscreen indicator. Animator fades it in while expanding the bounds outwards. + */ + public void createFullscreenIndicatorWithAnimatedBounds() { + mIsFullscreen = true; + mView.setBackgroundResource(R.drawable.desktop_windowing_transition_background); + final VisualIndicatorAnimator animator = VisualIndicatorAnimator + .toFullscreenAnimatorWithAnimatedBounds(mView, + mDisplayController.getDisplayLayout(mTaskInfo.displayId)); + animator.start(); + } + + /** + * Takes existing fullscreen indicator and animates it to freeform bounds + */ + public void transitionFullscreenIndicatorToFreeform() { + mIsFullscreen = false; + final VisualIndicatorAnimator animator = VisualIndicatorAnimator.toFreeformAnimator( + mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); + animator.start(); + } + + /** + * Takes the existing freeform indicator and animates it to fullscreen + */ + public void transitionFreeformIndicatorToFullscreen() { + mIsFullscreen = true; + final VisualIndicatorAnimator animator = + VisualIndicatorAnimator.toFullscreenAnimatorWithAnimatedBounds( + mView, mDisplayController.getDisplayLayout(mTaskInfo.displayId)); animator.start(); } /** * Release the indicator and its components when it is no longer needed. */ - public void releaseFullscreenIndicator() { + public void releaseVisualIndicator() { if (mViewHost == null) return; if (mViewHost != null) { mViewHost.release(); @@ -136,20 +179,28 @@ public class DesktopModeVisualIndicator { }); } } + + /** + * Returns true if visual indicator is fullscreen + */ + public boolean isFullscreen() { + return mIsFullscreen; + } + /** * 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 SCALE_ADJUSTMENT_PERCENT = 0.015f; + private static final float FULLSCREEN_SCALE_ADJUSTMENT_PERCENT = 0.015f; private static final float INDICATOR_FINAL_OPACITY = 0.7f; - private final ImageView mView; + private final View mView; private final Rect mStartBounds; private final Rect mEndBounds; private final RectEvaluator mRectEvaluator; - private VisualIndicatorAnimator(ImageView view, Rect startBounds, + private VisualIndicatorAnimator(View view, Rect startBounds, Rect endBounds) { mView = view; mStartBounds = new Rect(startBounds); @@ -161,30 +212,66 @@ public class DesktopModeVisualIndicator { /** * Create animator for visual indicator of fullscreen transition * - * @param view the view for this indicator - * @param startBounds the starting bounds of the fullscreen indicator + * @param view the view for this indicator + * @param displayLayout information about the display the transitioning task is currently on */ - public static VisualIndicatorAnimator fullscreenIndicator(ImageView view, - Rect startBounds) { - view.getDrawable().setBounds(startBounds); - int width = startBounds.width(); - int height = startBounds.height(); - Rect endBounds = new Rect((int) (startBounds.left - (SCALE_ADJUSTMENT_PERCENT * width)), - (int) (startBounds.top - (SCALE_ADJUSTMENT_PERCENT * height)), - (int) (startBounds.right + (SCALE_ADJUSTMENT_PERCENT * width)), - (int) (startBounds.bottom + (SCALE_ADJUSTMENT_PERCENT * height))); - VisualIndicatorAnimator animator = new VisualIndicatorAnimator( - view, startBounds, endBounds); + public static VisualIndicatorAnimator toFullscreenAnimator(@NonNull View view, + @NonNull DisplayLayout displayLayout) { + final Rect bounds = getMaxBounds(displayLayout); + final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( + view, bounds, bounds); animator.setInterpolator(new DecelerateInterpolator()); - setupFullscreenIndicatorAnimation(animator); + setupIndicatorAnimation(animator); + return animator; + } + + + /** + * Create animator for visual indicator of fullscreen transition + * + * @param view the view for this indicator + * @param displayLayout information about the display the transitioning task is currently on + */ + public static VisualIndicatorAnimator toFullscreenAnimatorWithAnimatedBounds( + @NonNull View view, @NonNull DisplayLayout displayLayout) { + final int padding = displayLayout.stableInsets().top; + Rect startBounds = new Rect(padding, padding, + displayLayout.width() - padding, displayLayout.height() - padding); + view.getBackground().setBounds(startBounds); + + final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( + view, startBounds, getMaxBounds(displayLayout)); + animator.setInterpolator(new DecelerateInterpolator()); + setupIndicatorAnimation(animator); return animator; } /** - * Add necessary listener for animation of fullscreen indicator + * Create animator for visual indicator of freeform transition + * + * @param view the view for this indicator + * @param displayLayout information about the display the transitioning task is currently on */ - private static void setupFullscreenIndicatorAnimation( - VisualIndicatorAnimator animator) { + public static VisualIndicatorAnimator toFreeformAnimator(@NonNull View view, + @NonNull DisplayLayout displayLayout) { + final float adjustmentPercentage = 1f - FINAL_FREEFORM_SCALE; + final int width = displayLayout.width(); + final int height = displayLayout.height(); + Rect endBounds = new Rect((int) (adjustmentPercentage * width / 2), + (int) (adjustmentPercentage * height / 2), + (int) (displayLayout.width() - (adjustmentPercentage * width / 2)), + (int) (displayLayout.height() - (adjustmentPercentage * height / 2))); + final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( + view, getMaxBounds(displayLayout), endBounds); + animator.setInterpolator(new DecelerateInterpolator()); + setupIndicatorAnimation(animator); + return animator; + } + + /** + * Add necessary listener for animation of indicator + */ + private static void setupIndicatorAnimation(@NonNull VisualIndicatorAnimator animator) { animator.addUpdateListener(a -> { if (animator.mView != null) { animator.updateBounds(a.getAnimatedFraction(), animator.mView); @@ -196,7 +283,7 @@ public class DesktopModeVisualIndicator { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - animator.mView.getDrawable().setBounds(animator.mEndBounds); + animator.mView.getBackground().setBounds(animator.mEndBounds); } }); animator.setDuration(FULLSCREEN_INDICATOR_DURATION); @@ -210,9 +297,12 @@ public class DesktopModeVisualIndicator { * @param fraction fraction to use, compared against previous fraction * @param view the view to update */ - private void updateBounds(float fraction, ImageView view) { + private void updateBounds(float fraction, View view) { + if (mStartBounds.equals(mEndBounds)) { + return; + } Rect currentBounds = mRectEvaluator.evaluate(fraction, mStartBounds, mEndBounds); - view.getDrawable().setBounds(currentBounds); + view.getBackground().setBounds(currentBounds); } /** @@ -223,5 +313,23 @@ public class DesktopModeVisualIndicator { private void updateIndicatorAlpha(float fraction, View view) { view.setAlpha(fraction * INDICATOR_FINAL_OPACITY); } + + /** + * Return the max bounds of a fullscreen indicator + */ + private static Rect getMaxBounds(@NonNull DisplayLayout displayLayout) { + final int padding = displayLayout.stableInsets().top; + final int width = displayLayout.width() - 2 * padding; + final int height = displayLayout.height() - 2 * padding; + Rect endBounds = new Rect((int) (padding + - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)), + (int) (padding + - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height)), + (int) (displayLayout.width() - padding + + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * width)), + (int) (displayLayout.height() - padding + + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * height))); + return endBounds; + } } } 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 c35cd5a8be028..670b24c176b5a 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 @@ -122,7 +122,7 @@ class DesktopTasksController( } /** Move a task to desktop */ - fun moveToDesktop(task: ActivityManager.RunningTaskInfo) { + fun moveToDesktop(task: RunningTaskInfo) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToDesktop: %d", task.taskId) val wct = WindowContainerTransaction() @@ -181,7 +181,7 @@ class DesktopTasksController( } /** Move a task to fullscreen */ - fun moveToFullscreen(task: ActivityManager.RunningTaskInfo) { + fun moveToFullscreen(task: RunningTaskInfo) { ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId) val wct = WindowContainerTransaction() @@ -206,7 +206,7 @@ class DesktopTasksController( } /** Move a task to the front **/ - fun moveTaskToFront(taskInfo: ActivityManager.RunningTaskInfo) { + fun moveTaskToFront(taskInfo: RunningTaskInfo) { val wct = WindowContainerTransaction() wct.reorder(taskInfo.token, true) if (Transitions.ENABLE_SHELL_TRANSITIONS) { @@ -273,7 +273,7 @@ class DesktopTasksController( request: TransitionRequestInfo ): WindowContainerTransaction? { // Check if we should skip handling this transition - val task: ActivityManager.RunningTaskInfo? = request.triggerTask + val task: RunningTaskInfo? = request.triggerTask val shouldHandleRequest = when { // Only handle open or to front transitions @@ -382,16 +382,15 @@ class DesktopTasksController( taskSurface: SurfaceControl, y: Float ) { - val statusBarHeight = displayController - .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0 if (taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + val statusBarHeight = getStatusBarHeight(taskInfo) if (y <= statusBarHeight && visualIndicator == null) { visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController, context, taskSurface, shellTaskOrganizer, rootTaskDisplayAreaOrganizer) - visualIndicator?.createFullscreenIndicator() + visualIndicator?.createFullscreenIndicatorWithAnimatedBounds() } else if (y > statusBarHeight && visualIndicator != null) { - visualIndicator?.releaseFullscreenIndicator() + visualIndicator?.releaseVisualIndicator() visualIndicator = null } } @@ -407,15 +406,72 @@ class DesktopTasksController( taskInfo: RunningTaskInfo, y: Float ) { - val statusBarHeight = displayController - .getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0 + val statusBarHeight = getStatusBarHeight(taskInfo) if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { - visualIndicator?.releaseFullscreenIndicator() + visualIndicator?.releaseVisualIndicator() visualIndicator = null moveToFullscreenWithAnimation(taskInfo) } } + /** + * Perform checks required on drag move. Create/release fullscreen indicator and transitions + * indicator to freeform or fullscreen dimensions as needed. + * + * @param taskInfo the task being dragged. + * @param taskSurface SurfaceControl of dragged task. + * @param y coordinate of dragged task. Used for checks against status bar height. + */ + fun onDragPositioningMoveThroughStatusBar( + taskInfo: RunningTaskInfo, + taskSurface: SurfaceControl, + y: Float + ) { + if (visualIndicator == null) { + visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, + displayController, context, taskSurface, shellTaskOrganizer, + rootTaskDisplayAreaOrganizer) + visualIndicator?.createFullscreenIndicator() + } + val indicator = visualIndicator ?: return + if (y >= getFreeformTransitionStatusBarDragThreshold(taskInfo)) { + if (indicator.isFullscreen) { + indicator.transitionFullscreenIndicatorToFreeform() + } + } else if (!indicator.isFullscreen) { + indicator.transitionFreeformIndicatorToFullscreen() + } + } + + /** + * Perform checks required when drag ends under status bar area. + * + * @param taskInfo the task being dragged. + * @param y height of drag, to be checked against status bar height. + */ + fun onDragPositioningEndThroughStatusBar( + taskInfo: RunningTaskInfo, + freeformBounds: Rect + ) { + moveToDesktopWithAnimation(taskInfo, freeformBounds) + visualIndicator?.releaseVisualIndicator() + visualIndicator = null + } + + + private fun getStatusBarHeight(taskInfo: RunningTaskInfo): Int { + return displayController.getDisplayLayout(taskInfo.displayId)?.stableInsets()?.top ?: 0 + } + + /** + * Returns the threshold at which we transition a task into freeform when dragging a + * fullscreen task down from the status bar + */ + private fun getFreeformTransitionStatusBarDragThreshold(taskInfo: RunningTaskInfo): Int { + return 2 * getStatusBarHeight(taskInfo) + } + + /** * Adds a listener to find out about changes in the visibility of freeform tasks. * diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index c0dcd0b68c6fd..f99821747fef7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -79,6 +79,7 @@ import java.util.function.Supplier; public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { private static final String TAG = "DesktopModeWindowDecorViewModel"; + private final DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory; private final ActivityTaskManager mActivityTaskManager; private final ShellTaskOrganizer mTaskOrganizer; @@ -542,7 +543,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mTransitionDragActive = false; final int statusBarHeight = getStatusBarHeight( relevantDecor.mTaskInfo.displayId); - if (ev.getY() > statusBarHeight) { + if (ev.getY() > 2 * statusBarHeight) { if (DesktopModeStatus.isProto2Enabled()) { mPauseRelayoutForTask = relevantDecor.mTaskInfo.taskId; centerAndMoveToDesktopWithAnimation(relevantDecor, ev); @@ -567,9 +568,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { return; } if (mTransitionDragActive) { - final int statusBarHeight = mDisplayController - .getDisplayLayout( - relevantDecor.mTaskInfo.displayId).stableInsets().top; + mDesktopTasksController.ifPresent( + c -> c.onDragPositioningMoveThroughStatusBar(relevantDecor.mTaskInfo, + relevantDecor.mTaskSurface, ev.getY())); + final int statusBarHeight = getStatusBarHeight( + relevantDecor.mTaskInfo.displayId); if (ev.getY() > statusBarHeight) { if (!mDragToDesktopAnimationStarted) { mDragToDesktopAnimationStarted = true; @@ -644,7 +647,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { @Override public void onAnimationEnd(Animator animation) { mDesktopTasksController.ifPresent( - c -> c.moveToDesktopWithAnimation(relevantDecor.mTaskInfo, + c -> c.onDragPositioningEndThroughStatusBar( + relevantDecor.mTaskInfo, calculateFreeformBounds(FINAL_FREEFORM_SCALE))); } });