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