Merge "Dragging hit target over nav bar is bounded by its window (1/2)"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f132488..0e41a7f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -215,6 +215,9 @@
     <!-- The width of the view containing navigation buttons -->
     <dimen name="navigation_key_width">70dp</dimen>
 
+    <!-- The width/height of the icon of a navigation button -->
+    <dimen name="navigation_icon_size">32dp</dimen>
+
     <dimen name="navigation_key_padding">0dp</dimen>
 
     <!-- The width of the view containing the menu/ime navigation bar icons -->
@@ -861,7 +864,6 @@
     <dimen name="nav_content_padding">0dp</dimen>
     <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen>
     <dimen name="nav_quick_scrub_track_thickness">10dp</dimen>
-    <dimen name="nav_home_back_gesture_drag_limit">40dp</dimen>
 
     <!-- Navigation bar shadow params. -->
     <dimen name="nav_key_button_shadow_offset_x">0dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
index c03800e..37c4c58a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickStepController.java
@@ -47,6 +47,7 @@
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.SysUiServiceProvider;
 import com.android.systemui.shared.recents.IOverviewProxy;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.system.NavigationBarCompat;
 
 import java.io.PrintWriter;
@@ -54,7 +55,7 @@
 /**
  * Class to detect gestures on the navigation bar and implement quick scrub.
  * Note that the variables in this class horizontal and vertical represents horizontal always
- * aligned with along the navigation bar).
+ * aligned with along the navigation bar.
  */
 public class QuickStepController implements GestureHelper {
 
@@ -65,7 +66,10 @@
     private static final long BACK_BUTTON_FADE_IN_ALPHA = 150;
 
     /** When the home-swipe-back gesture is disallowed, make it harder to pull */
-    private static final float DISALLOW_GESTURE_DAMPING_FACTOR = 0.16f;
+    private static final float HORIZONTAL_GESTURE_DAMPING = 0.3f;
+    private static final float VERTICAL_GESTURE_DAMPING = 0.15f;
+    private static final float HORIZONTAL_DISABLED_GESTURE_DAMPING = 0.16f;
+    private static final float VERTICAL_DISABLED_GESTURE_DAMPING = 0.06f;
 
     private static final int ACTION_SWIPE_UP_INDEX = 0;
     private static final int ACTION_SWIPE_DOWN_INDEX = 1;
@@ -89,13 +93,14 @@
     private boolean mIsInScreenPinning;
     private boolean mGestureHorizontalDragsButton;
     private boolean mGestureVerticalDragsButton;
-    private boolean mGestureTrackPositive;
+    private float mMaxDragLimit;
+    private float mMinDragLimit;
+    private float mDragDampeningFactor;
 
     private NavigationGestureAction mCurrentAction;
     private NavigationGestureAction[] mGestureActions = new NavigationGestureAction[MAX_GESTURES];
 
     private final OverviewProxyService mOverviewEventSender;
-    private final int mHomeBackGestureDragLimit;
     private final Context mContext;
     private final StatusBar mStatusBar;
     private final Matrix mTransformGlobalMatrix = new Matrix();
@@ -106,8 +111,6 @@
         mContext = context;
         mStatusBar = SysUiServiceProvider.getComponent(context, StatusBar.class);
         mOverviewEventSender = Dependency.get(OverviewProxyService.class);
-        mHomeBackGestureDragLimit =
-                res.getDimensionPixelSize(R.dimen.nav_home_back_gesture_drag_limit);
     }
 
     public void setComponents(NavigationBarView navigationBarView) {
@@ -335,21 +338,9 @@
                 mDragBtnAnimator = null;
             }
 
-            int diff = position - touchDown;
-            // If dragging the incorrect direction after starting gesture or unable to
-            // execute tried action, then move the button but dampen its distance
-            if (mCurrentAction == null || (mGestureTrackPositive ? diff < 0 : diff > 0)) {
-                diff *= DISALLOW_GESTURE_DAMPING_FACTOR;
-            } else if (Math.abs(diff) > mHomeBackGestureDragLimit) {
-                // Once the user drags the button past a certain limit, the distance will
-                // lessen as the button dampens that it was pulled too far
-                float distanceAfterDragLimit = (Math.abs(diff) - mHomeBackGestureDragLimit)
-                        * DISALLOW_GESTURE_DAMPING_FACTOR;
-                diff = (int) (distanceAfterDragLimit + mHomeBackGestureDragLimit);
-                if (!mGestureTrackPositive) {
-                    diff *= -1;
-                }
-            }
+            // Clamp drag to the bounding box of the navigation bar
+            float diff = (position - touchDown) * mDragDampeningFactor;
+            diff = Utilities.clamp(diff, mMinDragLimit, mMaxDragLimit);
             if (mGestureVerticalDragsButton ^ isNavBarVertical()) {
                 button.setTranslationY(diff);
             } else {
@@ -480,22 +471,44 @@
                 event.transform(mTransformGlobalMatrix);
                 action.startGesture(event);
                 event.transform(mTransformLocalMatrix);
+
+                // Calculate the bounding limits of drag to avoid dragging off nav bar's window
+                if (action.requiresDragWithHitTarget() && mHitTarget != null) {
+                    final int[] buttonCenter = new int[2];
+                    View button = mHitTarget.getCurrentView();
+                    button.getLocationInWindow(buttonCenter);
+                    buttonCenter[0] += button.getWidth() / 2;
+                    buttonCenter[1] += button.getHeight() / 2;
+                    final int x = isNavBarVertical() ? buttonCenter[1] : buttonCenter[0];
+                    final int y = isNavBarVertical() ? buttonCenter[0] : buttonCenter[1];
+                    final int iconHalfSize = mContext.getResources()
+                            .getDimensionPixelSize(R.dimen.navigation_icon_size) / 2;
+
+                    if (alignedWithNavBar) {
+                        mMinDragLimit =  iconHalfSize - x;
+                        mMaxDragLimit = -x - iconHalfSize + (isNavBarVertical()
+                                ? mNavigationBarView.getHeight() : mNavigationBarView.getWidth());
+                    } else {
+                        mMinDragLimit = iconHalfSize - y;
+                        mMaxDragLimit =  -y - iconHalfSize + (isNavBarVertical()
+                                ? mNavigationBarView.getWidth() : mNavigationBarView.getHeight());
+                    }
+                }
             }
 
             // Handle direction of the hit target drag from the axis that started the gesture
+            // Also calculate the dampening factor, weaker dampening if there is an active action
             if (action.requiresDragWithHitTarget()) {
                 if (alignedWithNavBar) {
                     mGestureHorizontalDragsButton = true;
                     mGestureVerticalDragsButton = false;
-                    if (positiveDirection) {
-                        mGestureTrackPositive = mDragHPositive;
-                    }
+                    mDragDampeningFactor = action.isActive()
+                            ? HORIZONTAL_GESTURE_DAMPING : HORIZONTAL_DISABLED_GESTURE_DAMPING;
                 } else {
                     mGestureVerticalDragsButton = true;
                     mGestureHorizontalDragsButton = false;
-                    if (positiveDirection) {
-                        mGestureTrackPositive = mDragVPositive;
-                    }
+                    mDragDampeningFactor = action.isActive()
+                            ? VERTICAL_GESTURE_DAMPING : VERTICAL_DISABLED_GESTURE_DAMPING;
                 }
             }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
index 0781602..4177cd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/QuickStepControllerTest.java
@@ -38,6 +38,7 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
+import android.content.Context;
 import com.android.systemui.R;
 import com.android.systemui.recents.OverviewProxyService;
 import com.android.systemui.shared.recents.IOverviewProxy;
@@ -472,8 +473,11 @@
 
     @Test
     public void testHitTargetDragged() throws Exception {
+        final int navbarWidth = 1000;
+        final int navbarHeight = 1000;
         ButtonDispatcher button = mock(ButtonDispatcher.class);
-        View buttonView = spy(new View(mContext));
+        FakeLocationView buttonView = spy(new FakeLocationView(mContext, navbarWidth / 2,
+                navbarHeight / 2));
         doReturn(buttonView).when(button).getCurrentView();
 
         NavigationGestureAction action = mockAction(true);
@@ -484,6 +488,8 @@
         doReturn(true).when(action).requiresDragWithHitTarget();
         doReturn(HIT_TARGET_HOME).when(mNavigationBarView).getDownHitTarget();
         doReturn(button).when(mNavigationBarView).getHomeButton();
+        doReturn(navbarWidth).when(mNavigationBarView).getWidth();
+        doReturn(navbarHeight).when(mNavigationBarView).getHeight();
 
         // Portrait
         assertGestureDragsHitTargetAllDirections(buttonView, false /* isRTL */, NAV_BAR_BOTTOM);
@@ -615,4 +621,21 @@
         assertNull(mController.getCurrentAction());
         verify(action, times(1)).endGesture();
     }
+
+    static class FakeLocationView extends View {
+        private final int mX;
+        private final int mY;
+
+        public FakeLocationView(Context context, int x, int y) {
+            super(context);
+            mX = x;
+            mY = y;
+        }
+
+        @Override
+        public void getLocationInWindow(int[] outLocation) {
+            outLocation[0] = mX;
+            outLocation[1] = mY;
+        }
+    }
 }