diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java index 779009a6b0..5391a7909c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -24,6 +24,7 @@ import static com.android.window.flags.Flags.enableDesktopWindowingWallpaperActi import android.animation.Animator; import android.animation.AnimatorSet; +import android.graphics.Rect; import android.window.RemoteTransition; import androidx.annotation.NonNull; @@ -179,6 +180,24 @@ public class LauncherTaskbarUIController extends TaskbarUIController { placeholderDuration)); } + /** + * Returns the bounds of launcher's hotseat. + */ + public void getHotseatBounds(Rect hotseatBoundsOut) { + DeviceProfile launcherDP = mLauncher.getDeviceProfile(); + if (launcherDP.isQsbInline) { + // Not currently supported. + hotseatBoundsOut.setEmpty(); + return; + } + int left = (launcherDP.widthPx - launcherDP.getHotseatWidthPx() + - mLauncher.getHotseat().getUnusedHorizontalSpace()) / 2; + int right = left + launcherDP.getHotseatWidthPx(); + int bottom = launcherDP.getHotseatLayoutPadding(mLauncher).bottom; + int top = bottom - launcherDP.hotseatCellHeightPx; + hotseatBoundsOut.set(left, top, right, bottom); + } + /** * Should be called from onResume() and onPause(), and animates the Taskbar accordingly. */ diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java index 252f2a81f0..266d0b9c98 100644 --- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java @@ -207,8 +207,11 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT * Creates and returns a {@link RevealOutlineAnimation} Animator that updates the stashed handle * shape and size. When stashed, the shape is a thin rounded pill. When unstashed, the shape * morphs into the size of where the taskbar icons will be. + * + * @param taskbarToHotseatOffsets A Rect of offsets used to transform the bounds of the + * stashed handle to wrap around the hotseat items. */ - public Animator createRevealAnimToIsStashed(boolean isStashed) { + public Animator createRevealAnimToIsStashed(boolean isStashed, Rect taskbarToHotseatOffsets) { Rect visualBounds = new Rect(mControllers.taskbarViewController.getIconLayoutBounds()); float startRadius = mStashedHandleRadius; @@ -219,6 +222,13 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT visualBounds.bottom += heightDiff; startRadius = visualBounds.height() / 2f; + + // We use these offsets to create a larger stashed handle to wrap around the items + // of the hotseat. This is only used for certain animations. + visualBounds.top += taskbarToHotseatOffsets.top; + visualBounds.bottom += taskbarToHotseatOffsets.bottom; + visualBounds.left += taskbarToHotseatOffsets.left; + visualBounds.right += taskbarToHotseatOffsets.right; } final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider( diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 0de0550016..4534860e4b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -336,6 +336,38 @@ public class TaskbarActivityContext extends BaseTaskbarContext { dispatchDeviceProfileChanged(); } + /** + * Calculate the offsets needed to transform the transient taskbar bounds to the hotseat bounds. + * @return The offsets will be stored in a Rect + */ + public Rect calculateTaskbarToHotseatOffsets(Rect hotseatBounds) { + Rect taskbar = getTransientTaskbarBounds(); + Rect offsets = new Rect(); + + offsets.left = hotseatBounds.left - taskbar.left; + offsets.right = hotseatBounds.right - taskbar.right; + + int heightDiff = hotseatBounds.height() - taskbar.height(); + offsets.top = (taskbar.height() - heightDiff) / 2; + + int gleanedTaskbarPadding = (mDeviceProfile.taskbarHeight + - getTransientTaskbarBounds().height()) / 2; + offsets.left -= gleanedTaskbarPadding; + offsets.top -= gleanedTaskbarPadding; + offsets.right += gleanedTaskbarPadding; + + // Bottom is relative to the bottom of layout, so we can calculate it with padding included. + offsets.bottom = (hotseatBounds.height() - taskbar.height()) / 2; + + // Update bounds in taskbar background + if (hotseatBounds.isEmpty()) { + mDragLayer.getTaskbarToHotseatOffsetRect().setEmpty(); + } else { + mDragLayer.getTaskbarToHotseatOffsetRect().set(offsets); + } + return offsets; + } + /** * Copy the original DeviceProfile, match the number of hotseat icons and qsb width and update * the icon size @@ -1517,6 +1549,18 @@ public class TaskbarActivityContext extends BaseTaskbarContext { return AnimatorPlaybackController.wrap(fullAnimation, duration); } + /** + * Returns the bounds of launcher's hotseat (if exists). + */ + public void getHotseatBounds(Rect hotseatBoundsOut) { + TaskbarUIController uiController = mControllers.uiController; + if (uiController instanceof LauncherTaskbarUIController launcherController) { + launcherController.getHotseatBounds(hotseatBoundsOut); + } else { + hotseatBoundsOut.setEmpty(); + } + } + /** * Called when we determine the touchable region. * diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt index bafd05924f..bc609b4bde 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt @@ -21,6 +21,7 @@ import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Path +import android.graphics.Rect import android.graphics.RectF import com.android.app.animation.Interpolators import com.android.launcher3.R @@ -59,6 +60,9 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) { var translationYForSwipe = 0f var translationYForStash = 0f + // When not empty, we can use this to transform transient taskbar background to hotseat bounds. + val taskbarToHotseatOffsetRect = Rect() + private val transientBackgroundBounds = context.transientTaskbarBounds private val shadowAlpha: Float @@ -215,6 +219,12 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) { val radius = newBackgroundHeight / 2f val bottomMarginProgress = bottomMargin * ((1f - progress) / 2f) + // Used to transform the background so that it wraps around the items on the hotseat. + val hotseatOffsetLeft = taskbarToHotseatOffsetRect.left * progress + val hotseatOffsetTop = taskbarToHotseatOffsetRect.top * progress + val hotseatOffsetRight = taskbarToHotseatOffsetRect.right * progress + val hotseatOffsetBottom = taskbarToHotseatOffsetRect.bottom * progress + // Aligns the bottom with the bottom of the stashed handle. val bottom = canvas.height - bottomMargin + @@ -238,10 +248,10 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) { ) lastDrawnTransientRect.set( - transientBackgroundBounds.left + halfWidthDelta, - bottom - newBackgroundHeight, - transientBackgroundBounds.right - halfWidthDelta, - bottom + transientBackgroundBounds.left + halfWidthDelta + hotseatOffsetLeft, + bottom - newBackgroundHeight + hotseatOffsetTop, + transientBackgroundBounds.right - halfWidthDelta + hotseatOffsetRight, + bottom + hotseatOffsetBottom ) val horizontalInset = fullWidth * widthInsetPercentage lastDrawnTransientRect.inset(horizontalInset, 0f) diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java index 84874a9371..e7d2d1077c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java @@ -22,6 +22,7 @@ import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UN import android.content.Context; import android.graphics.Canvas; +import android.graphics.Rect; import android.graphics.RectF; import android.media.permission.SafeCloseable; import android.util.AttributeSet; @@ -260,6 +261,11 @@ public class TaskbarDragLayer extends BaseDragLayer { return mBackgroundRenderer.getLastDrawnTransientRect(); } + /** Returns the rect used to transform transient taskbar to the hotseat */ + public Rect getTaskbarToHotseatOffsetRect() { + return mBackgroundRenderer.getTaskbarToHotseatOffsetRect(); + } + @Override public boolean dispatchTouchEvent(MotionEvent ev) { TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev); diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java index 74d2d60014..59320381f2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java @@ -22,6 +22,7 @@ import static com.android.app.animation.Interpolators.FINAL_FRAME; import static com.android.app.animation.Interpolators.INSTANT; import static com.android.app.animation.Interpolators.LINEAR; import static com.android.internal.jank.InteractionJankMonitor.Configuration; +import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE; import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW; @@ -41,6 +42,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.app.RemoteAction; +import android.graphics.Rect; import android.graphics.drawable.Icon; import android.os.SystemClock; import android.util.Log; @@ -196,6 +198,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba * by not scaling the height of the taskbar background. */ private static final int TRANSITION_UNSTASH_SUW_MANUAL = 3; + private static final Rect EMPTY_RECT = new Rect(); @Retention(RetentionPolicy.SOURCE) @IntDef(value = { @@ -698,7 +701,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba } fullLengthAnimatorSet.play(mControllers.stashedHandleViewController - .createRevealAnimToIsStashed(isStashed)); + .createRevealAnimToIsStashed(isStashed, EMPTY_RECT)); // Return the stashed handle to its default scale in case it was changed as part of the // feedforward hint. Note that the reveal animation above also visually scales it. fullLengthAnimatorSet.play(mTaskbarStashedHandleHintScale.animateToValue(1f)); @@ -748,6 +751,19 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba } } + + Rect taskbarToHotseatOffsets = new Rect(); + if (enableScalingRevealHomeAnimation() && animationType == TRANSITION_HOME_TO_APP) { + Rect hotseatRect = new Rect(); + mActivity.getHotseatBounds(hotseatRect); + + // Calculate and store offsets so that we can sync with the taskbar stashed handle + taskbarToHotseatOffsets.set( + mActivity.calculateTaskbarToHotseatOffsets(hotseatRect)); + as.addListener(AnimatorListeners.forEndCallback( + () -> mActivity.calculateTaskbarToHotseatOffsets(EMPTY_RECT))); + } + play(as, mTaskbarStashedHandleAlpha.animateToValue(stashedHandleAlphaTarget), backgroundAndHandleAlphaStartDelay, backgroundAndHandleAlphaDuration, LINEAR); @@ -786,10 +802,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba } mControllers.taskbarViewController.addRevealAnimToIsStashed(skippable, isStashed, duration, - EMPHASIZED, animationType == TRANSITION_UNSTASH_SUW_MANUAL); + EMPHASIZED, animationType == TRANSITION_UNSTASH_SUW_MANUAL, + animationType == TRANSITION_HOME_TO_APP); play(skippable, mControllers.stashedHandleViewController - .createRevealAnimToIsStashed(isStashed), 0, duration, EMPHASIZED); + .createRevealAnimToIsStashed(isStashed, taskbarToHotseatOffsets), 0, duration, + EMPHASIZED); // Return the stashed handle to its default scale in case it was changed as part of the // feedforward hint. Note that the reveal animation above also visually scales it. diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java index 23495adcf7..f8e205b916 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java @@ -33,6 +33,7 @@ import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VAL import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_ALIGNMENT_ANIM; import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_PINNING_ANIM; import static com.android.launcher3.util.MultiTranslateDelegate.INDEX_TASKBAR_REVEAL_ANIM; +import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; import android.animation.Animator; import android.animation.AnimatorSet; @@ -551,7 +552,8 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar * @param interpolator The interpolator to use for all animations. */ public void addRevealAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration, - Interpolator interpolator, boolean dispatchOnAnimationStart) { + Interpolator interpolator, boolean dispatchOnAnimationStart, + boolean isHomeToAppAnimation) { AnimatorSet reveal = new AnimatorSet(); Rect stashedBounds = new Rect(); @@ -600,8 +602,21 @@ public class TaskbarViewController implements TaskbarControllers.LoggableTaskbar reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationX(INDEX_TASKBAR_REVEAL_ANIM), MULTI_PROPERTY_VALUE, transX) .setDuration(duration)); - reveal.play(ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM), - MULTI_PROPERTY_VALUE, transY)); + + if (enableScalingRevealHomeAnimation()) { + // Delay y-translation by 1 frame to keep icons within the bounds of the bg. + int delay = isHomeToAppAnimation ? getSingleFrameMs(mActivity) : 0; + ObjectAnimator yAnimator = + ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM), + MULTI_PROPERTY_VALUE, transY) + .setDuration(Math.max(0, duration - delay)); + yAnimator.setStartDelay(delay); + reveal.play(yAnimator); + } else { + reveal.play( + ObjectAnimator.ofFloat(mtd.getTranslationY(INDEX_TASKBAR_REVEAL_ANIM), + MULTI_PROPERTY_VALUE, transY)); + } as.addListener(forEndCallback(() -> mtd.setTranslation(INDEX_TASKBAR_REVEAL_ANIM, 0, 0))); } else { diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 0daabb1451..3a81b8f774 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -994,7 +994,6 @@ public class DeviceProfile { return mHotseatColumnSpan; } - @VisibleForTesting public int getHotseatWidthPx() { return mHotseatWidthPx; }