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