Merge "Introduce a minimum scrollbar touch target size"
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d468117..0c6d9e3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5386,16 +5386,16 @@
         x += getScrollX();
         y += getScrollY();
         if (isVerticalScrollBarEnabled() && !isVerticalScrollBarHidden()) {
-            final Rect bounds = mScrollCache.mScrollBarBounds;
-            getVerticalScrollBarBounds(bounds);
-            if (bounds.contains((int)x, (int)y)) {
+            final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+            getVerticalScrollBarBounds(null, touchBounds);
+            if (touchBounds.contains((int) x, (int) y)) {
                 return true;
             }
         }
         if (isHorizontalScrollBarEnabled()) {
-            final Rect bounds = mScrollCache.mScrollBarBounds;
-            getHorizontalScrollBarBounds(bounds);
-            if (bounds.contains((int)x, (int)y)) {
+            final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+            getHorizontalScrollBarBounds(null, touchBounds);
+            if (touchBounds.contains((int) x, (int) y)) {
                 return true;
             }
         }
@@ -5414,7 +5414,8 @@
             x += getScrollX();
             y += getScrollY();
             final Rect bounds = mScrollCache.mScrollBarBounds;
-            getVerticalScrollBarBounds(bounds);
+            final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+            getVerticalScrollBarBounds(bounds, touchBounds);
             final int range = computeVerticalScrollRange();
             final int offset = computeVerticalScrollOffset();
             final int extent = computeVerticalScrollExtent();
@@ -5423,8 +5424,9 @@
             final int thumbOffset = ScrollBarUtils.getThumbOffset(bounds.height(), thumbLength,
                     extent, range, offset);
             final int thumbTop = bounds.top + thumbOffset;
-            if (x >= bounds.left && x <= bounds.right && y >= thumbTop
-                    && y <= thumbTop + thumbLength) {
+            final int adjust = Math.max(mScrollCache.scrollBarMinTouchTarget - thumbLength, 0) / 2;
+            if (x >= touchBounds.left && x <= touchBounds.right
+                    && y >= thumbTop - adjust && y <= thumbTop + thumbLength + adjust) {
                 return true;
             }
         }
@@ -5439,7 +5441,8 @@
             x += getScrollX();
             y += getScrollY();
             final Rect bounds = mScrollCache.mScrollBarBounds;
-            getHorizontalScrollBarBounds(bounds);
+            final Rect touchBounds = mScrollCache.mScrollBarTouchBounds;
+            getHorizontalScrollBarBounds(bounds, touchBounds);
             final int range = computeHorizontalScrollRange();
             final int offset = computeHorizontalScrollOffset();
             final int extent = computeHorizontalScrollExtent();
@@ -5448,8 +5451,9 @@
             final int thumbOffset = ScrollBarUtils.getThumbOffset(bounds.width(), thumbLength,
                     extent, range, offset);
             final int thumbLeft = bounds.left + thumbOffset;
-            if (x >= thumbLeft && x <= thumbLeft + thumbLength && y >= bounds.top
-                    && y <= bounds.bottom) {
+            final int adjust = Math.max(mScrollCache.scrollBarMinTouchTarget - thumbLength, 0) / 2;
+            if (x >= thumbLeft - adjust && x <= thumbLeft + thumbLength + adjust
+                    && y >= touchBounds.top && y <= touchBounds.bottom) {
                 return true;
             }
         }
@@ -11752,7 +11756,7 @@
                 if (mScrollCache.mScrollBarDraggingState
                         == ScrollabilityCache.DRAGGING_VERTICAL_SCROLL_BAR) {
                     final Rect bounds = mScrollCache.mScrollBarBounds;
-                    getVerticalScrollBarBounds(bounds);
+                    getVerticalScrollBarBounds(bounds, null);
                     final int range = computeVerticalScrollRange();
                     final int offset = computeVerticalScrollOffset();
                     final int extent = computeVerticalScrollExtent();
@@ -11781,7 +11785,7 @@
                 if (mScrollCache.mScrollBarDraggingState
                         == ScrollabilityCache.DRAGGING_HORIZONTAL_SCROLL_BAR) {
                     final Rect bounds = mScrollCache.mScrollBarBounds;
-                    getHorizontalScrollBarBounds(bounds);
+                    getHorizontalScrollBarBounds(bounds, null);
                     final int range = computeHorizontalScrollRange();
                     final int offset = computeHorizontalScrollOffset();
                     final int extent = computeHorizontalScrollExtent();
@@ -15488,7 +15492,12 @@
         }
     }
 
-    private void getHorizontalScrollBarBounds(Rect bounds) {
+    private void getHorizontalScrollBarBounds(@Nullable Rect drawBounds,
+            @Nullable Rect touchBounds) {
+        final Rect bounds = drawBounds != null ? drawBounds : touchBounds;
+        if (bounds == null) {
+            return;
+        }
         final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
         final boolean drawVerticalScrollBar = isVerticalScrollBarEnabled()
                 && !isVerticalScrollBarHidden();
@@ -15501,13 +15510,31 @@
         bounds.left = mScrollX + (mPaddingLeft & inside);
         bounds.right = mScrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap;
         bounds.bottom = bounds.top + size;
+
+        if (touchBounds == null) {
+            return;
+        }
+        if (touchBounds != bounds) {
+            touchBounds.set(bounds);
+        }
+        final int minTouchTarget = mScrollCache.scrollBarMinTouchTarget;
+        if (touchBounds.height() < minTouchTarget) {
+            final int adjust = (minTouchTarget - touchBounds.height()) / 2;
+            touchBounds.bottom = Math.min(touchBounds.bottom + adjust, mScrollY + height);
+            touchBounds.top = touchBounds.bottom - minTouchTarget;
+        }
+        if (touchBounds.width() < minTouchTarget) {
+            final int adjust = (minTouchTarget - touchBounds.width()) / 2;
+            touchBounds.left -= adjust;
+            touchBounds.right = touchBounds.left + minTouchTarget;
+        }
     }
 
-    private void getVerticalScrollBarBounds(Rect bounds) {
+    private void getVerticalScrollBarBounds(@Nullable Rect bounds, @Nullable Rect touchBounds) {
         if (mRoundScrollbarRenderer == null) {
-            getStraightVerticalScrollBarBounds(bounds);
+            getStraightVerticalScrollBarBounds(bounds, touchBounds);
         } else {
-            getRoundVerticalScrollBarBounds(bounds);
+            getRoundVerticalScrollBarBounds(bounds != null ? bounds : touchBounds);
         }
     }
 
@@ -15522,7 +15549,12 @@
         bounds.bottom = mScrollY + height;
     }
 
-    private void getStraightVerticalScrollBarBounds(Rect bounds) {
+    private void getStraightVerticalScrollBarBounds(@Nullable Rect drawBounds,
+            @Nullable Rect touchBounds) {
+        final Rect bounds = drawBounds != null ? drawBounds : touchBounds;
+        if (bounds == null) {
+            return;
+        }
         final int inside = (mViewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
         final int size = getVerticalScrollbarWidth();
         int verticalScrollbarPosition = mVerticalScrollbarPosition;
@@ -15544,6 +15576,29 @@
         bounds.top = mScrollY + (mPaddingTop & inside);
         bounds.right = bounds.left + size;
         bounds.bottom = mScrollY + height - (mUserPaddingBottom & inside);
+
+        if (touchBounds == null) {
+            return;
+        }
+        if (touchBounds != bounds) {
+            touchBounds.set(bounds);
+        }
+        final int minTouchTarget = mScrollCache.scrollBarMinTouchTarget;
+        if (touchBounds.width() < minTouchTarget) {
+            final int adjust = (minTouchTarget - touchBounds.width()) / 2;
+            if (verticalScrollbarPosition == SCROLLBAR_POSITION_RIGHT) {
+                touchBounds.right = Math.min(touchBounds.right + adjust, mScrollX + width);
+                touchBounds.left = touchBounds.right - minTouchTarget;
+            } else {
+                touchBounds.left = Math.max(touchBounds.left + adjust, mScrollX);
+                touchBounds.right = touchBounds.left + minTouchTarget;
+            }
+        }
+        if (touchBounds.height() < minTouchTarget) {
+            final int adjust = (minTouchTarget - touchBounds.height()) / 2;
+            touchBounds.top -= adjust;
+            touchBounds.bottom = touchBounds.top + minTouchTarget;
+        }
     }
 
     /**
@@ -15602,7 +15657,7 @@
             if (mRoundScrollbarRenderer != null) {
                 if (drawVerticalScrollBar) {
                     final Rect bounds = cache.mScrollBarBounds;
-                    getVerticalScrollBarBounds(bounds);
+                    getVerticalScrollBarBounds(bounds, null);
                     mRoundScrollbarRenderer.drawRoundScrollbars(
                             canvas, (float) cache.scrollBar.getAlpha() / 255f, bounds);
                     if (invalidate) {
@@ -15618,7 +15673,7 @@
                             computeHorizontalScrollOffset(),
                             computeHorizontalScrollExtent(), false);
                     final Rect bounds = cache.mScrollBarBounds;
-                    getHorizontalScrollBarBounds(bounds);
+                    getHorizontalScrollBarBounds(bounds, null);
                     onDrawHorizontalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
                             bounds.right, bounds.bottom);
                     if (invalidate) {
@@ -15631,7 +15686,7 @@
                             computeVerticalScrollOffset(),
                             computeVerticalScrollExtent(), true);
                     final Rect bounds = cache.mScrollBarBounds;
-                    getVerticalScrollBarBounds(bounds);
+                    getVerticalScrollBarBounds(bounds, null);
                     onDrawVerticalScrollBar(canvas, scrollBar, bounds.left, bounds.top,
                             bounds.right, bounds.bottom);
                     if (invalidate) {
@@ -24123,6 +24178,7 @@
         public int scrollBarFadeDuration;
 
         public int scrollBarSize;
+        public int scrollBarMinTouchTarget;
         public ScrollBarDrawable scrollBar;
         public float[] interpolatorValues;
         public View host;
@@ -24151,6 +24207,7 @@
         private int mLastColor;
 
         public final Rect mScrollBarBounds = new Rect();
+        public final Rect mScrollBarTouchBounds = new Rect();
 
         public static final int NOT_DRAGGING = 0;
         public static final int DRAGGING_VERTICAL_SCROLL_BAR = 1;
@@ -24162,6 +24219,7 @@
         public ScrollabilityCache(ViewConfiguration configuration, View host) {
             fadingEdgeLength = configuration.getScaledFadingEdgeLength();
             scrollBarSize = configuration.getScaledScrollBarSize();
+            scrollBarMinTouchTarget = configuration.getScaledMinScrollbarTouchTarget();
             scrollBarDefaultDelayBeforeFade = ViewConfiguration.getScrollDefaultDelay();
             scrollBarFadeDuration = ViewConfiguration.getScrollBarFadeDuration();
 
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 0e753f3..5d01b416 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -156,6 +156,11 @@
     private static final int TOUCH_SLOP = 8;
 
     /**
+     * Defines the minimum size of the touch target for a scrollbar in dips
+     */
+    private static final int MIN_SCROLLBAR_TOUCH_TARGET = 48;
+
+    /**
      * Distance the first touch can wander before we stop considering this event a double tap
      * (in dips)
      */
@@ -274,6 +279,7 @@
     private final int mMaximumFlingVelocity;
     private final int mScrollbarSize;
     private final int mTouchSlop;
+    private final int mMinScrollbarTouchTarget;
     private final int mDoubleTapTouchSlop;
     private final int mPagingTouchSlop;
     private final int mDoubleTapSlop;
@@ -302,6 +308,7 @@
         mMaximumFlingVelocity = MAXIMUM_FLING_VELOCITY;
         mScrollbarSize = SCROLL_BAR_SIZE;
         mTouchSlop = TOUCH_SLOP;
+        mMinScrollbarTouchTarget = MIN_SCROLLBAR_TOUCH_TARGET;
         mDoubleTapTouchSlop = DOUBLE_TAP_TOUCH_SLOP;
         mPagingTouchSlop = PAGING_TOUCH_SLOP;
         mDoubleTapSlop = DOUBLE_TAP_SLOP;
@@ -386,6 +393,8 @@
                 com.android.internal.R.bool.config_ui_enableFadingMarquee);
         mTouchSlop = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.config_viewConfigurationTouchSlop);
+        mMinScrollbarTouchTarget = res.getDimensionPixelSize(
+                com.android.internal.R.dimen.config_minScrollbarTouchTarget);
         mPagingTouchSlop = mTouchSlop * 2;
 
         mDoubleTapTouchSlop = mTouchSlop;
@@ -440,6 +449,14 @@
     }
 
     /**
+     * @return the minimum size of the scrollbar thumb's touch target in pixels
+     * @hide
+     */
+    public int getScaledMinScrollbarTouchTarget() {
+        return mMinScrollbarTouchTarget;
+    }
+
+    /**
      * @return Duration of the fade when scrollbars fade away in milliseconds
      */
     public static int getScrollBarFadeDuration() {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 581537d..54c392f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1847,6 +1847,9 @@
     -->
     <fraction name="config_maximumScreenDimRatio">20%</fraction>
 
+    <!-- Minimum size of the scrollbar thumb's touch target. -->
+    <dimen name="config_minScrollbarTouchTarget">48dp</dimen>
+
     <!-- Base "touch slop" value used by ViewConfiguration as a
          movement threshold where scrolling should begin. -->
     <dimen name="config_viewConfigurationTouchSlop">8dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index f72906d..1324e38 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -423,6 +423,7 @@
 
   <java-symbol type="dimen" name="accessibility_touch_slop" />
   <java-symbol type="dimen" name="alert_dialog_round_padding"/>
+  <java-symbol type="dimen" name="config_minScrollbarTouchTarget" />
   <java-symbol type="dimen" name="config_prefDialogWidth" />
   <java-symbol type="dimen" name="config_viewConfigurationTouchSlop" />
   <java-symbol type="dimen" name="config_viewMinFlingVelocity" />