App icon transitions with app window in overview
Fixes: 123641382
Test: Swipe up and hold from app when SWIPE_HOME is enabled or swipe up from app when it's not
Change-Id: I1bd35b1b96d66a3996f9b24c9a7e896535fa1ca0
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
index 7dc58a5..7bea593 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/WindowTransformSwipeHandler.java
@@ -415,6 +415,7 @@
});
mRecentsView.setRecentsAnimationWrapper(mRecentsAnimationWrapper);
mRecentsView.setClipAnimationHelper(mClipAnimationHelper);
+ mRecentsView.setLiveTileOverlay(mLiveTileOverlay);
mActivity.getRootView().getOverlay().add(mLiveTileOverlay);
mStateCallback.setState(STATE_LAUNCHER_PRESENT);
@@ -822,6 +823,7 @@
setShelfState(ShelfAnimState.CANCEL, LINEAR, 0);
duration = Math.max(MIN_OVERSHOOT_DURATION, duration);
} else if (endTarget == RECENTS) {
+ mLiveTileOverlay.startIconAnimation();
mRecentsAnimationWrapper.enableInputProxy();
if (mRecentsView != null) {
duration = Math.max(duration, mRecentsView.getScroller().getDuration());
@@ -1172,7 +1174,7 @@
mActivityControlHelper.onSwipeUpComplete(mActivity);
// Animate the first icon.
- mRecentsView.animateUpRunningTaskIconScale();
+ mRecentsView.animateUpRunningTaskIconScale(mLiveTileOverlay.cancelIconAnimation());
mRecentsView.setSwipeDownShouldLaunchApp(true);
RecentsModel.INSTANCE.get(mContext).onOverviewShown(false, TAG);
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
index ab2b90f..a838797 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/LiveTileOverlay.java
@@ -1,5 +1,11 @@
package com.android.quickstep.views;
+import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.launcher3.anim.Interpolators.LINEAR;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
@@ -9,16 +15,37 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.util.FloatProperty;
+
+import com.android.launcher3.anim.Interpolators;
public class LiveTileOverlay extends Drawable {
+ private static final long ICON_ANIM_DURATION = 120;
+
+ private static final FloatProperty<LiveTileOverlay> PROGRESS =
+ new FloatProperty<LiveTileOverlay>("progress") {
+ @Override
+ public void setValue(LiveTileOverlay liveTileOverlay, float progress) {
+ liveTileOverlay.setIconAnimationProgress(progress);
+ }
+
+ @Override
+ public Float get(LiveTileOverlay liveTileOverlay) {
+ return liveTileOverlay.mIconAnimationProgress;
+ }
+ };
+
private final Paint mPaint = new Paint();
private Rect mBoundsRect = new Rect();
private RectF mCurrentRect;
private float mCornerRadius;
+ private Drawable mIcon;
+ private Animator mIconAnimator;
private boolean mDrawEnabled = true;
+ private float mIconAnimationProgress = 0f;
public LiveTileOverlay() {
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
@@ -35,6 +62,33 @@
invalidateSelf();
}
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
+ }
+
+ public void startIconAnimation() {
+ if (mIconAnimator != null) {
+ mIconAnimator.cancel();
+ }
+ // This animator must match the icon part of {@link TaskView#FOCUS_TRANSITION} animation.
+ mIconAnimator = ObjectAnimator.ofFloat(this, PROGRESS, 1);
+ mIconAnimator.setDuration(ICON_ANIM_DURATION).setInterpolator(LINEAR);
+ mIconAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mIconAnimator = null;
+ }
+ });
+ mIconAnimator.start();
+ }
+
+ public float cancelIconAnimation() {
+ if (mIconAnimator != null) {
+ mIconAnimator.cancel();
+ }
+ return mIconAnimationProgress;
+ }
+
public void setDrawEnabled(boolean drawEnabled) {
if (mDrawEnabled != drawEnabled) {
mDrawEnabled = drawEnabled;
@@ -46,6 +100,16 @@
public void draw(Canvas canvas) {
if (mCurrentRect != null && mDrawEnabled) {
canvas.drawRoundRect(mCurrentRect, mCornerRadius, mCornerRadius, mPaint);
+ if (mIcon != null && mIconAnimationProgress > 0f) {
+ canvas.save();
+ float scale = Interpolators.clampToProgress(FAST_OUT_SLOW_IN, 0f,
+ 1f).getInterpolation(mIconAnimationProgress);
+ canvas.translate(mCurrentRect.centerX() - mIcon.getBounds().width() / 2 * scale,
+ mCurrentRect.top - mIcon.getBounds().height() / 2 * scale);
+ canvas.scale(scale, scale);
+ mIcon.draw(canvas);
+ canvas.restore();
+ }
}
}
@@ -59,4 +123,9 @@
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
+
+ private void setIconAnimationProgress(float progress) {
+ mIconAnimationProgress = progress;
+ invalidateSelf();
+ }
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index 3e0e8ae..2583ffb 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -278,6 +278,7 @@
private final int mEmptyMessagePadding;
private boolean mShowEmptyMessage;
private Layout mEmptyTextLayout;
+ private LiveTileOverlay mLiveTileOverlay;
private BaseActivity.MultiWindowModeChangedListener mMultiWindowModeChangedListener =
(inMultiWindowMode) -> {
@@ -855,10 +856,15 @@
}
public void animateUpRunningTaskIconScale() {
+ animateUpRunningTaskIconScale(0);
+ }
+
+ public void animateUpRunningTaskIconScale(float startProgress) {
mRunningTaskIconScaledDown = false;
TaskView firstTask = getRunningTaskView();
if (firstTask != null) {
firstTask.animateIconScaleAndDimIntoView();
+ firstTask.setIconScaleAnimStartProgress(startProgress);
}
}
@@ -1567,6 +1573,14 @@
mClipAnimationHelper = clipAnimationHelper;
}
+ public void setLiveTileOverlay(LiveTileOverlay liveTileOverlay) {
+ mLiveTileOverlay = liveTileOverlay;
+ }
+
+ public void updateLiveTileIcon(Drawable icon) {
+ mLiveTileOverlay.setIcon(icon);
+ }
+
public void finishRecentsAnimation(boolean toRecents, Runnable onFinishComplete) {
if (mRecentsAnimationWrapper == null) {
if (onFinishComplete != null) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 419a666..9eec584 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -156,7 +156,8 @@
private float mZoomScale;
private float mFullscreenProgress;
- private Animator mIconAndDimAnimator;
+ private ObjectAnimator mIconAndDimAnimator;
+ private float mIconScaleAnimStartProgress = 0;
private float mFocusTransitionProgress = 1;
private boolean mShowScreenshot;
@@ -317,6 +318,9 @@
mIconLoadRequest = iconCache.updateIconInBackground(mTask,
(task) -> {
setIcon(task.icon);
+ if (isRunningTask()) {
+ getRecentsView().updateLiveTileIcon(task.icon);
+ }
mDigitalWellBeingToast.initialize(
mTask,
(saturation, contentDescription) -> {
@@ -380,11 +384,16 @@
mIconView.setScaleY(scale);
}
+ public void setIconScaleAnimStartProgress(float startProgress) {
+ mIconScaleAnimStartProgress = startProgress;
+ }
+
public void animateIconScaleAndDimIntoView() {
if (mIconAndDimAnimator != null) {
mIconAndDimAnimator.cancel();
}
mIconAndDimAnimator = ObjectAnimator.ofFloat(this, FOCUS_TRANSITION, 1);
+ mIconAndDimAnimator.setCurrentFraction(mIconScaleAnimStartProgress);
mIconAndDimAnimator.setDuration(DIM_ANIM_DURATION).setInterpolator(LINEAR);
mIconAndDimAnimator.addListener(new AnimatorListenerAdapter() {
@Override