Add support for animating non-running tasks
It is possible to animate a non-running task to home mid-quick switch, which was not properly supported, causing a janky animation. Added custom animations to handle this more smoothly.
Flag: ACONFIG com.android.launcher3.enable_additional_home_animations DISABLED
Fixes: 237638627
Test: swipe to home mid-quick switch on handheld/large screen in portrait/landscape from an app present/missing on the workscape in RTL/LTR
Change-Id: I89c9cf1ed1c0b88ff6c3ce71a663cb16e69d5843
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 5df29bd..dd78ca4 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -267,3 +267,13 @@
description: "Enables smartspace removal toggle"
bug: "303471576"
}
+
+flag {
+ name: "enable_additional_home_animations"
+ namespace: "launcher"
+ description: "Enables custom home animations for non-running tasks"
+ bug: "237638627"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 8e4dde2..2ce40b4 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -30,6 +30,7 @@
import static com.android.launcher3.BaseActivity.EVENT_STARTED;
import static com.android.launcher3.BaseActivity.INVISIBLE_BY_STATE_HANDLER;
import static com.android.launcher3.BaseActivity.STATE_HANDLER_INVISIBILITY_FLAGS;
+import static com.android.launcher3.Flags.enableAdditionalHomeAnimations;
import static com.android.launcher3.Flags.enableGridOnlyOverview;
import static com.android.launcher3.PagedView.INVALID_PAGE;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
@@ -152,6 +153,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
@@ -1480,8 +1482,12 @@
}
protected abstract HomeAnimationFactory createHomeAnimationFactory(
- ArrayList<IBinder> launchCookies, long duration, boolean isTargetTranslucent,
- boolean appCanEnterPip, RemoteAnimationTarget runningTaskTarget);
+ List<IBinder> launchCookies,
+ long duration,
+ boolean isTargetTranslucent,
+ boolean appCanEnterPip,
+ RemoteAnimationTarget runningTaskTarget,
+ @Nullable TaskView targetTaskView);
private final TaskStackChangeListener mActivityRestartListener = new TaskStackChangeListener() {
@Override
@@ -1555,9 +1561,16 @@
&& runningTaskTarget.allowEnterPip
&& runningTaskTarget.taskInfo.pictureInPictureParams != null
&& runningTaskTarget.taskInfo.pictureInPictureParams.isAutoEnterEnabled();
- HomeAnimationFactory homeAnimFactory =
- createHomeAnimationFactory(cookies, duration, isTranslucent, appCanEnterPip,
- runningTaskTarget);
+ HomeAnimationFactory homeAnimFactory = createHomeAnimationFactory(
+ cookies,
+ duration,
+ isTranslucent,
+ appCanEnterPip,
+ runningTaskTarget,
+ !enableAdditionalHomeAnimations()
+ || mRecentsView == null
+ || mRecentsView.getCurrentPage() == mRecentsView.getRunningTaskIndex()
+ ? null : mRecentsView.getCurrentPageTaskView());
SwipePipToHomeAnimator swipePipToHomeAnimator = !mIsSwipeForSplit && appCanEnterPip
? createWindowAnimationToPip(homeAnimFactory, runningTaskTarget, start)
: null;
diff --git a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
index 92cdf72..625b6c6 100644
--- a/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
+++ b/quickstep/src/com/android/quickstep/FallbackSwipeHandler.java
@@ -68,11 +68,12 @@
import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
+import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.recents.model.Task.TaskKey;
import com.android.systemui.shared.system.InputConsumerController;
import java.lang.ref.WeakReference;
-import java.util.ArrayList;
+import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
@@ -140,9 +141,13 @@
}
@Override
- protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies,
- long duration, boolean isTargetTranslucent, boolean appCanEnterPip,
- RemoteAnimationTarget runningTaskTarget) {
+ protected HomeAnimationFactory createHomeAnimationFactory(
+ List<IBinder> launchCookies,
+ long duration,
+ boolean isTargetTranslucent,
+ boolean appCanEnterPip,
+ RemoteAnimationTarget runningTaskTarget,
+ @Nullable TaskView targetTaskView) {
mAppCanEnterPip = appCanEnterPip;
if (appCanEnterPip) {
return new FallbackPipToHomeAnimationFactory();
@@ -380,7 +385,7 @@
}
@Override
- public void update(RectF currentRect, float progress, float radius) {
+ public void update(RectF currentRect, float progress, float radius, int overlayAlpha) {
if (mSurfaceControl != null) {
currentRect.roundOut(mTempRect);
Transaction t = new Transaction();
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index af02ccf..4b5a15d 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -43,6 +43,7 @@
import com.android.launcher3.states.StateAnimationConfig;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.ObjectWrapper;
+import com.android.launcher3.views.ClipIconView;
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.FloatingView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
@@ -55,7 +56,8 @@
import com.android.quickstep.views.TaskView;
import com.android.systemui.shared.system.InputConsumerController;
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
/**
* Temporary class to allow easier refactoring
@@ -72,9 +74,13 @@
@Override
- protected HomeAnimationFactory createHomeAnimationFactory(ArrayList<IBinder> launchCookies,
- long duration, boolean isTargetTranslucent, boolean appCanEnterPip,
- RemoteAnimationTarget runningTaskTarget) {
+ protected HomeAnimationFactory createHomeAnimationFactory(
+ List<IBinder> launchCookies,
+ long duration,
+ boolean isTargetTranslucent,
+ boolean appCanEnterPip,
+ RemoteAnimationTarget runningTaskTarget,
+ @Nullable TaskView targetTaskView) {
if (mContainer == null) {
mStateCallback.addChangeListener(STATE_LAUNCHER_PRESENT | STATE_HANDLER_INVALIDATED,
isPresent -> mRecentsView.startHome());
@@ -86,8 +92,14 @@
};
}
- final View workspaceView = findWorkspaceView(launchCookies,
- mRecentsView.getRunningTaskView());
+ TaskView sourceTaskView = mRecentsView == null && targetTaskView == null
+ ? null
+ : targetTaskView == null
+ ? mRecentsView.getRunningTaskView()
+ : targetTaskView;
+ final View workspaceView = findWorkspaceView(
+ targetTaskView == null ? launchCookies : Collections.emptyList(),
+ sourceTaskView);
boolean canUseWorkspaceView = workspaceView != null
&& workspaceView.isAttachedToWindow()
&& workspaceView.getHeight() > 0
@@ -100,16 +112,24 @@
}
if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
- return new LauncherHomeAnimationFactory();
+ return new LauncherHomeAnimationFactory() {
+
+ @Nullable
+ @Override
+ public TaskView getTargetTaskView() {
+ return targetTaskView;
+ }
+ };
}
if (workspaceView instanceof LauncherAppWidgetHostView) {
return createWidgetHomeAnimationFactory((LauncherAppWidgetHostView) workspaceView,
isTargetTranslucent, runningTaskTarget);
}
- return createIconHomeAnimationFactory(workspaceView);
+ return createIconHomeAnimationFactory(workspaceView, targetTaskView);
}
- private HomeAnimationFactory createIconHomeAnimationFactory(View workspaceView) {
+ private HomeAnimationFactory createIconHomeAnimationFactory(
+ View workspaceView, @Nullable TaskView targetTaskView) {
RectF iconLocation = new RectF();
FloatingIconView floatingIconView = getFloatingIconView(mContainer, workspaceView, null,
mContainer.getTaskbarUIController() == null
@@ -175,10 +195,35 @@
}
@Override
- public void update(RectF currentRect, float progress, float radius) {
- super.update(currentRect, progress, radius);
+ public void update(
+ RectF currentRect,
+ float progress,
+ float radius,
+ int overlayAlpha) {
floatingIconView.update(1f /* alpha */, currentRect, progress, windowAlphaThreshold,
- radius, false);
+ radius, false, overlayAlpha);
+ }
+
+ @Override
+ public boolean isAnimationReady() {
+ return floatingIconView.isLaidOut();
+ }
+
+ @Override
+ public void setTaskViewArtist(ClipIconView.TaskViewArtist taskViewArtist) {
+ super.setTaskViewArtist(taskViewArtist);
+ floatingIconView.setOverlayArtist(taskViewArtist);
+ }
+
+ @Override
+ public boolean isAnimatingIntoIcon() {
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public TaskView getTargetTaskView() {
+ return targetTaskView;
}
};
}
@@ -232,8 +277,8 @@
}
@Override
- public void update(RectF currentRect, float progress, float radius) {
- super.update(currentRect, progress, radius);
+ public void update(RectF currentRect, float progress, float radius, int overlayAlpha) {
+ super.update(currentRect, progress, radius, overlayAlpha);
final float fallbackBackgroundAlpha =
1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE);
final float foregroundAlpha =
@@ -254,13 +299,14 @@
* associated with the running task.
*/
@Nullable
- private View findWorkspaceView(ArrayList<IBinder> launchCookies, TaskView runningTaskView) {
+ private View findWorkspaceView(List<IBinder> launchCookies, TaskView sourceTaskView) {
if (mIsSwipingPipToHome) {
// Disable if swiping to PIP
return null;
}
- if (runningTaskView == null || runningTaskView.getTask() == null
- || runningTaskView.getTask().key.getComponent() == null) {
+ if (sourceTaskView == null
+ || sourceTaskView.getTask() == null
+ || sourceTaskView.getTask().key.getComponent() == null) {
// Disable if it's an invalid task
return null;
}
@@ -277,8 +323,8 @@
}
return mContainer.getFirstMatchForAppClose(launchCookieItemId,
- runningTaskView.getTask().key.getComponent().getPackageName(),
- UserHandle.of(runningTaskView.getTask().key.userId),
+ sourceTaskView.getTask().key.getComponent().getPackageName(),
+ UserHandle.of(sourceTaskView.getTask().key.userId),
false /* supportsAllAppsState */);
}
diff --git a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
index 5ff9787..fb54241 100644
--- a/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
+++ b/quickstep/src/com/android/quickstep/SwipeUpAnimationLogic.java
@@ -17,6 +17,7 @@
import static com.android.app.animation.Interpolators.ACCELERATE_1_5;
import static com.android.app.animation.Interpolators.LINEAR;
+import static com.android.launcher3.PagedView.INVALID_PAGE;
import android.animation.Animator;
import android.content.Context;
@@ -27,6 +28,7 @@
import android.view.RemoteAnimationTarget;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
@@ -36,6 +38,7 @@
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.touch.PagedOrientationHandler;
+import com.android.launcher3.views.ClipIconView;
import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle;
import com.android.quickstep.orientation.RecentsPagedOrientationHandler;
import com.android.quickstep.util.AnimatorControllerWithResistance;
@@ -46,6 +49,8 @@
import com.android.quickstep.util.TaskViewSimulator;
import com.android.quickstep.util.TransformParams;
import com.android.quickstep.util.TransformParams.BuilderProxy;
+import com.android.quickstep.views.RecentsView;
+import com.android.quickstep.views.TaskView;
import java.util.Arrays;
import java.util.function.Consumer;
@@ -201,7 +206,7 @@
public void setAnimation(RectFSpringAnim anim) { }
- public void update(RectF currentRect, float progress, float radius) { }
+ public void update(RectF currentRect, float progress, float radius, int overlayAlpha) { }
public void onCancel() { }
@@ -222,6 +227,33 @@
}
return Utilities.mapToRange(progress, start, end, 1, 0, ACCELERATE_1_5);
}
+
+ /**
+ * Sets a {@link com.android.launcher3.views.ClipIconView.TaskViewArtist} that should be
+ * used draw a {@link TaskView} during this home animation.
+ */
+ public void setTaskViewArtist(ClipIconView.TaskViewArtist taskViewArtist) { }
+
+ public boolean isAnimationReady() {
+ return true;
+ }
+
+ public boolean isAnimatingIntoIcon() {
+ return false;
+ }
+
+ @Nullable
+ public TaskView getTargetTaskView() {
+ return null;
+ }
+
+ public boolean isRtl() {
+ return Utilities.isRtl(mContext.getResources());
+ }
+
+ public boolean isPortrait() {
+ return !mDp.isLandscape && !mDp.isSeascape();
+ }
}
/**
@@ -276,9 +308,13 @@
for (int i = 0, mRemoteTargetHandlesLength = mRemoteTargetHandles.length;
i < mRemoteTargetHandlesLength; i++) {
RemoteTargetHandle remoteHandle = mRemoteTargetHandles[i];
- out[i] = getWindowAnimationToHomeInternal(homeAnimationFactory,
- targetRect, remoteHandle.getTransformParams(),
- remoteHandle.getTaskViewSimulator(), startRects[i], homeToWindowPositionMap[i]);
+ out[i] = getWindowAnimationToHomeInternal(
+ homeAnimationFactory,
+ targetRect,
+ remoteHandle.getTransformParams(),
+ remoteHandle.getTaskViewSimulator(),
+ startRects[i],
+ homeToWindowPositionMap[i]);
}
return out;
}
@@ -288,21 +324,39 @@
}
private RectFSpringAnim getWindowAnimationToHomeInternal(
- HomeAnimationFactory homeAnimationFactory, RectF targetRect,
- TransformParams transformParams, TaskViewSimulator taskViewSimulator,
- RectF startRect, Matrix homeToWindowPositionMap) {
+ HomeAnimationFactory homeAnimationFactory,
+ RectF targetRect,
+ TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator,
+ RectF startRect,
+ Matrix homeToWindowPositionMap) {
RectF cropRectF = new RectF(taskViewSimulator.getCurrentCropRect());
// Move the startRect to Launcher space as floatingIconView runs in Launcher
Matrix windowToHomePositionMap = new Matrix();
- // If the start rect ends up overshooting too much to the left/right offscreen, bring it
- // back to fullscreen. This can happen when the recentsScroll value isn't aligned with
- // the pageScroll value for a given taskView, see b/228829958#comment12
- mRemoteTargetHandles[0].getTaskViewSimulator().getOrientationState().getOrientationHandler()
- .fixBoundsForHomeAnimStartRect(startRect, mDp);
+ TaskView targetTaskView = homeAnimationFactory.getTargetTaskView();
+ if (targetTaskView == null) {
+ // If the start rect ends up overshooting too much to the left/right offscreen, bring it
+ // back to fullscreen. This can happen when the recentsScroll value isn't aligned with
+ // the pageScroll value for a given taskView, see b/228829958#comment12
+ mRemoteTargetHandles[0].getTaskViewSimulator()
+ .getOrientationState()
+ .getOrientationHandler()
+ .fixBoundsForHomeAnimStartRect(startRect, mDp);
+ }
homeToWindowPositionMap.invert(windowToHomePositionMap);
windowToHomePositionMap.mapRect(startRect);
+ RectF invariantStartRect = new RectF(startRect);
+
+ if (targetTaskView != null) {
+ Rect thumbnailBounds = new Rect();
+ targetTaskView.getThumbnailBounds(thumbnailBounds, /* relativeToDragLayer= */ true);
+
+ invariantStartRect = new RectF(thumbnailBounds);
+ invariantStartRect.offsetTo(startRect.left, thumbnailBounds.top);
+ startRect = new RectF(thumbnailBounds);
+ }
boolean useTaskbarHotseatParams = mDp.isTaskbarPresent
&& homeAnimationFactory.isInHotseat();
@@ -312,8 +366,12 @@
homeAnimationFactory.setAnimation(anim);
SpringAnimationRunner runner = new SpringAnimationRunner(
- homeAnimationFactory, cropRectF, homeToWindowPositionMap,
- transformParams, taskViewSimulator);
+ homeAnimationFactory,
+ cropRectF,
+ homeToWindowPositionMap,
+ transformParams,
+ taskViewSimulator,
+ invariantStartRect);
anim.addAnimatorListener(runner);
anim.addOnUpdateListener(runner);
return anim;
@@ -336,9 +394,30 @@
final float mStartRadius;
final float mEndRadius;
- SpringAnimationRunner(HomeAnimationFactory factory, RectF cropRectF,
- Matrix homeToWindowPositionMap, TransformParams transformParams,
- TaskViewSimulator taskViewSimulator) {
+ final RectF mRunningTaskViewStartRectF;
+ @Nullable
+ final TaskView mTargetTaskView;
+ final float mRunningTaskViewScrollOffset;
+ final float mTaskViewWidth;
+ final float mTaskViewHeight;
+ final boolean mIsPortrait;
+ final Rect mThumbnailStartBounds = new Rect();
+
+ // Store the mTargetTaskView view properties onAnimationStart so that we can reset them
+ // when cleaning up.
+ float mTaskViewAlpha;
+ float mTaskViewTranslationX;
+ float mTaskViewTranslationY;
+ float mTaskViewScaleX;
+ float mTaskViewScaleY;
+
+ SpringAnimationRunner(
+ HomeAnimationFactory factory,
+ RectF cropRectF,
+ Matrix homeToWindowPositionMap,
+ TransformParams transformParams,
+ TaskViewSimulator taskViewSimulator,
+ RectF invariantStartRect) {
mAnimationFactory = factory;
mHomeAnim = factory.createActivityAnimationToHome();
mCropRectF = cropRectF;
@@ -351,22 +430,62 @@
// rounding at the end of the animation.
mStartRadius = taskViewSimulator.getCurrentCornerRadius();
mEndRadius = factory.getEndRadius(cropRectF);
+
+ mRunningTaskViewStartRectF = invariantStartRect;
+ mTargetTaskView = factory.getTargetTaskView();
+ mTaskViewWidth = mTargetTaskView == null ? 0 : mTargetTaskView.getWidth();
+ mTaskViewHeight = mTargetTaskView == null ? 0 : mTargetTaskView.getHeight();
+ mIsPortrait = factory.isPortrait();
+ // Use the running task's start position to determine how much it needs to be offset
+ // to end up offscreen.
+ mRunningTaskViewScrollOffset = factory.isRtl()
+ ? (Math.min(0, -invariantStartRect.right))
+ : (Math.max(0, mDp.widthPx - invariantStartRect.left));
}
@Override
public void onUpdate(RectF currentRect, float progress) {
- mHomeAnim.setPlayFraction(progress);
- mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
-
- mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
float cornerRadius = Utilities.mapRange(progress, mStartRadius, mEndRadius);
float alpha = mAnimationFactory.getWindowAlpha(progress);
- mLocalTransformParams
- .setTargetAlpha(alpha)
- .setCornerRadius(cornerRadius);
- mLocalTransformParams.applySurfaceParams(mLocalTransformParams
- .createSurfaceParams(this));
- mAnimationFactory.update(currentRect, progress, mMatrix.mapRadius(cornerRadius));
+
+ mHomeAnim.setPlayFraction(progress);
+ if (mTargetTaskView == null) {
+ mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, currentRect);
+ mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
+ mLocalTransformParams
+ .setTargetAlpha(alpha)
+ .setCornerRadius(cornerRadius);
+ } else {
+ mHomeToWindowPositionMap.mapRect(mWindowCurrentRect, mRunningTaskViewStartRectF);
+ mWindowCurrentRect.offset(mRunningTaskViewScrollOffset * progress, 0f);
+ mMatrix.setRectToRect(mCropRectF, mWindowCurrentRect, ScaleToFit.FILL);
+ mLocalTransformParams.setCornerRadius(mStartRadius);
+ }
+
+ mLocalTransformParams.applySurfaceParams(
+ mLocalTransformParams.createSurfaceParams(this));
+ mAnimationFactory.update(
+ currentRect, progress, mMatrix.mapRadius(cornerRadius), (int) (alpha * 255));
+
+ if (mTargetTaskView == null) {
+ return;
+ }
+ if (mAnimationFactory.isAnimatingIntoIcon() && mAnimationFactory.isAnimationReady()) {
+ mTargetTaskView.setAlpha(0f);
+ return;
+ }
+ mTargetTaskView.setAlpha(mAnimationFactory.isAnimatingIntoIcon() ? 1f : alpha);
+ float width = mThumbnailStartBounds.width();
+ float height = mThumbnailStartBounds.height();
+ float scale = Math.min(currentRect.width(), currentRect.height())
+ / Math.min(width, height);
+
+ mTargetTaskView.setScaleX(scale);
+ mTargetTaskView.setScaleY(scale);
+ mTargetTaskView.setTranslationX(
+ currentRect.centerX() - mThumbnailStartBounds.centerX());
+ mTargetTaskView.setTranslationY(
+ currentRect.centerY() - mThumbnailStartBounds.centerY());
}
@Override
@@ -379,16 +498,71 @@
@Override
public void onCancel() {
+ cleanUp();
mAnimationFactory.onCancel();
}
@Override
public void onAnimationStart(Animator animation) {
+ setUp();
mHomeAnim.dispatchOnStart();
+ if (mTargetTaskView == null) {
+ return;
+ }
+ Rect thumbnailBounds = new Rect();
+ // Use bounds relative to mTargetTaskView since it will be scaled afterwards
+ mTargetTaskView.getThumbnailBounds(thumbnailBounds);
+ mAnimationFactory.setTaskViewArtist(new ClipIconView.TaskViewArtist(
+ mTargetTaskView::draw,
+ 0f,
+ -thumbnailBounds.top,
+ Math.min(mTaskViewHeight, mTaskViewWidth),
+ mIsPortrait));
+ }
+
+ private void setUp() {
+ if (mTargetTaskView == null) {
+ return;
+ }
+ RecentsView recentsView = mTargetTaskView.getRecentsView();
+ if (recentsView != null) {
+ recentsView.setOffsetMidpointIndexOverride(
+ recentsView.indexOfChild(mTargetTaskView));
+ }
+ mTargetTaskView.getThumbnailBounds(
+ mThumbnailStartBounds, /* relativeToDragLayer= */ true);
+ mTaskViewAlpha = mTargetTaskView.getAlpha();
+ if (mAnimationFactory.isAnimatingIntoIcon()) {
+ return;
+ }
+ mTaskViewTranslationX = mTargetTaskView.getTranslationX();
+ mTaskViewTranslationY = mTargetTaskView.getTranslationY();
+ mTaskViewScaleX = mTargetTaskView.getScaleX();
+ mTaskViewScaleY = mTargetTaskView.getScaleY();
+ }
+
+ private void cleanUp() {
+ if (mTargetTaskView == null) {
+ return;
+ }
+ RecentsView recentsView = mTargetTaskView.getRecentsView();
+ if (recentsView != null) {
+ recentsView.setOffsetMidpointIndexOverride(INVALID_PAGE);
+ }
+ mTargetTaskView.setAlpha(mTaskViewAlpha);
+ if (!mAnimationFactory.isAnimatingIntoIcon()) {
+ mTargetTaskView.setTranslationX(mTaskViewTranslationX);
+ mTargetTaskView.setTranslationY(mTaskViewTranslationY);
+ mTargetTaskView.setScaleX(mTaskViewScaleX);
+ mTargetTaskView.setScaleY(mTaskViewScaleY);
+ return;
+ }
+ mAnimationFactory.setTaskViewArtist(null);
}
@Override
public void onAnimationSuccess(Animator animator) {
+ cleanUp();
mHomeAnim.getAnimationPlayer().end();
}
}
diff --git a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
index d5cc447..ad13efb 100644
--- a/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
+++ b/quickstep/src/com/android/quickstep/interaction/SwipeUpGestureTutorialController.java
@@ -382,7 +382,7 @@
}
@Override
- public void update(RectF rect, float progress, float radius) {
+ public void update(RectF rect, float progress, float radius, int overlayAlpha) {
mFakeIconView.setVisibility(View.VISIBLE);
mFakeIconView.update(rect, progress,
1f - SHAPE_PROGRESS_DURATION /* shapeProgressStart */,
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index e797819..4915b62 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -141,9 +141,14 @@
}
@Override
- protected Unit updateBorderBounds(@NonNull Rect bounds) {
- bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(), mBackgroundView.getRight(),
- mBackgroundView.getBottom());
+ public Unit getThumbnailBounds(@NonNull Rect bounds, boolean relativeToDragLayer) {
+ if (relativeToDragLayer) {
+ mContainer.getDragLayer().getDescendantRectRelativeToSelf(mBackgroundView, bounds);
+ } else {
+ bounds.set(mBackgroundView.getLeft(), mBackgroundView.getTop(),
+ mBackgroundView.getRight(),
+ mBackgroundView.getBottom());
+ }
return Unit.INSTANCE;
}
diff --git a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
index c7a4203..b2a8503 100644
--- a/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/GroupedTaskView.java
@@ -32,6 +32,7 @@
import com.android.launcher3.util.SplitConfigurationOptions;
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
import com.android.launcher3.util.TransformingTouchDelegate;
+import com.android.launcher3.views.BaseDragLayer;
import com.android.quickstep.RecentsModel;
import com.android.quickstep.TaskIconCache;
import com.android.quickstep.TaskThumbnailCache;
@@ -92,27 +93,37 @@
}
@Override
- protected Unit updateBorderBounds(@NonNull Rect bounds) {
+ public Unit getThumbnailBounds(@NonNull Rect bounds, boolean relativeToDragLayer) {
if (mSplitBoundsConfig == null) {
- super.updateBorderBounds(bounds);
+ super.getThumbnailBounds(bounds, relativeToDragLayer);
return Unit.INSTANCE;
}
- bounds.set(
- Math.min(mTaskThumbnailViewDeprecated.getLeft() + Math.round(
- mTaskThumbnailViewDeprecated.getTranslationX()),
- mSnapshotView2.getLeft() + Math.round(mSnapshotView2.getTranslationX())),
- Math.min(mTaskThumbnailViewDeprecated.getTop() + Math.round(
- mTaskThumbnailViewDeprecated.getTranslationY()),
- mSnapshotView2.getTop() + Math.round(mSnapshotView2.getTranslationY())),
- Math.max(mTaskThumbnailViewDeprecated.getRight() + Math.round(
- mTaskThumbnailViewDeprecated.getTranslationX()),
- mSnapshotView2.getRight() + Math.round(mSnapshotView2.getTranslationX())),
- Math.max(mTaskThumbnailViewDeprecated.getBottom() + Math.round(
- mTaskThumbnailViewDeprecated.getTranslationY()),
- mSnapshotView2.getBottom() + Math.round(mSnapshotView2.getTranslationY())));
+ if (relativeToDragLayer) {
+ Rect firstThumbnailBounds = new Rect();
+ Rect secondThumbnailBounds = new Rect();
+ BaseDragLayer dragLayer = mContainer.getDragLayer();
+ dragLayer.getDescendantRectRelativeToSelf(
+ mTaskThumbnailViewDeprecated, firstThumbnailBounds);
+ dragLayer.getDescendantRectRelativeToSelf(mSnapshotView2, secondThumbnailBounds);
+
+ bounds.set(firstThumbnailBounds);
+ bounds.union(secondThumbnailBounds);
+ } else {
+ bounds.set(getSnapshotViewBounds(mTaskThumbnailViewDeprecated));
+ bounds.union(getSnapshotViewBounds(mSnapshotView2));
+ }
return Unit.INSTANCE;
}
+ private Rect getSnapshotViewBounds(@NonNull View snapshotView) {
+ int snapshotViewX = Math.round(snapshotView.getX());
+ int snapshotViewY = Math.round(snapshotView.getY());
+ return new Rect(snapshotViewX,
+ snapshotViewY,
+ snapshotViewX + snapshotView.getWidth(),
+ snapshotViewY + snapshotView.getHeight());
+ }
+
@Override
protected void onFinishInflate() {
super.onFinishInflate();
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 62fa6c8..077cd1b 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -770,6 +770,8 @@
// keeps track of the state of the filter for tasks in recents view
private final RecentsFilterState mFilterState = new RecentsFilterState();
+ private int mOffsetMidpointIndexOverride = INVALID_PAGE;
+
public RecentsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
BaseContainerInterface sizeStrategy) {
super(context, attrs, defStyleAttr);
@@ -4465,15 +4467,26 @@
setPivotY(mTempPointF.y);
}
+ /**
+ * Sets whether we should force-override the page offset mid-point to the current task, rather
+ * than the running task, when updating page offsets.
+ */
+ public void setOffsetMidpointIndexOverride(int offsetMidpointIndexOverride) {
+ mOffsetMidpointIndexOverride = offsetMidpointIndexOverride;
+ updatePageOffsets();
+ }
+
private void updatePageOffsets() {
float offset = mAdjacentPageHorizontalOffset;
float modalOffset = ACCELERATE_0_75.getInterpolation(mTaskModalness);
int count = getChildCount();
boolean showAsGrid = showAsGrid();
- TaskView runningTask = mRunningTaskViewId == -1 || !mRunningTaskTileHidden
+ TaskView runningTask = mRunningTaskViewId == INVALID_PAGE || !mRunningTaskTileHidden
? null : getRunningTaskView();
- int midpoint = runningTask == null ? -1 : indexOfChild(runningTask);
+ int midpoint = mOffsetMidpointIndexOverride == INVALID_PAGE
+ ? (runningTask == null ? INVALID_PAGE : indexOfChild(runningTask))
+ : mOffsetMidpointIndexOverride;
int modalMidpoint = getCurrentPage();
boolean isModalGridWithoutFocusedTask =
showAsGrid && enableGridOnlyOverview() && mTaskModalness > 0;
diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java
index 43965b2..089b0ee 100644
--- a/quickstep/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskView.java
@@ -443,7 +443,7 @@
/* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
/* borderWidthPx= */ context.getResources().getDimensionPixelSize(
R.dimen.keyboard_quick_switch_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
+ /* boundsBuilder= */ this::getThumbnailBounds,
/* targetView= */ this,
/* borderColor= */ styledAttrs.getColor(
R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR))
@@ -458,7 +458,7 @@
/* borderRadiusPx= */ (int) mCurrentFullscreenParams.mCornerRadius,
/* borderWidthPx= */ context.getResources().getDimensionPixelSize(
R.dimen.task_hover_border_width),
- /* boundsBuilder= */ this::updateBorderBounds,
+ /* boundsBuilder= */ this::getThumbnailBounds,
/* targetView= */ this,
/* borderColor= */ styledAttrs.getColor(
R.styleable.TaskView_hoverBorderColor, DEFAULT_BORDER_COLOR))
@@ -467,12 +467,23 @@
styledAttrs.recycle();
}
- protected Unit updateBorderBounds(@NonNull Rect bounds) {
+ /** Returns the thumbnail's bounds relative to this view. */
+ public Unit getThumbnailBounds(@NonNull Rect bounds) {
+ return getThumbnailBounds(bounds, false);
+ }
+
+ /** Returns the thumbnail's bounds, optionally relative to the screen. */
+ public Unit getThumbnailBounds(@NonNull Rect bounds, boolean relativeToDragLayer) {
View snapshotView = getSnapshotView();
- bounds.set(snapshotView.getLeft() + Math.round(snapshotView.getTranslationX()),
- snapshotView.getTop() + Math.round(snapshotView.getTranslationY()),
- snapshotView.getRight() + Math.round(snapshotView.getTranslationX()),
- snapshotView.getBottom() + Math.round(snapshotView.getTranslationY()));
+
+ if (relativeToDragLayer) {
+ mContainer.getDragLayer().getDescendantRectRelativeToSelf(snapshotView, bounds);
+ } else {
+ bounds.set(snapshotView.getLeft() + Math.round(snapshotView.getTranslationX()),
+ snapshotView.getTop() + Math.round(snapshotView.getTranslationY()),
+ snapshotView.getRight() + Math.round(snapshotView.getTranslationX()),
+ snapshotView.getBottom() + Math.round(snapshotView.getTranslationY()));
+ }
return Unit.INSTANCE;
}
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 172f968..325c1cd 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -40,6 +40,7 @@
import android.view.ViewOutlineProvider;
import androidx.annotation.Nullable;
+import androidx.core.util.Consumer;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.R;
@@ -74,6 +75,8 @@
private final Rect mOutline = new Rect();
private final Rect mFinalDrawableBounds = new Rect();
+ @Nullable private TaskViewArtist mTaskViewArtist;
+
public ClipIconView(Context context) {
this(context, null);
}
@@ -90,10 +93,28 @@
}
/**
+ * Sets a {@link TaskViewArtist} that will draw a {@link com.android.quickstep.views.TaskView}
+ * within the clip bounds of this view.
+ */
+ public void setTaskViewArtist(TaskViewArtist taskViewArtist) {
+ mTaskViewArtist = taskViewArtist;
+ invalidate();
+ }
+
+ /**
* Update the icon UI to match the provided parameters during an animation frame
*/
public void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
boolean isOpening, View container, DeviceProfile dp) {
+ update(rect, progress, shapeProgressStart, cornerRadius, isOpening, container, dp, 255);
+ }
+
+ /**
+ * Update the icon UI to match the provided parameters during an animation frame, optionally
+ * varying the alpha of the {@link TaskViewArtist}
+ */
+ public void update(RectF rect, float progress, float shapeProgressStart, float cornerRadius,
+ boolean isOpening, View container, DeviceProfile dp, int taskViewDrawAlpha) {
MarginLayoutParams lp = (MarginLayoutParams) container.getLayoutParams();
float dX = mIsRtl
@@ -107,6 +128,14 @@
float scaleX = rect.width() / minSize;
float scaleY = rect.height() / minSize;
float scale = Math.max(1f, Math.min(scaleX, scaleY));
+ if (mTaskViewArtist != null) {
+ mTaskViewArtist.taskViewDrawWidth = lp.width;
+ mTaskViewArtist.taskViewDrawHeight = lp.height;
+ mTaskViewArtist.taskViewDrawAlpha = taskViewDrawAlpha;
+ mTaskViewArtist.taskViewDrawScale = (mTaskViewArtist.drawForPortraitLayout
+ ? Math.min(lp.height, lp.width) : Math.max(lp.height, lp.width))
+ / mTaskViewArtist.taskViewMinSize;
+ }
if (Float.isNaN(scale) || Float.isInfinite(scale)) {
// Views are no longer laid out, do not update.
@@ -287,6 +316,19 @@
if (mForeground != null) {
mForeground.draw(canvas);
}
+ if (mTaskViewArtist != null) {
+ canvas.saveLayerAlpha(
+ 0,
+ 0,
+ mTaskViewArtist.taskViewDrawWidth,
+ mTaskViewArtist.taskViewDrawHeight,
+ mTaskViewArtist.taskViewDrawAlpha);
+ float drawScale = mTaskViewArtist.taskViewDrawScale;
+ canvas.translate(drawScale * mTaskViewArtist.taskViewTranslationX,
+ drawScale * mTaskViewArtist.taskViewTranslationY);
+ canvas.scale(drawScale, drawScale);
+ mTaskViewArtist.taskViewDrawCallback.accept(canvas);
+ }
canvas.restoreToCount(count);
}
@@ -303,5 +345,37 @@
mRevealAnimator = null;
mTaskCornerRadius = 0;
mOutline.setEmpty();
+ mTaskViewArtist = null;
+ }
+
+ /**
+ * Utility class to help draw a {@link com.android.quickstep.views.TaskView} within
+ * a {@link ClipIconView} bounds.
+ */
+ public static class TaskViewArtist {
+
+ public final Consumer<Canvas> taskViewDrawCallback;
+ public final float taskViewTranslationX;
+ public final float taskViewTranslationY;
+ public final float taskViewMinSize;
+ public final boolean drawForPortraitLayout;
+
+ public int taskViewDrawAlpha;
+ public float taskViewDrawScale;
+ public int taskViewDrawWidth;
+ public int taskViewDrawHeight;
+
+ public TaskViewArtist(
+ Consumer<Canvas> taskViewDrawCallback,
+ float taskViewTranslationX,
+ float taskViewTranslationY,
+ float taskViewMinSize,
+ boolean drawForPortraitLayout) {
+ this.taskViewDrawCallback = taskViewDrawCallback;
+ this.taskViewTranslationX = taskViewTranslationX;
+ this.taskViewTranslationY = taskViewTranslationY;
+ this.taskViewMinSize = taskViewMinSize;
+ this.drawForPortraitLayout = drawForPortraitLayout;
+ }
}
}
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index f560311..0d07f63 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -55,7 +55,6 @@
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.LauncherIcons;
-import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.popup.SystemShortcut;
@@ -146,18 +145,28 @@
}
/**
- * Positions this view to match the size and location of {@param rect}.
- * @param alpha The alpha[0, 1] of the entire floating view.
- * @param progress A value from [0, 1] that represents the animation progress.
- * @param shapeProgressStart The progress value at which to start the shape reveal.
- * @param cornerRadius The corner radius of {@param rect}.
- * @param isOpening True if view is used for app open animation, false for app close animation.
+ * Positions this view to match the size and location of {@code rect}.
*/
public void update(float alpha, RectF rect, float progress, float shapeProgressStart,
float cornerRadius, boolean isOpening) {
- setAlpha(alpha);
+ update(alpha, rect, progress, shapeProgressStart, cornerRadius, isOpening, 0);
+ }
+
+ /**
+ * Positions this view to match the size and location of {@code rect}.
+ * <p>
+ * @param alpha The alpha[0, 1] of the entire floating view.
+ * @param progress A value from [0, 1] that represents the animation progress.
+ * @param shapeProgressStart The progress value at which to start the shape reveal.
+ * @param cornerRadius The corner radius of {@code rect}.
+ * @param isOpening True if view is used for app open animation, false for app close animation.
+ * @param taskViewDrawAlpha the drawn {@link com.android.quickstep.views.TaskView} alpha
+ */
+ public void update(float alpha, RectF rect, float progress, float shapeProgressStart,
+ float cornerRadius, boolean isOpening, int taskViewDrawAlpha) {
+ setAlpha(isLaidOut() ? alpha : 0f);
mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, this,
- mLauncher.getDeviceProfile());
+ mLauncher.getDeviceProfile(), taskViewDrawAlpha);
if (mFadeOutView != null) {
// The alpha goes from 1 to 0 when progress is 0 and 0.33 respectively.
@@ -165,6 +174,14 @@
}
}
+ /**
+ * Sets a {@link com.android.quickstep.views.TaskView} that will draw a
+ * {@link com.android.quickstep.views.TaskView} within the {@code mClipIconView} clip bounds
+ */
+ public void setOverlayArtist(ClipIconView.TaskViewArtist taskViewArtist) {
+ mClipIconView.setTaskViewArtist(taskViewArtist);
+ }
+
@Override
public void onAnimationEnd(Animator animator) {
if (mLoadIconSignal != null) {
@@ -179,8 +196,8 @@
}
/**
- * Sets the size and position of this view to match {@param v}.
- *
+ * Sets the size and position of this view to match {@code v}.
+ * <p>
* @param v The view to copy
* @param positionOut Rect that will hold the size and position of v.
*/
@@ -254,10 +271,11 @@
/**
* Loads the icon and saves the results to {@link #sIconLoadResult}.
+ * <p>
* Runs onIconLoaded callback (if any), which signifies that the FloatingIconView is
* ready to display the icon. Otherwise, the FloatingIconView will grab the results when its
* initialized.
- *
+ * <p>
* @param originalView The View that the FloatingIconView will replace.
* @param info ItemInfo of the originalView
* @param pos The position of the view.
@@ -324,8 +342,8 @@
}
/**
- * Sets the drawables of the {@param originalView} onto this view.
- *
+ * Sets the drawables of the {@code originalView} onto this view.
+ * <p>
* @param drawable The drawable of the original view.
* @param badge The badge of the original view.
* @param iconOffset The amount of offset needed to match this view with the original view.
@@ -368,11 +386,11 @@
/**
* Draws the drawable of the BubbleTextView behind ClipIconView
- *
+ * <p>
* This is used to:
* - Have icon displayed while Adaptive Icon is loading
* - Displays the built in shadow to ensure a clean handoff
- *
+ * <p>
* Allows nullable as this may be cleared when drawing is deferred to ClipIconView.
*/
private void setOriginalDrawableBackground(@Nullable Supplier<Drawable> btvIcon) {
@@ -573,11 +591,12 @@
}
/**
- * Creates a floating icon view for {@param originalView}.
+ * Creates a floating icon view for {@code originalView}.
+ * <p>
* @param originalView The view to copy
* @param visibilitySyncView A view whose visibility should update in sync with originalView.
* @param fadeOutView A view that will fade out as the animation progresses.
- * @param hideOriginal If true, it will hide {@param originalView} while this view is visible.
+ * @param hideOriginal If true, it will hide {@code originalView} while this view is visible.
* Else, we will not draw anything in this view.
* @param positionOut Rect that will hold the size and position of v.
* @param isOpening True if this view replaces the icon for app open animation.