• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget.cts;
18 
19 import static android.widget.cts.util.StretchEdgeUtil.dragAndHoldExecute;
20 import static android.widget.cts.util.StretchEdgeUtil.fling;
21 
22 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertFalse;
26 import static org.junit.Assert.assertNotEquals;
27 import static org.junit.Assert.assertSame;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.fail;
30 import static org.mockito.Matchers.eq;
31 import static org.mockito.Mockito.spy;
32 import static org.mockito.Mockito.verify;
33 
34 import android.animation.ValueAnimator;
35 import android.app.Activity;
36 import android.app.Instrumentation;
37 import android.content.Context;
38 import android.graphics.Bitmap;
39 import android.graphics.Canvas;
40 import android.graphics.Color;
41 import android.graphics.Paint;
42 import android.graphics.Rect;
43 import android.util.AttributeSet;
44 import android.util.Xml;
45 import android.view.InputDevice;
46 import android.view.KeyEvent;
47 import android.view.MotionEvent;
48 import android.view.Surface;
49 import android.view.SurfaceHolder;
50 import android.view.SurfaceView;
51 import android.view.View;
52 import android.view.View.MeasureSpec;
53 import android.view.ViewGroup;
54 import android.widget.EdgeEffect;
55 import android.widget.FrameLayout;
56 import android.widget.ScrollView;
57 import android.widget.TextView;
58 import android.widget.cts.util.NoReleaseEdgeEffect;
59 import android.widget.cts.util.StretchEdgeUtil;
60 import android.widget.cts.util.TestUtils;
61 
62 import androidx.annotation.NonNull;
63 import androidx.test.InstrumentationRegistry;
64 import androidx.test.annotation.UiThreadTest;
65 import androidx.test.filters.LargeTest;
66 import androidx.test.filters.MediumTest;
67 import androidx.test.rule.ActivityTestRule;
68 import androidx.test.runner.AndroidJUnit4;
69 
70 import com.android.compatibility.common.util.PollingCheck;
71 
72 import org.junit.After;
73 import org.junit.Before;
74 import org.junit.Rule;
75 import org.junit.Test;
76 import org.junit.runner.RunWith;
77 import org.xmlpull.v1.XmlPullParser;
78 
79 /**
80  * Test {@link ScrollView}.
81  */
82 @MediumTest
83 @RunWith(AndroidJUnit4.class)
84 public class ScrollViewTest {
85     // view dpi constants. Must match those defined in scroll_view layout
86     private static final int ITEM_WIDTH_DPI  = 250;
87     private static final int ITEM_HEIGHT_DPI = 100;
88     private static final int ITEM_COUNT  = 15;
89     private static final int PAGE_WIDTH_DPI  = 100;
90     private static final int PAGE_HEIGHT_DPI = 100;
91     private static final int TOLERANCE = 2;
92 
93     private int mItemWidth;
94     private int mItemHeight;
95     private int mPageWidth;
96     private int mPageHeight;
97     private int mScrollBottom;
98     private int mScrollRight;
99 
100     private Instrumentation mInstrumentation;
101     private Activity mActivity;
102     private ScrollView mScrollViewRegular;
103     private ScrollView mScrollViewCustom;
104     private MyScrollView mScrollViewCustomEmpty;
105     private MyScrollView mScrollViewStretch;
106 
107     private SurfaceView mSurfaceView;
108     private float mDurationScale = 1f;
109 
110     @Rule
111     public ActivityTestRule<ScrollViewCtsActivity> mActivityRule =
112             new ActivityTestRule<>(ScrollViewCtsActivity.class);
113 
114     @Before
setup()115     public void setup() {
116         mDurationScale = ValueAnimator.getDurationScale();
117         ValueAnimator.setDurationScale(1f);
118         mInstrumentation = InstrumentationRegistry.getInstrumentation();
119         mActivity = mActivityRule.getActivity();
120         mScrollViewRegular = (ScrollView) mActivity.findViewById(R.id.scroll_view_regular);
121         mScrollViewCustom = (ScrollView) mActivity.findViewById(R.id.scroll_view_custom);
122         mScrollViewCustomEmpty = (MyScrollView) mActivity.findViewById(
123                 R.id.scroll_view_custom_empty);
124         mScrollViewStretch = (MyScrollView) mActivity.findViewById(R.id.scroll_view_stretch);
125         mSurfaceView = mActivity.findViewById(R.id.surfaceview_stretch_target);
126 
127         // calculate pixel positions from dpi constants.
128         mItemWidth = TestUtils.dpToPx(mActivity, ITEM_WIDTH_DPI);
129         mItemHeight = TestUtils.dpToPx(mActivity, ITEM_HEIGHT_DPI);
130         mPageWidth = TestUtils.dpToPx(mActivity, PAGE_WIDTH_DPI);
131         mPageHeight = TestUtils.dpToPx(mActivity, PAGE_HEIGHT_DPI);
132 
133         mScrollBottom = mItemHeight * ITEM_COUNT - mPageHeight;
134         mScrollRight = mItemWidth - mPageWidth;
135     }
136 
137     @After
teardown()138     public void teardown() {
139         ValueAnimator.setDurationScale(mDurationScale);
140     }
141 
142     @Test
testConstructor()143     public void testConstructor() {
144         XmlPullParser parser = mActivity.getResources().getLayout(R.layout.scrollview_layout);
145         AttributeSet attrs = Xml.asAttributeSet(parser);
146         new ScrollView(mActivity);
147 
148         new ScrollView(mActivity, attrs);
149 
150         new ScrollView(mActivity, attrs, 0);
151     }
152 
153     @UiThreadTest
154     @Test
testGetMaxScrollAmount()155     public void testGetMaxScrollAmount() {
156         // the value is half of total layout height
157         mScrollViewRegular.layout(0, 0, 100, 200);
158         assertEquals((200 - 0) / 2, mScrollViewRegular.getMaxScrollAmount());
159 
160         mScrollViewRegular.layout(0, 0, 150, 100);
161         assertEquals((100 - 0) / 2, mScrollViewRegular.getMaxScrollAmount());
162     }
163 
164     @UiThreadTest
165     @Test
testAddView()166     public void testAddView() {
167         TextView child0 = new TextView(mActivity);
168         mScrollViewRegular.addView(child0);
169         assertSame(child0, mScrollViewRegular.getChildAt(0));
170 
171         assertEquals(1, mScrollViewRegular.getChildCount());
172         TextView child1 = new TextView(mActivity);
173         try {
174             mScrollViewRegular.addView(child1);
175             fail("ScrollView can host only one direct child");
176         } catch (IllegalStateException e) {
177             // expected
178         }
179         assertEquals(1, mScrollViewRegular.getChildCount());
180     }
181 
182     @UiThreadTest
183     @Test
testAddViewWithIndex()184     public void testAddViewWithIndex() {
185         TextView child0 = new TextView(mActivity);
186         mScrollViewRegular.addView(child0, 0);
187         assertSame(child0, mScrollViewRegular.getChildAt(0));
188 
189         assertEquals(1, mScrollViewRegular.getChildCount());
190         TextView child1 = new TextView(mActivity);
191         try {
192             mScrollViewRegular.addView(child1, 1);
193             fail("ScrollView can host only one direct child");
194         } catch (IllegalStateException e) {
195             // expected
196         }
197         assertEquals(1, mScrollViewRegular.getChildCount());
198 
199         mScrollViewRegular.removeAllViews();
200         mScrollViewRegular = new ScrollView(mActivity);
201         mScrollViewRegular.addView(child0, -1);
202         assertSame(child0, mScrollViewRegular.getChildAt(0));
203 
204         assertEquals(1, mScrollViewRegular.getChildCount());
205         child1 = new TextView(mActivity);
206         try {
207             mScrollViewRegular.addView(child1, -1);
208             fail("ScrollView can host only one direct child");
209         } catch (IllegalStateException e) {
210             // expected
211         }
212         assertEquals(1, mScrollViewRegular.getChildCount());
213 
214         mScrollViewRegular.removeAllViews();
215         mScrollViewRegular = new ScrollView(mActivity);
216         try {
217             mScrollViewRegular.addView(child0, 1);
218             fail("ScrollView can host only one direct child");
219         } catch (IndexOutOfBoundsException e) {
220             // expected
221         }
222     }
223 
224     @UiThreadTest
225     @Test
testAddViewWithLayoutParams()226     public void testAddViewWithLayoutParams() {
227         TextView child0 = new TextView(mActivity);
228         mScrollViewRegular.addView(child0, new ViewGroup.LayoutParams(200, 100));
229         assertSame(child0, mScrollViewRegular.getChildAt(0));
230         assertEquals(200, child0.getLayoutParams().width);
231         assertEquals(100, child0.getLayoutParams().height);
232 
233         assertEquals(1, mScrollViewRegular.getChildCount());
234         TextView child1 = new TextView(mActivity);
235         try {
236             mScrollViewRegular.addView(child1, new ViewGroup.LayoutParams(200, 100));
237             fail("ScrollView can host only one direct child");
238         } catch (IllegalStateException e) {
239             // expected
240         }
241         assertEquals(1, mScrollViewRegular.getChildCount());
242 
243         mScrollViewRegular.removeAllViews();
244         mScrollViewRegular = new ScrollView(mActivity);
245         child0 = new TextView(mActivity);
246         try {
247             mScrollViewRegular.addView(child0, null);
248             fail("The LayoutParams should not be null!");
249         } catch (NullPointerException e) {
250             // expected
251         }
252     }
253 
254     @UiThreadTest
255     @Test
testAddViewWithIndexAndLayoutParams()256     public void testAddViewWithIndexAndLayoutParams() {
257         TextView child0 = new TextView(mActivity);
258         mScrollViewRegular.addView(child0, 0, new ViewGroup.LayoutParams(200, 100));
259         assertSame(child0, mScrollViewRegular.getChildAt(0));
260         assertEquals(200, child0.getLayoutParams().width);
261         assertEquals(100, child0.getLayoutParams().height);
262 
263         assertEquals(1, mScrollViewRegular.getChildCount());
264         TextView child1 = new TextView(mActivity);
265         try {
266             mScrollViewRegular.addView(child1, 0, new ViewGroup.LayoutParams(200, 100));
267             fail("ScrollView can host only one direct child");
268         } catch (IllegalStateException e) {
269             // expected
270         }
271         assertEquals(1, mScrollViewRegular.getChildCount());
272 
273         mScrollViewRegular.removeAllViews();
274         child0 = new TextView(mActivity);
275         try {
276             mScrollViewRegular.addView(child0, null);
277             fail("The LayoutParams should not be null!");
278         } catch (NullPointerException e) {
279             // expected
280         }
281 
282         mScrollViewRegular.removeAllViews();
283         mScrollViewRegular.addView(child0, -1, new ViewGroup.LayoutParams(300, 150));
284         assertSame(child0, mScrollViewRegular.getChildAt(0));
285         assertEquals(300, child0.getLayoutParams().width);
286         assertEquals(150, child0.getLayoutParams().height);
287 
288         assertEquals(1, mScrollViewRegular.getChildCount());
289         child1 = new TextView(mActivity);
290         try {
291             mScrollViewRegular.addView(child1, -1, new ViewGroup.LayoutParams(200, 100));
292             fail("ScrollView can host only one direct child");
293         } catch (IllegalStateException e) {
294             // expected
295         }
296         assertEquals(1, mScrollViewRegular.getChildCount());
297 
298         mScrollViewRegular.removeAllViews();
299         try {
300             mScrollViewRegular.addView(child0, 1, new ViewGroup.LayoutParams(200, 100));
301             fail("ScrollView can host only one direct child");
302         } catch (IndexOutOfBoundsException e) {
303             // expected
304         }
305     }
306 
307     @UiThreadTest
308     @Test
testAccessFillViewport()309     public void testAccessFillViewport() {
310         assertFalse(mScrollViewRegular.isFillViewport());
311         mScrollViewRegular.layout(0, 0, 100, 100);
312         assertFalse(mScrollViewRegular.isLayoutRequested());
313 
314         mScrollViewRegular.setFillViewport(false);
315         assertFalse(mScrollViewRegular.isFillViewport());
316         assertFalse(mScrollViewRegular.isLayoutRequested());
317 
318         mScrollViewRegular.setFillViewport(true);
319         assertTrue(mScrollViewRegular.isFillViewport());
320         assertTrue(mScrollViewRegular.isLayoutRequested());
321 
322         mScrollViewRegular.layout(0, 0, 100, 100);
323         assertFalse(mScrollViewRegular.isLayoutRequested());
324 
325         mScrollViewRegular.setFillViewport(false);
326         assertFalse(mScrollViewRegular.isFillViewport());
327         assertTrue(mScrollViewRegular.isLayoutRequested());
328     }
329 
330     @Test
testAccessSmoothScrollingEnabled()331     public void testAccessSmoothScrollingEnabled() throws Throwable {
332         assertTrue(mScrollViewCustom.isSmoothScrollingEnabled());
333 
334         // scroll immediately
335         mScrollViewCustom.setSmoothScrollingEnabled(false);
336         assertFalse(mScrollViewCustom.isSmoothScrollingEnabled());
337 
338         mActivityRule.runOnUiThread(() -> mScrollViewCustom.fullScroll(View.FOCUS_DOWN));
339 
340         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY(), TOLERANCE);
341 
342         mActivityRule.runOnUiThread(() -> mScrollViewCustom.fullScroll(View.FOCUS_UP));
343         assertEquals(0, mScrollViewCustom.getScrollY());
344 
345         // smooth scroll
346         mScrollViewCustom.setSmoothScrollingEnabled(true);
347         assertTrue(mScrollViewCustom.isSmoothScrollingEnabled());
348 
349         mActivityRule.runOnUiThread(() -> mScrollViewCustom.fullScroll(View.FOCUS_DOWN));
350         pollingCheckSmoothScrolling(0, 0, 0, mScrollBottom);
351         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY(), TOLERANCE);
352 
353         mActivityRule.runOnUiThread(() -> mScrollViewCustom.fullScroll(View.FOCUS_UP));
354         pollingCheckSmoothScrolling(0, 0, mScrollBottom, 0);
355         assertEquals(0, mScrollViewCustom.getScrollY());
356     }
357 
358     @UiThreadTest
359     @Test
testMeasureChild()360     public void testMeasureChild() {
361         MyView child = new MyView(mActivity);
362         child.setBackgroundDrawable(null);
363         child.setPadding(0, 0, 0, 0);
364         child.setMinimumHeight(30);
365         child.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
366         child.measure(MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY),
367                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY));
368 
369         assertEquals(100, child.getMeasuredHeight());
370         assertEquals(100, child.getMeasuredWidth());
371 
372         mScrollViewCustomEmpty.measureChild(child,
373                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY),
374                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY));
375 
376         assertEquals(30, child.getMeasuredHeight());
377         assertEquals(100, child.getMeasuredWidth());
378     }
379 
380     @UiThreadTest
381     @Test
testMeasureChildWithMargins()382     public void testMeasureChildWithMargins() {
383         MyView child = new MyView(mActivity);
384         child.setBackgroundDrawable(null);
385         child.setPadding(0, 0, 0, 0);
386         child.setMinimumHeight(30);
387         child.setLayoutParams(new ViewGroup.MarginLayoutParams(100, 100));
388         child.measure(MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY),
389                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY));
390 
391         assertEquals(100, child.getMeasuredHeight());
392         assertEquals(100, child.getMeasuredWidth());
393 
394         mScrollViewCustomEmpty.measureChildWithMargins(child,
395                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY), 5,
396                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY), 5);
397 
398         assertEquals(30, child.getMeasuredHeight());
399         assertEquals(100, child.getMeasuredWidth());
400     }
401 
402     @UiThreadTest
403     @Test
testMeasureSpecs()404     public void testMeasureSpecs() {
405         MyView child = spy(new MyView(mActivity));
406         mScrollViewCustomEmpty.addView(child);
407 
408         mScrollViewCustomEmpty.measureChild(child,
409                 MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY),
410                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY));
411         verify(child).onMeasure(
412                 eq(MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY)),
413                 eq(MeasureSpec.makeMeasureSpec(100, MeasureSpec.UNSPECIFIED)));
414     }
415 
416     @UiThreadTest
417     @Test
testMeasureSpecsWithPadding()418     public void testMeasureSpecsWithPadding() {
419         MyView child = spy(new MyView(mActivity));
420         mScrollViewCustomEmpty.setPadding(3, 5, 7, 11);
421         mScrollViewCustomEmpty.addView(child);
422 
423         mScrollViewCustomEmpty.measureChild(child,
424                 MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY),
425                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY));
426         verify(child).onMeasure(
427                 eq(MeasureSpec.makeMeasureSpec(140, MeasureSpec.EXACTLY)),
428                 eq(MeasureSpec.makeMeasureSpec(84, MeasureSpec.UNSPECIFIED)));
429     }
430 
431     @UiThreadTest
432     @Test
testMeasureSpecsWithMargins()433     public void testMeasureSpecsWithMargins() {
434         MyView child = spy(new MyView(mActivity));
435         mScrollViewCustomEmpty.addView(child);
436 
437         mScrollViewCustomEmpty.measureChildWithMargins(child,
438                 MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY), 20,
439                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY), 15);
440         verify(child).onMeasure(
441                 eq(MeasureSpec.makeMeasureSpec(130, MeasureSpec.EXACTLY)),
442                 eq(MeasureSpec.makeMeasureSpec(85, MeasureSpec.UNSPECIFIED)));
443     }
444 
445     @UiThreadTest
446     @Test
testMeasureSpecsWithMarginsAndPadding()447     public void testMeasureSpecsWithMarginsAndPadding() {
448         MyView child = spy(new MyView(mActivity));
449         mScrollViewCustomEmpty.setPadding(3, 5, 7, 11);
450         mScrollViewCustomEmpty.addView(child);
451 
452         mScrollViewCustomEmpty.measureChildWithMargins(child,
453                 MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY), 20,
454                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY), 15);
455         verify(child).onMeasure(
456                 eq(MeasureSpec.makeMeasureSpec(120, MeasureSpec.EXACTLY)),
457                 eq(MeasureSpec.makeMeasureSpec(69, MeasureSpec.UNSPECIFIED)));
458     }
459 
460     @UiThreadTest
461     @Test
testMeasureSpecsWithMarginsAndNoHintWidth()462     public void testMeasureSpecsWithMarginsAndNoHintWidth() {
463         MyView child = spy(new MyView(mActivity));
464         mScrollViewCustomEmpty.addView(child);
465 
466         mScrollViewCustomEmpty.measureChildWithMargins(child,
467                 MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY), 20,
468                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 15);
469         verify(child).onMeasure(
470                 eq(MeasureSpec.makeMeasureSpec(130, MeasureSpec.EXACTLY)),
471                 eq(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)));
472     }
473 
474     @UiThreadTest
475     @Test
testFillViewport()476     public void testFillViewport() {
477         mScrollViewRegular.setFillViewport(true);
478 
479         MyView child = new MyView(mActivity);
480         child.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
481 
482         mScrollViewRegular.addView(child);
483         mScrollViewRegular.measure(
484                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY),
485                 MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY));
486 
487         assertEquals(150, child.getMeasuredHeight());
488         assertEquals(100, child.getMeasuredWidth());
489 
490         mScrollViewRegular.layout(0, 0, 100, 150);
491         assertEquals(0, child.getTop());
492     }
493 
494     @UiThreadTest
495     @Test
testFillViewportWithScrollViewPadding()496     public void testFillViewportWithScrollViewPadding() {
497         mScrollViewRegular.setFillViewport(true);
498         mScrollViewRegular.setPadding(3, 10, 5, 7);
499 
500         MyView child = new MyView(mActivity);
501         child.setLayoutParams(new ViewGroup.LayoutParams(10,10));
502         child.setDesiredHeight(30);
503 
504         mScrollViewRegular.addView(child);
505         mScrollViewRegular.measure(
506                 MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY),
507                 MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY));
508 
509         assertEquals(133, child.getMeasuredHeight());
510         assertEquals(10, child.getMeasuredWidth());
511 
512         mScrollViewRegular.layout(0, 0, 100, 150);
513         assertEquals(10, child.getTop());
514     }
515 
516     @UiThreadTest
517     @Test
testFillViewportWithChildMargins()518     public void testFillViewportWithChildMargins() {
519         mScrollViewRegular.setFillViewport(true);
520 
521         MyView child = new MyView(mActivity);
522         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(10, 10);
523         lp.leftMargin = 3;
524         lp.topMargin = 10;
525         lp.rightMargin = 5;
526         lp.bottomMargin = 7;
527         child.setDesiredHeight(30);
528         child.setLayoutParams(lp);
529 
530         mScrollViewRegular.addView(child);
531         mScrollViewRegular.measure(MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY),
532                 MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY));
533 
534         assertEquals(133, child.getMeasuredHeight());
535         assertEquals(10, child.getMeasuredWidth());
536 
537         mScrollViewRegular.layout(0, 0, 100, 150);
538         assertEquals(10, child.getTop());
539     }
540 
541     @UiThreadTest
542     @Test
testFillViewportWithScrollViewPaddingAlreadyFills()543     public void testFillViewportWithScrollViewPaddingAlreadyFills() {
544         mScrollViewRegular.setFillViewport(true);
545         mScrollViewRegular.setPadding(3, 10, 5, 7);
546 
547         MyView child = new MyView(mActivity);
548         child.setDesiredHeight(175);
549 
550         mScrollViewRegular.addView(child);
551         mScrollViewRegular.measure(MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY),
552                 MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY));
553 
554 
555         assertEquals(92, child.getMeasuredWidth());
556         assertEquals(175, child.getMeasuredHeight());
557 
558         mScrollViewRegular.layout(0, 0, 100, 150);
559         assertEquals(10, child.getTop());
560     }
561 
562     @UiThreadTest
563     @Test
testFillViewportWithChildMarginsAlreadyFills()564     public void testFillViewportWithChildMarginsAlreadyFills() {
565         mScrollViewRegular.setFillViewport(true);
566         MyView child = new MyView(mActivity);
567         FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
568                 ViewGroup.LayoutParams.MATCH_PARENT,
569                 ViewGroup.LayoutParams.WRAP_CONTENT);
570 
571         lp.leftMargin = 3;
572         lp.topMargin = 10;
573         lp.rightMargin = 5;
574         lp.bottomMargin = 7;
575         child.setLayoutParams(lp);
576         child.setDesiredHeight(175);
577 
578         mScrollViewRegular.addView(child);
579         mScrollViewRegular.measure(MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY),
580                 MeasureSpec.makeMeasureSpec(150, MeasureSpec.EXACTLY));
581 
582         assertEquals(92, child.getMeasuredWidth());
583         assertEquals(175, child.getMeasuredHeight());
584 
585         mScrollViewRegular.layout(0, 0, 100, 150);
586         assertEquals(10, child.getTop());
587     }
588 
589     @UiThreadTest
590     @Test
testPageScroll()591     public void testPageScroll() {
592         mScrollViewCustom.setSmoothScrollingEnabled(false);
593         assertEquals(0, mScrollViewCustom.getScrollY());
594 
595         assertTrue(mScrollViewCustom.pageScroll(View.FOCUS_DOWN));
596         assertEquals(mPageHeight, mScrollViewCustom.getScrollY(), TOLERANCE);
597 
598         assertTrue(mScrollViewCustom.pageScroll(View.FOCUS_DOWN));
599         assertEquals(mPageHeight * 2, mScrollViewCustom.getScrollY(), TOLERANCE);
600 
601         mScrollViewCustom.scrollTo(mPageWidth, mScrollBottom);
602         assertFalse(mScrollViewCustom.pageScroll(View.FOCUS_DOWN));
603         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY(), TOLERANCE);
604 
605         assertTrue(mScrollViewCustom.pageScroll(View.FOCUS_UP));
606         assertEquals(mScrollBottom - mPageHeight, mScrollViewCustom.getScrollY(), TOLERANCE);
607 
608         assertTrue(mScrollViewCustom.pageScroll(View.FOCUS_UP));
609         assertEquals(mScrollBottom -mPageHeight * 2, mScrollViewCustom.getScrollY(), TOLERANCE);
610 
611         mScrollViewCustom.scrollTo(mPageWidth, 0);
612         assertFalse(mScrollViewCustom.pageScroll(View.FOCUS_UP));
613         assertEquals(0, mScrollViewCustom.getScrollY());
614     }
615 
616     @UiThreadTest
617     @Test
testFullScroll()618     public void testFullScroll() {
619         mScrollViewCustom.setSmoothScrollingEnabled(false);
620         assertEquals(0, mScrollViewCustom.getScrollY());
621 
622         assertTrue(mScrollViewCustom.fullScroll(View.FOCUS_DOWN));
623         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY());
624 
625         assertFalse(mScrollViewCustom.fullScroll(View.FOCUS_DOWN));
626         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY());
627 
628         assertTrue(mScrollViewCustom.fullScroll(View.FOCUS_UP));
629         assertEquals(0, mScrollViewCustom.getScrollY());
630 
631         assertFalse(mScrollViewCustom.fullScroll(View.FOCUS_UP));
632         assertEquals(0, mScrollViewCustom.getScrollY());
633     }
634 
635     @UiThreadTest
636     @Test
testArrowScroll()637     public void testArrowScroll() {
638         mScrollViewCustom.setSmoothScrollingEnabled(false);
639         assertEquals(0, mScrollViewCustom.getScrollY());
640 
641         int y = mScrollViewCustom.getScrollY();
642         while (mScrollBottom != y) {
643             assertTrue(mScrollViewCustom.arrowScroll(View.FOCUS_DOWN));
644             assertTrue(y <= mScrollViewCustom.getScrollY());
645             y = mScrollViewCustom.getScrollY();
646         }
647 
648         assertFalse(mScrollViewCustom.arrowScroll(View.FOCUS_DOWN));
649         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY());
650 
651         y = mScrollViewCustom.getScrollY();
652         while (0 != y) {
653             assertTrue(mScrollViewCustom.arrowScroll(View.FOCUS_UP));
654             assertTrue(y >= mScrollViewCustom.getScrollY());
655             y = mScrollViewCustom.getScrollY();
656         }
657 
658         assertFalse(mScrollViewCustom.arrowScroll(View.FOCUS_UP));
659         assertEquals(0, mScrollViewCustom.getScrollY());
660     }
661 
662     @UiThreadTest
663     @Test
testKeyPageUpDownScroll()664     public void testKeyPageUpDownScroll() {
665         final KeyEvent pageDownDownEvent =
666                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_PAGE_DOWN);
667         final KeyEvent pageDownUpEvent =
668                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_PAGE_DOWN);
669 
670         final KeyEvent pageUpDownEvent =
671                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_PAGE_UP);
672         final KeyEvent pageUpUpEvent =
673                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_PAGE_UP);
674 
675         mScrollViewCustom.setSmoothScrollingEnabled(false);
676         assertEquals(0, mScrollViewCustom.getScrollY());
677 
678         int y = mScrollViewCustom.getScrollY();
679         while (mScrollBottom != y) {
680             assertTrue(mScrollViewCustom.dispatchKeyEvent(pageDownDownEvent));
681             assertFalse(mScrollViewCustom.dispatchKeyEvent(pageDownUpEvent));
682 
683             assertTrue(y <= mScrollViewCustom.getScrollY());
684             y = mScrollViewCustom.getScrollY();
685         }
686 
687         assertFalse(mScrollViewCustom.arrowScroll(View.FOCUS_DOWN));
688         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY());
689 
690         y = mScrollViewCustom.getScrollY();
691         while (0 != y) {
692             assertTrue(mScrollViewCustom.dispatchKeyEvent(pageUpDownEvent));
693             assertFalse(mScrollViewCustom.dispatchKeyEvent(pageUpUpEvent));
694 
695             assertTrue(y >= mScrollViewCustom.getScrollY());
696             y = mScrollViewCustom.getScrollY();
697         }
698 
699         assertFalse(mScrollViewCustom.arrowScroll(View.FOCUS_UP));
700         assertEquals(0, mScrollViewCustom.getScrollY());
701     }
702 
703     @UiThreadTest
704     @Test
testKeyHomeEndScroll()705     public void testKeyHomeEndScroll() {
706         final KeyEvent endDownEvent =
707                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MOVE_END);
708         final KeyEvent endUpEvent =
709                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MOVE_END);
710 
711         final KeyEvent homeDownEvent =
712                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MOVE_HOME);
713         final KeyEvent homeUpEvent =
714                 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MOVE_HOME);
715 
716         mScrollViewCustom.setSmoothScrollingEnabled(false);
717         assertEquals(0, mScrollViewCustom.getScrollY());
718 
719         // Send END KeyEvent
720         assertTrue(mScrollViewCustom.dispatchKeyEvent(endDownEvent));
721         assertFalse(mScrollViewCustom.dispatchKeyEvent(endUpEvent));
722 
723         // End key should scroll until end of the content.
724         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY());
725 
726         // Send HOME KeyEvent
727         assertTrue(mScrollViewCustom.dispatchKeyEvent(homeDownEvent));
728         assertFalse(mScrollViewCustom.dispatchKeyEvent(homeUpEvent));
729 
730         // HOME key should scroll up to top.
731         assertEquals(0, mScrollViewCustom.getScrollY());
732     }
733 
734     @Test
testSmoothScrollBy()735     public void testSmoothScrollBy() throws Throwable {
736         assertEquals(0, mScrollViewCustom.getScrollX());
737         assertEquals(0, mScrollViewCustom.getScrollY());
738 
739         mActivityRule.runOnUiThread(
740                 () -> mScrollViewCustom.smoothScrollBy(mScrollRight, mScrollBottom));
741         // smoothScrollBy doesn't scroll in X
742         pollingCheckSmoothScrolling(0, 0, 0, mScrollBottom);
743         assertEquals(0, mScrollViewCustom.getScrollX());
744         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY());
745 
746         mActivityRule.runOnUiThread(
747                 () -> mScrollViewCustom.smoothScrollBy(-mScrollRight, -mScrollBottom));
748         pollingCheckSmoothScrolling(mScrollRight, 0, mScrollBottom, 0);
749         assertEquals(0, mScrollViewCustom.getScrollX());
750         assertEquals(0, mScrollViewCustom.getScrollY());
751     }
752 
753     @Test
testSmoothScrollTo()754     public void testSmoothScrollTo() throws Throwable {
755         assertEquals(0, mScrollViewCustom.getScrollX());
756         assertEquals(0, mScrollViewCustom.getScrollY());
757 
758         mActivityRule.runOnUiThread(
759                 () -> mScrollViewCustom.smoothScrollTo(mScrollRight, mScrollBottom));
760         // smoothScrollTo doesn't scroll in X
761         pollingCheckSmoothScrolling(0, 0, 0, mScrollBottom);
762         assertEquals(0, mScrollViewCustom.getScrollX());
763         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY());
764 
765         mActivityRule.runOnUiThread(
766                 () -> mScrollViewCustom.smoothScrollTo(mPageWidth, mPageHeight));
767         pollingCheckSmoothScrolling(0, 0, mScrollBottom, mPageHeight);
768         assertEquals(0, mScrollViewCustom.getScrollX());
769         assertEquals(mPageHeight, mScrollViewCustom.getScrollY());
770     }
771 
772     @UiThreadTest
773     @Test
testComputeScrollDeltaToGetChildRectOnScreen()774     public void testComputeScrollDeltaToGetChildRectOnScreen() {
775         mScrollViewCustom.setSmoothScrollingEnabled(false);
776         int edge = mScrollViewCustom.getVerticalFadingEdgeLength();
777 
778         // Rect's height is smaller than scroll view
779         Rect rect = new Rect(0, 0, 0, 0);
780         assertEquals(0,
781                 ((MyScrollView) mScrollViewCustom).computeScrollDeltaToGetChildRectOnScreen(rect));
782 
783         rect = new Rect(0, edge, 0, mPageHeight);
784         assertEquals(0,
785                 ((MyScrollView) mScrollViewCustom).computeScrollDeltaToGetChildRectOnScreen(rect));
786 
787         mScrollViewCustom.scrollTo(0, 0);
788         rect = new Rect(0, edge + 1, 0, mPageHeight);
789         assertEquals(edge,
790                 ((MyScrollView) mScrollViewCustom).computeScrollDeltaToGetChildRectOnScreen(rect));
791     }
792 
793     @UiThreadTest
794     @Test
testComputeVerticalScrollRange()795     public void testComputeVerticalScrollRange() {
796         assertTrue(mScrollViewCustom.getChildCount() > 0);
797         assertEquals(mItemHeight * ITEM_COUNT,
798                 ((MyScrollView) mScrollViewCustom).computeVerticalScrollRange(), TOLERANCE);
799 
800         MyScrollView myScrollView = new MyScrollView(mActivity);
801         assertEquals(0, myScrollView.getChildCount());
802         assertEquals(0, myScrollView.computeVerticalScrollRange());
803     }
804 
805     @UiThreadTest
806     @Test
testRequestChildFocus()807     public void testRequestChildFocus() {
808         mScrollViewCustom.setSmoothScrollingEnabled(false);
809 
810         View firstChild = mScrollViewCustom.findViewById(R.id.first_child);
811         View lastChild = mScrollViewCustom.findViewById(R.id.last_child);
812         firstChild.requestFocus();
813 
814         int scrollY = mScrollViewCustom.getScrollY();
815         mScrollViewCustom.requestChildFocus(lastChild, lastChild);
816         // check scrolling to the child which wants focus
817         assertTrue(mScrollViewCustom.getScrollY() > scrollY);
818 
819         scrollY = mScrollViewCustom.getScrollY();
820         mScrollViewCustom.requestChildFocus(firstChild, firstChild);
821         // check scrolling to the child which wants focus
822         assertTrue(mScrollViewCustom.getScrollY() < scrollY);
823     }
824 
825     @UiThreadTest
826     @Test
827     public void testRequestChildRectangleOnScreen() {
828         mScrollViewCustom.setSmoothScrollingEnabled(false);
829         mScrollViewCustom.setVerticalFadingEdgeEnabled(true);
830         int edge = mScrollViewCustom.getVerticalFadingEdgeLength();
831 
832         View child = mScrollViewCustom.findViewById(R.id.first_child);
833         int orgRectSize = (int)(10 * mActivity.getResources().getDisplayMetrics().density);
834         final Rect originalRect = new Rect(0, 0, orgRectSize, orgRectSize);
835         final Rect newRect = new Rect(mItemWidth - orgRectSize, mItemHeight - orgRectSize,
836                 mItemWidth, mItemHeight);
837 
838         assertFalse(mScrollViewCustom.requestChildRectangleOnScreen(child, originalRect, true));
839         assertEquals(0, mScrollViewCustom.getScrollX());
840         assertEquals(0, mScrollViewCustom.getScrollY());
841 
842         assertTrue(mScrollViewCustom.requestChildRectangleOnScreen(child, newRect, true));
843         assertEquals(0, mScrollViewCustom.getScrollX());
844         assertEquals(edge, mScrollViewCustom.getScrollY());
845     }
846 
847     @UiThreadTest
848     @Test
849     public void testRequestLayout() {
850         mScrollViewCustom.requestLayout();
851 
852         assertTrue(mScrollViewCustom.isLayoutRequested());
853     }
854 
855     @Test
856     public void testFling() throws Throwable {
857         mScrollViewCustom.setSmoothScrollingEnabled(true);
858         assertEquals(0, mScrollViewCustom.getScrollY());
859 
860         // fling towards bottom
861         mActivityRule.runOnUiThread(() -> mScrollViewCustom.fling(2000));
862         pollingCheckFling(0, true);
863 
864         final int currentY = mScrollViewCustom.getScrollY();
865         // fling towards top
866         mActivityRule.runOnUiThread(() -> mScrollViewCustom.fling(-2000));
867         pollingCheckFling(currentY, false);
868     }
869 
870     @UiThreadTest
871     @Test
testScrollTo()872     public void testScrollTo() {
873         mScrollViewCustom.setSmoothScrollingEnabled(false);
874 
875         mScrollViewCustom.scrollTo(10, 10);
876         assertEquals(10, mScrollViewCustom.getScrollY());
877         assertEquals(10, mScrollViewCustom.getScrollX());
878 
879         mScrollViewCustom.scrollTo(mPageWidth, mPageHeight);
880         assertEquals(mPageHeight, mScrollViewCustom.getScrollY());
881         assertEquals(mPageWidth, mScrollViewCustom.getScrollX());
882 
883         mScrollViewCustom.scrollTo(mScrollRight, mScrollBottom);
884         assertEquals(mScrollBottom, mScrollViewCustom.getScrollY());
885         assertEquals(mScrollRight, mScrollViewCustom.getScrollX());
886 
887         // reach the top and left
888         mScrollViewCustom.scrollTo(-10, -10);
889         assertEquals(0, mScrollViewCustom.getScrollY());
890         assertEquals(0, mScrollViewCustom.getScrollX());
891     }
892 
893     @UiThreadTest
894     @Test
testGetVerticalFadingEdgeStrengths()895     public void testGetVerticalFadingEdgeStrengths() {
896         MyScrollView myScrollViewCustom = (MyScrollView) mScrollViewCustom;
897 
898         assertTrue(myScrollViewCustom.getChildCount() > 0);
899         assertTrue(myScrollViewCustom.getTopFadingEdgeStrength() <= 1.0f);
900         assertTrue(myScrollViewCustom.getTopFadingEdgeStrength() >= 0.0f);
901         assertTrue(myScrollViewCustom.getBottomFadingEdgeStrength() <= 1.0f);
902         assertTrue(myScrollViewCustom.getBottomFadingEdgeStrength() >= 0.0f);
903 
904         MyScrollView myScrollView = new MyScrollView(mActivity);
905         assertEquals(0, myScrollView.getChildCount());
906         assertTrue(myScrollView.getTopFadingEdgeStrength() <= 1.0f);
907         assertTrue(myScrollView.getTopFadingEdgeStrength() >= 0.0f);
908         assertTrue(myScrollView.getBottomFadingEdgeStrength() <= 1.0f);
909         assertTrue(myScrollView.getBottomFadingEdgeStrength() >= 0.0f);
910     }
911 
912     @Test
testScrollDescendant()913     public void testScrollDescendant() throws Throwable {
914         assertEquals(0, mScrollViewCustom.getScrollX());
915         assertEquals(0, mScrollViewCustom.getScrollY());
916 
917         View lastChild = mScrollViewCustom.findViewById(R.id.last_child);
918         int lastChildTop = (ITEM_COUNT - 1) * mItemHeight;
919 
920         mActivityRule.runOnUiThread(() -> mScrollViewCustom.scrollToDescendant(lastChild));
921         // smoothScrollBy doesn't scroll in X
922         pollingCheckSmoothScrolling(0, 0, 0, lastChildTop);
923 
924         assertEquals(0, mScrollViewCustom.getScrollX());
925         assertEquals(lastChildTop, mScrollViewCustom.getScrollY());
926     }
927 
928     @UiThreadTest
929     @Test
testEdgeEffectColors()930     public void testEdgeEffectColors() {
931         int defaultColor = new EdgeEffect(mScrollViewRegular.getContext()).getColor();
932         assertEquals(mScrollViewRegular.getTopEdgeEffectColor(), defaultColor);
933         assertEquals(mScrollViewRegular.getBottomEdgeEffectColor(), defaultColor);
934 
935         mScrollViewRegular.setEdgeEffectColor(Color.BLUE);
936         assertEquals(mScrollViewRegular.getTopEdgeEffectColor(), Color.BLUE);
937         assertEquals(mScrollViewRegular.getBottomEdgeEffectColor(), Color.BLUE);
938 
939         mScrollViewRegular.setTopEdgeEffectColor(Color.RED);
940         assertEquals(mScrollViewRegular.getTopEdgeEffectColor(), Color.RED);
941         assertEquals(mScrollViewRegular.getBottomEdgeEffectColor(), Color.BLUE);
942 
943         mScrollViewRegular.setBottomEdgeEffectColor(Color.GREEN);
944         assertEquals(mScrollViewRegular.getTopEdgeEffectColor(), Color.RED);
945         assertEquals(mScrollViewRegular.getBottomEdgeEffectColor(), Color.GREEN);
946     }
947 
948     @Test
testStretchAtTop()949     public void testStretchAtTop() throws Throwable {
950         // Make sure that the scroll view we care about is on screen and at the top:
951         showOnlyStretch();
952 
953         NoReleaseEdgeEffect edgeEffect = new NoReleaseEdgeEffect(mActivity);
954         mScrollViewStretch.mEdgeGlowTop = edgeEffect;
955         assertTrue(StretchEdgeUtil.dragStretches(
956                 mActivityRule,
957                 mScrollViewStretch,
958                 edgeEffect,
959                 0,
960                 300
961         ));
962     }
963 
964     @Test
testStretchAtTopAndCatch()965     public void testStretchAtTopAndCatch() throws Throwable {
966         // Make sure that the scroll view we care about is on screen and at the top:
967         showOnlyStretch();
968 
969         NoReleaseEdgeEffect edgeEffect = new NoReleaseEdgeEffect(mActivity);
970         mScrollViewStretch.mEdgeGlowTop = edgeEffect;
971         assertTrue(StretchEdgeUtil.dragAndHoldKeepsStretch(
972                 mActivityRule,
973                 mScrollViewStretch,
974                 edgeEffect,
975                 0,
976                 3000
977         ));
978     }
979 
980     /**
981      * Verify that the SurfaceView is at the correct location during the overscroll stretch
982      */
983     @Test
testSurfaceViewStretchAtEnd()984     public void testSurfaceViewStretchAtEnd() throws Throwable {
985         showOnlyStretch();
986 
987         mActivityRule.runOnUiThread(new Runnable() {
988             @Override
989             public void run() {
990                 SurfaceView surfaceView = mActivity.findViewById(R.id.surfaceview_stretch_target);
991                 surfaceView.setBackgroundColor(Color.RED);
992                 surfaceView.setVisibility(View.VISIBLE);
993             }
994         });
995 
996         mActivityRule.runOnUiThread(() -> {
997             // Scroll all the way to the bottom
998             mScrollViewStretch.scrollToEnd();
999         });
1000 
1001         NoReleaseEdgeEffect edgeEffect = new NoReleaseEdgeEffect(mActivity);
1002 
1003         mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
1004 
1005         StretchEdgeUtil.dragAndHoldExecute(
1006                 mActivityRule,
1007                 mScrollViewStretch,
1008                 edgeEffect,
1009                 0,
1010                 -3000,
1011                 null,
1012                 () -> {
1013                     int[] coords = new int[2];
1014                     mScrollViewStretch.getLocationInWindow(coords);
1015                     Bitmap screenshot = getInstrumentation().getUiAutomation().takeScreenshot(
1016                             mActivityRule.getActivity().getWindow());
1017 
1018                     assertEquals(Color.RED, screenshot.getPixel(
1019                             coords[0] + mScrollViewStretch.getWidth() / 2,
1020                             coords[1] + mScrollViewStretch.getHeight() / 2));
1021                 }
1022         );
1023     }
1024 
1025     @Test
testSurfaceViewStretchAtEndWithScale()1026     public void testSurfaceViewStretchAtEndWithScale() throws Throwable {
1027         showOnlyStretch();
1028 
1029         float scaleY = 2.0f;
1030 
1031         mActivityRule.runOnUiThread(new Runnable() {
1032             @Override
1033             public void run() {
1034                 SurfaceView surfaceView = mActivity.findViewById(R.id.surfaceview_stretch_target);
1035                 surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
1036                     @Override
1037                     public void surfaceCreated(@NonNull SurfaceHolder holder) {
1038                         // no-op
1039                     }
1040 
1041                     @Override
1042                     public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
1043                             int height) {
1044                         surfaceView.setScaleY(scaleY);
1045                         surfaceView.setPivotX(width / 2f);
1046                         surfaceView.setPivotY(height);
1047                         Surface surface = holder.getSurface();
1048                         Paint paint = new Paint();
1049                         Canvas canvas = surface.lockHardwareCanvas();
1050                         paint.setColor(Color.RED);
1051                         canvas.drawRect(0f, 0f, width / 2f, height / 2f, paint);
1052                         paint.setColor(Color.YELLOW);
1053                         canvas.drawRect(width / 2f, 0f, width, height / 2f, paint);
1054                         paint.setColor(Color.BLUE);
1055                         canvas.drawRect(0f, height / 2f, width / 2f, height, paint);
1056                         paint.setColor(Color.BLACK);
1057                         canvas.drawRect(width / 2f, height / 2f, width, height, paint);
1058                         surface.unlockCanvasAndPost(canvas);
1059                     }
1060 
1061                     @Override
1062                     public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
1063                         // no-op
1064                     }
1065                 });
1066                 surfaceView.setVisibility(View.VISIBLE);
1067 
1068             }
1069         });
1070 
1071         mActivityRule.runOnUiThread(() -> {
1072             // Scroll all the way to the end
1073             mScrollViewStretch.scrollToEnd();
1074         });
1075 
1076         NoReleaseEdgeEffect edgeEffect = new NoReleaseEdgeEffect(mActivity);
1077 
1078         mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
1079 
1080         StretchEdgeUtil.dragAndHoldExecute(
1081                 mActivityRule,
1082                 mScrollViewStretch,
1083                 edgeEffect,
1084                 0,
1085                 -3000,
1086                 null,
1087                 () -> {
1088                     int[] coords = new int[2];
1089                     mScrollViewStretch.getLocationInWindow(coords);
1090                     Bitmap screenshot = getInstrumentation().getUiAutomation().takeScreenshot(
1091                             mActivityRule.getActivity().getWindow());
1092 
1093                     int containerWidth = mScrollViewStretch.getWidth();
1094                     int containerHeight = mScrollViewStretch.getHeight();
1095 
1096                     int topLeftColor = screenshot.getPixel(
1097                             coords[0] + containerWidth / 4,
1098                             coords[1] + (containerHeight / 4) + 3
1099                     );
1100 
1101                     int topRightColor = screenshot.getPixel(
1102                             coords[0] + containerWidth / 2 + containerWidth / 4,
1103                             coords[1] + (containerHeight / 4) + 3
1104                     );
1105 
1106                     int bottomLeftColor = screenshot.getPixel(
1107                             coords[0] + containerWidth / 4,
1108                             coords[1] + containerHeight / 2 + containerHeight / 4
1109                     );
1110 
1111                     int bottomRightColor = screenshot.getPixel(
1112                             coords[0] + containerWidth / 2 + containerWidth / 4,
1113                             coords[1] + containerHeight / 2 + containerHeight / 4
1114                     );
1115                     assertEquals(Color.RED, topLeftColor);
1116                     assertEquals(Color.YELLOW, topRightColor);
1117                     assertEquals(Color.BLUE, bottomLeftColor);
1118                     assertEquals(Color.BLACK, bottomRightColor);
1119                 }
1120         );
1121     }
1122 
1123     @LargeTest
1124     @Test
testRequestDisallowInterceptTouchEventNotCalled()1125     public void testRequestDisallowInterceptTouchEventNotCalled() throws Throwable {
1126         // Make sure that the scroll view we care about is on screen and at the top:
1127         showOnlyStretch();
1128 
1129         InterceptView interceptView = mActivity.findViewById(R.id.wrapped_stretch);
1130 
1131         NoReleaseEdgeEffect edgeEffect = new NoReleaseEdgeEffect(mActivity);
1132         mScrollViewStretch.mEdgeGlowTop = edgeEffect;
1133 
1134         dragAndHoldExecute(
1135                 mActivityRule,
1136                 mScrollViewStretch,
1137                 edgeEffect,
1138                 0,
1139                 300,
1140                 () -> interceptView.requestDisallowInterceptCalled = false,
1141                 null
1142         );
1143 
1144         mActivityRule.runOnUiThread(
1145                 () -> assertFalse(interceptView.requestDisallowInterceptCalled)
1146         );
1147     }
1148 
1149     @Test
testStretchAtBottom()1150     public void testStretchAtBottom() throws Throwable {
1151         // Make sure that the scroll view we care about is on screen and at the top:
1152         showOnlyStretch();
1153 
1154         mActivityRule.runOnUiThread(() -> {
1155             // Scroll all the way to the bottom
1156             mScrollViewStretch.scrollToEnd();
1157         });
1158 
1159         NoReleaseEdgeEffect edgeEffect = new NoReleaseEdgeEffect(mActivity);
1160         mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
1161         assertTrue(StretchEdgeUtil.dragStretches(
1162                 mActivityRule,
1163                 mScrollViewStretch,
1164                 edgeEffect,
1165                 0,
1166                 -300
1167         ));
1168     }
1169 
1170     @Test
testStretchAtBottomAndCatch()1171     public void testStretchAtBottomAndCatch() throws Throwable {
1172         // Make sure that the scroll view we care about is on screen and at the top:
1173         showOnlyStretch();
1174 
1175         mActivityRule.runOnUiThread(() -> {
1176             // Scroll all the way to the bottom
1177             mScrollViewStretch.scrollToEnd();
1178         });
1179 
1180         NoReleaseEdgeEffect edgeEffect = new NoReleaseEdgeEffect(mActivity);
1181         mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
1182         assertTrue(StretchEdgeUtil.dragAndHoldKeepsStretch(
1183                 mActivityRule,
1184                 mScrollViewStretch,
1185                 edgeEffect,
1186                 0,
1187                 -300
1188         ));
1189     }
1190 
1191     @Test
testFlingWhileStretchedTop()1192     public void testFlingWhileStretchedTop() throws Throwable {
1193         // Make sure that the scroll view we care about is on screen and at the top:
1194         showOnlyStretch();
1195 
1196         CaptureOnAbsorbEdgeEffect edgeEffect = new CaptureOnAbsorbEdgeEffect(mActivity);
1197         mScrollViewStretch.mEdgeGlowTop = edgeEffect;
1198         fling(mActivityRule, mScrollViewStretch, 0, 300);
1199         assertTrue(edgeEffect.onAbsorbVelocity > 0);
1200     }
1201 
1202     @Test
testFlingWhileStretchedBottom()1203     public void testFlingWhileStretchedBottom() throws Throwable {
1204         // Make sure that the scroll view we care about is on screen and at the top:
1205         showOnlyStretch();
1206 
1207         mActivityRule.runOnUiThread(() -> {
1208             // Scroll all the way to the bottom
1209             mScrollViewStretch.scrollToEnd();
1210         });
1211 
1212         CaptureOnAbsorbEdgeEffect edgeEffect = new CaptureOnAbsorbEdgeEffect(mActivity);
1213         mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
1214         fling(mActivityRule, mScrollViewStretch, 0, -300);
1215         assertTrue(edgeEffect.onAbsorbVelocity > 0);
1216     }
1217 
1218     @Test
scrollFromRotaryStretchesTop()1219     public void scrollFromRotaryStretchesTop() throws Throwable {
1220         showOnlyStretch();
1221 
1222         CaptureOnReleaseEdgeEffect edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
1223         mScrollViewStretch.mEdgeGlowTop = edgeEffect;
1224 
1225         mActivityRule.runOnUiThread(() -> {
1226             assertTrue(mScrollViewStretch.dispatchGenericMotionEvent(
1227                     createScrollEvent(2f, InputDevice.SOURCE_ROTARY_ENCODER)));
1228             assertFalse(edgeEffect.isFinished());
1229             assertTrue(edgeEffect.getDistance() > 0f);
1230             assertTrue(edgeEffect.onReleaseCalled);
1231         });
1232     }
1233 
1234     @Test
scrollFromMouseDoesNotStretchTop()1235     public void scrollFromMouseDoesNotStretchTop() throws Throwable {
1236         showOnlyStretch();
1237 
1238         CaptureOnReleaseEdgeEffect edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
1239         mScrollViewStretch.mEdgeGlowTop = edgeEffect;
1240 
1241         mActivityRule.runOnUiThread(() -> {
1242             assertFalse(mScrollViewStretch.dispatchGenericMotionEvent(
1243                     createScrollEvent(2f, InputDevice.SOURCE_MOUSE)));
1244             assertTrue(edgeEffect.isFinished());
1245             assertFalse(edgeEffect.onReleaseCalled);
1246         });
1247     }
1248 
1249     @Test
scrollFromRotaryStretchesBottom()1250     public void scrollFromRotaryStretchesBottom() throws Throwable {
1251         showOnlyStretch();
1252 
1253         mActivityRule.runOnUiThread(() -> {
1254             // Scroll all the way to the bottom
1255             mScrollViewStretch.scrollToEnd();
1256         });
1257 
1258         CaptureOnReleaseEdgeEffect edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
1259         mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
1260 
1261         mActivityRule.runOnUiThread(() -> {
1262             assertTrue(mScrollViewStretch.dispatchGenericMotionEvent(
1263                     createScrollEvent(-2f, InputDevice.SOURCE_ROTARY_ENCODER)));
1264             assertFalse(edgeEffect.isFinished());
1265             assertTrue(edgeEffect.getDistance() > 0f);
1266             assertTrue(edgeEffect.onReleaseCalled);
1267         });
1268     }
1269 
1270     @Test
scrollFromMouseDoesNotStretchBottom()1271     public void scrollFromMouseDoesNotStretchBottom() throws Throwable {
1272         showOnlyStretch();
1273 
1274         mActivityRule.runOnUiThread(() -> {
1275             // Scroll all the way to the bottom
1276             mScrollViewStretch.scrollToEnd();
1277             assertEquals(210, mScrollViewStretch.getScrollY());
1278         });
1279 
1280         CaptureOnReleaseEdgeEffect edgeEffect = new CaptureOnReleaseEdgeEffect(mActivity);
1281         mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
1282 
1283         mActivityRule.runOnUiThread(() -> {
1284             assertFalse(mScrollViewStretch.dispatchGenericMotionEvent(
1285                     createScrollEvent(-2f, InputDevice.SOURCE_MOUSE)));
1286             assertTrue(edgeEffect.isFinished());
1287             assertFalse(edgeEffect.onReleaseCalled);
1288         });
1289     }
1290 
1291     @Test
flingUpWhileStretchedAtTop()1292     public void flingUpWhileStretchedAtTop() throws Throwable {
1293         showOnlyStretch();
1294         NoReleaseEdgeEffect edgeEffect = new NoReleaseEdgeEffect(mActivity);
1295 
1296         mScrollViewStretch.mEdgeGlowTop = edgeEffect;
1297 
1298         StretchEdgeUtil.dragAndHoldExecute(
1299                 mActivityRule,
1300                 mScrollViewStretch,
1301                 edgeEffect,
1302                 0,
1303                 3000,
1304                 null,
1305                 () -> assertNotEquals(0f, edgeEffect.getDistance())
1306         );
1307 
1308         mActivityRule.runOnUiThread(() -> {
1309             edgeEffect.setOnReleaseCalled(false);
1310             assertEquals(0, mScrollViewStretch.getScrollY());
1311             mScrollViewStretch.fling(10000);
1312             assertFalse(edgeEffect.getOnReleaseCalled());
1313             assertNotEquals(0f, edgeEffect.getDistance());
1314             assertEquals(0, mScrollViewStretch.getScrollY());
1315         });
1316 
1317         PollingCheck.waitFor(1000L, () -> edgeEffect.getDistance() == 0);
1318         PollingCheck.waitFor(1000L, () -> mScrollViewStretch.getScrollY() != 0);
1319     }
1320 
1321     @Test
flingDownWhileStretchedAtBottom()1322     public void flingDownWhileStretchedAtBottom() throws Throwable {
1323         showOnlyStretch();
1324         mActivityRule.runOnUiThread(() -> {
1325             // Scroll all the way to the bottom
1326             mScrollViewStretch.scrollToEnd();
1327         });
1328 
1329         NoReleaseEdgeEffect edgeEffect = new NoReleaseEdgeEffect(mActivity);
1330 
1331         mScrollViewStretch.mEdgeGlowBottom = edgeEffect;
1332 
1333         StretchEdgeUtil.dragAndHoldExecute(
1334                 mActivityRule,
1335                 mScrollViewStretch,
1336                 edgeEffect,
1337                 0,
1338                 -3000,
1339                 null,
1340                 () -> assertNotEquals(0f, edgeEffect.getDistance())
1341         );
1342 
1343         mActivityRule.runOnUiThread(() -> {
1344             edgeEffect.setOnReleaseCalled(false);
1345             assertEquals(210, mScrollViewStretch.getScrollY());
1346             mScrollViewStretch.fling(-10000);
1347             assertFalse(edgeEffect.getOnReleaseCalled());
1348             assertNotEquals(0f, edgeEffect.getDistance());
1349             assertEquals(210, mScrollViewStretch.getScrollY());
1350         });
1351 
1352         PollingCheck.waitFor(1000L, () -> edgeEffect.getDistance() == 0);
1353         PollingCheck.waitFor(1000L, () -> mScrollViewStretch.getScrollY() != 210);
1354     }
1355 
createScrollEvent(float scrollAmount, int source)1356     private MotionEvent createScrollEvent(float scrollAmount, int source) {
1357         MotionEvent.PointerProperties pointerProperties = new MotionEvent.PointerProperties();
1358         pointerProperties.toolType = MotionEvent.TOOL_TYPE_MOUSE;
1359         MotionEvent.PointerCoords pointerCoords = new MotionEvent.PointerCoords();
1360         int axis = source == InputDevice.SOURCE_ROTARY_ENCODER ? MotionEvent.AXIS_SCROLL
1361                 : MotionEvent.AXIS_VSCROLL;
1362         pointerCoords.setAxisValue(axis, scrollAmount);
1363 
1364         return MotionEvent.obtain(
1365                 0, /* downTime */
1366                 0, /* eventTime */
1367                 MotionEvent.ACTION_SCROLL, /* action */
1368                 1, /* pointerCount */
1369                 new MotionEvent.PointerProperties[] { pointerProperties },
1370                 new MotionEvent.PointerCoords[] { pointerCoords },
1371                 0, /* metaState */
1372                 0, /* buttonState */
1373                 0f, /* xPrecision */
1374                 0f, /* yPrecision */
1375                 0, /* deviceId */
1376                 0, /* edgeFlags */
1377                 source, /* source */
1378                 0 /* flags */
1379         );
1380     }
1381 
showOnlyStretch()1382     private void showOnlyStretch() throws Throwable {
1383         mActivityRule.runOnUiThread(() -> {
1384             mScrollViewCustom.setVisibility(View.GONE);
1385             mScrollViewCustomEmpty.setVisibility(View.GONE);
1386             mScrollViewRegular.setVisibility(View.GONE);
1387             mSurfaceView.setVisibility(View.GONE);
1388         });
1389     }
1390 
isInRange(int current, int from, int to)1391     private boolean isInRange(int current, int from, int to) {
1392         if (from < to) {
1393             return current >= from && current <= to;
1394         }
1395         return current <= from && current >= to;
1396     }
1397 
pollingCheckSmoothScrolling(final int fromX, final int toX, final int fromY, final int toY)1398     private void pollingCheckSmoothScrolling(final int fromX, final int toX,
1399             final int fromY, final int toY) {
1400 
1401         if (fromX == toX && fromY == toY) {
1402             return;
1403         }
1404 
1405         if (fromY != toY) {
1406             PollingCheck.waitFor(() -> isInRange(mScrollViewCustom.getScrollY(), fromY, toY));
1407         }
1408 
1409         if (fromX != toX) {
1410             PollingCheck.waitFor(() -> isInRange(mScrollViewCustom.getScrollX(), fromX, toX));
1411         }
1412 
1413         PollingCheck.waitFor(
1414                 () -> toX == mScrollViewCustom.getScrollX()
1415                         && toY == mScrollViewCustom.getScrollY());
1416     }
1417 
pollingCheckFling(final int startPosition, final boolean movingDown)1418     private void pollingCheckFling(final int startPosition, final boolean movingDown) {
1419         PollingCheck.waitFor(() -> {
1420             if (movingDown) {
1421                 return mScrollViewCustom.getScrollY() > startPosition;
1422             }
1423             return mScrollViewCustom.getScrollY() < startPosition;
1424         });
1425 
1426         final int[] previousScrollY = new int[] { mScrollViewCustom.getScrollY() };
1427         PollingCheck.waitFor(() -> {
1428             if (mScrollViewCustom.getScrollY() == previousScrollY[0]) {
1429                 return true;
1430             } else {
1431                 previousScrollY[0] = mScrollViewCustom.getScrollY();
1432                 return false;
1433             }
1434         });
1435     }
1436 
1437     public static class MyView extends View {
1438         // measure in this height if set
1439         private Integer mDesiredHeight;
MyView(Context context)1440         public MyView(Context context) {
1441             super(context);
1442         }
1443 
setDesiredHeight(Integer desiredHeight)1444         public void setDesiredHeight(Integer desiredHeight) {
1445             mDesiredHeight = desiredHeight;
1446         }
1447 
1448         @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)1449         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1450             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1451             if (mDesiredHeight != null) {
1452                 int mode = MeasureSpec.getMode(heightMeasureSpec);
1453                 int size = MeasureSpec.getSize(heightMeasureSpec);
1454                 int newHeight = size;
1455                 if (mode == MeasureSpec.AT_MOST) {
1456                     newHeight = Math.max(size, mDesiredHeight);
1457                 } else if (mode == MeasureSpec.UNSPECIFIED) {
1458                     newHeight = mDesiredHeight;
1459                 }
1460                 setMeasuredDimension(getMeasuredWidth(), newHeight);
1461             }
1462         }
1463     }
1464 
1465     public static class MyScrollView extends ScrollView {
MyScrollView(Context context)1466         public MyScrollView(Context context) {
1467             super(context);
1468         }
1469 
MyScrollView(Context context, AttributeSet attrs)1470         public MyScrollView(Context context, AttributeSet attrs) {
1471             super(context, attrs);
1472         }
1473 
MyScrollView(Context context, AttributeSet attrs, int defStyle)1474         public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
1475             super(context, attrs, defStyle);
1476         }
1477 
1478         @Override
computeScrollDeltaToGetChildRectOnScreen(Rect rect)1479         protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
1480             return super.computeScrollDeltaToGetChildRectOnScreen(rect);
1481         }
1482 
1483         @Override
computeVerticalScrollRange()1484         protected int computeVerticalScrollRange() {
1485             return super.computeVerticalScrollRange();
1486         }
1487 
1488         @Override
getBottomFadingEdgeStrength()1489         protected float getBottomFadingEdgeStrength() {
1490             return super.getBottomFadingEdgeStrength();
1491         }
1492 
1493         @Override
getTopFadingEdgeStrength()1494         protected float getTopFadingEdgeStrength() {
1495             return super.getTopFadingEdgeStrength();
1496         }
1497 
1498         @Override
measureChild(View c, int pWidthMeasureSpec, int pHeightMeasureSpec)1499         protected void measureChild(View c, int pWidthMeasureSpec, int pHeightMeasureSpec) {
1500             super.measureChild(c, pWidthMeasureSpec, pHeightMeasureSpec);
1501         }
1502 
1503         @Override
measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)1504         protected void measureChildWithMargins(View child, int parentWidthMeasureSpec,
1505                 int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
1506             super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed,
1507                     parentHeightMeasureSpec, heightUsed);
1508         }
1509 
scrollToEnd()1510         public void scrollToEnd() {
1511             scrollTo(0, computeVerticalScrollRange());
1512         }
1513     }
1514 
1515     public static class InterceptView extends FrameLayout {
1516         public boolean requestDisallowInterceptCalled = false;
1517 
InterceptView(Context context)1518         public InterceptView(Context context) {
1519             super(context);
1520         }
1521 
InterceptView(Context context, AttributeSet attrs)1522         public InterceptView(Context context, AttributeSet attrs) {
1523             super(context, attrs);
1524         }
1525 
InterceptView(Context context, AttributeSet attrs, int defStyle)1526         public InterceptView(Context context, AttributeSet attrs, int defStyle) {
1527             super(context, attrs, defStyle);
1528         }
1529 
1530         @Override
requestDisallowInterceptTouchEvent(boolean disallowIntercept)1531         public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
1532             requestDisallowInterceptCalled = true;
1533             super.requestDisallowInterceptTouchEvent(disallowIntercept);
1534         }
1535     }
1536 
1537     public static class CaptureOnAbsorbEdgeEffect extends EdgeEffect {
1538         public int onAbsorbVelocity;
1539 
CaptureOnAbsorbEdgeEffect(Context context)1540         public CaptureOnAbsorbEdgeEffect(Context context) {
1541             super(context);
1542         }
1543 
1544         @Override
onAbsorb(int velocity)1545         public void onAbsorb(int velocity) {
1546             onAbsorbVelocity = velocity;
1547             super.onAbsorb(velocity);
1548         }
1549     }
1550 
1551     public static class CaptureOnReleaseEdgeEffect extends EdgeEffect {
1552         public boolean onReleaseCalled;
1553 
CaptureOnReleaseEdgeEffect(Context context)1554         public CaptureOnReleaseEdgeEffect(Context context) {
1555             super(context);
1556         }
1557 
1558         @Override
onRelease()1559         public void onRelease() {
1560             onReleaseCalled = true;
1561             super.onRelease();
1562         }
1563     }
1564 }
1565