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