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.assertSame;
     23 import static org.junit.Assert.assertTrue;
     24 import static org.mockito.Matchers.any;
     25 import static org.mockito.Mockito.never;
     26 import static org.mockito.Mockito.reset;
     27 import static org.mockito.Mockito.spy;
     28 import static org.mockito.Mockito.times;
     29 import static org.mockito.Mockito.verify;
     30 
     31 import android.app.Activity;
     32 import android.os.SystemClock;
     33 import android.support.test.InstrumentationRegistry;
     34 import android.support.test.annotation.UiThreadTest;
     35 import android.support.test.filters.MediumTest;
     36 import android.support.test.rule.ActivityTestRule;
     37 import android.support.test.runner.AndroidJUnit4;
     38 import android.text.Selection;
     39 import android.text.Spannable;
     40 import android.text.SpannableString;
     41 import android.text.Spanned;
     42 import android.text.method.LinkMovementMethod;
     43 import android.text.method.MovementMethod;
     44 import android.text.style.ClickableSpan;
     45 import android.util.TypedValue;
     46 import android.view.KeyEvent;
     47 import android.view.MotionEvent;
     48 import android.view.View;
     49 import android.widget.TextView;
     50 import android.widget.TextView.BufferType;
     51 
     52 import org.junit.Before;
     53 import org.junit.Rule;
     54 import org.junit.Test;
     55 import org.junit.runner.RunWith;
     56 
     57 /**
     58  * Test {@link LinkMovementMethod}. The class is an implementation of interface
     59  * {@link MovementMethod}. The typical usage of {@link MovementMethod} is tested in
     60  * {@link android.widget.cts.TextViewTest} and this test case is only focused on the
     61  * implementation of the methods.
     62  *
     63  * @see android.widget.cts.TextViewTest
     64  */
     65 @MediumTest
     66 @RunWith(AndroidJUnit4.class)
     67 public class LinkMovementMethodTest {
     68     private static final String CONTENT = "clickable\nunclickable\nclickable";
     69 
     70     private Activity mActivity;
     71     private LinkMovementMethod mMethod;
     72     private TextView mView;
     73     private Spannable mSpannable;
     74     private ClickableSpan mClickable0;
     75     private ClickableSpan mClickable1;
     76 
     77     @Rule
     78     public ActivityTestRule<CtsActivity> mActivityRule = new ActivityTestRule<>(CtsActivity.class);
     79 
     80     @Before
     81     public void setup() throws Throwable {
     82         mActivity = mActivityRule.getActivity();
     83         mMethod = new LinkMovementMethod();
     84 
     85         // Set the content view with a text view which contains 3 lines,
     86         mActivityRule.runOnUiThread(() -> mView = new TextViewNoIme(mActivity));
     87         mView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 10);
     88         mView.setText(CONTENT, BufferType.SPANNABLE);
     89 
     90         mActivityRule.runOnUiThread(() -> mActivity.setContentView(mView));
     91         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     92 
     93         mSpannable = (Spannable) mView.getText();
     94         // make first line clickable
     95         mClickable0 = markClickable(0, CONTENT.indexOf('\n'));
     96         // make last line clickable
     97         mClickable1 = markClickable(CONTENT.lastIndexOf('\n'), CONTENT.length());
     98     }
     99 
    100     @Test
    101     public void testConstructor() {
    102         new LinkMovementMethod();
    103     }
    104 
    105     @Test
    106     public void testGetInstance() {
    107         MovementMethod method0 = LinkMovementMethod.getInstance();
    108         assertTrue(method0 instanceof LinkMovementMethod);
    109 
    110         MovementMethod method1 = LinkMovementMethod.getInstance();
    111         assertNotNull(method1);
    112         assertSame(method0, method1);
    113     }
    114 
    115     @Test
    116     public void testOnTakeFocus() {
    117         LinkMovementMethod method = new LinkMovementMethod();
    118         Spannable spannable = new SpannableString("test sequence");
    119         Selection.setSelection(spannable, 0, spannable.length());
    120 
    121         assertSelection(spannable, 0, spannable.length());
    122         assertTrue("Expected at least 2 spans",
    123                 2 <= spannable.getSpans(0, spannable.length(), Object.class).length);
    124         method.onTakeFocus(null, spannable, View.FOCUS_UP);
    125         assertSelection(spannable, -1);
    126         assertEquals(1, spannable.getSpans(0, spannable.length(), Object.class).length);
    127         Object span = spannable.getSpans(0, spannable.length(), Object.class)[0];
    128         assertEquals(0, spannable.getSpanStart(span));
    129         assertEquals(0, spannable.getSpanEnd(span));
    130         assertEquals(Spanned.SPAN_POINT_POINT, spannable.getSpanFlags(span));
    131 
    132         // focus forwards
    133         Selection.setSelection(spannable, 0, spannable.length());
    134         assertSelection(spannable, 0, spannable.length());
    135         assertTrue("Expected at least 3 spans",
    136                 3 <= spannable.getSpans(0, spannable.length(), Object.class).length);
    137         method.onTakeFocus(null, spannable, View.FOCUS_RIGHT);
    138         assertSelection(spannable, -1);
    139         assertEquals(0, spannable.getSpans(0, spannable.length(), Object.class).length);
    140 
    141         // force adding span while focus backward
    142         method.onTakeFocus(null, spannable, View.FOCUS_UP);
    143         // param direction is unknown(0)
    144         Selection.setSelection(spannable, 0, spannable.length());
    145         assertSelection(spannable, 0, spannable.length());
    146         assertTrue("Expected at least 3 spans",
    147                 3 <= spannable.getSpans(0, spannable.length(), Object.class).length);
    148         method.onTakeFocus(null, spannable, 0);
    149         assertSelection(spannable, -1);
    150         assertEquals(0, spannable.getSpans(0, spannable.length(), Object.class).length);
    151     }
    152 
    153     @UiThreadTest
    154     @Test(expected=NullPointerException.class)
    155     public void testOnTakeFocusNullSpannable() {
    156         LinkMovementMethod method = new LinkMovementMethod();
    157         method.onTakeFocus(new TextViewNoIme(mActivity), null, View.FOCUS_RIGHT);
    158     }
    159 
    160     @UiThreadTest
    161     @Test
    162     public void testOnKeyDown() {
    163         // no selection
    164         assertSelection(mSpannable, -1);
    165         reset(mClickable0);
    166         reset(mClickable1);
    167         assertFalse(mMethod.onKeyDown(mView, mSpannable, KeyEvent.KEYCODE_ENTER,
    168                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)));
    169         verify(mClickable0, never()).onClick(any());
    170         verify(mClickable1, never()).onClick(any());
    171 
    172         // select clickable0
    173         Selection.setSelection(mSpannable, mSpannable.getSpanStart(mClickable0),
    174                 mSpannable.getSpanEnd(mClickable0));
    175         reset(mClickable0);
    176         reset(mClickable1);
    177         assertFalse(mMethod.onKeyDown(mView, mSpannable, KeyEvent.KEYCODE_DPAD_CENTER,
    178                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER)));
    179         verify(mClickable0, times(1)).onClick(any());
    180         verify(mClickable1, never()).onClick(any());
    181 
    182         // select unclickable
    183         Selection.setSelection(mSpannable, mSpannable.getSpanEnd(mClickable0),
    184                 mSpannable.getSpanStart(mClickable1));
    185         reset(mClickable0);
    186         reset(mClickable1);
    187         assertFalse(mMethod.onKeyDown(mView, mSpannable, KeyEvent.KEYCODE_ENTER,
    188                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)));
    189         verify(mClickable0, never()).onClick(any());
    190         verify(mClickable1, never()).onClick(any());
    191 
    192         // select all clickables(more than one)
    193         Selection.selectAll(mSpannable);
    194         reset(mClickable0);
    195         reset(mClickable1);
    196         assertFalse(mMethod.onKeyDown(mView, mSpannable, KeyEvent.KEYCODE_DPAD_CENTER,
    197                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER)));
    198         verify(mClickable0, never()).onClick(any());
    199         verify(mClickable1, never()).onClick(any());
    200 
    201         // part of selection is clickable
    202         Selection.setSelection(mSpannable, mSpannable.getSpanEnd(mClickable0),
    203                 mSpannable.getSpanEnd(mClickable1));
    204         reset(mClickable0);
    205         reset(mClickable1);
    206         assertFalse(mMethod.onKeyDown(mView, mSpannable, KeyEvent.KEYCODE_DPAD_CENTER,
    207                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER)));
    208         verify(mClickable0, never()).onClick(any());
    209         verify(mClickable1, times(1)).onClick(any());
    210 
    211         // selection contains only clickable1 and repeat count of the event is not 0
    212         Selection.setSelection(mSpannable, mSpannable.getSpanEnd(mClickable0),
    213         mSpannable.getSpanEnd(mClickable1));
    214         long now = SystemClock.uptimeMillis();
    215         KeyEvent event = new KeyEvent(now, now, KeyEvent.ACTION_DOWN,
    216                 KeyEvent.KEYCODE_DPAD_CENTER, 1);
    217 
    218         reset(mClickable0);
    219         reset(mClickable1);
    220         assertFalse(mMethod.onKeyDown(mView, mSpannable, KeyEvent.KEYCODE_DPAD_CENTER, event));
    221         verify(mClickable0, never()).onClick(any());
    222         verify(mClickable1, never()).onClick(any());
    223     }
    224 
    225     @UiThreadTest
    226     @Test(expected=NullPointerException.class)
    227     public void testOnKeyDown_nullViewParam() {
    228         mMethod.onKeyDown(null, mSpannable, KeyEvent.KEYCODE_DPAD_CENTER,
    229                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER));
    230     }
    231 
    232     @UiThreadTest
    233     @Test(expected=NullPointerException.class)
    234     public void testOnKeyDown_nullSpannableParam() {
    235         mMethod.onKeyDown(mView, null, KeyEvent.KEYCODE_DPAD_CENTER,
    236                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER));
    237     }
    238 
    239     @UiThreadTest
    240     @Test(expected=NullPointerException.class)
    241     public void testOnKeyDown_nullKeyEventParam() {
    242         mMethod.onKeyDown(mView, mSpannable, KeyEvent.KEYCODE_DPAD_CENTER, null);
    243     }
    244 
    245     @UiThreadTest
    246     @Test
    247     public void testOnKeyUp() {
    248         LinkMovementMethod method = new LinkMovementMethod();
    249         // always returns false
    250         assertFalse(method.onKeyUp(null, null, 0, null));
    251         assertFalse(method.onKeyUp(new TextViewNoIme(mActivity), null, 0, null));
    252         assertFalse(method.onKeyUp(null, new SpannableString("blahblah"), 0, null));
    253         assertFalse(method.onKeyUp(null, null, KeyEvent.KEYCODE_0,
    254                 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0)));
    255     }
    256 
    257     @UiThreadTest
    258     @Test
    259     public void testOnTouchEvent() {
    260         assertSelection(mSpannable, -1);
    261 
    262         // press on first line (Clickable)
    263         assertTrue(pressOnLine(0));
    264         assertSelectClickableLeftToRight(mSpannable, mClickable0);
    265 
    266         // release on first line
    267         verify(mClickable0, never()).onClick(any());
    268         assertTrue(releaseOnLine(0));
    269         verify(mClickable0, times(1)).onClick(any());
    270 
    271         // press on second line (unclickable)
    272         assertSelectClickableLeftToRight(mSpannable, mClickable0);
    273         // just clear selection
    274         pressOnLine(1);
    275         assertSelection(mSpannable, -1);
    276 
    277         // press on last line  (Clickable)
    278         assertTrue(pressOnLine(2));
    279         assertSelectClickableLeftToRight(mSpannable, mClickable1);
    280 
    281         // release on last line
    282         verify(mClickable1, never()).onClick(any());
    283         assertTrue(releaseOnLine(2));
    284         verify(mClickable1, times(1)).onClick(any());
    285 
    286         // release on second line (unclickable)
    287         assertSelectClickableLeftToRight(mSpannable, mClickable1);
    288         // just clear selection
    289         releaseOnLine(1);
    290         assertSelection(mSpannable, -1);
    291     }
    292 
    293     @UiThreadTest
    294     @Test(expected=NullPointerException.class)
    295     public void testOnTouchEvent_nullViewParam() {
    296         long now = SystemClock.uptimeMillis();
    297         int y = (mView.getLayout().getLineTop(1) + mView.getLayout().getLineBottom(1)) / 2;
    298         mMethod.onTouchEvent(null, mSpannable,
    299                 MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 5, y, 0));
    300     }
    301 
    302     @UiThreadTest
    303     @Test(expected=NullPointerException.class)
    304     public void testOnTouchEvent_nullSpannableParam() {
    305         long now = SystemClock.uptimeMillis();
    306         int y = (mView.getLayout().getLineTop(1) + mView.getLayout().getLineBottom(1)) / 2;
    307         mMethod.onTouchEvent(mView, null,
    308                 MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 5, y, 0));
    309     }
    310 
    311     @UiThreadTest
    312     @Test(expected=NullPointerException.class)
    313     public void testOnTouchEvent_nullKeyEventParam() {
    314         mMethod.onTouchEvent(mView, mSpannable, null);
    315     }
    316 
    317     @UiThreadTest
    318     @Test
    319     public void testUp() {
    320         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    321         assertSelection(mSpannable, -1);
    322 
    323         assertTrue(method.up(mView, mSpannable));
    324         assertSelectClickableRightToLeft(mSpannable, mClickable1);
    325 
    326         assertTrue(method.up(mView, mSpannable));
    327         assertSelectClickableRightToLeft(mSpannable, mClickable0);
    328 
    329         assertFalse(method.up(mView, mSpannable));
    330         assertSelectClickableRightToLeft(mSpannable, mClickable0);
    331     }
    332 
    333     @UiThreadTest
    334     @Test(expected=NullPointerException.class)
    335     public void testUp_nullViewParam() {
    336         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    337         method.up(null, mSpannable);
    338     }
    339 
    340     @UiThreadTest
    341     @Test(expected=NullPointerException.class)
    342     public void testUp_nullSpannableParam() {
    343         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    344         method.up(mView, null);
    345     }
    346 
    347     @UiThreadTest
    348     @Test
    349     public void testDown() {
    350         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    351         assertSelection(mSpannable, -1);
    352 
    353         assertTrue(method.down(mView, mSpannable));
    354         assertSelectClickableLeftToRight(mSpannable, mClickable0);
    355 
    356         assertTrue(method.down(mView, mSpannable));
    357         assertSelectClickableLeftToRight(mSpannable, mClickable1);
    358 
    359         assertFalse(method.down(mView, mSpannable));
    360         assertSelectClickableLeftToRight(mSpannable, mClickable1);
    361     }
    362 
    363     @UiThreadTest
    364     @Test(expected=NullPointerException.class)
    365     public void testDown_nullViewParam() {
    366         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    367         method.down(null, mSpannable);
    368     }
    369 
    370     @UiThreadTest
    371     @Test(expected=NullPointerException.class)
    372     public void testDown_nullSpannableParam() {
    373         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    374         method.down(mView, null);
    375     }
    376 
    377     @UiThreadTest
    378     @Test
    379     public void testLeft() {
    380         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    381         assertSelection(mSpannable, -1);
    382 
    383         assertTrue(method.left(mView, mSpannable));
    384         assertSelectClickableRightToLeft(mSpannable, mClickable1);
    385 
    386         assertTrue(method.left(mView, mSpannable));
    387         assertSelectClickableRightToLeft(mSpannable, mClickable0);
    388 
    389         assertFalse(method.left(mView, mSpannable));
    390         assertSelectClickableRightToLeft(mSpannable, mClickable0);
    391     }
    392 
    393     @UiThreadTest
    394     @Test(expected=NullPointerException.class)
    395     public void testLeft_nullViewParam() {
    396         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    397         method.left(null, mSpannable);
    398     }
    399 
    400     @UiThreadTest
    401     @Test(expected=NullPointerException.class)
    402     public void testLeft_nullSpannableParam() {
    403         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    404         method.left(mView, null);
    405     }
    406 
    407     @UiThreadTest
    408     @Test
    409     public void testRight() {
    410         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    411         assertSelection(mSpannable, -1);
    412 
    413         assertTrue(method.right(mView, mSpannable));
    414         assertSelectClickableLeftToRight(mSpannable, mClickable0);
    415 
    416         assertTrue(method.right(mView, mSpannable));
    417         assertSelectClickableLeftToRight(mSpannable, mClickable1);
    418 
    419         assertFalse(method.right(mView, mSpannable));
    420         assertSelectClickableLeftToRight(mSpannable, mClickable1);
    421     }
    422 
    423     @UiThreadTest
    424     @Test(expected=NullPointerException.class)
    425     public void testRight_nullViewParam() {
    426         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    427         method.right(null, mSpannable);
    428     }
    429 
    430     @UiThreadTest
    431     @Test(expected=NullPointerException.class)
    432     public void testRight_nullSpannableParam() {
    433         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    434         method.right(mView, null);
    435     }
    436 
    437     @UiThreadTest
    438     @Test
    439     public void testMoveAroundUnclickable() {
    440         final MyLinkMovementMethod method = new MyLinkMovementMethod();
    441         mSpannable.removeSpan(mClickable0);
    442         mSpannable.removeSpan(mClickable1);
    443         assertSelection(mSpannable, -1);
    444 
    445         assertFalse(method.up(mView, mSpannable));
    446         assertSelection(mSpannable, -1);
    447 
    448         assertFalse(method.down(mView, mSpannable));
    449         assertSelection(mSpannable, -1);
    450 
    451         assertFalse(method.left(mView, mSpannable));
    452         assertSelection(mSpannable, -1);
    453 
    454         assertFalse(method.right(mView, mSpannable));
    455         assertSelection(mSpannable, -1);
    456     }
    457 
    458     @Test
    459     public void testInitialize() {
    460         LinkMovementMethod method = new LinkMovementMethod();
    461         Spannable spannable = new SpannableString("test sequence");
    462         method.onTakeFocus(null, spannable, View.FOCUS_UP);
    463         Selection.setSelection(spannable, 0, spannable.length());
    464 
    465         assertSelection(spannable, 0, spannable.length());
    466         assertTrue("Expected at least 3 spans", 3 <= spannable.getSpans(0, spannable.length(), Object.class).length);
    467         method.initialize(null, spannable);
    468         assertSelection(spannable, -1);
    469         assertEquals(0, spannable.getSpans(0, spannable.length(), Object.class).length);
    470 
    471     }
    472 
    473     @Test(expected=NullPointerException.class)
    474     public void testInitialize_nullViewParam() {
    475         final LinkMovementMethod method = new LinkMovementMethod();
    476         method.initialize(mView, null);
    477     }
    478 
    479     private ClickableSpan markClickable(final int start, final int end) throws Throwable {
    480         final ClickableSpan clickableSpan = spy(new MockClickableSpan());
    481         mActivityRule.runOnUiThread(() -> mSpannable.setSpan(clickableSpan, start, end,
    482                 Spanned.SPAN_MARK_MARK));
    483         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
    484         return clickableSpan;
    485     }
    486 
    487     private boolean performMotionOnLine(int line, int action) {
    488         int x = (mView.getLayout().getLineStart(line) + mView.getLayout().getLineEnd(line)) / 2;
    489         int y = (mView.getLayout().getLineTop(line) + mView.getLayout().getLineBottom(line)) / 2;
    490         long now = SystemClock.uptimeMillis();
    491 
    492         return mMethod.onTouchEvent(mView, mSpannable,
    493                 MotionEvent.obtain(now, now, action, x, y, 0));
    494     }
    495 
    496     private boolean pressOnLine(int line) {
    497         return performMotionOnLine(line, MotionEvent.ACTION_DOWN);
    498     }
    499 
    500     private boolean releaseOnLine(int line) {
    501         return performMotionOnLine(line, MotionEvent.ACTION_UP);
    502     }
    503 
    504     private void assertSelection(Spannable spannable, int start, int end) {
    505         assertEquals(start, Selection.getSelectionStart(spannable));
    506         assertEquals(end, Selection.getSelectionEnd(spannable));
    507     }
    508 
    509     private void assertSelection(Spannable spannable, int position) {
    510         assertSelection(spannable, position, position);
    511     }
    512 
    513     private void assertSelectClickableLeftToRight(Spannable spannable,
    514             ClickableSpan clickableSpan) {
    515         assertSelection(spannable, spannable.getSpanStart(clickableSpan),
    516                 spannable.getSpanEnd(clickableSpan));
    517     }
    518 
    519     private void assertSelectClickableRightToLeft(Spannable spannable,
    520             ClickableSpan clickableSpan) {
    521         assertSelection(spannable,  spannable.getSpanEnd(clickableSpan),
    522                 spannable.getSpanStart(clickableSpan));
    523     }
    524 
    525     private static class MyLinkMovementMethod extends LinkMovementMethod {
    526         @Override
    527         protected boolean down(TextView widget, Spannable buffer) {
    528             return super.down(widget, buffer);
    529         }
    530 
    531         @Override
    532         protected boolean left(TextView widget, Spannable buffer) {
    533             return super.left(widget, buffer);
    534         }
    535 
    536         @Override
    537         protected boolean right(TextView widget, Spannable buffer) {
    538             return super.right(widget, buffer);
    539         }
    540 
    541         @Override
    542         protected boolean up(TextView widget, Spannable buffer) {
    543             return super.up(widget, buffer);
    544         }
    545     }
    546 
    547     public static class MockClickableSpan extends ClickableSpan {
    548         @Override
    549         public void onClick(View widget) {
    550         }
    551     }
    552 }
    553