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.widget.cts;
     18 
     19 import static org.junit.Assert.assertArrayEquals;
     20 import static org.junit.Assert.assertEquals;
     21 import static org.junit.Assert.assertFalse;
     22 import static org.junit.Assert.assertNotEquals;
     23 import static org.junit.Assert.assertNotNull;
     24 import static org.junit.Assert.assertNull;
     25 import static org.junit.Assert.assertSame;
     26 import static org.junit.Assert.assertTrue;
     27 import static org.junit.Assert.fail;
     28 import static org.mockito.Matchers.any;
     29 import static org.mockito.Matchers.eq;
     30 import static org.mockito.Matchers.refEq;
     31 import static org.mockito.Mockito.doAnswer;
     32 import static org.mockito.Mockito.doCallRealMethod;
     33 import static org.mockito.Mockito.doNothing;
     34 import static org.mockito.Mockito.mock;
     35 import static org.mockito.Mockito.never;
     36 import static org.mockito.Mockito.reset;
     37 import static org.mockito.Mockito.spy;
     38 import static org.mockito.Mockito.times;
     39 import static org.mockito.Mockito.verify;
     40 import static org.mockito.Mockito.verifyNoMoreInteractions;
     41 import static org.mockito.Mockito.verifyZeroInteractions;
     42 import static org.mockito.Mockito.when;
     43 
     44 import android.app.Activity;
     45 import android.app.Instrumentation;
     46 import android.app.Instrumentation.ActivityMonitor;
     47 import android.content.Context;
     48 import android.content.Intent;
     49 import android.content.pm.PackageManager;
     50 import android.content.res.ColorStateList;
     51 import android.content.res.Configuration;
     52 import android.content.res.Resources;
     53 import android.content.res.Resources.NotFoundException;
     54 import android.graphics.BlendMode;
     55 import android.graphics.Canvas;
     56 import android.graphics.Color;
     57 import android.graphics.Paint;
     58 import android.graphics.Paint.FontMetricsInt;
     59 import android.graphics.Path;
     60 import android.graphics.Point;
     61 import android.graphics.PorterDuff;
     62 import android.graphics.Rect;
     63 import android.graphics.RectF;
     64 import android.graphics.Typeface;
     65 import android.graphics.drawable.BitmapDrawable;
     66 import android.graphics.drawable.ColorDrawable;
     67 import android.graphics.drawable.Drawable;
     68 import android.icu.lang.UCharacter;
     69 import android.net.Uri;
     70 import android.os.Bundle;
     71 import android.os.Handler;
     72 import android.os.LocaleList;
     73 import android.os.Looper;
     74 import android.os.Parcelable;
     75 import android.os.SystemClock;
     76 import android.text.Editable;
     77 import android.text.InputFilter;
     78 import android.text.InputType;
     79 import android.text.Layout;
     80 import android.text.PrecomputedText;
     81 import android.text.Selection;
     82 import android.text.Spannable;
     83 import android.text.SpannableString;
     84 import android.text.SpannableStringBuilder;
     85 import android.text.Spanned;
     86 import android.text.TextDirectionHeuristics;
     87 import android.text.TextPaint;
     88 import android.text.TextUtils;
     89 import android.text.TextUtils.TruncateAt;
     90 import android.text.TextWatcher;
     91 import android.text.method.ArrowKeyMovementMethod;
     92 import android.text.method.DateKeyListener;
     93 import android.text.method.DateTimeKeyListener;
     94 import android.text.method.DialerKeyListener;
     95 import android.text.method.DigitsKeyListener;
     96 import android.text.method.KeyListener;
     97 import android.text.method.LinkMovementMethod;
     98 import android.text.method.MovementMethod;
     99 import android.text.method.PasswordTransformationMethod;
    100 import android.text.method.QwertyKeyListener;
    101 import android.text.method.SingleLineTransformationMethod;
    102 import android.text.method.TextKeyListener;
    103 import android.text.method.TextKeyListener.Capitalize;
    104 import android.text.method.TimeKeyListener;
    105 import android.text.method.TransformationMethod;
    106 import android.text.style.ClickableSpan;
    107 import android.text.style.ImageSpan;
    108 import android.text.style.URLSpan;
    109 import android.text.style.UnderlineSpan;
    110 import android.text.util.Linkify;
    111 import android.util.AttributeSet;
    112 import android.util.DisplayMetrics;
    113 import android.util.SparseArray;
    114 import android.util.TypedValue;
    115 import android.view.ActionMode;
    116 import android.view.ContextMenu;
    117 import android.view.Gravity;
    118 import android.view.InputDevice;
    119 import android.view.KeyEvent;
    120 import android.view.LayoutInflater;
    121 import android.view.Menu;
    122 import android.view.MotionEvent;
    123 import android.view.PointerIcon;
    124 import android.view.View;
    125 import android.view.ViewConfiguration;
    126 import android.view.ViewGroup;
    127 import android.view.ViewGroup.LayoutParams;
    128 import android.view.accessibility.AccessibilityNodeInfo;
    129 import android.view.inputmethod.BaseInputConnection;
    130 import android.view.inputmethod.CompletionInfo;
    131 import android.view.inputmethod.CorrectionInfo;
    132 import android.view.inputmethod.EditorInfo;
    133 import android.view.inputmethod.ExtractedText;
    134 import android.view.inputmethod.ExtractedTextRequest;
    135 import android.view.inputmethod.InputConnection;
    136 import android.view.textclassifier.TextClassifier;
    137 import android.view.textclassifier.TextSelection;
    138 import android.widget.EditText;
    139 import android.widget.FrameLayout;
    140 import android.widget.LinearLayout;
    141 import android.widget.Scroller;
    142 import android.widget.TextView;
    143 import android.widget.TextView.BufferType;
    144 import android.widget.cts.util.TestUtils;
    145 
    146 import androidx.annotation.IntDef;
    147 import androidx.annotation.Nullable;
    148 import androidx.test.InstrumentationRegistry;
    149 import androidx.test.annotation.UiThreadTest;
    150 import androidx.test.filters.MediumTest;
    151 import androidx.test.filters.SmallTest;
    152 import androidx.test.rule.ActivityTestRule;
    153 import androidx.test.runner.AndroidJUnit4;
    154 
    155 import com.android.compatibility.common.util.CtsKeyEventUtil;
    156 import com.android.compatibility.common.util.CtsTouchUtils;
    157 import com.android.compatibility.common.util.PollingCheck;
    158 import com.android.compatibility.common.util.WidgetTestUtils;
    159 
    160 import org.junit.Before;
    161 import org.junit.Rule;
    162 import org.junit.Test;
    163 import org.junit.runner.RunWith;
    164 import org.mockito.invocation.InvocationOnMock;
    165 import org.xmlpull.v1.XmlPullParserException;
    166 
    167 import static java.lang.annotation.RetentionPolicy.SOURCE;
    168 
    169 import java.io.IOException;
    170 import java.lang.annotation.Retention;
    171 import java.util.Arrays;
    172 import java.util.Locale;
    173 
    174 /**
    175  * Test {@link TextView}.
    176  */
    177 @MediumTest
    178 @RunWith(AndroidJUnit4.class)
    179 public class TextViewTest {
    180     private Instrumentation mInstrumentation;
    181     private Activity mActivity;
    182     private TextView mTextView;
    183     private TextView mSecondTextView;
    184 
    185     private static final String LONG_TEXT = "This is a really long string which exceeds "
    186             + "the width of the view. New devices have a much larger screen which "
    187             + "actually enables long strings to be displayed with no fading. "
    188             + "I have made this string longer to fix this case. If you are correcting "
    189             + "this text, I would love to see the kind of devices you guys now use!";
    190     private static final long TIMEOUT = 5000;
    191 
    192     private static final int SMARTSELECT_START = 0;
    193     private static final int SMARTSELECT_END = 40;
    194     private static final TextClassifier FAKE_TEXT_CLASSIFIER = new TextClassifier() {
    195         @Override
    196         public TextSelection suggestSelection(TextSelection.Request request) {
    197             return new TextSelection.Builder(SMARTSELECT_START, SMARTSELECT_END).build();
    198         }
    199     };
    200     private static final int CLICK_TIMEOUT = ViewConfiguration.getDoubleTapTimeout() + 50;
    201 
    202     private CharSequence mTransformedText;
    203     private Handler mHandler = new Handler(Looper.getMainLooper());
    204 
    205     @Rule
    206     public ActivityTestRule<TextViewCtsActivity> mActivityRule =
    207             new ActivityTestRule<>(TextViewCtsActivity.class);
    208 
    209     @Before
    210     public void setup() {
    211         mInstrumentation = InstrumentationRegistry.getInstrumentation();
    212         mActivity = mActivityRule.getActivity();
    213         PollingCheck.waitFor(mActivity::hasWindowFocus);
    214     }
    215 
    216     /**
    217      * Promotes the TextView to editable and places focus in it to allow simulated typing. Used in
    218      * test methods annotated with {@link UiThreadTest}.
    219      */
    220     private void initTextViewForTyping() {
    221         mTextView = findTextView(R.id.textview_text);
    222         mTextView.setKeyListener(QwertyKeyListener.getInstance(false, Capitalize.NONE));
    223         mTextView.setText("", BufferType.EDITABLE);
    224         mTextView.requestFocus();
    225         // Disable smart selection
    226         mTextView.setTextClassifier(TextClassifier.NO_OP);
    227     }
    228 
    229     /**
    230      * Used in test methods that can not entirely be run on the UiThread (e.g: tests that need to
    231      * emulate touches and/or key presses).
    232      */
    233     private void initTextViewForTypingOnUiThread() throws Throwable {
    234         mActivityRule.runOnUiThread(this::initTextViewForTyping);
    235         mInstrumentation.waitForIdleSync();
    236     }
    237 
    238     @UiThreadTest
    239     @Test
    240     public void testConstructorOnUiThread() {
    241         verifyConstructor();
    242     }
    243 
    244     @Test
    245     public void testConstructorOffUiThread() {
    246         verifyConstructor();
    247     }
    248 
    249     private void verifyConstructor() {
    250         new TextView(mActivity);
    251         new TextView(mActivity, null);
    252         new TextView(mActivity, null, android.R.attr.textViewStyle);
    253         new TextView(mActivity, null, 0, android.R.style.Widget_DeviceDefault_TextView);
    254         new TextView(mActivity, null, 0, android.R.style.Widget_DeviceDefault_Light_TextView);
    255         new TextView(mActivity, null, 0, android.R.style.Widget_Material_TextView);
    256         new TextView(mActivity, null, 0, android.R.style.Widget_Material_Light_TextView);
    257     }
    258 
    259     @UiThreadTest
    260     @Test
    261     public void testAccessText() {
    262         TextView tv = findTextView(R.id.textview_text);
    263 
    264         String expected = mActivity.getResources().getString(R.string.text_view_hello);
    265         tv.setText(expected);
    266         assertEquals(expected, tv.getText().toString());
    267 
    268         tv.setText(null);
    269         assertEquals("", tv.getText().toString());
    270     }
    271 
    272     @UiThreadTest
    273     @Test
    274     public void testGetLineHeight() {
    275         mTextView = new TextView(mActivity);
    276         assertTrue(mTextView.getLineHeight() > 0);
    277 
    278         mTextView.setLineSpacing(1.2f, 1.5f);
    279         assertTrue(mTextView.getLineHeight() > 0);
    280     }
    281 
    282     @Test
    283     public void testGetLayout() throws Throwable {
    284         mActivityRule.runOnUiThread(() -> {
    285             mTextView = findTextView(R.id.textview_text);
    286             mTextView.setGravity(Gravity.CENTER);
    287         });
    288         mInstrumentation.waitForIdleSync();
    289         assertNotNull(mTextView.getLayout());
    290 
    291         TestLayoutRunnable runnable = new TestLayoutRunnable(mTextView) {
    292             public void run() {
    293                 // change the text of TextView.
    294                 mTextView.setText("Hello, Android!");
    295                 saveLayout();
    296             }
    297         };
    298         mActivityRule.runOnUiThread(runnable);
    299         mInstrumentation.waitForIdleSync();
    300         assertNull(runnable.getLayout());
    301         assertNotNull(mTextView.getLayout());
    302     }
    303 
    304     @Test
    305     public void testAccessKeyListener() throws Throwable {
    306         mActivityRule.runOnUiThread(() -> mTextView = findTextView(R.id.textview_text));
    307         mInstrumentation.waitForIdleSync();
    308 
    309         assertNull(mTextView.getKeyListener());
    310 
    311         final KeyListener digitsKeyListener = DigitsKeyListener.getInstance();
    312 
    313         mActivityRule.runOnUiThread(() -> mTextView.setKeyListener(digitsKeyListener));
    314         mInstrumentation.waitForIdleSync();
    315         assertSame(digitsKeyListener, mTextView.getKeyListener());
    316 
    317         final QwertyKeyListener qwertyKeyListener
    318                 = QwertyKeyListener.getInstance(false, Capitalize.NONE);
    319         mActivityRule.runOnUiThread(() -> mTextView.setKeyListener(qwertyKeyListener));
    320         mInstrumentation.waitForIdleSync();
    321         assertSame(qwertyKeyListener, mTextView.getKeyListener());
    322     }
    323 
    324     @Test
    325     public void testAccessMovementMethod() throws Throwable {
    326         final CharSequence LONG_TEXT = "Scrolls the specified widget to the specified "
    327                 + "coordinates, except constrains the X scrolling position to the horizontal "
    328                 + "regions of the text that will be visible after scrolling to "
    329                 + "the specified Y position.";
    330         final int selectionStart = 10;
    331         final int selectionEnd = LONG_TEXT.length();
    332         final MovementMethod movementMethod = ArrowKeyMovementMethod.getInstance();
    333         mActivityRule.runOnUiThread(() -> {
    334             mTextView = findTextView(R.id.textview_text);
    335             mTextView.setMovementMethod(movementMethod);
    336             mTextView.setText(LONG_TEXT, BufferType.EDITABLE);
    337             Selection.setSelection((Editable) mTextView.getText(),
    338                     selectionStart, selectionEnd);
    339             mTextView.requestFocus();
    340         });
    341         mInstrumentation.waitForIdleSync();
    342 
    343         assertSame(movementMethod, mTextView.getMovementMethod());
    344         assertEquals(selectionStart, Selection.getSelectionStart(mTextView.getText()));
    345         assertEquals(selectionEnd, Selection.getSelectionEnd(mTextView.getText()));
    346         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_SHIFT_LEFT,
    347                 KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_DPAD_UP);
    348         // the selection has been removed.
    349         assertEquals(selectionStart, Selection.getSelectionStart(mTextView.getText()));
    350         assertEquals(selectionStart, Selection.getSelectionEnd(mTextView.getText()));
    351 
    352         mActivityRule.runOnUiThread(() -> {
    353             mTextView.setMovementMethod(null);
    354             Selection.setSelection((Editable) mTextView.getText(),
    355                     selectionStart, selectionEnd);
    356             mTextView.requestFocus();
    357         });
    358         mInstrumentation.waitForIdleSync();
    359 
    360         assertNull(mTextView.getMovementMethod());
    361         assertEquals(selectionStart, Selection.getSelectionStart(mTextView.getText()));
    362         assertEquals(selectionEnd, Selection.getSelectionEnd(mTextView.getText()));
    363         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_SHIFT_LEFT,
    364                 KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_DPAD_UP);
    365         // the selection will not be changed.
    366         assertEquals(selectionStart, Selection.getSelectionStart(mTextView.getText()));
    367         assertEquals(selectionEnd, Selection.getSelectionEnd(mTextView.getText()));
    368     }
    369 
    370     @UiThreadTest
    371     @Test
    372     public void testLength() {
    373         mTextView = findTextView(R.id.textview_text);
    374 
    375         String content = "This is content";
    376         mTextView.setText(content);
    377         assertEquals(content.length(), mTextView.length());
    378 
    379         mTextView.setText("");
    380         assertEquals(0, mTextView.length());
    381 
    382         mTextView.setText(null);
    383         assertEquals(0, mTextView.length());
    384     }
    385 
    386     @UiThreadTest
    387     @Test
    388     public void testAccessGravity() {
    389         mActivity.setContentView(R.layout.textview_gravity);
    390 
    391         mTextView = findTextView(R.id.gravity_default);
    392         assertEquals(Gravity.TOP | Gravity.START, mTextView.getGravity());
    393 
    394         mTextView = findTextView(R.id.gravity_bottom);
    395         assertEquals(Gravity.BOTTOM | Gravity.START, mTextView.getGravity());
    396 
    397         mTextView = findTextView(R.id.gravity_right);
    398         assertEquals(Gravity.TOP | Gravity.RIGHT, mTextView.getGravity());
    399 
    400         mTextView = findTextView(R.id.gravity_center);
    401         assertEquals(Gravity.CENTER, mTextView.getGravity());
    402 
    403         mTextView = findTextView(R.id.gravity_fill);
    404         assertEquals(Gravity.FILL, mTextView.getGravity());
    405 
    406         mTextView = findTextView(R.id.gravity_center_vertical_right);
    407         assertEquals(Gravity.CENTER_VERTICAL | Gravity.RIGHT, mTextView.getGravity());
    408 
    409         mTextView.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL);
    410         assertEquals(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, mTextView.getGravity());
    411         mTextView.setGravity(Gravity.FILL);
    412         assertEquals(Gravity.FILL, mTextView.getGravity());
    413         mTextView.setGravity(Gravity.CENTER);
    414         assertEquals(Gravity.CENTER, mTextView.getGravity());
    415 
    416         mTextView.setGravity(Gravity.NO_GRAVITY);
    417         assertEquals(Gravity.TOP | Gravity.START, mTextView.getGravity());
    418 
    419         mTextView.setGravity(Gravity.RIGHT);
    420         assertEquals(Gravity.TOP | Gravity.RIGHT, mTextView.getGravity());
    421 
    422         mTextView.setGravity(Gravity.FILL_VERTICAL);
    423         assertEquals(Gravity.FILL_VERTICAL | Gravity.START, mTextView.getGravity());
    424 
    425         //test negative input value.
    426         mTextView.setGravity(-1);
    427         assertEquals(-1, mTextView.getGravity());
    428     }
    429 
    430     @Retention(SOURCE)
    431     @IntDef({EditorInfo.IME_ACTION_UNSPECIFIED, EditorInfo.IME_ACTION_NONE,
    432             EditorInfo.IME_ACTION_GO, EditorInfo.IME_ACTION_SEARCH, EditorInfo.IME_ACTION_SEND,
    433             EditorInfo.IME_ACTION_NEXT, EditorInfo.IME_ACTION_DONE, EditorInfo.IME_ACTION_PREVIOUS})
    434     private @interface ImeOptionAction {}
    435 
    436     @Retention(SOURCE)
    437     @IntDef(flag = true,
    438             value = {EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING,
    439                     EditorInfo.IME_FLAG_NO_FULLSCREEN, EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS,
    440                     EditorInfo.IME_FLAG_NAVIGATE_NEXT, EditorInfo.IME_FLAG_NO_EXTRACT_UI,
    441                     EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION, EditorInfo.IME_FLAG_NO_ENTER_ACTION,
    442                     EditorInfo.IME_FLAG_FORCE_ASCII})
    443     private @interface ImeOptionFlags {}
    444 
    445     private static void assertImeOptions(TextView textView,
    446             @ImeOptionAction int expectedImeOptionAction,
    447             @ImeOptionFlags int expectedImeOptionFlags) {
    448         final int actualAction = textView.getImeOptions() & EditorInfo.IME_MASK_ACTION;
    449         final int actualFlags = textView.getImeOptions() & ~EditorInfo.IME_MASK_ACTION;
    450         assertEquals(expectedImeOptionAction, actualAction);
    451         assertEquals(expectedImeOptionFlags, actualFlags);
    452     }
    453 
    454     @UiThreadTest
    455     @Test
    456     public void testImeOptions() {
    457         mActivity.setContentView(R.layout.textview_imeoptions);
    458 
    459         // Test "normal" to be a synonym EditorInfo.IME_NULL
    460         assertEquals(EditorInfo.IME_NULL,
    461                 mActivity.<TextView>findViewById(R.id.textview_imeoption_normal).getImeOptions());
    462 
    463         // Test EditorInfo.IME_ACTION_*
    464         assertImeOptions(
    465                 mActivity.findViewById(R.id.textview_imeoption_action_unspecified),
    466                 EditorInfo.IME_ACTION_UNSPECIFIED, 0);
    467         assertImeOptions(
    468                 mActivity.findViewById(R.id.textview_imeoption_action_none),
    469                 EditorInfo.IME_ACTION_NONE, 0);
    470         assertImeOptions(
    471                 mActivity.findViewById(R.id.textview_imeoption_action_go),
    472                 EditorInfo.IME_ACTION_GO, 0);
    473         assertImeOptions(
    474                 mActivity.findViewById(R.id.textview_imeoption_action_search),
    475                 EditorInfo.IME_ACTION_SEARCH, 0);
    476         assertImeOptions(
    477                 mActivity.findViewById(R.id.textview_imeoption_action_send),
    478                 EditorInfo.IME_ACTION_SEND, 0);
    479         assertImeOptions(
    480                 mActivity.findViewById(R.id.textview_imeoption_action_next),
    481                 EditorInfo.IME_ACTION_NEXT, 0);
    482         assertImeOptions(
    483                 mActivity.findViewById(R.id.textview_imeoption_action_done),
    484                 EditorInfo.IME_ACTION_DONE, 0);
    485         assertImeOptions(
    486                 mActivity.findViewById(R.id.textview_imeoption_action_previous),
    487                 EditorInfo.IME_ACTION_PREVIOUS, 0);
    488 
    489         // Test EditorInfo.IME_FLAG_*
    490         assertImeOptions(
    491                 mActivity.findViewById(R.id.textview_imeoption_no_personalized_learning),
    492                 EditorInfo.IME_ACTION_UNSPECIFIED,
    493                 EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING);
    494         assertImeOptions(
    495                 mActivity.findViewById(R.id.textview_imeoption_no_fullscreen),
    496                 EditorInfo.IME_ACTION_UNSPECIFIED,
    497                 EditorInfo.IME_FLAG_NO_FULLSCREEN);
    498         assertImeOptions(
    499                 mActivity.findViewById(R.id.textview_imeoption_navigation_previous),
    500                 EditorInfo.IME_ACTION_UNSPECIFIED,
    501                 EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS);
    502         assertImeOptions(
    503                 mActivity.findViewById(R.id.textview_imeoption_navigation_next),
    504                 EditorInfo.IME_ACTION_UNSPECIFIED,
    505                 EditorInfo.IME_FLAG_NAVIGATE_NEXT);
    506         assertImeOptions(
    507                 mActivity.findViewById(R.id.textview_imeoption_no_extract_ui),
    508                 EditorInfo.IME_ACTION_UNSPECIFIED,
    509                 EditorInfo.IME_FLAG_NO_EXTRACT_UI);
    510         assertImeOptions(
    511                 mActivity.findViewById(R.id.textview_imeoption_no_accessory_action),
    512                 EditorInfo.IME_ACTION_UNSPECIFIED,
    513                 EditorInfo.IME_FLAG_NO_ACCESSORY_ACTION);
    514         assertImeOptions(
    515                 mActivity.findViewById(R.id.textview_imeoption_no_enter_action),
    516                 EditorInfo.IME_ACTION_UNSPECIFIED,
    517                 EditorInfo.IME_FLAG_NO_ENTER_ACTION);
    518         assertImeOptions(
    519                 mActivity.findViewById(R.id.textview_imeoption_force_ascii),
    520                 EditorInfo.IME_ACTION_UNSPECIFIED,
    521                 EditorInfo.IME_FLAG_FORCE_ASCII);
    522 
    523         // test action + multiple flags
    524         assertImeOptions(
    525                 mActivity.findViewById(
    526                         R.id.textview_imeoption_action_go_nagivate_next_no_extract_ui_force_ascii),
    527                 EditorInfo.IME_ACTION_GO,
    528                 EditorInfo.IME_FLAG_NAVIGATE_NEXT | EditorInfo.IME_FLAG_NO_EXTRACT_UI
    529                         | EditorInfo.IME_FLAG_FORCE_ASCII);
    530     }
    531 
    532     @Test
    533     public void testAccessAutoLinkMask() throws Throwable {
    534         mTextView = findTextView(R.id.textview_text);
    535         final CharSequence text1 =
    536                 new SpannableString("URL: http://www.google.com. mailto: account (at) gmail.com");
    537         mActivityRule.runOnUiThread(() -> {
    538             mTextView.setAutoLinkMask(Linkify.ALL);
    539             mTextView.setText(text1, BufferType.EDITABLE);
    540         });
    541         mInstrumentation.waitForIdleSync();
    542         assertEquals(Linkify.ALL, mTextView.getAutoLinkMask());
    543 
    544         Spannable spanString = (Spannable) mTextView.getText();
    545         URLSpan[] spans = spanString.getSpans(0, spanString.length(), URLSpan.class);
    546         assertNotNull(spans);
    547         assertEquals(2, spans.length);
    548         assertEquals("http://www.google.com", spans[0].getURL());
    549         assertEquals("mailto:account (at) gmail.com", spans[1].getURL());
    550 
    551         final CharSequence text2 =
    552             new SpannableString("name: Jack. tel: +41 44 800 8999");
    553         mActivityRule.runOnUiThread(() -> {
    554             mTextView.setAutoLinkMask(Linkify.PHONE_NUMBERS);
    555             mTextView.setText(text2, BufferType.EDITABLE);
    556         });
    557         mInstrumentation.waitForIdleSync();
    558         assertEquals(Linkify.PHONE_NUMBERS, mTextView.getAutoLinkMask());
    559 
    560         spanString = (Spannable) mTextView.getText();
    561         spans = spanString.getSpans(0, spanString.length(), URLSpan.class);
    562         assertNotNull(spans);
    563         assertEquals(1, spans.length);
    564         assertEquals("tel:+41448008999", spans[0].getURL());
    565 
    566         layout(R.layout.textview_autolink);
    567         // 1 for web, 2 for email, 4 for phone, 7 for all(web|email|phone)
    568         assertEquals(0, getAutoLinkMask(R.id.autolink_default));
    569         assertEquals(Linkify.WEB_URLS, getAutoLinkMask(R.id.autolink_web));
    570         assertEquals(Linkify.EMAIL_ADDRESSES, getAutoLinkMask(R.id.autolink_email));
    571         assertEquals(Linkify.PHONE_NUMBERS, getAutoLinkMask(R.id.autolink_phone));
    572         assertEquals(Linkify.ALL, getAutoLinkMask(R.id.autolink_all));
    573         assertEquals(Linkify.WEB_URLS | Linkify.EMAIL_ADDRESSES,
    574                 getAutoLinkMask(R.id.autolink_compound1));
    575         assertEquals(Linkify.WEB_URLS | Linkify.PHONE_NUMBERS,
    576                 getAutoLinkMask(R.id.autolink_compound2));
    577         assertEquals(Linkify.EMAIL_ADDRESSES | Linkify.PHONE_NUMBERS,
    578                 getAutoLinkMask(R.id.autolink_compound3));
    579         assertEquals(Linkify.PHONE_NUMBERS | Linkify.ALL,
    580                 getAutoLinkMask(R.id.autolink_compound4));
    581     }
    582 
    583     @UiThreadTest
    584     @Test
    585     public void testAccessTextSize() {
    586         DisplayMetrics metrics = mActivity.getResources().getDisplayMetrics();
    587 
    588         mTextView = new TextView(mActivity);
    589         mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 20f);
    590         assertEquals(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 20f, metrics),
    591                 mTextView.getTextSize(), 0.01f);
    592 
    593         mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20f);
    594         assertEquals(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20f, metrics),
    595                 mTextView.getTextSize(), 0.01f);
    596 
    597         mTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20f);
    598         assertEquals(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20f, metrics),
    599                 mTextView.getTextSize(), 0.01f);
    600 
    601         // setTextSize by default unit "sp"
    602         mTextView.setTextSize(20f);
    603         assertEquals(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20f, metrics),
    604                 mTextView.getTextSize(), 0.01f);
    605 
    606         mTextView.setTextSize(200f);
    607         assertEquals(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 200f, metrics),
    608                 mTextView.getTextSize(), 0.01f);
    609     }
    610 
    611     @UiThreadTest
    612     @Test
    613     public void testAccessTextColor() {
    614         mTextView = new TextView(mActivity);
    615 
    616         mTextView.setTextColor(Color.GREEN);
    617         assertEquals(Color.GREEN, mTextView.getCurrentTextColor());
    618         assertSame(ColorStateList.valueOf(Color.GREEN), mTextView.getTextColors());
    619 
    620         mTextView.setTextColor(Color.BLACK);
    621         assertEquals(Color.BLACK, mTextView.getCurrentTextColor());
    622         assertSame(ColorStateList.valueOf(Color.BLACK), mTextView.getTextColors());
    623 
    624         mTextView.setTextColor(Color.RED);
    625         assertEquals(Color.RED, mTextView.getCurrentTextColor());
    626         assertSame(ColorStateList.valueOf(Color.RED), mTextView.getTextColors());
    627 
    628         // using ColorStateList
    629         // normal
    630         ColorStateList colors = new ColorStateList(new int[][] {
    631                 new int[] { android.R.attr.state_focused}, new int[0] },
    632                 new int[] { Color.rgb(0, 255, 0), Color.BLACK });
    633         mTextView.setTextColor(colors);
    634         assertSame(colors, mTextView.getTextColors());
    635         assertEquals(Color.BLACK, mTextView.getCurrentTextColor());
    636 
    637         // exceptional
    638         try {
    639             mTextView.setTextColor(null);
    640             fail("Should thrown exception if the colors is null");
    641         } catch (NullPointerException e){
    642         }
    643     }
    644 
    645     @Test
    646     public void testGetTextColor() {
    647         // TODO: How to get a suitable TypedArray to test this method.
    648 
    649         try {
    650             TextView.getTextColor(mActivity, null, -1);
    651             fail("There should be a NullPointerException thrown out.");
    652         } catch (NullPointerException e) {
    653         }
    654     }
    655 
    656     @Test
    657     public void testAccessHighlightColor() throws Throwable {
    658         final TextView textView = (TextView) mActivity.findViewById(R.id.textview_text);
    659 
    660         mActivityRule.runOnUiThread(() -> {
    661             textView.setTextIsSelectable(true);
    662             textView.setText("abcd", BufferType.EDITABLE);
    663             textView.setHighlightColor(Color.BLUE);
    664         });
    665         mInstrumentation.waitForIdleSync();
    666 
    667         assertTrue(textView.isTextSelectable());
    668         assertEquals(Color.BLUE, textView.getHighlightColor());
    669 
    670         // Long click on the text selects all text and shows selection handlers. The view has an
    671         // attribute layout_width="wrap_content", so clicked location (the center of the view)
    672         // should be on the text.
    673         CtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mActivityRule, textView);
    674 
    675         // At this point the entire content of our TextView should be selected and highlighted
    676         // with blue. Now change the highlight to red while the selection is still on.
    677         mActivityRule.runOnUiThread(() -> textView.setHighlightColor(Color.RED));
    678         mInstrumentation.waitForIdleSync();
    679 
    680         assertEquals(Color.RED, textView.getHighlightColor());
    681         assertTrue(TextUtils.equals("abcd", textView.getText()));
    682 
    683         // Remove the selection
    684         mActivityRule.runOnUiThread(() -> Selection.removeSelection((Spannable) textView.getText()));
    685         mInstrumentation.waitForIdleSync();
    686 
    687         // And switch highlight to green after the selection has been removed
    688         mActivityRule.runOnUiThread(() -> textView.setHighlightColor(Color.GREEN));
    689         mInstrumentation.waitForIdleSync();
    690 
    691         assertEquals(Color.GREEN, textView.getHighlightColor());
    692         assertTrue(TextUtils.equals("abcd", textView.getText()));
    693     }
    694 
    695     @UiThreadTest
    696     @Test
    697     public void testSetShadowLayer() {
    698         // test values
    699         final MockTextView mockTextView = new MockTextView(mActivity);
    700 
    701         mockTextView.setShadowLayer(1.0f, 0.3f, 0.4f, Color.CYAN);
    702         assertEquals(Color.CYAN, mockTextView.getShadowColor());
    703         assertEquals(0.3f, mockTextView.getShadowDx(), 0.0f);
    704         assertEquals(0.4f, mockTextView.getShadowDy(), 0.0f);
    705         assertEquals(1.0f, mockTextView.getShadowRadius(), 0.0f);
    706 
    707         // shadow is placed to the left and below the text
    708         mockTextView.setShadowLayer(1.0f, 0.3f, 0.3f, Color.CYAN);
    709         assertTrue(mockTextView.isPaddingOffsetRequired());
    710         assertEquals(0, mockTextView.getLeftPaddingOffset());
    711         assertEquals(0, mockTextView.getTopPaddingOffset());
    712         assertEquals(1, mockTextView.getRightPaddingOffset());
    713         assertEquals(1, mockTextView.getBottomPaddingOffset());
    714 
    715         // shadow is placed to the right and above the text
    716         mockTextView.setShadowLayer(1.0f, -0.8f, -0.8f, Color.CYAN);
    717         assertTrue(mockTextView.isPaddingOffsetRequired());
    718         assertEquals(-1, mockTextView.getLeftPaddingOffset());
    719         assertEquals(-1, mockTextView.getTopPaddingOffset());
    720         assertEquals(0, mockTextView.getRightPaddingOffset());
    721         assertEquals(0, mockTextView.getBottomPaddingOffset());
    722 
    723         // no shadow
    724         mockTextView.setShadowLayer(0.0f, 0.0f, 0.0f, Color.CYAN);
    725         assertFalse(mockTextView.isPaddingOffsetRequired());
    726         assertEquals(0, mockTextView.getLeftPaddingOffset());
    727         assertEquals(0, mockTextView.getTopPaddingOffset());
    728         assertEquals(0, mockTextView.getRightPaddingOffset());
    729         assertEquals(0, mockTextView.getBottomPaddingOffset());
    730     }
    731 
    732     @UiThreadTest
    733     @Test
    734     public void testSetSelectAllOnFocus() {
    735         mActivity.setContentView(R.layout.textview_selectallonfocus);
    736         String content = "This is the content";
    737         String blank = "";
    738         mTextView = findTextView(R.id.selectAllOnFocus_default);
    739         mTextView.setText(blank, BufferType.SPANNABLE);
    740         // change the focus
    741         findTextView(R.id.selectAllOnFocus_dummy).requestFocus();
    742         assertFalse(mTextView.isFocused());
    743         mTextView.requestFocus();
    744         assertTrue(mTextView.isFocused());
    745 
    746         assertEquals(-1, mTextView.getSelectionStart());
    747         assertEquals(-1, mTextView.getSelectionEnd());
    748 
    749         mTextView.setText(content, BufferType.SPANNABLE);
    750         mTextView.setSelectAllOnFocus(true);
    751         // change the focus
    752         findTextView(R.id.selectAllOnFocus_dummy).requestFocus();
    753         assertFalse(mTextView.isFocused());
    754         mTextView.requestFocus();
    755         assertTrue(mTextView.isFocused());
    756 
    757         assertEquals(0, mTextView.getSelectionStart());
    758         assertEquals(content.length(), mTextView.getSelectionEnd());
    759 
    760         Selection.setSelection((Spannable) mTextView.getText(), 0);
    761         mTextView.setSelectAllOnFocus(false);
    762         // change the focus
    763         findTextView(R.id.selectAllOnFocus_dummy).requestFocus();
    764         assertFalse(mTextView.isFocused());
    765         mTextView.requestFocus();
    766         assertTrue(mTextView.isFocused());
    767 
    768         assertEquals(0, mTextView.getSelectionStart());
    769         assertEquals(0, mTextView.getSelectionEnd());
    770 
    771         mTextView.setText(blank, BufferType.SPANNABLE);
    772         mTextView.setSelectAllOnFocus(true);
    773         // change the focus
    774         findTextView(R.id.selectAllOnFocus_dummy).requestFocus();
    775         assertFalse(mTextView.isFocused());
    776         mTextView.requestFocus();
    777         assertTrue(mTextView.isFocused());
    778 
    779         assertEquals(0, mTextView.getSelectionStart());
    780         assertEquals(blank.length(), mTextView.getSelectionEnd());
    781 
    782         Selection.setSelection((Spannable) mTextView.getText(), 0);
    783         mTextView.setSelectAllOnFocus(false);
    784         // change the focus
    785         findTextView(R.id.selectAllOnFocus_dummy).requestFocus();
    786         assertFalse(mTextView.isFocused());
    787         mTextView.requestFocus();
    788         assertTrue(mTextView.isFocused());
    789 
    790         assertEquals(0, mTextView.getSelectionStart());
    791         assertEquals(0, mTextView.getSelectionEnd());
    792     }
    793 
    794     @UiThreadTest
    795     @Test
    796     public void testGetPaint() {
    797         mTextView = new TextView(mActivity);
    798         TextPaint tp = mTextView.getPaint();
    799         assertNotNull(tp);
    800 
    801         assertEquals(mTextView.getPaintFlags(), tp.getFlags());
    802     }
    803 
    804     @UiThreadTest
    805     @Test
    806     public void testAccessLinksClickable() {
    807         mActivity.setContentView(R.layout.textview_hint_linksclickable_freezestext);
    808 
    809         mTextView = findTextView(R.id.hint_linksClickable_freezesText_default);
    810         assertTrue(mTextView.getLinksClickable());
    811 
    812         mTextView = findTextView(R.id.linksClickable_true);
    813         assertTrue(mTextView.getLinksClickable());
    814 
    815         mTextView = findTextView(R.id.linksClickable_false);
    816         assertFalse(mTextView.getLinksClickable());
    817 
    818         mTextView.setLinksClickable(false);
    819         assertFalse(mTextView.getLinksClickable());
    820 
    821         mTextView.setLinksClickable(true);
    822         assertTrue(mTextView.getLinksClickable());
    823 
    824         assertNull(mTextView.getMovementMethod());
    825 
    826         final CharSequence text = new SpannableString("name: Jack. tel: +41 44 800 8999");
    827 
    828         mTextView.setAutoLinkMask(Linkify.PHONE_NUMBERS);
    829         mTextView.setText(text, BufferType.EDITABLE);
    830 
    831         // Movement method will be automatically set to LinkMovementMethod
    832         assertTrue(mTextView.getMovementMethod() instanceof LinkMovementMethod);
    833     }
    834 
    835     @UiThreadTest
    836     @Test
    837     public void testAccessHintTextColor() {
    838         mTextView = new TextView(mActivity);
    839         // using int values
    840         // normal
    841         mTextView.setHintTextColor(Color.GREEN);
    842         assertEquals(Color.GREEN, mTextView.getCurrentHintTextColor());
    843         assertSame(ColorStateList.valueOf(Color.GREEN), mTextView.getHintTextColors());
    844 
    845         mTextView.setHintTextColor(Color.BLUE);
    846         assertSame(ColorStateList.valueOf(Color.BLUE), mTextView.getHintTextColors());
    847         assertEquals(Color.BLUE, mTextView.getCurrentHintTextColor());
    848 
    849         mTextView.setHintTextColor(Color.RED);
    850         assertSame(ColorStateList.valueOf(Color.RED), mTextView.getHintTextColors());
    851         assertEquals(Color.RED, mTextView.getCurrentHintTextColor());
    852 
    853         // using ColorStateList
    854         // normal
    855         ColorStateList colors = new ColorStateList(new int[][] {
    856                 new int[] { android.R.attr.state_focused}, new int[0] },
    857                 new int[] { Color.rgb(0, 255, 0), Color.BLACK });
    858         mTextView.setHintTextColor(colors);
    859         assertSame(colors, mTextView.getHintTextColors());
    860         assertEquals(Color.BLACK, mTextView.getCurrentHintTextColor());
    861 
    862         // exceptional
    863         mTextView.setHintTextColor(null);
    864         assertNull(mTextView.getHintTextColors());
    865         assertEquals(mTextView.getCurrentTextColor(), mTextView.getCurrentHintTextColor());
    866     }
    867 
    868     @UiThreadTest
    869     @Test
    870     public void testAccessLinkTextColor() {
    871         mTextView = new TextView(mActivity);
    872         // normal
    873         mTextView.setLinkTextColor(Color.GRAY);
    874         assertSame(ColorStateList.valueOf(Color.GRAY), mTextView.getLinkTextColors());
    875         assertEquals(Color.GRAY, mTextView.getPaint().linkColor);
    876 
    877         mTextView.setLinkTextColor(Color.YELLOW);
    878         assertSame(ColorStateList.valueOf(Color.YELLOW), mTextView.getLinkTextColors());
    879         assertEquals(Color.YELLOW, mTextView.getPaint().linkColor);
    880 
    881         mTextView.setLinkTextColor(Color.WHITE);
    882         assertSame(ColorStateList.valueOf(Color.WHITE), mTextView.getLinkTextColors());
    883         assertEquals(Color.WHITE, mTextView.getPaint().linkColor);
    884 
    885         ColorStateList colors = new ColorStateList(new int[][] {
    886                 new int[] { android.R.attr.state_expanded}, new int[0] },
    887                 new int[] { Color.rgb(0, 255, 0), Color.BLACK });
    888         mTextView.setLinkTextColor(colors);
    889         assertSame(colors, mTextView.getLinkTextColors());
    890         assertEquals(Color.BLACK, mTextView.getPaint().linkColor);
    891 
    892         mTextView.setLinkTextColor(null);
    893         assertNull(mTextView.getLinkTextColors());
    894         assertEquals(Color.BLACK, mTextView.getPaint().linkColor);
    895     }
    896 
    897     @UiThreadTest
    898     @Test
    899     public void testAccessPaintFlags() {
    900         mTextView = new TextView(mActivity);
    901         assertEquals(Paint.DEV_KERN_TEXT_FLAG | Paint.EMBEDDED_BITMAP_TEXT_FLAG
    902                 | Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG, mTextView.getPaintFlags());
    903 
    904         mTextView.setPaintFlags(Paint.UNDERLINE_TEXT_FLAG | Paint.FAKE_BOLD_TEXT_FLAG);
    905         assertEquals(Paint.UNDERLINE_TEXT_FLAG | Paint.FAKE_BOLD_TEXT_FLAG,
    906                 mTextView.getPaintFlags());
    907 
    908         mTextView.setPaintFlags(Paint.STRIKE_THRU_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG);
    909         assertEquals(Paint.STRIKE_THRU_TEXT_FLAG | Paint.LINEAR_TEXT_FLAG,
    910                 mTextView.getPaintFlags());
    911     }
    912 
    913     @Test
    914     public void testHeight() throws Throwable {
    915         mTextView = findTextView(R.id.textview_text);
    916         final int originalHeight = mTextView.getHeight();
    917 
    918         // test setMaxHeight
    919         int newHeight = originalHeight + 1;
    920         setMaxHeight(newHeight);
    921         assertEquals(originalHeight, mTextView.getHeight());
    922         assertEquals(newHeight, mTextView.getMaxHeight());
    923 
    924         newHeight = originalHeight - 1;
    925         setMaxHeight(newHeight);
    926         assertEquals(newHeight, mTextView.getHeight());
    927         assertEquals(newHeight, mTextView.getMaxHeight());
    928 
    929         newHeight = -1;
    930         setMaxHeight(newHeight);
    931         assertEquals(0, mTextView.getHeight());
    932         assertEquals(newHeight, mTextView.getMaxHeight());
    933 
    934         newHeight = Integer.MAX_VALUE;
    935         setMaxHeight(newHeight);
    936         assertEquals(originalHeight, mTextView.getHeight());
    937         assertEquals(newHeight, mTextView.getMaxHeight());
    938 
    939         // test setMinHeight
    940         newHeight = originalHeight + 1;
    941         setMinHeight(newHeight);
    942         assertEquals(newHeight, mTextView.getHeight());
    943         assertEquals(newHeight, mTextView.getMinHeight());
    944 
    945         newHeight = originalHeight - 1;
    946         setMinHeight(newHeight);
    947         assertEquals(originalHeight, mTextView.getHeight());
    948         assertEquals(newHeight, mTextView.getMinHeight());
    949 
    950         newHeight = -1;
    951         setMinHeight(newHeight);
    952         assertEquals(originalHeight, mTextView.getHeight());
    953         assertEquals(newHeight, mTextView.getMinHeight());
    954 
    955         // reset min and max height
    956         setMinHeight(0);
    957         setMaxHeight(Integer.MAX_VALUE);
    958 
    959         // test setHeight
    960         newHeight = originalHeight + 1;
    961         setHeight(newHeight);
    962         assertEquals(newHeight, mTextView.getHeight());
    963         assertEquals(newHeight, mTextView.getMaxHeight());
    964         assertEquals(newHeight, mTextView.getMinHeight());
    965 
    966         newHeight = originalHeight - 1;
    967         setHeight(newHeight);
    968         assertEquals(newHeight, mTextView.getHeight());
    969         assertEquals(newHeight, mTextView.getMaxHeight());
    970         assertEquals(newHeight, mTextView.getMinHeight());
    971 
    972         newHeight = -1;
    973         setHeight(newHeight);
    974         assertEquals(0, mTextView.getHeight());
    975         assertEquals(newHeight, mTextView.getMaxHeight());
    976         assertEquals(newHeight, mTextView.getMinHeight());
    977 
    978         setHeight(originalHeight);
    979         assertEquals(originalHeight, mTextView.getHeight());
    980         assertEquals(originalHeight, mTextView.getMaxHeight());
    981         assertEquals(originalHeight, mTextView.getMinHeight());
    982 
    983         // setting max/min lines should cause getMaxHeight/getMinHeight to return -1
    984         setMaxLines(2);
    985         assertEquals("Setting maxLines should return -1 fir maxHeight",
    986                 -1, mTextView.getMaxHeight());
    987 
    988         setMinLines(1);
    989         assertEquals("Setting minLines should return -1 for minHeight",
    990                 -1, mTextView.getMinHeight());
    991     }
    992 
    993     @Test
    994     public void testSetMaxLines_toZero_shouldNotDisplayAnyLines() throws Throwable {
    995         mTextView = findTextView(R.id.textview_text);
    996         mActivityRule.runOnUiThread(() -> {
    997             mTextView.setPadding(0, 0, 0, 0);
    998             mTextView.setText("Single");
    999             mTextView.setMaxLines(0);
   1000         });
   1001         mInstrumentation.waitForIdleSync();
   1002 
   1003         final int expectedHeight = mTextView.getTotalPaddingBottom()
   1004                 + mTextView.getTotalPaddingTop();
   1005 
   1006         assertEquals(expectedHeight, mTextView.getHeight());
   1007 
   1008         mActivityRule.runOnUiThread(() -> mTextView.setText("Two\nLines"));
   1009         mInstrumentation.waitForIdleSync();
   1010         assertEquals(expectedHeight, mTextView.getHeight());
   1011 
   1012         mActivityRule.runOnUiThread(() -> mTextView.setTextIsSelectable(true));
   1013         mInstrumentation.waitForIdleSync();
   1014         assertEquals(expectedHeight, mTextView.getHeight());
   1015     }
   1016 
   1017     @Test
   1018     public void testWidth() throws Throwable {
   1019         mTextView = findTextView(R.id.textview_text);
   1020         int originalWidth = mTextView.getWidth();
   1021 
   1022         int newWidth = mTextView.getWidth() / 8;
   1023         setWidth(newWidth);
   1024         assertEquals(newWidth, mTextView.getWidth());
   1025         assertEquals(newWidth, mTextView.getMaxWidth());
   1026         assertEquals(newWidth, mTextView.getMinWidth());
   1027 
   1028         // Min Width
   1029         newWidth = originalWidth + 1;
   1030         setMinWidth(newWidth);
   1031         assertEquals(1, mTextView.getLineCount());
   1032         assertEquals(newWidth, mTextView.getWidth());
   1033         assertEquals(newWidth, mTextView.getMinWidth());
   1034 
   1035         newWidth = originalWidth - 1;
   1036         setMinWidth(originalWidth - 1);
   1037         assertEquals(2, mTextView.getLineCount());
   1038         assertEquals(newWidth, mTextView.getWidth());
   1039         assertEquals(newWidth, mTextView.getMinWidth());
   1040 
   1041         // Width
   1042         newWidth = originalWidth + 1;
   1043         setWidth(newWidth);
   1044         assertEquals(1, mTextView.getLineCount());
   1045         assertEquals(newWidth, mTextView.getWidth());
   1046         assertEquals(newWidth, mTextView.getMaxWidth());
   1047         assertEquals(newWidth, mTextView.getMinWidth());
   1048 
   1049         newWidth = originalWidth - 1;
   1050         setWidth(newWidth);
   1051         assertEquals(2, mTextView.getLineCount());
   1052         assertEquals(newWidth, mTextView.getWidth());
   1053         assertEquals(newWidth, mTextView.getMaxWidth());
   1054         assertEquals(newWidth, mTextView.getMinWidth());
   1055 
   1056         // setting ems should cause getMaxWidth/getMinWidth to return -1
   1057         setEms(1);
   1058         assertEquals("Setting ems should return -1 for maxWidth", -1, mTextView.getMaxWidth());
   1059         assertEquals("Setting ems should return -1 for maxWidth", -1, mTextView.getMinWidth());
   1060     }
   1061 
   1062     @Test
   1063     public void testSetMinEms() throws Throwable {
   1064         mTextView = findTextView(R.id.textview_text);
   1065         assertEquals(1, mTextView.getLineCount());
   1066 
   1067         final int originalWidth = mTextView.getWidth();
   1068         final int originalEms = originalWidth / mTextView.getLineHeight();
   1069 
   1070         setMinEms(originalEms + 1);
   1071         assertEquals((originalEms + 1) * mTextView.getLineHeight(), mTextView.getWidth());
   1072         assertEquals(-1, mTextView.getMinWidth());
   1073         assertEquals(originalEms + 1, mTextView.getMinEms());
   1074 
   1075         setMinEms(originalEms - 1);
   1076         assertEquals(originalWidth, mTextView.getWidth());
   1077         assertEquals(-1, mTextView.getMinWidth());
   1078         assertEquals(originalEms - 1, mTextView.getMinEms());
   1079 
   1080         setMinWidth(1);
   1081         assertEquals(-1, mTextView.getMinEms());
   1082     }
   1083 
   1084     @Test
   1085     public void testSetMaxEms() throws Throwable {
   1086         mTextView = findTextView(R.id.textview_text);
   1087         assertEquals(1, mTextView.getLineCount());
   1088 
   1089         final int originalWidth = mTextView.getWidth();
   1090         final int originalEms = originalWidth / mTextView.getLineHeight();
   1091 
   1092         setMaxEms(originalEms + 1);
   1093         assertEquals(1, mTextView.getLineCount());
   1094         assertEquals(originalWidth, mTextView.getWidth());
   1095         assertEquals(-1, mTextView.getMaxWidth());
   1096         assertEquals(originalEms + 1, mTextView.getMaxEms());
   1097 
   1098         setMaxEms(originalEms - 1);
   1099         assertTrue(1 < mTextView.getLineCount());
   1100         assertEquals((originalEms - 1) * mTextView.getLineHeight(), mTextView.getWidth());
   1101         assertEquals(-1, mTextView.getMaxWidth());
   1102         assertEquals(originalEms - 1, mTextView.getMaxEms());
   1103 
   1104         setMaxWidth(originalWidth);
   1105         assertEquals(-1, mTextView.getMaxEms());
   1106     }
   1107 
   1108     @Test
   1109     public void testSetEms() throws Throwable {
   1110         mTextView = findTextView(R.id.textview_text);
   1111         assertEquals("check height", 1, mTextView.getLineCount());
   1112         final int originalWidth = mTextView.getWidth();
   1113         final int originalEms = originalWidth / mTextView.getLineHeight();
   1114 
   1115         setEms(originalEms + 1);
   1116         assertEquals(1, mTextView.getLineCount());
   1117         assertEquals((originalEms + 1) * mTextView.getLineHeight(), mTextView.getWidth());
   1118         assertEquals(-1, mTextView.getMinWidth());
   1119         assertEquals(-1, mTextView.getMaxWidth());
   1120         assertEquals(originalEms + 1, mTextView.getMinEms());
   1121         assertEquals(originalEms + 1, mTextView.getMaxEms());
   1122 
   1123         setEms(originalEms - 1);
   1124         assertTrue((1 < mTextView.getLineCount()));
   1125         assertEquals((originalEms - 1) * mTextView.getLineHeight(), mTextView.getWidth());
   1126         assertEquals(-1, mTextView.getMinWidth());
   1127         assertEquals(-1, mTextView.getMaxWidth());
   1128         assertEquals(originalEms - 1, mTextView.getMinEms());
   1129         assertEquals(originalEms - 1, mTextView.getMaxEms());
   1130     }
   1131 
   1132     @Test
   1133     public void testSetLineSpacing() throws Throwable {
   1134         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
   1135         mInstrumentation.waitForIdleSync();
   1136         int originalLineHeight = mTextView.getLineHeight();
   1137 
   1138         // normal
   1139         float add = 1.2f;
   1140         float mult = 1.4f;
   1141         setLineSpacing(add, mult);
   1142         assertEquals(Math.round(originalLineHeight * mult + add), mTextView.getLineHeight());
   1143         add = 0.0f;
   1144         mult = 1.4f;
   1145         setLineSpacing(add, mult);
   1146         assertEquals(Math.round(originalLineHeight * mult + add), mTextView.getLineHeight());
   1147 
   1148         // abnormal
   1149         add = -1.2f;
   1150         mult = 1.4f;
   1151         setLineSpacing(add, mult);
   1152         assertEquals(Math.round(originalLineHeight * mult + add), mTextView.getLineHeight());
   1153         add = -1.2f;
   1154         mult = -1.4f;
   1155         setLineSpacing(add, mult);
   1156         assertEquals(Math.round(originalLineHeight * mult + add), mTextView.getLineHeight());
   1157         add = 1.2f;
   1158         mult = 0.0f;
   1159         setLineSpacing(add, mult);
   1160         assertEquals(Math.round(originalLineHeight * mult + add), mTextView.getLineHeight());
   1161 
   1162         // edge
   1163         add = Float.MIN_VALUE;
   1164         mult = Float.MIN_VALUE;
   1165         setLineSpacing(add, mult);
   1166         assertEquals(Math.round(originalLineHeight * mult + add), mTextView.getLineHeight());
   1167 
   1168         // edge case where the behavior of Math.round() deviates from
   1169         // FastMath.round(), requiring us to use an explicit 0 value
   1170         add = Float.MAX_VALUE;
   1171         mult = Float.MAX_VALUE;
   1172         setLineSpacing(add, mult);
   1173         assertEquals(0, mTextView.getLineHeight());
   1174     }
   1175 
   1176     @Test
   1177     public void testSetElegantLineHeight() throws Throwable {
   1178         mTextView = findTextView(R.id.textview_text);
   1179         assertFalse(mTextView.getPaint().isElegantTextHeight());
   1180         mActivityRule.runOnUiThread(() -> {
   1181             mTextView.setWidth(mTextView.getWidth() / 3);
   1182             mTextView.setPadding(1, 2, 3, 4);
   1183             mTextView.setGravity(Gravity.BOTTOM);
   1184         });
   1185         mInstrumentation.waitForIdleSync();
   1186 
   1187         int oldHeight = mTextView.getHeight();
   1188         mActivityRule.runOnUiThread(() -> mTextView.setElegantTextHeight(true));
   1189         mInstrumentation.waitForIdleSync();
   1190 
   1191         assertTrue(mTextView.getPaint().isElegantTextHeight());
   1192         assertTrue(mTextView.getHeight() > oldHeight);
   1193 
   1194         mActivityRule.runOnUiThread(() -> mTextView.setElegantTextHeight(false));
   1195         mInstrumentation.waitForIdleSync();
   1196         assertFalse(mTextView.getPaint().isElegantTextHeight());
   1197         assertTrue(mTextView.getHeight() == oldHeight);
   1198     }
   1199 
   1200     @Test
   1201     public void testAccessFreezesText() throws Throwable {
   1202         layout(R.layout.textview_hint_linksclickable_freezestext);
   1203 
   1204         mTextView = findTextView(R.id.hint_linksClickable_freezesText_default);
   1205         assertFalse(mTextView.getFreezesText());
   1206 
   1207         mTextView = findTextView(R.id.freezesText_true);
   1208         assertTrue(mTextView.getFreezesText());
   1209 
   1210         mTextView = findTextView(R.id.freezesText_false);
   1211         assertFalse(mTextView.getFreezesText());
   1212 
   1213         mTextView.setFreezesText(false);
   1214         assertFalse(mTextView.getFreezesText());
   1215 
   1216         final CharSequence text = "Hello, TextView.";
   1217         mActivityRule.runOnUiThread(() -> mTextView.setText(text));
   1218         mInstrumentation.waitForIdleSync();
   1219 
   1220         final URLSpan urlSpan = new URLSpan("ctstest://TextView/test");
   1221         // TODO: How to simulate the TextView in frozen icicles.
   1222         ActivityMonitor am = mInstrumentation.addMonitor(MockURLSpanTestActivity.class.getName(),
   1223                 null, false);
   1224 
   1225         mActivityRule.runOnUiThread(() -> {
   1226             Uri uri = Uri.parse(urlSpan.getURL());
   1227             Intent intent = new Intent(Intent.ACTION_VIEW, uri);
   1228             mActivity.startActivity(intent);
   1229         });
   1230 
   1231         Activity newActivity = am.waitForActivityWithTimeout(TIMEOUT);
   1232         assertNotNull(newActivity);
   1233         newActivity.finish();
   1234         mInstrumentation.removeMonitor(am);
   1235         // the text of TextView is removed.
   1236         mTextView = findTextView(R.id.freezesText_false);
   1237 
   1238         assertEquals(text.toString(), mTextView.getText().toString());
   1239 
   1240         mTextView.setFreezesText(true);
   1241         assertTrue(mTextView.getFreezesText());
   1242 
   1243         mActivityRule.runOnUiThread(() -> mTextView.setText(text));
   1244         mInstrumentation.waitForIdleSync();
   1245         // TODO: How to simulate the TextView in frozen icicles.
   1246         am = mInstrumentation.addMonitor(MockURLSpanTestActivity.class.getName(),
   1247                 null, false);
   1248 
   1249         mActivityRule.runOnUiThread(() -> {
   1250             Uri uri = Uri.parse(urlSpan.getURL());
   1251             Intent intent = new Intent(Intent.ACTION_VIEW, uri);
   1252             mActivity.startActivity(intent);
   1253         });
   1254 
   1255         Activity oldActivity = newActivity;
   1256         while (true) {
   1257             newActivity = am.waitForActivityWithTimeout(TIMEOUT);
   1258             assertNotNull(newActivity);
   1259             if (newActivity != oldActivity) {
   1260                 break;
   1261             }
   1262         }
   1263         newActivity.finish();
   1264         mInstrumentation.removeMonitor(am);
   1265         // the text of TextView is still there.
   1266         mTextView = findTextView(R.id.freezesText_false);
   1267         assertEquals(text.toString(), mTextView.getText().toString());
   1268     }
   1269 
   1270     @UiThreadTest
   1271     @Test
   1272     public void testSetEditableFactory() {
   1273         mTextView = new TextView(mActivity);
   1274         String text = "sample";
   1275 
   1276         final Editable.Factory mockEditableFactory = spy(new Editable.Factory());
   1277         doCallRealMethod().when(mockEditableFactory).newEditable(any(CharSequence.class));
   1278         mTextView.setEditableFactory(mockEditableFactory);
   1279 
   1280         mTextView.setText(text);
   1281         verify(mockEditableFactory, never()).newEditable(any(CharSequence.class));
   1282 
   1283         reset(mockEditableFactory);
   1284         mTextView.setText(text, BufferType.SPANNABLE);
   1285         verify(mockEditableFactory, never()).newEditable(any(CharSequence.class));
   1286 
   1287         reset(mockEditableFactory);
   1288         mTextView.setText(text, BufferType.NORMAL);
   1289         verify(mockEditableFactory, never()).newEditable(any(CharSequence.class));
   1290 
   1291         reset(mockEditableFactory);
   1292         mTextView.setText(text, BufferType.EDITABLE);
   1293         verify(mockEditableFactory, times(1)).newEditable(text);
   1294 
   1295         mTextView.setKeyListener(DigitsKeyListener.getInstance());
   1296         reset(mockEditableFactory);
   1297         mTextView.setText(text, BufferType.EDITABLE);
   1298         verify(mockEditableFactory, times(1)).newEditable(text);
   1299 
   1300         try {
   1301             mTextView.setEditableFactory(null);
   1302             fail("The factory can not set to null!");
   1303         } catch (NullPointerException e) {
   1304         }
   1305     }
   1306 
   1307     @UiThreadTest
   1308     @Test
   1309     public void testSetSpannableFactory() {
   1310         mTextView = new TextView(mActivity);
   1311         String text = "sample";
   1312 
   1313         final Spannable.Factory mockSpannableFactory = spy(new Spannable.Factory());
   1314         doCallRealMethod().when(mockSpannableFactory).newSpannable(any(CharSequence.class));
   1315         mTextView.setSpannableFactory(mockSpannableFactory);
   1316 
   1317         mTextView.setText(text);
   1318         verify(mockSpannableFactory, never()).newSpannable(any(CharSequence.class));
   1319 
   1320         reset(mockSpannableFactory);
   1321         mTextView.setText(text, BufferType.EDITABLE);
   1322         verify(mockSpannableFactory, never()).newSpannable(any(CharSequence.class));
   1323 
   1324         reset(mockSpannableFactory);
   1325         mTextView.setText(text, BufferType.NORMAL);
   1326         verify(mockSpannableFactory, never()).newSpannable(any(CharSequence.class));
   1327 
   1328         reset(mockSpannableFactory);
   1329         mTextView.setText(text, BufferType.SPANNABLE);
   1330         verify(mockSpannableFactory, times(1)).newSpannable(text);
   1331 
   1332         mTextView.setMovementMethod(LinkMovementMethod.getInstance());
   1333         reset(mockSpannableFactory);
   1334         mTextView.setText(text, BufferType.NORMAL);
   1335         verify(mockSpannableFactory, times(1)).newSpannable(text);
   1336 
   1337         try {
   1338             mTextView.setSpannableFactory(null);
   1339             fail("The factory can not set to null!");
   1340         } catch (NullPointerException e) {
   1341         }
   1342     }
   1343 
   1344     @UiThreadTest
   1345     @Test
   1346     public void testTextChangedListener() {
   1347         mTextView = new TextView(mActivity);
   1348         MockTextWatcher watcher0 = new MockTextWatcher();
   1349         MockTextWatcher watcher1 = new MockTextWatcher();
   1350 
   1351         mTextView.addTextChangedListener(watcher0);
   1352         mTextView.addTextChangedListener(watcher1);
   1353 
   1354         watcher0.reset();
   1355         watcher1.reset();
   1356         mTextView.setText("Changed");
   1357         assertTrue(watcher0.hasCalledBeforeTextChanged());
   1358         assertTrue(watcher0.hasCalledOnTextChanged());
   1359         assertTrue(watcher0.hasCalledAfterTextChanged());
   1360         assertTrue(watcher1.hasCalledBeforeTextChanged());
   1361         assertTrue(watcher1.hasCalledOnTextChanged());
   1362         assertTrue(watcher1.hasCalledAfterTextChanged());
   1363 
   1364         watcher0.reset();
   1365         watcher1.reset();
   1366         // BeforeTextChanged and OnTextChanged are called though the strings are same
   1367         mTextView.setText("Changed");
   1368         assertTrue(watcher0.hasCalledBeforeTextChanged());
   1369         assertTrue(watcher0.hasCalledOnTextChanged());
   1370         assertTrue(watcher0.hasCalledAfterTextChanged());
   1371         assertTrue(watcher1.hasCalledBeforeTextChanged());
   1372         assertTrue(watcher1.hasCalledOnTextChanged());
   1373         assertTrue(watcher1.hasCalledAfterTextChanged());
   1374 
   1375         watcher0.reset();
   1376         watcher1.reset();
   1377         // BeforeTextChanged and OnTextChanged are called twice (The text is not
   1378         // Editable, so in Append() it calls setText() first)
   1379         mTextView.append("and appended");
   1380         assertTrue(watcher0.hasCalledBeforeTextChanged());
   1381         assertTrue(watcher0.hasCalledOnTextChanged());
   1382         assertTrue(watcher0.hasCalledAfterTextChanged());
   1383         assertTrue(watcher1.hasCalledBeforeTextChanged());
   1384         assertTrue(watcher1.hasCalledOnTextChanged());
   1385         assertTrue(watcher1.hasCalledAfterTextChanged());
   1386 
   1387         watcher0.reset();
   1388         watcher1.reset();
   1389         // Methods are not called if the string does not change
   1390         mTextView.append("");
   1391         assertFalse(watcher0.hasCalledBeforeTextChanged());
   1392         assertFalse(watcher0.hasCalledOnTextChanged());
   1393         assertFalse(watcher0.hasCalledAfterTextChanged());
   1394         assertFalse(watcher1.hasCalledBeforeTextChanged());
   1395         assertFalse(watcher1.hasCalledOnTextChanged());
   1396         assertFalse(watcher1.hasCalledAfterTextChanged());
   1397 
   1398         watcher0.reset();
   1399         watcher1.reset();
   1400         mTextView.removeTextChangedListener(watcher1);
   1401         mTextView.setText(null);
   1402         assertTrue(watcher0.hasCalledBeforeTextChanged());
   1403         assertTrue(watcher0.hasCalledOnTextChanged());
   1404         assertTrue(watcher0.hasCalledAfterTextChanged());
   1405         assertFalse(watcher1.hasCalledBeforeTextChanged());
   1406         assertFalse(watcher1.hasCalledOnTextChanged());
   1407         assertFalse(watcher1.hasCalledAfterTextChanged());
   1408     }
   1409 
   1410     @UiThreadTest
   1411     @Test
   1412     public void testSetTextKeepState1() {
   1413         mTextView = new TextView(mActivity);
   1414 
   1415         String longString = "very long content";
   1416         String shortString = "short";
   1417 
   1418         // selection is at the exact place which is inside the short string
   1419         mTextView.setText(longString, BufferType.SPANNABLE);
   1420         Selection.setSelection((Spannable) mTextView.getText(), 3);
   1421         mTextView.setTextKeepState(shortString);
   1422         assertEquals(shortString, mTextView.getText().toString());
   1423         assertEquals(3, mTextView.getSelectionStart());
   1424         assertEquals(3, mTextView.getSelectionEnd());
   1425 
   1426         // selection is at the exact place which is outside the short string
   1427         mTextView.setText(longString);
   1428         Selection.setSelection((Spannable) mTextView.getText(), shortString.length() + 1);
   1429         mTextView.setTextKeepState(shortString);
   1430         assertEquals(shortString, mTextView.getText().toString());
   1431         assertEquals(shortString.length(), mTextView.getSelectionStart());
   1432         assertEquals(shortString.length(), mTextView.getSelectionEnd());
   1433 
   1434         // select the sub string which is inside the short string
   1435         mTextView.setText(longString);
   1436         Selection.setSelection((Spannable) mTextView.getText(), 1, 4);
   1437         mTextView.setTextKeepState(shortString);
   1438         assertEquals(shortString, mTextView.getText().toString());
   1439         assertEquals(1, mTextView.getSelectionStart());
   1440         assertEquals(4, mTextView.getSelectionEnd());
   1441 
   1442         // select the sub string which ends outside the short string
   1443         mTextView.setText(longString);
   1444         Selection.setSelection((Spannable) mTextView.getText(), 2, shortString.length() + 1);
   1445         mTextView.setTextKeepState(shortString);
   1446         assertEquals(shortString, mTextView.getText().toString());
   1447         assertEquals(2, mTextView.getSelectionStart());
   1448         assertEquals(shortString.length(), mTextView.getSelectionEnd());
   1449 
   1450         // select the sub string which is outside the short string
   1451         mTextView.setText(longString);
   1452         Selection.setSelection((Spannable) mTextView.getText(),
   1453                 shortString.length() + 1, shortString.length() + 3);
   1454         mTextView.setTextKeepState(shortString);
   1455         assertEquals(shortString, mTextView.getText().toString());
   1456         assertEquals(shortString.length(), mTextView.getSelectionStart());
   1457         assertEquals(shortString.length(), mTextView.getSelectionEnd());
   1458     }
   1459 
   1460     @UiThreadTest
   1461     @Test
   1462     public void testGetEditableText() {
   1463         TextView tv = findTextView(R.id.textview_text);
   1464 
   1465         String text = "Hello";
   1466         tv.setText(text, BufferType.EDITABLE);
   1467         assertEquals(text, tv.getText().toString());
   1468         assertTrue(tv.getText() instanceof Editable);
   1469         assertEquals(text, tv.getEditableText().toString());
   1470 
   1471         tv.setText(text, BufferType.SPANNABLE);
   1472         assertEquals(text, tv.getText().toString());
   1473         assertTrue(tv.getText() instanceof Spannable);
   1474         assertNull(tv.getEditableText());
   1475 
   1476         tv.setText(null, BufferType.EDITABLE);
   1477         assertEquals("", tv.getText().toString());
   1478         assertTrue(tv.getText() instanceof Editable);
   1479         assertEquals("", tv.getEditableText().toString());
   1480 
   1481         tv.setText(null, BufferType.SPANNABLE);
   1482         assertEquals("", tv.getText().toString());
   1483         assertTrue(tv.getText() instanceof Spannable);
   1484         assertNull(tv.getEditableText());
   1485     }
   1486 
   1487     @UiThreadTest
   1488     @Test
   1489     public void testSetText2() {
   1490         String string = "This is a test for setting text content by char array";
   1491         char[] input = string.toCharArray();
   1492         TextView tv = findTextView(R.id.textview_text);
   1493 
   1494         tv.setText(input, 0, input.length);
   1495         assertEquals(string, tv.getText().toString());
   1496 
   1497         tv.setText(input, 0, 5);
   1498         assertEquals(string.substring(0, 5), tv.getText().toString());
   1499 
   1500         try {
   1501             tv.setText(input, -1, input.length);
   1502             fail("Should throw exception if the start position is negative!");
   1503         } catch (IndexOutOfBoundsException exception) {
   1504         }
   1505 
   1506         try {
   1507             tv.setText(input, 0, -1);
   1508             fail("Should throw exception if the length is negative!");
   1509         } catch (IndexOutOfBoundsException exception) {
   1510         }
   1511 
   1512         try {
   1513             tv.setText(input, 1, input.length);
   1514             fail("Should throw exception if the end position is out of index!");
   1515         } catch (IndexOutOfBoundsException exception) {
   1516         }
   1517 
   1518         tv.setText(input, 1, 0);
   1519         assertEquals("", tv.getText().toString());
   1520     }
   1521 
   1522     @UiThreadTest
   1523     @Test
   1524     public void testSetText1() {
   1525         mTextView = findTextView(R.id.textview_text);
   1526 
   1527         String longString = "very long content";
   1528         String shortString = "short";
   1529 
   1530         // selection is at the exact place which is inside the short string
   1531         mTextView.setText(longString, BufferType.SPANNABLE);
   1532         Selection.setSelection((Spannable) mTextView.getText(), 3);
   1533         mTextView.setTextKeepState(shortString, BufferType.EDITABLE);
   1534         assertTrue(mTextView.getText() instanceof Editable);
   1535         assertEquals(shortString, mTextView.getText().toString());
   1536         assertEquals(shortString, mTextView.getEditableText().toString());
   1537         assertEquals(3, mTextView.getSelectionStart());
   1538         assertEquals(3, mTextView.getSelectionEnd());
   1539 
   1540         mTextView.setText(shortString, BufferType.EDITABLE);
   1541         assertTrue(mTextView.getText() instanceof Editable);
   1542         assertEquals(shortString, mTextView.getText().toString());
   1543         assertEquals(shortString, mTextView.getEditableText().toString());
   1544         // there is no selection.
   1545         assertEquals(-1, mTextView.getSelectionStart());
   1546         assertEquals(-1, mTextView.getSelectionEnd());
   1547 
   1548         // selection is at the exact place which is outside the short string
   1549         mTextView.setText(longString);
   1550         Selection.setSelection((Spannable) mTextView.getText(), longString.length());
   1551         mTextView.setTextKeepState(shortString, BufferType.EDITABLE);
   1552         assertTrue(mTextView.getText() instanceof Editable);
   1553         assertEquals(shortString, mTextView.getText().toString());
   1554         assertEquals(shortString, mTextView.getEditableText().toString());
   1555         assertEquals(shortString.length(), mTextView.getSelectionStart());
   1556         assertEquals(shortString.length(), mTextView.getSelectionEnd());
   1557 
   1558         mTextView.setText(shortString, BufferType.EDITABLE);
   1559         assertTrue(mTextView.getText() instanceof Editable);
   1560         assertEquals(shortString, mTextView.getText().toString());
   1561         assertEquals(shortString, mTextView.getEditableText().toString());
   1562         // there is no selection.
   1563         assertEquals(-1, mTextView.getSelectionStart());
   1564         assertEquals(-1, mTextView.getSelectionEnd());
   1565 
   1566         // select the sub string which is inside the short string
   1567         mTextView.setText(longString);
   1568         Selection.setSelection((Spannable) mTextView.getText(), 1, shortString.length() - 1);
   1569         mTextView.setTextKeepState(shortString, BufferType.EDITABLE);
   1570         assertTrue(mTextView.getText() instanceof Editable);
   1571         assertEquals(shortString, mTextView.getText().toString());
   1572         assertEquals(shortString, mTextView.getEditableText().toString());
   1573         assertEquals(1, mTextView.getSelectionStart());
   1574         assertEquals(shortString.length() - 1, mTextView.getSelectionEnd());
   1575 
   1576         mTextView.setText(shortString, BufferType.EDITABLE);
   1577         assertTrue(mTextView.getText() instanceof Editable);
   1578         assertEquals(shortString, mTextView.getText().toString());
   1579         assertEquals(shortString, mTextView.getEditableText().toString());
   1580         // there is no selection.
   1581         assertEquals(-1, mTextView.getSelectionStart());
   1582         assertEquals(-1, mTextView.getSelectionEnd());
   1583 
   1584         // select the sub string which ends outside the short string
   1585         mTextView.setText(longString);
   1586         Selection.setSelection((Spannable) mTextView.getText(), 2, longString.length());
   1587         mTextView.setTextKeepState(shortString, BufferType.EDITABLE);
   1588         assertTrue(mTextView.getText() instanceof Editable);
   1589         assertEquals(shortString, mTextView.getText().toString());
   1590         assertEquals(shortString, mTextView.getEditableText().toString());
   1591         assertEquals(2, mTextView.getSelectionStart());
   1592         assertEquals(shortString.length(), mTextView.getSelectionEnd());
   1593 
   1594         mTextView.setText(shortString, BufferType.EDITABLE);
   1595         assertTrue(mTextView.getText() instanceof Editable);
   1596         assertEquals(shortString, mTextView.getText().toString());
   1597         assertEquals(shortString, mTextView.getEditableText().toString());
   1598         // there is no selection.
   1599         assertEquals(-1, mTextView.getSelectionStart());
   1600         assertEquals(-1, mTextView.getSelectionEnd());
   1601 
   1602         // select the sub string which is outside the short string
   1603         mTextView.setText(longString);
   1604         Selection.setSelection((Spannable) mTextView.getText(),
   1605                 shortString.length() + 1, shortString.length() + 3);
   1606         mTextView.setTextKeepState(shortString, BufferType.EDITABLE);
   1607         assertTrue(mTextView.getText() instanceof Editable);
   1608         assertEquals(shortString, mTextView.getText().toString());
   1609         assertEquals(shortString, mTextView.getEditableText().toString());
   1610         assertEquals(shortString.length(), mTextView.getSelectionStart());
   1611         assertEquals(shortString.length(), mTextView.getSelectionEnd());
   1612 
   1613         mTextView.setText(shortString, BufferType.EDITABLE);
   1614         assertTrue(mTextView.getText() instanceof Editable);
   1615         assertEquals(shortString, mTextView.getText().toString());
   1616         assertEquals(shortString, mTextView.getEditableText().toString());
   1617         // there is no selection.
   1618         assertEquals(-1, mTextView.getSelectionStart());
   1619         assertEquals(-1, mTextView.getSelectionEnd());
   1620     }
   1621 
   1622     @UiThreadTest
   1623     @Test
   1624     public void testSetText3() {
   1625         TextView tv = findTextView(R.id.textview_text);
   1626 
   1627         int resId = R.string.text_view_hint;
   1628         String result = mActivity.getResources().getString(resId);
   1629 
   1630         tv.setText(resId);
   1631         assertEquals(result, tv.getText().toString());
   1632 
   1633         try {
   1634             tv.setText(-1);
   1635             fail("Should throw exception with illegal id");
   1636         } catch (NotFoundException e) {
   1637         }
   1638     }
   1639 
   1640     @UiThreadTest
   1641     @Test
   1642     public void testSetText_PrecomputedText() {
   1643         final TextView tv = findTextView(R.id.textview_text);
   1644         final PrecomputedText measured = PrecomputedText.create(
   1645                 "This is an example text.", tv.getTextMetricsParams());
   1646         tv.setText(measured);
   1647         assertEquals(measured.toString(), tv.getText().toString());
   1648     }
   1649 
   1650     @Test
   1651     public void testSetTextUpdatesHeightAfterRemovingImageSpan() throws Throwable {
   1652         // Height calculation had problems when TextView had width: match_parent
   1653         final int textViewWidth = ViewGroup.LayoutParams.MATCH_PARENT;
   1654         final Spannable text = new SpannableString("some text");
   1655         final int spanHeight = 100;
   1656 
   1657         // prepare TextView, width: MATCH_PARENT
   1658         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
   1659         mInstrumentation.waitForIdleSync();
   1660         mTextView.setSingleLine(true);
   1661         mTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 2);
   1662         mTextView.setPadding(0, 0, 0, 0);
   1663         mTextView.setIncludeFontPadding(false);
   1664         mTextView.setText(text);
   1665         final FrameLayout layout = new FrameLayout(mActivity);
   1666         final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(textViewWidth,
   1667                 ViewGroup.LayoutParams.WRAP_CONTENT);
   1668         layout.addView(mTextView, layoutParams);
   1669         layout.setLayoutParams(layoutParams);
   1670         mActivityRule.runOnUiThread(() -> mActivity.setContentView(layout));
   1671         mInstrumentation.waitForIdleSync();
   1672 
   1673         // measure height of text with no span
   1674         final int heightWithoutSpan = mTextView.getHeight();
   1675         assertTrue("Text height should be smaller than span height",
   1676                 heightWithoutSpan < spanHeight);
   1677 
   1678         // add ImageSpan to text
   1679         Drawable drawable = mInstrumentation.getContext().getDrawable(R.drawable.scenery);
   1680         drawable.setBounds(0, 0, spanHeight, spanHeight);
   1681         ImageSpan span = new ImageSpan(drawable);
   1682         text.setSpan(span, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
   1683         mActivityRule.runOnUiThread(() -> mTextView.setText(text));
   1684         mInstrumentation.waitForIdleSync();
   1685 
   1686         // measure height with span
   1687         final int heightWithSpan = mTextView.getHeight();
   1688         assertTrue("Text height should be greater or equal than span height",
   1689                 heightWithSpan >= spanHeight);
   1690 
   1691         // remove the span
   1692         text.removeSpan(span);
   1693         mActivityRule.runOnUiThread(() -> mTextView.setText(text));
   1694         mInstrumentation.waitForIdleSync();
   1695 
   1696         final int heightAfterRemoveSpan = mTextView.getHeight();
   1697         assertEquals("Text height should be same after removing the span",
   1698                 heightWithoutSpan, heightAfterRemoveSpan);
   1699     }
   1700 
   1701     @Test
   1702     public void testRemoveSelectionWithSelectionHandles() throws Throwable {
   1703         initTextViewForTypingOnUiThread();
   1704 
   1705         assertFalse(mTextView.isTextSelectable());
   1706         mActivityRule.runOnUiThread(() -> {
   1707             mTextView.setTextIsSelectable(true);
   1708             mTextView.setText("abcd", BufferType.EDITABLE);
   1709         });
   1710         mInstrumentation.waitForIdleSync();
   1711         assertTrue(mTextView.isTextSelectable());
   1712 
   1713         // Long click on the text selects all text and shows selection handlers. The view has an
   1714         // attribute layout_width="wrap_content", so clicked location (the center of the view)
   1715         // should be on the text.
   1716         CtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mActivityRule, mTextView);
   1717 
   1718         mActivityRule.runOnUiThread(() -> Selection.removeSelection((Spannable) mTextView.getText()));
   1719         mInstrumentation.waitForIdleSync();
   1720 
   1721         assertTrue(TextUtils.equals("abcd", mTextView.getText()));
   1722     }
   1723 
   1724     @Test
   1725     public void testUndo_insert() throws Throwable {
   1726         initTextViewForTypingOnUiThread();
   1727 
   1728         // Type some text.
   1729         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   1730         mActivityRule.runOnUiThread(() -> {
   1731             // Precondition: The cursor is at the end of the text.
   1732             assertEquals(3, mTextView.getSelectionStart());
   1733 
   1734             // Undo removes the typed string in one step.
   1735             mTextView.onTextContextMenuItem(android.R.id.undo);
   1736             assertEquals("", mTextView.getText().toString());
   1737             assertEquals(0, mTextView.getSelectionStart());
   1738 
   1739             // Redo restores the text and cursor position.
   1740             mTextView.onTextContextMenuItem(android.R.id.redo);
   1741             assertEquals("abc", mTextView.getText().toString());
   1742             assertEquals(3, mTextView.getSelectionStart());
   1743 
   1744             // Undoing the redo clears the text again.
   1745             mTextView.onTextContextMenuItem(android.R.id.undo);
   1746             assertEquals("", mTextView.getText().toString());
   1747 
   1748             // Undo when the undo stack is empty does nothing.
   1749             mTextView.onTextContextMenuItem(android.R.id.undo);
   1750             assertEquals("", mTextView.getText().toString());
   1751         });
   1752         mInstrumentation.waitForIdleSync();
   1753     }
   1754 
   1755     @Test
   1756     public void testUndo_delete() throws Throwable {
   1757         initTextViewForTypingOnUiThread();
   1758 
   1759         // Simulate deleting text and undoing it.
   1760         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "xyz");
   1761         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_DEL,
   1762                 KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL);
   1763         mActivityRule.runOnUiThread(() -> {
   1764             // Precondition: The text was actually deleted.
   1765             assertEquals("", mTextView.getText().toString());
   1766             assertEquals(0, mTextView.getSelectionStart());
   1767 
   1768             // Undo restores the typed string and cursor position in one step.
   1769             mTextView.onTextContextMenuItem(android.R.id.undo);
   1770             assertEquals("xyz", mTextView.getText().toString());
   1771             assertEquals(3, mTextView.getSelectionStart());
   1772 
   1773             // Redo removes the text in one step.
   1774             mTextView.onTextContextMenuItem(android.R.id.redo);
   1775             assertEquals("", mTextView.getText().toString());
   1776             assertEquals(0, mTextView.getSelectionStart());
   1777 
   1778             // Undoing the redo restores the text again.
   1779             mTextView.onTextContextMenuItem(android.R.id.undo);
   1780             assertEquals("xyz", mTextView.getText().toString());
   1781             assertEquals(3, mTextView.getSelectionStart());
   1782 
   1783             // Undoing again undoes the original typing.
   1784             mTextView.onTextContextMenuItem(android.R.id.undo);
   1785             assertEquals("", mTextView.getText().toString());
   1786             assertEquals(0, mTextView.getSelectionStart());
   1787         });
   1788         mInstrumentation.waitForIdleSync();
   1789     }
   1790 
   1791     // Initialize the text view for simulated IME typing. Must be called on UI thread.
   1792     private InputConnection initTextViewForSimulatedIme() {
   1793         mTextView = findTextView(R.id.textview_text);
   1794         return initTextViewForSimulatedIme(mTextView);
   1795     }
   1796 
   1797     private InputConnection initTextViewForSimulatedIme(TextView textView) {
   1798         textView.setKeyListener(QwertyKeyListener.getInstance(false, Capitalize.NONE));
   1799         textView.setText("", BufferType.EDITABLE);
   1800         return textView.onCreateInputConnection(new EditorInfo());
   1801     }
   1802 
   1803     // Simulates IME composing text behavior.
   1804     private void setComposingTextInBatch(InputConnection input, CharSequence text) {
   1805         input.beginBatchEdit();
   1806         input.setComposingText(text, 1);  // Leave cursor at end.
   1807         input.endBatchEdit();
   1808     }
   1809 
   1810     @UiThreadTest
   1811     @Test
   1812     public void testUndo_imeInsertLatin() {
   1813         InputConnection input = initTextViewForSimulatedIme();
   1814 
   1815         // Simulate IME text entry behavior. The Latin IME enters text by replacing partial words,
   1816         // such as "c" -> "ca" -> "cat" -> "cat ".
   1817         setComposingTextInBatch(input, "c");
   1818         setComposingTextInBatch(input, "ca");
   1819 
   1820         // The completion and space are added in the same batch.
   1821         input.beginBatchEdit();
   1822         input.commitText("cat", 1);
   1823         input.commitText(" ", 1);
   1824         input.endBatchEdit();
   1825 
   1826         // The repeated replacements undo in a single step.
   1827         mTextView.onTextContextMenuItem(android.R.id.undo);
   1828         assertEquals("", mTextView.getText().toString());
   1829     }
   1830 
   1831     @UiThreadTest
   1832     @Test
   1833     public void testUndo_imeInsertJapanese() {
   1834         InputConnection input = initTextViewForSimulatedIme();
   1835 
   1836         // The Japanese IME does repeated replacements of Latin characters to hiragana to kanji.
   1837         final String HA = "\u306F";  // HIRAGANA LETTER HA
   1838         final String NA = "\u306A";  // HIRAGANA LETTER NA
   1839         setComposingTextInBatch(input, "h");
   1840         setComposingTextInBatch(input, HA);
   1841         setComposingTextInBatch(input, HA + "n");
   1842         setComposingTextInBatch(input, HA + NA);
   1843 
   1844         // The result may be a surrogate pair. The composition ends in the same batch.
   1845         input.beginBatchEdit();
   1846         input.commitText("\uD83C\uDF37", 1);  // U+1F337 TULIP
   1847         input.setComposingText("", 1);
   1848         input.endBatchEdit();
   1849 
   1850         // The repeated replacements are a single undo step.
   1851         mTextView.onTextContextMenuItem(android.R.id.undo);
   1852         assertEquals("", mTextView.getText().toString());
   1853     }
   1854 
   1855     @UiThreadTest
   1856     @Test
   1857     public void testUndo_imeInsertAndDeleteLatin() {
   1858         InputConnection input = initTextViewForSimulatedIme();
   1859 
   1860         setComposingTextInBatch(input, "t");
   1861         setComposingTextInBatch(input, "te");
   1862         setComposingTextInBatch(input, "tes");
   1863         setComposingTextInBatch(input, "test");
   1864         setComposingTextInBatch(input, "tes");
   1865         setComposingTextInBatch(input, "te");
   1866         setComposingTextInBatch(input, "t");
   1867 
   1868         input.beginBatchEdit();
   1869         input.setComposingText("", 1);
   1870         input.finishComposingText();
   1871         input.endBatchEdit();
   1872 
   1873         mTextView.onTextContextMenuItem(android.R.id.undo);
   1874         assertEquals("test", mTextView.getText().toString());
   1875         mTextView.onTextContextMenuItem(android.R.id.undo);
   1876         assertEquals("", mTextView.getText().toString());
   1877     }
   1878 
   1879     @UiThreadTest
   1880     @Test
   1881     public void testUndo_imeAutoCorrection() {
   1882         mTextView = findTextView(R.id.textview_text);
   1883         TextView spiedTextView = spy(mTextView);
   1884         InputConnection input = initTextViewForSimulatedIme(spiedTextView);
   1885 
   1886         // Start typing a composition.
   1887         setComposingTextInBatch(input, "t");
   1888         setComposingTextInBatch(input, "te");
   1889         setComposingTextInBatch(input, "teh");
   1890 
   1891         CorrectionInfo correctionInfo = new CorrectionInfo(0, "teh", "the");
   1892         reset(spiedTextView);
   1893         input.beginBatchEdit();
   1894         // Auto correct "teh" to "the".
   1895         assertTrue(input.commitCorrection(correctionInfo));
   1896         input.commitText("the", 1);
   1897         input.endBatchEdit();
   1898 
   1899         verify(spiedTextView, times(1)).onCommitCorrection(refEq(correctionInfo));
   1900 
   1901         assertEquals("the", spiedTextView.getText().toString());
   1902         spiedTextView.onTextContextMenuItem(android.R.id.undo);
   1903         assertEquals("teh", spiedTextView.getText().toString());
   1904         spiedTextView.onTextContextMenuItem(android.R.id.undo);
   1905         assertEquals("", spiedTextView.getText().toString());
   1906     }
   1907 
   1908     @UiThreadTest
   1909     @Test
   1910     public void testUndo_imeAutoCompletion() {
   1911         mTextView = findTextView(R.id.textview_text);
   1912         TextView spiedTextView = spy(mTextView);
   1913         InputConnection input = initTextViewForSimulatedIme(spiedTextView);
   1914 
   1915         // Start typing a composition.
   1916         setComposingTextInBatch(input, "a");
   1917         setComposingTextInBatch(input, "an");
   1918         setComposingTextInBatch(input, "and");
   1919 
   1920         CompletionInfo completionInfo = new CompletionInfo(0, 0, "android");
   1921         reset(spiedTextView);
   1922         input.beginBatchEdit();
   1923         // Auto complete "and" to "android".
   1924         assertTrue(input.commitCompletion(completionInfo));
   1925         input.commitText("android", 1);
   1926         input.endBatchEdit();
   1927 
   1928         verify(spiedTextView, times(1)).onCommitCompletion(refEq(completionInfo));
   1929 
   1930         assertEquals("android", spiedTextView.getText().toString());
   1931         spiedTextView.onTextContextMenuItem(android.R.id.undo);
   1932         assertEquals("", spiedTextView.getText().toString());
   1933     }
   1934 
   1935     @UiThreadTest
   1936     @Test
   1937     public void testUndo_imeCancel() {
   1938         InputConnection input = initTextViewForSimulatedIme();
   1939         mTextView.setText("flower");
   1940 
   1941         // Start typing a composition.
   1942         final String HA = "\u306F";  // HIRAGANA LETTER HA
   1943         setComposingTextInBatch(input, "h");
   1944         setComposingTextInBatch(input, HA);
   1945         setComposingTextInBatch(input, HA + "n");
   1946 
   1947         // Cancel the composition.
   1948         setComposingTextInBatch(input, "");
   1949 
   1950         mTextView.onTextContextMenuItem(android.R.id.undo);
   1951         assertEquals(HA + "n" + "flower", mTextView.getText().toString());
   1952         mTextView.onTextContextMenuItem(android.R.id.redo);
   1953         assertEquals("flower", mTextView.getText().toString());
   1954     }
   1955 
   1956     @UiThreadTest
   1957     @Test
   1958     public void testUndo_imeEmptyBatch() {
   1959         InputConnection input = initTextViewForSimulatedIme();
   1960         mTextView.setText("flower");
   1961 
   1962         // Send an empty batch edit. This happens if the IME is hidden and shown.
   1963         input.beginBatchEdit();
   1964         input.endBatchEdit();
   1965 
   1966         // Undo and redo do nothing.
   1967         mTextView.onTextContextMenuItem(android.R.id.undo);
   1968         assertEquals("flower", mTextView.getText().toString());
   1969         mTextView.onTextContextMenuItem(android.R.id.redo);
   1970         assertEquals("flower", mTextView.getText().toString());
   1971     }
   1972 
   1973     @Test
   1974     public void testUndo_setText() throws Throwable {
   1975         initTextViewForTypingOnUiThread();
   1976 
   1977         // Create two undo operations, an insert and a delete.
   1978         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "xyz");
   1979         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_DEL,
   1980                 KeyEvent.KEYCODE_DEL, KeyEvent.KEYCODE_DEL);
   1981         mActivityRule.runOnUiThread(() -> {
   1982             // Calling setText() clears both undo operations, so undo doesn't happen.
   1983             mTextView.setText("Hello", BufferType.EDITABLE);
   1984             mTextView.onTextContextMenuItem(android.R.id.undo);
   1985             assertEquals("Hello", mTextView.getText().toString());
   1986 
   1987             // Clearing text programmatically does not undo either.
   1988             mTextView.setText("", BufferType.EDITABLE);
   1989             mTextView.onTextContextMenuItem(android.R.id.undo);
   1990             assertEquals("", mTextView.getText().toString());
   1991         });
   1992         mInstrumentation.waitForIdleSync();
   1993     }
   1994 
   1995     @Test
   1996     public void testRedo_setText() throws Throwable {
   1997         initTextViewForTypingOnUiThread();
   1998 
   1999         // Type some text. This creates an undo entry.
   2000         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   2001         mActivityRule.runOnUiThread(() -> {
   2002             // Undo the typing to create a redo entry.
   2003             mTextView.onTextContextMenuItem(android.R.id.undo);
   2004 
   2005             // Calling setText() clears the redo stack, so redo doesn't happen.
   2006             mTextView.setText("Hello", BufferType.EDITABLE);
   2007             mTextView.onTextContextMenuItem(android.R.id.redo);
   2008             assertEquals("Hello", mTextView.getText().toString());
   2009         });
   2010         mInstrumentation.waitForIdleSync();
   2011     }
   2012 
   2013     @Test
   2014     public void testUndo_directAppend() throws Throwable {
   2015         initTextViewForTypingOnUiThread();
   2016 
   2017         // Type some text.
   2018         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   2019         mActivityRule.runOnUiThread(() -> {
   2020             // Programmatically append some text.
   2021             mTextView.append("def");
   2022             assertEquals("abcdef", mTextView.getText().toString());
   2023 
   2024             // Undo removes the append as a separate step.
   2025             mTextView.onTextContextMenuItem(android.R.id.undo);
   2026             assertEquals("abc", mTextView.getText().toString());
   2027 
   2028             // Another undo removes the original typing.
   2029             mTextView.onTextContextMenuItem(android.R.id.undo);
   2030             assertEquals("", mTextView.getText().toString());
   2031         });
   2032         mInstrumentation.waitForIdleSync();
   2033     }
   2034 
   2035     @Test
   2036     public void testUndo_directInsert() throws Throwable {
   2037         initTextViewForTypingOnUiThread();
   2038 
   2039         // Type some text.
   2040         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   2041         mActivityRule.runOnUiThread(() -> {
   2042             // Directly modify the underlying Editable to insert some text.
   2043             // NOTE: This is a violation of the API of getText() which specifies that the
   2044             // returned object should not be modified. However, some apps do this anyway and
   2045             // the framework needs to handle it.
   2046             Editable text = (Editable) mTextView.getText();
   2047             text.insert(0, "def");
   2048             assertEquals("defabc", mTextView.getText().toString());
   2049 
   2050             // Undo removes the insert as a separate step.
   2051             mTextView.onTextContextMenuItem(android.R.id.undo);
   2052             assertEquals("abc", mTextView.getText().toString());
   2053 
   2054             // Another undo removes the original typing.
   2055             mTextView.onTextContextMenuItem(android.R.id.undo);
   2056             assertEquals("", mTextView.getText().toString());
   2057         });
   2058         mInstrumentation.waitForIdleSync();
   2059     }
   2060 
   2061     @UiThreadTest
   2062     @Test
   2063     public void testUndo_noCursor() {
   2064         initTextViewForTyping();
   2065 
   2066         // Append some text to create an undo operation. There is no cursor present.
   2067         mTextView.append("cat");
   2068 
   2069         // Place the cursor at the end of the text so the undo will have to change it.
   2070         Selection.setSelection((Spannable) mTextView.getText(), 3);
   2071 
   2072         // Undo the append. This should not crash, despite not having a valid cursor
   2073         // position in the undo operation.
   2074         mTextView.onTextContextMenuItem(android.R.id.undo);
   2075     }
   2076 
   2077     @Test
   2078     public void testUndo_textWatcher() throws Throwable {
   2079         initTextViewForTypingOnUiThread();
   2080 
   2081         // Add a TextWatcher that converts the text to spaces on each change.
   2082         mTextView.addTextChangedListener(new ConvertToSpacesTextWatcher());
   2083 
   2084         // Type some text.
   2085         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   2086         mActivityRule.runOnUiThread(() -> {
   2087             // TextWatcher altered the text.
   2088             assertEquals("   ", mTextView.getText().toString());
   2089 
   2090             // Undo reverses both changes in one step.
   2091             mTextView.onTextContextMenuItem(android.R.id.undo);
   2092             assertEquals("", mTextView.getText().toString());
   2093         });
   2094         mInstrumentation.waitForIdleSync();
   2095     }
   2096 
   2097     @UiThreadTest
   2098     @Test
   2099     public void testUndo_textWatcherDirectAppend() {
   2100         initTextViewForTyping();
   2101 
   2102         // Add a TextWatcher that converts the text to spaces on each change.
   2103         mTextView.addTextChangedListener(new ConvertToSpacesTextWatcher());
   2104 
   2105         // Programmatically append some text. The TextWatcher changes it to spaces.
   2106         mTextView.append("abc");
   2107         assertEquals("   ", mTextView.getText().toString());
   2108 
   2109         // Undo reverses both changes in one step.
   2110         mTextView.onTextContextMenuItem(android.R.id.undo);
   2111         assertEquals("", mTextView.getText().toString());
   2112     }
   2113 
   2114     @Test
   2115     public void testUndo_shortcuts() throws Throwable {
   2116         initTextViewForTypingOnUiThread();
   2117 
   2118         // Type some text.
   2119         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   2120         mActivityRule.runOnUiThread(() -> {
   2121             // Pressing Control-Z triggers undo.
   2122             KeyEvent control = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_Z, 0,
   2123                     KeyEvent.META_CTRL_LEFT_ON);
   2124             assertTrue(mTextView.onKeyShortcut(KeyEvent.KEYCODE_Z, control));
   2125             assertEquals("", mTextView.getText().toString());
   2126 
   2127             // Pressing Control-Shift-Z triggers redo.
   2128             KeyEvent controlShift = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_Z,
   2129                     0, KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_SHIFT_LEFT_ON);
   2130             assertTrue(mTextView.onKeyShortcut(KeyEvent.KEYCODE_Z, controlShift));
   2131             assertEquals("abc", mTextView.getText().toString());
   2132         });
   2133         mInstrumentation.waitForIdleSync();
   2134     }
   2135 
   2136     @Test
   2137     public void testUndo_saveInstanceState() throws Throwable {
   2138         initTextViewForTypingOnUiThread();
   2139 
   2140         // Type some text to create an undo operation.
   2141         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   2142         mActivityRule.runOnUiThread(() -> {
   2143             // Parcel and unparcel the TextView.
   2144             Parcelable state = mTextView.onSaveInstanceState();
   2145             mTextView.onRestoreInstanceState(state);
   2146         });
   2147         mInstrumentation.waitForIdleSync();
   2148 
   2149         // Delete a character to create a new undo operation.
   2150         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_DEL);
   2151         mActivityRule.runOnUiThread(() -> {
   2152             assertEquals("ab", mTextView.getText().toString());
   2153 
   2154             // Undo the delete.
   2155             mTextView.onTextContextMenuItem(android.R.id.undo);
   2156             assertEquals("abc", mTextView.getText().toString());
   2157 
   2158             // Undo the typing, which verifies that the original undo operation was parceled
   2159             // correctly.
   2160             mTextView.onTextContextMenuItem(android.R.id.undo);
   2161             assertEquals("", mTextView.getText().toString());
   2162 
   2163             // Parcel and unparcel the undo stack (which is empty but has been used and may
   2164             // contain other state).
   2165             Parcelable state = mTextView.onSaveInstanceState();
   2166             mTextView.onRestoreInstanceState(state);
   2167         });
   2168         mInstrumentation.waitForIdleSync();
   2169     }
   2170 
   2171     @Test
   2172     public void testUndo_saveInstanceStateEmpty() throws Throwable {
   2173         initTextViewForTypingOnUiThread();
   2174 
   2175         // Type and delete to create two new undo operations.
   2176         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "a");
   2177         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_DEL);
   2178         mActivityRule.runOnUiThread(() -> {
   2179             // Empty the undo stack then parcel and unparcel the TextView. While the undo
   2180             // stack contains no operations it may contain other state.
   2181             mTextView.onTextContextMenuItem(android.R.id.undo);
   2182             mTextView.onTextContextMenuItem(android.R.id.undo);
   2183             Parcelable state = mTextView.onSaveInstanceState();
   2184             mTextView.onRestoreInstanceState(state);
   2185         });
   2186         mInstrumentation.waitForIdleSync();
   2187 
   2188         // Create two more undo operations.
   2189         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "b");
   2190         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_DEL);
   2191         mActivityRule.runOnUiThread(() -> {
   2192             // Verify undo still works.
   2193             mTextView.onTextContextMenuItem(android.R.id.undo);
   2194             assertEquals("b", mTextView.getText().toString());
   2195             mTextView.onTextContextMenuItem(android.R.id.undo);
   2196             assertEquals("", mTextView.getText().toString());
   2197         });
   2198         mInstrumentation.waitForIdleSync();
   2199     }
   2200 
   2201     @UiThreadTest
   2202     @Test
   2203     public void testCopyAndPaste() {
   2204         initTextViewForTyping();
   2205 
   2206         mTextView.setText("abcd", BufferType.EDITABLE);
   2207         mTextView.setSelected(true);
   2208 
   2209         // Copy "bc".
   2210         Selection.setSelection((Spannable) mTextView.getText(), 1, 3);
   2211         mTextView.onTextContextMenuItem(android.R.id.copy);
   2212 
   2213         // Paste "bc" between "b" and "c".
   2214         Selection.setSelection((Spannable) mTextView.getText(), 2, 2);
   2215         mTextView.onTextContextMenuItem(android.R.id.paste);
   2216         assertEquals("abbccd", mTextView.getText().toString());
   2217 
   2218         // Select entire text and paste "bc".
   2219         Selection.selectAll((Spannable) mTextView.getText());
   2220         mTextView.onTextContextMenuItem(android.R.id.paste);
   2221         assertEquals("bc", mTextView.getText().toString());
   2222     }
   2223 
   2224     @Test
   2225     public void testCopyAndPaste_byKey() throws Throwable {
   2226         initTextViewForTypingOnUiThread();
   2227 
   2228         // Type "abc".
   2229         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   2230         mActivityRule.runOnUiThread(() -> {
   2231             // Select "bc"
   2232             Selection.setSelection((Spannable) mTextView.getText(), 1, 3);
   2233         });
   2234         mInstrumentation.waitForIdleSync();
   2235         // Copy "bc"
   2236         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_COPY);
   2237 
   2238         mActivityRule.runOnUiThread(() -> {
   2239             // Set cursor between 'b' and 'c'.
   2240             Selection.setSelection((Spannable) mTextView.getText(), 2, 2);
   2241         });
   2242         mInstrumentation.waitForIdleSync();
   2243         // Paste "bc"
   2244         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_PASTE);
   2245         assertEquals("abbcc", mTextView.getText().toString());
   2246 
   2247         mActivityRule.runOnUiThread(() -> {
   2248             Selection.selectAll((Spannable) mTextView.getText());
   2249             KeyEvent copyWithMeta = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
   2250                     KeyEvent.KEYCODE_COPY, 0, KeyEvent.META_SHIFT_LEFT_ON);
   2251             // Shift + copy doesn't perform copy.
   2252             mTextView.onKeyDown(KeyEvent.KEYCODE_COPY, copyWithMeta);
   2253             Selection.setSelection((Spannable) mTextView.getText(), 0, 0);
   2254             mTextView.onTextContextMenuItem(android.R.id.paste);
   2255             assertEquals("bcabbcc", mTextView.getText().toString());
   2256 
   2257             Selection.selectAll((Spannable) mTextView.getText());
   2258             copyWithMeta = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_COPY, 0,
   2259                     KeyEvent.META_CTRL_LEFT_ON);
   2260             // Control + copy doesn't perform copy.
   2261             mTextView.onKeyDown(KeyEvent.KEYCODE_COPY, copyWithMeta);
   2262             Selection.setSelection((Spannable) mTextView.getText(), 0, 0);
   2263             mTextView.onTextContextMenuItem(android.R.id.paste);
   2264             assertEquals("bcbcabbcc", mTextView.getText().toString());
   2265 
   2266             Selection.selectAll((Spannable) mTextView.getText());
   2267             copyWithMeta = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_COPY, 0,
   2268                     KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_CTRL_LEFT_ON);
   2269             // Control + Shift + copy doesn't perform copy.
   2270             mTextView.onKeyDown(KeyEvent.KEYCODE_COPY, copyWithMeta);
   2271             Selection.setSelection((Spannable) mTextView.getText(), 0, 0);
   2272             mTextView.onTextContextMenuItem(android.R.id.paste);
   2273             assertEquals("bcbcbcabbcc", mTextView.getText().toString());
   2274         });
   2275         mInstrumentation.waitForIdleSync();
   2276     }
   2277 
   2278     @Test
   2279     public void testCopyAndPaste_byCtrlInsert() throws Throwable {
   2280         // Test copy-and-paste by Ctrl-Insert and Shift-Insert.
   2281         initTextViewForTypingOnUiThread();
   2282 
   2283         // Type "abc"
   2284         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   2285         mActivityRule.runOnUiThread(() -> {
   2286             // Select "bc"
   2287             Selection.setSelection((Spannable) mTextView.getText(), 1, 3);
   2288         });
   2289         mInstrumentation.waitForIdleSync();
   2290 
   2291         // Copy "bc"
   2292         CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTextView,
   2293                 KeyEvent.KEYCODE_INSERT, KeyEvent.KEYCODE_CTRL_LEFT);
   2294         mActivityRule.runOnUiThread(() -> {
   2295             // Set cursor between 'b' and 'c'
   2296             Selection.setSelection((Spannable) mTextView.getText(), 2, 2);
   2297         });
   2298         mInstrumentation.waitForIdleSync();
   2299 
   2300         // Paste "bc"
   2301         CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTextView,
   2302                 KeyEvent.KEYCODE_INSERT, KeyEvent.KEYCODE_SHIFT_LEFT);
   2303         assertEquals("abbcc", mTextView.getText().toString());
   2304     }
   2305 
   2306     @UiThreadTest
   2307     @Test
   2308     public void testCutAndPaste() {
   2309         initTextViewForTyping();
   2310 
   2311         mTextView.setText("abcd", BufferType.EDITABLE);
   2312         mTextView.setSelected(true);
   2313 
   2314         // Cut "bc".
   2315         Selection.setSelection((Spannable) mTextView.getText(), 1, 3);
   2316         mTextView.onTextContextMenuItem(android.R.id.cut);
   2317         assertEquals("ad", mTextView.getText().toString());
   2318 
   2319         // Cut "ad".
   2320         Selection.setSelection((Spannable) mTextView.getText(), 0, 2);
   2321         mTextView.onTextContextMenuItem(android.R.id.cut);
   2322         assertEquals("", mTextView.getText().toString());
   2323 
   2324         // Paste "ad".
   2325         mTextView.onTextContextMenuItem(android.R.id.paste);
   2326         assertEquals("ad", mTextView.getText().toString());
   2327     }
   2328 
   2329     @Test
   2330     public void testCutAndPaste_byKey() throws Throwable {
   2331         initTextViewForTypingOnUiThread();
   2332 
   2333         // Type "abc".
   2334         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   2335         mActivityRule.runOnUiThread(() -> {
   2336             // Select "bc"
   2337             Selection.setSelection((Spannable) mTextView.getText(), 1, 3);
   2338         });
   2339         mInstrumentation.waitForIdleSync();
   2340         // Cut "bc"
   2341         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_CUT);
   2342 
   2343         mActivityRule.runOnUiThread(() -> {
   2344             assertEquals("a", mTextView.getText().toString());
   2345             // Move cursor to the head
   2346             Selection.setSelection((Spannable) mTextView.getText(), 0, 0);
   2347         });
   2348         mInstrumentation.waitForIdleSync();
   2349         // Paste "bc"
   2350         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_PASTE);
   2351         assertEquals("bca", mTextView.getText().toString());
   2352 
   2353         mActivityRule.runOnUiThread(() -> {
   2354             Selection.selectAll((Spannable) mTextView.getText());
   2355             KeyEvent cutWithMeta = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
   2356                     KeyEvent.KEYCODE_CUT, 0, KeyEvent.META_SHIFT_LEFT_ON);
   2357             // Shift + cut doesn't perform cut.
   2358             mTextView.onKeyDown(KeyEvent.KEYCODE_CUT, cutWithMeta);
   2359             assertEquals("bca", mTextView.getText().toString());
   2360 
   2361             cutWithMeta = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CUT, 0,
   2362                     KeyEvent.META_CTRL_LEFT_ON);
   2363             // Control + cut doesn't perform cut.
   2364             mTextView.onKeyDown(KeyEvent.KEYCODE_CUT, cutWithMeta);
   2365             assertEquals("bca", mTextView.getText().toString());
   2366 
   2367             cutWithMeta = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CUT, 0,
   2368                     KeyEvent.META_SHIFT_LEFT_ON | KeyEvent.META_CTRL_LEFT_ON);
   2369             // Control + Shift + cut doesn't perform cut.
   2370             mTextView.onKeyDown(KeyEvent.KEYCODE_CUT, cutWithMeta);
   2371             assertEquals("bca", mTextView.getText().toString());
   2372         });
   2373         mInstrumentation.waitForIdleSync();
   2374     }
   2375 
   2376     @Test
   2377     public void testCutAndPaste_byShiftDelete() throws Throwable {
   2378         // Test cut and paste by Shift-Delete and Shift-Insert
   2379         initTextViewForTypingOnUiThread();
   2380 
   2381         // Type "abc".
   2382         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "abc");
   2383         mActivityRule.runOnUiThread(() -> {
   2384             // Select "bc"
   2385             Selection.setSelection((Spannable) mTextView.getText(), 1, 3);
   2386         });
   2387         mInstrumentation.waitForIdleSync();
   2388 
   2389         // Cut "bc"
   2390         CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTextView,
   2391                 KeyEvent.KEYCODE_FORWARD_DEL, KeyEvent.KEYCODE_SHIFT_LEFT);
   2392         mActivityRule.runOnUiThread(() -> {
   2393             assertEquals("a", mTextView.getText().toString());
   2394             // Move cursor to the head
   2395             Selection.setSelection((Spannable) mTextView.getText(), 0, 0);
   2396         });
   2397         mInstrumentation.waitForIdleSync();
   2398 
   2399         // Paste "bc"
   2400         CtsKeyEventUtil.sendKeyWhileHoldingModifier(mInstrumentation, mTextView,
   2401                 KeyEvent.KEYCODE_INSERT, KeyEvent.KEYCODE_SHIFT_LEFT);
   2402         assertEquals("bca", mTextView.getText().toString());
   2403     }
   2404 
   2405     private static boolean hasSpansAtMiddleOfText(final TextView textView, final Class<?> type) {
   2406         final Spannable spannable = (Spannable)textView.getText();
   2407         final int at = spannable.length() / 2;
   2408         return spannable.getSpans(at, at, type).length > 0;
   2409     }
   2410 
   2411     @UiThreadTest
   2412     @Test
   2413     public void testCutAndPaste_withAndWithoutStyle() {
   2414         initTextViewForTyping();
   2415 
   2416         mTextView.setText("example", BufferType.EDITABLE);
   2417         mTextView.setSelected(true);
   2418 
   2419         // Set URLSpan.
   2420         final Spannable spannable = (Spannable) mTextView.getText();
   2421         spannable.setSpan(new URLSpan("http://example.com"), 0, spannable.length(), 0);
   2422         assertTrue(hasSpansAtMiddleOfText(mTextView, URLSpan.class));
   2423 
   2424         // Cut entire text.
   2425         Selection.selectAll((Spannable) mTextView.getText());
   2426         mTextView.onTextContextMenuItem(android.R.id.cut);
   2427         assertEquals("", mTextView.getText().toString());
   2428 
   2429         // Paste without style.
   2430         mTextView.onTextContextMenuItem(android.R.id.pasteAsPlainText);
   2431         assertEquals("example", mTextView.getText().toString());
   2432         // Check that the text doesn't have URLSpan.
   2433         assertFalse(hasSpansAtMiddleOfText(mTextView, URLSpan.class));
   2434 
   2435         // Paste with style.
   2436         Selection.selectAll((Spannable) mTextView.getText());
   2437         mTextView.onTextContextMenuItem(android.R.id.paste);
   2438         assertEquals("example", mTextView.getText().toString());
   2439         // Check that the text has URLSpan.
   2440         assertTrue(hasSpansAtMiddleOfText(mTextView, URLSpan.class));
   2441     }
   2442 
   2443     @UiThreadTest
   2444     @Test
   2445     public void testSaveInstanceState() {
   2446         // should save text when freezesText=true
   2447         TextView originalTextView = new TextView(mActivity);
   2448         final String text = "This is a string";
   2449         originalTextView.setText(text);
   2450         originalTextView.setFreezesText(true);  // needed to actually save state
   2451         Parcelable state = originalTextView.onSaveInstanceState();
   2452 
   2453         TextView restoredTextView = new TextView(mActivity);
   2454         restoredTextView.onRestoreInstanceState(state);
   2455         assertEquals(text, restoredTextView.getText().toString());
   2456     }
   2457 
   2458     @UiThreadTest
   2459     @Test
   2460     public void testOnSaveInstanceState_whenFreezesTextIsFalse() {
   2461         final String text = "This is a string";
   2462         { // should not save text when freezesText=false
   2463             // prepare TextView for before saveInstanceState
   2464             TextView textView1 = new TextView(mActivity);
   2465             textView1.setFreezesText(false);
   2466             textView1.setText(text);
   2467 
   2468             // prepare TextView for after saveInstanceState
   2469             TextView textView2 = new TextView(mActivity);
   2470             textView2.setFreezesText(false);
   2471 
   2472             textView2.onRestoreInstanceState(textView1.onSaveInstanceState());
   2473 
   2474             assertEquals("", textView2.getText().toString());
   2475         }
   2476 
   2477         { // should not save text even when textIsSelectable=true
   2478             // prepare TextView for before saveInstanceState
   2479             TextView textView1 = new TextView(mActivity);
   2480             textView1.setFreezesText(false);
   2481             textView1.setTextIsSelectable(true);
   2482             textView1.setText(text);
   2483 
   2484             // prepare TextView for after saveInstanceState
   2485             TextView textView2 = new TextView(mActivity);
   2486             textView2.setFreezesText(false);
   2487             textView2.setTextIsSelectable(true);
   2488 
   2489             textView2.onRestoreInstanceState(textView1.onSaveInstanceState());
   2490 
   2491             assertEquals("", textView2.getText().toString());
   2492         }
   2493     }
   2494 
   2495     @UiThreadTest
   2496     @SmallTest
   2497     @Test
   2498     public void testOnSaveInstanceState_doesNotSaveSelectionWhenDoesNotExist() {
   2499         // prepare TextView for before saveInstanceState
   2500         final String text = "This is a string";
   2501         TextView textView1 = new TextView(mActivity);
   2502         textView1.setFreezesText(true);
   2503         textView1.setText(text);
   2504 
   2505         // prepare TextView for after saveInstanceState
   2506         TextView textView2 = new TextView(mActivity);
   2507         textView2.setFreezesText(true);
   2508 
   2509         textView2.onRestoreInstanceState(textView1.onSaveInstanceState());
   2510 
   2511         assertEquals(-1, textView2.getSelectionStart());
   2512         assertEquals(-1, textView2.getSelectionEnd());
   2513     }
   2514 
   2515     @UiThreadTest
   2516     @SmallTest
   2517     @Test
   2518     public void testOnSaveInstanceState_doesNotRestoreSelectionWhenTextIsAbsent() {
   2519         // prepare TextView for before saveInstanceState
   2520         final String text = "This is a string";
   2521         TextView textView1 = new TextView(mActivity);
   2522         textView1.setFreezesText(false);
   2523         textView1.setTextIsSelectable(true);
   2524         textView1.setText(text);
   2525         Selection.setSelection((Spannable) textView1.getText(), 2, text.length() - 2);
   2526 
   2527         // prepare TextView for after saveInstanceState
   2528         TextView textView2 = new TextView(mActivity);
   2529         textView2.setFreezesText(false);
   2530         textView2.setTextIsSelectable(true);
   2531 
   2532         textView2.onRestoreInstanceState(textView1.onSaveInstanceState());
   2533 
   2534         assertEquals("", textView2.getText().toString());
   2535         //when textIsSelectable, selection start and end are initialized to 0
   2536         assertEquals(0, textView2.getSelectionStart());
   2537         assertEquals(0, textView2.getSelectionEnd());
   2538     }
   2539 
   2540     @UiThreadTest
   2541     @SmallTest
   2542     @Test
   2543     public void testOnSaveInstanceState_savesSelectionWhenExists() {
   2544         final String text = "This is a string";
   2545         // prepare TextView for before saveInstanceState
   2546         TextView textView1 = new TextView(mActivity);
   2547         textView1.setFreezesText(true);
   2548         textView1.setTextIsSelectable(true);
   2549         textView1.setText(text);
   2550         Selection.setSelection((Spannable) textView1.getText(), 2, text.length() - 2);
   2551 
   2552         // prepare TextView for after saveInstanceState
   2553         TextView textView2 = new TextView(mActivity);
   2554         textView2.setFreezesText(true);
   2555         textView2.setTextIsSelectable(true);
   2556 
   2557         textView2.onRestoreInstanceState(textView1.onSaveInstanceState());
   2558 
   2559         assertEquals(textView1.getSelectionStart(), textView2.getSelectionStart());
   2560         assertEquals(textView1.getSelectionEnd(), textView2.getSelectionEnd());
   2561     }
   2562 
   2563     @UiThreadTest
   2564     @Test
   2565     public void testSetText() {
   2566         TextView tv = findTextView(R.id.textview_text);
   2567 
   2568         int resId = R.string.text_view_hint;
   2569         String result = mActivity.getResources().getString(resId);
   2570 
   2571         tv.setText(resId, BufferType.EDITABLE);
   2572         assertEquals(result, tv.getText().toString());
   2573         assertTrue(tv.getText() instanceof Editable);
   2574 
   2575         tv.setText(resId, BufferType.SPANNABLE);
   2576         assertEquals(result, tv.getText().toString());
   2577         assertTrue(tv.getText() instanceof Spannable);
   2578 
   2579         try {
   2580             tv.setText(-1, BufferType.EDITABLE);
   2581             fail("Should throw exception with illegal id");
   2582         } catch (NotFoundException e) {
   2583         }
   2584     }
   2585 
   2586     @UiThreadTest
   2587     @Test
   2588     public void testAccessHint() {
   2589         mActivity.setContentView(R.layout.textview_hint_linksclickable_freezestext);
   2590 
   2591         mTextView = findTextView(R.id.hint_linksClickable_freezesText_default);
   2592         assertNull(mTextView.getHint());
   2593 
   2594         mTextView = findTextView(R.id.hint_blank);
   2595         assertEquals("", mTextView.getHint());
   2596 
   2597         mTextView = findTextView(R.id.hint_string);
   2598         assertEquals(mActivity.getResources().getString(R.string.text_view_simple_hint),
   2599                 mTextView.getHint());
   2600 
   2601         mTextView = findTextView(R.id.hint_resid);
   2602         assertEquals(mActivity.getResources().getString(R.string.text_view_hint),
   2603                 mTextView.getHint());
   2604 
   2605         mTextView.setHint("This is hint");
   2606         assertEquals("This is hint", mTextView.getHint().toString());
   2607 
   2608         mTextView.setHint(R.string.text_view_hello);
   2609         assertEquals(mActivity.getResources().getString(R.string.text_view_hello),
   2610                 mTextView.getHint().toString());
   2611 
   2612         // Non-exist resid
   2613         try {
   2614             mTextView.setHint(-1);
   2615             fail("Should throw exception if id is illegal");
   2616         } catch (NotFoundException e) {
   2617         }
   2618     }
   2619 
   2620     @Test
   2621     public void testAccessError() throws Throwable {
   2622         mTextView = findTextView(R.id.textview_text);
   2623         assertNull(mTextView.getError());
   2624 
   2625         final String errorText = "Oops! There is an error";
   2626 
   2627         mActivityRule.runOnUiThread(() -> mTextView.setError(null));
   2628         mInstrumentation.waitForIdleSync();
   2629         assertNull(mTextView.getError());
   2630 
   2631         final Drawable icon = TestUtils.getDrawable(mActivity, R.drawable.failed);
   2632         mActivityRule.runOnUiThread(() -> mTextView.setError(errorText, icon));
   2633         mInstrumentation.waitForIdleSync();
   2634         assertEquals(errorText, mTextView.getError().toString());
   2635         // can not check whether the drawable is set correctly
   2636 
   2637         mActivityRule.runOnUiThread(() -> mTextView.setError(null, null));
   2638         mInstrumentation.waitForIdleSync();
   2639         assertNull(mTextView.getError());
   2640 
   2641         mActivityRule.runOnUiThread(() -> {
   2642             mTextView.setKeyListener(DigitsKeyListener.getInstance(""));
   2643             mTextView.setText("", BufferType.EDITABLE);
   2644             mTextView.setError(errorText);
   2645             mTextView.requestFocus();
   2646         });
   2647         mInstrumentation.waitForIdleSync();
   2648 
   2649         assertEquals(errorText, mTextView.getError().toString());
   2650 
   2651         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "a");
   2652         // a key event that will not change the TextView's text
   2653         assertEquals("", mTextView.getText().toString());
   2654         // The icon and error message will not be reset to null
   2655         assertEquals(errorText, mTextView.getError().toString());
   2656 
   2657         mActivityRule.runOnUiThread(() -> {
   2658             mTextView.setKeyListener(DigitsKeyListener.getInstance());
   2659             mTextView.setText("", BufferType.EDITABLE);
   2660             mTextView.setError(errorText);
   2661             mTextView.requestFocus();
   2662         });
   2663         mInstrumentation.waitForIdleSync();
   2664 
   2665         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "1");
   2666         // a key event cause changes to the TextView's text
   2667         assertEquals("1", mTextView.getText().toString());
   2668         // the error message and icon will be cleared.
   2669         assertNull(mTextView.getError());
   2670     }
   2671 
   2672     @Test
   2673     public void testAccessFilters() throws Throwable {
   2674         final InputFilter[] expected = { new InputFilter.AllCaps(),
   2675                 new InputFilter.LengthFilter(2) };
   2676 
   2677         final QwertyKeyListener qwertyKeyListener
   2678                 = QwertyKeyListener.getInstance(false, Capitalize.NONE);
   2679         mActivityRule.runOnUiThread(() -> {
   2680             mTextView = findTextView(R.id.textview_text);
   2681             mTextView.setKeyListener(qwertyKeyListener);
   2682             mTextView.setText("", BufferType.EDITABLE);
   2683             mTextView.setFilters(expected);
   2684             mTextView.requestFocus();
   2685         });
   2686         mInstrumentation.waitForIdleSync();
   2687 
   2688         assertSame(expected, mTextView.getFilters());
   2689 
   2690         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "a");
   2691         // the text is capitalized by InputFilter.AllCaps
   2692         assertEquals("A", mTextView.getText().toString());
   2693         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "b");
   2694         // the text is capitalized by InputFilter.AllCaps
   2695         assertEquals("AB", mTextView.getText().toString());
   2696         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "c");
   2697         // 'C' could not be accepted, because there is a length filter.
   2698         assertEquals("AB", mTextView.getText().toString());
   2699 
   2700         try {
   2701             mTextView.setFilters(null);
   2702             fail("Should throw IllegalArgumentException!");
   2703         } catch (IllegalArgumentException e) {
   2704         }
   2705     }
   2706 
   2707     @Test
   2708     public void testGetFocusedRect() throws Throwable {
   2709         Rect rc = new Rect();
   2710 
   2711         // Basic
   2712         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
   2713         mInstrumentation.waitForIdleSync();
   2714         mTextView.getFocusedRect(rc);
   2715         assertEquals(mTextView.getScrollX(), rc.left);
   2716         assertEquals(mTextView.getScrollX() + mTextView.getWidth(), rc.right);
   2717         assertEquals(mTextView.getScrollY(), rc.top);
   2718         assertEquals(mTextView.getScrollY() + mTextView.getHeight(), rc.bottom);
   2719 
   2720         // Single line
   2721         mTextView = findTextView(R.id.textview_text);
   2722         mTextView.getFocusedRect(rc);
   2723         assertEquals(mTextView.getScrollX(), rc.left);
   2724         assertEquals(mTextView.getScrollX() + mTextView.getWidth(), rc.right);
   2725         assertEquals(mTextView.getScrollY(), rc.top);
   2726         assertEquals(mTextView.getScrollY() + mTextView.getHeight(), rc.bottom);
   2727 
   2728         mActivityRule.runOnUiThread(() -> {
   2729             final SpannableString text = new SpannableString(mTextView.getText());
   2730             mTextView.setTextIsSelectable(true);
   2731             mTextView.setText(text);
   2732             Selection.setSelection((Spannable) mTextView.getText(), 3, 13);
   2733         });
   2734         mInstrumentation.waitForIdleSync();
   2735         mTextView.getFocusedRect(rc);
   2736         assertNotNull(mTextView.getLayout());
   2737         /* Cursor coordinates from getPrimaryHorizontal() may have a fractional
   2738          * component, while the result of getFocusedRect is in int coordinates.
   2739          * It's not practical for these to match exactly, so we compare that the
   2740          * integer components match - there can be a fractional pixel
   2741          * discrepancy, which should be okay for all practical applications. */
   2742         assertEquals((int) mTextView.getLayout().getPrimaryHorizontal(3), rc.left);
   2743         assertEquals((int) mTextView.getLayout().getPrimaryHorizontal(13), rc.right);
   2744         assertEquals(mTextView.getLayout().getLineTop(0), rc.top);
   2745         assertEquals(mTextView.getLayout().getLineBottom(0), rc.bottom);
   2746 
   2747         mActivityRule.runOnUiThread(() -> {
   2748             final SpannableString text = new SpannableString(mTextView.getText());
   2749             mTextView.setTextIsSelectable(true);
   2750             mTextView.setText(text);
   2751             Selection.setSelection((Spannable) mTextView.getText(), 13, 3);
   2752         });
   2753         mInstrumentation.waitForIdleSync();
   2754         mTextView.getFocusedRect(rc);
   2755         assertNotNull(mTextView.getLayout());
   2756         assertEquals((int) mTextView.getLayout().getPrimaryHorizontal(3) - 2, rc.left);
   2757         assertEquals((int) mTextView.getLayout().getPrimaryHorizontal(3) + 2, rc.right);
   2758         assertEquals(mTextView.getLayout().getLineTop(0), rc.top);
   2759         assertEquals(mTextView.getLayout().getLineBottom(0), rc.bottom);
   2760 
   2761         // Multi lines
   2762         mTextView = findTextView(R.id.textview_text_two_lines);
   2763         mTextView.getFocusedRect(rc);
   2764         assertEquals(mTextView.getScrollX(), rc.left);
   2765         assertEquals(mTextView.getScrollX() + mTextView.getWidth(), rc.right);
   2766         assertEquals(mTextView.getScrollY(), rc.top);
   2767         assertEquals(mTextView.getScrollY() + mTextView.getHeight(), rc.bottom);
   2768 
   2769         mActivityRule.runOnUiThread(() -> {
   2770             final SpannableString text = new SpannableString(mTextView.getText());
   2771             mTextView.setTextIsSelectable(true);
   2772             mTextView.setText(text);
   2773             Selection.setSelection((Spannable) mTextView.getText(), 2, 4);
   2774         });
   2775         mInstrumentation.waitForIdleSync();
   2776         mTextView.getFocusedRect(rc);
   2777         assertNotNull(mTextView.getLayout());
   2778         assertEquals((int) mTextView.getLayout().getPrimaryHorizontal(2), rc.left);
   2779         assertEquals((int) mTextView.getLayout().getPrimaryHorizontal(4), rc.right);
   2780         assertEquals(mTextView.getLayout().getLineTop(0), rc.top);
   2781         assertEquals(mTextView.getLayout().getLineBottom(0), rc.bottom);
   2782 
   2783         mActivityRule.runOnUiThread(() -> {
   2784             final SpannableString text = new SpannableString(mTextView.getText());
   2785             mTextView.setTextIsSelectable(true);
   2786             mTextView.setText(text);
   2787             // cross the "\n" and two lines
   2788             Selection.setSelection((Spannable) mTextView.getText(), 2, 10);
   2789         });
   2790         mInstrumentation.waitForIdleSync();
   2791         mTextView.getFocusedRect(rc);
   2792         Path path = new Path();
   2793         mTextView.getLayout().getSelectionPath(2, 10, path);
   2794         RectF rcf = new RectF();
   2795         path.computeBounds(rcf, true);
   2796         assertNotNull(mTextView.getLayout());
   2797         assertEquals(rcf.left - 1, (float) rc.left, 0.0f);
   2798         assertEquals(rcf.right + 1, (float) rc.right, 0.0f);
   2799         assertEquals(mTextView.getLayout().getLineTop(0), rc.top);
   2800         assertEquals(mTextView.getLayout().getLineBottom(1), rc.bottom);
   2801 
   2802         // Exception
   2803         try {
   2804             mTextView.getFocusedRect(null);
   2805             fail("Should throw NullPointerException!");
   2806         } catch (NullPointerException e) {
   2807         }
   2808     }
   2809 
   2810     @Test
   2811     public void testGetLineCount() throws Throwable {
   2812         mActivityRule.runOnUiThread(() -> mTextView = findTextView(R.id.textview_text));
   2813         mInstrumentation.waitForIdleSync();
   2814         // this is an one line text with default setting.
   2815         assertEquals(1, mTextView.getLineCount());
   2816 
   2817         // make it multi-lines
   2818         setMaxWidth(mTextView.getWidth() / 3);
   2819         assertTrue(1 < mTextView.getLineCount());
   2820 
   2821         // make it to an one line
   2822         setMaxWidth(Integer.MAX_VALUE);
   2823         assertEquals(1, mTextView.getLineCount());
   2824 
   2825         // set min lines don't effect the lines count for actual text.
   2826         setMinLines(12);
   2827         assertEquals(1, mTextView.getLineCount());
   2828 
   2829         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
   2830         mInstrumentation.waitForIdleSync();
   2831         // the internal Layout has not been built.
   2832         assertNull(mTextView.getLayout());
   2833         assertEquals(0, mTextView.getLineCount());
   2834     }
   2835 
   2836     @Test
   2837     public void testGetLineBounds() throws Throwable {
   2838         Rect rc = new Rect();
   2839         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
   2840         mInstrumentation.waitForIdleSync();
   2841         assertEquals(0, mTextView.getLineBounds(0, null));
   2842 
   2843         assertEquals(0, mTextView.getLineBounds(0, rc));
   2844         assertEquals(0, rc.left);
   2845         assertEquals(0, rc.right);
   2846         assertEquals(0, rc.top);
   2847         assertEquals(0, rc.bottom);
   2848 
   2849         mTextView = findTextView(R.id.textview_text);
   2850         assertEquals(mTextView.getBaseline(), mTextView.getLineBounds(0, null));
   2851 
   2852         assertEquals(mTextView.getBaseline(), mTextView.getLineBounds(0, rc));
   2853         assertEquals(0, rc.left);
   2854         assertEquals(mTextView.getWidth(), rc.right);
   2855         assertEquals(0, rc.top);
   2856         assertEquals(mTextView.getHeight(), rc.bottom);
   2857 
   2858         mActivityRule.runOnUiThread(() -> {
   2859             mTextView.setPadding(1, 2, 3, 4);
   2860             mTextView.setGravity(Gravity.BOTTOM);
   2861         });
   2862         mInstrumentation.waitForIdleSync();
   2863         assertEquals(mTextView.getBaseline(), mTextView.getLineBounds(0, rc));
   2864         assertEquals(mTextView.getTotalPaddingLeft(), rc.left);
   2865         assertEquals(mTextView.getWidth() - mTextView.getTotalPaddingRight(), rc.right);
   2866         assertEquals(mTextView.getTotalPaddingTop(), rc.top);
   2867         assertEquals(mTextView.getHeight() - mTextView.getTotalPaddingBottom(), rc.bottom);
   2868     }
   2869 
   2870     @Test
   2871     public void testGetBaseLine() throws Throwable {
   2872         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
   2873         mInstrumentation.waitForIdleSync();
   2874         assertEquals(-1, mTextView.getBaseline());
   2875 
   2876         mTextView = findTextView(R.id.textview_text);
   2877         assertEquals(mTextView.getLayout().getLineBaseline(0), mTextView.getBaseline());
   2878 
   2879         mActivityRule.runOnUiThread(() -> {
   2880             mTextView.setPadding(1, 2, 3, 4);
   2881             mTextView.setGravity(Gravity.BOTTOM);
   2882         });
   2883         mInstrumentation.waitForIdleSync();
   2884         int expected = mTextView.getTotalPaddingTop() + mTextView.getLayout().getLineBaseline(0);
   2885         assertEquals(expected, mTextView.getBaseline());
   2886     }
   2887 
   2888     @Test
   2889     public void testPressKey() throws Throwable {
   2890         initTextViewForTypingOnUiThread();
   2891 
   2892         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "a");
   2893         assertEquals("a", mTextView.getText().toString());
   2894         CtsKeyEventUtil.sendString(mInstrumentation, mTextView, "b");
   2895         assertEquals("ab", mTextView.getText().toString());
   2896         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, KeyEvent.KEYCODE_DEL);
   2897         assertEquals("a", mTextView.getText().toString());
   2898     }
   2899 
   2900     @Test
   2901     public void testKeyNavigation() throws Throwable {
   2902         initTextViewForTypingOnUiThread();
   2903         mActivityRule.runOnUiThread(() -> {
   2904             mActivity.findViewById(R.id.textview_singleLine).setFocusableInTouchMode(true);
   2905             mActivity.findViewById(R.id.textview_text_two_lines).setFocusableInTouchMode(true);
   2906             mTextView.setMovementMethod(ArrowKeyMovementMethod.getInstance());
   2907             mTextView.setText("abc");
   2908             mTextView.setFocusableInTouchMode(true);
   2909         });
   2910 
   2911         mTextView.requestFocus();
   2912         mInstrumentation.waitForIdleSync();
   2913         assertTrue(mTextView.isFocused());
   2914 
   2915         // Pure-keyboard arrows should not cause focus to leave the textfield
   2916         CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTextView, KeyEvent.KEYCODE_DPAD_UP);
   2917         mInstrumentation.waitForIdleSync();
   2918         assertTrue(mTextView.isFocused());
   2919 
   2920         // Non-pure-keyboard arrows, however, should.
   2921         int dpadRemote = InputDevice.SOURCE_DPAD | InputDevice.SOURCE_KEYBOARD;
   2922         sendSourceKeyDownUp(mInstrumentation, mTextView, KeyEvent.KEYCODE_DPAD_UP, dpadRemote);
   2923         mInstrumentation.waitForIdleSync();
   2924         assertFalse(mTextView.isFocused());
   2925 
   2926         sendSourceKeyDownUp(mInstrumentation, mTextView, KeyEvent.KEYCODE_DPAD_DOWN, dpadRemote);
   2927         mInstrumentation.waitForIdleSync();
   2928         assertTrue(mTextView.isFocused());
   2929 
   2930         // Tab should
   2931         CtsKeyEventUtil.sendKeyDownUp(mInstrumentation, mTextView, KeyEvent.KEYCODE_TAB);
   2932         mInstrumentation.waitForIdleSync();
   2933         assertFalse(mTextView.isFocused());
   2934     }
   2935 
   2936     private void sendSourceKeyDownUp(Instrumentation instrumentation, View targetView, int key,
   2937             int source) {
   2938         KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, key);
   2939         event.setSource(source);
   2940         CtsKeyEventUtil.sendKey(instrumentation, targetView, event);
   2941         event = new KeyEvent(KeyEvent.ACTION_UP, key);
   2942         event.setSource(source);
   2943         CtsKeyEventUtil.sendKey(instrumentation, targetView, event);
   2944     }
   2945 
   2946     @Test
   2947     public void testSetIncludeFontPadding() throws Throwable {
   2948         mTextView = findTextView(R.id.textview_text);
   2949         assertTrue(mTextView.getIncludeFontPadding());
   2950         mActivityRule.runOnUiThread(() -> {
   2951             mTextView.setWidth(mTextView.getWidth() / 3);
   2952             mTextView.setPadding(1, 2, 3, 4);
   2953             mTextView.setGravity(Gravity.BOTTOM);
   2954         });
   2955         mInstrumentation.waitForIdleSync();
   2956 
   2957         int oldHeight = mTextView.getHeight();
   2958         mActivityRule.runOnUiThread(() -> mTextView.setIncludeFontPadding(false));
   2959         mInstrumentation.waitForIdleSync();
   2960 
   2961         assertTrue(mTextView.getHeight() < oldHeight);
   2962         assertFalse(mTextView.getIncludeFontPadding());
   2963     }
   2964 
   2965     @UiThreadTest
   2966     @Test
   2967     public void testScroll() {
   2968         mTextView = new TextView(mActivity);
   2969 
   2970         assertEquals(0, mTextView.getScrollX());
   2971         assertEquals(0, mTextView.getScrollY());
   2972 
   2973         //don't set the Scroller, nothing changed.
   2974         mTextView.computeScroll();
   2975         assertEquals(0, mTextView.getScrollX());
   2976         assertEquals(0, mTextView.getScrollY());
   2977 
   2978         //set the Scroller
   2979         Scroller s = new Scroller(mActivity);
   2980         assertNotNull(s);
   2981         s.startScroll(0, 0, 320, 480, 0);
   2982         s.abortAnimation();
   2983         s.forceFinished(false);
   2984         mTextView.setScroller(s);
   2985 
   2986         mTextView.computeScroll();
   2987         assertEquals(320, mTextView.getScrollX());
   2988         assertEquals(480, mTextView.getScrollY());
   2989     }
   2990 
   2991     @Test
   2992     public void testDebug() throws Throwable {
   2993         mActivityRule.runOnUiThread(() -> {
   2994             mTextView = new TextView(mActivity);
   2995             mTextView.debug(0);
   2996             mTextView.setText("Hello!");
   2997         });
   2998         mInstrumentation.waitForIdleSync();
   2999 
   3000         layout(mTextView);
   3001         mTextView.debug(1);
   3002     }
   3003 
   3004     @UiThreadTest
   3005     @Test
   3006     public void testSelection() throws Throwable {
   3007         mTextView = new TextView(mActivity);
   3008         String text = "This is the content";
   3009         mTextView.setText(text, BufferType.SPANNABLE);
   3010         assertFalse(mTextView.hasSelection());
   3011 
   3012         Selection.selectAll((Spannable) mTextView.getText());
   3013         assertEquals(0, mTextView.getSelectionStart());
   3014         assertEquals(text.length(), mTextView.getSelectionEnd());
   3015         assertTrue(mTextView.hasSelection());
   3016 
   3017         int selectionStart = 5;
   3018         int selectionEnd = 7;
   3019         Selection.setSelection((Spannable) mTextView.getText(), selectionStart);
   3020         assertEquals(selectionStart, mTextView.getSelectionStart());
   3021         assertEquals(selectionStart, mTextView.getSelectionEnd());
   3022         assertFalse(mTextView.hasSelection());
   3023 
   3024         Selection.setSelection((Spannable) mTextView.getText(), selectionStart, selectionEnd);
   3025         assertEquals(selectionStart, mTextView.getSelectionStart());
   3026         assertEquals(selectionEnd, mTextView.getSelectionEnd());
   3027         assertTrue(mTextView.hasSelection());
   3028     }
   3029 
   3030     @Test
   3031     public void testOnSelectionChangedIsTriggeredWhenSelectionChanges() throws Throwable {
   3032         final String text = "any text";
   3033         mActivityRule.runOnUiThread(() -> mTextView = spy(new MockTextView(mActivity)));
   3034         mInstrumentation.waitForIdleSync();
   3035         mTextView.setText(text, BufferType.SPANNABLE);
   3036 
   3037         // assert that there is currently no selection
   3038         assertFalse(mTextView.hasSelection());
   3039 
   3040         // select all
   3041         Selection.selectAll((Spannable) mTextView.getText());
   3042         // After selectAll OnSelectionChanged should have been called
   3043         ((MockTextView) verify(mTextView, times(1))).onSelectionChanged(0, text.length());
   3044 
   3045         reset(mTextView);
   3046         // change selection
   3047         Selection.setSelection((Spannable) mTextView.getText(), 1, 5);
   3048         ((MockTextView) verify(mTextView, times(1))).onSelectionChanged(1, 5);
   3049 
   3050         reset(mTextView);
   3051         // clear selection
   3052         Selection.removeSelection((Spannable) mTextView.getText());
   3053         ((MockTextView) verify(mTextView, times(1))).onSelectionChanged(-1, -1);
   3054     }
   3055 
   3056     @UiThreadTest
   3057     @Test
   3058     public void testAccessEllipsize() {
   3059         mActivity.setContentView(R.layout.textview_ellipsize);
   3060 
   3061         mTextView = findTextView(R.id.ellipsize_default);
   3062         assertNull(mTextView.getEllipsize());
   3063 
   3064         mTextView = findTextView(R.id.ellipsize_none);
   3065         assertNull(mTextView.getEllipsize());
   3066 
   3067         mTextView = findTextView(R.id.ellipsize_start);
   3068         assertSame(TruncateAt.START, mTextView.getEllipsize());
   3069 
   3070         mTextView = findTextView(R.id.ellipsize_middle);
   3071         assertSame(TruncateAt.MIDDLE, mTextView.getEllipsize());
   3072 
   3073         mTextView = findTextView(R.id.ellipsize_end);
   3074         assertSame(TruncateAt.END, mTextView.getEllipsize());
   3075 
   3076         mTextView.setEllipsize(TextUtils.TruncateAt.START);
   3077         assertSame(TextUtils.TruncateAt.START, mTextView.getEllipsize());
   3078 
   3079         mTextView.setEllipsize(TextUtils.TruncateAt.MIDDLE);
   3080         assertSame(TextUtils.TruncateAt.MIDDLE, mTextView.getEllipsize());
   3081 
   3082         mTextView.setEllipsize(TextUtils.TruncateAt.END);
   3083         assertSame(TextUtils.TruncateAt.END, mTextView.getEllipsize());
   3084 
   3085         mTextView.setEllipsize(null);
   3086         assertNull(mTextView.getEllipsize());
   3087 
   3088         mTextView.setWidth(10);
   3089         mTextView.setEllipsize(TextUtils.TruncateAt.START);
   3090         mTextView.setText("ThisIsAVeryLongVeryLongVeryLongVeryLongVeryLongWord");
   3091         mTextView.invalidate();
   3092 
   3093         assertSame(TextUtils.TruncateAt.START, mTextView.getEllipsize());
   3094         // there is no method to check if '...yLongVeryLongWord' is painted in the screen.
   3095     }
   3096 
   3097     @Test
   3098     public void testEllipsizeAndMaxLinesForSingleLine() throws Throwable {
   3099         // no maxline or ellipsize set, single line text
   3100         final TextView tvNoMaxLine = new TextView(mActivity);
   3101         tvNoMaxLine.setLineSpacing(0, 1.5f);
   3102         tvNoMaxLine.setText("a");
   3103 
   3104         // maxline set, no ellipsize, text with two lines
   3105         final TextView tvEllipsizeNone = new TextView(mActivity);
   3106         tvEllipsizeNone.setMaxLines(1);
   3107         tvEllipsizeNone.setLineSpacing(0, 1.5f);
   3108         tvEllipsizeNone.setText("a\na");
   3109 
   3110         // maxline set, ellipsize end, text with two lines
   3111         final TextView tvEllipsizeEnd = new TextView(mActivity);
   3112         tvEllipsizeEnd.setEllipsize(TruncateAt.END);
   3113         tvEllipsizeEnd.setMaxLines(1);
   3114         tvEllipsizeEnd.setLineSpacing(0, 1.5f);
   3115         tvEllipsizeEnd.setText("a\na");
   3116 
   3117         final FrameLayout layout = new FrameLayout(mActivity);
   3118         ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
   3119                 ViewGroup.LayoutParams.WRAP_CONTENT,
   3120                 ViewGroup.LayoutParams.WRAP_CONTENT);
   3121         layout.addView(tvEllipsizeEnd, layoutParams);
   3122         layout.addView(tvEllipsizeNone, layoutParams);
   3123         layout.addView(tvNoMaxLine, layoutParams);
   3124 
   3125         mActivityRule.runOnUiThread(() -> mActivity.setContentView(layout,
   3126                 new ViewGroup.LayoutParams(
   3127                         ViewGroup.LayoutParams.MATCH_PARENT,
   3128                         ViewGroup.LayoutParams.MATCH_PARENT)));
   3129         mInstrumentation.waitForIdleSync();
   3130 
   3131         assertEquals(tvEllipsizeEnd.getHeight(), tvEllipsizeNone.getHeight());
   3132 
   3133         assertEquals(tvEllipsizeEnd.getHeight(), tvNoMaxLine.getHeight());
   3134 
   3135         assertEquals(tvEllipsizeEnd.getLayout().getLineBaseline(0),
   3136                 tvEllipsizeNone.getLayout().getLineBaseline(0));
   3137 
   3138         assertEquals(tvEllipsizeEnd.getLayout().getLineBaseline(0),
   3139                 tvNoMaxLine.getLayout().getLineBaseline(0));
   3140     }
   3141 
   3142     @Test
   3143     public void testEllipsizeAndMaxLinesForMultiLine() throws Throwable {
   3144         // no maxline, no ellipsize, text with two lines
   3145         final TextView tvNoMaxLine = new TextView(mActivity);
   3146         tvNoMaxLine.setLineSpacing(0, 1.5f);
   3147         tvNoMaxLine.setText("a\na");
   3148 
   3149         // maxline set, no ellipsize, text with three lines
   3150         final TextView tvEllipsizeNone = new TextView(mActivity);
   3151         tvEllipsizeNone.setMaxLines(2);
   3152         tvEllipsizeNone.setLineSpacing(0, 1.5f);
   3153         tvEllipsizeNone.setText("a\na\na");
   3154 
   3155         // maxline set, ellipsize end, text with three lines
   3156         final TextView tvEllipsizeEnd = new TextView(mActivity);
   3157         tvEllipsizeEnd.setEllipsize(TruncateAt.END);
   3158         tvEllipsizeEnd.setMaxLines(2);
   3159         tvEllipsizeEnd.setLineSpacing(0, 1.5f);
   3160         tvEllipsizeEnd.setText("a\na\na");
   3161 
   3162         final FrameLayout layout = new FrameLayout(mActivity);
   3163         ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
   3164                 ViewGroup.LayoutParams.WRAP_CONTENT,
   3165                 ViewGroup.LayoutParams.WRAP_CONTENT);
   3166 
   3167         layout.addView(tvNoMaxLine, layoutParams);
   3168         layout.addView(tvEllipsizeEnd, layoutParams);
   3169         layout.addView(tvEllipsizeNone, layoutParams);
   3170 
   3171         mActivityRule.runOnUiThread(() ->  mActivity.setContentView(layout,
   3172                 new ViewGroup.LayoutParams(
   3173                         ViewGroup.LayoutParams.MATCH_PARENT,
   3174                         ViewGroup.LayoutParams.MATCH_PARENT)));
   3175         mInstrumentation.waitForIdleSync();
   3176 
   3177         assertEquals(tvEllipsizeEnd.getHeight(), tvEllipsizeNone.getHeight());
   3178 
   3179         assertEquals(tvEllipsizeEnd.getHeight(), tvNoMaxLine.getHeight());
   3180 
   3181         for (int i = 0; i < tvEllipsizeEnd.getLineCount(); i++) {
   3182             assertEquals("Should have the same baseline for line " + i,
   3183                     tvEllipsizeEnd.getLayout().getLineBaseline(i),
   3184                     tvEllipsizeNone.getLayout().getLineBaseline(i));
   3185 
   3186             assertEquals("Should have the same baseline for line " + i,
   3187                     tvEllipsizeEnd.getLayout().getLineBaseline(i),
   3188                     tvNoMaxLine.getLayout().getLineBaseline(i));
   3189         }
   3190     }
   3191 
   3192     @Test
   3193     public void testEllipsizeAndMaxLinesForHint() throws Throwable {
   3194         // no maxline, no ellipsize, hint with two lines
   3195         final TextView tvTwoLines = new TextView(mActivity);
   3196         tvTwoLines.setLineSpacing(0, 1.5f);
   3197         tvTwoLines.setHint("a\na");
   3198 
   3199         // no maxline, no ellipsize, hint with three lines
   3200         final TextView tvThreeLines = new TextView(mActivity);
   3201         tvThreeLines.setLineSpacing(0, 1.5f);
   3202         tvThreeLines.setHint("a\na\na");
   3203 
   3204         // maxline set, ellipsize end, hint with three lines
   3205         final TextView tvEllipsizeEnd = new TextView(mActivity);
   3206         tvEllipsizeEnd.setEllipsize(TruncateAt.END);
   3207         tvEllipsizeEnd.setMaxLines(2);
   3208         tvEllipsizeEnd.setLineSpacing(0, 1.5f);
   3209         tvEllipsizeEnd.setHint("a\na\na");
   3210 
   3211         // maxline set, no ellipsize, hint with three lines
   3212         final TextView tvEllipsizeNone = new TextView(mActivity);
   3213         tvEllipsizeNone.setMaxLines(2);
   3214         tvEllipsizeNone.setLineSpacing(0, 1.5f);
   3215         tvEllipsizeNone.setHint("a\na\na");
   3216 
   3217         final FrameLayout layout = new FrameLayout(mActivity);
   3218         ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
   3219                 ViewGroup.LayoutParams.WRAP_CONTENT,
   3220                 ViewGroup.LayoutParams.WRAP_CONTENT);
   3221 
   3222         layout.addView(tvTwoLines, layoutParams);
   3223         layout.addView(tvEllipsizeEnd, layoutParams);
   3224         layout.addView(tvEllipsizeNone, layoutParams);
   3225         layout.addView(tvThreeLines, layoutParams);
   3226 
   3227         mActivityRule.runOnUiThread(() ->  mActivity.setContentView(layout,
   3228                 new ViewGroup.LayoutParams(
   3229                         ViewGroup.LayoutParams.MATCH_PARENT,
   3230                         ViewGroup.LayoutParams.MATCH_PARENT)));
   3231         mInstrumentation.waitForIdleSync();
   3232 
   3233         assertEquals("Non-ellipsized hint should not crop text at maxLines",
   3234                 tvThreeLines.getHeight(), tvEllipsizeNone.getHeight());
   3235 
   3236         assertEquals("Ellipsized hint should crop text at maxLines",
   3237                 tvTwoLines.getHeight(), tvEllipsizeEnd.getHeight());
   3238     }
   3239 
   3240     @UiThreadTest
   3241     @Test
   3242     public void testAccessCursorVisible() {
   3243         mTextView = new TextView(mActivity);
   3244 
   3245         mTextView.setCursorVisible(true);
   3246         assertTrue(mTextView.isCursorVisible());
   3247         mTextView.setCursorVisible(false);
   3248         assertFalse(mTextView.isCursorVisible());
   3249     }
   3250 
   3251     @UiThreadTest
   3252     @Test
   3253     public void testPerformLongClick() {
   3254         mTextView = findTextView(R.id.textview_text);
   3255         mTextView.setText("This is content");
   3256 
   3257         View.OnLongClickListener mockOnLongClickListener = mock(View.OnLongClickListener.class);
   3258         when(mockOnLongClickListener.onLongClick(any(View.class))).thenReturn(Boolean.TRUE);
   3259 
   3260         View.OnCreateContextMenuListener mockOnCreateContextMenuListener =
   3261                 mock(View.OnCreateContextMenuListener.class);
   3262         doAnswer((InvocationOnMock invocation) -> {
   3263             ((ContextMenu) invocation.getArguments() [0]).add("menu item");
   3264             return null;
   3265         }).when(mockOnCreateContextMenuListener).onCreateContextMenu(
   3266                 any(ContextMenu.class), any(View.class), any());
   3267 
   3268         mTextView.setOnLongClickListener(mockOnLongClickListener);
   3269         mTextView.setOnCreateContextMenuListener(mockOnCreateContextMenuListener);
   3270         assertTrue(mTextView.performLongClick());
   3271         verify(mockOnLongClickListener, times(1)).onLongClick(mTextView);
   3272         verifyZeroInteractions(mockOnCreateContextMenuListener);
   3273 
   3274         reset(mockOnLongClickListener);
   3275         when(mockOnLongClickListener.onLongClick(any(View.class))).thenReturn(Boolean.FALSE);
   3276         assertTrue(mTextView.performLongClick());
   3277         verify(mockOnLongClickListener, times(1)).onLongClick(mTextView);
   3278         verify(mockOnCreateContextMenuListener, times(1)).onCreateContextMenu(
   3279                 any(ContextMenu.class), eq(mTextView), any());
   3280 
   3281         reset(mockOnCreateContextMenuListener);
   3282         mTextView.setOnLongClickListener(null);
   3283         doNothing().when(mockOnCreateContextMenuListener).onCreateContextMenu(
   3284                 any(ContextMenu.class), any(View.class), any());
   3285         assertFalse(mTextView.performLongClick());
   3286         verifyNoMoreInteractions(mockOnLongClickListener);
   3287         verify(mockOnCreateContextMenuListener, times(1)).onCreateContextMenu(
   3288                 any(ContextMenu.class), eq(mTextView), any());
   3289     }
   3290 
   3291     @UiThreadTest
   3292     @Test
   3293     public void testTextAttr() {
   3294         mTextView = findTextView(R.id.textview_textAttr);
   3295         // getText
   3296         assertEquals(mActivity.getString(R.string.text_view_hello), mTextView.getText().toString());
   3297 
   3298         // getCurrentTextColor
   3299         assertEquals(mActivity.getResources().getColor(R.drawable.black),
   3300                 mTextView.getCurrentTextColor());
   3301         assertEquals(mActivity.getResources().getColor(R.drawable.red),
   3302                 mTextView.getCurrentHintTextColor());
   3303         assertEquals(mActivity.getResources().getColor(R.drawable.red),
   3304                 mTextView.getHintTextColors().getDefaultColor());
   3305         assertEquals(mActivity.getResources().getColor(R.drawable.blue),
   3306                 mTextView.getLinkTextColors().getDefaultColor());
   3307 
   3308         // getTextScaleX
   3309         assertEquals(1.2f, mTextView.getTextScaleX(), 0.01f);
   3310 
   3311         // setTextScaleX
   3312         mTextView.setTextScaleX(2.4f);
   3313         assertEquals(2.4f, mTextView.getTextScaleX(), 0.01f);
   3314 
   3315         mTextView.setTextScaleX(0f);
   3316         assertEquals(0f, mTextView.getTextScaleX(), 0.01f);
   3317 
   3318         mTextView.setTextScaleX(- 2.4f);
   3319         assertEquals(- 2.4f, mTextView.getTextScaleX(), 0.01f);
   3320 
   3321         // getTextSize
   3322         assertEquals(20f, mTextView.getTextSize(), 0.01f);
   3323 
   3324         // getTypeface
   3325         // getTypeface will be null if android:typeface is set to normal,
   3326         // and android:style is not set or is set to normal, and
   3327         // android:fontFamily is not set
   3328         assertNull(mTextView.getTypeface());
   3329 
   3330         mTextView.setTypeface(Typeface.DEFAULT);
   3331         assertSame(Typeface.DEFAULT, mTextView.getTypeface());
   3332         // null type face
   3333         mTextView.setTypeface(null);
   3334         assertNull(mTextView.getTypeface());
   3335 
   3336         // default type face, bold style, note: the type face will be changed
   3337         // after call set method
   3338         mTextView.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
   3339         assertSame(Typeface.BOLD, mTextView.getTypeface().getStyle());
   3340 
   3341         // null type face, BOLD style
   3342         mTextView.setTypeface(null, Typeface.BOLD);
   3343         assertSame(Typeface.BOLD, mTextView.getTypeface().getStyle());
   3344 
   3345         // old type face, null style
   3346         mTextView.setTypeface(Typeface.DEFAULT, 0);
   3347         assertEquals(Typeface.NORMAL, mTextView.getTypeface().getStyle());
   3348     }
   3349 
   3350     @UiThreadTest
   3351     @Test
   3352     public void testTextAttr_zeroTextSize() {
   3353         mTextView = findTextView(R.id.textview_textAttr_zeroTextSize);
   3354         // text size should be 0 as set in xml, rather than the text view default (15.0)
   3355         assertEquals(0f, mTextView.getTextSize(), 0.01f);
   3356         // text size can be set programmatically to non-negative values
   3357         mTextView.setTextSize(20f);
   3358         assertTrue(mTextView.getTextSize() > 0.0f);
   3359         mTextView.setTextSize(0f);
   3360         assertEquals(0f, mTextView.getTextSize(), 0.01f);
   3361     }
   3362 
   3363     @UiThreadTest
   3364     @Test
   3365     public void testAppend() {
   3366         mTextView = new TextView(mActivity);
   3367 
   3368         // 1: check the original length, should be blank as initialised.
   3369         assertEquals(0, mTextView.getText().length());
   3370 
   3371         // 2: append a string use append(CharSquence) into the original blank
   3372         // buffer, check the content. And upgrading it to BufferType.EDITABLE if it was
   3373         // not already editable.
   3374         assertFalse(mTextView.getText() instanceof Editable);
   3375         mTextView.append("Append.");
   3376         assertEquals("Append.", mTextView.getText().toString());
   3377         assertTrue(mTextView.getText() instanceof Editable);
   3378 
   3379         // 3: append a string from 0~3.
   3380         mTextView.append("Append", 0, 3);
   3381         assertEquals("Append.App", mTextView.getText().toString());
   3382         assertTrue(mTextView.getText() instanceof Editable);
   3383 
   3384         // 4: append a string from 0~0, nothing will be append as expected.
   3385         mTextView.append("Append", 0, 0);
   3386         assertEquals("Append.App", mTextView.getText().toString());
   3387         assertTrue(mTextView.getText() instanceof Editable);
   3388 
   3389         // 5: append a string from -3~3. check the wrong left edge.
   3390         try {
   3391             mTextView.append("Append", -3, 3);
   3392             fail("Should throw StringIndexOutOfBoundsException");
   3393         } catch (StringIndexOutOfBoundsException e) {
   3394         }
   3395 
   3396         // 6: append a string from 3~10. check the wrong right edge.
   3397         try {
   3398             mTextView.append("Append", 3, 10);
   3399             fail("Should throw StringIndexOutOfBoundsException");
   3400         } catch (StringIndexOutOfBoundsException e) {
   3401         }
   3402 
   3403         // 7: append a null string.
   3404         try {
   3405             mTextView.append(null);
   3406             fail("Should throw NullPointerException");
   3407         } catch (NullPointerException e) {
   3408         }
   3409     }
   3410 
   3411     @UiThreadTest
   3412     @Test
   3413     public void testAppend_doesNotAddLinksWhenAppendedTextDoesNotContainLinks() {
   3414         mTextView = new TextView(mActivity);
   3415         mTextView.setAutoLinkMask(Linkify.ALL);
   3416         mTextView.setText("text without URL");
   3417 
   3418         mTextView.append(" another text without URL");
   3419 
   3420         Spannable text = (Spannable) mTextView.getText();
   3421         URLSpan[] urlSpans = text.getSpans(0, text.length(), URLSpan.class);
   3422         assertEquals("URLSpan count should be zero", 0, urlSpans.length);
   3423         assertEquals("text without URL another text without URL", text.toString());
   3424     }
   3425 
   3426     @UiThreadTest
   3427     @Test
   3428     public void testAppend_doesNotAddLinksWhenAutoLinkIsNotEnabled() {
   3429         mTextView = new TextView(mActivity);
   3430         mTextView.setText("text without URL");
   3431 
   3432         mTextView.append(" text with URL http://android.com");
   3433 
   3434         Spannable text = (Spannable) mTextView.getText();
   3435         URLSpan[] urlSpans = text.getSpans(0, text.length(), URLSpan.class);
   3436         assertEquals("URLSpan count should be zero", 0, urlSpans.length);
   3437         assertEquals("text without URL text with URL http://android.com", text.toString());
   3438     }
   3439 
   3440     @UiThreadTest
   3441     @Test
   3442     public void testAppend_addsLinksWhenAutoLinkIsEnabled() {
   3443         mTextView = new TextView(mActivity);
   3444         mTextView.setAutoLinkMask(Linkify.ALL);
   3445         mTextView.setText("text without URL");
   3446 
   3447         mTextView.append(" text with URL http://android.com");
   3448 
   3449         Spannable text = (Spannable) mTextView.getText();
   3450         URLSpan[] urlSpans = text.getSpans(0, text.length(), URLSpan.class);
   3451         assertEquals("URLSpan count should be one after appending a URL", 1, urlSpans.length);
   3452         assertEquals("URLSpan URL should be same as the appended URL",
   3453                 urlSpans[0].getURL(), "http://android.com");
   3454         assertEquals("text without URL text with URL http://android.com", text.toString());
   3455     }
   3456 
   3457     @UiThreadTest
   3458     @Test
   3459     public void testAppend_addsLinksEvenWhenThereAreUrlsSetBefore() {
   3460         mTextView = new TextView(mActivity);
   3461         mTextView.setAutoLinkMask(Linkify.ALL);
   3462         mTextView.setText("text with URL http://android.com/before");
   3463 
   3464         mTextView.append(" text with URL http://android.com");
   3465 
   3466         Spannable text = (Spannable) mTextView.getText();
   3467         URLSpan[] urlSpans = text.getSpans(0, text.length(), URLSpan.class);
   3468         assertEquals("URLSpan count should be two after appending another URL", 2, urlSpans.length);
   3469         assertEquals("First URLSpan URL should be same",
   3470                 urlSpans[0].getURL(), "http://android.com/before");
   3471         assertEquals("URLSpan URL should be same as the appended URL",
   3472                 urlSpans[1].getURL(), "http://android.com");
   3473         assertEquals("text with URL http://android.com/before text with URL http://android.com",
   3474                 text.toString());
   3475     }
   3476 
   3477     @UiThreadTest
   3478     @Test
   3479     public void testAppend_setsMovementMethodWhenTextContainsUrlAndAutoLinkIsEnabled() {
   3480         mTextView = new TextView(mActivity);
   3481         mTextView.setAutoLinkMask(Linkify.ALL);
   3482         mTextView.setText("text without a URL");
   3483 
   3484         mTextView.append(" text with a url: http://android.com");
   3485 
   3486         assertNotNull("MovementMethod should not be null when text contains url",
   3487                 mTextView.getMovementMethod());
   3488         assertTrue("MovementMethod should be instance of LinkMovementMethod when text contains url",
   3489                 mTextView.getMovementMethod() instanceof LinkMovementMethod);
   3490     }
   3491 
   3492     @UiThreadTest
   3493     @Test
   3494     public void testAppend_addsLinksWhenTextIsSpannableAndContainsUrlAndAutoLinkIsEnabled() {
   3495         mTextView = new TextView(mActivity);
   3496         mTextView.setAutoLinkMask(Linkify.ALL);
   3497         mTextView.setText("text without a URL");
   3498 
   3499         mTextView.append(new SpannableString(" text with a url: http://android.com"));
   3500 
   3501         Spannable text = (Spannable) mTextView.getText();
   3502         URLSpan[] urlSpans = text.getSpans(0, text.length(), URLSpan.class);
   3503         assertEquals("URLSpan count should be one after appending a URL", 1, urlSpans.length);
   3504         assertEquals("URLSpan URL should be same as the appended URL",
   3505                 urlSpans[0].getURL(), "http://android.com");
   3506     }
   3507 
   3508     @UiThreadTest
   3509     @Test
   3510     public void testAppend_addsLinkIfAppendedTextCompletesPartialUrlAtTheEndOfExistingText() {
   3511         mTextView = new TextView(mActivity);
   3512         mTextView.setAutoLinkMask(Linkify.ALL);
   3513         mTextView.setText("text with a partial url android.");
   3514 
   3515         mTextView.append("com");
   3516 
   3517         Spannable text = (Spannable) mTextView.getText();
   3518         URLSpan[] urlSpans = text.getSpans(0, text.length(), URLSpan.class);
   3519         assertEquals("URLSpan count should be one after appending to partial URL",
   3520                 1, urlSpans.length);
   3521         assertEquals("URLSpan URL should be same as the appended URL",
   3522                 urlSpans[0].getURL(), "http://android.com");
   3523     }
   3524 
   3525     @UiThreadTest
   3526     @Test
   3527     public void testAppend_addsLinkIfAppendedTextUpdatesUrlAtTheEndOfExistingText() {
   3528         mTextView = new TextView(mActivity);
   3529         mTextView.setAutoLinkMask(Linkify.ALL);
   3530         mTextView.setText("text with a url http://android.com");
   3531 
   3532         mTextView.append("/textview");
   3533 
   3534         Spannable text = (Spannable) mTextView.getText();
   3535         URLSpan[] urlSpans = text.getSpans(0, text.length(), URLSpan.class);
   3536         assertEquals("URLSpan count should still be one after extending a URL", 1, urlSpans.length);
   3537         assertEquals("URLSpan URL should be same as the new URL",
   3538                 urlSpans[0].getURL(), "http://android.com/textview");
   3539     }
   3540 
   3541     @UiThreadTest
   3542     @Test
   3543     public void testGetLetterSpacing_returnsValueThatWasSet() {
   3544         mTextView = new TextView(mActivity);
   3545         mTextView.setLetterSpacing(2f);
   3546         assertEquals("getLetterSpacing should return the value that was set",
   3547                 2f, mTextView.getLetterSpacing(), 0.0f);
   3548     }
   3549 
   3550     @Test
   3551     public void testSetLetterSpacingChangesTextWidth() throws Throwable {
   3552         mActivityRule.runOnUiThread(() -> {
   3553             mTextView = new TextView(mActivity);
   3554             mTextView.setText("aa");
   3555             mTextView.setLetterSpacing(0f);
   3556             mTextView.setTextSize(8f);
   3557         });
   3558         mInstrumentation.waitForIdleSync();
   3559 
   3560         final FrameLayout layout = new FrameLayout(mActivity);
   3561         final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
   3562                 ViewGroup.LayoutParams.WRAP_CONTENT,
   3563                 ViewGroup.LayoutParams.MATCH_PARENT);
   3564         layout.addView(mTextView, layoutParams);
   3565         layout.setLayoutParams(layoutParams);
   3566 
   3567         mActivityRule.runOnUiThread(() -> mActivity.setContentView(layout));
   3568         mInstrumentation.waitForIdleSync();
   3569 
   3570         // measure text with zero letter spacing
   3571         final float zeroSpacing = mTextView.getLayout().getLineWidth(0);
   3572 
   3573         mActivityRule.runOnUiThread(() -> mTextView.setLetterSpacing(1f));
   3574         mInstrumentation.waitForIdleSync();
   3575 
   3576         // measure text with single letter spacing
   3577         final float singleSpacing = mTextView.getLayout().getLineWidth(0);
   3578 
   3579         mActivityRule.runOnUiThread(() -> mTextView.setLetterSpacing(2f));
   3580         mInstrumentation.waitForIdleSync();
   3581 
   3582         // measure text with double letter spacing
   3583         final float doubleSpacing = mTextView.getLayout().getLineWidth(0);
   3584 
   3585         assertEquals("Double spacing should have two times the spacing of single spacing",
   3586                 doubleSpacing - zeroSpacing, 2f * (singleSpacing - zeroSpacing), 2f);
   3587     }
   3588 
   3589     @UiThreadTest
   3590     @Test
   3591     public void testGetFontFeatureSettings_returnsValueThatWasSet() {
   3592         mTextView = new TextView(mActivity);
   3593         mTextView.setFontFeatureSettings("\"smcp\" on");
   3594         assertEquals("getFontFeatureSettings should return the value that was set",
   3595                 "\"smcp\" on", mTextView.getFontFeatureSettings());
   3596     }
   3597 
   3598     @UiThreadTest
   3599     @Test
   3600     public void testSetGetFontVariationSettings() {
   3601         mTextView = new TextView(mActivity);
   3602         Context context = InstrumentationRegistry.getTargetContext();
   3603         Typeface typeface = Typeface.createFromAsset(context.getAssets(), "multiaxis.ttf");
   3604         mTextView.setTypeface(typeface);
   3605 
   3606         // multiaxis.ttf supports "aaaa", "BBBB", "a b ", " C D" axes.
   3607 
   3608         // The default variation settings should be null.
   3609         assertNull(mTextView.getFontVariationSettings());
   3610 
   3611         final String[] invalidFormatSettings = {
   3612                 "invalid syntax",
   3613                 "'aaa' 1.0",  // tag is not 4 ascii chars
   3614         };
   3615         for (String settings : invalidFormatSettings) {
   3616             try {
   3617                 mTextView.setFontVariationSettings(settings);
   3618                 fail();
   3619             } catch (IllegalArgumentException e) {
   3620                 // pass.
   3621             }
   3622             assertNull("Must not change settings for " + settings,
   3623                     mTextView.getFontVariationSettings());
   3624         }
   3625 
   3626         final String[] nonEffectiveSettings = {
   3627                 "'bbbb' 1.0",  // unsupported tag
   3628                 "'    ' 1.0",  // unsupported tag
   3629                 "'AAAA' 0.7",  // unsupported tag (case sensitive)
   3630                 "' a b' 1.3",  // unsupported tag (white space should not be ignored)
   3631                 "'C D ' 1.3",  // unsupported tag (white space should not be ignored)
   3632                 "'bbbb' 1.0, 'cccc' 2.0",  // none of them are supported.
   3633         };
   3634 
   3635         for (String notEffectiveSetting : nonEffectiveSettings) {
   3636             assertFalse("Must return false for " + notEffectiveSetting,
   3637                     mTextView.setFontVariationSettings(notEffectiveSetting));
   3638             assertNull("Must not change settings for " + notEffectiveSetting,
   3639                     mTextView.getFontVariationSettings());
   3640         }
   3641 
   3642         String retainSettings = "'aaaa' 1.0";
   3643         assertTrue(mTextView.setFontVariationSettings(retainSettings));
   3644         for (String notEffectiveSetting : nonEffectiveSettings) {
   3645             assertFalse(mTextView.setFontVariationSettings(notEffectiveSetting));
   3646             assertEquals("Must not change settings for " + notEffectiveSetting,
   3647                     retainSettings, mTextView.getFontVariationSettings());
   3648         }
   3649 
   3650         // At least one axis is supported, the settings should be applied.
   3651         final String[] effectiveSettings = {
   3652                 "'aaaa' 1.0",  // supported tag
   3653                 "'a b ' .7",  // supported tag (contains whitespace)
   3654                 "'aaaa' 1.0, 'BBBB' 0.5",  // both are supported
   3655                 "'aaaa' 1.0, ' C D' 0.5",  // both are supported
   3656                 "'aaaa' 1.0, 'bbbb' 0.4",  // 'bbbb' is unspported.
   3657         };
   3658 
   3659         for (String effectiveSetting : effectiveSettings) {
   3660             assertTrue(mTextView.setFontVariationSettings(effectiveSetting));
   3661             assertEquals(effectiveSetting, mTextView.getFontVariationSettings());
   3662         }
   3663 
   3664         mTextView.setFontVariationSettings("");
   3665         assertNull(mTextView.getFontVariationSettings());
   3666     }
   3667 
   3668     @Test
   3669     public void testGetOffsetForPositionSingleLineLtr() throws Throwable {
   3670         // asserts getOffsetPosition returns correct values for a single line LTR text
   3671         final String text = "aaaaa";
   3672 
   3673         mActivityRule.runOnUiThread(() -> {
   3674             mTextView = new TextView(mActivity);
   3675             mTextView.setText(text);
   3676             mTextView.setTextSize(8f);
   3677             mTextView.setSingleLine(true);
   3678         });
   3679         mInstrumentation.waitForIdleSync();
   3680 
   3681         // add a compound drawable to TextView to make offset calculation more interesting
   3682         final Drawable drawable = TestUtils.getDrawable(mActivity, R.drawable.red);
   3683         drawable.setBounds(0, 0, 10, 10);
   3684         mTextView.setCompoundDrawables(drawable, drawable, drawable, drawable);
   3685 
   3686         final FrameLayout layout = new FrameLayout(mActivity);
   3687         final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
   3688                 ViewGroup.LayoutParams.MATCH_PARENT,
   3689                 ViewGroup.LayoutParams.WRAP_CONTENT);
   3690         layout.addView(mTextView, layoutParams);
   3691         layout.setLayoutParams(layoutParams);
   3692 
   3693         mActivityRule.runOnUiThread(() -> mActivity.setContentView(layout));
   3694         mInstrumentation.waitForIdleSync();
   3695 
   3696         final float horizontalPosFix = (float) Math.ceil(
   3697                 mTextView.getPaint().measureText("a") * 2f / 3f);
   3698         final int paddingTop = mTextView.getTotalPaddingTop();
   3699         final int paddingLeft = mTextView.getTotalPaddingLeft();
   3700 
   3701         final int firstOffset = 0;
   3702         final int lastOffset = text.length() - 1;
   3703         final int midOffset = text.length() / 2;
   3704 
   3705         // left edge of view
   3706         float x = 0f;
   3707         float y = mTextView.getHeight() / 2f + paddingTop;
   3708         assertEquals(firstOffset, mTextView.getOffsetForPosition(x, y));
   3709 
   3710         // right edge of text
   3711         x = mTextView.getLayout().getLineWidth(0) + paddingLeft - horizontalPosFix;
   3712         assertEquals(lastOffset, mTextView.getOffsetForPosition(x, y));
   3713 
   3714         // right edge of view
   3715         x = mTextView.getWidth();
   3716         assertEquals(lastOffset + 1, mTextView.getOffsetForPosition(x, y));
   3717 
   3718         // left edge of view - out of bounds
   3719         x = -1f;
   3720         assertEquals(firstOffset, mTextView.getOffsetForPosition(x, y));
   3721 
   3722         // horizontal center of text
   3723         x = mTextView.getLayout().getLineWidth(0) / 2f + paddingLeft - horizontalPosFix;
   3724         assertEquals(midOffset, mTextView.getOffsetForPosition(x, y));
   3725     }
   3726 
   3727     @Test
   3728     public void testGetOffsetForPositionMultiLineLtr() throws Throwable {
   3729         final String line = "aaa\n";
   3730         final String threeLines = line + line + line;
   3731         mActivityRule.runOnUiThread(() -> {
   3732             mTextView = new TextView(mActivity);
   3733             mTextView.setText(threeLines);
   3734             mTextView.setTextSize(8f);
   3735             mTextView.setLines(2);
   3736         });
   3737         mInstrumentation.waitForIdleSync();
   3738 
   3739         // add a compound drawable to TextView to make offset calculation more interesting
   3740         final Drawable drawable = TestUtils.getDrawable(mActivity, R.drawable.red);
   3741         drawable.setBounds(0, 0, 10, 10);
   3742         mTextView.setCompoundDrawables(drawable, drawable, drawable, drawable);
   3743 
   3744         final FrameLayout layout = new FrameLayout(mActivity);
   3745         final ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
   3746                 ViewGroup.LayoutParams.MATCH_PARENT,
   3747                 ViewGroup.LayoutParams.WRAP_CONTENT);
   3748         layout.addView(mTextView, layoutParams);
   3749         layout.setLayoutParams(layoutParams);
   3750 
   3751         mActivityRule.runOnUiThread(() -> mActivity.setContentView(layout));
   3752         mInstrumentation.waitForIdleSync();
   3753 
   3754         final Rect lineBounds = new Rect();
   3755         mTextView.getLayout().getLineBounds(0, lineBounds);
   3756 
   3757         final float horizontalPosFix = (float) Math.ceil(
   3758                 mTextView.getPaint().measureText("a") * 2f / 3f);
   3759         final int paddingTop = mTextView.getTotalPaddingTop();
   3760         final int paddingLeft = mTextView.getTotalPaddingLeft();
   3761 
   3762         // left edge of view at first line
   3763         float x = 0f;
   3764         float y = lineBounds.height() / 2f + paddingTop;
   3765         assertEquals(0, mTextView.getOffsetForPosition(x, y));
   3766 
   3767         // right edge of view at first line
   3768         x = mTextView.getWidth() - 1f;
   3769         assertEquals(line.length() - 1, mTextView.getOffsetForPosition(x, y));
   3770 
   3771         // update lineBounds to be the second line
   3772         mTextView.getLayout().getLineBounds(1, lineBounds);
   3773         y = lineBounds.top + lineBounds.height() / 2f + paddingTop;
   3774 
   3775         // left edge of view at second line
   3776         x = 0f;
   3777         assertEquals(line.length(), mTextView.getOffsetForPosition(x, y));
   3778 
   3779         // right edge of text at second line
   3780         x = mTextView.getLayout().getLineWidth(1) + paddingLeft - horizontalPosFix;
   3781         assertEquals(line.length() + line.length() - 1, mTextView.getOffsetForPosition(x, y));
   3782 
   3783         // right edge of view at second line
   3784         x = mTextView.getWidth() - 1f;
   3785         assertEquals(line.length() + line.length() - 1, mTextView.getOffsetForPosition(x, y));
   3786 
   3787         // horizontal center of text at second line
   3788         x = mTextView.getLayout().getLineWidth(1) / 2f + paddingLeft - horizontalPosFix;
   3789         // second line mid offset should not include next line, therefore subtract one
   3790         assertEquals(line.length() + (line.length() - 1) / 2, mTextView.getOffsetForPosition(x, y));
   3791     }
   3792 
   3793     @Test
   3794     public void testGetOffsetForPositionMultiLineRtl() throws Throwable {
   3795         final String line = "\u0635\u0635\u0635\n";
   3796         final String threeLines = line + line + line;
   3797         mActivityRule.runOnUiThread(() -> {
   3798             mTextView = new TextView(mActivity);
   3799             mTextView.setText(threeLines);
   3800             mTextView.setTextSize(8f);
   3801             mTextView.setLines(2);
   3802         });
   3803         mInstrumentation.waitForIdleSync();
   3804 
   3805         // add a compound drawable to TextView to make offset calculation more interesting
   3806         final Drawable drawable = TestUtils.getDrawable(mActivity, R.drawable.red);
   3807         drawable.setBounds(0, 0, 10, 10);
   3808         mTextView.setCompoundDrawables(drawable, drawable, drawable, drawable);
   3809 
   3810         final FrameLayout layout = new FrameLayout(mActivity);
   3811         final LayoutParams layoutParams = new LayoutParams(
   3812                 LayoutParams.MATCH_PARENT,
   3813                 LayoutParams.WRAP_CONTENT);
   3814         layout.addView(mTextView, layoutParams);
   3815         layout.setLayoutParams(layoutParams);
   3816 
   3817         mActivityRule.runOnUiThread(() -> mActivity.setContentView(layout));
   3818         mInstrumentation.waitForIdleSync();
   3819 
   3820         final Rect lineBounds = new Rect();
   3821         mTextView.getLayout().getLineBounds(0, lineBounds);
   3822 
   3823         final float horizontalPosFix = (float) Math.ceil(
   3824                 mTextView.getPaint().measureText("\u0635") * 2f / 3f);
   3825         final int paddingTop = mTextView.getTotalPaddingTop();
   3826         final int paddingRight = mTextView.getTotalPaddingRight();
   3827 
   3828         // right edge of view at first line
   3829         float x = mTextView.getWidth() - 1f;
   3830         float y = lineBounds.height() / 2f + paddingTop;
   3831         assertEquals(0, mTextView.getOffsetForPosition(x, y));
   3832 
   3833         // left edge of view at first line
   3834         x = 0f;
   3835         assertEquals(line.length() - 1, mTextView.getOffsetForPosition(x, y));
   3836 
   3837         // update lineBounds to be the second line
   3838         mTextView.getLayout().getLineBounds(1, lineBounds);
   3839         y = lineBounds.top + lineBounds.height() / 2f + paddingTop;
   3840 
   3841         // right edge of view at second line
   3842         x = mTextView.getWidth() - 1f;
   3843         assertEquals(line.length(), mTextView.getOffsetForPosition(x, y));
   3844 
   3845         // left edge of view at second line
   3846         x = 0f;
   3847         assertEquals(line.length() + line.length() - 1, mTextView.getOffsetForPosition(x, y));
   3848 
   3849         // left edge of text at second line
   3850         x = mTextView.getWidth() - (mTextView.getLayout().getLineWidth(1) + paddingRight
   3851                 - horizontalPosFix);
   3852         assertEquals(line.length() + line.length() - 1, mTextView.getOffsetForPosition(x, y));
   3853 
   3854         // horizontal center of text at second line
   3855         x = mTextView.getWidth() - (mTextView.getLayout().getLineWidth(1) / 2f + paddingRight
   3856                 - horizontalPosFix);
   3857         // second line mid offset should not include next line, therefore subtract one
   3858         assertEquals(line.length() + (line.length() - 1) / 2, mTextView.getOffsetForPosition(x, y));
   3859     }
   3860 
   3861     @UiThreadTest
   3862     @Test
   3863     public void testIsTextSelectable_returnsFalseByDefault() {
   3864         final TextView textView = new TextView(mActivity);
   3865         textView.setText("any text");
   3866         assertFalse(textView.isTextSelectable());
   3867     }
   3868 
   3869     @UiThreadTest
   3870     @Test
   3871     public void testIsTextSelectable_returnsTrueIfSetTextIsSelectableCalledWithTrue() {
   3872         final TextView textView = new TextView(mActivity);
   3873         textView.setText("any text");
   3874         textView.setTextIsSelectable(true);
   3875         assertTrue(textView.isTextSelectable());
   3876     }
   3877 
   3878     @UiThreadTest
   3879     @Test
   3880     public void testSetIsTextSelectable() {
   3881         final TextView textView = new TextView(mActivity);
   3882 
   3883         assertFalse(textView.isTextSelectable());
   3884         assertFalse(textView.isFocusable());
   3885         assertFalse(textView.isFocusableInTouchMode());
   3886         assertFalse(textView.isClickable());
   3887         assertFalse(textView.isLongClickable());
   3888 
   3889         textView.setTextIsSelectable(true);
   3890 
   3891         assertTrue(textView.isTextSelectable());
   3892         assertTrue(textView.isFocusable());
   3893         assertTrue(textView.isFocusableInTouchMode());
   3894         assertTrue(textView.isClickable());
   3895         assertTrue(textView.isLongClickable());
   3896         assertNotNull(textView.getMovementMethod());
   3897     }
   3898 
   3899     @Test
   3900     public void testAccessTransformationMethod() throws Throwable {
   3901         // check the password attribute in xml
   3902         mTextView = findTextView(R.id.textview_password);
   3903         assertNotNull(mTextView);
   3904         assertSame(PasswordTransformationMethod.getInstance(),
   3905                 mTextView.getTransformationMethod());
   3906 
   3907         // check the singleLine attribute in xml
   3908         mTextView = findTextView(R.id.textview_singleLine);
   3909         assertNotNull(mTextView);
   3910         assertSame(SingleLineTransformationMethod.getInstance(),
   3911                 mTextView.getTransformationMethod());
   3912 
   3913         final QwertyKeyListener qwertyKeyListener = QwertyKeyListener.getInstance(false,
   3914                 Capitalize.NONE);
   3915         final TransformationMethod method = PasswordTransformationMethod.getInstance();
   3916         // change transformation method by function
   3917         mActivityRule.runOnUiThread(() -> {
   3918             mTextView.setKeyListener(qwertyKeyListener);
   3919             mTextView.setTransformationMethod(method);
   3920             mTransformedText = method.getTransformation(mTextView.getText(), mTextView);
   3921 
   3922             mTextView.requestFocus();
   3923         });
   3924         mInstrumentation.waitForIdleSync();
   3925         assertSame(PasswordTransformationMethod.getInstance(),
   3926                 mTextView.getTransformationMethod());
   3927 
   3928         CtsKeyEventUtil.sendKeys(mInstrumentation, mTextView, "H E 2*L O");
   3929         mActivityRule.runOnUiThread(() -> mTextView.append(" "));
   3930         mInstrumentation.waitForIdleSync();
   3931 
   3932         // It will get transformed after a while
   3933         // We're waiting for transformation to "******"
   3934         PollingCheck.waitFor(TIMEOUT, () -> mTransformedText.toString()
   3935                 .equals("\u2022\u2022\u2022\u2022\u2022\u2022"));
   3936 
   3937         // set null
   3938         mActivityRule.runOnUiThread(() -> mTextView.setTransformationMethod(null));
   3939         mInstrumentation.waitForIdleSync();
   3940         assertNull(mTextView.getTransformationMethod());
   3941     }
   3942 
   3943     @UiThreadTest
   3944     @Test
   3945     public void testCompound() {
   3946         mTextView = new TextView(mActivity);
   3947         int padding = 3;
   3948         Drawable[] drawables = mTextView.getCompoundDrawables();
   3949         assertNull(drawables[0]);
   3950         assertNull(drawables[1]);
   3951         assertNull(drawables[2]);
   3952         assertNull(drawables[3]);
   3953 
   3954         // test setCompoundDrawablePadding and getCompoundDrawablePadding
   3955         mTextView.setCompoundDrawablePadding(padding);
   3956         assertEquals(padding, mTextView.getCompoundDrawablePadding());
   3957 
   3958         // using resid, 0 represents null
   3959         mTextView.setCompoundDrawablesWithIntrinsicBounds(R.drawable.start, R.drawable.pass,
   3960                 R.drawable.failed, 0);
   3961         drawables = mTextView.getCompoundDrawables();
   3962 
   3963         // drawableLeft
   3964         WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.start),
   3965                 ((BitmapDrawable) drawables[0]).getBitmap());
   3966         // drawableTop
   3967         WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.pass),
   3968                 ((BitmapDrawable) drawables[1]).getBitmap());
   3969         // drawableRight
   3970         WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.failed),
   3971                 ((BitmapDrawable) drawables[2]).getBitmap());
   3972         // drawableBottom
   3973         assertNull(drawables[3]);
   3974 
   3975         Drawable left = TestUtils.getDrawable(mActivity, R.drawable.blue);
   3976         Drawable right = TestUtils.getDrawable(mActivity, R.drawable.yellow);
   3977         Drawable top = TestUtils.getDrawable(mActivity, R.drawable.red);
   3978 
   3979         // using drawables directly
   3980         mTextView.setCompoundDrawablesWithIntrinsicBounds(left, top, right, null);
   3981         drawables = mTextView.getCompoundDrawables();
   3982 
   3983         // drawableLeft
   3984         assertSame(left, drawables[0]);
   3985         // drawableTop
   3986         assertSame(top, drawables[1]);
   3987         // drawableRight
   3988         assertSame(right, drawables[2]);
   3989         // drawableBottom
   3990         assertNull(drawables[3]);
   3991 
   3992         // check compound padding
   3993         assertEquals(mTextView.getPaddingLeft() + padding + left.getIntrinsicWidth(),
   3994                 mTextView.getCompoundPaddingLeft());
   3995         assertEquals(mTextView.getPaddingTop() + padding + top.getIntrinsicHeight(),
   3996                 mTextView.getCompoundPaddingTop());
   3997         assertEquals(mTextView.getPaddingRight() + padding + right.getIntrinsicWidth(),
   3998                 mTextView.getCompoundPaddingRight());
   3999         assertEquals(mTextView.getPaddingBottom(), mTextView.getCompoundPaddingBottom());
   4000 
   4001         // set bounds to drawables and set them again.
   4002         left.setBounds(0, 0, 1, 2);
   4003         right.setBounds(0, 0, 3, 4);
   4004         top.setBounds(0, 0, 5, 6);
   4005         // usinf drawables
   4006         mTextView.setCompoundDrawables(left, top, right, null);
   4007         drawables = mTextView.getCompoundDrawables();
   4008 
   4009         // drawableLeft
   4010         assertSame(left, drawables[0]);
   4011         // drawableTop
   4012         assertSame(top, drawables[1]);
   4013         // drawableRight
   4014         assertSame(right, drawables[2]);
   4015         // drawableBottom
   4016         assertNull(drawables[3]);
   4017 
   4018         // check compound padding
   4019         assertEquals(mTextView.getPaddingLeft() + padding + left.getBounds().width(),
   4020                 mTextView.getCompoundPaddingLeft());
   4021         assertEquals(mTextView.getPaddingTop() + padding + top.getBounds().height(),
   4022                 mTextView.getCompoundPaddingTop());
   4023         assertEquals(mTextView.getPaddingRight() + padding + right.getBounds().width(),
   4024                 mTextView.getCompoundPaddingRight());
   4025         assertEquals(mTextView.getPaddingBottom(), mTextView.getCompoundPaddingBottom());
   4026     }
   4027 
   4028     @UiThreadTest
   4029     @Test
   4030     public void testGetCompoundDrawablesRelative() {
   4031         // prepare textview
   4032         mTextView = new TextView(mActivity);
   4033 
   4034         // prepare drawables
   4035         final Drawable start = TestUtils.getDrawable(mActivity, R.drawable.blue);
   4036         final Drawable end = TestUtils.getDrawable(mActivity, R.drawable.yellow);
   4037         final Drawable top = TestUtils.getDrawable(mActivity, R.drawable.red);
   4038         final Drawable bottom = TestUtils.getDrawable(mActivity, R.drawable.black);
   4039         assertNotNull(start);
   4040         assertNotNull(end);
   4041         assertNotNull(top);
   4042         assertNotNull(bottom);
   4043 
   4044         Drawable[] drawables = mTextView.getCompoundDrawablesRelative();
   4045         assertNotNull(drawables);
   4046         assertEquals(4, drawables.length);
   4047         assertNull(drawables[0]);
   4048         assertNull(drawables[1]);
   4049         assertNull(drawables[2]);
   4050         assertNull(drawables[3]);
   4051 
   4052         mTextView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
   4053         mTextView.setCompoundDrawablesRelative(start, top, end, bottom);
   4054         drawables = mTextView.getCompoundDrawablesRelative();
   4055 
   4056         assertNotNull(drawables);
   4057         assertEquals(4, drawables.length);
   4058         assertSame(start, drawables[0]);
   4059         assertSame(top, drawables[1]);
   4060         assertSame(end, drawables[2]);
   4061         assertSame(bottom, drawables[3]);
   4062 
   4063         mTextView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
   4064         mTextView.setCompoundDrawablesRelative(start, top, end, bottom);
   4065         drawables = mTextView.getCompoundDrawablesRelative();
   4066 
   4067         assertNotNull(drawables);
   4068         assertEquals(4, drawables.length);
   4069         assertSame(start, drawables[0]);
   4070         assertSame(top, drawables[1]);
   4071         assertSame(end, drawables[2]);
   4072         assertSame(bottom, drawables[3]);
   4073 
   4074         mTextView.setCompoundDrawablesRelative(null, null, null, null);
   4075         drawables = mTextView.getCompoundDrawablesRelative();
   4076 
   4077         assertNotNull(drawables);
   4078         assertEquals(4, drawables.length);
   4079         assertNull(drawables[0]);
   4080         assertNull(drawables[1]);
   4081         assertNull(drawables[2]);
   4082         assertNull(drawables[3]);
   4083     }
   4084 
   4085     @UiThreadTest
   4086     @Test
   4087     public void testCursorDrawable_isNotNullByDefault() {
   4088         assertNotNull(new TextView(mActivity).getTextCursorDrawable());
   4089     }
   4090 
   4091     @UiThreadTest
   4092     @Test
   4093     public void testCursorDrawable_canBeSet_toDrawable() {
   4094         mTextView = new TextView(mActivity);
   4095         final Drawable cursor = TestUtils.getDrawable(mActivity, R.drawable.blue);
   4096         mTextView.setTextCursorDrawable(cursor);
   4097         assertSame(cursor, mTextView.getTextCursorDrawable());
   4098     }
   4099 
   4100     @UiThreadTest
   4101     @Test
   4102     public void testCursorDrawable_canBeSet_toDrawableResource() {
   4103         mTextView = new TextView(mActivity);
   4104         mTextView.setTextCursorDrawable(R.drawable.start);
   4105         WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.start),
   4106                 ((BitmapDrawable) mTextView.getTextCursorDrawable()).getBitmap());
   4107     }
   4108 
   4109     @UiThreadTest
   4110     @Test
   4111     public void testCursorDrawable_canBeSetToNull() {
   4112         new TextView(mActivity).setTextCursorDrawable(null);
   4113     }
   4114 
   4115     @UiThreadTest
   4116     @Test
   4117     public void testCursorDrawable_canBeSetToZeroResId() {
   4118         new TextView(mActivity).setTextCursorDrawable(0);
   4119     }
   4120 
   4121     @UiThreadTest
   4122     @Test
   4123     public void testHandleDrawables_areNotNullByDefault() {
   4124         mTextView = new TextView(mActivity);
   4125         assertNotNull(mTextView.getTextSelectHandle());
   4126         assertNotNull(mTextView.getTextSelectHandleLeft());
   4127         assertNotNull(mTextView.getTextSelectHandleRight());
   4128     }
   4129 
   4130     @UiThreadTest
   4131     @Test
   4132     public void testHandleDrawables_canBeSet_toDrawables() {
   4133         mTextView = new TextView(mActivity);
   4134 
   4135         final Drawable blue = TestUtils.getDrawable(mActivity, R.drawable.blue);
   4136         final Drawable yellow = TestUtils.getDrawable(mActivity, R.drawable.yellow);
   4137         final Drawable red = TestUtils.getDrawable(mActivity, R.drawable.red);
   4138 
   4139         mTextView.setTextSelectHandle(blue);
   4140         mTextView.setTextSelectHandleLeft(yellow);
   4141         mTextView.setTextSelectHandleRight(red);
   4142 
   4143         assertSame(blue, mTextView.getTextSelectHandle());
   4144         assertSame(yellow, mTextView.getTextSelectHandleLeft());
   4145         assertSame(red, mTextView.getTextSelectHandleRight());
   4146     }
   4147 
   4148     @UiThreadTest
   4149     @Test
   4150     public void testHandleDrawables_canBeSet_toDrawableResources() {
   4151         mTextView = new TextView(mActivity);
   4152 
   4153         mTextView.setTextSelectHandle(R.drawable.start);
   4154         mTextView.setTextSelectHandleLeft(R.drawable.pass);
   4155         mTextView.setTextSelectHandleRight(R.drawable.failed);
   4156 
   4157         WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.start),
   4158                 ((BitmapDrawable) mTextView.getTextSelectHandle()).getBitmap());
   4159         WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.pass),
   4160                 ((BitmapDrawable) mTextView.getTextSelectHandleLeft()).getBitmap());
   4161         WidgetTestUtils.assertEquals(TestUtils.getBitmap(mActivity, R.drawable.failed),
   4162                 ((BitmapDrawable) mTextView.getTextSelectHandleRight()).getBitmap());
   4163     }
   4164 
   4165     @UiThreadTest
   4166     @Test(expected = NullPointerException.class)
   4167     public void testSelectHandleDrawable_cannotBeSetToNull() {
   4168         new TextView(mActivity).setTextSelectHandle(null);
   4169     }
   4170 
   4171     @UiThreadTest
   4172     @Test(expected = IllegalArgumentException.class)
   4173     public void testSelectHandleDrawable_cannotBeSetToZeroResId() {
   4174         new TextView(mActivity).setTextSelectHandle(0);
   4175     }
   4176 
   4177     @UiThreadTest
   4178     @Test(expected = NullPointerException.class)
   4179     public void testSelectHandleDrawableLeft_cannotBeSetToNull() {
   4180         new TextView(mActivity).setTextSelectHandleLeft(null);
   4181     }
   4182 
   4183     @UiThreadTest
   4184     @Test(expected = IllegalArgumentException.class)
   4185     public void testSelectHandleDrawableLeft_cannotBeSetToZeroResId() {
   4186         new TextView(mActivity).setTextSelectHandleLeft(0);
   4187     }
   4188 
   4189     @UiThreadTest
   4190     @Test(expected = NullPointerException.class)
   4191     public void testSelectHandleDrawableRight_cannotBeSetToNull() {
   4192         new TextView(mActivity).setTextSelectHandleRight(null);
   4193     }
   4194 
   4195     @UiThreadTest
   4196     @Test(expected = IllegalArgumentException.class)
   4197     public void testSelectHandleDrawableRight_cannotBeSetToZeroResId() {
   4198         new TextView(mActivity).setTextSelectHandleRight(0);
   4199     }
   4200 
   4201     @Test
   4202     public void testHandleDrawable_canBeSet_whenInsertionHandleIsShown() throws Throwable {
   4203         initTextViewForTypingOnUiThread();
   4204         mActivityRule.runOnUiThread(() -> {
   4205             mTextView.setTextIsSelectable(true);
   4206             mTextView.setText("abcd", BufferType.EDITABLE);
   4207         });
   4208         mInstrumentation.waitForIdleSync();
   4209 
   4210         // Trigger insertion.
   4211         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mTextView);
   4212 
   4213         final boolean[] mDrawn = new boolean[3];
   4214         mActivityRule.runOnUiThread(() -> {
   4215             mTextView.setTextSelectHandle(new TestHandleDrawable(mDrawn, 0));
   4216             mTextView.setTextSelectHandleLeft(new TestHandleDrawable(mDrawn, 1));
   4217             mTextView.setTextSelectHandleRight(new TestHandleDrawable(mDrawn, 2));
   4218         });
   4219         mInstrumentation.waitForIdleSync();
   4220 
   4221         assertTrue(mDrawn[0]);
   4222         assertFalse(mDrawn[1]);
   4223         assertFalse(mDrawn[2]);
   4224     }
   4225 
   4226     @Test
   4227     public void testHandleDrawables_canBeSet_whenSelectionHandlesAreShown()
   4228             throws Throwable {
   4229         initTextViewForTypingOnUiThread();
   4230         mActivityRule.runOnUiThread(() -> {
   4231             mTextView.setTextIsSelectable(true);
   4232             mTextView.setText("abcd", BufferType.EDITABLE);
   4233         });
   4234         mInstrumentation.waitForIdleSync();
   4235 
   4236         // Trigger selection.
   4237         CtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mActivityRule, mTextView);
   4238 
   4239         final boolean[] mDrawn = new boolean[3];
   4240         mActivityRule.runOnUiThread(() -> {
   4241             mTextView.setTextSelectHandle(new TestHandleDrawable(mDrawn, 0));
   4242             mTextView.setTextSelectHandleLeft(new TestHandleDrawable(mDrawn, 1));
   4243             mTextView.setTextSelectHandleRight(new TestHandleDrawable(mDrawn, 2));
   4244         });
   4245         mInstrumentation.waitForIdleSync();
   4246 
   4247         assertFalse(mDrawn[0]);
   4248         assertTrue(mDrawn[1]);
   4249         assertTrue(mDrawn[2]);
   4250     }
   4251 
   4252     @Test
   4253     public void testTextActionModeCallback_loadsHandleDrawables() throws Throwable {
   4254         final String text = "abcde";
   4255         mActivityRule.runOnUiThread(() -> {
   4256             mTextView = new EditText(mActivity);
   4257             mActivity.setContentView(mTextView);
   4258             mTextView.setText(text, BufferType.SPANNABLE);
   4259             mTextView.setTextIsSelectable(true);
   4260             mTextView.requestFocus();
   4261             mTextView.setSelected(true);
   4262             mTextView.setTextClassifier(TextClassifier.NO_OP);
   4263         });
   4264         mInstrumentation.waitForIdleSync();
   4265 
   4266         mActivityRule.runOnUiThread(() -> {
   4267             // Set selection and try to start action mode.
   4268             final Bundle args = new Bundle();
   4269             args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
   4270             args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, text.length());
   4271             mTextView.performAccessibilityAction(
   4272                     AccessibilityNodeInfo.ACTION_SET_SELECTION, args);
   4273         });
   4274         mInstrumentation.waitForIdleSync();
   4275 
   4276         // There should be no null pointer exception caused by handle drawables not being loaded.
   4277     }
   4278 
   4279     private class TestHandleDrawable extends ColorDrawable {
   4280         private final boolean[] mArray;
   4281         private final int mIndex;
   4282 
   4283         TestHandleDrawable(final boolean[] array, final int index) {
   4284             mArray = array;
   4285             mIndex = index;
   4286         }
   4287 
   4288         @Override
   4289         public void draw(Canvas canvas) {
   4290             super.draw(canvas);
   4291             mArray[mIndex] = true;
   4292         }
   4293     }
   4294 
   4295     @Test
   4296     public void testSingleLine() throws Throwable {
   4297         mActivityRule.runOnUiThread(() -> mTextView = new TextView(mActivity));
   4298         mInstrumentation.waitForIdleSync();
   4299 
   4300         setSpannableText(mTextView, "This is a really long sentence"
   4301                 + " which can not be placed in one line on the screen.");
   4302 
   4303         // Narrow layout assures that the text will get wrapped.
   4304         final FrameLayout innerLayout = new FrameLayout(mActivity);
   4305         innerLayout.setLayoutParams(new ViewGroup.LayoutParams(100, 100));
   4306         innerLayout.addView(mTextView);
   4307 
   4308         final FrameLayout layout = new FrameLayout(mActivity);
   4309         layout.addView(innerLayout);
   4310 
   4311         mActivityRule.runOnUiThread(() -> {
   4312             mActivity.setContentView(layout);
   4313             mTextView.setSingleLine(true);
   4314         });
   4315         mInstrumentation.waitForIdleSync();
   4316 
   4317         assertEquals(SingleLineTransformationMethod.getInstance(),
   4318                 mTextView.getTransformationMethod());
   4319 
   4320         int singleLineWidth = 0;
   4321         int singleLineHeight = 0;
   4322 
   4323         if (mTextView.getLayout() != null) {
   4324             singleLineWidth = mTextView.getLayout().getWidth();
   4325             singleLineHeight = mTextView.getLayout().getHeight();
   4326         }
   4327 
   4328         mActivityRule.runOnUiThread(() -> mTextView.setSingleLine(false));
   4329         mInstrumentation.waitForIdleSync();
   4330         assertEquals(null, mTextView.getTransformationMethod());
   4331 
   4332         if (mTextView.getLayout() != null) {
   4333             assertTrue(mTextView.getLayout().getHeight() > singleLineHeight);
   4334             assertTrue(mTextView.getLayout().getWidth() < singleLineWidth);
   4335         }
   4336 
   4337         // same behaviours as setSingLine(true)
   4338         mActivityRule.runOnUiThread(mTextView::setSingleLine);
   4339         mInstrumentation.waitForIdleSync();
   4340         assertEquals(SingleLineTransformationMethod.getInstance(),
   4341                 mTextView.getTransformationMethod());
   4342 
   4343         if (mTextView.getLayout() != null) {
   4344             assertEquals(singleLineHeight, mTextView.getLayout().getHeight());
   4345             assertEquals(singleLineWidth, mTextView.getLayout().getWidth());
   4346         }
   4347     }
   4348 
   4349     @UiThreadTest
   4350     @Test
   4351     public void testIsSingleLineTrue() {
   4352         mTextView = new TextView(mActivity);
   4353 
   4354         mTextView.setSingleLine(true);
   4355 
   4356         assertTrue(mTextView.isSingleLine());
   4357     }
   4358 
   4359     @UiThreadTest
   4360     @Test
   4361     public void testIsSingleLineFalse() {
   4362         mTextView = new TextView(mActivity);
   4363 
   4364         mTextView.setSingleLine(false);
   4365 
   4366         assertFalse(mTextView.isSingleLine());
   4367     }
   4368 
   4369     @Test
   4370     public void testXmlIsSingleLineTrue() {
   4371         final Context context = InstrumentationRegistry.getTargetContext();
   4372         final LayoutInflater layoutInflater = LayoutInflater.from(context);
   4373         final View root = layoutInflater.inflate(R.layout.textview_singleline, null);
   4374 
   4375         mTextView = root.findViewById(R.id.textview_singleline_true);
   4376 
   4377         assertTrue(mTextView.isSingleLine());
   4378     }
   4379 
   4380     @Test
   4381     public void testXmlIsSingleLineFalse() {
   4382         final Context context = InstrumentationRegistry.getTargetContext();
   4383         final LayoutInflater layoutInflater = LayoutInflater.from(context);
   4384         final View root = layoutInflater.inflate(R.layout.textview_singleline, null);
   4385 
   4386         mTextView = root.findViewById(R.id.textview_singleline_false);
   4387 
   4388         assertFalse(mTextView.isSingleLine());
   4389     }
   4390 
   4391     @UiThreadTest
   4392     @Test
   4393     public void testAccessMaxLines() {
   4394         mTextView = findTextView(R.id.textview_text);
   4395         mTextView.setWidth((int) (mTextView.getPaint().measureText(LONG_TEXT) / 4));
   4396         mTextView.setText(LONG_TEXT);
   4397 
   4398         final int maxLines = 2;
   4399         assertTrue(mTextView.getLineCount() > maxLines);
   4400 
   4401         mTextView.setMaxLines(maxLines);
   4402         mTextView.requestLayout();
   4403 
   4404         assertEquals(2, mTextView.getMaxLines());
   4405         assertEquals(-1, mTextView.getMaxHeight());
   4406         assertTrue(mTextView.getHeight() <= maxLines * mTextView.getLineHeight());
   4407     }
   4408 
   4409     @UiThreadTest
   4410     @Test
   4411     public void testHyphenationNotHappen_frequencyNone() {
   4412         final int[] BREAK_STRATEGIES = {
   4413             Layout.BREAK_STRATEGY_SIMPLE, Layout.BREAK_STRATEGY_HIGH_QUALITY,
   4414             Layout.BREAK_STRATEGY_BALANCED };
   4415 
   4416         mTextView = findTextView(R.id.textview_text);
   4417 
   4418         for (int breakStrategy : BREAK_STRATEGIES) {
   4419             for (int charWidth = 10; charWidth < 120; charWidth += 5) {
   4420                 // Change the text view's width to charWidth width.
   4421                 final String substring = LONG_TEXT.substring(0, charWidth);
   4422                 mTextView.setWidth((int) Math.ceil(mTextView.getPaint().measureText(substring)));
   4423 
   4424                 mTextView.setText(LONG_TEXT);
   4425                 mTextView.setBreakStrategy(breakStrategy);
   4426 
   4427                 mTextView.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE);
   4428 
   4429                 mTextView.requestLayout();
   4430                 mTextView.onPreDraw();  // For freezing the layout.
   4431                 Layout layout = mTextView.getLayout();
   4432 
   4433                 final int lineCount = layout.getLineCount();
   4434                 for (int line = 0; line < lineCount; ++line) {
   4435                     final int lineEnd = layout.getLineEnd(line);
   4436                     // In any width, any break strategy, hyphenation should not happen if
   4437                     // HYPHENATION_FREQUENCY_NONE is specified.
   4438                     assertTrue(lineEnd == LONG_TEXT.length() ||
   4439                             Character.isWhitespace(LONG_TEXT.charAt(lineEnd - 1)));
   4440                 }
   4441             }
   4442         }
   4443     }
   4444 
   4445     @UiThreadTest
   4446     @Test
   4447     public void testHyphenationNotHappen_breakStrategySimple() {
   4448         final int[] HYPHENATION_FREQUENCIES = {
   4449             Layout.HYPHENATION_FREQUENCY_NORMAL, Layout.HYPHENATION_FREQUENCY_FULL,
   4450             Layout.HYPHENATION_FREQUENCY_NONE };
   4451 
   4452         mTextView = findTextView(R.id.textview_text);
   4453 
   4454         for (int hyphenationFrequency: HYPHENATION_FREQUENCIES) {
   4455             for (int charWidth = 10; charWidth < 120; charWidth += 5) {
   4456                 // Change the text view's width to charWidth width.
   4457                 final String substring = LONG_TEXT.substring(0, charWidth);
   4458                 mTextView.setWidth((int) Math.ceil(mTextView.getPaint().measureText(substring)));
   4459 
   4460                 mTextView.setText(LONG_TEXT);
   4461                 mTextView.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE);
   4462 
   4463                 mTextView.setHyphenationFrequency(hyphenationFrequency);
   4464 
   4465                 mTextView.requestLayout();
   4466                 mTextView.onPreDraw();  // For freezing the layout.
   4467                 Layout layout = mTextView.getLayout();
   4468 
   4469                 final int lineCount = layout.getLineCount();
   4470                 for (int line = 0; line < lineCount; ++line) {
   4471                     final int lineEnd = layout.getLineEnd(line);
   4472                     // In any width, any hyphenation frequency, hyphenation should not happen if
   4473                     // BREAK_STRATEGY_SIMPLE is specified.
   4474                     assertTrue(lineEnd == LONG_TEXT.length() ||
   4475                             Character.isWhitespace(LONG_TEXT.charAt(lineEnd - 1)));
   4476                 }
   4477             }
   4478         }
   4479     }
   4480 
   4481     @UiThreadTest
   4482     @Test
   4483     public void testSetMaxLinesException() {
   4484         mTextView = new TextView(mActivity);
   4485         mActivity.setContentView(mTextView);
   4486         mTextView.setWidth(mTextView.getWidth() >> 3);
   4487         mTextView.setMaxLines(-1);
   4488     }
   4489 
   4490     @Test
   4491     public void testAccessMinLines() throws Throwable {
   4492         mTextView = findTextView(R.id.textview_text);
   4493         setWidth(mTextView.getWidth() >> 3);
   4494         int originalLines = mTextView.getLineCount();
   4495 
   4496         setMinLines(originalLines - 1);
   4497         assertTrue((originalLines - 1) * mTextView.getLineHeight() <= mTextView.getHeight());
   4498         assertEquals(originalLines - 1, mTextView.getMinLines());
   4499         assertEquals(-1, mTextView.getMinHeight());
   4500 
   4501         setMinLines(originalLines + 1);
   4502         assertTrue((originalLines + 1) * mTextView.getLineHeight() <= mTextView.getHeight());
   4503         assertEquals(originalLines + 1, mTextView.getMinLines());
   4504         assertEquals(-1, mTextView.getMinHeight());
   4505     }
   4506 
   4507     @Test
   4508     public void testSetLines() throws Throwable {
   4509         mTextView = findTextView(R.id.textview_text);
   4510         // make it multiple lines
   4511         setWidth(mTextView.getWidth() >> 3);
   4512         int originalLines = mTextView.getLineCount();
   4513 
   4514         setLines(originalLines - 1);
   4515         assertTrue((originalLines - 1) * mTextView.getLineHeight() <= mTextView.getHeight());
   4516 
   4517         setLines(originalLines + 1);
   4518         assertTrue((originalLines + 1) * mTextView.getLineHeight() <= mTextView.getHeight());
   4519     }
   4520 
   4521     @UiThreadTest
   4522     @Test
   4523     public void testSetLinesException() {
   4524         mTextView = new TextView(mActivity);
   4525         mActivity.setContentView(mTextView);
   4526         mTextView.setWidth(mTextView.getWidth() >> 3);
   4527         mTextView.setLines(-1);
   4528     }
   4529 
   4530     @UiThreadTest
   4531     @Test
   4532     public void testGetExtendedPaddingTop() {
   4533         mTextView = findTextView(R.id.textview_text);
   4534         // Initialized value
   4535         assertEquals(0, mTextView.getExtendedPaddingTop());
   4536 
   4537         // After Set a Drawable
   4538         final Drawable top = TestUtils.getDrawable(mActivity, R.drawable.red);
   4539         top.setBounds(0, 0, 100, 10);
   4540         mTextView.setCompoundDrawables(null, top, null, null);
   4541         assertEquals(mTextView.getCompoundPaddingTop(), mTextView.getExtendedPaddingTop());
   4542 
   4543         // Change line count
   4544         mTextView.setLines(mTextView.getLineCount() - 1);
   4545         mTextView.setGravity(Gravity.BOTTOM);
   4546 
   4547         assertTrue(mTextView.getExtendedPaddingTop() > 0);
   4548     }
   4549 
   4550     @UiThreadTest
   4551     @Test
   4552     public void testGetExtendedPaddingBottom() {
   4553         mTextView = findTextView(R.id.textview_text);
   4554         // Initialized value
   4555         assertEquals(0, mTextView.getExtendedPaddingBottom());
   4556 
   4557         // After Set a Drawable
   4558         final Drawable bottom = TestUtils.getDrawable(mActivity, R.drawable.red);
   4559         bottom.setBounds(0, 0, 100, 10);
   4560         mTextView.setCompoundDrawables(null, null, null, bottom);
   4561         assertEquals(mTextView.getCompoundPaddingBottom(), mTextView.getExtendedPaddingBottom());
   4562 
   4563         // Change line count
   4564         mTextView.setLines(mTextView.getLineCount() - 1);
   4565         mTextView.setGravity(Gravity.CENTER_VERTICAL);
   4566 
   4567         assertTrue(mTextView.getExtendedPaddingBottom() > 0);
   4568     }
   4569 
   4570     @Test
   4571     public void testGetTotalPaddingTop() throws Throwable {
   4572         mTextView = findTextView(R.id.textview_text);
   4573         // Initialized value
   4574         assertEquals(0, mTextView.getTotalPaddingTop());
   4575 
   4576         // After Set a Drawable
   4577         final Drawable top = TestUtils.getDrawable(mActivity, R.drawable.red);
   4578         top.setBounds(0, 0, 100, 10);
   4579         mActivityRule.runOnUiThread(() -> {
   4580             mTextView.setCompoundDrawables(null, top, null, null);
   4581             mTextView.setLines(mTextView.getLineCount() - 1);
   4582             mTextView.setGravity(Gravity.BOTTOM);
   4583         });
   4584         mInstrumentation.waitForIdleSync();
   4585         assertEquals(mTextView.getExtendedPaddingTop(), mTextView.getTotalPaddingTop());
   4586 
   4587         // Change line count
   4588         setLines(mTextView.getLineCount() + 1);
   4589         int expected = mTextView.getHeight()
   4590                 - mTextView.getExtendedPaddingBottom()
   4591                 - mTextView.getLayout().getLineTop(mTextView.getLineCount());
   4592         assertEquals(expected, mTextView.getTotalPaddingTop());
   4593     }
   4594 
   4595     @Test
   4596     public void testGetTotalPaddingBottom() throws Throwable {
   4597         mTextView = findTextView(R.id.textview_text);
   4598         // Initialized value
   4599         assertEquals(0, mTextView.getTotalPaddingBottom());
   4600 
   4601         // After Set a Drawable
   4602         final Drawable bottom = TestUtils.getDrawable(mActivity, R.drawable.red);
   4603         bottom.setBounds(0, 0, 100, 10);
   4604         mActivityRule.runOnUiThread(() -> {
   4605             mTextView.setCompoundDrawables(null, null, null, bottom);
   4606             mTextView.setLines(mTextView.getLineCount() - 1);
   4607             mTextView.setGravity(Gravity.CENTER_VERTICAL);
   4608         });
   4609         mInstrumentation.waitForIdleSync();
   4610         assertEquals(mTextView.getExtendedPaddingBottom(), mTextView.getTotalPaddingBottom());
   4611 
   4612         // Change line count
   4613         setLines(mTextView.getLineCount() + 1);
   4614         int expected = ((mTextView.getHeight()
   4615                 - mTextView.getExtendedPaddingBottom()
   4616                 - mTextView.getExtendedPaddingTop()
   4617                 - mTextView.getLayout().getLineBottom(mTextView.getLineCount())) >> 1)
   4618                 + mTextView.getExtendedPaddingBottom();
   4619         assertEquals(expected, mTextView.getTotalPaddingBottom());
   4620     }
   4621 
   4622     @UiThreadTest
   4623     @Test
   4624     public void testGetTotalPaddingLeft() {
   4625         mTextView = findTextView(R.id.textview_text);
   4626         // Initialized value
   4627         assertEquals(0, mTextView.getTotalPaddingLeft());
   4628 
   4629         // After Set a Drawable
   4630         Drawable left = TestUtils.getDrawable(mActivity, R.drawable.red);
   4631         left.setBounds(0, 0, 10, 100);
   4632         mTextView.setCompoundDrawables(left, null, null, null);
   4633         mTextView.setGravity(Gravity.RIGHT);
   4634         assertEquals(mTextView.getCompoundPaddingLeft(), mTextView.getTotalPaddingLeft());
   4635 
   4636         // Change width
   4637         mTextView.setWidth(Integer.MAX_VALUE);
   4638         assertEquals(mTextView.getCompoundPaddingLeft(), mTextView.getTotalPaddingLeft());
   4639     }
   4640 
   4641     @UiThreadTest
   4642     @Test
   4643     public void testGetTotalPaddingRight() {
   4644         mTextView = findTextView(R.id.textview_text);
   4645         // Initialized value
   4646         assertEquals(0, mTextView.getTotalPaddingRight());
   4647 
   4648         // After Set a Drawable
   4649         Drawable right = TestUtils.getDrawable(mActivity, R.drawable.red);
   4650         right.setBounds(0, 0, 10, 100);
   4651         mTextView.setCompoundDrawables(null, null, right, null);
   4652         mTextView.setGravity(Gravity.CENTER_HORIZONTAL);
   4653         assertEquals(mTextView.getCompoundPaddingRight(), mTextView.getTotalPaddingRight());
   4654 
   4655         // Change width
   4656         mTextView.setWidth(Integer.MAX_VALUE);
   4657         assertEquals(mTextView.getCompoundPaddingRight(), mTextView.getTotalPaddingRight());
   4658     }
   4659 
   4660     @UiThreadTest
   4661     @Test
   4662     public void testGetUrls() {
   4663         mTextView = new TextView(mActivity);
   4664 
   4665         URLSpan[] spans = mTextView.getUrls();
   4666         assertEquals(0, spans.length);
   4667 
   4668         String url = "http://www.google.com";
   4669         String email = "name (at) gmail.com";
   4670         String string = url + " mailto:" + email;
   4671         SpannableString spannable = new SpannableString(string);
   4672         spannable.setSpan(new URLSpan(url), 0, url.length(), 0);
   4673         mTextView.setText(spannable, BufferType.SPANNABLE);
   4674         spans = mTextView.getUrls();
   4675         assertEquals(1, spans.length);
   4676         assertEquals(url, spans[0].getURL());
   4677 
   4678         spannable.setSpan(new URLSpan(email), 0, email.length(), 0);
   4679         mTextView.setText(spannable, BufferType.SPANNABLE);
   4680 
   4681         spans = mTextView.getUrls();
   4682         assertEquals(2, spans.length);
   4683         assertEquals(url, spans[0].getURL());
   4684         assertEquals(email, spans[1].getURL());
   4685 
   4686         // test the situation that param what is not a URLSpan
   4687         spannable.setSpan(new Object(), 0, 9, 0);
   4688         mTextView.setText(spannable, BufferType.SPANNABLE);
   4689         spans = mTextView.getUrls();
   4690         assertEquals(2, spans.length);
   4691     }
   4692 
   4693     @UiThreadTest
   4694     @Test
   4695     public void testSetPadding() {
   4696         mTextView = new TextView(mActivity);
   4697 
   4698         mTextView.setPadding(0, 1, 2, 4);
   4699         assertEquals(0, mTextView.getPaddingLeft());
   4700         assertEquals(1, mTextView.getPaddingTop());
   4701         assertEquals(2, mTextView.getPaddingRight());
   4702         assertEquals(4, mTextView.getPaddingBottom());
   4703 
   4704         mTextView.setPadding(10, 20, 30, 40);
   4705         assertEquals(10, mTextView.getPaddingLeft());
   4706         assertEquals(20, mTextView.getPaddingTop());
   4707         assertEquals(30, mTextView.getPaddingRight());
   4708         assertEquals(40, mTextView.getPaddingBottom());
   4709     }
   4710 
   4711     @UiThreadTest
   4712     @Test
   4713     public void testBaselineAttributes() {
   4714         mTextView = findTextView(R.id.textview_baseline);
   4715 
   4716         final int firstBaselineToTopHeight = mTextView.getResources()
   4717                 .getDimensionPixelSize(R.dimen.textview_firstBaselineToTopHeight);
   4718         final int lastBaselineToBottomHeight = mTextView.getResources()
   4719                 .getDimensionPixelSize(R.dimen.textview_lastBaselineToBottomHeight);
   4720         final int lineHeight = mTextView.getResources()
   4721                 .getDimensionPixelSize(R.dimen.textview_lineHeight);
   4722 
   4723         assertEquals(firstBaselineToTopHeight, mTextView.getFirstBaselineToTopHeight());
   4724         assertEquals(lastBaselineToBottomHeight, mTextView.getLastBaselineToBottomHeight());
   4725         assertEquals(lineHeight, mTextView.getLineHeight());
   4726     }
   4727 
   4728     @UiThreadTest
   4729     @Test
   4730     public void testSetFirstBaselineToTopHeight() {
   4731         mTextView = new TextView(mActivity);
   4732         mTextView.setText("This is some random text");
   4733         final int padding = 100;
   4734         mTextView.setPadding(padding, padding, padding, padding);
   4735 
   4736         final FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
   4737         final int fontMetricsTop = Math.max(
   4738                 Math.abs(fontMetrics.top), Math.abs(fontMetrics.ascent));
   4739 
   4740         int firstBaselineToTopHeight = fontMetricsTop + 10;
   4741         mTextView.setFirstBaselineToTopHeight(firstBaselineToTopHeight);
   4742         assertEquals(firstBaselineToTopHeight, mTextView.getFirstBaselineToTopHeight());
   4743         assertNotEquals(padding, mTextView.getPaddingTop());
   4744 
   4745         firstBaselineToTopHeight = fontMetricsTop + 40;
   4746         mTextView.setFirstBaselineToTopHeight(firstBaselineToTopHeight);
   4747         assertEquals(firstBaselineToTopHeight, mTextView.getFirstBaselineToTopHeight());
   4748 
   4749         mTextView.setPadding(padding, padding, padding, padding);
   4750         assertEquals(padding, mTextView.getPaddingTop());
   4751     }
   4752 
   4753     @UiThreadTest
   4754     @Test
   4755     public void testSetFirstBaselineToTopHeight_tooSmall() {
   4756         mTextView = new TextView(mActivity);
   4757         mTextView.setText("This is some random text");
   4758         final int padding = 100;
   4759         mTextView.setPadding(padding, padding, padding, padding);
   4760 
   4761         final FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
   4762         final int fontMetricsTop = Math.min(
   4763                 Math.abs(fontMetrics.top), Math.abs(fontMetrics.ascent));
   4764 
   4765         int firstBaselineToTopHeight = fontMetricsTop - 1;
   4766         mTextView.setFirstBaselineToTopHeight(firstBaselineToTopHeight);
   4767         assertNotEquals(firstBaselineToTopHeight, mTextView.getFirstBaselineToTopHeight());
   4768         assertEquals(padding, mTextView.getPaddingTop());
   4769     }
   4770 
   4771     @UiThreadTest
   4772     @Test(expected = IllegalArgumentException.class)
   4773     public void testSetFirstBaselineToTopHeight_negative() {
   4774         new TextView(mActivity).setFirstBaselineToTopHeight(-1);
   4775     }
   4776 
   4777     @UiThreadTest
   4778     @Test
   4779     public void testSetLastBaselineToBottomHeight() {
   4780         mTextView = new TextView(mActivity);
   4781         mTextView.setText("This is some random text");
   4782         final int padding = 100;
   4783         mTextView.setPadding(padding, padding, padding, padding);
   4784 
   4785         final FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
   4786         final int fontMetricsBottom = Math.max(
   4787                 Math.abs(fontMetrics.bottom), Math.abs(fontMetrics.descent));
   4788 
   4789         int lastBaselineToBottomHeight = fontMetricsBottom + 20;
   4790         mTextView.setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
   4791         assertEquals(lastBaselineToBottomHeight, mTextView.getLastBaselineToBottomHeight());
   4792         assertNotEquals(padding, mTextView.getPaddingBottom());
   4793 
   4794         lastBaselineToBottomHeight = fontMetricsBottom + 30;
   4795         mTextView.setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
   4796         assertEquals(lastBaselineToBottomHeight, mTextView.getLastBaselineToBottomHeight());
   4797 
   4798         mTextView.setPadding(padding, padding, padding, padding);
   4799         assertEquals(padding, mTextView.getPaddingBottom());
   4800     }
   4801 
   4802     @UiThreadTest
   4803     @Test
   4804     public void testSetLastBaselineToBottomHeight_tooSmall() {
   4805         mTextView = new TextView(mActivity);
   4806         mTextView.setText("This is some random text");
   4807         final int padding = 100;
   4808         mTextView.setPadding(padding, padding, padding, padding);
   4809 
   4810         final FontMetricsInt fontMetrics = mTextView.getPaint().getFontMetricsInt();
   4811         final int fontMetricsBottom = Math.min(
   4812                 Math.abs(fontMetrics.bottom), Math.abs(fontMetrics.descent));
   4813 
   4814         int lastBaselineToBottomHeight = fontMetricsBottom - 1;
   4815         mTextView.setLastBaselineToBottomHeight(lastBaselineToBottomHeight);
   4816         assertNotEquals(lastBaselineToBottomHeight, mTextView.getLastBaselineToBottomHeight());
   4817         assertEquals(padding, mTextView.getPaddingBottom());
   4818     }
   4819 
   4820     @UiThreadTest
   4821     @Test(expected = IllegalArgumentException.class)
   4822     public void testSetLastBaselineToBottomHeight_negative() {
   4823         new TextView(mActivity).setLastBaselineToBottomHeight(-1);
   4824     }
   4825 
   4826     @UiThreadTest
   4827     @Test
   4828     public void testSetLineHeight() {
   4829         mTextView = new TextView(mActivity);
   4830         mTextView.setText("This is some random text");
   4831         final float lineSpacingExtra = 50;
   4832         final float lineSpacingMultiplier = 0.2f;
   4833         mTextView.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier);
   4834 
   4835         mTextView.setLineHeight(100);
   4836         assertEquals(100, mTextView.getLineHeight());
   4837         assertNotEquals(lineSpacingExtra, mTextView.getLineSpacingExtra(), 0);
   4838         assertNotEquals(lineSpacingMultiplier, mTextView.getLineSpacingMultiplier(), 0);
   4839 
   4840         mTextView.setLineHeight(200);
   4841         assertEquals(200, mTextView.getLineHeight());
   4842 
   4843         mTextView.setLineSpacing(lineSpacingExtra, lineSpacingMultiplier);
   4844         assertEquals(lineSpacingExtra, mTextView.getLineSpacingExtra(), 0);
   4845         assertEquals(lineSpacingMultiplier, mTextView.getLineSpacingMultiplier(), 0);
   4846     }
   4847 
   4848     @UiThreadTest
   4849     @Test(expected = IllegalArgumentException.class)
   4850     public void testSetLineHeight_negative() {
   4851         new TextView(mActivity).setLineHeight(-1);
   4852     }
   4853 
   4854     @UiThreadTest
   4855     @Test
   4856     public void testDeprecatedSetTextAppearance() {
   4857         mTextView = new TextView(mActivity);
   4858 
   4859         mTextView.setTextAppearance(mActivity, R.style.TextAppearance_All);
   4860         assertEquals(mActivity.getResources().getColor(R.drawable.black),
   4861                 mTextView.getCurrentTextColor());
   4862         assertEquals(20f, mTextView.getTextSize(), 0.01f);
   4863         assertEquals(Typeface.BOLD, mTextView.getTypeface().getStyle());
   4864         assertEquals(mActivity.getResources().getColor(R.drawable.red),
   4865                 mTextView.getCurrentHintTextColor());
   4866         assertEquals(mActivity.getResources().getColor(R.drawable.blue),
   4867                 mTextView.getLinkTextColors().getDefaultColor());
   4868 
   4869         mTextView.setTextAppearance(mActivity, R.style.TextAppearance_Colors);
   4870         assertEquals(mActivity.getResources().getColor(R.drawable.black),
   4871                 mTextView.getCurrentTextColor());
   4872         assertEquals(mActivity.getResources().getColor(R.drawable.blue),
   4873                 mTextView.getCurrentHintTextColor());
   4874         assertEquals(mActivity.getResources().getColor(R.drawable.yellow),
   4875                 mTextView.getLinkTextColors().getDefaultColor());
   4876 
   4877         mTextView.setTextAppearance(mActivity, R.style.TextAppearance_NotColors);
   4878         assertEquals(17f, mTextView.getTextSize(), 0.01f);
   4879         assertEquals(Typeface.NORMAL, mTextView.getTypeface().getStyle());
   4880 
   4881         mTextView.setTextAppearance(mActivity, R.style.TextAppearance_Style);
   4882         assertEquals(null, mTextView.getTypeface());
   4883     }
   4884 
   4885     @UiThreadTest
   4886     @Test
   4887     public void testSetTextAppearance() {
   4888         mTextView = new TextView(mActivity);
   4889 
   4890         mTextView.setTextAppearance(R.style.TextAppearance_All);
   4891         assertEquals(mActivity.getResources().getColor(R.drawable.black),
   4892                 mTextView.getCurrentTextColor());
   4893         assertEquals(20f, mTextView.getTextSize(), 0.01f);
   4894         assertEquals(Typeface.BOLD, mTextView.getTypeface().getStyle());
   4895         assertEquals(mActivity.getResources().getColor(R.drawable.red),
   4896                 mTextView.getCurrentHintTextColor());
   4897         assertEquals(mActivity.getResources().getColor(R.drawable.blue),
   4898                 mTextView.getLinkTextColors().getDefaultColor());
   4899         assertEquals(mActivity.getResources().getColor(R.drawable.yellow),
   4900                 mTextView.getHighlightColor());
   4901 
   4902         mTextView.setTextAppearance(R.style.TextAppearance_Colors);
   4903         assertEquals(mActivity.getResources().getColor(R.drawable.black),
   4904                 mTextView.getCurrentTextColor());
   4905         assertEquals(mActivity.getResources().getColor(R.drawable.blue),
   4906                 mTextView.getCurrentHintTextColor());
   4907         assertEquals(mActivity.getResources().getColor(R.drawable.yellow),
   4908                 mTextView.getLinkTextColors().getDefaultColor());
   4909         assertEquals(mActivity.getResources().getColor(R.drawable.red),
   4910                 mTextView.getHighlightColor());
   4911 
   4912         mTextView.setTextAppearance(R.style.TextAppearance_NotColors);
   4913         assertEquals(17f, mTextView.getTextSize(), 0.01f);
   4914         assertEquals(Typeface.NORMAL, mTextView.getTypeface().getStyle());
   4915 
   4916         mTextView.setTextAppearance(R.style.TextAppearance_Style);
   4917         assertEquals(null, mTextView.getTypeface());
   4918     }
   4919 
   4920     @Test
   4921     public void testXmlTextAppearance() {
   4922         mTextView = findTextView(R.id.textview_textappearance_attrs1);
   4923         assertEquals(22f, mTextView.getTextSize(), 0.01f);
   4924         Typeface italicSans = Typeface.create(Typeface.SANS_SERIF, Typeface.ITALIC);
   4925         assertEquals(italicSans, mTextView.getTypeface());
   4926         assertEquals(Typeface.ITALIC, mTextView.getTypeface().getStyle());
   4927         assertTrue(mTextView.isAllCaps());
   4928         assertEquals(2.4f, mTextView.getLetterSpacing(), 0.01f);
   4929         assertEquals("smcp", mTextView.getFontFeatureSettings());
   4930 
   4931         mTextView = findTextView(R.id.textview_textappearance_attrs2);
   4932         assertEquals(Typeface.MONOSPACE, mTextView.getTypeface());
   4933         assertEquals(mActivity.getResources().getColor(R.drawable.red),
   4934                 mTextView.getShadowColor());
   4935         assertEquals(10.3f, mTextView.getShadowDx(), 0.01f);
   4936         assertEquals(0.5f, mTextView.getShadowDy(), 0.01f);
   4937         assertEquals(3.3f, mTextView.getShadowRadius(), 0.01f);
   4938         assertTrue(mTextView.isElegantTextHeight());
   4939 
   4940         // This TextView has both a TextAppearance and a style, so the style should override.
   4941         mTextView = findTextView(R.id.textview_textappearance_attrs3);
   4942         assertEquals(32f, mTextView.getTextSize(), 0.01f);
   4943         Typeface boldSerif = Typeface.create(Typeface.SERIF, Typeface.BOLD);
   4944         assertEquals(boldSerif, mTextView.getTypeface());
   4945         assertEquals(Typeface.BOLD, mTextView.getTypeface().getStyle());
   4946         assertFalse(mTextView.isAllCaps());
   4947         assertEquals(2.6f, mTextView.getLetterSpacing(), 0.01f);
   4948         assertEquals(mActivity.getResources().getColor(R.drawable.blue),
   4949                 mTextView.getShadowColor());
   4950         assertEquals(1.3f, mTextView.getShadowDx(), 0.01f);
   4951         assertEquals(10.5f, mTextView.getShadowDy(), 0.01f);
   4952         assertEquals(5.3f, mTextView.getShadowRadius(), 0.01f);
   4953         assertFalse(mTextView.isElegantTextHeight());
   4954 
   4955         // This TextView has no TextAppearance and has a style, so the style should be applied.
   4956         mTextView = findTextView(R.id.textview_textappearance_attrs4);
   4957         assertEquals(32f, mTextView.getTextSize(), 0.01f);
   4958         assertEquals(boldSerif, mTextView.getTypeface());
   4959         assertEquals(Typeface.BOLD, mTextView.getTypeface().getStyle());
   4960         assertFalse(mTextView.isAllCaps());
   4961         assertEquals(2.6f, mTextView.getLetterSpacing(), 0.01f);
   4962         assertEquals(mActivity.getResources().getColor(R.drawable.blue),
   4963                 mTextView.getShadowColor());
   4964         assertEquals(1.3f, mTextView.getShadowDx(), 0.01f);
   4965         assertEquals(10.5f, mTextView.getShadowDy(), 0.01f);
   4966         assertEquals(5.3f, mTextView.getShadowRadius(), 0.01f);
   4967         assertFalse(mTextView.isElegantTextHeight());
   4968 
   4969         // Note: text, link and hint colors can't be tested due to the default style overriding
   4970         // values b/63923542
   4971     }
   4972 
   4973     @Test
   4974     public void testXmlTypefaceFontFamilyHierarchy() {
   4975         // This view has typeface=serif set on the view directly and a fontFamily on the appearance.
   4976         // In this case, the attr set directly on the view should take precedence.
   4977         mTextView = findTextView(R.id.textview_textappearance_attrs_serif_fontfamily);
   4978 
   4979         assertEquals(Typeface.SERIF, mTextView.getTypeface());
   4980     }
   4981 
   4982     @Test
   4983     public void testAttributeReading_allCapsAndPassword() {
   4984         // This TextView has all caps & password, therefore all caps should be ignored.
   4985         mTextView = findTextView(R.id.textview_textappearance_attrs_allcaps_password);
   4986         assertFalse(mTextView.isAllCaps());
   4987     }
   4988 
   4989     @UiThreadTest
   4990     @Test
   4991     public void testAccessCompoundDrawableTint() {
   4992         mTextView = new TextView(mActivity);
   4993 
   4994         ColorStateList colors = ColorStateList.valueOf(Color.RED);
   4995         mTextView.setCompoundDrawableTintList(colors);
   4996         mTextView.setCompoundDrawableTintMode(PorterDuff.Mode.XOR);
   4997         assertSame(colors, mTextView.getCompoundDrawableTintList());
   4998         assertEquals(PorterDuff.Mode.XOR, mTextView.getCompoundDrawableTintMode());
   4999 
   5000         // Ensure the tint is preserved across drawable changes.
   5001         mTextView.setCompoundDrawablesRelative(null, null, null, null);
   5002         assertSame(colors, mTextView.getCompoundDrawableTintList());
   5003         assertEquals(PorterDuff.Mode.XOR, mTextView.getCompoundDrawableTintMode());
   5004 
   5005         mTextView.setCompoundDrawables(null, null, null, null);
   5006         assertSame(colors, mTextView.getCompoundDrawableTintList());
   5007         assertEquals(PorterDuff.Mode.XOR, mTextView.getCompoundDrawableTintMode());
   5008 
   5009         ColorDrawable dr1 = new ColorDrawable(Color.RED);
   5010         ColorDrawable dr2 = new ColorDrawable(Color.GREEN);
   5011         ColorDrawable dr3 = new ColorDrawable(Color.BLUE);
   5012         ColorDrawable dr4 = new ColorDrawable(Color.YELLOW);
   5013         mTextView.setCompoundDrawables(dr1, dr2, dr3, dr4);
   5014         assertSame(colors, mTextView.getCompoundDrawableTintList());
   5015         assertEquals(PorterDuff.Mode.XOR, mTextView.getCompoundDrawableTintMode());
   5016     }
   5017 
   5018     @Test
   5019     public void testAccessCompoundDrawableTintBlendMode() {
   5020         mTextView = new TextView(InstrumentationRegistry.getTargetContext());
   5021 
   5022         ColorStateList colors = ColorStateList.valueOf(Color.RED);
   5023         mTextView.setCompoundDrawableTintList(colors);
   5024         mTextView.setCompoundDrawableTintBlendMode(BlendMode.XOR);
   5025         assertSame(colors, mTextView.getCompoundDrawableTintList());
   5026         assertEquals(BlendMode.XOR, mTextView.getCompoundDrawableTintBlendMode());
   5027 
   5028         // Ensure the tint is preserved across drawable changes.
   5029         mTextView.setCompoundDrawablesRelative(null, null, null, null);
   5030         assertSame(colors, mTextView.getCompoundDrawableTintList());
   5031         assertEquals(BlendMode.XOR, mTextView.getCompoundDrawableTintBlendMode());
   5032 
   5033         mTextView.setCompoundDrawables(null, null, null, null);
   5034         assertSame(colors, mTextView.getCompoundDrawableTintList());
   5035         assertEquals(BlendMode.XOR, mTextView.getCompoundDrawableTintBlendMode());
   5036 
   5037         ColorDrawable dr1 = new ColorDrawable(Color.RED);
   5038         ColorDrawable dr2 = new ColorDrawable(Color.GREEN);
   5039         ColorDrawable dr3 = new ColorDrawable(Color.BLUE);
   5040         ColorDrawable dr4 = new ColorDrawable(Color.YELLOW);
   5041         mTextView.setCompoundDrawables(dr1, dr2, dr3, dr4);
   5042         assertSame(colors, mTextView.getCompoundDrawableTintList());
   5043         assertEquals(BlendMode.XOR, mTextView.getCompoundDrawableTintBlendMode());
   5044     }
   5045 
   5046     @Test
   5047     public void testSetHorizontallyScrolling() throws Throwable {
   5048         // make the text view has more than one line
   5049         mTextView = findTextView(R.id.textview_text);
   5050         setWidth(mTextView.getWidth() >> 1);
   5051         assertTrue(mTextView.getLineCount() > 1);
   5052 
   5053         setHorizontallyScrolling(true);
   5054         assertEquals(1, mTextView.getLineCount());
   5055 
   5056         setHorizontallyScrolling(false);
   5057         assertTrue(mTextView.getLineCount() > 1);
   5058     }
   5059 
   5060     @Test
   5061     public void testComputeHorizontalScrollRange() throws Throwable {
   5062         mActivityRule.runOnUiThread(() -> mTextView = new MockTextView(mActivity));
   5063         mInstrumentation.waitForIdleSync();
   5064         // test when layout is null
   5065         assertNull(mTextView.getLayout());
   5066         assertEquals(mTextView.getWidth(),
   5067                 ((MockTextView) mTextView).computeHorizontalScrollRange());
   5068 
   5069         mActivityRule.runOnUiThread(() -> ((MockTextView) mTextView).setFrame(0, 0, 40, 50));
   5070         mInstrumentation.waitForIdleSync();
   5071         assertEquals(mTextView.getWidth(),
   5072                 ((MockTextView) mTextView).computeHorizontalScrollRange());
   5073 
   5074         // set the layout
   5075         layout(mTextView);
   5076         assertEquals(mTextView.getLayout().getWidth(),
   5077                 ((MockTextView) mTextView).computeHorizontalScrollRange());
   5078     }
   5079 
   5080     @Test
   5081     public void testComputeVerticalScrollRange() throws Throwable {
   5082         mActivityRule.runOnUiThread(() -> mTextView = new MockTextView(mActivity));
   5083         mInstrumentation.waitForIdleSync();
   5084 
   5085         // test when layout is null
   5086         assertNull(mTextView.getLayout());
   5087         assertEquals(0, ((MockTextView) mTextView).computeVerticalScrollRange());
   5088 
   5089         mActivityRule.runOnUiThread(() -> ((MockTextView) mTextView).setFrame(0, 0, 40, 50));
   5090         mInstrumentation.waitForIdleSync();
   5091         assertEquals(mTextView.getHeight(), ((MockTextView) mTextView).computeVerticalScrollRange());
   5092 
   5093         //set the layout
   5094         layout(mTextView);
   5095         assertEquals(mTextView.getLayout().getHeight(),
   5096                 ((MockTextView) mTextView).computeVerticalScrollRange());
   5097     }
   5098 
   5099     @Test
   5100     public void testDrawableStateChanged() throws Throwable {
   5101         mActivityRule.runOnUiThread(() -> mTextView = spy(new MockTextView(mActivity)));
   5102         mInstrumentation.waitForIdleSync();
   5103         reset(mTextView);
   5104         mTextView.refreshDrawableState();
   5105         ((MockTextView) verify(mTextView, times(1))).drawableStateChanged();
   5106     }
   5107 
   5108     @UiThreadTest
   5109     @Test
   5110     public void testGetDefaultEditable() {
   5111         mTextView = new MockTextView(mActivity);
   5112 
   5113         //the TextView#getDefaultEditable() does nothing, and always return false.
   5114         assertFalse(((MockTextView) mTextView).getDefaultEditable());
   5115     }
   5116 
   5117     @UiThreadTest
   5118     @Test
   5119     public void testGetDefaultMovementMethod() {
   5120         MockTextView textView = new MockTextView(mActivity);
   5121 
   5122         //the TextView#getDefaultMovementMethod() does nothing, and always return null.
   5123         assertNull(textView.getDefaultMovementMethod());
   5124     }
   5125 
   5126     @UiThreadTest
   5127     @Test
   5128     public void testSetFrame() {
   5129         MockTextView textView = new MockTextView(mActivity);
   5130 
   5131         //Assign a new size to this view
   5132         assertTrue(textView.setFrame(0, 0, 320, 480));
   5133         assertEquals(0, textView.getLeft());
   5134         assertEquals(0, textView.getTop());
   5135         assertEquals(320, textView.getRight());
   5136         assertEquals(480, textView.getBottom());
   5137 
   5138         //Assign a same size to this view
   5139         assertFalse(textView.setFrame(0, 0, 320, 480));
   5140 
   5141         //negative input
   5142         assertTrue(textView.setFrame(-1, -1, -1, -1));
   5143         assertEquals(-1, textView.getLeft());
   5144         assertEquals(-1, textView.getTop());
   5145         assertEquals(-1, textView.getRight());
   5146         assertEquals(-1, textView.getBottom());
   5147     }
   5148 
   5149     @Test
   5150     public void testMarquee() throws Throwable {
   5151         // Both are pointing to the same object. This works around current limitation in CTS
   5152         // coverage report tool for properly reporting coverage of base class method calls.
   5153         mActivityRule.runOnUiThread(() -> {
   5154             mSecondTextView = new MockTextView(mActivity);
   5155 
   5156             mTextView = mSecondTextView;
   5157             mTextView.setText(LONG_TEXT);
   5158             mTextView.setSingleLine();
   5159             mTextView.setEllipsize(TruncateAt.MARQUEE);
   5160             mTextView.setLayoutParams(new LayoutParams(100, 100));
   5161         });
   5162         mInstrumentation.waitForIdleSync();
   5163 
   5164         final FrameLayout layout = new FrameLayout(mActivity);
   5165         layout.addView(mTextView);
   5166 
   5167         // make the fading to be shown
   5168         mTextView.setHorizontalFadingEdgeEnabled(true);
   5169 
   5170         mActivityRule.runOnUiThread(() -> mActivity.setContentView(layout));
   5171         mInstrumentation.waitForIdleSync();
   5172 
   5173         TestSelectedRunnable runnable = new TestSelectedRunnable(mTextView) {
   5174             public void run() {
   5175                 mTextView.setMarqueeRepeatLimit(-1);
   5176                 // force the marquee to start
   5177                 saveIsSelected1();
   5178                 mTextView.setSelected(true);
   5179                 saveIsSelected2();
   5180             }
   5181         };
   5182         mActivityRule.runOnUiThread(runnable);
   5183 
   5184         // wait for the marquee to run
   5185         // fading is shown on both sides if the marquee runs for a while
   5186         PollingCheck.waitFor(TIMEOUT, () ->
   5187                 ((MockTextView) mSecondTextView).getLeftFadingEdgeStrength() > 0.0f
   5188                         && ((MockTextView) mSecondTextView).getRightFadingEdgeStrength() > 0.0f);
   5189 
   5190         // wait for left marquee to fully apply
   5191         PollingCheck.waitFor(TIMEOUT, () ->
   5192                 ((MockTextView) mSecondTextView).getLeftFadingEdgeStrength() > 0.99f);
   5193 
   5194         assertFalse(runnable.getIsSelected1());
   5195         assertTrue(runnable.getIsSelected2());
   5196         assertEquals(-1, mTextView.getMarqueeRepeatLimit());
   5197 
   5198         runnable = new TestSelectedRunnable(mTextView) {
   5199             public void run() {
   5200                 mTextView.setMarqueeRepeatLimit(0);
   5201                 // force the marquee to stop
   5202                 saveIsSelected1();
   5203                 mTextView.setSelected(false);
   5204                 saveIsSelected2();
   5205                 mTextView.setGravity(Gravity.LEFT);
   5206             }
   5207         };
   5208         // force the marquee to stop
   5209         mActivityRule.runOnUiThread(runnable);
   5210         mInstrumentation.waitForIdleSync();
   5211         assertTrue(runnable.getIsSelected1());
   5212         assertFalse(runnable.getIsSelected2());
   5213         assertEquals(0.0f, ((MockTextView) mSecondTextView).getLeftFadingEdgeStrength(), 0.01f);
   5214         assertTrue(((MockTextView) mSecondTextView).getRightFadingEdgeStrength() > 0.0f);
   5215         assertEquals(0, mTextView.getMarqueeRepeatLimit());
   5216 
   5217         mActivityRule.runOnUiThread(() -> mTextView.setGravity(Gravity.RIGHT));
   5218         mInstrumentation.waitForIdleSync();
   5219         assertTrue(((MockTextView) mSecondTextView).getLeftFadingEdgeStrength() > 0.0f);
   5220         assertEquals(0.0f, ((MockTextView) mSecondTextView).getRightFadingEdgeStrength(), 0.01f);
   5221 
   5222         mActivityRule.runOnUiThread(() -> mTextView.setGravity(Gravity.CENTER_HORIZONTAL));
   5223         mInstrumentation.waitForIdleSync();
   5224         // there is no left fading (Is it correct?)
   5225         assertEquals(0.0f, ((MockTextView) mSecondTextView).getLeftFadingEdgeStrength(), 0.01f);
   5226         assertTrue(((MockTextView) mSecondTextView).getRightFadingEdgeStrength() > 0.0f);
   5227     }
   5228 
   5229     @UiThreadTest
   5230     @Test
   5231     public void testGetMarqueeRepeatLimit() {
   5232         final TextView textView = new TextView(mActivity);
   5233 
   5234         textView.setMarqueeRepeatLimit(10);
   5235         assertEquals(10, textView.getMarqueeRepeatLimit());
   5236     }
   5237 
   5238     @UiThreadTest
   5239     @Test
   5240     public void testAccessInputExtras() throws XmlPullParserException, IOException {
   5241         mTextView = new TextView(mActivity);
   5242         mTextView.setText(null, BufferType.EDITABLE);
   5243         mTextView.setInputType(InputType.TYPE_CLASS_TEXT);
   5244 
   5245         // do not create the extras
   5246         assertNull(mTextView.getInputExtras(false));
   5247 
   5248         // create if it does not exist
   5249         Bundle inputExtras = mTextView.getInputExtras(true);
   5250         assertNotNull(inputExtras);
   5251         assertTrue(inputExtras.isEmpty());
   5252 
   5253         // it is created already
   5254         assertNotNull(mTextView.getInputExtras(false));
   5255 
   5256         try {
   5257             mTextView.setInputExtras(R.xml.input_extras);
   5258             fail("Should throw NullPointerException!");
   5259         } catch (NullPointerException e) {
   5260         }
   5261     }
   5262 
   5263     @UiThreadTest
   5264     @Test
   5265     public void testAccessContentType() {
   5266         mTextView = new TextView(mActivity);
   5267         mTextView.setText(null, BufferType.EDITABLE);
   5268         mTextView.setKeyListener(null);
   5269         mTextView.setTransformationMethod(null);
   5270 
   5271         mTextView.setInputType(InputType.TYPE_CLASS_DATETIME
   5272                 | InputType.TYPE_DATETIME_VARIATION_NORMAL);
   5273         assertEquals(InputType.TYPE_CLASS_DATETIME
   5274                 | InputType.TYPE_DATETIME_VARIATION_NORMAL, mTextView.getInputType());
   5275         assertTrue(mTextView.getKeyListener() instanceof DateTimeKeyListener);
   5276 
   5277         mTextView.setInputType(InputType.TYPE_CLASS_DATETIME
   5278                 | InputType.TYPE_DATETIME_VARIATION_DATE);
   5279         assertEquals(InputType.TYPE_CLASS_DATETIME
   5280                 | InputType.TYPE_DATETIME_VARIATION_DATE, mTextView.getInputType());
   5281         assertTrue(mTextView.getKeyListener() instanceof DateKeyListener);
   5282 
   5283         mTextView.setInputType(InputType.TYPE_CLASS_DATETIME
   5284                 | InputType.TYPE_DATETIME_VARIATION_TIME);
   5285         assertEquals(InputType.TYPE_CLASS_DATETIME
   5286                 | InputType.TYPE_DATETIME_VARIATION_TIME, mTextView.getInputType());
   5287         assertTrue(mTextView.getKeyListener() instanceof TimeKeyListener);
   5288 
   5289         mTextView.setInputType(InputType.TYPE_CLASS_NUMBER
   5290                 | InputType.TYPE_NUMBER_FLAG_DECIMAL
   5291                 | InputType.TYPE_NUMBER_FLAG_SIGNED);
   5292         assertEquals(InputType.TYPE_CLASS_NUMBER
   5293                 | InputType.TYPE_NUMBER_FLAG_DECIMAL
   5294                 | InputType.TYPE_NUMBER_FLAG_SIGNED, mTextView.getInputType());
   5295         assertSame(mTextView.getKeyListener(),
   5296                 DigitsKeyListener.getInstance(null, true, true));
   5297 
   5298         mTextView.setInputType(InputType.TYPE_CLASS_PHONE);
   5299         assertEquals(InputType.TYPE_CLASS_PHONE, mTextView.getInputType());
   5300         assertTrue(mTextView.getKeyListener() instanceof DialerKeyListener);
   5301 
   5302         mTextView.setInputType(InputType.TYPE_CLASS_TEXT
   5303                 | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
   5304         assertEquals(InputType.TYPE_CLASS_TEXT
   5305                 | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT, mTextView.getInputType());
   5306         assertSame(mTextView.getKeyListener(), TextKeyListener.getInstance(true, Capitalize.NONE));
   5307 
   5308         mTextView.setSingleLine();
   5309         assertTrue(mTextView.getTransformationMethod() instanceof SingleLineTransformationMethod);
   5310         mTextView.setInputType(InputType.TYPE_CLASS_TEXT
   5311                 | InputType.TYPE_TEXT_FLAG_MULTI_LINE
   5312                 | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
   5313         assertEquals(InputType.TYPE_CLASS_TEXT
   5314                 | InputType.TYPE_TEXT_FLAG_MULTI_LINE
   5315                 | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS, mTextView.getInputType());
   5316         assertSame(mTextView.getKeyListener(),
   5317                 TextKeyListener.getInstance(false, Capitalize.CHARACTERS));
   5318         assertNull(mTextView.getTransformationMethod());
   5319 
   5320         mTextView.setInputType(InputType.TYPE_CLASS_TEXT
   5321                 | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
   5322         assertEquals(InputType.TYPE_CLASS_TEXT
   5323                 | InputType.TYPE_TEXT_FLAG_CAP_WORDS, mTextView.getInputType());
   5324         assertSame(mTextView.getKeyListener(),
   5325                 TextKeyListener.getInstance(false, Capitalize.WORDS));
   5326         assertTrue(mTextView.getTransformationMethod() instanceof SingleLineTransformationMethod);
   5327 
   5328         mTextView.setInputType(InputType.TYPE_CLASS_TEXT
   5329                 | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
   5330         assertEquals(InputType.TYPE_CLASS_TEXT
   5331                 | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES, mTextView.getInputType());
   5332         assertSame(mTextView.getKeyListener(),
   5333                 TextKeyListener.getInstance(false, Capitalize.SENTENCES));
   5334 
   5335         mTextView.setInputType(InputType.TYPE_NULL);
   5336         assertEquals(InputType.TYPE_NULL, mTextView.getInputType());
   5337         assertTrue(mTextView.getKeyListener() instanceof TextKeyListener);
   5338     }
   5339 
   5340     @UiThreadTest
   5341     @Test
   5342     public void testAccessRawContentType() {
   5343         mTextView = new TextView(mActivity);
   5344         mTextView.setText(null, BufferType.EDITABLE);
   5345         mTextView.setKeyListener(null);
   5346         mTextView.setTransformationMethod(null);
   5347 
   5348         mTextView.setRawInputType(InputType.TYPE_CLASS_DATETIME
   5349                 | InputType.TYPE_DATETIME_VARIATION_NORMAL);
   5350         assertEquals(InputType.TYPE_CLASS_DATETIME
   5351                 | InputType.TYPE_DATETIME_VARIATION_NORMAL, mTextView.getInputType());
   5352         assertNull(mTextView.getTransformationMethod());
   5353         assertNull(mTextView.getKeyListener());
   5354 
   5355         mTextView.setRawInputType(InputType.TYPE_CLASS_DATETIME
   5356                 | InputType.TYPE_DATETIME_VARIATION_DATE);
   5357         assertEquals(InputType.TYPE_CLASS_DATETIME
   5358                 | InputType.TYPE_DATETIME_VARIATION_DATE, mTextView.getInputType());
   5359         assertNull(mTextView.getTransformationMethod());
   5360         assertNull(mTextView.getKeyListener());
   5361 
   5362         mTextView.setRawInputType(InputType.TYPE_CLASS_DATETIME
   5363                 | InputType.TYPE_DATETIME_VARIATION_TIME);
   5364         assertEquals(InputType.TYPE_CLASS_DATETIME
   5365                 | InputType.TYPE_DATETIME_VARIATION_TIME, mTextView.getInputType());
   5366         assertNull(mTextView.getTransformationMethod());
   5367         assertNull(mTextView.getKeyListener());
   5368 
   5369         mTextView.setRawInputType(InputType.TYPE_CLASS_NUMBER
   5370                 | InputType.TYPE_NUMBER_FLAG_DECIMAL
   5371                 | InputType.TYPE_NUMBER_FLAG_SIGNED);
   5372         assertEquals(InputType.TYPE_CLASS_NUMBER
   5373                 | InputType.TYPE_NUMBER_FLAG_DECIMAL
   5374                 | InputType.TYPE_NUMBER_FLAG_SIGNED, mTextView.getInputType());
   5375         assertNull(mTextView.getTransformationMethod());
   5376         assertNull(mTextView.getKeyListener());
   5377 
   5378         mTextView.setRawInputType(InputType.TYPE_CLASS_PHONE);
   5379         assertEquals(InputType.TYPE_CLASS_PHONE, mTextView.getInputType());
   5380         assertNull(mTextView.getTransformationMethod());
   5381         assertNull(mTextView.getKeyListener());
   5382 
   5383         mTextView.setRawInputType(InputType.TYPE_CLASS_TEXT
   5384                 | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT);
   5385         assertEquals(InputType.TYPE_CLASS_TEXT
   5386                 | InputType.TYPE_TEXT_FLAG_AUTO_CORRECT, mTextView.getInputType());
   5387         assertNull(mTextView.getTransformationMethod());
   5388         assertNull(mTextView.getKeyListener());
   5389 
   5390         mTextView.setSingleLine();
   5391         assertTrue(mTextView.getTransformationMethod() instanceof SingleLineTransformationMethod);
   5392         mTextView.setRawInputType(InputType.TYPE_CLASS_TEXT
   5393                 | InputType.TYPE_TEXT_FLAG_MULTI_LINE
   5394                 | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS);
   5395         assertEquals(InputType.TYPE_CLASS_TEXT
   5396                 | InputType.TYPE_TEXT_FLAG_MULTI_LINE
   5397                 | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS, mTextView.getInputType());
   5398         assertTrue(mTextView.getTransformationMethod() instanceof SingleLineTransformationMethod);
   5399         assertNull(mTextView.getKeyListener());
   5400 
   5401         mTextView.setRawInputType(InputType.TYPE_CLASS_TEXT
   5402                 | InputType.TYPE_TEXT_FLAG_CAP_WORDS);
   5403         assertEquals(InputType.TYPE_CLASS_TEXT
   5404                 | InputType.TYPE_TEXT_FLAG_CAP_WORDS, mTextView.getInputType());
   5405         assertTrue(mTextView.getTransformationMethod() instanceof SingleLineTransformationMethod);
   5406         assertNull(mTextView.getKeyListener());
   5407 
   5408         mTextView.setRawInputType(InputType.TYPE_CLASS_TEXT
   5409                 | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES);
   5410         assertEquals(InputType.TYPE_CLASS_TEXT
   5411                 | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES, mTextView.getInputType());
   5412         assertTrue(mTextView.getTransformationMethod() instanceof SingleLineTransformationMethod);
   5413         assertNull(mTextView.getKeyListener());
   5414 
   5415         mTextView.setRawInputType(InputType.TYPE_NULL);
   5416         assertTrue(mTextView.getTransformationMethod() instanceof SingleLineTransformationMethod);
   5417         assertNull(mTextView.getKeyListener());
   5418     }
   5419 
   5420     @UiThreadTest
   5421     @Test
   5422     public void testVerifyDrawable() {
   5423         mTextView = new MockTextView(mActivity);
   5424 
   5425         Drawable d = TestUtils.getDrawable(mActivity, R.drawable.pass);
   5426         assertFalse(((MockTextView ) mTextView).verifyDrawable(d));
   5427 
   5428         mTextView.setCompoundDrawables(null, d, null, null);
   5429         assertTrue(((MockTextView ) mTextView).verifyDrawable(d));
   5430     }
   5431 
   5432     @Test
   5433     public void testAccessPrivateImeOptions() {
   5434         mTextView = findTextView(R.id.textview_text);
   5435         assertNull(mTextView.getPrivateImeOptions());
   5436 
   5437         mTextView.setPrivateImeOptions("com.example.myapp.SpecialMode=3");
   5438         assertEquals("com.example.myapp.SpecialMode=3", mTextView.getPrivateImeOptions());
   5439 
   5440         mTextView.setPrivateImeOptions(null);
   5441         assertNull(mTextView.getPrivateImeOptions());
   5442     }
   5443 
   5444     @Test
   5445     public void testSetOnEditorActionListener() {
   5446         mTextView = findTextView(R.id.textview_text);
   5447 
   5448         final TextView.OnEditorActionListener mockOnEditorActionListener =
   5449                 mock(TextView.OnEditorActionListener.class);
   5450         verifyZeroInteractions(mockOnEditorActionListener);
   5451 
   5452         mTextView.setOnEditorActionListener(mockOnEditorActionListener);
   5453         verifyZeroInteractions(mockOnEditorActionListener);
   5454 
   5455         mTextView.onEditorAction(EditorInfo.IME_ACTION_DONE);
   5456         verify(mockOnEditorActionListener, times(1)).onEditorAction(mTextView,
   5457                 EditorInfo.IME_ACTION_DONE, null);
   5458     }
   5459 
   5460     @Test
   5461     public void testAccessImeOptions() {
   5462         mTextView = findTextView(R.id.textview_text);
   5463         assertEquals(EditorInfo.IME_NULL, mTextView.getImeOptions());
   5464 
   5465         mTextView.setImeOptions(EditorInfo.IME_ACTION_GO);
   5466         assertEquals(EditorInfo.IME_ACTION_GO, mTextView.getImeOptions());
   5467 
   5468         mTextView.setImeOptions(EditorInfo.IME_ACTION_DONE);
   5469         assertEquals(EditorInfo.IME_ACTION_DONE, mTextView.getImeOptions());
   5470 
   5471         mTextView.setImeOptions(EditorInfo.IME_NULL);
   5472         assertEquals(EditorInfo.IME_NULL, mTextView.getImeOptions());
   5473     }
   5474 
   5475     @Test
   5476     public void testAccessImeActionLabel() {
   5477         mTextView = findTextView(R.id.textview_text);
   5478         assertNull(mTextView.getImeActionLabel());
   5479         assertEquals(0, mTextView.getImeActionId());
   5480 
   5481         mTextView.setImeActionLabel("pinyin", 1);
   5482         assertEquals("pinyin", mTextView.getImeActionLabel().toString());
   5483         assertEquals(1, mTextView.getImeActionId());
   5484     }
   5485 
   5486     @UiThreadTest
   5487     @Test
   5488     public void testAccessImeHintLocales() {
   5489         final TextView textView = new TextView(mActivity);
   5490         textView.setText("", BufferType.EDITABLE);
   5491         textView.setKeyListener(null);
   5492         textView.setRawInputType(InputType.TYPE_CLASS_TEXT);
   5493         assertNull(textView.getImeHintLocales());
   5494         {
   5495             final EditorInfo editorInfo = new EditorInfo();
   5496             textView.onCreateInputConnection(editorInfo);
   5497             assertNull(editorInfo.hintLocales);
   5498         }
   5499 
   5500         final LocaleList localeList = LocaleList.forLanguageTags("en-PH,en-US");
   5501         textView.setImeHintLocales(localeList);
   5502         assertEquals(localeList, textView.getImeHintLocales());
   5503         {
   5504             final EditorInfo editorInfo = new EditorInfo();
   5505             textView.onCreateInputConnection(editorInfo);
   5506             assertEquals(localeList, editorInfo.hintLocales);
   5507         }
   5508     }
   5509 
   5510     @UiThreadTest
   5511     @Test
   5512     public void testSetImeHintLocalesChangesInputType() {
   5513         final TextView textView = new TextView(mActivity);
   5514         textView.setText("", BufferType.EDITABLE);
   5515 
   5516         textView.setInputType(InputType.TYPE_CLASS_NUMBER);
   5517         assertEquals(InputType.TYPE_CLASS_NUMBER, textView.getInputType());
   5518 
   5519         final LocaleList localeList = LocaleList.forLanguageTags("fa-IR");
   5520         textView.setImeHintLocales(localeList);
   5521         final int textType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL;
   5522         // Setting IME hint locales to Persian must change the input type to a full text IME,
   5523         // since the typical number input IME may not have localized digits.
   5524         assertEquals(textType, textView.getInputType());
   5525 
   5526         // Changing the input type to datetime should keep the full text IME, since the IME hint
   5527         // is still set to Persian, which needs advanced input.
   5528         final int dateType = InputType.TYPE_CLASS_DATETIME | InputType.TYPE_DATETIME_VARIATION_DATE;
   5529         textView.setInputType(dateType);
   5530         assertEquals(textType, textView.getInputType());
   5531 
   5532         // Changing the input type to number password should keep the full text IME, since the IME
   5533         // hint is still set to Persian, which needs advanced input. But it also needs to set the
   5534         // text password flag.
   5535         final int numberPasswordType = InputType.TYPE_CLASS_NUMBER
   5536                 | InputType.TYPE_NUMBER_VARIATION_PASSWORD;
   5537         final int textPasswordType = InputType.TYPE_CLASS_TEXT
   5538                 | InputType.TYPE_TEXT_VARIATION_PASSWORD;
   5539         textView.setInputType(numberPasswordType);
   5540         assertEquals(textPasswordType, textView.getInputType());
   5541 
   5542         // Setting the IME hint locales to null should reset the type to number password, since we
   5543         // no longer need internationalized input.
   5544         textView.setImeHintLocales(null);
   5545         assertEquals(numberPasswordType, textView.getInputType());
   5546     }
   5547 
   5548     @UiThreadTest
   5549     @Test
   5550     public void testSetImeHintLocalesDoesntLoseInputType() {
   5551         final TextView textView = new TextView(mActivity);
   5552         textView.setText("", BufferType.EDITABLE);
   5553         final int inputType = InputType.TYPE_CLASS_TEXT
   5554                 | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT
   5555                 | InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
   5556                 | InputType.TYPE_TEXT_FLAG_MULTI_LINE
   5557                 | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
   5558         textView.setInputType(inputType);
   5559         textView.setImeHintLocales(new LocaleList(Locale.US));
   5560         assertEquals(inputType, textView.getInputType());
   5561     }
   5562 
   5563     @UiThreadTest
   5564     @Test
   5565     public void testSetExtractedText() {
   5566         mTextView = findTextView(R.id.textview_text);
   5567         assertEquals(mActivity.getResources().getString(R.string.text_view_hello),
   5568                 mTextView.getText().toString());
   5569 
   5570         ExtractedText et = new ExtractedText();
   5571 
   5572         // Update text and selection.
   5573         et.text = "test";
   5574         et.selectionStart = 0;
   5575         et.selectionEnd = 2;
   5576 
   5577         mTextView.setExtractedText(et);
   5578         assertEquals("test", mTextView.getText().toString());
   5579         assertEquals(0, mTextView.getSelectionStart());
   5580         assertEquals(2, mTextView.getSelectionEnd());
   5581 
   5582         // Use partialStartOffset and partialEndOffset
   5583         et.partialStartOffset = 2;
   5584         et.partialEndOffset = 3;
   5585         et.text = "x";
   5586         et.selectionStart = 2;
   5587         et.selectionEnd = 3;
   5588 
   5589         mTextView.setExtractedText(et);
   5590         assertEquals("text", mTextView.getText().toString());
   5591         assertEquals(2, mTextView.getSelectionStart());
   5592         assertEquals(3, mTextView.getSelectionEnd());
   5593 
   5594         // Update text with spans.
   5595         final SpannableString ss = new SpannableString("ex");
   5596         ss.setSpan(new UnderlineSpan(), 0, 2, 0);
   5597         ss.setSpan(new URLSpan("ctstest://TextView/test"), 1, 2, 0);
   5598 
   5599         et.text = ss;
   5600         et.partialStartOffset = 1;
   5601         et.partialEndOffset = 3;
   5602         mTextView.setExtractedText(et);
   5603 
   5604         assertEquals("text", mTextView.getText().toString());
   5605         final Editable editable = mTextView.getEditableText();
   5606         final UnderlineSpan[] underlineSpans = mTextView.getEditableText().getSpans(
   5607                 0, editable.length(), UnderlineSpan.class);
   5608         assertEquals(1, underlineSpans.length);
   5609         assertEquals(1, editable.getSpanStart(underlineSpans[0]));
   5610         assertEquals(3, editable.getSpanEnd(underlineSpans[0]));
   5611 
   5612         final URLSpan[] urlSpans = mTextView.getEditableText().getSpans(
   5613                 0, editable.length(), URLSpan.class);
   5614         assertEquals(1, urlSpans.length);
   5615         assertEquals(2, editable.getSpanStart(urlSpans[0]));
   5616         assertEquals(3, editable.getSpanEnd(urlSpans[0]));
   5617         assertEquals("ctstest://TextView/test", urlSpans[0].getURL());
   5618     }
   5619 
   5620     @Test
   5621     public void testMoveCursorToVisibleOffset() throws Throwable {
   5622         mTextView = findTextView(R.id.textview_text);
   5623 
   5624         // not a spannable text
   5625         mActivityRule.runOnUiThread(() -> assertFalse(mTextView.moveCursorToVisibleOffset()));
   5626         mInstrumentation.waitForIdleSync();
   5627 
   5628         // a selection range
   5629         final String spannableText = "text";
   5630         mActivityRule.runOnUiThread(() ->  mTextView = new TextView(mActivity));
   5631         mInstrumentation.waitForIdleSync();
   5632 
   5633         mActivityRule.runOnUiThread(
   5634                 () -> mTextView.setText(spannableText, BufferType.SPANNABLE));
   5635         mInstrumentation.waitForIdleSync();
   5636         Selection.setSelection((Spannable) mTextView.getText(), 0, spannableText.length());
   5637 
   5638         assertEquals(0, mTextView.getSelectionStart());
   5639         assertEquals(spannableText.length(), mTextView.getSelectionEnd());
   5640         mActivityRule.runOnUiThread(() -> assertFalse(mTextView.moveCursorToVisibleOffset()));
   5641         mInstrumentation.waitForIdleSync();
   5642 
   5643         // a spannable without range
   5644         mActivityRule.runOnUiThread(() -> {
   5645             mTextView = findTextView(R.id.textview_text);
   5646             mTextView.setText(spannableText, BufferType.SPANNABLE);
   5647         });
   5648         mInstrumentation.waitForIdleSync();
   5649 
   5650         mActivityRule.runOnUiThread(() -> assertTrue(mTextView.moveCursorToVisibleOffset()));
   5651         mInstrumentation.waitForIdleSync();
   5652     }
   5653 
   5654     @Test
   5655     public void testIsInputMethodTarget() throws Throwable {
   5656         mTextView = findTextView(R.id.textview_text);
   5657         assertFalse(mTextView.isInputMethodTarget());
   5658 
   5659         assertFalse(mTextView.isFocused());
   5660         mActivityRule.runOnUiThread(() -> {
   5661             mTextView.setFocusable(true);
   5662             mTextView.requestFocus();
   5663          });
   5664         mInstrumentation.waitForIdleSync();
   5665         assertTrue(mTextView.isFocused());
   5666 
   5667         PollingCheck.waitFor(mTextView::isInputMethodTarget);
   5668     }
   5669 
   5670     @UiThreadTest
   5671     @Test
   5672     public void testBeginEndBatchEditAreNotCalledForNonEditableText() {
   5673         final TextView mockTextView = spy(new TextView(mActivity));
   5674 
   5675         // TextView should not call onBeginBatchEdit or onEndBatchEdit during initialization
   5676         verify(mockTextView, never()).onBeginBatchEdit();
   5677         verify(mockTextView, never()).onEndBatchEdit();
   5678 
   5679 
   5680         mockTextView.beginBatchEdit();
   5681         // Since TextView doesn't support editing, the callbacks should not be called
   5682         verify(mockTextView, never()).onBeginBatchEdit();
   5683         verify(mockTextView, never()).onEndBatchEdit();
   5684 
   5685         mockTextView.endBatchEdit();
   5686         // Since TextView doesn't support editing, the callbacks should not be called
   5687         verify(mockTextView, never()).onBeginBatchEdit();
   5688         verify(mockTextView, never()).onEndBatchEdit();
   5689     }
   5690 
   5691     @Test
   5692     public void testBeginEndBatchEditCallbacksAreCalledForEditableText() throws Throwable {
   5693         mActivityRule.runOnUiThread(() -> mTextView = spy(new TextView(mActivity)));
   5694         mInstrumentation.waitForIdleSync();
   5695 
   5696         final FrameLayout layout = new FrameLayout(mActivity);
   5697         ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
   5698                 ViewGroup.LayoutParams.MATCH_PARENT,
   5699                 ViewGroup.LayoutParams.MATCH_PARENT);
   5700         layout.addView(mTextView, layoutParams);
   5701         layout.setLayoutParams(layoutParams);
   5702 
   5703         mActivityRule.runOnUiThread(() -> mActivity.setContentView(layout));
   5704         mInstrumentation.waitForIdleSync();
   5705 
   5706         mActivityRule.runOnUiThread(() -> {
   5707             mTextView.setKeyListener(QwertyKeyListener.getInstance(false, Capitalize.NONE));
   5708             mTextView.setText("", BufferType.EDITABLE);
   5709             mTextView.requestFocus();
   5710         });
   5711         mInstrumentation.waitForIdleSync();
   5712 
   5713         reset(mTextView);
   5714         assertTrue(mTextView.hasFocus());
   5715         verify(mTextView, never()).onBeginBatchEdit();
   5716         verify(mTextView, never()).onEndBatchEdit();
   5717 
   5718         mTextView.beginBatchEdit();
   5719 
   5720         verify(mTextView, times(1)).onBeginBatchEdit();
   5721         verify(mTextView, never()).onEndBatchEdit();
   5722 
   5723         reset(mTextView);
   5724         mTextView.endBatchEdit();
   5725         verify(mTextView, never()).onBeginBatchEdit();
   5726         verify(mTextView, times(1)).onEndBatchEdit();
   5727     }
   5728 
   5729     @UiThreadTest
   5730     @Test
   5731     public void testBringPointIntoView() throws Throwable {
   5732         mTextView = findTextView(R.id.textview_text);
   5733         assertFalse(mTextView.bringPointIntoView(1));
   5734 
   5735         mTextView.layout(0, 0, 100, 100);
   5736         assertFalse(mTextView.bringPointIntoView(2));
   5737     }
   5738 
   5739     @Test
   5740     public void testCancelLongPress() {
   5741         mTextView = findTextView(R.id.textview_text);
   5742         CtsTouchUtils.emulateLongPressOnViewCenter(mInstrumentation, mActivityRule, mTextView);
   5743         mTextView.cancelLongPress();
   5744     }
   5745 
   5746     @UiThreadTest
   5747     @Test
   5748     public void testClearComposingText() {
   5749         mTextView = findTextView(R.id.textview_text);
   5750         mTextView.setText("Hello world!", BufferType.SPANNABLE);
   5751         Spannable text = (Spannable) mTextView.getText();
   5752 
   5753         assertEquals(-1, BaseInputConnection.getComposingSpanStart(text));
   5754         assertEquals(-1, BaseInputConnection.getComposingSpanStart(text));
   5755 
   5756         BaseInputConnection.setComposingSpans((Spannable) mTextView.getText());
   5757         assertEquals(0, BaseInputConnection.getComposingSpanStart(text));
   5758         assertEquals(0, BaseInputConnection.getComposingSpanStart(text));
   5759 
   5760         mTextView.clearComposingText();
   5761         assertEquals(-1, BaseInputConnection.getComposingSpanStart(text));
   5762         assertEquals(-1, BaseInputConnection.getComposingSpanStart(text));
   5763     }
   5764 
   5765     @UiThreadTest
   5766     @Test
   5767     public void testComputeVerticalScrollExtent() {
   5768         mTextView = new MockTextView(mActivity);
   5769         assertEquals(0, ((MockTextView) mTextView).computeVerticalScrollExtent());
   5770 
   5771         Drawable d = TestUtils.getDrawable(mActivity, R.drawable.pass);
   5772         mTextView.setCompoundDrawables(null, d, null, d);
   5773 
   5774         assertEquals(0, ((MockTextView) mTextView).computeVerticalScrollExtent());
   5775     }
   5776 
   5777     @UiThreadTest
   5778     @Test
   5779     public void testDidTouchFocusSelect() {
   5780         mTextView = new EditText(mActivity);
   5781         assertFalse(mTextView.didTouchFocusSelect());
   5782 
   5783         mTextView.setFocusable(true);
   5784         mTextView.requestFocus();
   5785         assertTrue(mTextView.didTouchFocusSelect());
   5786     }
   5787 
   5788     @Test
   5789     public void testSelectAllJustAfterTap() throws Throwable {
   5790         // Prepare an EditText with focus.
   5791         mActivityRule.runOnUiThread(() -> {
   5792             // Make a dummy focusable so that initial focus doesn't go to our test textview
   5793             LinearLayout top = new LinearLayout(mActivity);
   5794             TextView dummy = new TextView(mActivity);
   5795             dummy.setFocusableInTouchMode(true);
   5796             top.addView(dummy, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
   5797             mTextView = new EditText(mActivity);
   5798             top.addView(mTextView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
   5799             mActivity.setContentView(top);
   5800 
   5801             assertFalse(mTextView.didTouchFocusSelect());
   5802             mTextView.setFocusable(true);
   5803             mTextView.requestFocus();
   5804             assertTrue(mTextView.didTouchFocusSelect());
   5805 
   5806             mTextView.setText("Hello, World.", BufferType.SPANNABLE);
   5807         });
   5808         mInstrumentation.waitForIdleSync();
   5809 
   5810         // Tap the view to show InsertPointController.
   5811         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mTextView);
   5812         // bad workaround for waiting onStartInputView of LeanbackIme.apk done
   5813         try {
   5814             Thread.sleep(1000);
   5815         } catch (InterruptedException e) {
   5816             e.printStackTrace();
   5817         }
   5818 
   5819         // Execute SelectAll context menu.
   5820         mActivityRule.runOnUiThread(() -> mTextView.onTextContextMenuItem(android.R.id.selectAll));
   5821         mInstrumentation.waitForIdleSync();
   5822 
   5823         // The selection must be whole of the text contents.
   5824         assertEquals(0, mTextView.getSelectionStart());
   5825         assertEquals("Hello, World.", mTextView.getText().toString());
   5826         assertEquals(mTextView.length(), mTextView.getSelectionEnd());
   5827     }
   5828 
   5829     @UiThreadTest
   5830     @Test
   5831     public void testExtractText() {
   5832         mTextView = new TextView(mActivity);
   5833 
   5834         ExtractedTextRequest request = new ExtractedTextRequest();
   5835         ExtractedText outText = new ExtractedText();
   5836 
   5837         request.token = 0;
   5838         request.flags = 10;
   5839         request.hintMaxLines = 2;
   5840         request.hintMaxChars = 20;
   5841         assertTrue(mTextView.extractText(request, outText));
   5842 
   5843         mTextView = findTextView(R.id.textview_text);
   5844         assertTrue(mTextView.extractText(request, outText));
   5845 
   5846         assertEquals(mActivity.getResources().getString(R.string.text_view_hello),
   5847                 outText.text.toString());
   5848 
   5849         // Tests for invalid arguments.
   5850         assertFalse(mTextView.extractText(request, null));
   5851         assertFalse(mTextView.extractText(null, outText));
   5852         assertFalse(mTextView.extractText(null, null));
   5853     }
   5854 
   5855     @UiThreadTest
   5856     @Test
   5857     public void testTextDirectionDefault() {
   5858         TextView tv = new TextView(mActivity);
   5859         assertEquals(View.TEXT_DIRECTION_INHERIT, tv.getRawTextDirection());
   5860     }
   5861 
   5862     @UiThreadTest
   5863     @Test
   5864     public void testSetGetTextDirection() {
   5865         TextView tv = new TextView(mActivity);
   5866 
   5867         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
   5868         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getRawTextDirection());
   5869 
   5870         tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
   5871         assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getRawTextDirection());
   5872 
   5873         tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
   5874         assertEquals(View.TEXT_DIRECTION_INHERIT, tv.getRawTextDirection());
   5875 
   5876         tv.setTextDirection(View.TEXT_DIRECTION_LTR);
   5877         assertEquals(View.TEXT_DIRECTION_LTR, tv.getRawTextDirection());
   5878 
   5879         tv.setTextDirection(View.TEXT_DIRECTION_RTL);
   5880         assertEquals(View.TEXT_DIRECTION_RTL, tv.getRawTextDirection());
   5881 
   5882         tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
   5883         assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getRawTextDirection());
   5884 
   5885         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
   5886         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_LTR, tv.getRawTextDirection());
   5887 
   5888         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
   5889         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_RTL, tv.getRawTextDirection());
   5890     }
   5891 
   5892     @UiThreadTest
   5893     @Test
   5894     public void testGetResolvedTextDirectionLtr() {
   5895         TextView tv = new TextView(mActivity);
   5896         tv.setText("this is a test");
   5897 
   5898         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   5899 
   5900         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
   5901         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   5902 
   5903         tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
   5904         assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getTextDirection());
   5905 
   5906         tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
   5907         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   5908 
   5909         tv.setTextDirection(View.TEXT_DIRECTION_LTR);
   5910         assertEquals(View.TEXT_DIRECTION_LTR, tv.getTextDirection());
   5911 
   5912         tv.setTextDirection(View.TEXT_DIRECTION_RTL);
   5913         assertEquals(View.TEXT_DIRECTION_RTL, tv.getTextDirection());
   5914 
   5915         tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
   5916         assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getTextDirection());
   5917 
   5918         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
   5919         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_LTR, tv.getTextDirection());
   5920 
   5921         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
   5922         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_RTL, tv.getTextDirection());
   5923     }
   5924 
   5925     @UiThreadTest
   5926     @Test
   5927     public void testGetResolvedTextDirectionLtrWithInheritance() {
   5928         LinearLayout ll = new LinearLayout(mActivity);
   5929         ll.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
   5930 
   5931         TextView tv = new TextView(mActivity);
   5932         tv.setText("this is a test");
   5933         ll.addView(tv);
   5934 
   5935         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
   5936         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   5937 
   5938         tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
   5939         assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getTextDirection());
   5940 
   5941         tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
   5942         assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getTextDirection());
   5943 
   5944         tv.setTextDirection(View.TEXT_DIRECTION_LTR);
   5945         assertEquals(View.TEXT_DIRECTION_LTR, tv.getTextDirection());
   5946 
   5947         tv.setTextDirection(View.TEXT_DIRECTION_RTL);
   5948         assertEquals(View.TEXT_DIRECTION_RTL, tv.getTextDirection());
   5949 
   5950         tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
   5951         assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getTextDirection());
   5952 
   5953         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
   5954         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_LTR, tv.getTextDirection());
   5955 
   5956         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
   5957         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_RTL, tv.getTextDirection());
   5958     }
   5959 
   5960     @UiThreadTest
   5961     @Test
   5962     public void testGetResolvedTextDirectionRtl() {
   5963         TextView tv = new TextView(mActivity);
   5964         tv.setText("\u05DD\u05DE"); // hebrew
   5965 
   5966         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   5967 
   5968         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
   5969         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   5970 
   5971         tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
   5972         assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getTextDirection());
   5973 
   5974         tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
   5975         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   5976 
   5977         tv.setTextDirection(View.TEXT_DIRECTION_LTR);
   5978         assertEquals(View.TEXT_DIRECTION_LTR, tv.getTextDirection());
   5979 
   5980         tv.setTextDirection(View.TEXT_DIRECTION_RTL);
   5981         assertEquals(View.TEXT_DIRECTION_RTL, tv.getTextDirection());
   5982 
   5983         tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
   5984         assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getTextDirection());
   5985 
   5986         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
   5987         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_LTR, tv.getTextDirection());
   5988 
   5989         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
   5990         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_RTL, tv.getTextDirection());
   5991     }
   5992 
   5993     @UiThreadTest
   5994     @Test
   5995     public void testGetResolvedTextDirectionRtlWithInheritance() {
   5996         LinearLayout ll = new LinearLayout(mActivity);
   5997         ll.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
   5998 
   5999         TextView tv = new TextView(mActivity);
   6000         tv.setText("\u05DD\u05DE"); // hebrew
   6001         ll.addView(tv);
   6002 
   6003         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
   6004         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   6005 
   6006         tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
   6007         assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getTextDirection());
   6008 
   6009         tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
   6010         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   6011 
   6012         tv.setTextDirection(View.TEXT_DIRECTION_LTR);
   6013         assertEquals(View.TEXT_DIRECTION_LTR, tv.getTextDirection());
   6014 
   6015         tv.setTextDirection(View.TEXT_DIRECTION_RTL);
   6016         assertEquals(View.TEXT_DIRECTION_RTL, tv.getTextDirection());
   6017 
   6018         tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
   6019         assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getTextDirection());
   6020 
   6021         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
   6022         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_LTR, tv.getTextDirection());
   6023 
   6024         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
   6025         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_RTL, tv.getTextDirection());
   6026 
   6027         // Force to RTL text direction on the layout
   6028         ll.setTextDirection(View.TEXT_DIRECTION_RTL);
   6029 
   6030         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
   6031         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   6032 
   6033         tv.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
   6034         assertEquals(View.TEXT_DIRECTION_ANY_RTL, tv.getTextDirection());
   6035 
   6036         tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
   6037         assertEquals(View.TEXT_DIRECTION_RTL, tv.getTextDirection());
   6038 
   6039         tv.setTextDirection(View.TEXT_DIRECTION_LTR);
   6040         assertEquals(View.TEXT_DIRECTION_LTR, tv.getTextDirection());
   6041 
   6042         tv.setTextDirection(View.TEXT_DIRECTION_RTL);
   6043         assertEquals(View.TEXT_DIRECTION_RTL, tv.getTextDirection());
   6044 
   6045         tv.setTextDirection(View.TEXT_DIRECTION_LOCALE);
   6046         assertEquals(View.TEXT_DIRECTION_LOCALE, tv.getTextDirection());
   6047 
   6048         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
   6049         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_LTR, tv.getTextDirection());
   6050 
   6051         tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
   6052         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_RTL, tv.getTextDirection());
   6053     }
   6054 
   6055     @UiThreadTest
   6056     @Test
   6057     public void testResetTextDirection() {
   6058         LinearLayout ll = (LinearLayout) mActivity.findViewById(R.id.layout_textviewtest);
   6059         TextView tv = (TextView) mActivity.findViewById(R.id.textview_rtl);
   6060 
   6061         ll.setTextDirection(View.TEXT_DIRECTION_RTL);
   6062         tv.setTextDirection(View.TEXT_DIRECTION_INHERIT);
   6063         assertEquals(View.TEXT_DIRECTION_RTL, tv.getTextDirection());
   6064 
   6065         // No reset when we remove the view
   6066         ll.removeView(tv);
   6067         assertEquals(View.TEXT_DIRECTION_RTL, tv.getTextDirection());
   6068 
   6069         // Reset is done when we add the view
   6070         ll.addView(tv);
   6071         assertEquals(View.TEXT_DIRECTION_FIRST_STRONG, tv.getTextDirection());
   6072     }
   6073 
   6074     @UiThreadTest
   6075     @Test
   6076     public void testTextDirectionFirstStrongLtr() {
   6077         {
   6078             // The first directional character is LTR, the paragraph direction is LTR.
   6079             LinearLayout ll = new LinearLayout(mActivity);
   6080 
   6081             TextView tv = new TextView(mActivity);
   6082             tv.setText("this is a test");
   6083             ll.addView(tv);
   6084 
   6085             tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
   6086             assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_LTR, tv.getTextDirection());
   6087 
   6088             tv.onPreDraw();  // For freezing layout.
   6089             Layout layout = tv.getLayout();
   6090             assertEquals(Layout.DIR_LEFT_TO_RIGHT, layout.getParagraphDirection(0));
   6091         }
   6092         {
   6093             // The first directional character is RTL, the paragraph direction is RTL.
   6094             LinearLayout ll = new LinearLayout(mActivity);
   6095 
   6096             TextView tv = new TextView(mActivity);
   6097             tv.setText("\u05DD\u05DE"); // Hebrew
   6098             ll.addView(tv);
   6099 
   6100             tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
   6101             assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_LTR, tv.getTextDirection());
   6102 
   6103             tv.onPreDraw();  // For freezing layout.
   6104             Layout layout = tv.getLayout();
   6105             assertEquals(Layout.DIR_RIGHT_TO_LEFT, layout.getParagraphDirection(0));
   6106         }
   6107         {
   6108             // The first directional character is not a strong directional character, the paragraph
   6109             // direction is LTR.
   6110             LinearLayout ll = new LinearLayout(mActivity);
   6111 
   6112             TextView tv = new TextView(mActivity);
   6113             tv.setText("\uFFFD");  // REPLACEMENT CHARACTER. Neutral direction.
   6114             ll.addView(tv);
   6115 
   6116             tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
   6117             assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_LTR, tv.getTextDirection());
   6118 
   6119             tv.onPreDraw();  // For freezing layout.
   6120             Layout layout = tv.getLayout();
   6121             assertEquals(Layout.DIR_LEFT_TO_RIGHT, layout.getParagraphDirection(0));
   6122         }
   6123     }
   6124 
   6125     @UiThreadTest
   6126     @Test
   6127     public void testTextDirectionFirstStrongRtl() {
   6128         {
   6129             // The first directional character is LTR, the paragraph direction is LTR.
   6130             LinearLayout ll = new LinearLayout(mActivity);
   6131 
   6132             TextView tv = new TextView(mActivity);
   6133             tv.setText("this is a test");
   6134             ll.addView(tv);
   6135 
   6136             tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
   6137             assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_RTL, tv.getTextDirection());
   6138 
   6139             tv.onPreDraw();  // For freezing layout.
   6140             Layout layout = tv.getLayout();
   6141             assertEquals(Layout.DIR_LEFT_TO_RIGHT, layout.getParagraphDirection(0));
   6142         }
   6143         {
   6144             // The first directional character is RTL, the paragraph direction is RTL.
   6145             LinearLayout ll = new LinearLayout(mActivity);
   6146 
   6147             TextView tv = new TextView(mActivity);
   6148             tv.setText("\u05DD\u05DE"); // Hebrew
   6149             ll.addView(tv);
   6150 
   6151             tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
   6152             assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_RTL, tv.getTextDirection());
   6153 
   6154             tv.onPreDraw();  // For freezing layout.
   6155             Layout layout = tv.getLayout();
   6156             assertEquals(Layout.DIR_RIGHT_TO_LEFT, layout.getParagraphDirection(0));
   6157         }
   6158         {
   6159             // The first directional character is not a strong directional character, the paragraph
   6160             // direction is RTL.
   6161             LinearLayout ll = new LinearLayout(mActivity);
   6162 
   6163             TextView tv = new TextView(mActivity);
   6164             tv.setText("\uFFFD");  // REPLACEMENT CHARACTER. Neutral direction.
   6165             ll.addView(tv);
   6166 
   6167             tv.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
   6168             assertEquals(View.TEXT_DIRECTION_FIRST_STRONG_RTL, tv.getTextDirection());
   6169 
   6170             tv.onPreDraw();  // For freezing layout.
   6171             Layout layout = tv.getLayout();
   6172             assertEquals(Layout.DIR_RIGHT_TO_LEFT, layout.getParagraphDirection(0));
   6173         }
   6174     }
   6175 
   6176     @UiThreadTest
   6177     @Test
   6178     public void testTextLocales() {
   6179         TextView tv = new TextView(mActivity);
   6180         assertEquals(Locale.getDefault(), tv.getTextLocale());
   6181         assertEquals(LocaleList.getDefault(), tv.getTextLocales());
   6182 
   6183         tv.setTextLocale(Locale.CHINESE);
   6184         assertEquals(Locale.CHINESE, tv.getTextLocale());
   6185         assertEquals(new LocaleList(Locale.CHINESE), tv.getTextLocales());
   6186 
   6187         tv.setTextLocales(LocaleList.forLanguageTags("en,ja"));
   6188         assertEquals(Locale.forLanguageTag("en"), tv.getTextLocale());
   6189         assertEquals(LocaleList.forLanguageTags("en,ja"), tv.getTextLocales());
   6190 
   6191         try {
   6192             tv.setTextLocale(null);
   6193             fail("Setting the text locale to null should throw");
   6194         } catch (Throwable e) {
   6195             assertEquals(IllegalArgumentException.class, e.getClass());
   6196         }
   6197 
   6198         try {
   6199             tv.setTextLocales(null);
   6200             fail("Setting the text locales to null should throw");
   6201         } catch (Throwable e) {
   6202             assertEquals(IllegalArgumentException.class, e.getClass());
   6203         }
   6204 
   6205         try {
   6206             tv.setTextLocales(new LocaleList());
   6207             fail("Setting the text locale to an empty list should throw");
   6208         } catch (Throwable e) {
   6209             assertEquals(IllegalArgumentException.class, e.getClass());
   6210         }
   6211     }
   6212 
   6213     @UiThreadTest
   6214     @Test
   6215     public void testAllCaps_Localization() {
   6216         final String testString = "abcdefghijklmnopqrstuvwxyz i\u0307\u0301  ";
   6217 
   6218         // Capital "i" in Turkish and Azerbaijani is different from English, Lithuanian has special
   6219         // rules for uppercasing dotted i with accents, and Greek has complex capitalization rules.
   6220         final Locale[] testLocales = {
   6221             new Locale("az", "AZ"),  // Azerbaijani
   6222             new Locale("tr", "TR"),  // Turkish
   6223             new Locale("lt", "LT"),  // Lithuanian
   6224             new Locale("el", "GR"),  // Greek
   6225             Locale.US,
   6226         };
   6227 
   6228         final TextView tv = new TextView(mActivity);
   6229         tv.setAllCaps(true);
   6230         for (Locale locale: testLocales) {
   6231             tv.setTextLocale(locale);
   6232             assertEquals("Locale: " + locale.getDisplayName(),
   6233                          UCharacter.toUpperCase(locale, testString),
   6234                          tv.getTransformationMethod().getTransformation(testString, tv).toString());
   6235         }
   6236     }
   6237 
   6238     @UiThreadTest
   6239     @Test
   6240     public void testAllCaps_SpansArePreserved() {
   6241         final Locale greek = new Locale("el", "GR");
   6242         final String lowerString = "\u0301";  //  with first letter decomposed
   6243         final String upperString = "";  // uppercased
   6244         // expected lowercase to uppercase index map
   6245         final int[] indexMap = {0, 1, 1, 2, 3, 4, 5};
   6246         final int flags = Spanned.SPAN_INCLUSIVE_INCLUSIVE;
   6247 
   6248         final TextView tv = new TextView(mActivity);
   6249         tv.setTextLocale(greek);
   6250         tv.setAllCaps(true);
   6251 
   6252         final Spannable source = new SpannableString(lowerString);
   6253         source.setSpan(new Object(), 0, 1, flags);
   6254         source.setSpan(new Object(), 1, 2, flags);
   6255         source.setSpan(new Object(), 2, 3, flags);
   6256         source.setSpan(new Object(), 3, 4, flags);
   6257         source.setSpan(new Object(), 4, 5, flags);
   6258         source.setSpan(new Object(), 5, 6, flags);
   6259         source.setSpan(new Object(), 0, 2, flags);
   6260         source.setSpan(new Object(), 1, 3, flags);
   6261         source.setSpan(new Object(), 2, 4, flags);
   6262         source.setSpan(new Object(), 0, 6, flags);
   6263         final Object[] sourceSpans = source.getSpans(0, source.length(), Object.class);
   6264 
   6265         final CharSequence transformed =
   6266                 tv.getTransformationMethod().getTransformation(source, tv);
   6267         assertTrue(transformed instanceof Spanned);
   6268         final Spanned result = (Spanned) transformed;
   6269 
   6270         assertEquals(upperString, transformed.toString());
   6271         final Object[] resultSpans = result.getSpans(0, result.length(), Object.class);
   6272         assertEquals(sourceSpans.length, resultSpans.length);
   6273         for (int i = 0; i < sourceSpans.length; i++) {
   6274             assertSame(sourceSpans[i], resultSpans[i]);
   6275             final Object span = sourceSpans[i];
   6276             assertEquals(indexMap[source.getSpanStart(span)], result.getSpanStart(span));
   6277             assertEquals(indexMap[source.getSpanEnd(span)], result.getSpanEnd(span));
   6278             assertEquals(source.getSpanFlags(span), result.getSpanFlags(span));
   6279         }
   6280     }
   6281 
   6282     @UiThreadTest
   6283     @Test
   6284     public void testTextAlignmentDefault() {
   6285         TextView tv = new TextView(mActivity);
   6286         assertEquals(View.TEXT_ALIGNMENT_GRAVITY, tv.getRawTextAlignment());
   6287         // resolved default text alignment is GRAVITY
   6288         assertEquals(View.TEXT_ALIGNMENT_GRAVITY, tv.getTextAlignment());
   6289     }
   6290 
   6291     @UiThreadTest
   6292     @Test
   6293     public void testSetGetTextAlignment() {
   6294         TextView tv = new TextView(mActivity);
   6295 
   6296         tv.setTextAlignment(View.TEXT_ALIGNMENT_GRAVITY);
   6297         assertEquals(View.TEXT_ALIGNMENT_GRAVITY, tv.getRawTextAlignment());
   6298 
   6299         tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
   6300         assertEquals(View.TEXT_ALIGNMENT_CENTER, tv.getRawTextAlignment());
   6301 
   6302         tv.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
   6303         assertEquals(View.TEXT_ALIGNMENT_TEXT_START, tv.getRawTextAlignment());
   6304 
   6305         tv.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_END);
   6306         assertEquals(View.TEXT_ALIGNMENT_TEXT_END, tv.getRawTextAlignment());
   6307 
   6308         tv.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
   6309         assertEquals(View.TEXT_ALIGNMENT_VIEW_START, tv.getRawTextAlignment());
   6310 
   6311         tv.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
   6312         assertEquals(View.TEXT_ALIGNMENT_VIEW_END, tv.getRawTextAlignment());
   6313     }
   6314 
   6315     @UiThreadTest
   6316     @Test
   6317     public void testGetResolvedTextAlignment() {
   6318         TextView tv = new TextView(mActivity);
   6319 
   6320         assertEquals(View.TEXT_ALIGNMENT_GRAVITY, tv.getTextAlignment());
   6321 
   6322         // Test center alignment first so that we dont hit the default case
   6323         tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
   6324         assertEquals(View.TEXT_ALIGNMENT_CENTER, tv.getTextAlignment());
   6325 
   6326         // Test the default case too
   6327         tv.setTextAlignment(View.TEXT_ALIGNMENT_GRAVITY);
   6328         assertEquals(View.TEXT_ALIGNMENT_GRAVITY, tv.getTextAlignment());
   6329 
   6330         tv.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
   6331         assertEquals(View.TEXT_ALIGNMENT_TEXT_START, tv.getTextAlignment());
   6332 
   6333         tv.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_END);
   6334         assertEquals(View.TEXT_ALIGNMENT_TEXT_END, tv.getTextAlignment());
   6335 
   6336         tv.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
   6337         assertEquals(View.TEXT_ALIGNMENT_VIEW_START, tv.getTextAlignment());
   6338 
   6339         tv.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
   6340         assertEquals(View.TEXT_ALIGNMENT_VIEW_END, tv.getTextAlignment());
   6341     }
   6342 
   6343     @UiThreadTest
   6344     @Test
   6345     public void testGetResolvedTextAlignmentWithInheritance() {
   6346         LinearLayout ll = new LinearLayout(mActivity);
   6347         ll.setTextAlignment(View.TEXT_ALIGNMENT_GRAVITY);
   6348 
   6349         TextView tv = new TextView(mActivity);
   6350         ll.addView(tv);
   6351 
   6352         // check defaults
   6353         assertEquals(View.TEXT_ALIGNMENT_GRAVITY, tv.getRawTextAlignment());
   6354         assertEquals(View.TEXT_ALIGNMENT_GRAVITY, tv.getTextAlignment());
   6355 
   6356         // set inherit and check that child is following parent
   6357         tv.setTextAlignment(View.TEXT_ALIGNMENT_INHERIT);
   6358         assertEquals(View.TEXT_ALIGNMENT_INHERIT, tv.getRawTextAlignment());
   6359 
   6360         ll.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
   6361         assertEquals(View.TEXT_ALIGNMENT_CENTER, tv.getTextAlignment());
   6362 
   6363         ll.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
   6364         assertEquals(View.TEXT_ALIGNMENT_TEXT_START, tv.getTextAlignment());
   6365 
   6366         ll.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_END);
   6367         assertEquals(View.TEXT_ALIGNMENT_TEXT_END, tv.getTextAlignment());
   6368 
   6369         ll.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
   6370         assertEquals(View.TEXT_ALIGNMENT_VIEW_START, tv.getTextAlignment());
   6371 
   6372         ll.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
   6373         assertEquals(View.TEXT_ALIGNMENT_VIEW_END, tv.getTextAlignment());
   6374 
   6375         // now get rid of the inheritance but still change the parent
   6376         tv.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
   6377 
   6378         ll.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
   6379         assertEquals(View.TEXT_ALIGNMENT_CENTER, tv.getTextAlignment());
   6380 
   6381         ll.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_START);
   6382         assertEquals(View.TEXT_ALIGNMENT_CENTER, tv.getTextAlignment());
   6383 
   6384         ll.setTextAlignment(View.TEXT_ALIGNMENT_TEXT_END);
   6385         assertEquals(View.TEXT_ALIGNMENT_CENTER, tv.getTextAlignment());
   6386 
   6387         ll.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_START);
   6388         assertEquals(View.TEXT_ALIGNMENT_CENTER, tv.getTextAlignment());
   6389 
   6390         ll.setTextAlignment(View.TEXT_ALIGNMENT_VIEW_END);
   6391         assertEquals(View.TEXT_ALIGNMENT_CENTER, tv.getTextAlignment());
   6392     }
   6393 
   6394     @UiThreadTest
   6395     @Test
   6396     public void testResetTextAlignment() {
   6397         LinearLayout ll = (LinearLayout) mActivity.findViewById(R.id.layout_textviewtest);
   6398         TextView tv = (TextView) mActivity.findViewById(R.id.textview_rtl);
   6399 
   6400         ll.setTextAlignment(View.TEXT_ALIGNMENT_CENTER);
   6401         tv.setTextAlignment(View.TEXT_ALIGNMENT_INHERIT);
   6402         assertEquals(View.TEXT_ALIGNMENT_CENTER, tv.getTextAlignment());
   6403 
   6404         // No reset when we remove the view
   6405         ll.removeView(tv);
   6406         assertEquals(View.TEXT_ALIGNMENT_CENTER, tv.getTextAlignment());
   6407 
   6408         // Reset is done when we add the view
   6409         // Default text alignment is GRAVITY
   6410         ll.addView(tv);
   6411         assertEquals(View.TEXT_ALIGNMENT_GRAVITY, tv.getTextAlignment());
   6412     }
   6413 
   6414     @UiThreadTest
   6415     @Test
   6416     public void testDrawableResolution() {
   6417         // Case 1.1: left / right drawable defined in default LTR mode
   6418         TextView tv = (TextView) mActivity.findViewById(R.id.textview_drawable_1_1);
   6419         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6420                 R.drawable.icon_green, R.drawable.icon_yellow);
   6421         TestUtils.verifyCompoundDrawablesRelative(tv, -1, -1,
   6422                 R.drawable.icon_green, R.drawable.icon_yellow);
   6423 
   6424         // Case 1.2: left / right drawable defined in default RTL mode
   6425         tv = (TextView) mActivity.findViewById(R.id.textview_drawable_1_2);
   6426         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6427                 R.drawable.icon_green, R.drawable.icon_yellow);
   6428         TestUtils.verifyCompoundDrawablesRelative(tv, -1, -1,
   6429                 R.drawable.icon_green, R.drawable.icon_yellow);
   6430 
   6431         // Case 2.1: start / end drawable defined in LTR mode
   6432         tv = (TextView) mActivity.findViewById(R.id.textview_drawable_2_1);
   6433         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6434                 R.drawable.icon_green, R.drawable.icon_yellow);
   6435         TestUtils.verifyCompoundDrawablesRelative(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6436                 R.drawable.icon_green, R.drawable.icon_yellow);
   6437 
   6438         // Case 2.2: start / end drawable defined in RTL mode
   6439         tv = (TextView) mActivity.findViewById(R.id.textview_drawable_2_2);
   6440         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_red, R.drawable.icon_blue,
   6441                 R.drawable.icon_green, R.drawable.icon_yellow);
   6442         TestUtils.verifyCompoundDrawablesRelative(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6443                 R.drawable.icon_green, R.drawable.icon_yellow);
   6444 
   6445         // Case 3.1: left / right / start / end drawable defined in LTR mode
   6446         tv = (TextView) mActivity.findViewById(R.id.textview_drawable_3_1);
   6447         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6448                 R.drawable.icon_green, R.drawable.icon_yellow);
   6449         TestUtils.verifyCompoundDrawablesRelative(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6450                 R.drawable.icon_green, R.drawable.icon_yellow);
   6451 
   6452         // Case 3.2: left / right / start / end drawable defined in RTL mode
   6453         tv = (TextView) mActivity.findViewById(R.id.textview_drawable_3_2);
   6454         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_red, R.drawable.icon_blue,
   6455                 R.drawable.icon_green, R.drawable.icon_yellow);
   6456         TestUtils.verifyCompoundDrawablesRelative(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6457                 R.drawable.icon_green, R.drawable.icon_yellow);
   6458 
   6459         // Case 4.1: start / end drawable defined in LTR mode inside a layout
   6460         // that defines the layout direction
   6461         tv = (TextView) mActivity.findViewById(R.id.textview_drawable_4_1);
   6462         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6463                 R.drawable.icon_green, R.drawable.icon_yellow);
   6464         TestUtils.verifyCompoundDrawablesRelative(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6465                 R.drawable.icon_green, R.drawable.icon_yellow);
   6466 
   6467         // Case 4.2: start / end drawable defined in RTL mode inside a layout
   6468         // that defines the layout direction
   6469         tv = (TextView) mActivity.findViewById(R.id.textview_drawable_4_2);
   6470         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_red, R.drawable.icon_blue,
   6471                 R.drawable.icon_green, R.drawable.icon_yellow);
   6472         TestUtils.verifyCompoundDrawablesRelative(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6473                 R.drawable.icon_green, R.drawable.icon_yellow);
   6474 
   6475         // Case 5.1: left / right / start / end drawable defined in LTR mode inside a layout
   6476         // that defines the layout direction
   6477         tv = (TextView) mActivity.findViewById(R.id.textview_drawable_5_1);
   6478         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6479                 R.drawable.icon_green, R.drawable.icon_yellow);
   6480         TestUtils.verifyCompoundDrawablesRelative(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6481                 R.drawable.icon_green, R.drawable.icon_yellow);
   6482 
   6483         // Case 5.2: left / right / start / end drawable defined in RTL mode inside a layout
   6484         // that defines the layout direction
   6485         tv = (TextView) mActivity.findViewById(R.id.textview_drawable_5_2);
   6486         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_red, R.drawable.icon_blue,
   6487                 R.drawable.icon_green, R.drawable.icon_yellow);
   6488         TestUtils.verifyCompoundDrawablesRelative(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6489                 R.drawable.icon_green, R.drawable.icon_yellow);
   6490     }
   6491 
   6492     @UiThreadTest
   6493     @Test
   6494     public void testDrawableResolution2() {
   6495         // Case 1.1: left / right drawable defined in default LTR mode
   6496         TextView tv = (TextView) mActivity.findViewById(R.id.textview_drawable_1_1);
   6497         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6498                 R.drawable.icon_green, R.drawable.icon_yellow);
   6499 
   6500         tv.setCompoundDrawables(null, null,
   6501                 TestUtils.getDrawable(mActivity, R.drawable.icon_yellow), null);
   6502         TestUtils.verifyCompoundDrawables(tv, -1, R.drawable.icon_yellow, -1, -1);
   6503 
   6504         tv = (TextView) mActivity.findViewById(R.id.textview_drawable_1_2);
   6505         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_blue, R.drawable.icon_red,
   6506                 R.drawable.icon_green, R.drawable.icon_yellow);
   6507 
   6508         tv.setCompoundDrawables(TestUtils.getDrawable(mActivity, R.drawable.icon_yellow), null,
   6509                 null, null);
   6510         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_yellow, -1, -1, -1);
   6511 
   6512         tv = (TextView) mActivity.findViewById(R.id.textview_ltr);
   6513         TestUtils.verifyCompoundDrawables(tv, -1, -1, -1, -1);
   6514 
   6515         tv.setCompoundDrawables(TestUtils.getDrawable(mActivity, R.drawable.icon_blue), null,
   6516                 TestUtils.getDrawable(mActivity, R.drawable.icon_red), null);
   6517         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_blue, R.drawable.icon_red, -1, -1);
   6518 
   6519         tv.setCompoundDrawablesRelative(TestUtils.getDrawable(mActivity, R.drawable.icon_yellow),
   6520                 null, null, null);
   6521         TestUtils.verifyCompoundDrawables(tv, R.drawable.icon_yellow, -1, -1, -1);
   6522     }
   6523 
   6524     @Test
   6525     public void testCompoundAndTotalPadding() {
   6526         final Resources res = mActivity.getResources();
   6527         final int drawablePadding = res.getDimensionPixelSize(R.dimen.textview_drawable_padding);
   6528         final int paddingLeft = res.getDimensionPixelSize(R.dimen.textview_padding_left);
   6529         final int paddingRight = res.getDimensionPixelSize(R.dimen.textview_padding_right);
   6530         final int paddingTop = res.getDimensionPixelSize(R.dimen.textview_padding_top);
   6531         final int paddingBottom = res.getDimensionPixelSize(R.dimen.textview_padding_bottom);
   6532         final int iconSize = TestUtils.dpToPx(mActivity, 32);
   6533 
   6534         final TextView textViewLtr = (TextView) mActivity.findViewById(
   6535                 R.id.textview_compound_drawable_ltr);
   6536         final int combinedPaddingLeftLtr = paddingLeft + drawablePadding + iconSize;
   6537         final int combinedPaddingRightLtr = paddingRight + drawablePadding + iconSize;
   6538         assertEquals(combinedPaddingLeftLtr, textViewLtr.getCompoundPaddingLeft());
   6539         assertEquals(combinedPaddingLeftLtr, textViewLtr.getCompoundPaddingStart());
   6540         assertEquals(combinedPaddingLeftLtr, textViewLtr.getTotalPaddingLeft());
   6541         assertEquals(combinedPaddingLeftLtr, textViewLtr.getTotalPaddingStart());
   6542         assertEquals(combinedPaddingRightLtr, textViewLtr.getCompoundPaddingRight());
   6543         assertEquals(combinedPaddingRightLtr, textViewLtr.getCompoundPaddingEnd());
   6544         assertEquals(combinedPaddingRightLtr, textViewLtr.getTotalPaddingRight());
   6545         assertEquals(combinedPaddingRightLtr, textViewLtr.getTotalPaddingEnd());
   6546         assertEquals(paddingTop + drawablePadding + iconSize,
   6547                 textViewLtr.getCompoundPaddingTop());
   6548         assertEquals(paddingBottom + drawablePadding + iconSize,
   6549                 textViewLtr.getCompoundPaddingBottom());
   6550 
   6551         final TextView textViewRtl = (TextView) mActivity.findViewById(
   6552                 R.id.textview_compound_drawable_rtl);
   6553         final int combinedPaddingLeftRtl = paddingLeft + drawablePadding + iconSize;
   6554         final int combinedPaddingRightRtl = paddingRight + drawablePadding + iconSize;
   6555         assertEquals(combinedPaddingLeftRtl, textViewRtl.getCompoundPaddingLeft());
   6556         assertEquals(combinedPaddingLeftRtl, textViewRtl.getCompoundPaddingEnd());
   6557         assertEquals(combinedPaddingLeftRtl, textViewRtl.getTotalPaddingLeft());
   6558         assertEquals(combinedPaddingLeftRtl, textViewRtl.getTotalPaddingEnd());
   6559         assertEquals(combinedPaddingRightRtl, textViewRtl.getCompoundPaddingRight());
   6560         assertEquals(combinedPaddingRightRtl, textViewRtl.getCompoundPaddingStart());
   6561         assertEquals(combinedPaddingRightRtl, textViewRtl.getTotalPaddingRight());
   6562         assertEquals(combinedPaddingRightRtl, textViewRtl.getTotalPaddingStart());
   6563         assertEquals(paddingTop + drawablePadding + iconSize,
   6564                 textViewRtl.getCompoundPaddingTop());
   6565         assertEquals(paddingBottom + drawablePadding + iconSize,
   6566                 textViewRtl.getCompoundPaddingBottom());
   6567     }
   6568 
   6569     @UiThreadTest
   6570     @Test
   6571     public void testSetGetBreakStrategy() {
   6572         TextView tv = new TextView(mActivity);
   6573 
   6574         final PackageManager pm = mInstrumentation.getTargetContext().getPackageManager();
   6575 
   6576         // The default value is from the theme, here the default is BREAK_STRATEGY_HIGH_QUALITY for
   6577         // TextView except for Android Wear. The default value for Android Wear is
   6578         // BREAK_STRATEGY_BALANCED.
   6579         if (pm.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
   6580             // Android Wear
   6581             assertEquals(Layout.BREAK_STRATEGY_BALANCED, tv.getBreakStrategy());
   6582         } else {
   6583             // All other form factor.
   6584             assertEquals(Layout.BREAK_STRATEGY_HIGH_QUALITY, tv.getBreakStrategy());
   6585         }
   6586 
   6587         tv.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE);
   6588         assertEquals(Layout.BREAK_STRATEGY_SIMPLE, tv.getBreakStrategy());
   6589 
   6590         tv.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
   6591         assertEquals(Layout.BREAK_STRATEGY_HIGH_QUALITY, tv.getBreakStrategy());
   6592 
   6593         tv.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED);
   6594         assertEquals(Layout.BREAK_STRATEGY_BALANCED, tv.getBreakStrategy());
   6595 
   6596         EditText et = new EditText(mActivity);
   6597 
   6598         // The default value is from the theme, here the default is BREAK_STRATEGY_SIMPLE for
   6599         // EditText.
   6600         assertEquals(Layout.BREAK_STRATEGY_SIMPLE, et.getBreakStrategy());
   6601 
   6602         et.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE);
   6603         assertEquals(Layout.BREAK_STRATEGY_SIMPLE, et.getBreakStrategy());
   6604 
   6605         et.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
   6606         assertEquals(Layout.BREAK_STRATEGY_HIGH_QUALITY, et.getBreakStrategy());
   6607 
   6608         et.setBreakStrategy(Layout.BREAK_STRATEGY_BALANCED);
   6609         assertEquals(Layout.BREAK_STRATEGY_BALANCED, et.getBreakStrategy());
   6610     }
   6611 
   6612     @UiThreadTest
   6613     @Test
   6614     public void testSetGetHyphenationFrequency() {
   6615         TextView tv = new TextView(mActivity);
   6616 
   6617         // Hypenation is enabled by default on watches to fit more text on their tiny screens.
   6618         if (isWatch()) {
   6619             assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL, tv.getHyphenationFrequency());
   6620         } else {
   6621             assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, tv.getHyphenationFrequency());
   6622         }
   6623 
   6624         tv.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL);
   6625         assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL, tv.getHyphenationFrequency());
   6626 
   6627         tv.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
   6628         assertEquals(Layout.HYPHENATION_FREQUENCY_FULL, tv.getHyphenationFrequency());
   6629 
   6630         tv.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE);
   6631         assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, tv.getHyphenationFrequency());
   6632     }
   6633 
   6634     @UiThreadTest
   6635     @Test
   6636     public void testSetGetJustify() {
   6637         TextView tv = new TextView(mActivity);
   6638 
   6639         assertEquals(Layout.JUSTIFICATION_MODE_NONE, tv.getJustificationMode());
   6640         tv.setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD);
   6641         assertEquals(Layout.JUSTIFICATION_MODE_INTER_WORD, tv.getJustificationMode());
   6642         tv.setJustificationMode(Layout.JUSTIFICATION_MODE_NONE);
   6643         assertEquals(Layout.JUSTIFICATION_MODE_NONE, tv.getJustificationMode());
   6644     }
   6645 
   6646     @Test
   6647     public void testJustificationByStyle() {
   6648         TextView defaultTv = findTextView(R.id.textview_justification_default);
   6649         TextView noneTv = findTextView(R.id.textview_justification_none);
   6650         TextView interWordTv = findTextView(R.id.textview_justification_inter_word);
   6651 
   6652         assertEquals(Layout.JUSTIFICATION_MODE_NONE, defaultTv.getJustificationMode());
   6653         assertEquals(Layout.JUSTIFICATION_MODE_NONE, noneTv.getJustificationMode());
   6654         assertEquals(Layout.JUSTIFICATION_MODE_INTER_WORD, interWordTv.getJustificationMode());
   6655     }
   6656 
   6657     @Test
   6658     public void testSetAndGetCustomSelectionActionModeCallback() throws Throwable {
   6659         final String text = "abcde";
   6660         mActivityRule.runOnUiThread(() -> {
   6661             mTextView = new EditText(mActivity);
   6662             mActivity.setContentView(mTextView);
   6663             mTextView.setText(text, BufferType.SPANNABLE);
   6664             mTextView.setTextIsSelectable(true);
   6665             mTextView.requestFocus();
   6666             mTextView.setSelected(true);
   6667             mTextView.setTextClassifier(TextClassifier.NO_OP);
   6668         });
   6669         mInstrumentation.waitForIdleSync();
   6670 
   6671         // Check default value.
   6672         assertNull(mTextView.getCustomSelectionActionModeCallback());
   6673 
   6674         final ActionMode.Callback mockActionModeCallback = mock(ActionMode.Callback.class);
   6675         when(mockActionModeCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class))).
   6676                 thenReturn(Boolean.FALSE);
   6677         mTextView.setCustomSelectionActionModeCallback(mockActionModeCallback);
   6678         assertEquals(mockActionModeCallback,
   6679                 mTextView.getCustomSelectionActionModeCallback());
   6680 
   6681         mActivityRule.runOnUiThread(() -> {
   6682             // Set selection and try to start action mode.
   6683             final Bundle args = new Bundle();
   6684             args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
   6685             args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, text.length());
   6686             mTextView.performAccessibilityAction(
   6687                     AccessibilityNodeInfo.ACTION_SET_SELECTION, args);
   6688         });
   6689         mInstrumentation.waitForIdleSync();
   6690 
   6691         verify(mockActionModeCallback, times(1)).onCreateActionMode(
   6692                 any(ActionMode.class), any(Menu.class));
   6693 
   6694         mActivityRule.runOnUiThread(() -> {
   6695             // Remove selection and stop action mode.
   6696             mTextView.onTextContextMenuItem(android.R.id.copy);
   6697         });
   6698         mInstrumentation.waitForIdleSync();
   6699 
   6700         // Action mode was blocked.
   6701         verify(mockActionModeCallback, never()).onDestroyActionMode(any(ActionMode.class));
   6702 
   6703         // Reset and reconfigure callback.
   6704         reset(mockActionModeCallback);
   6705         when(mockActionModeCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class))).
   6706                 thenReturn(Boolean.TRUE);
   6707         assertEquals(mockActionModeCallback, mTextView.getCustomSelectionActionModeCallback());
   6708 
   6709         mActivityRule.runOnUiThread(() -> {
   6710             // Set selection and try to start action mode.
   6711             final Bundle args = new Bundle();
   6712             args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
   6713             args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, text.length());
   6714             mTextView.performAccessibilityAction(
   6715                     AccessibilityNodeInfo.ACTION_SET_SELECTION, args);
   6716 
   6717         });
   6718         mInstrumentation.waitForIdleSync();
   6719 
   6720         verify(mockActionModeCallback, times(1)).onCreateActionMode(
   6721                 any(ActionMode.class), any(Menu.class));
   6722 
   6723         mActivityRule.runOnUiThread(() -> {
   6724             // Remove selection and stop action mode.
   6725             mTextView.onTextContextMenuItem(android.R.id.copy);
   6726         });
   6727         mInstrumentation.waitForIdleSync();
   6728 
   6729         // Action mode was started
   6730         verify(mockActionModeCallback, times(1)).onDestroyActionMode(any(ActionMode.class));
   6731     }
   6732 
   6733     @UiThreadTest
   6734     @Test
   6735     public void testSetAndGetCustomInsertionActionMode() {
   6736         initTextViewForTyping();
   6737         // Check default value.
   6738         assertNull(mTextView.getCustomInsertionActionModeCallback());
   6739 
   6740         final ActionMode.Callback mockActionModeCallback = mock(ActionMode.Callback.class);
   6741         when(mockActionModeCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class))).
   6742                 thenReturn(Boolean.FALSE);
   6743         mTextView.setCustomInsertionActionModeCallback(mockActionModeCallback);
   6744         assertEquals(mockActionModeCallback, mTextView.getCustomInsertionActionModeCallback());
   6745         // TODO(Bug: 22033189): Tests the set callback is actually used.
   6746     }
   6747 
   6748     @UiThreadTest
   6749     @Test
   6750     public void testRespectsViewFocusability() {
   6751         TextView v = (TextView) mActivity.findViewById(R.id.textview_singleLine);
   6752         assertFalse(v.isFocusable());
   6753         // TextView used to set focusable to true or false verbatim which would break the following.
   6754         v.setClickable(true);
   6755         assertTrue(v.isFocusable());
   6756     }
   6757 
   6758     @Test
   6759     public void testTextShadows() throws Throwable {
   6760         final TextView textViewWithConfiguredShadow =
   6761                 (TextView) mActivity.findViewById(R.id.textview_with_shadow);
   6762         assertEquals(1.0f, textViewWithConfiguredShadow.getShadowDx(), 0.0f);
   6763         assertEquals(2.0f, textViewWithConfiguredShadow.getShadowDy(), 0.0f);
   6764         assertEquals(3.0f, textViewWithConfiguredShadow.getShadowRadius(), 0.0f);
   6765         assertEquals(Color.GREEN, textViewWithConfiguredShadow.getShadowColor());
   6766 
   6767         final TextView textView = (TextView) mActivity.findViewById(R.id.textview_text);
   6768         assertEquals(0.0f, textView.getShadowDx(), 0.0f);
   6769         assertEquals(0.0f, textView.getShadowDy(), 0.0f);
   6770         assertEquals(0.0f, textView.getShadowRadius(), 0.0f);
   6771 
   6772         mActivityRule.runOnUiThread(() -> textView.setShadowLayer(5.0f, 3.0f, 4.0f, Color.RED));
   6773         mInstrumentation.waitForIdleSync();
   6774         assertEquals(3.0f, textView.getShadowDx(), 0.0f);
   6775         assertEquals(4.0f, textView.getShadowDy(), 0.0f);
   6776         assertEquals(5.0f, textView.getShadowRadius(), 0.0f);
   6777         assertEquals(Color.RED, textView.getShadowColor());
   6778     }
   6779 
   6780     @Test
   6781     public void testFontFeatureSettings() throws Throwable {
   6782         final TextView textView = (TextView) mActivity.findViewById(R.id.textview_text);
   6783         assertTrue(TextUtils.isEmpty(textView.getFontFeatureSettings()));
   6784 
   6785         mActivityRule.runOnUiThread(() -> textView.setFontFeatureSettings("smcp"));
   6786         mInstrumentation.waitForIdleSync();
   6787         assertEquals("smcp", textView.getFontFeatureSettings());
   6788 
   6789         mActivityRule.runOnUiThread(() -> textView.setFontFeatureSettings("frac"));
   6790         mInstrumentation.waitForIdleSync();
   6791         assertEquals("frac", textView.getFontFeatureSettings());
   6792     }
   6793 
   6794     @Test
   6795     public void testIsSuggestionsEnabled() throws Throwable {
   6796         mTextView = findTextView(R.id.textview_text);
   6797 
   6798         // Anything without InputType.TYPE_CLASS_TEXT doesn't have suggestions enabled
   6799         mActivityRule.runOnUiThread(() -> mTextView.setInputType(InputType.TYPE_CLASS_DATETIME));
   6800         assertFalse(mTextView.isSuggestionsEnabled());
   6801 
   6802         mActivityRule.runOnUiThread(() -> mTextView.setInputType(InputType.TYPE_CLASS_PHONE));
   6803         assertFalse(mTextView.isSuggestionsEnabled());
   6804 
   6805         mActivityRule.runOnUiThread(() -> mTextView.setInputType(InputType.TYPE_CLASS_NUMBER));
   6806         assertFalse(mTextView.isSuggestionsEnabled());
   6807 
   6808         // From this point our text view has InputType.TYPE_CLASS_TEXT
   6809 
   6810         // Anything with InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS doesn't have suggestions enabled
   6811         mActivityRule.runOnUiThread(
   6812                 () -> mTextView.setInputType(
   6813                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS));
   6814         assertFalse(mTextView.isSuggestionsEnabled());
   6815 
   6816         mActivityRule.runOnUiThread(
   6817                 () -> mTextView.setInputType(
   6818                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL |
   6819                                 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS));
   6820         assertFalse(mTextView.isSuggestionsEnabled());
   6821 
   6822         mActivityRule.runOnUiThread(
   6823                 () -> mTextView.setInputType(
   6824                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS |
   6825                                 InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS));
   6826         assertFalse(mTextView.isSuggestionsEnabled());
   6827 
   6828         // Otherwise suggestions are enabled for specific type variations enumerated in the
   6829         // documentation of TextView.isSuggestionsEnabled
   6830         mActivityRule.runOnUiThread(
   6831                 () -> mTextView.setInputType(
   6832                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_NORMAL));
   6833         assertTrue(mTextView.isSuggestionsEnabled());
   6834 
   6835         mActivityRule.runOnUiThread(
   6836                 () -> mTextView.setInputType(
   6837                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT));
   6838         assertTrue(mTextView.isSuggestionsEnabled());
   6839 
   6840         mActivityRule.runOnUiThread(
   6841                 () -> mTextView.setInputType(
   6842                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE));
   6843         assertTrue(mTextView.isSuggestionsEnabled());
   6844 
   6845         mActivityRule.runOnUiThread(
   6846                 () -> mTextView.setInputType(
   6847                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
   6848         assertTrue(mTextView.isSuggestionsEnabled());
   6849 
   6850         mActivityRule.runOnUiThread(
   6851                 () -> mTextView.setInputType(
   6852                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT));
   6853         assertTrue(mTextView.isSuggestionsEnabled());
   6854 
   6855         // and not on any other type variation
   6856         mActivityRule.runOnUiThread(
   6857                 () -> mTextView.setInputType(
   6858                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS));
   6859         assertFalse(mTextView.isSuggestionsEnabled());
   6860 
   6861         mActivityRule.runOnUiThread(
   6862                 () -> mTextView.setInputType(
   6863                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_FILTER));
   6864         assertFalse(mTextView.isSuggestionsEnabled());
   6865 
   6866         mActivityRule.runOnUiThread(
   6867                 () -> mTextView.setInputType(
   6868                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD));
   6869         assertFalse(mTextView.isSuggestionsEnabled());
   6870 
   6871         mActivityRule.runOnUiThread(
   6872                 () -> mTextView.setInputType(
   6873                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PERSON_NAME));
   6874         assertFalse(mTextView.isSuggestionsEnabled());
   6875 
   6876         mActivityRule.runOnUiThread(
   6877                 () -> mTextView.setInputType(
   6878                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PHONETIC));
   6879         assertFalse(mTextView.isSuggestionsEnabled());
   6880 
   6881         mActivityRule.runOnUiThread(
   6882                 () -> mTextView.setInputType(
   6883                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS));
   6884         assertFalse(mTextView.isSuggestionsEnabled());
   6885 
   6886         mActivityRule.runOnUiThread(
   6887                 () -> mTextView.setInputType(
   6888                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_URI));
   6889         assertFalse(mTextView.isSuggestionsEnabled());
   6890 
   6891         mActivityRule.runOnUiThread(
   6892                 () -> mTextView.setInputType(
   6893                         InputType.TYPE_CLASS_TEXT |
   6894                                 InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD));
   6895         assertFalse(mTextView.isSuggestionsEnabled());
   6896 
   6897         mActivityRule.runOnUiThread(
   6898                 () -> mTextView.setInputType(
   6899                         InputType.TYPE_CLASS_TEXT |
   6900                                 InputType.TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS));
   6901         assertFalse(mTextView.isSuggestionsEnabled());
   6902 
   6903         mActivityRule.runOnUiThread(
   6904                 () -> mTextView.setInputType(
   6905                         InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
   6906         assertFalse(mTextView.isSuggestionsEnabled());
   6907     }
   6908 
   6909     @Test
   6910     public void testAccessLetterSpacing() throws Throwable {
   6911         mTextView = findTextView(R.id.textview_text);
   6912         assertEquals(0.0f, mTextView.getLetterSpacing(), 0.0f);
   6913 
   6914         final CharSequence text = mTextView.getText();
   6915         final int textLength = text.length();
   6916 
   6917         // Get advance widths of each character at the default letter spacing
   6918         final float[] initialWidths = new float[textLength];
   6919         mTextView.getPaint().getTextWidths(text.toString(), initialWidths);
   6920 
   6921         // Get advance widths of each character at letter spacing = 1.0f
   6922         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mTextView,
   6923                 () -> mTextView.setLetterSpacing(1.0f));
   6924         assertEquals(1.0f, mTextView.getLetterSpacing(), 0.0f);
   6925         final float[] singleWidths = new float[textLength];
   6926         mTextView.getPaint().getTextWidths(text.toString(), singleWidths);
   6927 
   6928         // Get advance widths of each character at letter spacing = 2.0f
   6929         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mTextView,
   6930                 () -> mTextView.setLetterSpacing(2.0f));
   6931         assertEquals(2.0f, mTextView.getLetterSpacing(), 0.0f);
   6932         final float[] doubleWidths = new float[textLength];
   6933         mTextView.getPaint().getTextWidths(text.toString(), doubleWidths);
   6934 
   6935         // Since letter spacing setter treats the parameter as EM units, and we don't have
   6936         // a way to convert EMs into pixels, go over the three arrays of advance widths and
   6937         // test that the extra advance width at letter spacing 2.0f is double the extra
   6938         // advance width at letter spacing 1.0f.
   6939         for (int i = 0; i < textLength; i++) {
   6940             float singleWidthDelta = singleWidths[i] - initialWidths[i];
   6941             float doubleWidthDelta = doubleWidths[i] - initialWidths[i];
   6942             assertEquals("At index " + i + " initial is " + initialWidths[i] +
   6943                 ", single is " + singleWidths[i] + " and double is " + doubleWidths[i],
   6944                     singleWidthDelta * 2.0f, doubleWidthDelta, 0.05f);
   6945         }
   6946     }
   6947 
   6948     @Test
   6949     public void testTextIsSelectableFocusAndOnClick() throws Throwable {
   6950         // Prepare a focusable TextView with an onClickListener attached.
   6951         final View.OnClickListener mockOnClickListener = mock(View.OnClickListener.class);
   6952         final int safeDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout() + 1;
   6953         mActivityRule.runOnUiThread(() -> {
   6954             // set up a dummy focusable so that initial focus doesn't go to our test textview
   6955             LinearLayout top = new LinearLayout(mActivity);
   6956             TextView dummy = new TextView(mActivity);
   6957             dummy.setFocusableInTouchMode(true);
   6958             top.addView(dummy, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
   6959             mTextView = new TextView(mActivity);
   6960             mTextView.setText("...text 11:11. some more text is in here...");
   6961             mTextView.setFocusable(true);
   6962             mTextView.setOnClickListener(mockOnClickListener);
   6963             top.addView(mTextView, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
   6964             mActivity.setContentView(top);
   6965         });
   6966         mInstrumentation.waitForIdleSync();
   6967         assertTrue(mTextView.isFocusable());
   6968         assertFalse(mTextView.isTextSelectable());
   6969         assertFalse(mTextView.isFocusableInTouchMode());
   6970         assertFalse(mTextView.isFocused());
   6971         assertFalse(mTextView.isInTouchMode());
   6972 
   6973         // First tap on the view triggers onClick() but does not focus the TextView.
   6974         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mTextView);
   6975         SystemClock.sleep(safeDoubleTapTimeout);
   6976         assertTrue(mTextView.isInTouchMode());
   6977         assertFalse(mTextView.isFocused());
   6978         verify(mockOnClickListener, times(1)).onClick(mTextView);
   6979         reset(mockOnClickListener);
   6980         // So does the second tap.
   6981         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mTextView);
   6982         SystemClock.sleep(safeDoubleTapTimeout);
   6983         assertTrue(mTextView.isInTouchMode());
   6984         assertFalse(mTextView.isFocused());
   6985         verify(mockOnClickListener, times(1)).onClick(mTextView);
   6986 
   6987         mActivityRule.runOnUiThread(() -> mTextView.setTextIsSelectable(true));
   6988         mInstrumentation.waitForIdleSync();
   6989         assertTrue(mTextView.isFocusable());
   6990         assertTrue(mTextView.isTextSelectable());
   6991         assertTrue(mTextView.isFocusableInTouchMode());
   6992         assertFalse(mTextView.isFocused());
   6993 
   6994         // First tap on the view focuses the TextView but does not trigger onClick().
   6995         reset(mockOnClickListener);
   6996         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mTextView);
   6997         SystemClock.sleep(safeDoubleTapTimeout);
   6998         assertTrue(mTextView.isInTouchMode());
   6999         assertTrue(mTextView.isFocused());
   7000         verify(mockOnClickListener, never()).onClick(mTextView);
   7001         reset(mockOnClickListener);
   7002         // The second tap triggers onClick() and keeps the focus.
   7003         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mTextView);
   7004         SystemClock.sleep(safeDoubleTapTimeout);
   7005         assertTrue(mTextView.isInTouchMode());
   7006         assertTrue(mTextView.isFocused());
   7007         verify(mockOnClickListener, times(1)).onClick(mTextView);
   7008     }
   7009 
   7010     private void verifyGetOffsetForPosition(final int x, final int y) {
   7011         final int actual = mTextView.getOffsetForPosition(x, y);
   7012 
   7013         final Layout layout = mTextView.getLayout();
   7014         if (layout == null) {
   7015             assertEquals("For [" + x + ", " + y + "]", -1, actual);
   7016             return;
   7017         }
   7018 
   7019         // Get the line which corresponds to the Y position
   7020         final int line = layout.getLineForVertical(y + mTextView.getScrollY());
   7021         // Get the offset in that line that corresponds to the X position
   7022         final int expected = layout.getOffsetForHorizontal(line, x + mTextView.getScrollX());
   7023         assertEquals("For [" + x + ", " + y + "]", expected, actual);
   7024     }
   7025 
   7026     @Test
   7027     public void testGetOffsetForPosition() throws Throwable {
   7028         mTextView = findTextView(R.id.textview_text);
   7029         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mTextView, () -> {
   7030             mTextView.setText(LONG_TEXT);
   7031             mTextView.setPadding(0, 0, 0, 0);
   7032         });
   7033 
   7034         assertNotNull(mTextView.getLayout());
   7035         final int viewWidth = mTextView.getWidth();
   7036         final int viewHeight = mTextView.getHeight();
   7037         final int lineHeight = mTextView.getLineHeight();
   7038 
   7039         verifyGetOffsetForPosition(0, 0);
   7040         verifyGetOffsetForPosition(0, viewHeight / 2);
   7041         verifyGetOffsetForPosition(viewWidth / 3, lineHeight / 2);
   7042         verifyGetOffsetForPosition(viewWidth / 2, viewHeight / 2);
   7043         verifyGetOffsetForPosition(viewWidth, viewHeight);
   7044     }
   7045 
   7046     @UiThreadTest
   7047     @Test
   7048     public void testOnResolvePointerIcon() throws InterruptedException {
   7049         final TextView selectableTextView = findTextView(R.id.textview_pointer);
   7050         final MotionEvent event = createMouseHoverEvent(selectableTextView);
   7051 
   7052         // A selectable view shows the I beam
   7053         selectableTextView.setTextIsSelectable(true);
   7054 
   7055         assertEquals(PointerIcon.getSystemIcon(mActivity, PointerIcon.TYPE_TEXT),
   7056                 selectableTextView.onResolvePointerIcon(event, 0));
   7057         selectableTextView.setTextIsSelectable(false);
   7058 
   7059         // A clickable view shows the hand
   7060         selectableTextView.setLinksClickable(true);
   7061         SpannableString builder = new SpannableString("hello world");
   7062         selectableTextView.setText(builder, BufferType.SPANNABLE);
   7063         Spannable text = (Spannable) selectableTextView.getText();
   7064         text.setSpan(
   7065                 new ClickableSpan() {
   7066                     @Override
   7067                     public void onClick(View widget) {
   7068 
   7069                     }
   7070                 }, 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
   7071 
   7072         assertEquals(PointerIcon.getSystemIcon(mActivity, PointerIcon.TYPE_HAND),
   7073                 selectableTextView.onResolvePointerIcon(event, 0));
   7074 
   7075         // A selectable & clickable view shows hand
   7076         selectableTextView.setTextIsSelectable(true);
   7077 
   7078         assertEquals(PointerIcon.getSystemIcon(mActivity, PointerIcon.TYPE_HAND),
   7079                 selectableTextView.onResolvePointerIcon(event, 0));
   7080 
   7081         // An editable view shows the I-beam
   7082         final TextView editableTextView = new EditText(mActivity);
   7083 
   7084         assertEquals(PointerIcon.getSystemIcon(mActivity, PointerIcon.TYPE_TEXT),
   7085                 editableTextView.onResolvePointerIcon(event, 0));
   7086     }
   7087 
   7088     @Test
   7089     public void testClickableSpanOnClickSingleTapInside() throws Throwable {
   7090         ClickableSpanTestDetails spanDetails = prepareAndRetrieveClickableSpanDetails();
   7091         CtsTouchUtils.emulateTapOnView(mInstrumentation, mActivityRule, mTextView,
   7092                 spanDetails.mXPosInside, spanDetails.mYPosInside);
   7093         verify(spanDetails.mClickableSpan, times(1)).onClick(mTextView);
   7094     }
   7095 
   7096     @Test
   7097     public void testClickableSpanOnClickDoubleTapInside() throws Throwable {
   7098         ClickableSpanTestDetails spanDetails = prepareAndRetrieveClickableSpanDetails();
   7099         CtsTouchUtils.emulateDoubleTapOnView(mInstrumentation, mActivityRule, mTextView,
   7100                 spanDetails.mXPosInside, spanDetails.mYPosInside);
   7101         verify(spanDetails.mClickableSpan, times(2)).onClick(mTextView);
   7102     }
   7103 
   7104     @Test
   7105     public void testClickableSpanOnClickSingleTapOutside() throws Throwable {
   7106         ClickableSpanTestDetails spanDetails = prepareAndRetrieveClickableSpanDetails();
   7107         CtsTouchUtils.emulateTapOnView(mInstrumentation, mActivityRule, mTextView,
   7108                 spanDetails.mXPosOutside, spanDetails.mYPosOutside);
   7109         verify(spanDetails.mClickableSpan, never()).onClick(mTextView);
   7110     }
   7111 
   7112     @Test
   7113     public void testClickableSpanOnClickDragOutside() throws Throwable {
   7114         ClickableSpanTestDetails spanDetails = prepareAndRetrieveClickableSpanDetails();
   7115         final int[] viewOnScreenXY = new int[2];
   7116         mTextView.getLocationOnScreen(viewOnScreenXY);
   7117 
   7118         SparseArray<Point> swipeCoordinates = new SparseArray<>();
   7119         swipeCoordinates.put(0, new Point(viewOnScreenXY[0] + spanDetails.mXPosOutside,
   7120                 viewOnScreenXY[1] + spanDetails.mYPosOutside));
   7121         swipeCoordinates.put(1, new Point(viewOnScreenXY[0] + spanDetails.mXPosOutside + 50,
   7122                 viewOnScreenXY[1] + spanDetails.mYPosOutside + 50));
   7123         CtsTouchUtils.emulateDragGesture(mInstrumentation, mActivityRule, swipeCoordinates);
   7124         verify(spanDetails.mClickableSpan, never()).onClick(mTextView);
   7125     }
   7126 
   7127     @UiThreadTest
   7128     @Test
   7129     public void testOnInitializeA11yNodeInfo_populatesHintTextProperly() {
   7130         final TextView textView = new TextView(mActivity);
   7131         textView.setText("", BufferType.EDITABLE);
   7132         final String hintText = "Hint text";
   7133         textView.setHint(hintText);
   7134         AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
   7135         textView.onInitializeAccessibilityNodeInfo(info);
   7136         assertTrue("Hint text flag set incorrectly for accessibility", info.isShowingHintText());
   7137         assertTrue("Hint text not showing as accessibility text",
   7138                 TextUtils.equals(hintText, info.getText()));
   7139         assertTrue("Hint text not provided to accessibility",
   7140                 TextUtils.equals(hintText, info.getHintText()));
   7141 
   7142         final String nonHintText = "Something else";
   7143         textView.setText(nonHintText, BufferType.EDITABLE);
   7144         textView.onInitializeAccessibilityNodeInfo(info);
   7145         assertFalse("Hint text flag set incorrectly for accessibility", info.isShowingHintText());
   7146         assertTrue("Text not provided to accessibility",
   7147                 TextUtils.equals(nonHintText, info.getText()));
   7148         assertTrue("Hint text not provided to accessibility",
   7149                 TextUtils.equals(hintText, info.getHintText()));
   7150     }
   7151 
   7152     @Test
   7153     public void testAutosizeWithMaxLines_shouldNotThrowException() throws Throwable {
   7154         // the layout contains an instance of CustomTextViewWithTransformationMethod
   7155         final TextView textView = (TextView) mActivity.getLayoutInflater()
   7156                 .inflate(R.layout.textview_autosize_maxlines, null);
   7157         assertTrue(textView instanceof CustomTextViewWithTransformationMethod);
   7158         assertEquals(1, textView.getMaxLines());
   7159         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM, textView.getAutoSizeTextType());
   7160         assertTrue(textView.getTransformationMethod() instanceof SingleLineTransformationMethod);
   7161     }
   7162 
   7163     public static class CustomTextViewWithTransformationMethod extends TextView {
   7164         public CustomTextViewWithTransformationMethod(Context context) {
   7165             super(context);
   7166             init();
   7167         }
   7168 
   7169         public CustomTextViewWithTransformationMethod(Context context,
   7170                 @Nullable AttributeSet attrs) {
   7171             super(context, attrs);
   7172             init();
   7173         }
   7174 
   7175         public CustomTextViewWithTransformationMethod(Context context,
   7176                 @Nullable AttributeSet attrs, int defStyleAttr) {
   7177             super(context, attrs, defStyleAttr);
   7178             init();
   7179         }
   7180 
   7181         public CustomTextViewWithTransformationMethod(Context context,
   7182                 @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
   7183             super(context, attrs, defStyleAttr, defStyleRes);
   7184             init();
   7185         }
   7186 
   7187         private void init() {
   7188             setTransformationMethod(new SingleLineTransformationMethod());
   7189         }
   7190     }
   7191 
   7192     @Test
   7193     public void testAutoSizeCallers_setText() throws Throwable {
   7194         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7195                 R.id.textview_autosize_uniform, false);
   7196 
   7197         // Configure layout params and auto-size both in pixels to dodge flakiness on different
   7198         // devices.
   7199         final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
   7200                 200, 200);
   7201         mActivityRule.runOnUiThread(() -> {
   7202             autoSizeTextView.setLayoutParams(layoutParams);
   7203             autoSizeTextView.setAutoSizeTextTypeUniformWithConfiguration(
   7204                     1, 5000, 1, TypedValue.COMPLEX_UNIT_PX);
   7205         });
   7206         mInstrumentation.waitForIdleSync();
   7207 
   7208         final String initialText = "13characters ";
   7209         final StringBuilder textToSet = new StringBuilder().append(initialText);
   7210         float initialSize = 0;
   7211 
   7212         // As we add characters the text size shrinks.
   7213         for (int i = 0; i < 10; i++) {
   7214             mActivityRule.runOnUiThread(() ->
   7215                     autoSizeTextView.setText(textToSet.toString()));
   7216             mInstrumentation.waitForIdleSync();
   7217             float expectedLargerSize = autoSizeTextView.getTextSize();
   7218             if (i == 0) {
   7219                 initialSize = expectedLargerSize;
   7220             }
   7221 
   7222             textToSet.append(initialText);
   7223             mActivityRule.runOnUiThread(() ->
   7224                     autoSizeTextView.setText(textToSet.toString()));
   7225             mInstrumentation.waitForIdleSync();
   7226 
   7227             assertTrue(expectedLargerSize >= autoSizeTextView.getTextSize());
   7228         }
   7229         assertTrue(initialSize > autoSizeTextView.getTextSize());
   7230 
   7231         initialSize = Integer.MAX_VALUE;
   7232         // As we remove characters the text size expands.
   7233         for (int i = 9; i >= 0; i--) {
   7234             mActivityRule.runOnUiThread(() ->
   7235                     autoSizeTextView.setText(textToSet.toString()));
   7236             mInstrumentation.waitForIdleSync();
   7237             float expectedSmallerSize = autoSizeTextView.getTextSize();
   7238             if (i == 9) {
   7239                 initialSize = expectedSmallerSize;
   7240             }
   7241 
   7242             textToSet.replace((textToSet.length() - initialText.length()), textToSet.length(), "");
   7243             mActivityRule.runOnUiThread(() ->
   7244                     autoSizeTextView.setText(textToSet.toString()));
   7245             mInstrumentation.waitForIdleSync();
   7246 
   7247             assertTrue(autoSizeTextView.getTextSize() >= expectedSmallerSize);
   7248         }
   7249         assertTrue(autoSizeTextView.getTextSize() > initialSize);
   7250     }
   7251 
   7252     @Test
   7253     public void testAutoSize_setEllipsize() throws Throwable {
   7254         final TextView textView = (TextView) mActivity.findViewById(
   7255                 R.id.textview_autosize_uniform_predef_sizes);
   7256         final int initialAutoSizeType = textView.getAutoSizeTextType();
   7257         final int initialMinTextSize = textView.getAutoSizeMinTextSize();
   7258         final int initialMaxTextSize = textView.getAutoSizeMaxTextSize();
   7259         final int initialAutoSizeGranularity = textView.getAutoSizeStepGranularity();
   7260         final int initialSizes = textView.getAutoSizeTextAvailableSizes().length;
   7261 
   7262         assertEquals(null, textView.getEllipsize());
   7263         // Verify styled attributes.
   7264         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM, initialAutoSizeType);
   7265         assertNotEquals(-1, initialMinTextSize);
   7266         assertNotEquals(-1, initialMaxTextSize);
   7267         // Because this TextView has been configured to use predefined sizes.
   7268         assertEquals(-1, initialAutoSizeGranularity);
   7269         assertNotEquals(0, initialSizes);
   7270 
   7271         final TextUtils.TruncateAt newEllipsizeValue = TextUtils.TruncateAt.END;
   7272         mActivityRule.runOnUiThread(() ->
   7273                 textView.setEllipsize(newEllipsizeValue));
   7274         mInstrumentation.waitForIdleSync();
   7275         assertEquals(newEllipsizeValue, textView.getEllipsize());
   7276         // Beside the ellipsis no auto-size attribute has changed.
   7277         assertEquals(initialAutoSizeType, textView.getAutoSizeTextType());
   7278         assertEquals(initialMinTextSize, textView.getAutoSizeMinTextSize());
   7279         assertEquals(initialMaxTextSize, textView.getAutoSizeMaxTextSize());
   7280         assertEquals(initialAutoSizeGranularity, textView.getAutoSizeStepGranularity());
   7281         assertEquals(initialSizes, textView.getAutoSizeTextAvailableSizes().length);
   7282     }
   7283 
   7284     @Test
   7285     public void testEllipsize_setAutoSize() throws Throwable {
   7286         TextView textView = findTextView(R.id.textview_text);
   7287         final TextUtils.TruncateAt newEllipsizeValue = TextUtils.TruncateAt.END;
   7288         mActivityRule.runOnUiThread(() ->
   7289                 textView.setEllipsize(newEllipsizeValue));
   7290         mInstrumentation.waitForIdleSync();
   7291         assertEquals(newEllipsizeValue, textView.getEllipsize());
   7292         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, textView.getAutoSizeTextType());
   7293         assertEquals(-1, textView.getAutoSizeMinTextSize());
   7294         assertEquals(-1, textView.getAutoSizeMaxTextSize());
   7295         assertEquals(-1, textView.getAutoSizeStepGranularity());
   7296         assertEquals(0, textView.getAutoSizeTextAvailableSizes().length);
   7297 
   7298         mActivityRule.runOnUiThread(() ->
   7299                 textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM));
   7300         mInstrumentation.waitForIdleSync();
   7301         assertEquals(newEllipsizeValue, textView.getEllipsize());
   7302         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM, textView.getAutoSizeTextType());
   7303         // The auto-size defaults have been used.
   7304         assertNotEquals(-1, textView.getAutoSizeMinTextSize());
   7305         assertNotEquals(-1, textView.getAutoSizeMaxTextSize());
   7306         assertNotEquals(-1, textView.getAutoSizeStepGranularity());
   7307         assertNotEquals(0, textView.getAutoSizeTextAvailableSizes().length);
   7308     }
   7309 
   7310     @Test
   7311     public void testAutoSizeCallers_setTransformationMethod() throws Throwable {
   7312         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7313                 R.id.textview_autosize_uniform, false);
   7314         // Mock transformation method to return the duplicated input text in order to measure
   7315         // auto-sizing.
   7316         TransformationMethod duplicateTextTransformationMethod = mock(TransformationMethod.class);
   7317         when(duplicateTextTransformationMethod
   7318                 .getTransformation(any(CharSequence.class), any(View.class)))
   7319                 .thenAnswer(invocation -> {
   7320                     CharSequence source = (CharSequence) invocation.getArguments()[0];
   7321                     return new StringBuilder().append(source).append(source).toString();
   7322                 });
   7323 
   7324         mActivityRule.runOnUiThread(() ->
   7325                 autoSizeTextView.setTransformationMethod(null));
   7326         mInstrumentation.waitForIdleSync();
   7327         final float initialTextSize = autoSizeTextView.getTextSize();
   7328         mActivityRule.runOnUiThread(() ->
   7329                 autoSizeTextView.setTransformationMethod(duplicateTextTransformationMethod));
   7330         mInstrumentation.waitForIdleSync();
   7331 
   7332         assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
   7333     }
   7334 
   7335     @Test
   7336     public void testAutoSizeCallers_setCompoundDrawables() throws Throwable {
   7337         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7338                 R.id.textview_autosize_uniform, false);
   7339         final float initialTextSize = autoSizeTextView.getTextSize();
   7340         Drawable drawable = TestUtils.getDrawable(mActivity, R.drawable.red);
   7341         drawable.setBounds(0, 0, autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3);
   7342         mActivityRule.runOnUiThread(() ->
   7343                 autoSizeTextView.setCompoundDrawables(drawable, drawable, drawable, drawable));
   7344         mInstrumentation.waitForIdleSync();
   7345 
   7346         assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
   7347     }
   7348 
   7349     @Test
   7350     public void testAutoSizeCallers_setCompoundDrawablesRelative() throws Throwable {
   7351         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7352                 R.id.textview_autosize_uniform, false);
   7353         final float initialTextSize = autoSizeTextView.getTextSize();
   7354         Drawable drawable = TestUtils.getDrawable(mActivity, R.drawable.red);
   7355         drawable.setBounds(0, 0, autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3);
   7356         mActivityRule.runOnUiThread(() -> autoSizeTextView.setCompoundDrawablesRelative(
   7357                 drawable, drawable, drawable, drawable));
   7358         mInstrumentation.waitForIdleSync();
   7359 
   7360         assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
   7361     }
   7362 
   7363     @Test
   7364     public void testAutoSizeCallers_setCompoundDrawablePadding() throws Throwable {
   7365         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7366                 R.id.textview_autosize_uniform, false);
   7367         // Prepare a larger layout in order not to hit the min value easily.
   7368         mActivityRule.runOnUiThread(() -> {
   7369             autoSizeTextView.setWidth(autoSizeTextView.getWidth() * 2);
   7370             autoSizeTextView.setHeight(autoSizeTextView.getHeight() * 2);
   7371         });
   7372         mInstrumentation.waitForIdleSync();
   7373         // Setup the drawables before setting their padding in order to modify the available
   7374         // space and trigger a resize.
   7375         Drawable drawable = TestUtils.getDrawable(mActivity, R.drawable.red);
   7376         drawable.setBounds(0, 0, autoSizeTextView.getWidth() / 4, autoSizeTextView.getHeight() / 4);
   7377         mActivityRule.runOnUiThread(() -> autoSizeTextView.setCompoundDrawables(
   7378                 drawable, drawable, drawable, drawable));
   7379         mInstrumentation.waitForIdleSync();
   7380         final float initialTextSize = autoSizeTextView.getTextSize();
   7381         mActivityRule.runOnUiThread(() -> autoSizeTextView.setCompoundDrawablePadding(
   7382                 autoSizeTextView.getCompoundDrawablePadding() + 10));
   7383         mInstrumentation.waitForIdleSync();
   7384 
   7385         assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
   7386     }
   7387 
   7388     @Test
   7389     public void testAutoSizeCallers_setPadding() throws Throwable {
   7390         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7391                 R.id.textview_autosize_uniform, false);
   7392         final float initialTextSize = autoSizeTextView.getTextSize();
   7393         mActivityRule.runOnUiThread(() -> autoSizeTextView.setPadding(
   7394                 autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3,
   7395                 autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3));
   7396         mInstrumentation.waitForIdleSync();
   7397 
   7398         assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
   7399     }
   7400 
   7401     @Test
   7402     public void testAutoSizeCallers_setPaddingRelative() throws Throwable {
   7403         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7404                 R.id.textview_autosize_uniform, false);
   7405         final float initialTextSize = autoSizeTextView.getTextSize();
   7406 
   7407         mActivityRule.runOnUiThread(() -> autoSizeTextView.setPaddingRelative(
   7408                 autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3,
   7409                 autoSizeTextView.getWidth() / 3, autoSizeTextView.getHeight() / 3));
   7410         mInstrumentation.waitForIdleSync();
   7411 
   7412         assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
   7413     }
   7414 
   7415     @Test
   7416     public void testAutoSizeCallers_setTextScaleX() throws Throwable {
   7417         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7418                 R.id.textview_autosize_uniform, false);
   7419         final float initialTextSize = autoSizeTextView.getTextSize();
   7420 
   7421         mActivityRule.runOnUiThread(() ->
   7422                 autoSizeTextView.setTextScaleX(autoSizeTextView.getTextScaleX() * 4.5f));
   7423         mInstrumentation.waitForIdleSync();
   7424         final float changedTextSize = autoSizeTextView.getTextSize();
   7425 
   7426         assertTrue(changedTextSize < initialTextSize);
   7427 
   7428         mActivityRule.runOnUiThread(() ->
   7429                 autoSizeTextView.setTextScaleX(autoSizeTextView.getTextScaleX()));
   7430         mInstrumentation.waitForIdleSync();
   7431 
   7432         assertEquals(changedTextSize, autoSizeTextView.getTextSize(), 0f);
   7433     }
   7434 
   7435     @Test
   7436     public void testAutoSizeCallers_setTypeface() throws Throwable {
   7437         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7438                 R.id.textview_autosize_uniform, false);
   7439         mActivityRule.runOnUiThread(() ->
   7440                 autoSizeTextView.setText("The typeface change needs a bit more text then "
   7441                         + "the default used for this batch of tests in order to get to resize text."
   7442                         + " The resize function is always called but even with different typefaces "
   7443                         + "there may not be a need to resize text because it just fits. The longer "
   7444                         + "the text, the higher the chance for a resize. And here is yet another "
   7445                         + "sentence to make sure this test is not flaky. Not flaky at all."));
   7446         mInstrumentation.waitForIdleSync();
   7447         final float initialTextSize = autoSizeTextView.getTextSize();
   7448 
   7449         mActivityRule.runOnUiThread(() -> {
   7450             Typeface differentTypeface = Typeface.MONOSPACE;
   7451             if (autoSizeTextView.getTypeface() == Typeface.MONOSPACE) {
   7452                 differentTypeface = Typeface.SANS_SERIF;
   7453             }
   7454             autoSizeTextView.setTypeface(differentTypeface);
   7455         });
   7456         mInstrumentation.waitForIdleSync();
   7457         final float changedTextSize = autoSizeTextView.getTextSize();
   7458 
   7459         // Don't really know if it is larger or smaller (depends on the typeface chosen above),
   7460         // but it should definitely have changed.
   7461         assertNotEquals(initialTextSize, changedTextSize, 0f);
   7462 
   7463         mActivityRule.runOnUiThread(() ->
   7464                 autoSizeTextView.setTypeface(autoSizeTextView.getTypeface()));
   7465         mInstrumentation.waitForIdleSync();
   7466 
   7467         assertEquals(changedTextSize, autoSizeTextView.getTextSize(), 0f);
   7468     }
   7469 
   7470     @Test
   7471     public void testAutoSizeCallers_setLetterSpacing() throws Throwable {
   7472         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7473                 R.id.textview_autosize_uniform, false);
   7474         final float initialTextSize = autoSizeTextView.getTextSize();
   7475 
   7476         mActivityRule.runOnUiThread(() ->
   7477                 // getLetterSpacing() could return 0, make sure there is enough of a difference to
   7478                 // trigger auto-size.
   7479                 autoSizeTextView.setLetterSpacing(
   7480                         autoSizeTextView.getLetterSpacing() * 1.5f + 4.5f));
   7481         mInstrumentation.waitForIdleSync();
   7482         final float changedTextSize = autoSizeTextView.getTextSize();
   7483 
   7484         assertTrue(changedTextSize < initialTextSize);
   7485 
   7486         mActivityRule.runOnUiThread(() ->
   7487                 autoSizeTextView.setLetterSpacing(autoSizeTextView.getLetterSpacing()));
   7488         mInstrumentation.waitForIdleSync();
   7489 
   7490         assertEquals(changedTextSize, autoSizeTextView.getTextSize(), 0f);
   7491     }
   7492 
   7493     @Test
   7494     public void testAutoSizeCallers_setHorizontallyScrolling() throws Throwable {
   7495         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7496                 R.id.textview_autosize_uniform, false);
   7497         // Verify that we do not have horizontal scrolling turned on.
   7498         assertTrue(!autoSizeTextView.getHorizontallyScrolling());
   7499 
   7500         final float initialTextSize = autoSizeTextView.getTextSize();
   7501         mActivityRule.runOnUiThread(() -> autoSizeTextView.setHorizontallyScrolling(true));
   7502         mInstrumentation.waitForIdleSync();
   7503         assertTrue(autoSizeTextView.getTextSize() > initialTextSize);
   7504 
   7505         mActivityRule.runOnUiThread(() -> autoSizeTextView.setHorizontallyScrolling(false));
   7506         mInstrumentation.waitForIdleSync();
   7507         assertEquals(initialTextSize, autoSizeTextView.getTextSize(), 0f);
   7508     }
   7509 
   7510     @Test
   7511     public void testAutoSizeCallers_setMaxLines() throws Throwable {
   7512         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7513                 R.id.textview_autosize_uniform, false);
   7514         // Configure layout params and auto-size both in pixels to dodge flakiness on different
   7515         // devices.
   7516         final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
   7517                 200, 200);
   7518         final String text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\nnine\nten";
   7519         mActivityRule.runOnUiThread(() -> {
   7520             autoSizeTextView.setLayoutParams(layoutParams);
   7521             autoSizeTextView.setAutoSizeTextTypeUniformWithConfiguration(
   7522                     1 /* autoSizeMinTextSize */,
   7523                     5000 /* autoSizeMaxTextSize */,
   7524                     1 /* autoSizeStepGranularity */,
   7525                     TypedValue.COMPLEX_UNIT_PX);
   7526             autoSizeTextView.setText(text);
   7527         });
   7528         mInstrumentation.waitForIdleSync();
   7529 
   7530         float initialSize = 0;
   7531         for (int i = 1; i < 10; i++) {
   7532             final int maxLines = i;
   7533             mActivityRule.runOnUiThread(() -> autoSizeTextView.setMaxLines(maxLines));
   7534             mInstrumentation.waitForIdleSync();
   7535             float expectedSmallerSize = autoSizeTextView.getTextSize();
   7536             if (i == 1) {
   7537                 initialSize = expectedSmallerSize;
   7538             }
   7539 
   7540             mActivityRule.runOnUiThread(() -> autoSizeTextView.setMaxLines(maxLines + 1));
   7541             mInstrumentation.waitForIdleSync();
   7542             assertTrue(expectedSmallerSize <= autoSizeTextView.getTextSize());
   7543         }
   7544         assertTrue(initialSize < autoSizeTextView.getTextSize());
   7545 
   7546         initialSize = Integer.MAX_VALUE;
   7547         for (int i = 10; i > 1; i--) {
   7548             final int maxLines = i;
   7549             mActivityRule.runOnUiThread(() -> autoSizeTextView.setMaxLines(maxLines));
   7550             mInstrumentation.waitForIdleSync();
   7551             float expectedLargerSize = autoSizeTextView.getTextSize();
   7552             if (i == 10) {
   7553                 initialSize = expectedLargerSize;
   7554             }
   7555 
   7556             mActivityRule.runOnUiThread(() -> autoSizeTextView.setMaxLines(maxLines - 1));
   7557             mInstrumentation.waitForIdleSync();
   7558             assertTrue(expectedLargerSize >= autoSizeTextView.getTextSize());
   7559         }
   7560         assertTrue(initialSize > autoSizeTextView.getTextSize());
   7561     }
   7562 
   7563     @Test
   7564     public void testAutoSizeCallers_setMaxHeight() throws Throwable {
   7565         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7566                 R.id.textview_autosize_uniform, true);
   7567         // Do not force exact height only.
   7568         final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
   7569                 200,
   7570                 LinearLayout.LayoutParams.WRAP_CONTENT);
   7571         mActivityRule.runOnUiThread(() -> autoSizeTextView.setLayoutParams(layoutParams));
   7572         mInstrumentation.waitForIdleSync();
   7573         final float initialTextSize = autoSizeTextView.getTextSize();
   7574         mActivityRule.runOnUiThread(() -> autoSizeTextView.setMaxHeight(
   7575                 autoSizeTextView.getHeight() / 4));
   7576         mInstrumentation.waitForIdleSync();
   7577 
   7578         assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
   7579     }
   7580 
   7581     @Test
   7582     public void testAutoSizeCallers_setHeight() throws Throwable {
   7583         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7584                 R.id.textview_autosize_uniform, true);
   7585         // Do not force exact height only.
   7586         final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
   7587                 200,
   7588                 LinearLayout.LayoutParams.WRAP_CONTENT);
   7589         mActivityRule.runOnUiThread(() -> autoSizeTextView.setLayoutParams(layoutParams));
   7590         mInstrumentation.waitForIdleSync();
   7591         final float initialTextSize = autoSizeTextView.getTextSize();
   7592         mActivityRule.runOnUiThread(() -> autoSizeTextView.setHeight(
   7593                 autoSizeTextView.getHeight() / 4));
   7594         mInstrumentation.waitForIdleSync();
   7595 
   7596         assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
   7597     }
   7598 
   7599     @Test
   7600     public void testAutoSizeCallers_setLines() throws Throwable {
   7601         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7602                 R.id.textview_autosize_uniform, false);
   7603         final float initialTextSize = autoSizeTextView.getTextSize();
   7604         mActivityRule.runOnUiThread(() -> autoSizeTextView.setLines(1));
   7605         mInstrumentation.waitForIdleSync();
   7606 
   7607         assertTrue(autoSizeTextView.getTextSize() < initialTextSize);
   7608     }
   7609 
   7610     @Test
   7611     public void testAutoSizeCallers_setMaxWidth() throws Throwable {
   7612         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7613                 R.id.textview_autosize_uniform, true);
   7614         // Do not force exact width only.
   7615         final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
   7616                 LinearLayout.LayoutParams.WRAP_CONTENT,
   7617                 200);
   7618         mActivityRule.runOnUiThread(() -> autoSizeTextView.setLayoutParams(layoutParams));
   7619         mInstrumentation.waitForIdleSync();
   7620         final float initialTextSize = autoSizeTextView.getTextSize();
   7621         mActivityRule.runOnUiThread(() -> autoSizeTextView.setMaxWidth(
   7622                 autoSizeTextView.getWidth() / 4));
   7623         mInstrumentation.waitForIdleSync();
   7624 
   7625         assertTrue(autoSizeTextView.getTextSize() != initialTextSize);
   7626     }
   7627 
   7628     @Test
   7629     public void testAutoSizeCallers_setWidth() throws Throwable {
   7630         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7631                 R.id.textview_autosize_uniform, true);
   7632         // Do not force exact width only.
   7633         final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
   7634                 LinearLayout.LayoutParams.WRAP_CONTENT,
   7635                 200);
   7636         mActivityRule.runOnUiThread(() -> autoSizeTextView.setLayoutParams(layoutParams));
   7637         mInstrumentation.waitForIdleSync();
   7638 
   7639         final float initialTextSize = autoSizeTextView.getTextSize();
   7640         mActivityRule.runOnUiThread(() -> autoSizeTextView.setWidth(
   7641                 autoSizeTextView.getWidth() / 4));
   7642         mInstrumentation.waitForIdleSync();
   7643 
   7644         assertTrue(autoSizeTextView.getTextSize() != initialTextSize);
   7645     }
   7646 
   7647     @Test
   7648     public void testAutoSizeCallers_setLineSpacing() throws Throwable {
   7649         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7650                 R.id.textview_autosize_uniform, false);
   7651         final float initialTextSize = autoSizeTextView.getTextSize();
   7652 
   7653         mActivityRule.runOnUiThread(() -> autoSizeTextView.setLineSpacing(
   7654                 autoSizeTextView.getLineSpacingExtra() * 4,
   7655                 autoSizeTextView.getLineSpacingMultiplier() * 4));
   7656         mInstrumentation.waitForIdleSync();
   7657         final float changedTextSize = autoSizeTextView.getTextSize();
   7658 
   7659         assertTrue(changedTextSize < initialTextSize);
   7660 
   7661         mActivityRule.runOnUiThread(() -> autoSizeTextView.setLineSpacing(
   7662                 autoSizeTextView.getLineSpacingExtra(),
   7663                 autoSizeTextView.getLineSpacingMultiplier()));
   7664         mInstrumentation.waitForIdleSync();
   7665 
   7666         assertEquals(changedTextSize, autoSizeTextView.getTextSize(), 0f);
   7667     }
   7668 
   7669     @Test
   7670     public void testAutoSizeCallers_setTextSizeIsNoOp() throws Throwable {
   7671         final TextView autoSizeTextView = prepareAndRetrieveAutoSizeTestData(
   7672                 R.id.textview_autosize_uniform, false);
   7673         final float initialTextSize = autoSizeTextView.getTextSize();
   7674 
   7675         mActivityRule.runOnUiThread(() -> autoSizeTextView.setTextSize(
   7676                 initialTextSize + 123f));
   7677         mInstrumentation.waitForIdleSync();
   7678 
   7679         assertEquals(initialTextSize, autoSizeTextView.getTextSize(), 0f);
   7680     }
   7681 
   7682     @Test
   7683     public void testAutoSizeCallers_setHeightForOneLineText() throws Throwable {
   7684         final TextView autoSizeTextView = (TextView) mActivity.findViewById(
   7685                 R.id.textview_autosize_basic);
   7686         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM, autoSizeTextView.getAutoSizeTextType());
   7687         final float initialTextSize = autoSizeTextView.getTextSize();
   7688         mActivityRule.runOnUiThread(() -> autoSizeTextView.setHeight(
   7689                 autoSizeTextView.getHeight() * 3));
   7690         mInstrumentation.waitForIdleSync();
   7691 
   7692         assertTrue(autoSizeTextView.getTextSize() > initialTextSize);
   7693     }
   7694 
   7695     @Test
   7696     public void testAutoSizeUniform_obtainStyledAttributes() {
   7697         DisplayMetrics metrics = mActivity.getResources().getDisplayMetrics();
   7698         TextView autoSizeTextViewUniform = (TextView) mActivity.findViewById(
   7699                 R.id.textview_autosize_uniform);
   7700 
   7701         // The size has been set to 50dp in the layout but this being an AUTO_SIZE_TEXT_TYPE_UNIFORM
   7702         // TextView, the size is considered max size thus the value returned by getSize() in this
   7703         // case should be lower than the one set (given that there is not much available space and
   7704         // the font size is very high). In theory the values could be equal for a different TextView
   7705         // configuration.
   7706         final float sizeSetInPixels = TypedValue.applyDimension(
   7707                 TypedValue.COMPLEX_UNIT_DIP, 50f, metrics);
   7708         assertTrue(autoSizeTextViewUniform.getTextSize() < sizeSetInPixels);
   7709     }
   7710 
   7711     @Test
   7712     public void testAutoSizeUniform_obtainStyledAttributesUsingPredefinedSizes() {
   7713         DisplayMetrics m = mActivity.getResources().getDisplayMetrics();
   7714         final TextView autoSizeTextViewUniform = (TextView) mActivity.findViewById(
   7715                 R.id.textview_autosize_uniform_predef_sizes);
   7716 
   7717         // In arrays.xml predefined the step sizes as: 10px, 10dp, 10sp, 10pt, 10in and 10mm.
   7718         // TypedValue can not use the math library and instead naively ceils the value by adding
   7719         // 0.5f when obtaining styled attributes. Check TypedValue#complexToDimensionPixelSize(...)
   7720         int[] expectedSizesInPx = new int[] {
   7721                 (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 10f, m)),
   7722                 (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10f, m)),
   7723                 (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10f, m)),
   7724                 (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PT, 10f, m)),
   7725                 (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_IN, 10f, m)),
   7726                 (int) (0.5f + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 10f, m))};
   7727 
   7728         boolean containsValueFromExpectedSizes = false;
   7729         int textSize = (int) autoSizeTextViewUniform.getTextSize();
   7730         for (int i = 0; i < expectedSizesInPx.length; i++) {
   7731             if (expectedSizesInPx[i] == textSize) {
   7732                 containsValueFromExpectedSizes = true;
   7733                 break;
   7734             }
   7735         }
   7736         assertTrue(containsValueFromExpectedSizes);
   7737     }
   7738 
   7739     @Test
   7740     public void testAutoSizeUniform_obtainStyledAttributesPredefinedSizesFiltering() {
   7741         TextView autoSizeTextViewUniform = (TextView) mActivity.findViewById(
   7742                 R.id.textview_autosize_uniform_predef_sizes_redundant_values);
   7743 
   7744         // In arrays.xml predefined the step sizes as: 40px, 10px, 10px, 10px, 0dp.
   7745         final int[] expectedSizes = new int[] {10, 40};
   7746         assertArrayEquals(expectedSizes, autoSizeTextViewUniform.getAutoSizeTextAvailableSizes());
   7747     }
   7748 
   7749     @Test
   7750     public void testAutoSizeUniform_predefinedSizesFilteringAndSorting() throws Throwable {
   7751         mTextView = findTextView(R.id.textview_text);
   7752         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, mTextView.getAutoSizeTextType());
   7753 
   7754         final int[] predefinedSizes = new int[] {400, 0, 10, 40, 10, 10, 0, 0};
   7755         mActivityRule.runOnUiThread(() -> mTextView.setAutoSizeTextTypeUniformWithPresetSizes(
   7756                 predefinedSizes, TypedValue.COMPLEX_UNIT_PX));
   7757         mInstrumentation.waitForIdleSync();
   7758         assertArrayEquals(new int[] {10, 40, 400}, mTextView.getAutoSizeTextAvailableSizes());
   7759     }
   7760 
   7761     @Test(expected = NullPointerException.class)
   7762     public void testAutoSizeUniform_predefinedSizesNullArray() throws Throwable {
   7763         mTextView = findTextView(R.id.textview_text);
   7764         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, mTextView.getAutoSizeTextType());
   7765 
   7766         final int[] predefinedSizes = null;
   7767         mActivityRule.runOnUiThread(() -> mTextView.setAutoSizeTextTypeUniformWithPresetSizes(
   7768                 predefinedSizes, TypedValue.COMPLEX_UNIT_PX));
   7769         mInstrumentation.waitForIdleSync();
   7770     }
   7771 
   7772     @Test
   7773     public void testAutoSizeUniform_predefinedSizesEmptyArray() throws Throwable {
   7774         mTextView = findTextView(R.id.textview_text);
   7775         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, mTextView.getAutoSizeTextType());
   7776 
   7777         mActivityRule.runOnUiThread(() ->
   7778                 mTextView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM));
   7779         mInstrumentation.waitForIdleSync();
   7780 
   7781         final int[] defaultSizes = mTextView.getAutoSizeTextAvailableSizes();
   7782         assertNotNull(defaultSizes);
   7783         assertTrue(defaultSizes.length > 0);
   7784 
   7785         final int[] predefinedSizes = new int[0];
   7786         mActivityRule.runOnUiThread(() -> mTextView.setAutoSizeTextTypeUniformWithPresetSizes(
   7787                 predefinedSizes, TypedValue.COMPLEX_UNIT_PX));
   7788         mInstrumentation.waitForIdleSync();
   7789 
   7790         final int[] newSizes = mTextView.getAutoSizeTextAvailableSizes();
   7791         assertNotNull(defaultSizes);
   7792         assertArrayEquals(defaultSizes, newSizes);
   7793     }
   7794 
   7795     @Test
   7796     public void testAutoSizeUniform_buildsSizes() throws Throwable {
   7797         TextView autoSizeTextViewUniform = (TextView) mActivity.findViewById(
   7798                 R.id.textview_autosize_uniform);
   7799 
   7800         // Verify that the interval limits are both included.
   7801         mActivityRule.runOnUiThread(() -> autoSizeTextViewUniform
   7802                 .setAutoSizeTextTypeUniformWithConfiguration(10, 20, 2,
   7803                         TypedValue.COMPLEX_UNIT_PX));
   7804         mInstrumentation.waitForIdleSync();
   7805         assertArrayEquals(
   7806                 new int[] {10, 12, 14, 16, 18, 20},
   7807                 autoSizeTextViewUniform.getAutoSizeTextAvailableSizes());
   7808 
   7809         mActivityRule.runOnUiThread(() -> autoSizeTextViewUniform
   7810                 .setAutoSizeTextTypeUniformWithConfiguration(
   7811                         autoSizeTextViewUniform.getAutoSizeMinTextSize(),
   7812                         19,
   7813                         autoSizeTextViewUniform.getAutoSizeStepGranularity(),
   7814                         TypedValue.COMPLEX_UNIT_PX));
   7815         mInstrumentation.waitForIdleSync();
   7816         assertArrayEquals(
   7817                 new int[] {10, 12, 14, 16, 18},
   7818                 autoSizeTextViewUniform.getAutoSizeTextAvailableSizes());
   7819 
   7820         mActivityRule.runOnUiThread(() -> autoSizeTextViewUniform
   7821                 .setAutoSizeTextTypeUniformWithConfiguration(
   7822                         autoSizeTextViewUniform.getAutoSizeMinTextSize(),
   7823                         21,
   7824                         autoSizeTextViewUniform.getAutoSizeStepGranularity(),
   7825                         TypedValue.COMPLEX_UNIT_PX));
   7826         mInstrumentation.waitForIdleSync();
   7827         assertArrayEquals(
   7828                 new int[] {10, 12, 14, 16, 18, 20},
   7829                 autoSizeTextViewUniform.getAutoSizeTextAvailableSizes());
   7830     }
   7831 
   7832     @Test
   7833     public void testAutoSizeUniform_getSetAutoSizeTextDefaults() {
   7834         final TextView textView = new TextView(mActivity);
   7835         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, textView.getAutoSizeTextType());
   7836         // Min/Max/Granularity values for auto-sizing are 0 because they are not used.
   7837         assertEquals(-1, textView.getAutoSizeMinTextSize());
   7838         assertEquals(-1, textView.getAutoSizeMaxTextSize());
   7839         assertEquals(-1, textView.getAutoSizeStepGranularity());
   7840 
   7841         textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM);
   7842         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM, textView.getAutoSizeTextType());
   7843         // Min/Max default values for auto-sizing XY have been loaded.
   7844         final int minSize = textView.getAutoSizeMinTextSize();
   7845         final int maxSize = textView.getAutoSizeMaxTextSize();
   7846         assertTrue(0 < minSize);
   7847         assertTrue(minSize < maxSize);
   7848         assertNotEquals(0, textView.getAutoSizeStepGranularity());
   7849 
   7850         textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_NONE);
   7851         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, textView.getAutoSizeTextType());
   7852         // Min/Max values for auto-sizing XY have been cleared.
   7853         assertEquals(-1, textView.getAutoSizeMinTextSize());
   7854         assertEquals(-1, textView.getAutoSizeMaxTextSize());
   7855         assertEquals(-1, textView.getAutoSizeStepGranularity());
   7856     }
   7857 
   7858     @Test
   7859     public void testAutoSizeUniform_getSetAutoSizeStepGranularity() {
   7860         final TextView textView = new TextView(mActivity);
   7861         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, textView.getAutoSizeTextType());
   7862         final int initialValue = -1;
   7863         assertEquals(initialValue, textView.getAutoSizeStepGranularity());
   7864 
   7865         textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM);
   7866         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM, textView.getAutoSizeTextType());
   7867         final int defaultValue = 1; // 1px.
   7868         // If the auto-size type is AUTO_SIZE_TEXT_TYPE_UNIFORM then it means textView went through
   7869         // the auto-size setup and given that 0 is an invalid value it changed it to the default.
   7870         assertEquals(defaultValue, textView.getAutoSizeStepGranularity());
   7871 
   7872         final int newValue = 33;
   7873         textView.setAutoSizeTextTypeUniformWithConfiguration(
   7874                 textView.getAutoSizeMinTextSize(),
   7875                 textView.getAutoSizeMaxTextSize(),
   7876                 newValue,
   7877                 TypedValue.COMPLEX_UNIT_PX);
   7878         assertEquals(newValue, textView.getAutoSizeStepGranularity());
   7879     }
   7880 
   7881     @Test
   7882     public void testAutoSizeUniform_getSetAutoSizeMinTextSize() {
   7883         final TextView textView = new TextView(mActivity);
   7884         textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM);
   7885         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM, textView.getAutoSizeTextType());
   7886         final int minSize = textView.getAutoSizeMinTextSize();
   7887         assertNotEquals(0, minSize);
   7888         final int maxSize = textView.getAutoSizeMaxTextSize();
   7889         assertNotEquals(0, maxSize);
   7890 
   7891         // This is just a test check to verify the next assertions. If this fails it is a problem
   7892         // of this test setup (we need at least 2 units).
   7893         assertTrue((maxSize - minSize) > 1);
   7894         final int newMinSize = maxSize - 1;
   7895         textView.setAutoSizeTextTypeUniformWithConfiguration(
   7896                 newMinSize,
   7897                 textView.getAutoSizeMaxTextSize(),
   7898                 textView.getAutoSizeStepGranularity(),
   7899                 TypedValue.COMPLEX_UNIT_PX);
   7900 
   7901         assertEquals(newMinSize, textView.getAutoSizeMinTextSize());
   7902         // Max size has not changed.
   7903         assertEquals(maxSize, textView.getAutoSizeMaxTextSize());
   7904 
   7905         textView.setAutoSizeTextTypeUniformWithConfiguration(
   7906                 newMinSize,
   7907                 newMinSize + 10,
   7908                 textView.getAutoSizeStepGranularity(),
   7909                 TypedValue.COMPLEX_UNIT_SP);
   7910 
   7911         // It does not matter which unit has been used to set the min size, the getter always
   7912         // returns it in pixels.
   7913         assertEquals(Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, newMinSize,
   7914                 mActivity.getResources().getDisplayMetrics())), textView.getAutoSizeMinTextSize());
   7915     }
   7916 
   7917     @Test(expected = IllegalArgumentException.class)
   7918     public void testAutoSizeUniform_throwsException_whenMaxLessThanMin() {
   7919         final TextView textView = new TextView(mActivity);
   7920         textView.setAutoSizeTextTypeUniformWithConfiguration(
   7921                 10, 9, 1, TypedValue.COMPLEX_UNIT_SP);
   7922     }
   7923 
   7924     @Test(expected = IllegalArgumentException.class)
   7925     public void testAutoSizeUniform_throwsException_minLessThanZero() {
   7926         final TextView textView = new TextView(mActivity);
   7927         textView.setAutoSizeTextTypeUniformWithConfiguration(
   7928                 -1, 9, 1, TypedValue.COMPLEX_UNIT_SP);
   7929     }
   7930 
   7931     @Test(expected = IllegalArgumentException.class)
   7932     public void testAutoSizeUniform_throwsException_maxLessThanZero() {
   7933         final TextView textView = new TextView(mActivity);
   7934         textView.setAutoSizeTextTypeUniformWithConfiguration(
   7935                 10, -1, 1, TypedValue.COMPLEX_UNIT_SP);
   7936     }
   7937 
   7938     @Test(expected = IllegalArgumentException.class)
   7939     public void testAutoSizeUniform_throwsException_granularityLessThanZero() {
   7940         final TextView textView = new TextView(mActivity);
   7941         textView.setAutoSizeTextTypeUniformWithConfiguration(
   7942                 10, 20, -1, TypedValue.COMPLEX_UNIT_SP);
   7943     }
   7944 
   7945     @Test
   7946     public void testAutoSizeUniform_equivalentConfigurations() throws Throwable {
   7947         final DisplayMetrics dm = mActivity.getResources().getDisplayMetrics();
   7948         final int minTextSize = 10;
   7949         final int maxTextSize = 20;
   7950         final int granularity = 2;
   7951         final int unit = TypedValue.COMPLEX_UNIT_SP;
   7952 
   7953         final TextView granularityTextView = new TextView(mActivity);
   7954         granularityTextView.setAutoSizeTextTypeUniformWithConfiguration(
   7955                 minTextSize, maxTextSize, granularity, unit);
   7956 
   7957         final TextView presetTextView = new TextView(mActivity);
   7958         presetTextView.setAutoSizeTextTypeUniformWithPresetSizes(
   7959                 new int[] {minTextSize, 12, 14, 16, 18, maxTextSize}, unit);
   7960 
   7961         // The TextViews have been configured differently but the end result should be nearly
   7962         // identical.
   7963         final int expectedAutoSizeType = TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM;
   7964         assertEquals(expectedAutoSizeType, granularityTextView.getAutoSizeTextType());
   7965         assertEquals(expectedAutoSizeType, presetTextView.getAutoSizeTextType());
   7966 
   7967         final int expectedMinTextSizeInPx = Math.round(
   7968                 TypedValue.applyDimension(unit, minTextSize, dm));
   7969         assertEquals(expectedMinTextSizeInPx, granularityTextView.getAutoSizeMinTextSize());
   7970         assertEquals(expectedMinTextSizeInPx, presetTextView.getAutoSizeMinTextSize());
   7971 
   7972         final int expectedMaxTextSizeInPx = Math.round(
   7973                 TypedValue.applyDimension(unit, maxTextSize, dm));
   7974         assertEquals(expectedMaxTextSizeInPx, granularityTextView.getAutoSizeMaxTextSize());
   7975         assertEquals(expectedMaxTextSizeInPx, presetTextView.getAutoSizeMaxTextSize());
   7976 
   7977         // Configured with granularity.
   7978         assertEquals(Math.round(TypedValue.applyDimension(unit, granularity, dm)),
   7979                 granularityTextView.getAutoSizeStepGranularity());
   7980         // Configured with preset values, there is no granularity.
   7981         assertEquals(-1, presetTextView.getAutoSizeStepGranularity());
   7982 
   7983         // Both TextViews generate exactly the same sizes in pixels to choose from when auto-sizing.
   7984         assertArrayEquals("Expected the granularity and preset configured auto-sized "
   7985                 + "TextViews to have identical available sizes for auto-sizing."
   7986                 + "\ngranularity sizes: "
   7987                 + Arrays.toString(granularityTextView.getAutoSizeTextAvailableSizes())
   7988                 + "\npreset sizes: "
   7989                 + Arrays.toString(presetTextView.getAutoSizeTextAvailableSizes()),
   7990                 granularityTextView.getAutoSizeTextAvailableSizes(),
   7991                 presetTextView.getAutoSizeTextAvailableSizes());
   7992 
   7993         final String someText = "This is a string";
   7994         final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
   7995                 200, 200);
   7996         // Configure identically and attach to layout.
   7997         mActivityRule.runOnUiThread(() -> {
   7998             granularityTextView.setLayoutParams(layoutParams);
   7999             presetTextView.setLayoutParams(layoutParams);
   8000 
   8001             LinearLayout ll = mActivity.findViewById(R.id.layout_textviewtest);
   8002             ll.removeAllViews();
   8003             ll.addView(granularityTextView);
   8004             ll.addView(presetTextView);
   8005 
   8006             granularityTextView.setText(someText);
   8007             presetTextView.setText(someText);
   8008         });
   8009         mInstrumentation.waitForIdleSync();
   8010 
   8011         assertEquals(granularityTextView.getTextSize(), presetTextView.getTextSize(), 0f);
   8012     }
   8013 
   8014     @Test
   8015     public void testAutoSizeUniform_getSetAutoSizeMaxTextSize() {
   8016         final TextView textView = new TextView(mActivity);
   8017         textView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM);
   8018         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM, textView.getAutoSizeTextType());
   8019         final int minSize = textView.getAutoSizeMinTextSize();
   8020         assertNotEquals(0, minSize);
   8021         final int maxSize = textView.getAutoSizeMaxTextSize();
   8022         assertNotEquals(0, maxSize);
   8023 
   8024         final int newMaxSize = maxSize + 11;
   8025         textView.setAutoSizeTextTypeUniformWithConfiguration(
   8026                 textView.getAutoSizeMinTextSize(),
   8027                 newMaxSize,
   8028                 textView.getAutoSizeStepGranularity(),
   8029                 TypedValue.COMPLEX_UNIT_PX);
   8030 
   8031         assertEquals(newMaxSize, textView.getAutoSizeMaxTextSize());
   8032         // Min size has not changed.
   8033         assertEquals(minSize, textView.getAutoSizeMinTextSize());
   8034         textView.setAutoSizeTextTypeUniformWithConfiguration(
   8035                 textView.getAutoSizeMinTextSize(),
   8036                 newMaxSize,
   8037                 textView.getAutoSizeStepGranularity(),
   8038                 TypedValue.COMPLEX_UNIT_SP);
   8039         // It does not matter which unit has been used to set the max size, the getter always
   8040         // returns it in pixels.
   8041         assertEquals(Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, newMaxSize,
   8042                 mActivity.getResources().getDisplayMetrics())), textView.getAutoSizeMaxTextSize());
   8043     }
   8044 
   8045     @Test
   8046     public void testAutoSizeUniform_autoSizeCalledWhenTypeChanged() throws Throwable {
   8047         mTextView = findTextView(R.id.textview_text);
   8048         // Make sure we pick an already inflated non auto-sized text view.
   8049         assertEquals(TextView.AUTO_SIZE_TEXT_TYPE_NONE, mTextView.getAutoSizeTextType());
   8050         // Set the text size to a very low value in order to prepare for auto-size.
   8051         final int customTextSize = 3;
   8052         mActivityRule.runOnUiThread(() ->
   8053                 mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, customTextSize));
   8054         mInstrumentation.waitForIdleSync();
   8055         assertEquals(customTextSize, mTextView.getTextSize(), 0f);
   8056         mActivityRule.runOnUiThread(() ->
   8057                 mTextView.setAutoSizeTextTypeWithDefaults(TextView.AUTO_SIZE_TEXT_TYPE_UNIFORM));
   8058         mInstrumentation.waitForIdleSync();
   8059         // The size of the text should have changed.
   8060         assertNotEquals(customTextSize, mTextView.getTextSize(), 0f);
   8061     }
   8062 
   8063     @Test
   8064     public void testSmartSelection() throws Throwable {
   8065         mTextView = findTextView(R.id.textview_text);
   8066         String text = "The president-elect, Filip, is coming to town tomorrow.";
   8067         int startIndex = text.indexOf("president");
   8068         int endIndex = startIndex + "president".length();
   8069         initializeTextForSmartSelection(text);
   8070 
   8071         // Long-press for smart selection. Expect smart selection.
   8072         Point offset = getCenterPositionOfTextAt(mTextView, startIndex, endIndex);
   8073         emulateLongPressOnView(mTextView, offset.x, offset.y);
   8074         PollingCheck.waitFor(() -> mTextView.getSelectionStart() == SMARTSELECT_START
   8075                 && mTextView.getSelectionEnd() == SMARTSELECT_END);
   8076         // TODO: Test the floating toolbar content.
   8077     }
   8078 
   8079     private boolean isWatch() {
   8080         return (mActivity.getResources().getConfiguration().uiMode
   8081                 & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_WATCH;
   8082     }
   8083 
   8084     @Test
   8085     public void testSmartSelection_dragSelection() throws Throwable {
   8086         if (isWatch()) {
   8087             return;
   8088         }
   8089         mTextView = findTextView(R.id.textview_text);
   8090         String text = "The president-elect, Filip, is coming to town tomorrow.";
   8091         int startIndex = text.indexOf("is coming to town");
   8092         int endIndex = startIndex + "is coming to town".length();
   8093         initializeTextForSmartSelection(text);
   8094 
   8095         Point start = getCenterPositionOfTextAt(mTextView, startIndex, startIndex);
   8096         Point end = getCenterPositionOfTextAt(mTextView, endIndex, endIndex);
   8097         int[] viewOnScreenXY = new int[2];
   8098         mTextView.getLocationOnScreen(viewOnScreenXY);
   8099         int startX = start.x + viewOnScreenXY[0];
   8100         int startY = start.y + viewOnScreenXY[1];
   8101         int offsetX = end.x - start.x;
   8102 
   8103         // Perform drag selection.
   8104         CtsTouchUtils.emulateLongPressAndDragGesture(
   8105                 mInstrumentation, mActivityRule, startX, startY, offsetX, 0 /* offsetY */);
   8106 
   8107         // No smart selection on drag selection.
   8108         assertEquals(startIndex, mTextView.getSelectionStart());
   8109         assertEquals(endIndex, mTextView.getSelectionEnd());
   8110     }
   8111 
   8112     @Test
   8113     public void testSmartSelection_resetSelection() throws Throwable {
   8114         mTextView = findTextView(R.id.textview_text);
   8115         String text = "The president-elect, Filip, is coming to town tomorrow.";
   8116         int startIndex = text.indexOf("president");
   8117         int endIndex = startIndex + "president".length();
   8118         initializeTextForSmartSelection(text);
   8119 
   8120         // Long-press for smart selection. Expect smart selection.
   8121         Point offset = getCenterPositionOfTextAt(mTextView, startIndex, endIndex);
   8122         emulateLongPressOnView(mTextView, offset.x, offset.y);
   8123         PollingCheck.waitFor(() -> mTextView.getSelectionStart() == SMARTSELECT_START
   8124                 && mTextView.getSelectionEnd() == SMARTSELECT_END);
   8125 
   8126         // Tap to reset selection. Expect tapped word to be selected.
   8127         startIndex = text.indexOf("Filip");
   8128         endIndex = startIndex + "Filip".length();
   8129         offset = getCenterPositionOfTextAt(mTextView, startIndex, endIndex);
   8130         emulateClickOnView(mTextView, offset.x, offset.y);
   8131         final int selStart = startIndex;
   8132         final int selEnd = endIndex;
   8133         PollingCheck.waitFor(() -> mTextView.getSelectionStart() == selStart
   8134                 && mTextView.getSelectionEnd() == selEnd);
   8135 
   8136         // Tap one more time to dismiss the selection.
   8137         emulateClickOnView(mTextView, offset.x, offset.y);
   8138         assertFalse(mTextView.hasSelection());
   8139     }
   8140 
   8141     @Test
   8142     public void testFontResources_setInXmlFamilyName() {
   8143         mTextView = findTextView(R.id.textview_fontresource_fontfamily);
   8144         Typeface expected = mActivity.getResources().getFont(R.font.samplefont);
   8145 
   8146         assertEquals(expected, mTextView.getTypeface());
   8147     }
   8148 
   8149     @Test
   8150     public void testFontResourcesXml_setInXmlFamilyName() {
   8151         mTextView = findTextView(R.id.textview_fontxmlresource_fontfamily);
   8152         Typeface expected = mActivity.getResources().getFont(R.font.samplexmlfont);
   8153 
   8154         assertEquals(expected, mTextView.getTypeface());
   8155     }
   8156 
   8157     @Test
   8158     public void testFontResources_setInXmlStyle() {
   8159         mTextView = findTextView(R.id.textview_fontresource_style);
   8160         Typeface expected = mActivity.getResources().getFont(R.font.samplefont);
   8161 
   8162         assertEquals(expected, mTextView.getTypeface());
   8163     }
   8164 
   8165     @Test
   8166     public void testFontResourcesXml_setInXmlStyle() {
   8167         mTextView = findTextView(R.id.textview_fontxmlresource_style);
   8168         Typeface expected = mActivity.getResources().getFont(R.font.samplexmlfont);
   8169 
   8170         assertEquals(expected, mTextView.getTypeface());
   8171     }
   8172 
   8173     @Test
   8174     public void testFontResources_setInXmlTextAppearance() {
   8175         mTextView = findTextView(R.id.textview_fontresource_textAppearance);
   8176         Typeface expected = mActivity.getResources().getFont(R.font.samplefont);
   8177 
   8178         assertEquals(expected, mTextView.getTypeface());
   8179     }
   8180 
   8181     @Test
   8182     public void testFontResourcesXml_setInXmlWithStyle() {
   8183         mTextView = findTextView(R.id.textview_fontxmlresource_fontfamily);
   8184         Typeface expected = mActivity.getResources().getFont(R.font.samplexmlfont);
   8185 
   8186         assertEquals(expected, mTextView.getTypeface());
   8187 
   8188         mTextView = findTextView(R.id.textview_fontxmlresource_withStyle);
   8189 
   8190         Typeface resultTypeface = mTextView.getTypeface();
   8191         assertNotEquals(resultTypeface, expected);
   8192         assertEquals(Typeface.create(expected, Typeface.ITALIC), resultTypeface);
   8193         assertEquals(Typeface.ITALIC, resultTypeface.getStyle());
   8194     }
   8195 
   8196     @Test
   8197     public void testFontResourcesXml_setInXmlTextAppearance() {
   8198         mTextView = findTextView(R.id.textview_fontxmlresource_textAppearance);
   8199         Typeface expected = mActivity.getResources().getFont(R.font.samplexmlfont);
   8200 
   8201         assertEquals(expected, mTextView.getTypeface());
   8202     }
   8203 
   8204     @Test
   8205     @MediumTest
   8206     public void testFontResourcesXml_restrictedContext()
   8207             throws PackageManager.NameNotFoundException {
   8208         Context restrictedContext = mActivity.createPackageContext(mActivity.getPackageName(),
   8209                 Context.CONTEXT_RESTRICTED);
   8210         LayoutInflater layoutInflater = (LayoutInflater) restrictedContext.getSystemService(
   8211                 Context.LAYOUT_INFLATER_SERVICE);
   8212         View root = layoutInflater.inflate(R.layout.textview_restricted_layout, null);
   8213 
   8214         mTextView = root.findViewById(R.id.textview_fontresource_fontfamily);
   8215         assertEquals(Typeface.DEFAULT, mTextView.getTypeface());
   8216         mTextView = root.findViewById(R.id.textview_fontxmlresource_fontfamily);
   8217         assertEquals(Typeface.DEFAULT, mTextView.getTypeface());
   8218         mTextView = root.findViewById(R.id.textview_fontxmlresource_nonFontReference);
   8219         assertEquals(Typeface.DEFAULT, mTextView.getTypeface());
   8220         mTextView = root.findViewById(R.id.textview_fontresource_style);
   8221         assertEquals(Typeface.DEFAULT, mTextView.getTypeface());
   8222         mTextView = root.findViewById(R.id.textview_fontxmlresource_style);
   8223         assertEquals(Typeface.DEFAULT, mTextView.getTypeface());
   8224         mTextView = root.findViewById(R.id.textview_fontresource_textAppearance);
   8225         assertEquals(Typeface.DEFAULT, mTextView.getTypeface());
   8226         mTextView = root.findViewById(R.id.textview_fontxmlresource_textAppearance);
   8227         assertEquals(Typeface.DEFAULT, mTextView.getTypeface());
   8228     }
   8229 
   8230     @UiThreadTest
   8231     @Test
   8232     public void testFallbackLineSpacing_readsFromLayoutXml() {
   8233         mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
   8234         mTextView = findTextView(R.id.textview_true);
   8235         assertTrue(mTextView.isFallbackLineSpacing());
   8236 
   8237         mTextView = findTextView(R.id.textview_default);
   8238         assertTrue(mTextView.isFallbackLineSpacing());
   8239 
   8240         mTextView = findTextView(R.id.textview_false);
   8241         assertFalse(mTextView.isFallbackLineSpacing());
   8242     }
   8243 
   8244     @UiThreadTest
   8245     @Test
   8246     public void testFallbackLineSpacing_set_get() {
   8247         mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
   8248         mTextView = findTextView(R.id.textview_true);
   8249         assertTrue(mTextView.isFallbackLineSpacing());
   8250 
   8251         mTextView.setFallbackLineSpacing(false);
   8252         assertFalse(mTextView.isFallbackLineSpacing());
   8253 
   8254         mTextView.setFallbackLineSpacing(true);
   8255         assertTrue(mTextView.isFallbackLineSpacing());
   8256     }
   8257 
   8258     @UiThreadTest
   8259     @Test
   8260     public void testFallbackLineSpacing_readsFromStyleXml() {
   8261         mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
   8262         mTextView = findTextView(R.id.textview_style_true);
   8263         assertTrue(mTextView.isFallbackLineSpacing());
   8264 
   8265         mTextView = findTextView(R.id.textview_style_default);
   8266         assertTrue(mTextView.isFallbackLineSpacing());
   8267 
   8268         mTextView = findTextView(R.id.textview_style_false);
   8269         assertFalse(mTextView.isFallbackLineSpacing());
   8270     }
   8271 
   8272     @UiThreadTest
   8273     @Test
   8274     public void testFallbackLineSpacing_textAppearance_set_get() {
   8275         mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
   8276         mTextView = findTextView(R.id.textview_default);
   8277         assertTrue(mTextView.isFallbackLineSpacing());
   8278 
   8279         mTextView.setTextAppearance(R.style.TextAppearance_FallbackLineSpacingFalse);
   8280         assertFalse(mTextView.isFallbackLineSpacing());
   8281 
   8282         mTextView.setTextAppearance(R.style.TextAppearance_FallbackLineSpacingTrue);
   8283         assertTrue(mTextView.isFallbackLineSpacing());
   8284 
   8285         mTextView.setFallbackLineSpacing(false);
   8286         mTextView.setTextAppearance(R.style.TextAppearance);
   8287         assertFalse(mTextView.isFallbackLineSpacing());
   8288 
   8289         mTextView.setFallbackLineSpacing(true);
   8290         mTextView.setTextAppearance(R.style.TextAppearance);
   8291         assertTrue(mTextView.isFallbackLineSpacing());
   8292     }
   8293 
   8294     @UiThreadTest
   8295     @Test
   8296     public void testTextLayoutParam() {
   8297         mActivity.setContentView(R.layout.textview_fallbacklinespacing_layout);
   8298         mTextView = findTextView(R.id.textview_default);
   8299         PrecomputedText.Params param = mTextView.getTextMetricsParams();
   8300 
   8301         assertEquals(mTextView.getBreakStrategy(), param.getBreakStrategy());
   8302         assertEquals(mTextView.getHyphenationFrequency(), param.getHyphenationFrequency());
   8303 
   8304         assertTrue(param.equals(mTextView.getTextMetricsParams()));
   8305 
   8306         mTextView.setBreakStrategy(
   8307                 mTextView.getBreakStrategy() == Layout.BREAK_STRATEGY_SIMPLE
   8308                 ?  Layout.BREAK_STRATEGY_BALANCED : Layout.BREAK_STRATEGY_SIMPLE);
   8309 
   8310         assertFalse(param.equals(mTextView.getTextMetricsParams()));
   8311 
   8312         mTextView.setTextMetricsParams(param);
   8313         assertTrue(param.equals(mTextView.getTextMetricsParams()));
   8314     }
   8315 
   8316     @Test
   8317     public void testDynamicLayoutReflowCrash_b75652829() throws Throwable {
   8318         final SpannableStringBuilder text = new SpannableStringBuilder("abcde");
   8319         text.setSpan(new UnderlineSpan(), 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
   8320 
   8321         mActivityRule.runOnUiThread(() -> {
   8322             mTextView = new EditText(mActivity);
   8323             mActivity.setContentView(mTextView);
   8324             mTextView.setText(text, BufferType.EDITABLE);
   8325             mTextView.requestFocus();
   8326             mTextView.setSelected(true);
   8327             mTextView.setTextClassifier(TextClassifier.NO_OP);
   8328         });
   8329         mInstrumentation.waitForIdleSync();
   8330 
   8331         mActivityRule.runOnUiThread(() -> {
   8332             // Set selection and try to start action mode.
   8333             final Bundle args = new Bundle();
   8334             args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 0);
   8335             args.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, text.length());
   8336             mTextView.performAccessibilityAction(
   8337                     AccessibilityNodeInfo.ACTION_SET_SELECTION, args);
   8338         });
   8339         mInstrumentation.waitForIdleSync();
   8340 
   8341         mActivityRule.runOnUiThread(() -> {
   8342             Editable editable = (Editable) mTextView.getText();
   8343             SpannableStringBuilder ssb = new SpannableStringBuilder("a");
   8344             ssb.setSpan(new UnderlineSpan(), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
   8345             editable.replace(5, 5, ssb);
   8346         });
   8347     }
   8348 
   8349     @Test
   8350     public void testBreakStrategyDefaultValue() {
   8351         final Context context = InstrumentationRegistry.getTargetContext();
   8352         final TextView textView = new TextView(context);
   8353         assertEquals(Layout.BREAK_STRATEGY_HIGH_QUALITY, textView.getBreakStrategy());
   8354     }
   8355 
   8356     @Test
   8357     public void testHyphenationFrequencyDefaultValue() {
   8358         final Context context = InstrumentationRegistry.getTargetContext();
   8359         final TextView textView = new TextView(context);
   8360 
   8361         // Hypenation is enabled by default on watches to fit more text on their tiny screens.
   8362         if (isWatch()) {
   8363             assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL, textView.getHyphenationFrequency());
   8364         } else {
   8365             assertEquals(Layout.HYPHENATION_FREQUENCY_NONE, textView.getHyphenationFrequency());
   8366         }
   8367     }
   8368 
   8369     @Test
   8370     @UiThreadTest
   8371     public void testGetTextDirectionHeuristic_password_returnsLTR() {
   8372         mActivity.setContentView(R.layout.textview_textdirectionheuristic);
   8373         final TextView textView = mActivity.findViewById(R.id.text_password);
   8374 
   8375         assertEquals(TextDirectionHeuristics.LTR, textView.getTextDirectionHeuristic());
   8376     }
   8377 
   8378     @Test
   8379     @UiThreadTest
   8380     public void testGetTextDirectionHeuristic_LtrLayout_TextDirectionFirstStrong() {
   8381         mActivity.setContentView(R.layout.textview_textdirectionheuristic);
   8382         final TextView textView = mActivity.findViewById(R.id.text);
   8383         textView.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
   8384         textView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
   8385 
   8386         assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR, textView.getTextDirectionHeuristic());
   8387     }
   8388 
   8389     @Test
   8390     @UiThreadTest
   8391     public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionFirstStrong() {
   8392         mActivity.setContentView(R.layout.textview_textdirectionheuristic);
   8393         final TextView textView = mActivity.findViewById(R.id.text);
   8394         textView.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG);
   8395         textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
   8396 
   8397         assertEquals(TextDirectionHeuristics.FIRSTSTRONG_RTL, textView.getTextDirectionHeuristic());
   8398     }
   8399 
   8400     @Test
   8401     @UiThreadTest
   8402     public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionAnyRtl() {
   8403         mActivity.setContentView(R.layout.textview_textdirectionheuristic);
   8404         final TextView textView = mActivity.findViewById(R.id.text);
   8405         textView.setTextDirection(View.TEXT_DIRECTION_ANY_RTL);
   8406 
   8407         textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
   8408         assertEquals(TextDirectionHeuristics.ANYRTL_LTR, textView.getTextDirectionHeuristic());
   8409 
   8410         textView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
   8411         assertEquals(TextDirectionHeuristics.ANYRTL_LTR, textView.getTextDirectionHeuristic());
   8412     }
   8413 
   8414     @Test
   8415     @UiThreadTest
   8416     public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionLtr() {
   8417         mActivity.setContentView(R.layout.textview_textdirectionheuristic);
   8418         final TextView textView = mActivity.findViewById(R.id.text);
   8419         textView.setTextDirection(View.TEXT_DIRECTION_LTR);
   8420 
   8421         assertEquals(TextDirectionHeuristics.LTR, textView.getTextDirectionHeuristic());
   8422     }
   8423 
   8424     @Test
   8425     @UiThreadTest
   8426     public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionRtl() {
   8427         mActivity.setContentView(R.layout.textview_textdirectionheuristic);
   8428         final TextView textView = mActivity.findViewById(R.id.text);
   8429         textView.setTextDirection(View.TEXT_DIRECTION_RTL);
   8430 
   8431         assertEquals(TextDirectionHeuristics.RTL, textView.getTextDirectionHeuristic());
   8432     }
   8433 
   8434     @Test
   8435     @UiThreadTest
   8436     public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionFirstStrongLtr() {
   8437         mActivity.setContentView(R.layout.textview_textdirectionheuristic);
   8438         final TextView textView = mActivity.findViewById(R.id.text);
   8439         textView.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_LTR);
   8440 
   8441         textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
   8442         assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR, textView.getTextDirectionHeuristic());
   8443 
   8444         textView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
   8445         assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR, textView.getTextDirectionHeuristic());
   8446     }
   8447 
   8448     @Test
   8449     @UiThreadTest
   8450     public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionFirstStrongRtl() {
   8451         mActivity.setContentView(R.layout.textview_textdirectionheuristic);
   8452         final TextView textView = mActivity.findViewById(R.id.text);
   8453         textView.setTextDirection(View.TEXT_DIRECTION_FIRST_STRONG_RTL);
   8454 
   8455         textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
   8456         assertEquals(TextDirectionHeuristics.FIRSTSTRONG_RTL, textView.getTextDirectionHeuristic());
   8457 
   8458         textView.setLayoutDirection(View.LAYOUT_DIRECTION_LTR);
   8459         assertEquals(TextDirectionHeuristics.FIRSTSTRONG_RTL, textView.getTextDirectionHeuristic());
   8460     }
   8461 
   8462     @Test
   8463     @UiThreadTest
   8464     public void testGetTextDirectionHeuristic_phoneInputType_returnsLTR() {
   8465         mActivity.setContentView(R.layout.textview_textdirectionheuristic);
   8466         final TextView textView = mActivity.findViewById(R.id.text_phone);
   8467 
   8468         textView.setTextLocale(Locale.forLanguageTag("ar"));
   8469         textView.setTextDirection(View.TEXT_DIRECTION_RTL);
   8470         textView.setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
   8471 
   8472         assertEquals(TextDirectionHeuristics.LTR, textView.getTextDirectionHeuristic());
   8473     }
   8474 
   8475     @Test
   8476     @UiThreadTest
   8477     public void testGetTextDirectionHeuristic_RtlLayout_TextDirectionLocale() {
   8478         mActivity.setContentView(R.layout.textview_textdirectionheuristic);
   8479         final TextView textView = mActivity.findViewById(R.id.text);
   8480         textView.setTextDirection(View.TEXT_DIRECTION_LOCALE);
   8481 
   8482         assertEquals(TextDirectionHeuristics.LOCALE, textView.getTextDirectionHeuristic());
   8483     }
   8484 
   8485     private void initializeTextForSmartSelection(CharSequence text) throws Throwable {
   8486         assertTrue(text.length() >= SMARTSELECT_END);
   8487         mActivityRule.runOnUiThread(() -> {
   8488             mTextView.setTextIsSelectable(true);
   8489             mTextView.setText(text);
   8490             mTextView.setTextClassifier(FAKE_TEXT_CLASSIFIER);
   8491             mTextView.requestFocus();
   8492         });
   8493         mInstrumentation.waitForIdleSync();
   8494     }
   8495 
   8496     private void emulateClickOnView(View view, int offsetX, int offsetY) {
   8497         CtsTouchUtils.emulateTapOnView(mInstrumentation, mActivityRule, view, offsetX, offsetY);
   8498         SystemClock.sleep(CLICK_TIMEOUT);
   8499     }
   8500 
   8501     private void emulateLongPressOnView(View view, int offsetX, int offsetY) {
   8502         CtsTouchUtils.emulateLongPressOnView(mInstrumentation, mActivityRule, view,
   8503                 offsetX, offsetY);
   8504         // TODO: Ideally, we shouldn't have to wait for a click timeout after a long-press but it
   8505         // seems like we have a minor bug (call it inconvenience) in TextView that requires this.
   8506         SystemClock.sleep(CLICK_TIMEOUT);
   8507     }
   8508 
   8509     /**
   8510      * Some TextView attributes require non-fixed width and/or layout height. This function removes
   8511      * all other existing views from the layout leaving only one auto-size TextView (for exercising
   8512      * the auto-size behavior) which has been set up to suit the test needs.
   8513      *
   8514      * @param viewId The id of the view to prepare.
   8515      * @param shouldWrapLayoutContent Specifies if the layout params should wrap content
   8516      *
   8517      * @return a TextView configured for auto size tests.
   8518      */
   8519     private TextView prepareAndRetrieveAutoSizeTestData(final int viewId,
   8520             final boolean shouldWrapLayoutContent) throws Throwable {
   8521         mActivityRule.runOnUiThread(() -> {
   8522             LinearLayout ll = (LinearLayout) mActivity.findViewById(R.id.layout_textviewtest);
   8523             TextView targetedTextView = (TextView) mActivity.findViewById(viewId);
   8524             ll.removeAllViews();
   8525             ll.addView(targetedTextView);
   8526         });
   8527         mInstrumentation.waitForIdleSync();
   8528 
   8529         final TextView textView = (TextView) mActivity.findViewById(viewId);
   8530         if (shouldWrapLayoutContent) {
   8531             // Do not force exact width or height.
   8532             final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
   8533                     LinearLayout.LayoutParams.WRAP_CONTENT,
   8534                     LinearLayout.LayoutParams.WRAP_CONTENT);
   8535             mActivityRule.runOnUiThread(() -> {
   8536                 textView.setLayoutParams(layoutParams);
   8537             });
   8538             mInstrumentation.waitForIdleSync();
   8539         }
   8540 
   8541         return textView;
   8542     }
   8543 
   8544     /**
   8545      * Removes all existing views from the layout and adds a basic TextView (for exercising the
   8546      * ClickableSpan onClick() behavior) in order to prevent scrolling. Adds a ClickableSpan to the
   8547      * TextView and returns the ClickableSpan and position details about it to be used in individual
   8548      * tests.
   8549      */
   8550     private ClickableSpanTestDetails prepareAndRetrieveClickableSpanDetails() throws Throwable {
   8551         mActivityRule.runOnUiThread(() -> {
   8552             LinearLayout ll = (LinearLayout) mActivity.findViewById(R.id.layout_textviewtest);
   8553             ll.removeAllViews();
   8554             mTextView = new TextView(mActivity);
   8555             ll.addView(mTextView);
   8556         });
   8557         mInstrumentation.waitForIdleSync();
   8558 
   8559         ClickableSpan mockTextLink = mock(ClickableSpan.class);
   8560         StringBuilder textViewContent = new StringBuilder();
   8561         String clickableString = "clickMe!";
   8562         textViewContent.append(clickableString);
   8563         final int startPos = 0;
   8564 
   8565         // Insert more characters to make some room for swiping.
   8566         for (int i = 0; i < 200; i++) {
   8567             textViewContent.append(" text");
   8568         }
   8569         SpannableString spannableString = new SpannableString(textViewContent);
   8570         final int endPos = clickableString.length();
   8571         spannableString.setSpan(mockTextLink, startPos, endPos, 0);
   8572         mActivityRule.runOnUiThread(() -> {
   8573             mTextView.setText(spannableString);
   8574             mTextView.setMovementMethod(LinkMovementMethod.getInstance());
   8575         });
   8576         mInstrumentation.waitForIdleSync();
   8577 
   8578         return new ClickableSpanTestDetails(mockTextLink, mTextView, startPos, endPos);
   8579     }
   8580 
   8581     private static final class ClickableSpanTestDetails {
   8582         ClickableSpan mClickableSpan;
   8583         int mXPosInside;
   8584         int mYPosInside;
   8585         int mXPosOutside;
   8586         int mYPosOutside;
   8587 
   8588         private int mStartCharPos;
   8589         private int mEndCharPos;
   8590         private TextView mParent;
   8591 
   8592         ClickableSpanTestDetails(ClickableSpan clickableSpan, TextView parent,
   8593                 int startCharPos, int endCharPos) {
   8594             mClickableSpan = clickableSpan;
   8595             mParent = parent;
   8596             mStartCharPos = startCharPos;
   8597             mEndCharPos = endCharPos;
   8598 
   8599             calculatePositions();
   8600         }
   8601 
   8602         private void calculatePositions() {
   8603             int xStart = (int) mParent.getLayout().getPrimaryHorizontal(mStartCharPos, true);
   8604             int xEnd = (int) mParent.getLayout().getPrimaryHorizontal(mEndCharPos, true);
   8605             int line = mParent.getLayout().getLineForOffset(mEndCharPos);
   8606             int yTop = mParent.getLayout().getLineTop(line);
   8607             int yBottom = mParent.getLayout().getLineBottom(line);
   8608 
   8609             mXPosInside = (xStart + xEnd) / 2;
   8610             mYPosInside = (yTop + yBottom) / 2;
   8611             mXPosOutside = xEnd + 1;
   8612             mYPosOutside = yBottom + 1;
   8613         }
   8614     }
   8615 
   8616     private MotionEvent createMouseHoverEvent(View view) {
   8617         final int[] xy = new int[2];
   8618         view.getLocationOnScreen(xy);
   8619         final int viewWidth = view.getWidth();
   8620         final int viewHeight = view.getHeight();
   8621         float x = xy[0] + viewWidth / 2.0f;
   8622         float y = xy[1] + viewHeight / 2.0f;
   8623         long eventTime = SystemClock.uptimeMillis();
   8624         MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[1];
   8625         pointerCoords[0] = new MotionEvent.PointerCoords();
   8626         pointerCoords[0].x = x;
   8627         pointerCoords[0].y = y;
   8628         final int[] pointerIds = new int[1];
   8629         pointerIds[0] = 0;
   8630         return MotionEvent.obtain(0, eventTime, MotionEvent.ACTION_HOVER_MOVE, 1, pointerIds,
   8631                 pointerCoords, 0, 0, 0, 0, 0, InputDevice.SOURCE_MOUSE, 0);
   8632     }
   8633 
   8634     private void layout(final TextView textView) throws Throwable {
   8635         mActivityRule.runOnUiThread(() -> mActivity.setContentView(textView));
   8636         mInstrumentation.waitForIdleSync();
   8637     }
   8638 
   8639     private void layout(final int layoutId) throws Throwable {
   8640         mActivityRule.runOnUiThread(() -> mActivity.setContentView(layoutId));
   8641         mInstrumentation.waitForIdleSync();
   8642     }
   8643 
   8644     private TextView findTextView(int id) {
   8645         return (TextView) mActivity.findViewById(id);
   8646     }
   8647 
   8648     private int getAutoLinkMask(int id) {
   8649         return findTextView(id).getAutoLinkMask();
   8650     }
   8651 
   8652     private void setMaxLines(final int lines) throws Throwable {
   8653         mActivityRule.runOnUiThread(() -> mTextView.setMaxLines(lines));
   8654         mInstrumentation.waitForIdleSync();
   8655     }
   8656 
   8657     private void setMaxWidth(final int pixels) throws Throwable {
   8658         mActivityRule.runOnUiThread(() -> mTextView.setMaxWidth(pixels));
   8659         mInstrumentation.waitForIdleSync();
   8660     }
   8661 
   8662     private void setMinWidth(final int pixels) throws Throwable {
   8663         mActivityRule.runOnUiThread(() -> mTextView.setMinWidth(pixels));
   8664         mInstrumentation.waitForIdleSync();
   8665     }
   8666 
   8667     private void setMaxHeight(final int pixels) throws Throwable {
   8668         mActivityRule.runOnUiThread(() -> mTextView.setMaxHeight(pixels));
   8669         mInstrumentation.waitForIdleSync();
   8670     }
   8671 
   8672     private void setMinHeight(final int pixels) throws Throwable {
   8673         mActivityRule.runOnUiThread(() -> mTextView.setMinHeight(pixels));
   8674         mInstrumentation.waitForIdleSync();
   8675     }
   8676 
   8677     private void setMinLines(final int minLines) throws Throwable {
   8678         mActivityRule.runOnUiThread(() -> mTextView.setMinLines(minLines));
   8679         mInstrumentation.waitForIdleSync();
   8680     }
   8681 
   8682     /**
   8683      * Convenience for {@link TextView#setText(CharSequence, BufferType)}. And
   8684      * the buffer type is fixed to SPANNABLE.
   8685      *
   8686      * @param tv the text view
   8687      * @param content the content
   8688      */
   8689     private void setSpannableText(final TextView tv, final String content) throws Throwable {
   8690         mActivityRule.runOnUiThread(() -> tv.setText(content, BufferType.SPANNABLE));
   8691         mInstrumentation.waitForIdleSync();
   8692     }
   8693 
   8694     private void setLines(final int lines) throws Throwable {
   8695         mActivityRule.runOnUiThread(() -> mTextView.setLines(lines));
   8696         mInstrumentation.waitForIdleSync();
   8697     }
   8698 
   8699     private void setHorizontallyScrolling(final boolean whether) throws Throwable {
   8700         mActivityRule.runOnUiThread(() -> mTextView.setHorizontallyScrolling(whether));
   8701         mInstrumentation.waitForIdleSync();
   8702     }
   8703 
   8704     private void setWidth(final int pixels) throws Throwable {
   8705         mActivityRule.runOnUiThread(() -> mTextView.setWidth(pixels));
   8706         mInstrumentation.waitForIdleSync();
   8707     }
   8708 
   8709     private void setHeight(final int pixels) throws Throwable {
   8710         mActivityRule.runOnUiThread(() -> mTextView.setHeight(pixels));
   8711         mInstrumentation.waitForIdleSync();
   8712     }
   8713 
   8714     private void setMinEms(final int ems) throws Throwable {
   8715         mActivityRule.runOnUiThread(() -> mTextView.setMinEms(ems));
   8716         mInstrumentation.waitForIdleSync();
   8717     }
   8718 
   8719     private void setMaxEms(final int ems) throws Throwable {
   8720         mActivityRule.runOnUiThread(() -> mTextView.setMaxEms(ems));
   8721         mInstrumentation.waitForIdleSync();
   8722     }
   8723 
   8724     private void setEms(final int ems) throws Throwable {
   8725         mActivityRule.runOnUiThread(() -> mTextView.setEms(ems));
   8726         mInstrumentation.waitForIdleSync();
   8727     }
   8728 
   8729     private void setLineSpacing(final float add, final float mult) throws Throwable {
   8730         mActivityRule.runOnUiThread(() -> mTextView.setLineSpacing(add, mult));
   8731         mInstrumentation.waitForIdleSync();
   8732     }
   8733 
   8734     /**
   8735      * Returns the x, y coordinates of text at a specified indices relative to the position of the
   8736      * TextView.
   8737      *
   8738      * @param textView
   8739      * @param startIndex start index of the text in the textView
   8740      * @param endIndex end index of the text in the textView
   8741      */
   8742     private static Point getCenterPositionOfTextAt(
   8743             TextView textView, int startIndex, int endIndex) {
   8744         int xStart = (int) textView.getLayout().getPrimaryHorizontal(startIndex, true);
   8745         int xEnd = (int) textView.getLayout().getPrimaryHorizontal(endIndex, true);
   8746         int line = textView.getLayout().getLineForOffset(endIndex);
   8747         int yTop = textView.getLayout().getLineTop(line);
   8748         int yBottom = textView.getLayout().getLineBottom(line);
   8749 
   8750         return new Point((xStart + xEnd) / 2 /* x */, (yTop + yBottom) / 2 /* y */);
   8751     }
   8752 
   8753     private static abstract class TestSelectedRunnable implements Runnable {
   8754         private TextView mTextView;
   8755         private boolean mIsSelected1;
   8756         private boolean mIsSelected2;
   8757 
   8758         public TestSelectedRunnable(TextView textview) {
   8759             mTextView = textview;
   8760         }
   8761 
   8762         public boolean getIsSelected1() {
   8763             return mIsSelected1;
   8764         }
   8765 
   8766         public boolean getIsSelected2() {
   8767             return mIsSelected2;
   8768         }
   8769 
   8770         public void saveIsSelected1() {
   8771             mIsSelected1 = mTextView.isSelected();
   8772         }
   8773 
   8774         public void saveIsSelected2() {
   8775             mIsSelected2 = mTextView.isSelected();
   8776         }
   8777     }
   8778 
   8779     private static abstract class TestLayoutRunnable implements Runnable {
   8780         private TextView mTextView;
   8781         private Layout mLayout;
   8782 
   8783         public TestLayoutRunnable(TextView textview) {
   8784             mTextView = textview;
   8785         }
   8786 
   8787         public Layout getLayout() {
   8788             return mLayout;
   8789         }
   8790 
   8791         public void saveLayout() {
   8792             mLayout = mTextView.getLayout();
   8793         }
   8794     }
   8795 
   8796     private static class MockTextWatcher implements TextWatcher {
   8797         private boolean mHasCalledAfterTextChanged;
   8798         private boolean mHasCalledBeforeTextChanged;
   8799         private boolean mHasOnTextChanged;
   8800 
   8801         public void reset(){
   8802             mHasCalledAfterTextChanged = false;
   8803             mHasCalledBeforeTextChanged = false;
   8804             mHasOnTextChanged = false;
   8805         }
   8806 
   8807         public boolean hasCalledAfterTextChanged() {
   8808             return mHasCalledAfterTextChanged;
   8809         }
   8810 
   8811         public boolean hasCalledBeforeTextChanged() {
   8812             return mHasCalledBeforeTextChanged;
   8813         }
   8814 
   8815         public boolean hasCalledOnTextChanged() {
   8816             return mHasOnTextChanged;
   8817         }
   8818 
   8819         public void afterTextChanged(Editable s) {
   8820             mHasCalledAfterTextChanged = true;
   8821         }
   8822 
   8823         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
   8824             mHasCalledBeforeTextChanged = true;
   8825         }
   8826 
   8827         public void onTextChanged(CharSequence s, int start, int before, int count) {
   8828             mHasOnTextChanged = true;
   8829         }
   8830     }
   8831 
   8832     /**
   8833      * A TextWatcher that converts the text to spaces whenever the text changes.
   8834      */
   8835     private static class ConvertToSpacesTextWatcher implements TextWatcher {
   8836         boolean mChangingText;
   8837 
   8838         @Override
   8839         public void beforeTextChanged(CharSequence s, int start, int count, int after) {
   8840         }
   8841 
   8842         @Override
   8843         public void onTextChanged(CharSequence s, int start, int before, int count) {
   8844         }
   8845 
   8846         @Override
   8847         public void afterTextChanged(Editable s) {
   8848             // Avoid infinite recursion.
   8849             if (mChangingText) {
   8850                 return;
   8851             }
   8852             mChangingText = true;
   8853             // Create a string of s.length() spaces.
   8854             StringBuilder builder = new StringBuilder(s.length());
   8855             for (int i = 0; i < s.length(); i++) {
   8856                 builder.append(' ');
   8857             }
   8858             s.replace(0, s.length(), builder.toString());
   8859             mChangingText = false;
   8860         }
   8861     }
   8862 }
   8863