diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 36c9d13b88..5b912ade65 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -244,8 +244,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS); mHandler = new Handler(Looper.getMainLooper()); mDeviceProfile = mLauncher.getDeviceProfile(); - mBackAnimationController = new LauncherBackAnimationController( - mDeviceProfile, mLauncher, this); + mBackAnimationController = new LauncherBackAnimationController(mLauncher, this); Resources res = mLauncher.getResources(); mContentScale = res.getFloat(R.dimen.content_scale); @@ -1441,6 +1440,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener } }; anim.addOnUpdateListener(runner); + } else { + // If no floating icon or widget is present, animate the to the default window + // target rect. + anim.addOnUpdateListener(new SpringAnimRunner(targets, targetRect, windowTargetBounds)); } // Use a fixed velocity to start the animation. diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java index 7abcbdb47a..cc79f4a1d8 100644 --- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java +++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java @@ -25,6 +25,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.graphics.Matrix; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.os.Handler; @@ -33,11 +34,12 @@ import android.util.MathUtils; import android.util.Pair; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; import android.window.BackEvent; import android.window.IOnBackInvokedCallback; import com.android.launcher3.BaseQuickstepLauncher; -import com.android.launcher3.DeviceProfile; import com.android.launcher3.QuickstepTransitionManager; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -62,34 +64,36 @@ import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat; * */ public class LauncherBackAnimationController { - private static final int CANCEL_TRANSITION_DURATION = 150; + private static final int CANCEL_TRANSITION_DURATION = 233; + private static final float MIN_WINDOW_SCALE = 0.7f; private static final String TAG = "LauncherBackAnimationController"; - private final DeviceProfile mDeviceProfile; private final QuickstepTransitionManager mQuickstepTransitionManager; private final Matrix mTransformMatrix = new Matrix(); - private final RectF mTargetRectF = new RectF(); - private final RectF mStartRectF = new RectF(); + /** The window position at the beginning of the back animation. */ + private final Rect mStartRect = new Rect(); + /** The window position when the back gesture is cancelled. */ + private final RectF mCancelRect = new RectF(); + /** The current window position. */ private final RectF mCurrentRect = new RectF(); private final BaseQuickstepLauncher mLauncher; private final int mWindowScaleMarginX; - private final int mWindowScaleMarginY; + /** Max window translation in the Y axis. */ + private final int mWindowMaxDeltaY; private final float mWindowScaleEndCornerRadius; private final float mWindowScaleStartCornerRadius; + private final Interpolator mCancelInterpolator; + private final PointF mInitialTouchPos = new PointF(); private RemoteAnimationTargetCompat mBackTarget; private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); private boolean mSpringAnimationInProgress = false; private boolean mAnimatorSetInProgress = false; - @BackEvent.SwipeEdge - private int mSwipeEdge; private float mBackProgress = 0; private boolean mBackInProgress = false; public LauncherBackAnimationController( - DeviceProfile deviceProfile, BaseQuickstepLauncher launcher, QuickstepTransitionManager quickstepTransitionManager) { - mDeviceProfile = deviceProfile; mLauncher = launcher; mQuickstepTransitionManager = quickstepTransitionManager; mWindowScaleEndCornerRadius = QuickStepContract.supportsRoundedCornersOnWindows( @@ -100,8 +104,10 @@ public class LauncherBackAnimationController { mWindowScaleStartCornerRadius = QuickStepContract.getWindowCornerRadius(mLauncher); mWindowScaleMarginX = mLauncher.getResources().getDimensionPixelSize( R.dimen.swipe_back_window_scale_x_margin); - mWindowScaleMarginY = mLauncher.getResources().getDimensionPixelSize( - R.dimen.swipe_back_window_scale_y_margin); + mWindowMaxDeltaY = mLauncher.getResources().getDimensionPixelSize( + R.dimen.swipe_back_window_max_delta_y); + mCancelInterpolator = + AnimationUtils.loadInterpolator(mLauncher, R.interpolator.back_cancel); } /** @@ -136,7 +142,7 @@ public class LauncherBackAnimationController { if (!mBackInProgress) { startBack(backEvent); } else { - updateBackProgress(mBackProgress); + updateBackProgress(mBackProgress, backEvent); } } @@ -145,11 +151,13 @@ public class LauncherBackAnimationController { } private void resetPositionAnimated() { - ValueAnimator cancelAnimator = ValueAnimator.ofFloat(mBackProgress, 0); + ValueAnimator cancelAnimator = ValueAnimator.ofFloat(0, 1); + mCancelRect.set(mCurrentRect); cancelAnimator.setDuration(CANCEL_TRANSITION_DURATION); + cancelAnimator.setInterpolator(mCancelInterpolator); cancelAnimator.addUpdateListener( animation -> { - updateBackProgress((float) animation.getAnimatedValue()); + updateCancelProgress((float) animation.getAnimatedValue()); }); cancelAnimator.addListener(new AnimatorListenerAdapter() { @Override @@ -179,50 +187,70 @@ public class LauncherBackAnimationController { mTransaction.show(appTarget.leash).apply(); mTransaction.setAnimationTransaction(); mBackTarget = new RemoteAnimationTargetCompat(appTarget); - mSwipeEdge = backEvent.getSwipeEdge(); - float screenWidth = mDeviceProfile.widthPx; - float screenHeight = mDeviceProfile.heightPx; - float targetHeight = screenHeight - 2 * mWindowScaleMarginY; - float targetWidth = targetHeight * screenWidth / screenHeight; - float left; - if (mSwipeEdge == BackEvent.EDGE_LEFT) { - left = screenWidth - targetWidth - mWindowScaleMarginX; - } else { - left = mWindowScaleMarginX; - } - float top = mWindowScaleMarginY; + mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY()); + // TODO(b/218916755): Offset start rectangle in multiwindow mode. - mStartRectF.set(0, 0, screenWidth, screenHeight); - mTargetRectF.set(left, top, targetWidth + left, targetHeight + top); + mStartRect.set(mBackTarget.windowConfiguration.getMaxBounds()); } - private void updateBackProgress(float progress) { + private void updateBackProgress(float progress, BackEvent event) { if (mBackTarget == null) { return; } + float screenWidth = mStartRect.width(); + float screenHeight = mStartRect.height(); + float dX = Math.abs(event.getTouchX() - mInitialTouchPos.x); + // The 'follow width' is the width of the window if it completely matches + // the gesture displacement. + float followWidth = screenWidth - dX; + // The 'progress width' is the width of the window if it strictly linearly interpolates + // to minimum scale base on progress. + float progressWidth = MathUtils.lerp(1, MIN_WINDOW_SCALE, progress) * screenWidth; + // The final width is derived from interpolating between the follow with and progress width + // using gesture progress. + float width = MathUtils.lerp(followWidth, progressWidth, progress); + float height = screenHeight / screenWidth * width; + float deltaYRatio = (event.getTouchY() - mInitialTouchPos.y) / screenHeight; + // Base the window movement in the Y axis on the touch movement in the Y axis. + float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * mWindowMaxDeltaY; + // Move the window along the Y axis. + float top = (screenHeight - height) * 0.5f + deltaY; + // Move the window along the X axis. + float left = event.getSwipeEdge() == BackEvent.EDGE_RIGHT + ? progress * mWindowScaleMarginX + : screenWidth - progress * mWindowScaleMarginX - width; - mCurrentRect.set( - MathUtils.lerp(mStartRectF.left, mTargetRectF.left, progress), - MathUtils.lerp(mStartRectF.top, mTargetRectF.top, progress), - MathUtils.lerp(mStartRectF.right, mTargetRectF.right, progress), - MathUtils.lerp(mStartRectF.bottom, mTargetRectF.bottom, progress)); - SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder builder = - new SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder(mBackTarget.leash); - - Rect currentRect = new Rect(); - mCurrentRect.round(currentRect); - - // Scale the target window to match the currentRectF. - final float scale = mCurrentRect.width() / mStartRectF.width(); - mTransformMatrix.reset(); - mTransformMatrix.setScale(scale, scale); - mTransformMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top); - Rect startRect = new Rect(); - mStartRectF.round(startRect); + mCurrentRect.set(left, top, left + width, top + height); float cornerRadius = Utilities.mapRange( progress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius); + applyTransform(mCurrentRect, cornerRadius); + } + + private void updateCancelProgress(float progress) { + if (mBackTarget == null) { + return; + } + mCurrentRect.set( + MathUtils.lerp(mCancelRect.left, mStartRect.left, progress), + MathUtils.lerp(mCancelRect.top, mStartRect.top, progress), + MathUtils.lerp(mCancelRect.right, mStartRect.right, progress), + MathUtils.lerp(mCancelRect.bottom, mStartRect.bottom, progress)); + + float cornerRadius = Utilities.mapRange( + progress, mWindowScaleEndCornerRadius, mWindowScaleStartCornerRadius); + applyTransform(mCurrentRect, cornerRadius); + } + + /** Transform the target window to match the target rect. */ + private void applyTransform(RectF targetRect, float cornerRadius) { + SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder builder = + new SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder(mBackTarget.leash); + final float scale = targetRect.width() / mStartRect.width(); + mTransformMatrix.reset(); + mTransformMatrix.setScale(scale, scale); + mTransformMatrix.postTranslate(targetRect.left, targetRect.top); builder.withMatrix(mTransformMatrix) - .withWindowCrop(startRect) + .withWindowCrop(mStartRect) .withCornerRadius(cornerRadius); SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams = builder.build(); @@ -263,11 +291,11 @@ public class LauncherBackAnimationController { mBackTarget = null; mBackInProgress = false; mBackProgress = 0; - mSwipeEdge = BackEvent.EDGE_LEFT; mTransformMatrix.reset(); - mTargetRectF.setEmpty(); + mCancelRect.setEmpty(); mCurrentRect.setEmpty(); - mStartRectF.setEmpty(); + mStartRect.setEmpty(); + mInitialTouchPos.set(0, 0); mAnimatorSetInProgress = false; mSpringAnimationInProgress = false; SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate(); diff --git a/res/interpolator/back_cancel.xml b/res/interpolator/back_cancel.xml new file mode 100644 index 0000000000..2165457c72 --- /dev/null +++ b/res/interpolator/back_cancel.xml @@ -0,0 +1,24 @@ + + + + \ No newline at end of file diff --git a/res/values/config.xml b/res/values/config.xml index 0ed2d858df..5ecd9299c7 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -166,6 +166,6 @@ 10dp - 80dp + 160dp 40dp