Home | History | Annotate | Download | only in cts
      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