Merge "GestureNav: Fix AbsSeekBar thumb exclusion" into qt-r1-dev
am: 92f61f0323

Change-Id: If88cb2016add304bb4ac18c957abb71538329583
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 50bb688..1db9123 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -91,6 +91,7 @@
     @UnsupportedAppUsage
     private float mDisabledAlpha;
 
+    private int mThumbExclusionMaxSize;
     private int mScaledTouchSlop;
     private float mTouchDownX;
     @UnsupportedAppUsage
@@ -170,6 +171,8 @@
         applyTickMarkTint();
 
         mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        mThumbExclusionMaxSize = getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.seekbar_thumb_exclusion_max_size);
     }
 
     /**
@@ -762,12 +765,30 @@
         }
         mGestureExclusionRects.clear();
         thumb.copyBounds(mThumbRect);
+        mThumbRect.offset(mPaddingLeft - mThumbOffset, mPaddingTop);
+        growRectTo(mThumbRect, Math.min(getHeight(), mThumbExclusionMaxSize));
         mGestureExclusionRects.add(mThumbRect);
         mGestureExclusionRects.addAll(mUserGestureExclusionRects);
         super.setSystemGestureExclusionRects(mGestureExclusionRects);
     }
 
     /**
+     * Grows {@code r} from its center such that each dimension is at least {@code minimumSize}.
+     */
+    private void growRectTo(Rect r, int minimumSize) {
+        int dy = (minimumSize - r.height()) / 2;
+        if (dy > 0) {
+            r.top -= dy;
+            r.bottom += dy;
+        }
+        int dx = (minimumSize - r.width()) / 2;
+        if (dx > 0) {
+            r.left -= dx;
+            r.right += dx;
+        }
+    }
+
+    /**
      * @hide
      */
     @Override
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 6653879..609659b 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -748,6 +748,8 @@
     <!-- Line spacing modifier for the message field of the harmful app dialog -->
     <item name="harmful_app_message_line_spacing_modifier" type="dimen">1.22</item>
 
+    <dimen name="seekbar_thumb_exclusion_max_size">48dp</dimen>
+
     <!-- chooser (sharesheet) spacing -->
     <dimen name="chooser_corner_radius">8dp</dimen>
     <dimen name="chooser_row_text_option_translate">25dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d275a37..a28bec9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3817,6 +3817,7 @@
   <java-symbol type="color" name="chooser_gradient_highlight" />
   <java-symbol type="drawable" name="chooser_direct_share_label_placeholder" />
   <java-symbol type="dimen" name="chooser_direct_share_label_placeholder_max_width" />
+  <java-symbol type="dimen" name="seekbar_thumb_exclusion_max_size" />
   <java-symbol type="layout" name="chooser_az_label_row" />
   <java-symbol type="string" name="chooser_all_apps_button_label" />
   <java-symbol type="anim" name="resolver_launch_anim" />
diff --git a/core/tests/coretests/src/android/widget/AbsSeekBarTest.java b/core/tests/coretests/src/android/widget/AbsSeekBarTest.java
new file mode 100644
index 0000000..aec6096
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/AbsSeekBarTest.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 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 android.widget;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.hasSize;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.platform.test.annotations.Presubmit;
+import android.view.View;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+@Presubmit
+public class AbsSeekBarTest {
+
+    private Context mContext;
+    private AbsSeekBar mBar;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mBar = new SeekBar(mContext);
+    }
+
+    @Test
+    public void testExclusionForThumb_limitedTo48dp() {
+        mBar.setPadding(10, 10, 10, 10);
+        mBar.setThumb(newThumb(dpToPx(20)));
+        mBar.setMin(0);
+        mBar.setMax(100);
+        mBar.setProgress(50);
+        measureAndLayout(dpToPx(200), dpToPx(100));
+        List<Rect> exclusions = mBar.getSystemGestureExclusionRects();
+
+        assertEquals("exclusions should be size 1, but was " + exclusions, 1, exclusions.size());
+        assertEquals("exclusion should be centered on thumb",
+                center(mBar), center(exclusions.get(0)));
+        assertEquals("exclusion should be 48dp high", dpToPx(48), exclusions.get(0).height());
+        assertEquals("exclusion should be 48dp wide", dpToPx(48), exclusions.get(0).width());
+    }
+
+    @Test
+    public void testExclusionForThumb_limitedToHeight() {
+        mBar.setPadding(10, 10, 10, 10);
+        mBar.setThumb(newThumb(dpToPx(20)));
+        mBar.setMin(0);
+        mBar.setMax(100);
+        mBar.setProgress(50);
+        measureAndLayout(dpToPx(200), dpToPx(32));
+        List<Rect> exclusions = mBar.getSystemGestureExclusionRects();
+
+        assertEquals("exclusions should be size 1, but was " + exclusions, 1, exclusions.size());
+        assertEquals("exclusion should be centered on thumb",
+                center(mBar), center(exclusions.get(0)));
+        assertEquals("exclusion should be 32dp high", dpToPx(32), exclusions.get(0).height());
+        assertEquals("exclusion should be 32dp wide", dpToPx(32), exclusions.get(0).width());
+    }
+
+    @Test
+    public void testExclusionForThumb_passesThroughUserExclusions() {
+        mBar.setSystemGestureExclusionRects(Arrays.asList(new Rect(1, 2, 3, 4)));
+
+        mBar.setPadding(10, 10, 10, 10);
+        mBar.setThumb(newThumb(dpToPx(20)));
+        mBar.setMin(0);
+        mBar.setMax(100);
+        mBar.setProgress(50);
+        measureAndLayout(dpToPx(200), dpToPx(32));
+
+        assertThat(mBar.getSystemGestureExclusionRects(), hasItem(new Rect(1, 2, 3, 4)));
+        assertThat(mBar.getSystemGestureExclusionRects(), hasSize(2));
+
+        mBar.setSystemGestureExclusionRects(Arrays.asList(new Rect(3, 4, 5, 6)));
+        assertThat(mBar.getSystemGestureExclusionRects(), hasItem(new Rect(3, 4, 5, 6)));
+        assertThat(mBar.getSystemGestureExclusionRects(), hasSize(2));
+    }
+
+    private Point center(Rect rect) {
+        return new Point(rect.centerX(), rect.centerY());
+    }
+
+    private Point center(View view) {
+        return center(new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+    }
+
+    private ShapeDrawable newThumb(int size) {
+        final ShapeDrawable thumb = new ShapeDrawable(new RectShape());
+        thumb.setIntrinsicWidth(size);
+        thumb.setIntrinsicHeight(size);
+        return thumb;
+    }
+
+    private void measureAndLayout(int wPx, int hPx) {
+        mBar.measure(makeMeasureSpec(wPx, EXACTLY), makeMeasureSpec(hPx, EXACTLY));
+        mBar.layout(0, 0, wPx, hPx);
+    }
+
+    private int dpToPx(int dp) {
+        return (int) (mContext.getResources().getDisplayMetrics().density * dp);
+    }
+}