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