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