Merge "Fixed overlap issue where toggle button gets overlapped by 3 button nav bar..now button shifts up so it's not totally obscured" into tm-dev
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 36c9d13..5b912ad 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -244,8 +244,7 @@
         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 @@
                 }
             };
             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/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index a22398d..89e54b8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -156,14 +156,7 @@
         updateIconSize(resources);
         mTaskbarHeightForIme = resources.getDimensionPixelSize(R.dimen.taskbar_ime_size);
 
-        // Inflate views.
-        mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(
-                R.layout.taskbar, null, false);
-        TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
-        TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
-        FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
-        StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
-
+        // Get display and corners first, as views might use them in constructor.
         Display display = windowContext.getDisplay();
         Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY
                 ? windowContext.getApplicationContext()
@@ -172,6 +165,14 @@
         mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
         mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);
 
+        // Inflate views.
+        mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(
+                R.layout.taskbar, null, false);
+        TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
+        TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
+        FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
+        StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);
+
         mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);
 
         // Construct controllers.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
new file mode 100644
index 0000000..1177bdb
--- /dev/null
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.launcher3.taskbar
+
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Path
+import com.android.launcher3.R
+
+/**
+ * Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners.
+ */
+class TaskbarBackgroundRenderer(context: TaskbarActivityContext) {
+
+    val paint: Paint = Paint()
+    var backgroundHeight = context.deviceProfile.taskbarSize.toFloat()
+
+    private val leftCornerRadius = context.leftCornerRadius.toFloat()
+    private val rightCornerRadius = context.rightCornerRadius.toFloat()
+    private val invertedLeftCornerPath: Path = Path()
+    private val invertedRightCornerPath: Path = Path()
+
+    init {
+        paint.color = context.getColor(R.color.taskbar_background)
+        paint.flags = Paint.ANTI_ALIAS_FLAG
+        paint.style = Paint.Style.FILL
+
+        // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
+        // square, and then subtract out a circle from the appropriate corner.
+        val square = Path()
+        square.addRect(0f, 0f, leftCornerRadius, leftCornerRadius, Path.Direction.CW)
+        val circle = Path()
+        circle.addCircle(leftCornerRadius, 0f, leftCornerRadius, Path.Direction.CW)
+        invertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE)
+        square.reset()
+        square.addRect(0f, 0f, rightCornerRadius, rightCornerRadius, Path.Direction.CW)
+        circle.reset()
+        circle.addCircle(0f, 0f, rightCornerRadius, Path.Direction.CW)
+        invertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE)
+    }
+
+    /**
+     * Draws the background with the given paint and height, on the provided canvas.
+     */
+    fun draw(canvas: Canvas) {
+        canvas.save()
+        canvas.translate(0f, canvas.height - backgroundHeight)
+
+        // Draw the background behind taskbar content.
+        canvas.drawRect(0f, 0f, canvas.width.toFloat(), backgroundHeight, paint)
+
+        // Draw the inverted rounded corners above the taskbar.
+        canvas.translate(0f, -leftCornerRadius)
+        canvas.drawPath(invertedLeftCornerPath, paint)
+        canvas.translate(0f, leftCornerRadius)
+        canvas.translate(canvas.width - rightCornerRadius, -rightCornerRadius)
+        canvas.drawPath(invertedRightCornerPath, paint)
+
+        canvas.restore()
+    }
+}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index 9ba4a65..5c10565 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -477,7 +477,6 @@
                 tx.setScale(dragSurface, scale, scale);
                 tx.setAlpha(dragSurface, alpha);
                 tx.apply();
-                tx.close();
             }
         });
         mReturnAnimator.addListener(new AnimatorListenerAdapter() {
@@ -498,6 +497,7 @@
             }
 
             private void cleanUpSurface() {
+                tx.close();
                 maybeOnDragEnd();
                 // Synchronize removing the drag surface with the next draw after calling
                 // maybeOnDragEnd()
@@ -508,7 +508,6 @@
                 syncer.addToSync(syncId, viewRoot.getView());
                 syncer.addTransactionToSync(syncId, transaction);
                 syncer.markSyncReady(syncId);
-
                 mReturnAnimator = null;
             }
         });
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
index 4a80665..089c26d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragLayer.java
@@ -20,8 +20,6 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -31,7 +29,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.launcher3.AbstractFloatingView;
-import com.android.launcher3.R;
 import com.android.launcher3.testing.TestLogging;
 import com.android.launcher3.testing.TestProtocol;
 import com.android.launcher3.views.BaseDragLayer;
@@ -44,13 +41,11 @@
  */
 public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
 
-    private final Paint mTaskbarBackgroundPaint;
-    private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
+    private final TaskbarBackgroundRenderer mBackgroundRenderer;
     private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets;
 
     // Initialized in init.
     private TaskbarDragLayerController.TaskbarDragLayerCallbacks mControllerCallbacks;
-    private float mLeftCornerRadius, mRightCornerRadius;
 
     private float mTaskbarBackgroundOffset;
 
@@ -70,35 +65,13 @@
     public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, 1 /* alphaChannelCount */);
-        mTaskbarBackgroundPaint = new Paint();
-        mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background));
-        mTaskbarBackgroundPaint.setAlpha(0);
-        mTaskbarBackgroundPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
-        mTaskbarBackgroundPaint.setStyle(Paint.Style.FILL);
-
-        // Will be set in init(), but this ensures they are always non-null.
-        mInvertedLeftCornerPath = new Path();
-        mInvertedRightCornerPath = new Path();
+        mBackgroundRenderer = new TaskbarBackgroundRenderer(mActivity);
+        mBackgroundRenderer.getPaint().setAlpha(0);
     }
 
     public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
         mControllerCallbacks = callbacks;
 
-        // Create the paths for the inverted rounded corners above the taskbar. Start with a filled
-        // square, and then subtracting out a circle from the appropriate corner.
-        mLeftCornerRadius = mActivity.getLeftCornerRadius();
-        mRightCornerRadius = mActivity.getRightCornerRadius();
-        Path square = new Path();
-        square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
-        Path circle = new Path();
-        circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
-        mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
-        square.reset();
-        square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
-        circle.reset();
-        circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
-        mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
-
         recreateControllers();
     }
 
@@ -151,20 +124,8 @@
     protected void dispatchDraw(Canvas canvas) {
         float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
                 * (1f - mTaskbarBackgroundOffset);
-        canvas.save();
-        canvas.translate(0, canvas.getHeight() - backgroundHeight);
-
-        // Draw the background behind taskbar content.
-        canvas.drawRect(0, 0, canvas.getWidth(), backgroundHeight, mTaskbarBackgroundPaint);
-
-        // Draw the inverted rounded corners above the taskbar.
-        canvas.translate(0, -mLeftCornerRadius);
-        canvas.drawPath(mInvertedLeftCornerPath, mTaskbarBackgroundPaint);
-        canvas.translate(0, mLeftCornerRadius);
-        canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
-        canvas.drawPath(mInvertedRightCornerPath, mTaskbarBackgroundPaint);
-
-        canvas.restore();
+        mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
+        mBackgroundRenderer.draw(canvas);
         super.dispatchDraw(canvas);
     }
 
@@ -173,7 +134,7 @@
      * @param alpha 0 is fully transparent, 1 is fully opaque.
      */
     protected void setTaskbarBackgroundAlpha(float alpha) {
-        mTaskbarBackgroundPaint.setAlpha((int) (alpha * 255));
+        mBackgroundRenderer.getPaint().setAlpha((int) (alpha * 255));
         invalidate();
     }
 
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java
index 94a3307..1d3757f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimView.java
@@ -17,22 +17,19 @@
 
 import android.content.Context;
 import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.launcher3.views.ActivityContext;
+
 /**
  * View that handles scrimming the taskbar and the inverted corners it draws. The scrim is used
  * when bubbles is expanded.
  */
 public class TaskbarScrimView extends View {
-    private final Paint mTaskbarScrimPaint;
-    private final Path mInvertedLeftCornerPath, mInvertedRightCornerPath;
+    private final TaskbarBackgroundRenderer mRenderer;
 
     private boolean mShowScrim;
-    private float mLeftCornerRadius, mRightCornerRadius;
-    private float mBackgroundHeight;
 
     public TaskbarScrimView(Context context) {
         this(context, null);
@@ -49,14 +46,9 @@
     public TaskbarScrimView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-
-        mTaskbarScrimPaint = new Paint();
-        mTaskbarScrimPaint.setColor(getResources().getColor(android.R.color.system_neutral1_1000));
-        mTaskbarScrimPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
-        mTaskbarScrimPaint.setStyle(Paint.Style.FILL);
-
-        mInvertedLeftCornerPath = new Path();
-        mInvertedRightCornerPath = new Path();
+        mRenderer = new TaskbarBackgroundRenderer(ActivityContext.lookupContext(context));
+        mRenderer.getPaint().setColor(getResources().getColor(
+                android.R.color.system_neutral1_1000));
     }
 
     @Override
@@ -64,31 +56,7 @@
         super.onDraw(canvas);
 
         if (mShowScrim) {
-            canvas.save();
-            canvas.translate(0, canvas.getHeight() - mBackgroundHeight);
-
-            // Scrim the taskbar itself.
-            canvas.drawRect(0, 0, canvas.getWidth(), mBackgroundHeight, mTaskbarScrimPaint);
-
-            // Scrim the inverted rounded corners above the taskbar.
-            canvas.translate(0, -mLeftCornerRadius);
-            canvas.drawPath(mInvertedLeftCornerPath, mTaskbarScrimPaint);
-            canvas.translate(0, mLeftCornerRadius);
-            canvas.translate(canvas.getWidth() - mRightCornerRadius, -mRightCornerRadius);
-            canvas.drawPath(mInvertedRightCornerPath, mTaskbarScrimPaint);
-
-            canvas.restore();
-        }
-    }
-
-    /**
-     * Sets the height of the taskbar background.
-     * @param height the height of the background.
-     */
-    protected void setBackgroundHeight(float height) {
-        mBackgroundHeight = height;
-        if (mShowScrim) {
-            invalidate();
+            mRenderer.draw(canvas);
         }
     }
 
@@ -98,32 +66,7 @@
      */
     protected void setScrimAlpha(float alpha) {
         mShowScrim = alpha > 0f;
-        mTaskbarScrimPaint.setAlpha((int) (alpha * 255));
+        mRenderer.getPaint().setAlpha((int) (alpha * 255));
         invalidate();
     }
-
-    /**
-     * Sets the radius of the left and right corners above the taskbar.
-     * @param leftCornerRadius the radius of the left corner.
-     * @param rightCornerRadius the radius of the right corner.
-     */
-    protected void setCornerSizes(float leftCornerRadius, float rightCornerRadius) {
-        mLeftCornerRadius = leftCornerRadius;
-        mRightCornerRadius = rightCornerRadius;
-
-        Path square = new Path();
-        square.addRect(0, 0, mLeftCornerRadius, mLeftCornerRadius, Path.Direction.CW);
-        Path circle = new Path();
-        circle.addCircle(mLeftCornerRadius, 0, mLeftCornerRadius, Path.Direction.CW);
-        mInvertedLeftCornerPath.op(square, circle, Path.Op.DIFFERENCE);
-        square.reset();
-        square.addRect(0, 0, mRightCornerRadius, mRightCornerRadius, Path.Direction.CW);
-        circle.reset();
-        circle.addCircle(0, 0, mRightCornerRadius, Path.Direction.CW);
-        mInvertedRightCornerPath.op(square, circle, Path.Op.DIFFERENCE);
-
-        if (mShowScrim) {
-            invalidate();
-        }
-    }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
index 02bbae4..58ace17 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarScrimViewController.java
@@ -49,9 +49,6 @@
     public TaskbarScrimViewController(TaskbarActivityContext activity, TaskbarScrimView scrimView) {
         mActivity = activity;
         mScrimView = scrimView;
-        mScrimView.setCornerSizes(mActivity.getLeftCornerRadius(),
-                mActivity.getRightCornerRadius());
-        mScrimView.setBackgroundHeight(mActivity.getDeviceProfile().taskbarSize);
     }
 
     /**
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 7abcbdb..cc79f4a 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -25,6 +25,7 @@
 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.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 @@
  *
  */
 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 @@
         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 @@
                         if (!mBackInProgress) {
                             startBack(backEvent);
                         } else {
-                            updateBackProgress(mBackProgress);
+                            updateBackProgress(mBackProgress, backEvent);
                         }
                     }
 
@@ -145,11 +151,13 @@
     }
 
     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 @@
         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 @@
         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 0000000..2165457
--- /dev/null
+++ b/res/interpolator/back_cancel.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<pathInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:controlX1="0.2"
+    android:controlY1="0"
+    android:controlX2="0"
+    android:controlY2="1"/>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 0ed2d85..5ecd929 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -166,6 +166,6 @@
 
     <!-- Swipe back to home related -->
     <dimen name="swipe_back_window_scale_x_margin">10dp</dimen>
-    <dimen name="swipe_back_window_scale_y_margin">80dp</dimen>
+    <dimen name="swipe_back_window_max_delta_y">160dp</dimen>
     <dimen name="swipe_back_window_corner_radius">40dp</dimen>
 </resources>
diff --git a/src/com/android/launcher3/notification/NotificationListener.java b/src/com/android/launcher3/notification/NotificationListener.java
index bbeb886..04eb38a 100644
--- a/src/com/android/launcher3/notification/NotificationListener.java
+++ b/src/com/android/launcher3/notification/NotificationListener.java
@@ -35,6 +35,7 @@
 import android.util.Pair;
 
 import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
@@ -154,14 +155,9 @@
             case MSG_NOTIFICATION_FULL_REFRESH:
                 List<StatusBarNotification> activeNotifications = null;
                 if (sIsConnected) {
-                    try {
-                        activeNotifications = Arrays.stream(getActiveNotifications())
-                                .filter(this::notificationIsValidForUI)
-                                .collect(Collectors.toList());
-                    } catch (SecurityException ex) {
-                        Log.e(TAG, "SecurityException: failed to fetch notifications");
-                        activeNotifications = new ArrayList<>();
-                    }
+                    activeNotifications = Arrays.stream(getActiveNotificationsSafely(null))
+                            .filter(this::notificationIsValidForUI)
+                            .collect(Collectors.toList());
                 } else {
                     activeNotifications = new ArrayList<>();
                 }
@@ -175,7 +171,7 @@
             }
             case MSG_RANKING_UPDATE: {
                 String[] keys = ((RankingMap) message.obj).getOrderedKeys();
-                for (StatusBarNotification sbn : getActiveNotifications(keys)) {
+                for (StatusBarNotification sbn : getActiveNotificationsSafely(keys)) {
                     updateGroupKeyIfNecessary(sbn);
                 }
                 return true;
@@ -214,6 +210,16 @@
         return true;
     }
 
+    private @NonNull StatusBarNotification[] getActiveNotificationsSafely(@Nullable String[] keys) {
+        StatusBarNotification[] result = null;
+        try {
+            result = getActiveNotifications(keys);
+        } catch (SecurityException e) {
+            Log.e(TAG, "SecurityException: failed to fetch notifications");
+        }
+        return result == null ? new StatusBarNotification[0] : result;
+    }
+
     @Override
     public void onListenerConnected() {
         super.onListenerConnected();
@@ -313,9 +319,8 @@
      */
     @WorkerThread
     public List<StatusBarNotification> getNotificationsForKeys(List<NotificationKeyData> keys) {
-        StatusBarNotification[] notifications = getActiveNotifications(
-                keys.stream().map(n -> n.notificationKey).toArray(String[]::new));
-        return notifications == null ? Collections.emptyList() : Arrays.asList(notifications);
+        return Arrays.asList(getActiveNotificationsSafely(
+                keys.stream().map(n -> n.notificationKey).toArray(String[]::new)));
     }
 
     /**