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.text.method.cts; 18 19 import static org.junit.Assert.assertEquals; 20 import static org.junit.Assert.assertFalse; 21 import static org.junit.Assert.assertNotNull; 22 import static org.junit.Assert.assertNull; 23 import static org.junit.Assert.assertSame; 24 import static org.junit.Assert.assertTrue; 25 26 import android.app.Activity; 27 import android.app.Instrumentation; 28 import android.os.SystemClock; 29 import android.support.test.InstrumentationRegistry; 30 import android.support.test.annotation.UiThreadTest; 31 import android.support.test.filters.MediumTest; 32 import android.support.test.rule.ActivityTestRule; 33 import android.support.test.runner.AndroidJUnit4; 34 import android.text.Layout; 35 import android.text.Spannable; 36 import android.text.SpannableString; 37 import android.text.method.MovementMethod; 38 import android.text.method.ScrollingMovementMethod; 39 import android.util.DisplayMetrics; 40 import android.util.TypedValue; 41 import android.view.KeyEvent; 42 import android.view.MotionEvent; 43 import android.view.View; 44 import android.view.ViewConfiguration; 45 import android.view.ViewGroup.LayoutParams; 46 import android.widget.TextView; 47 import android.widget.TextView.BufferType; 48 49 import com.android.compatibility.common.util.WidgetTestUtils; 50 51 import org.junit.Before; 52 import org.junit.Rule; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 56 /** 57 * Test {@link ScrollingMovementMethod}. The class is an implementation of interface 58 * {@link MovementMethod}. The typical usage of {@link MovementMethod} is tested in 59 * {@link android.widget.cts.TextViewTest} and this test case is only focused on the 60 * implementation of the methods. 61 * 62 * @see android.widget.cts.TextViewTest 63 */ 64 @MediumTest 65 @RunWith(AndroidJUnit4.class) 66 public class ScrollingMovementMethodTest { 67 private static final int LITTLE_SPACE = 20; 68 69 private static final String THREE_LINES_TEXT = "first line\nsecond line\nlast line"; 70 71 private Instrumentation mInstrumentation; 72 private Activity mActivity; 73 private TextView mTextView; 74 private Spannable mSpannable; 75 private int mScaledTouchSlop; 76 77 @Rule 78 public ActivityTestRule<CtsActivity> mActivityRule = new ActivityTestRule<>(CtsActivity.class); 79 80 @UiThreadTest 81 @Before 82 public void setup() throws Throwable { 83 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 84 mActivity = mActivityRule.getActivity(); 85 mTextView = new TextViewNoIme(mActivity); 86 mTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); 87 mTextView.setText(THREE_LINES_TEXT, BufferType.EDITABLE); 88 mSpannable = (Spannable) mTextView.getText(); 89 mScaledTouchSlop = ViewConfiguration.get(mActivity).getScaledTouchSlop(); 90 } 91 92 @Test 93 public void testConstructor() { 94 new ScrollingMovementMethod(); 95 } 96 97 @Test 98 public void testGetInstance() { 99 final MovementMethod method0 = ScrollingMovementMethod.getInstance(); 100 assertTrue(method0 instanceof ScrollingMovementMethod); 101 102 final MovementMethod method1 = ScrollingMovementMethod.getInstance(); 103 assertSame(method0, method1); 104 } 105 106 @Test 107 public void testOnTouchEventHorizontalMotion() throws Throwable { 108 final ScrollingMovementMethod method = new ScrollingMovementMethod(); 109 runActionOnUiThread(() -> { 110 mTextView.setText("hello world", BufferType.SPANNABLE); 111 mTextView.setSingleLine(); 112 mSpannable = (Spannable) mTextView.getText(); 113 final int width = WidgetTestUtils.convertDipToPixels(mActivity, LITTLE_SPACE); 114 mActivity.setContentView(mTextView, 115 new LayoutParams(width, LayoutParams.WRAP_CONTENT)); 116 }); 117 assertNotNull(mTextView.getLayout()); 118 119 final float rightMost = mTextView.getLayout().getLineRight(0) - mTextView.getWidth() 120 + mTextView.getTotalPaddingLeft() + mTextView.getTotalPaddingRight(); 121 final int leftMost = mTextView.getScrollX(); 122 123 final long now = SystemClock.uptimeMillis(); 124 assertTrue(getActionResult(new ActionRunnerWithResult() { 125 public void run() { 126 // press 127 mResult = method.onTouchEvent(mTextView, mSpannable, 128 MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 0, 0, 0)); 129 } 130 })); 131 132 final int tinyDist = -(mScaledTouchSlop - 1); 133 int previousScrollX = mTextView.getScrollX(); 134 assertFalse(getActionResult(new ActionRunnerWithResult() { 135 public void run() { 136 // move for short distance 137 mResult = method.onTouchEvent(mTextView, mSpannable, 138 MotionEvent.obtain(now, now, MotionEvent.ACTION_MOVE, tinyDist, 0, 0)); 139 } 140 })); 141 assertEquals(previousScrollX, mTextView.getScrollX()); 142 143 assertFalse(getActionResult(new ActionRunnerWithResult() { 144 public void run() { 145 // release 146 mResult = method.onTouchEvent(mTextView, mSpannable, 147 MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, tinyDist, 0, 0)); 148 } 149 })); 150 151 assertTrue(getActionResult(new ActionRunnerWithResult() { 152 public void run() { 153 // press 154 mResult = method.onTouchEvent(mTextView, mSpannable, 155 MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 0, 0, 0)); 156 } 157 })); 158 159 final int distFar = -mScaledTouchSlop; 160 previousScrollX = mTextView.getScrollX(); 161 assertTrue(getActionResult(new ActionRunnerWithResult() { 162 public void run() { 163 // move for enough distance 164 mResult = method.onTouchEvent(mTextView, mSpannable, 165 MotionEvent.obtain(now, now, MotionEvent.ACTION_MOVE, distFar, 0, 0)); 166 } 167 })); 168 assertTrue(mTextView.getScrollX() > previousScrollX); 169 assertTrue(mTextView.getScrollX() < rightMost); 170 171 previousScrollX = mTextView.getScrollX(); 172 final int distTooFar = (int) (-rightMost * 10); 173 assertTrue(getActionResult(new ActionRunnerWithResult() { 174 public void run() { 175 // move for long distance 176 mResult = method.onTouchEvent(mTextView, mSpannable, 177 MotionEvent.obtain(now, now, MotionEvent.ACTION_MOVE, distTooFar, 0, 0)); 178 } 179 })); 180 assertTrue(mTextView.getScrollX() > previousScrollX); 181 assertEquals(rightMost, mTextView.getScrollX(), 1.0f); 182 183 previousScrollX = mTextView.getScrollX(); 184 assertTrue(getActionResult(new ActionRunnerWithResult() { 185 public void run() { 186 // move back 187 mResult = method.onTouchEvent(mTextView, mSpannable, 188 MotionEvent.obtain(now, now, MotionEvent.ACTION_MOVE, 0, 0, 0)); 189 } 190 })); 191 assertTrue(mTextView.getScrollX() < previousScrollX); 192 assertEquals(leftMost, mTextView.getScrollX()); 193 194 assertTrue(getActionResult(new ActionRunnerWithResult() { 195 public void run() { 196 // release 197 mResult = method.onTouchEvent(mTextView, mSpannable, 198 MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0, 0, 0)); 199 } 200 })); 201 } 202 203 @Test 204 public void testOnTouchEventVerticalMotion() throws Throwable { 205 final ScrollingMovementMethod method = new ScrollingMovementMethod(); 206 runActionOnUiThread(() -> { 207 mTextView.setLines(1); 208 mActivity.setContentView(mTextView, 209 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); 210 }); 211 assertNotNull(mTextView.getLayout()); 212 213 final float bottom = mTextView.getLayout().getHeight() - mTextView.getHeight() 214 + mTextView.getTotalPaddingTop() + mTextView.getTotalPaddingBottom(); 215 final int top = mTextView.getScrollY(); 216 217 final long now = SystemClock.uptimeMillis(); 218 assertTrue(getActionResult(new ActionRunnerWithResult() { 219 public void run() { 220 // press 221 mResult = method.onTouchEvent(mTextView, mSpannable, MotionEvent.obtain(now, now, 222 MotionEvent.ACTION_DOWN, 0, 0, 0)); 223 } 224 })); 225 226 final int tinyDist = -(mScaledTouchSlop - 1); 227 int previousScrollY = mTextView.getScrollY(); 228 assertFalse(getActionResult(new ActionRunnerWithResult() { 229 public void run() { 230 // move for short distance 231 mResult = method.onTouchEvent(mTextView, mSpannable, MotionEvent.obtain(now, now, 232 MotionEvent.ACTION_MOVE, 0, tinyDist, 0)); 233 } 234 })); 235 assertEquals(previousScrollY, mTextView.getScrollY()); 236 237 assertFalse(getActionResult(new ActionRunnerWithResult() { 238 public void run() { 239 // release 240 mResult = method.onTouchEvent(mTextView, mSpannable, MotionEvent.obtain(now, now, 241 MotionEvent.ACTION_UP, 0, tinyDist, 0)); 242 } 243 })); 244 245 assertTrue(getActionResult(new ActionRunnerWithResult() { 246 public void run() { 247 // press 248 mResult = method.onTouchEvent(mTextView, mSpannable, MotionEvent.obtain(now, now, 249 MotionEvent.ACTION_DOWN, 0, 0, 0)); 250 } 251 })); 252 253 final int distFar = -mScaledTouchSlop; 254 previousScrollY = mTextView.getScrollY(); 255 assertTrue(getActionResult(new ActionRunnerWithResult() { 256 public void run() { 257 // move for enough distance 258 mResult = method.onTouchEvent(mTextView, mSpannable, MotionEvent.obtain(now, now, 259 MotionEvent.ACTION_MOVE, 0, distFar, 0)); 260 } 261 })); 262 assertTrue(mTextView.getScrollY() > previousScrollY); 263 assertTrue(mTextView.getScrollX() < bottom); 264 265 previousScrollY = mTextView.getScrollY(); 266 final int distTooFar = (int) (-bottom * 10); 267 assertTrue(getActionResult(new ActionRunnerWithResult() { 268 public void run() { 269 // move for long distance 270 mResult = method.onTouchEvent(mTextView, mSpannable, MotionEvent.obtain(now, now, 271 MotionEvent.ACTION_MOVE, 0, distTooFar, 0)); 272 } 273 })); 274 assertTrue(mTextView.getScrollY() > previousScrollY); 275 assertEquals(bottom, mTextView.getScrollY(), 0f); 276 277 previousScrollY = mTextView.getScrollY(); 278 assertTrue(getActionResult(new ActionRunnerWithResult() { 279 public void run() { 280 // move back 281 mResult = method.onTouchEvent(mTextView, mSpannable, MotionEvent.obtain(now, now, 282 MotionEvent.ACTION_MOVE, 0, 0, 0)); 283 } 284 })); 285 assertTrue(mTextView.getScrollY() < previousScrollY); 286 assertEquals(top, mTextView.getScrollX()); 287 288 assertTrue(getActionResult(new ActionRunnerWithResult() { 289 public void run() { 290 // release 291 mResult = method.onTouchEvent(mTextView, mSpannable, MotionEvent.obtain(now, now, 292 MotionEvent.ACTION_UP, 0, 0, 0)); 293 } 294 })); 295 } 296 297 @Test 298 public void testOnTouchEventExceptional() throws Throwable { 299 runActionOnUiThread(() -> { 300 final int width = WidgetTestUtils.convertDipToPixels(mActivity, LITTLE_SPACE); 301 mActivity.setContentView(mTextView, 302 new LayoutParams(width, LayoutParams.WRAP_CONTENT)); 303 }); 304 assertNotNull(mTextView.getLayout()); 305 306 runActionOnUiThread(() -> { 307 try { 308 new ScrollingMovementMethod().onTouchEvent(mTextView, mSpannable, null); 309 } catch (NullPointerException e) { 310 // NPE is acceptable 311 } 312 313 long now = SystemClock.uptimeMillis(); 314 try { 315 new ScrollingMovementMethod().onTouchEvent(mTextView, null, 316 MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 0, 0, 0)); 317 } catch (NullPointerException e) { 318 // NPE is acceptable 319 } 320 321 try { 322 new ScrollingMovementMethod().onTouchEvent(null, mSpannable, 323 MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 0, 0, 0)); 324 } catch (NullPointerException e) { 325 // NPE is acceptable 326 } 327 328 new ScrollingMovementMethod().onTouchEvent(mTextView, mSpannable, 329 MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 0, 0, 0)); 330 try { 331 new ScrollingMovementMethod().onTouchEvent(null, mSpannable, 332 MotionEvent.obtain(now, now, MotionEvent.ACTION_MOVE, -10000, 0, 0)); 333 } catch (NullPointerException e) { 334 // NPE is acceptable 335 } 336 337 try { 338 new ScrollingMovementMethod().onTouchEvent(mTextView, null, 339 MotionEvent.obtain(now, now, MotionEvent.ACTION_MOVE, -10000, 0, 0)); 340 } catch (NullPointerException e) { 341 // NPE is acceptable 342 } 343 344 try { 345 new ScrollingMovementMethod().onTouchEvent(null, mSpannable, 346 MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, -10000, 0, 0)); 347 } catch (NullPointerException e) { 348 // NPE is acceptable 349 } 350 351 try { 352 new ScrollingMovementMethod().onTouchEvent(mTextView, null, 353 MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, -10000, 0, 0)); 354 } catch (NullPointerException e) { 355 // NPE is acceptable 356 } 357 }); 358 } 359 360 @Test 361 public void testCanSelectArbitrarily() { 362 assertFalse(new ScrollingMovementMethod().canSelectArbitrarily()); 363 } 364 365 @Test 366 public void testOnKeyDownVerticalMovement() throws Throwable { 367 runActionOnUiThread(() -> mActivity.setContentView(mTextView)); 368 assertNotNull(mTextView.getLayout()); 369 370 verifyVisibleLineInTextView(0); 371 final MyScrollingMovementMethod method = new MyScrollingMovementMethod(); 372 runActionOnUiThread(() -> method.onKeyDown(mTextView, null, KeyEvent.KEYCODE_DPAD_DOWN, 373 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN))); 374 verifyVisibleLineInTextView(1); 375 376 runActionOnUiThread(() -> method.onKeyDown(mTextView, null, KeyEvent.KEYCODE_DPAD_UP, 377 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP))); 378 verifyVisibleLineInTextView(0); 379 } 380 381 @Test 382 public void testOnKeyDownHorizontalMovement() throws Throwable { 383 runActionOnUiThread(() -> { 384 mTextView.setText("short"); 385 mTextView.setSingleLine(); 386 final int width = WidgetTestUtils.convertDipToPixels(mActivity, LITTLE_SPACE); 387 mActivity.setContentView(mTextView, 388 new LayoutParams(width, LayoutParams.WRAP_CONTENT)); 389 }); 390 assertNotNull(mTextView.getLayout()); 391 392 final MyScrollingMovementMethod method = new MyScrollingMovementMethod(); 393 int previousScrollX = mTextView.getScrollX(); 394 runActionOnUiThread(() -> method.onKeyDown(mTextView, (Spannable) mTextView.getText(), 395 KeyEvent.KEYCODE_DPAD_RIGHT, new KeyEvent(KeyEvent.ACTION_DOWN, 396 KeyEvent.KEYCODE_DPAD_RIGHT))); 397 assertTrue(mTextView.getScrollX() > previousScrollX); 398 399 previousScrollX = mTextView.getScrollX(); 400 runActionOnUiThread(() -> method.onKeyDown(mTextView, (Spannable) mTextView.getText(), 401 KeyEvent.KEYCODE_DPAD_LEFT, new KeyEvent(KeyEvent.ACTION_DOWN, 402 KeyEvent.KEYCODE_DPAD_LEFT))); 403 assertTrue(mTextView.getScrollX() < previousScrollX); 404 405 previousScrollX = mTextView.getScrollX(); 406 verifyVisibleLineInTextView(0); 407 runActionOnUiThread(() -> assertFalse(method.onKeyDown(mTextView, mSpannable, 0, 408 new KeyEvent(KeyEvent.ACTION_DOWN, 0)))); 409 assertEquals(previousScrollX, mTextView.getScrollX()); 410 verifyVisibleLineInTextView(0); 411 } 412 413 @Test 414 public void testOnKeyDownExceptions() throws Throwable { 415 runActionOnUiThread(() -> mActivity.setContentView(mTextView)); 416 assertNotNull(mTextView.getLayout()); 417 418 final MyScrollingMovementMethod method = new MyScrollingMovementMethod(); 419 runActionOnUiThread(() -> { 420 try { 421 method.onKeyDown(null, mSpannable, KeyEvent.KEYCODE_DPAD_RIGHT, 422 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP)); 423 } catch (NullPointerException e) { 424 // NPE is acceptable 425 } 426 427 try { 428 method.onKeyDown(mTextView, null, KeyEvent.KEYCODE_DPAD_RIGHT, 429 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP)); 430 } catch (NullPointerException e) { 431 // NPE is acceptable 432 } 433 434 try { 435 method.onKeyDown(mTextView, mSpannable, KeyEvent.KEYCODE_DPAD_RIGHT, null); 436 } catch (NullPointerException e) { 437 // NPE is acceptable 438 } 439 }); 440 } 441 442 @Test 443 public void testVerticalMovement() throws Throwable { 444 final MyScrollingMovementMethod method = new MyScrollingMovementMethod(); 445 runActionOnUiThread(() -> { 446 mTextView.setLines(1); 447 mActivity.setContentView(mTextView, 448 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); 449 }); 450 assertNotNull(mTextView.getLayout()); 451 452 assertTrue(getActionResult(new ActionRunnerWithResult() { 453 public void run() { 454 mResult = method.down(mTextView, mSpannable); 455 } 456 })); 457 verifyVisibleLineInTextView(1); 458 459 assertTrue(getActionResult(new ActionRunnerWithResult() { 460 public void run() { 461 mResult = method.down(mTextView, mSpannable); 462 } 463 })); 464 verifyVisibleLineInTextView(2); 465 466 assertFalse(getActionResult(new ActionRunnerWithResult() { 467 public void run() { 468 mResult = method.down(mTextView, mSpannable); 469 } 470 })); 471 verifyVisibleLineInTextView(2); 472 473 assertTrue(getActionResult(new ActionRunnerWithResult() { 474 public void run() { 475 mResult = method.up(mTextView, mSpannable); 476 } 477 })); 478 verifyVisibleLineInTextView(1); 479 480 assertTrue(getActionResult(new ActionRunnerWithResult() { 481 public void run() { 482 mResult = method.up(mTextView, mSpannable); 483 } 484 })); 485 verifyVisibleLineInTextView(0); 486 487 assertFalse(getActionResult(new ActionRunnerWithResult() { 488 public void run() { 489 mResult = method.up(mTextView, mSpannable); 490 } 491 })); 492 verifyVisibleLineInTextView(0); 493 494 runActionOnUiThread(() -> { 495 try { 496 method.up(null, mSpannable); 497 } catch (NullPointerException e) { 498 // NPE is acceptable 499 } 500 501 try { 502 method.up(mTextView, null); 503 } catch (NullPointerException e) { 504 // NPE is acceptable 505 } 506 507 try { 508 method.down(null, mSpannable); 509 } catch (NullPointerException e) { 510 // NPE is acceptable 511 } 512 513 try { 514 method.down(mTextView, null); 515 } catch (NullPointerException e) { 516 // NPE is acceptable 517 } 518 }); 519 } 520 521 @Test 522 public void testMovementWithNullLayout() { 523 assertNull(mTextView.getLayout()); 524 try { 525 new MyScrollingMovementMethod().down(mTextView, mSpannable); 526 } catch (NullPointerException e) { 527 // NPE is acceptable 528 } 529 530 try { 531 new MyScrollingMovementMethod().up(mTextView, mSpannable); 532 } catch (NullPointerException e) { 533 // NPE is acceptable 534 } 535 536 try { 537 new MyScrollingMovementMethod().left(mTextView, mSpannable); 538 } catch (NullPointerException e) { 539 // NPE is acceptable 540 } 541 542 try { 543 new MyScrollingMovementMethod().right(mTextView, mSpannable); 544 } catch (NullPointerException e) { 545 // NPE is acceptable 546 } 547 548 final long now = SystemClock.uptimeMillis(); 549 try { 550 KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_RIGHT); 551 new ScrollingMovementMethod().onKeyDown(mTextView, mSpannable, 552 KeyEvent.KEYCODE_DPAD_RIGHT, event); 553 } catch (NullPointerException e) { 554 // NPE is acceptable 555 } 556 557 new ScrollingMovementMethod().onTouchEvent(mTextView, mSpannable, 558 MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 0, 0, 0)); 559 try { 560 new ScrollingMovementMethod().onTouchEvent(mTextView, mSpannable, 561 MotionEvent.obtain(now, now, MotionEvent.ACTION_MOVE, - 10000, 0, 0)); 562 } catch (NullPointerException e) { 563 // NPE is acceptable 564 } 565 } 566 567 @Test 568 public void testInitialize() { 569 new ScrollingMovementMethod().initialize(null, null); 570 } 571 572 @Test 573 public void testOnTrackballEvent() { 574 final long now = SystemClock.uptimeMillis(); 575 final MotionEvent event = MotionEvent.obtain(now, now, 0, 2, -2, 0); 576 final MyScrollingMovementMethod mockMethod = new MyScrollingMovementMethod(); 577 578 assertFalse(mockMethod.onTrackballEvent(mTextView, mSpannable, event)); 579 assertFalse(mockMethod.onTrackballEvent(null, mSpannable, event)); 580 assertFalse(mockMethod.onTrackballEvent(mTextView, mSpannable, null)); 581 assertFalse(mockMethod.onTrackballEvent(mTextView, null, event)); 582 } 583 584 @UiThreadTest 585 @Test 586 public void testOnKeyUp() { 587 final ScrollingMovementMethod method = new ScrollingMovementMethod(); 588 final SpannableString spannable = new SpannableString("Test Content"); 589 final KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0); 590 final TextView view = new TextViewNoIme(mActivity); 591 view.setTextSize(TypedValue.COMPLEX_UNIT_SP, 12); 592 593 assertFalse(method.onKeyUp(view, spannable, KeyEvent.KEYCODE_0, event)); 594 assertFalse(method.onKeyUp(null, null, 0, null)); 595 assertFalse(method.onKeyUp(null, spannable, KeyEvent.KEYCODE_0, event)); 596 assertFalse(method.onKeyUp(view, null, KeyEvent.KEYCODE_0, event)); 597 assertFalse(method.onKeyUp(view, spannable, 0, event)); 598 assertFalse(method.onKeyUp(view, spannable, KeyEvent.KEYCODE_0, null)); 599 } 600 601 @Test 602 public void testOnTakeFocus() throws Throwable { 603 final ScrollingMovementMethod method = new ScrollingMovementMethod(); 604 // wait until the text view gets layout 605 assertNull(mTextView.getLayout()); 606 try { 607 method.onTakeFocus(mTextView, mSpannable, View.FOCUS_BACKWARD); 608 } catch (NullPointerException e) { 609 // NPE is acceptable 610 } 611 612 runActionOnUiThread(() -> { 613 final int height = WidgetTestUtils.convertDipToPixels(mActivity, LITTLE_SPACE); 614 mActivity.setContentView(mTextView, 615 new LayoutParams(LayoutParams.MATCH_PARENT, 616 height)); 617 }); 618 final Layout layout = mTextView.getLayout(); 619 assertNotNull(layout); 620 621 int previousScrollY = mTextView.getScrollY(); 622 runActionOnUiThread(() -> method.onTakeFocus(mTextView, mSpannable, View.FOCUS_BACKWARD)); 623 assertTrue(mTextView.getScrollY() >= previousScrollY); 624 verifyVisibleLineInTextView(2); 625 626 previousScrollY = mTextView.getScrollY(); 627 runActionOnUiThread(() -> method.onTakeFocus(mTextView, mSpannable, View.FOCUS_FORWARD)); 628 assertTrue(mTextView.getScrollY() <= previousScrollY); 629 verifyVisibleLineInTextView(0); 630 631 runActionOnUiThread(() -> { 632 try { 633 method.onTakeFocus(null, mSpannable, View.FOCUS_FORWARD); 634 } catch (NullPointerException e) { 635 // NPE is acceptable 636 } 637 638 try { 639 method.onTakeFocus(mTextView, null, View.FOCUS_FORWARD); 640 } catch (NullPointerException e) { 641 // NPE is acceptable 642 } 643 }); 644 } 645 646 @Test 647 public void testHorizontalMovement() throws Throwable { 648 final MyScrollingMovementMethod method = new MyScrollingMovementMethod(); 649 runActionOnUiThread(() -> { 650 mTextView.setText("short"); 651 mTextView.setSingleLine(); 652 final DisplayMetrics dm = mActivity.getResources().getDisplayMetrics(); 653 final int width = (int) (LITTLE_SPACE * dm.scaledDensity); 654 mActivity.setContentView(mTextView, 655 new LayoutParams(width, LayoutParams.WRAP_CONTENT)); 656 }); 657 assertNotNull(mTextView.getLayout()); 658 659 int previousScrollX = mTextView.getScrollX(); 660 assertTrue(getActionResult(new ActionRunnerWithResult() { 661 662 public void run() { 663 mResult = method.right(mTextView, mSpannable); 664 } 665 })); 666 assertTrue(mTextView.getScrollX() > previousScrollX); 667 668 previousScrollX = mTextView.getScrollX(); 669 assertFalse(getActionResult(new ActionRunnerWithResult() { 670 public void run() { 671 mResult = method.right(mTextView, mSpannable); 672 } 673 })); 674 assertEquals(previousScrollX, mTextView.getScrollX()); 675 676 previousScrollX = mTextView.getScrollX(); 677 assertTrue(getActionResult(new ActionRunnerWithResult() { 678 public void run() { 679 mResult = method.left(mTextView, mSpannable); 680 } 681 })); 682 assertTrue(mTextView.getScrollX() < previousScrollX); 683 684 previousScrollX = mTextView.getScrollX(); 685 assertFalse(getActionResult(new ActionRunnerWithResult() { 686 public void run() { 687 mResult = method.left(mTextView, mSpannable); 688 } 689 })); 690 assertEquals(previousScrollX, mTextView.getScrollX()); 691 } 692 693 @Test 694 public void testOnKeyOther() throws Throwable { 695 runActionOnUiThread(() -> mActivity.setContentView(mTextView)); 696 assertNotNull(mTextView.getLayout()); 697 698 verifyVisibleLineInTextView(0); 699 final MyScrollingMovementMethod method = new MyScrollingMovementMethod(); 700 runActionOnUiThread(() -> method.onKeyOther(mTextView, null, 701 new KeyEvent(0, 0, KeyEvent.ACTION_MULTIPLE, 702 KeyEvent.KEYCODE_DPAD_DOWN, 2))); 703 verifyVisibleLineInTextView(1); 704 705 runActionOnUiThread(() -> method.onKeyOther(mTextView, null, 706 new KeyEvent(0, 0, KeyEvent.ACTION_MULTIPLE, 707 KeyEvent.KEYCODE_DPAD_UP, 2))); 708 verifyVisibleLineInTextView(0); 709 } 710 711 private void verifyVisibleLineInTextView(int line) { 712 final Layout layout = mTextView.getLayout(); 713 final int scrollY = mTextView.getScrollY(); 714 final int padding = mTextView.getTotalPaddingTop() + mTextView.getTotalPaddingBottom(); 715 assertTrue(layout.getLineForVertical(scrollY) <= line); 716 assertTrue(layout.getLineForVertical(scrollY + mTextView.getHeight() - padding) >= line); 717 } 718 719 private boolean getActionResult(ActionRunnerWithResult actionRunner) throws Throwable { 720 runActionOnUiThread(actionRunner); 721 return actionRunner.getResult(); 722 } 723 724 private void runActionOnUiThread(Runnable actionRunner) throws Throwable { 725 mActivityRule.runOnUiThread(actionRunner); 726 mInstrumentation.waitForIdleSync(); 727 } 728 729 private static abstract class ActionRunnerWithResult implements Runnable { 730 protected boolean mResult = false; 731 732 public boolean getResult() { 733 return mResult; 734 } 735 } 736 737 private static class MyScrollingMovementMethod extends ScrollingMovementMethod { 738 @Override 739 protected boolean down(TextView widget, Spannable buffer) { 740 return super.down(widget, buffer); 741 } 742 743 @Override 744 protected boolean left(TextView widget, Spannable buffer) { 745 return super.left(widget, buffer); 746 } 747 748 @Override 749 protected boolean right(TextView widget, Spannable buffer) { 750 return super.right(widget, buffer); 751 } 752 753 @Override 754 protected boolean up(TextView widget, Spannable buffer) { 755 return super.up(widget, buffer); 756 } 757 } 758 } 759