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