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