Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2015 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;
     18 
     19 import static android.support.test.espresso.Espresso.onView;
     20 import static android.support.test.espresso.action.ViewActions.click;
     21 import static android.support.test.espresso.action.ViewActions.longClick;
     22 import static android.support.test.espresso.action.ViewActions.pressKey;
     23 import static android.support.test.espresso.action.ViewActions.replaceText;
     24 import static android.support.test.espresso.assertion.ViewAssertions.matches;
     25 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
     26 import static android.support.test.espresso.matcher.ViewMatchers.withId;
     27 import static android.support.test.espresso.matcher.ViewMatchers.withText;
     28 import static android.widget.espresso.CustomViewActions.longPressAtRelativeCoordinates;
     29 import static android.widget.espresso.DragHandleUtils.onHandleView;
     30 import static android.widget.espresso.FloatingToolbarEspressoUtils
     31         .assertFloatingToolbarContainsItem;
     32 import static android.widget.espresso.FloatingToolbarEspressoUtils
     33         .assertFloatingToolbarDoesNotContainItem;
     34 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarIsDisplayed;
     35 import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarItemIndex;
     36 import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
     37 import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
     38 import static android.widget.espresso.TextViewActions.Handle;
     39 import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
     40 import static android.widget.espresso.TextViewActions.doubleClickOnTextAtIndex;
     41 import static android.widget.espresso.TextViewActions.doubleTapAndDragOnText;
     42 import static android.widget.espresso.TextViewActions.dragHandle;
     43 import static android.widget.espresso.TextViewActions.longPressAndDragOnText;
     44 import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
     45 import static android.widget.espresso.TextViewAssertions.doesNotHaveStyledText;
     46 import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
     47 import static android.widget.espresso.TextViewAssertions.hasSelection;
     48 
     49 import static junit.framework.Assert.assertEquals;
     50 import static junit.framework.Assert.assertFalse;
     51 import static junit.framework.Assert.assertTrue;
     52 
     53 import static org.hamcrest.Matchers.anyOf;
     54 import static org.hamcrest.Matchers.is;
     55 import static org.mockito.Matchers.any;
     56 import static org.mockito.Mockito.mock;
     57 import static org.mockito.Mockito.never;
     58 import static org.mockito.Mockito.verify;
     59 import static org.mockito.Mockito.when;
     60 
     61 import android.app.Activity;
     62 import android.app.Instrumentation;
     63 import android.content.ClipData;
     64 import android.content.ClipboardManager;
     65 import android.support.test.InstrumentationRegistry;
     66 import android.support.test.espresso.action.EspressoKey;
     67 import android.support.test.filters.MediumTest;
     68 import android.support.test.rule.ActivityTestRule;
     69 import android.support.test.runner.AndroidJUnit4;
     70 import android.support.test.uiautomator.UiDevice;
     71 import android.test.suitebuilder.annotation.Suppress;
     72 import android.text.InputType;
     73 import android.text.Selection;
     74 import android.text.Spannable;
     75 import android.text.SpannableString;
     76 import android.text.method.LinkMovementMethod;
     77 import android.view.ActionMode;
     78 import android.view.KeyEvent;
     79 import android.view.Menu;
     80 import android.view.MenuItem;
     81 import android.view.textclassifier.SelectionEvent;
     82 import android.view.textclassifier.TextClassificationManager;
     83 import android.view.textclassifier.TextClassifier;
     84 import android.view.textclassifier.TextLinks;
     85 import android.view.textclassifier.TextLinksParams;
     86 import android.widget.espresso.CustomViewActions.RelativeCoordinatesProvider;
     87 
     88 import com.android.frameworks.coretests.R;
     89 
     90 import org.junit.Before;
     91 import org.junit.Rule;
     92 import org.junit.Test;
     93 import org.junit.runner.RunWith;
     94 
     95 import java.util.ArrayList;
     96 import java.util.List;
     97 
     98 /**
     99  * Tests the TextView widget from an Activity
    100  */
    101 @RunWith(AndroidJUnit4.class)
    102 @MediumTest
    103 public class TextViewActivityTest {
    104 
    105     @Rule
    106     public ActivityTestRule<TextViewActivity> mActivityRule = new ActivityTestRule<>(
    107             TextViewActivity.class);
    108 
    109     private Activity mActivity;
    110     private Instrumentation mInstrumentation;
    111 
    112     @Before
    113     public void setUp() {
    114         mActivity = mActivityRule.getActivity();
    115         mInstrumentation = InstrumentationRegistry.getInstrumentation();
    116         mActivity.getSystemService(TextClassificationManager.class)
    117                 .setTextClassifier(TextClassifier.NO_OP);
    118     }
    119 
    120     @Test
    121     public void testTypedTextIsOnScreen() {
    122         final String helloWorld = "Hello world!";
    123         // We use replaceText instead of typeTextIntoFocusedView to input text to avoid
    124         // unintentional interactions with software keyboard.
    125         onView(withId(R.id.textview)).perform(replaceText(helloWorld));
    126 
    127         onView(withId(R.id.textview)).check(matches(withText(helloWorld)));
    128     }
    129     @Test
    130     public void testPositionCursorAtTextAtIndex() {
    131         final String helloWorld = "Hello world!";
    132         onView(withId(R.id.textview)).perform(replaceText(helloWorld));
    133         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("world")));
    134 
    135         // Delete text at specified index and see if we got the right one.
    136         onView(withId(R.id.textview)).perform(pressKey(KeyEvent.KEYCODE_FORWARD_DEL));
    137         onView(withId(R.id.textview)).check(matches(withText("Hello orld!")));
    138     }
    139 
    140     @Test
    141     public void testPositionCursorAtTextAtIndex_arabic() {
    142         // Arabic text. The expected cursorable boundary is
    143         // | \u0623 \u064F | \u067A | \u0633 \u0652 |
    144         final String text = "\u0623\u064F\u067A\u0633\u0652";
    145         onView(withId(R.id.textview)).perform(replaceText(text));
    146 
    147         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
    148         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
    149         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
    150         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
    151         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
    152         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
    153         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(3));
    154         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(3));
    155         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(4));
    156         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(3), is(5))));
    157         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(5));
    158         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(5));
    159     }
    160 
    161     @Test
    162     public void testPositionCursorAtTextAtIndex_devanagari() {
    163         // Devanagari text. The expected cursorable boundary is | \u0915 \u093E |
    164         final String text = "\u0915\u093E";
    165         onView(withId(R.id.textview)).perform(replaceText(text));
    166 
    167         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
    168         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
    169         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(1));
    170         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(anyOf(is(0), is(2))));
    171         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(2));
    172         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
    173     }
    174 
    175     @Test
    176     public void testLongPressToSelect() {
    177         final String helloWorld = "Hello Kirk!";
    178         onView(withId(R.id.textview)).perform(click());
    179         onView(withId(R.id.textview)).perform(replaceText(helloWorld));
    180         onView(withId(R.id.textview)).perform(
    181                 longPressOnTextAtIndex(helloWorld.indexOf("Kirk")));
    182 
    183         onView(withId(R.id.textview)).check(hasSelection("Kirk"));
    184     }
    185 
    186     @Test
    187     public void testLongPressEmptySpace() {
    188         final String helloWorld = "Hello big round sun!";
    189         onView(withId(R.id.textview)).perform(replaceText(helloWorld));
    190         // Move cursor somewhere else
    191         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(helloWorld.indexOf("big")));
    192         // Long-press at end of line.
    193         onView(withId(R.id.textview)).perform(longPressAtRelativeCoordinates(
    194                 RelativeCoordinatesProvider.HorizontalReference.RIGHT, -5,
    195                 RelativeCoordinatesProvider.VerticalReference.CENTER, 0));
    196 
    197         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(helloWorld.length()));
    198     }
    199 
    200     @Test
    201     public void testLongPressAndDragToSelect() {
    202         final String helloWorld = "Hello little handsome boy!";
    203         onView(withId(R.id.textview)).perform(replaceText(helloWorld));
    204         onView(withId(R.id.textview)).perform(
    205                 longPressAndDragOnText(helloWorld.indexOf("little"), helloWorld.indexOf(" boy!")));
    206 
    207         onView(withId(R.id.textview)).check(hasSelection("little handsome"));
    208     }
    209 
    210     @Test
    211     public void testLongPressAndDragToSelect_emoji() {
    212         final String text = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03";
    213         onView(withId(R.id.textview)).perform(replaceText(text));
    214 
    215         onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 6));
    216         onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE02"));
    217 
    218         onView(withId(R.id.textview)).perform(click());
    219 
    220         onView(withId(R.id.textview)).perform(longPressAndDragOnText(4, 2));
    221         onView(withId(R.id.textview)).check(hasSelection("\uD83D\uDE01"));
    222     }
    223 
    224     @Test
    225     public void testDragAndDrop() {
    226         final String text = "abc def ghi.";
    227         onView(withId(R.id.textview)).perform(replaceText(text));
    228         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("e")));
    229 
    230         onView(withId(R.id.textview)).perform(
    231                 longPressAndDragOnText(text.indexOf("e"), text.length()));
    232 
    233         onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
    234         onView(withId(R.id.textview)).check(hasSelection(""));
    235         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
    236 
    237         // Test undo returns to the original state.
    238         onView(withId(R.id.textview)).perform(pressKey(
    239                 (new EspressoKey.Builder()).withCtrlPressed(true).withKeyCode(KeyEvent.KEYCODE_Z)
    240                         .build()));
    241         onView(withId(R.id.textview)).check(matches(withText(text)));
    242     }
    243 
    244     @Test
    245     public void testDoubleTapToSelect() {
    246         final String helloWorld = "Hello SuetYi!";
    247         onView(withId(R.id.textview)).perform(replaceText(helloWorld));
    248 
    249         onView(withId(R.id.textview)).perform(
    250                 doubleClickOnTextAtIndex(helloWorld.indexOf("SuetYi")));
    251 
    252         onView(withId(R.id.textview)).check(hasSelection("SuetYi"));
    253     }
    254 
    255     @Test
    256     public void testDoubleTapAndDragToSelect() {
    257         final String helloWorld = "Hello young beautiful person!";
    258         onView(withId(R.id.textview)).perform(replaceText(helloWorld));
    259         onView(withId(R.id.textview)).perform(doubleTapAndDragOnText(helloWorld.indexOf("young"),
    260                         helloWorld.indexOf(" person!")));
    261 
    262         onView(withId(R.id.textview)).check(hasSelection("young beautiful"));
    263     }
    264 
    265     @Test
    266     public void testDoubleTapAndDragToSelect_multiLine() {
    267         final String helloWorld = "abcd\n" + "efg\n" + "hijklm\n" + "nop";
    268         onView(withId(R.id.textview)).perform(replaceText(helloWorld));
    269         onView(withId(R.id.textview)).perform(
    270                 doubleTapAndDragOnText(helloWorld.indexOf("m"), helloWorld.indexOf("a")));
    271         onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijklm"));
    272     }
    273 
    274     @Test
    275     public void testSelectBackwordsByTouch() {
    276         final String helloWorld = "Hello king of the Jungle!";
    277         onView(withId(R.id.textview)).perform(replaceText(helloWorld));
    278         onView(withId(R.id.textview)).perform(
    279                 doubleTapAndDragOnText(helloWorld.indexOf(" Jungle!"), helloWorld.indexOf("king")));
    280 
    281         onView(withId(R.id.textview)).check(hasSelection("king of the"));
    282     }
    283 
    284     @Test
    285     public void testToolbarAppearsAfterSelection() {
    286         final String text = "Toolbar appears after selection.";
    287         onView(withId(R.id.textview)).perform(replaceText(text));
    288         onView(withId(R.id.textview)).perform(
    289                 longPressOnTextAtIndex(text.indexOf("appears")));
    290 
    291         sleepForFloatingToolbarPopup();
    292         assertFloatingToolbarIsDisplayed();
    293     }
    294 
    295     @Test
    296     public void testToolbarAppearsAfterSelection_withFirstStringLtrAlgorithmAndRtlHint()
    297             throws Throwable {
    298         // after the hint layout change, the floating toolbar was not visible in the case below
    299         // this test tests that the floating toolbar is displayed on the screen and is visible to
    300         // user.
    301         mActivityRule.runOnUiThread(() -> {
    302             final TextView textView = mActivity.findViewById(R.id.textview);
    303             textView.setTextDirection(TextView.TEXT_DIRECTION_FIRST_STRONG_LTR);
    304             textView.setInputType(InputType.TYPE_CLASS_TEXT);
    305             textView.setSingleLine(true);
    306             textView.setHint("");
    307         });
    308         mInstrumentation.waitForIdleSync();
    309 
    310         onView(withId(R.id.textview)).perform(replaceText("test"));
    311         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(1));
    312         clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.cut));
    313         onView(withId(R.id.textview)).perform(longClick());
    314         sleepForFloatingToolbarPopup();
    315 
    316         assertFloatingToolbarIsDisplayed();
    317     }
    318 
    319     @Test
    320     public void testToolbarAppearsAfterLinkClicked() throws Throwable {
    321         TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview);
    322         int position = (textLink.getStart() + textLink.getEnd()) / 2;
    323         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position));
    324         sleepForFloatingToolbarPopup();
    325         assertFloatingToolbarIsDisplayed();
    326     }
    327 
    328     @Test
    329     public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable {
    330         final TextView textView = mActivity.findViewById(R.id.nonselectable_textview);
    331         final TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
    332         final int position = (textLink.getStart() + textLink.getEnd()) / 2;
    333 
    334         onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
    335         sleepForFloatingToolbarPopup();
    336         assertFloatingToolbarIsDisplayed();
    337         assertTrue(textView.hasSelection());
    338 
    339         // toggle
    340         onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
    341         sleepForFloatingToolbarPopup();
    342         assertFalse(textView.hasSelection());
    343 
    344         onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
    345         sleepForFloatingToolbarPopup();
    346         assertFloatingToolbarIsDisplayed();
    347         assertTrue(textView.hasSelection());
    348 
    349         // click outside
    350         onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(0));
    351         sleepForFloatingToolbarPopup();
    352         assertFalse(textView.hasSelection());
    353     }
    354 
    355     @Test
    356     public void testSelectionRemovedWhenNonselectableTextLosesFocus() throws Throwable {
    357         final TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
    358         final int position = (textLink.getStart() + textLink.getEnd()) / 2;
    359         final TextView textView = mActivity.findViewById(R.id.nonselectable_textview);
    360         mActivityRule.runOnUiThread(() -> textView.setFocusableInTouchMode(true));
    361 
    362         onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
    363         sleepForFloatingToolbarPopup();
    364         assertFloatingToolbarIsDisplayed();
    365         assertTrue(textView.hasSelection());
    366 
    367         mActivityRule.runOnUiThread(() -> textView.clearFocus());
    368         mInstrumentation.waitForIdleSync();
    369         sleepForFloatingToolbarPopup();
    370 
    371         assertFalse(textView.hasSelection());
    372     }
    373 
    374     @Test
    375     public void testSelectionRemovedFromNonselectableTextWhenWindowLosesFocus() throws Throwable {
    376         TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
    377         int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2;
    378         TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview);
    379 
    380         onView(withId(R.id.nonselectable_textview))
    381                 .perform(clickOnTextAtIndex(nonselectablePosition));
    382         sleepForFloatingToolbarPopup();
    383         assertFloatingToolbarIsDisplayed();
    384         assertTrue(nonselectableTextView.hasSelection());
    385 
    386         UiDevice device = UiDevice.getInstance(mInstrumentation);
    387         device.openNotification();
    388         Thread.sleep(2000);
    389         device.pressBack();
    390         Thread.sleep(2000);
    391 
    392         assertFalse(nonselectableTextView.hasSelection());
    393     }
    394 
    395     private TextLinks.TextLink addLinkifiedTextToTextView(int id) throws Throwable {
    396         TextView textView = mActivity.findViewById(id);
    397         useSystemDefaultTextClassifier();
    398         TextClassificationManager textClassificationManager =
    399                 mActivity.getSystemService(TextClassificationManager.class);
    400         TextClassifier textClassifier = textClassificationManager.getTextClassifier();
    401         Spannable content = new SpannableString("Call me at +19148277737");
    402         TextLinks.Request request = new TextLinks.Request.Builder(content).build();
    403         TextLinks links = textClassifier.generateLinks(request);
    404         TextLinksParams applyParams = new TextLinksParams.Builder()
    405                 .setApplyStrategy(TextLinks.APPLY_STRATEGY_REPLACE)
    406                 .build();
    407         applyParams.apply(content, links);
    408 
    409         mActivityRule.runOnUiThread(() -> {
    410             textView.setText(content);
    411             textView.setMovementMethod(LinkMovementMethod.getInstance());
    412         });
    413         mInstrumentation.waitForIdleSync();
    414 
    415         // Wait for the UI thread to refresh
    416         Thread.sleep(1000);
    417 
    418         return links.getLinks().iterator().next();
    419     }
    420 
    421     @Test
    422     public void testToolbarAndInsertionHandle() {
    423         final String text = "text";
    424         onView(withId(R.id.textview)).perform(replaceText(text));
    425         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
    426 
    427         onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
    428         sleepForFloatingToolbarPopup();
    429         assertFloatingToolbarIsDisplayed();
    430 
    431         assertFloatingToolbarContainsItem(
    432                 mActivity.getString(com.android.internal.R.string.selectAll));
    433         assertFloatingToolbarDoesNotContainItem(
    434                 mActivity.getString(com.android.internal.R.string.copy));
    435         assertFloatingToolbarDoesNotContainItem(
    436                 mActivity.getString(com.android.internal.R.string.cut));
    437     }
    438 
    439     @Test
    440     public void testToolbarAndSelectionHandle() {
    441         final String text = "abcd efg hijk";
    442         onView(withId(R.id.textview)).perform(replaceText(text));
    443 
    444         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf("f")));
    445         sleepForFloatingToolbarPopup();
    446         assertFloatingToolbarIsDisplayed();
    447 
    448         assertFloatingToolbarContainsItem(
    449                 mActivity.getString(com.android.internal.R.string.selectAll));
    450         assertFloatingToolbarContainsItem(
    451                 mActivity.getString(com.android.internal.R.string.copy));
    452         assertFloatingToolbarContainsItem(
    453                 mActivity.getString(com.android.internal.R.string.cut));
    454 
    455         final TextView textView = mActivity.findViewById(R.id.textview);
    456         onHandleView(com.android.internal.R.id.selection_start_handle)
    457                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
    458         sleepForFloatingToolbarPopup();
    459         assertFloatingToolbarIsDisplayed();
    460 
    461         onHandleView(com.android.internal.R.id.selection_end_handle)
    462                 .perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
    463         sleepForFloatingToolbarPopup();
    464         assertFloatingToolbarIsDisplayed();
    465 
    466         assertFloatingToolbarDoesNotContainItem(
    467                 mActivity.getString(com.android.internal.R.string.selectAll));
    468         assertFloatingToolbarContainsItem(
    469                 mActivity.getString(com.android.internal.R.string.copy));
    470         assertFloatingToolbarContainsItem(
    471                 mActivity.getString(com.android.internal.R.string.cut));
    472     }
    473 
    474     @Test
    475     public void testInsertionHandle() {
    476         final String text = "abcd efg hijk ";
    477         onView(withId(R.id.textview)).perform(replaceText(text));
    478 
    479         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
    480         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
    481 
    482         final TextView textView = mActivity.findViewById(R.id.textview);
    483 
    484         onHandleView(com.android.internal.R.id.insertion_handle)
    485                 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
    486         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
    487 
    488         onHandleView(com.android.internal.R.id.insertion_handle)
    489                 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
    490         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
    491     }
    492 
    493     @Test
    494     public void testInsertionHandle_multiLine() {
    495         final String text = "abcd\n" + "efg\n" + "hijk\n";
    496         onView(withId(R.id.textview)).perform(replaceText(text));
    497 
    498         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
    499         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length()));
    500 
    501         final TextView textView = mActivity.findViewById(R.id.textview);
    502 
    503         onHandleView(com.android.internal.R.id.insertion_handle)
    504                 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('a')));
    505         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("a")));
    506 
    507         onHandleView(com.android.internal.R.id.insertion_handle)
    508                 .perform(dragHandle(textView, Handle.INSERTION, text.indexOf('f')));
    509         onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("f")));
    510     }
    511 
    512     @Test
    513     public void testSelectionHandles() {
    514         final String text = "abcd efg hijk lmn";
    515         onView(withId(R.id.textview)).perform(replaceText(text));
    516 
    517         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
    518 
    519         onHandleView(com.android.internal.R.id.selection_start_handle)
    520                 .check(matches(isDisplayed()));
    521         onHandleView(com.android.internal.R.id.selection_end_handle)
    522                 .check(matches(isDisplayed()));
    523 
    524         final TextView textView = mActivity.findViewById(R.id.textview);
    525         onHandleView(com.android.internal.R.id.selection_start_handle)
    526                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
    527         onView(withId(R.id.textview)).check(hasSelection("abcd efg"));
    528 
    529         onHandleView(com.android.internal.R.id.selection_end_handle)
    530                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('k') + 1));
    531         onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk"));
    532     }
    533 
    534     @Test
    535     public void testSelectionHandles_bidi() {
    536         final String text = "abc \u0621\u0622\u0623 def";
    537         onView(withId(R.id.textview)).perform(replaceText(text));
    538 
    539         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0622')));
    540 
    541         onHandleView(com.android.internal.R.id.selection_start_handle)
    542                 .check(matches(isDisplayed()));
    543         onHandleView(com.android.internal.R.id.selection_end_handle)
    544                 .check(matches(isDisplayed()));
    545 
    546         onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
    547 
    548         final TextView textView = mActivity.findViewById(R.id.textview);
    549         onHandleView(com.android.internal.R.id.selection_start_handle)
    550                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f')));
    551         onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
    552 
    553         onHandleView(com.android.internal.R.id.selection_end_handle)
    554                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
    555         onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
    556 
    557         onHandleView(com.android.internal.R.id.selection_start_handle)
    558                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u0623'),
    559                         false));
    560         onView(withId(R.id.textview)).check(hasSelection("\u0623"));
    561 
    562         onHandleView(com.android.internal.R.id.selection_start_handle)
    563                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u0621'),
    564                         false));
    565         onView(withId(R.id.textview)).check(hasSelection("\u0621\u0622\u0623"));
    566 
    567         onHandleView(com.android.internal.R.id.selection_start_handle)
    568                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
    569         onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623"));
    570 
    571         onHandleView(com.android.internal.R.id.selection_end_handle)
    572                 .perform(dragHandle(textView, Handle.SELECTION_END, text.length()));
    573         onView(withId(R.id.textview)).check(hasSelection("abc \u0621\u0622\u0623 def"));
    574     }
    575 
    576     @Test
    577     public void testSelectionHandles_multiLine() {
    578         final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
    579         onView(withId(R.id.textview)).perform(replaceText(text));
    580         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
    581 
    582         final TextView textView = mActivity.findViewById(R.id.textview);
    583         onHandleView(com.android.internal.R.id.selection_start_handle)
    584                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('e')));
    585         onView(withId(R.id.textview)).check(hasSelection("efg\nhijk"));
    586 
    587         onHandleView(com.android.internal.R.id.selection_start_handle)
    588                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('a')));
    589         onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk"));
    590 
    591         onHandleView(com.android.internal.R.id.selection_end_handle)
    592                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('n') + 1));
    593         onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn"));
    594 
    595         onHandleView(com.android.internal.R.id.selection_end_handle)
    596                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('r') + 1));
    597         onView(withId(R.id.textview)).check(hasSelection("abcd\nefg\nhijk\nlmn\nopqr"));
    598     }
    599 
    600     @Suppress // Consistently failing.
    601     @Test
    602     public void testSelectionHandles_multiLine_rtl() {
    603         // Arabic text.
    604         final String text = "\u062A\u062B\u062C\n" + "\u062D\u062E\u062F\n"
    605                 + "\u0630\u0631\u0632\n" + "\u0633\u0634\u0635\n" + "\u0636\u0637\u0638\n"
    606                 + "\u0639\u063A\u063B";
    607         onView(withId(R.id.textview)).perform(replaceText(text));
    608         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('\u0634')));
    609 
    610         final TextView textView = mActivity.findViewById(R.id.textview);
    611         onHandleView(com.android.internal.R.id.selection_start_handle)
    612                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062E')));
    613         onView(withId(R.id.textview)).check(hasSelection(
    614                 text.substring(text.indexOf('\u062D'), text.indexOf('\u0635') + 1)));
    615 
    616         onHandleView(com.android.internal.R.id.selection_start_handle)
    617                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('\u062A')));
    618         onView(withId(R.id.textview)).check(hasSelection(
    619                 text.substring(text.indexOf('\u062A'), text.indexOf('\u0635') + 1)));
    620 
    621         onHandleView(com.android.internal.R.id.selection_end_handle)
    622                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u0638')));
    623         onView(withId(R.id.textview)).check(hasSelection(
    624                 text.substring(text.indexOf('\u062A'), text.indexOf('\u0638') + 1)));
    625 
    626         onHandleView(com.android.internal.R.id.selection_end_handle)
    627                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('\u063B')));
    628         onView(withId(R.id.textview)).check(hasSelection(text));
    629     }
    630 
    631     @Test
    632     public void testSelectionHandles_doesNotPassAnotherHandle() {
    633         final String text = "abcd efg hijk lmn";
    634         onView(withId(R.id.textview)).perform(replaceText(text));
    635         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
    636 
    637         final TextView textView = mActivity.findViewById(R.id.textview);
    638         onHandleView(com.android.internal.R.id.selection_start_handle)
    639                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('l')));
    640         onView(withId(R.id.textview)).check(hasSelection("g"));
    641 
    642         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
    643         onHandleView(com.android.internal.R.id.selection_end_handle)
    644                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
    645         onView(withId(R.id.textview)).check(hasSelection("e"));
    646     }
    647 
    648     @Test
    649     public void testSelectionHandles_doesNotPassAnotherHandle_multiLine() {
    650         final String text = "abcd\n" + "efg\n" + "hijk\n" + "lmn\n" + "opqr";
    651         onView(withId(R.id.textview)).perform(replaceText(text));
    652         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
    653 
    654         final TextView textView = mActivity.findViewById(R.id.textview);
    655         onHandleView(com.android.internal.R.id.selection_start_handle)
    656                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('r') + 1));
    657         onView(withId(R.id.textview)).check(hasSelection("k"));
    658 
    659         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
    660         onHandleView(com.android.internal.R.id.selection_end_handle)
    661                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('a')));
    662         onView(withId(R.id.textview)).check(hasSelection("h"));
    663     }
    664 
    665     @Test
    666     public void testSelectionHandles_snapToWordBoundary() {
    667         final String text = "abcd efg hijk lmn opqr";
    668         onView(withId(R.id.textview)).perform(replaceText(text));
    669         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
    670 
    671         final TextView textView = mActivity.findViewById(R.id.textview);
    672 
    673         onHandleView(com.android.internal.R.id.selection_start_handle)
    674                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('f')));
    675         onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
    676 
    677         onHandleView(com.android.internal.R.id.selection_start_handle)
    678                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('d') + 1));
    679         onView(withId(R.id.textview)).check(hasSelection("efg hijk"));
    680 
    681 
    682         onHandleView(com.android.internal.R.id.selection_start_handle)
    683                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c')));
    684         onView(withId(R.id.textview)).check(hasSelection("abcd efg hijk"));
    685 
    686         onHandleView(com.android.internal.R.id.selection_start_handle)
    687                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('d')));
    688         onView(withId(R.id.textview)).check(hasSelection("d efg hijk"));
    689 
    690         onHandleView(com.android.internal.R.id.selection_start_handle)
    691                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('b')));
    692         onView(withId(R.id.textview)).check(hasSelection("bcd efg hijk"));
    693 
    694         onView(withId(R.id.textview)).perform(click());
    695         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
    696 
    697         onHandleView(com.android.internal.R.id.selection_end_handle)
    698                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('n')));
    699         onView(withId(R.id.textview)).check(hasSelection("hijk lmn"));
    700 
    701         onHandleView(com.android.internal.R.id.selection_end_handle)
    702                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('o')));
    703         onView(withId(R.id.textview)).check(hasSelection("hijk lmn"));
    704 
    705         onHandleView(com.android.internal.R.id.selection_end_handle)
    706                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('q')));
    707         onView(withId(R.id.textview)).check(hasSelection("hijk lmn opqr"));
    708 
    709         onHandleView(com.android.internal.R.id.selection_end_handle)
    710                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('p')));
    711         onView(withId(R.id.textview)).check(hasSelection("hijk lmn o"));
    712 
    713         onHandleView(com.android.internal.R.id.selection_end_handle)
    714                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('r')));
    715         onView(withId(R.id.textview)).check(hasSelection("hijk lmn opq"));
    716     }
    717 
    718     @Test
    719     public void testSelectionHandles_snapToWordBoundary_multiLine() {
    720         final String text = "abcd efg\n" + "hijk lmn\n" + "opqr stu";
    721         onView(withId(R.id.textview)).perform(replaceText(text));
    722         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('m')));
    723 
    724         final TextView textView = mActivity.findViewById(R.id.textview);
    725 
    726         onHandleView(com.android.internal.R.id.selection_start_handle)
    727                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('c')));
    728         onView(withId(R.id.textview)).check(hasSelection("abcd efg\nhijk lmn"));
    729 
    730         onHandleView(com.android.internal.R.id.selection_start_handle)
    731                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('g')));
    732         onView(withId(R.id.textview)).check(hasSelection("g\nhijk lmn"));
    733 
    734         onHandleView(com.android.internal.R.id.selection_start_handle)
    735                 .perform(dragHandle(textView, Handle.SELECTION_START, text.indexOf('m')));
    736         onView(withId(R.id.textview)).check(hasSelection("lmn"));
    737 
    738         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('i')));
    739 
    740         onHandleView(com.android.internal.R.id.selection_end_handle)
    741                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('u')));
    742         onView(withId(R.id.textview)).check(hasSelection("hijk lmn\nopqr stu"));
    743 
    744         onHandleView(com.android.internal.R.id.selection_end_handle)
    745                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('p')));
    746         onView(withId(R.id.textview)).check(hasSelection("hijk lmn\no"));
    747 
    748         onHandleView(com.android.internal.R.id.selection_end_handle)
    749                 .perform(dragHandle(textView, Handle.SELECTION_END, text.indexOf('i')));
    750         onView(withId(R.id.textview)).check(hasSelection("hijk"));
    751     }
    752 
    753     @Test
    754     public void testSelectionHandles_visibleEvenWithEmptyMenu() {
    755         ((TextView) mActivity.findViewById(R.id.textview)).setCustomSelectionActionModeCallback(
    756                 new ActionMode.Callback() {
    757                     @Override
    758                     public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    759                         menu.clear();
    760                         return true;
    761                     }
    762 
    763                     @Override
    764                     public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    765                         menu.clear();
    766                         return true;
    767                     }
    768 
    769                     @Override
    770                     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    771                         return false;
    772                     }
    773 
    774                     @Override
    775                     public void onDestroyActionMode(ActionMode mode) {}
    776                 });
    777         final String text = "abcd efg hijk lmn";
    778         onView(withId(R.id.textview)).perform(replaceText(text));
    779 
    780         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('f')));
    781 
    782         onHandleView(com.android.internal.R.id.selection_start_handle)
    783                 .check(matches(isDisplayed()));
    784         onHandleView(com.android.internal.R.id.selection_end_handle)
    785                 .check(matches(isDisplayed()));
    786     }
    787 
    788     @Test
    789     public void testSetSelectionAndActionMode() throws Throwable {
    790         final TextView textView = mActivity.findViewById(R.id.textview);
    791         final ActionMode.Callback amCallback = mock(ActionMode.Callback.class);
    792         when(amCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class)))
    793                 .thenReturn(true);
    794         when(amCallback.onPrepareActionMode(any(ActionMode.class), any(Menu.class)))
    795                 .thenReturn(true);
    796         textView.setCustomSelectionActionModeCallback(amCallback);
    797 
    798         final String text = "abc def";
    799         onView(withId(R.id.textview)).perform(replaceText(text));
    800         mActivityRule.runOnUiThread(
    801                 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
    802         mInstrumentation.waitForIdleSync();
    803         // Don't automatically start action mode.
    804         verify(amCallback, never()).onCreateActionMode(any(ActionMode.class), any(Menu.class));
    805         // Make sure that "Select All" is included in the selection action mode when the entire text
    806         // is not selected.
    807         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
    808         sleepForFloatingToolbarPopup();
    809         assertFloatingToolbarIsDisplayed();
    810         // Changing the selection range by API should not interrupt the selection action mode.
    811         mActivityRule.runOnUiThread(
    812                 () -> Selection.setSelection((Spannable) textView.getText(), 0, 3));
    813         mInstrumentation.waitForIdleSync();
    814         sleepForFloatingToolbarPopup();
    815         assertFloatingToolbarIsDisplayed();
    816         assertFloatingToolbarContainsItem(
    817                 mActivity.getString(com.android.internal.R.string.selectAll));
    818         // Make sure that "Select All" is no longer included when the entire text is selected by
    819         // API.
    820         mActivityRule.runOnUiThread(
    821                 () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length()));
    822         mInstrumentation.waitForIdleSync();
    823 
    824         sleepForFloatingToolbarPopup();
    825         assertFloatingToolbarIsDisplayed();
    826         assertFloatingToolbarDoesNotContainItem(
    827                 mActivity.getString(com.android.internal.R.string.selectAll));
    828         // Make sure that shrinking the selection range to cursor (an empty range) by API
    829         // terminates selection action mode and does not trigger the insertion action mode.
    830         mActivityRule.runOnUiThread(
    831                 () -> Selection.setSelection((Spannable) textView.getText(), 0));
    832         mInstrumentation.waitForIdleSync();
    833 
    834         // Make sure that user click can trigger the insertion action mode.
    835         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.length()));
    836         onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
    837         sleepForFloatingToolbarPopup();
    838         assertFloatingToolbarIsDisplayed();
    839         // Make sure that an existing insertion action mode keeps alive after the insertion point is
    840         // moved by API.
    841         mActivityRule.runOnUiThread(
    842                 () -> Selection.setSelection((Spannable) textView.getText(), 0));
    843         mInstrumentation.waitForIdleSync();
    844 
    845         sleepForFloatingToolbarPopup();
    846         assertFloatingToolbarIsDisplayed();
    847         assertFloatingToolbarDoesNotContainItem(
    848                 mActivity.getString(com.android.internal.R.string.copy));
    849         // Make sure that selection action mode is started after selection is created by API when
    850         // insertion action mode is active.
    851         mActivityRule.runOnUiThread(
    852                 () -> Selection.setSelection((Spannable) textView.getText(), 1, text.length()));
    853         mInstrumentation.waitForIdleSync();
    854 
    855         sleepForFloatingToolbarPopup();
    856         assertFloatingToolbarIsDisplayed();
    857         assertFloatingToolbarContainsItem(
    858                 mActivity.getString(com.android.internal.R.string.copy));
    859     }
    860 
    861     @Test
    862     public void testTransientState() throws Throwable {
    863         final String text = "abc def";
    864         onView(withId(R.id.textview)).perform(replaceText(text));
    865 
    866         final TextView textView = mActivity.findViewById(R.id.textview);
    867         assertFalse(textView.hasTransientState());
    868 
    869         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('b')));
    870         // hasTransientState should return true when user generated selection is active.
    871         assertTrue(textView.hasTransientState());
    872         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('d')));
    873         // hasTransientState should return false as the selection has been cleared.
    874         assertFalse(textView.hasTransientState());
    875         mActivityRule.runOnUiThread(
    876                 () -> Selection.setSelection((Spannable) textView.getText(), 0, text.length()));
    877         mInstrumentation.waitForIdleSync();
    878 
    879         // hasTransientState should return false when selection is created by API.
    880         assertFalse(textView.hasTransientState());
    881     }
    882 
    883     @Test
    884     public void testResetMenuItemTitle() throws Throwable {
    885         mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null);
    886         final TextView textView = mActivity.findViewById(R.id.textview);
    887         final int itemId = 1;
    888         final String title1 = " AFIGBO";
    889         final int index = title1.indexOf('I');
    890         final String title2 = title1.substring(index);
    891         final String[] title = new String[]{title1};
    892         mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback(
    893                 new ActionMode.Callback() {
    894                     @Override
    895                     public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
    896                         return true;
    897                     }
    898 
    899                     @Override
    900                     public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
    901                         menu.removeItem(itemId);
    902                         menu.add(Menu.NONE /* group */, itemId, 0 /* order */, title[0]);
    903                         return true;
    904                     }
    905 
    906                     @Override
    907                     public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
    908                         return false;
    909                     }
    910 
    911                     @Override
    912                     public void onDestroyActionMode(ActionMode actionMode) {
    913                     }
    914                 }));
    915         mInstrumentation.waitForIdleSync();
    916 
    917         onView(withId(R.id.textview)).perform(replaceText(title1));
    918         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(index));
    919         sleepForFloatingToolbarPopup();
    920         assertFloatingToolbarContainsItem(title1);
    921 
    922         // Change the menu item title.
    923         title[0] = title2;
    924         // Change the selection to invalidate the action mode without restarting it.
    925         onHandleView(com.android.internal.R.id.selection_start_handle)
    926                 .perform(dragHandle(textView, Handle.SELECTION_START, index));
    927         sleepForFloatingToolbarPopup();
    928         assertFloatingToolbarContainsItem(title2);
    929     }
    930 
    931     @Test
    932     public void testAssistItemIsAtIndexZero() throws Throwable {
    933         useSystemDefaultTextClassifier();
    934         final TextView textView = mActivity.findViewById(R.id.textview);
    935         mActivityRule.runOnUiThread(() -> textView.setCustomSelectionActionModeCallback(
    936                 new ActionMode.Callback() {
    937                     @Override
    938                     public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
    939                         // Create another item at order position 0 to confirm that it will never be
    940                         // placed before the textAssist item.
    941                         menu.add(Menu.NONE, 0 /* id */, 0 /* order */, "Test");
    942                         return true;
    943                     }
    944 
    945                     @Override
    946                     public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
    947                         return true;
    948                     }
    949 
    950                     @Override
    951                     public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
    952                         return false;
    953                     }
    954 
    955                     @Override
    956                     public void onDestroyActionMode(ActionMode actionMode) {
    957                     }
    958                 }));
    959         mInstrumentation.waitForIdleSync();
    960         final String text = "droid (at) android.com";
    961 
    962         onView(withId(R.id.textview)).perform(replaceText(text));
    963         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@')));
    964         sleepForFloatingToolbarPopup();
    965         assertFloatingToolbarItemIndex(android.R.id.textAssist, 0);
    966     }
    967 
    968     @Test
    969     public void testNoAssistItemForPasswordField() throws Throwable {
    970         useSystemDefaultTextClassifier();
    971         final TextView textView = mActivity.findViewById(R.id.textview);
    972         mActivityRule.runOnUiThread(() -> {
    973             textView.setInputType(
    974                     InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
    975         });
    976         mInstrumentation.waitForIdleSync();
    977         final String password = "afigbo (at) android.com";
    978 
    979         onView(withId(R.id.textview)).perform(replaceText(password));
    980         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(password.indexOf('@')));
    981         sleepForFloatingToolbarPopup();
    982         assertFloatingToolbarDoesNotContainItem(android.R.id.textAssist);
    983     }
    984 
    985     @Test
    986     public void testSelectionMetricsLogger_noAbandonAfterCopy() throws Throwable {
    987         final List<SelectionEvent> selectionEvents = new ArrayList<>();
    988         final TextClassifier classifier = new TextClassifier() {
    989             @Override
    990             public void onSelectionEvent(SelectionEvent event) {
    991                 selectionEvents.add(event);
    992             }
    993         };
    994         final TextView textView = mActivity.findViewById(R.id.textview);
    995         mActivityRule.runOnUiThread(() -> textView.setTextClassifier(classifier));
    996         mInstrumentation.waitForIdleSync();
    997         final String text = "andyroid (at) android.com";
    998 
    999         onView(withId(R.id.textview)).perform(replaceText(text));
   1000         onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('@')));
   1001         sleepForFloatingToolbarPopup();
   1002         clickFloatingToolbarItem(mActivity.getString(com.android.internal.R.string.copy));
   1003         mInstrumentation.waitForIdleSync();
   1004 
   1005         final SelectionEvent lastEvent = selectionEvents.get(selectionEvents.size() - 1);
   1006         assertEquals(SelectionEvent.ACTION_COPY, lastEvent.getEventType());
   1007     }
   1008 
   1009     @Test
   1010     public void testPastePlainText_menuAction() {
   1011         initializeClipboardWithText(TextStyle.STYLED);
   1012 
   1013         onView(withId(R.id.textview)).perform(replaceText(""));
   1014         onView(withId(R.id.textview)).perform(longClick());
   1015         sleepForFloatingToolbarPopup();
   1016         clickFloatingToolbarItem(
   1017                 mActivity.getString(com.android.internal.R.string.paste_as_plain_text));
   1018         mInstrumentation.waitForIdleSync();
   1019 
   1020         onView(withId(R.id.textview)).check(matches(withText("styledtext")));
   1021         onView(withId(R.id.textview)).check(doesNotHaveStyledText());
   1022     }
   1023 
   1024     @Test
   1025     public void testPastePlainText_noMenuItemForPlainText() {
   1026         initializeClipboardWithText(TextStyle.PLAIN);
   1027 
   1028         onView(withId(R.id.textview)).perform(replaceText(""));
   1029         onView(withId(R.id.textview)).perform(longClick());
   1030         sleepForFloatingToolbarPopup();
   1031 
   1032         assertFloatingToolbarDoesNotContainItem(
   1033                 mActivity.getString(com.android.internal.R.string.paste_as_plain_text));
   1034     }
   1035 
   1036     private void useSystemDefaultTextClassifier() {
   1037         mActivity.getSystemService(TextClassificationManager.class).setTextClassifier(null);
   1038     }
   1039 
   1040     private void initializeClipboardWithText(TextStyle textStyle) {
   1041         final ClipData clip;
   1042         switch (textStyle) {
   1043             case STYLED:
   1044                 clip = ClipData.newHtmlText("html", "styledtext", "<b>styledtext</b>");
   1045                 break;
   1046             case PLAIN:
   1047                 clip = ClipData.newPlainText("plain", "plaintext");
   1048                 break;
   1049             default:
   1050                 throw new IllegalArgumentException("Invalid text style");
   1051         }
   1052         mActivity.getWindow().getDecorView().post(() ->
   1053                 mActivity.getSystemService(ClipboardManager.class).setPrimaryClip(clip));
   1054         mInstrumentation.waitForIdleSync();
   1055     }
   1056 
   1057     private enum TextStyle {
   1058         PLAIN, STYLED
   1059     }
   1060 }
   1061