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.text.cts;
18 
19 import static android.view.View.LAYOUT_DIRECTION_LTR;
20 import static android.view.View.LAYOUT_DIRECTION_RTL;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertFalse;
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.Mockito.mock;
31 import static org.mockito.Mockito.when;
32 
33 import android.content.Context;
34 import android.content.res.ColorStateList;
35 import android.content.res.Configuration;
36 import android.content.res.Resources;
37 import android.graphics.Color;
38 import android.graphics.Typeface;
39 import android.os.LocaleList;
40 import android.os.Parcel;
41 import android.os.Parcelable;
42 import android.text.GetChars;
43 import android.text.SpannableString;
44 import android.text.SpannableStringBuilder;
45 import android.text.Spanned;
46 import android.text.SpannedString;
47 import android.text.TextPaint;
48 import android.text.TextUtils;
49 import android.text.TextUtils.TruncateAt;
50 import android.text.style.BackgroundColorSpan;
51 import android.text.style.ReplacementSpan;
52 import android.text.style.TextAppearanceSpan;
53 import android.text.style.URLSpan;
54 import android.util.StringBuilderPrinter;
55 
56 import androidx.test.InstrumentationRegistry;
57 import androidx.test.filters.SmallTest;
58 import androidx.test.runner.AndroidJUnit4;
59 
60 import org.junit.Before;
61 import org.junit.Test;
62 import org.junit.runner.RunWith;
63 
64 import java.util.ArrayList;
65 import java.util.Arrays;
66 import java.util.List;
67 import java.util.Locale;
68 import java.util.regex.Pattern;
69 
70 /**
71  * Test {@link TextUtils}.
72  */
73 @SmallTest
74 @RunWith(AndroidJUnit4.class)
75 public class TextUtilsTest  {
76     private Context mContext;
77     private String mEllipsis;
78     private int mStart;
79     private int mEnd;
80 
81     @Before
setup()82     public void setup() {
83         mContext = InstrumentationRegistry.getTargetContext();
84         mEllipsis = getEllipsis();
85         resetRange();
86     }
87 
resetRange()88     private void resetRange() {
89         mStart = -1;
90         mEnd = -1;
91     }
92 
93     /**
94      * Get the ellipsis from system.
95      * @return the string of ellipsis.
96      */
getEllipsis()97     private static String getEllipsis() {
98         String text = "xxxxx";
99         TextPaint p = new TextPaint();
100         float width = p.measureText(text.substring(1));
101         String re = TextUtils.ellipsize(text, p, width, TruncateAt.START).toString();
102         return re.substring(0, re.indexOf("x"));
103     }
104 
105     /**
106      * @return the number of times the code unit appears in the CharSequence.
107      */
countChars(CharSequence s, char c)108     private static int countChars(CharSequence s, char c) {
109         int count = 0;
110         for (int i = 0; i < s.length(); i++) {
111             if (s.charAt(i) == c) {
112                 count++;
113             }
114         }
115         return count;
116     }
117 
118     @Test
testListEllipsize()119     public void testListEllipsize() {
120         final TextPaint paint = new TextPaint();
121         final int moreId = R.plurals.list_ellipsize_test;  // "one more" for 1, "%d more" for other
122 
123         final List fullList = Arrays.asList("A", "B", "C", "D", "E", "F", "G", "H", "I", "J");
124         final String separator = ", ";
125         final String fullString = TextUtils.join(separator, fullList);
126         final float fullWidth = paint.measureText(fullString);
127         assertEquals("",
128             TextUtils.listEllipsize(mContext, null, separator, paint, fullWidth, moreId));
129 
130         final List<CharSequence> emptyList = new ArrayList<>();
131         assertEquals("",
132             TextUtils.listEllipsize(mContext, emptyList, separator, paint, fullWidth, moreId));
133 
134         // Null context should cause ellipsis to be used at the end.
135         final String ellipsizedWithNull = TextUtils.listEllipsize(
136                 null, fullList, separator, paint, fullWidth / 2, 0).toString();
137         assertTrue(ellipsizedWithNull.endsWith(getEllipsis()));
138 
139         // Test that the empty string gets returned if there's no space.
140         assertEquals("",
141                 TextUtils.listEllipsize(mContext, fullList, separator, paint, 1.0f, moreId));
142 
143         // Test that the full string itself can get returned if there's enough space.
144         assertEquals(fullString,
145                 TextUtils.listEllipsize(mContext, fullList, separator, paint, fullWidth, moreId)
146                         .toString());
147         assertEquals(fullString,
148                 TextUtils.listEllipsize(mContext, fullList, separator, paint, fullWidth * 2,
149                         moreId).toString());
150 
151         final float epsilon = fullWidth / 20;
152         for (float width = epsilon; width < fullWidth - epsilon / 2; width += epsilon) {
153             final String ellipsized = TextUtils.listEllipsize(
154                     mContext, fullList, separator, paint, width, moreId).toString();
155             // Since we don't have the full space, test that we are not getting the full string.
156             assertFalse(fullString.equals(ellipsized));
157 
158             if (!ellipsized.isEmpty()) {
159                 assertTrue(ellipsized.endsWith(" more"));
160                 // Test that the number of separators (which equals the number of output elements),
161                 // plus the number output before more always equals the number of original elements.
162                 final int lastSpace = ellipsized.lastIndexOf(' ');
163                 final int penultimateSpace = ellipsized.lastIndexOf(' ', lastSpace - 1);
164                 assertEquals(',', ellipsized.charAt(penultimateSpace - 1));
165                 final String moreCountString = ellipsized.substring(
166                         penultimateSpace + 1, lastSpace);
167                 final int moreCount = (moreCountString.equals("one"))
168                         ? 1 : Integer.parseInt(moreCountString);
169                 final int commaCount = countChars(ellipsized, ',');
170                 assertEquals(fullList.size(), commaCount + moreCount);
171             }
172         }
173 }
174 
175     @Test
testListEllipsize_rtl()176     public void testListEllipsize_rtl() {
177         final Resources res = mContext.getResources();
178         final Configuration newConfig = new Configuration(res.getConfiguration());
179 
180         // save the locales and set them to just Arabic
181         final LocaleList previousLocales = newConfig.getLocales();
182         newConfig.setLocales(LocaleList.forLanguageTags("ar"));
183         res.updateConfiguration(newConfig, null);
184 
185         try {
186             final TextPaint paint = new TextPaint();
187             final int moreId = R.plurals.list_ellipsize_test;  // "one more" for 1, else "%d more"
188             final String RLM = "\u200F";
189             final String LRE = "\u202A";
190             final String PDF = "\u202C";
191 
192             final List fullList = Arrays.asList("A", "B");
193             final String separator = ", ";
194             final String expectedString =
195                     RLM + LRE + "A" + PDF + RLM + ", " + RLM + LRE + "B" + PDF + RLM;
196             final float enoughWidth = paint.measureText(expectedString);
197 
198             assertEquals(expectedString,
199                     TextUtils.listEllipsize(mContext, fullList, separator, paint, enoughWidth,
200                                             moreId).toString());
201         } finally {
202             // Restore the original locales
203             newConfig.setLocales(previousLocales);
204             res.updateConfiguration(newConfig, null);
205         }
206     }
207 
208     @Test
testCommaEllipsize()209     public void testCommaEllipsize() {
210         TextPaint p = new TextPaint();
211         String text = "long, string, to, truncate";
212 
213         float textWidth = p.measureText("long, 3 plus");
214         // avail is shorter than text width for only one item plus the appropriate ellipsis.
215         // issue 1688347, the expected result for this case does not be described
216         // in the javadoc of commaEllipsize().
217         assertEquals("",
218                 TextUtils.commaEllipsize(text, p, textWidth - 1.4f, "plus 1", "%d plus").toString());
219         // avail is long enough for only one item plus the appropriate ellipsis.
220         assertEquals("long, 3 plus",
221                 TextUtils.commaEllipsize(text, p, textWidth, "plus 1", "%d plus").toString());
222 
223         // avail is long enough for two item plus the appropriate ellipsis.
224         textWidth = p.measureText("long, string, 2 more");
225         assertEquals("long, string, 2 more",
226                 TextUtils.commaEllipsize(text, p, textWidth, "more 1", "%d more").toString());
227 
228         // avail is long enough for the whole sentence.
229         textWidth = p.measureText("long, string, to, truncate");
230         assertEquals("long, string, to, truncate",
231                 TextUtils.commaEllipsize(text, p, textWidth, "more 1", "%d more").toString());
232 
233         // the sentence is extended, avail is NOT long enough for the whole sentence.
234         assertEquals("long, string, to, more 1", TextUtils.commaEllipsize(
235                 text + "-extended", p, textWidth, "more 1", "%d more").toString());
236 
237         // exceptional value
238         assertEquals("", TextUtils.commaEllipsize(text, p, -1f, "plus 1", "%d plus").toString());
239 
240         assertEquals(text, TextUtils.commaEllipsize(
241                 text, p, Float.MAX_VALUE, "more 1", "%d more").toString());
242 
243         assertEquals("long, string, to, null", TextUtils.commaEllipsize(
244                 text + "-extended", p, textWidth, null, "%d more").toString());
245 
246         try {
247             TextUtils.commaEllipsize(null, p, textWidth, "plus 1", "%d plus");
248             fail("Should throw NullPointerException");
249         } catch (NullPointerException e) {
250             // issue 1688347, not clear what is supposed to happen if the text to truncate is null.
251         }
252 
253         try {
254             TextUtils.commaEllipsize(text, null, textWidth, "plus 1", "%d plus");
255             fail("Should throw NullPointerException");
256         } catch (NullPointerException e) {
257             // issue 1688347, not clear what is supposed to happen if TextPaint is null.
258         }
259     }
260 
261     @Test
testConcat()262     public void testConcat() {
263         assertEquals("", TextUtils.concat().toString());
264 
265         assertEquals("first", TextUtils.concat("first").toString());
266 
267         assertEquals("first, second", TextUtils.concat("first", ", ", "second").toString());
268 
269         SpannableString string1 = new SpannableString("first");
270         SpannableString string2 = new SpannableString("second");
271         final String url = "www.test_url.com";
272         URLSpan urlSpan = new URLSpan(url);
273         string1.setSpan(urlSpan, 0, string1.length() - 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
274         BackgroundColorSpan bgColorSpan = new BackgroundColorSpan(Color.GREEN);
275         string2.setSpan(bgColorSpan, 0, string2.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
276 
277         final String comma = ", ";
278         Spanned strResult = (Spanned) TextUtils.concat(string1, comma, string2);
279         assertEquals(string1.toString() + comma + string2.toString(), strResult.toString());
280         Object spans[] = strResult.getSpans(0, strResult.length(), Object.class);
281         assertEquals(2, spans.length);
282         assertTrue(spans[0] instanceof URLSpan);
283         assertEquals(url, ((URLSpan) spans[0]).getURL());
284         assertTrue(spans[1] instanceof BackgroundColorSpan);
285         assertEquals(Color.GREEN, ((BackgroundColorSpan) spans[1]).getBackgroundColor());
286         assertEquals(0, strResult.getSpanStart(urlSpan));
287         assertEquals(string1.length() - 1, strResult.getSpanEnd(urlSpan));
288         assertEquals(string1.length() + comma.length(), strResult.getSpanStart(bgColorSpan));
289         assertEquals(strResult.length() - 1, strResult.getSpanEnd(bgColorSpan));
290 
291         assertEquals(string1, TextUtils.concat(string1));
292 
293         assertEquals(null, TextUtils.concat((CharSequence) null));
294     }
295 
296     @Test(expected = NullPointerException.class)
testConcat_NullArray()297     public void testConcat_NullArray() {
298         TextUtils.concat((CharSequence[]) null);
299     }
300 
301     @Test
testConcat_NullParameters()302     public void testConcat_NullParameters() {
303         assertEquals("nullA", TextUtils.concat(null, "A"));
304         assertEquals("Anull", TextUtils.concat("A", null));
305         assertEquals("AnullB", TextUtils.concat("A", null, "B"));
306 
307         final SpannableString piece = new SpannableString("A");
308         final Object span = new Object();
309         piece.setSpan(span, 0, piece.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
310         final Spanned result = (Spanned) TextUtils.concat(piece, null);
311         assertEquals("Anull", result.toString());
312         final Object[] spans = result.getSpans(0, result.length(), Object.class);
313         assertEquals(1, spans.length);
314         assertSame(span, spans[0]);
315         assertEquals(0, result.getSpanStart(spans[0]));
316         assertEquals(piece.length(), result.getSpanEnd(spans[0]));
317     }
318 
319     @Test
testConcat_twoParagraphSpans()320     public void testConcat_twoParagraphSpans() {
321         // Two paragraph spans. The first will get extended to cover the whole string and the second
322         // will be dropped.
323         final SpannableString string1 = new SpannableString("a");
324         final SpannableString string2 = new SpannableString("b");
325         final Object span1 = new Object();
326         final Object span2 = new Object();
327         string1.setSpan(span1, 0, string1.length(), Spanned.SPAN_PARAGRAPH);
328         string2.setSpan(span2, 0, string2.length(), Spanned.SPAN_PARAGRAPH);
329 
330         final Spanned result = (Spanned) TextUtils.concat(string1, string2);
331         assertEquals("ab", result.toString());
332         final Object[] spans = result.getSpans(0, result.length(), Object.class);
333         assertEquals(1, spans.length);
334         assertSame(span1, spans[0]);
335         assertEquals(0, result.getSpanStart(spans[0]));
336         assertEquals(result.length(), result.getSpanEnd(spans[0]));
337     }
338 
339     @Test
testConcat_oneParagraphSpanAndOneInclusiveSpan()340     public void testConcat_oneParagraphSpanAndOneInclusiveSpan() {
341         // One paragraph span and one double-inclusive span. The first will get extended to cover
342         // the whole string and the second will be kept.
343         final SpannableString string1 = new SpannableString("a");
344         final SpannableString string2 = new SpannableString("b");
345         final Object span1 = new Object();
346         final Object span2 = new Object();
347         string1.setSpan(span1, 0, string1.length(), Spanned.SPAN_PARAGRAPH);
348         string2.setSpan(span2, 0, string2.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
349 
350         final Spanned result = (Spanned) TextUtils.concat(string1, string2);
351         assertEquals("ab", result.toString());
352         final Object[] spans = result.getSpans(0, result.length(), Object.class);
353         assertEquals(2, spans.length);
354         assertSame(span1, spans[0]);
355         assertEquals(0, result.getSpanStart(spans[0]));
356         assertEquals(result.length(), result.getSpanEnd(spans[0]));
357         assertSame(span2, spans[1]);
358         assertEquals(string1.length(), result.getSpanStart(spans[1]));
359         assertEquals(result.length(), result.getSpanEnd(spans[1]));
360     }
361 
362     @Test
testCopySpansFrom()363     public void testCopySpansFrom() {
364         Object[] spans;
365         String text = "content";
366         SpannableString source1 = new SpannableString(text);
367         int midPos = source1.length() / 2;
368         final String url = "www.test_url.com";
369         URLSpan urlSpan = new URLSpan(url);
370         source1.setSpan(urlSpan, 0, midPos, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
371         BackgroundColorSpan bgColorSpan = new BackgroundColorSpan(Color.GREEN);
372         source1.setSpan(bgColorSpan, midPos - 1,
373                 source1.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
374 
375         // normal test
376         SpannableString dest1 = new SpannableString(text);
377         TextUtils.copySpansFrom(source1, 0, source1.length(), Object.class, dest1, 0);
378         spans = dest1.getSpans(0, dest1.length(), Object.class);
379         assertEquals(2, spans.length);
380         assertTrue(spans[0] instanceof URLSpan);
381         assertEquals(url, ((URLSpan) spans[0]).getURL());
382         assertTrue(spans[1] instanceof BackgroundColorSpan);
383         assertEquals(Color.GREEN, ((BackgroundColorSpan) spans[1]).getBackgroundColor());
384         assertEquals(0, dest1.getSpanStart(urlSpan));
385         assertEquals(midPos, dest1.getSpanEnd(urlSpan));
386         assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, dest1.getSpanFlags(urlSpan));
387         assertEquals(midPos - 1, dest1.getSpanStart(bgColorSpan));
388         assertEquals(source1.length() - 1, dest1.getSpanEnd(bgColorSpan));
389         assertEquals(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE, dest1.getSpanFlags(bgColorSpan));
390 
391         SpannableString source2 = new SpannableString(text);
392         source2.setSpan(urlSpan, 0, source2.length() - 1, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
393         SpannableString dest2 = new SpannableString(text);
394         TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest2, 0);
395         spans = dest2.getSpans(0, dest2.length(), Object.class);
396         assertEquals(1, spans.length);
397         assertTrue(spans[0] instanceof URLSpan);
398         assertEquals(url, ((URLSpan) spans[0]).getURL());
399         assertEquals(0, dest2.getSpanStart(urlSpan));
400         assertEquals(source2.length() - 1, dest2.getSpanEnd(urlSpan));
401         assertEquals(Spanned.SPAN_EXCLUSIVE_INCLUSIVE, dest2.getSpanFlags(urlSpan));
402 
403         SpannableString dest3 = new SpannableString(text);
404         TextUtils.copySpansFrom(source2, 0, source2.length(), BackgroundColorSpan.class, dest3, 0);
405         spans = dest3.getSpans(0, dest3.length(), Object.class);
406         assertEquals(0, spans.length);
407         TextUtils.copySpansFrom(source2, 0, source2.length(), URLSpan.class, dest3, 0);
408         spans = dest3.getSpans(0, dest3.length(), Object.class);
409         assertEquals(1, spans.length);
410 
411         SpannableString dest4 = new SpannableString("short");
412         try {
413             TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest4, 0);
414             fail("Should throw IndexOutOfBoundsException");
415         } catch (IndexOutOfBoundsException e) {
416             // expected
417         }
418         TextUtils.copySpansFrom(source2, 0, dest4.length(), Object.class, dest4, 0);
419         spans = dest4.getSpans(0, dest4.length(), Object.class);
420         assertEquals(1, spans.length);
421         assertEquals(0, dest4.getSpanStart(spans[0]));
422         // issue 1688347, not clear the expected result when 'start ~ end' only
423         // covered a part of the span.
424         assertEquals(dest4.length(), dest4.getSpanEnd(spans[0]));
425 
426         SpannableString dest5 = new SpannableString("longer content");
427         TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest5, 0);
428         spans = dest5.getSpans(0, 1, Object.class);
429         assertEquals(1, spans.length);
430 
431         dest5 = new SpannableString("longer content");
432         TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest5, 2);
433         spans = dest5.getSpans(0, 1, Object.class);
434         assertEquals(0, spans.length);
435         spans = dest5.getSpans(2, dest5.length(), Object.class);
436         assertEquals(1, spans.length);
437         try {
438             TextUtils.copySpansFrom(source2, 0, source2.length(),
439                     Object.class, dest5, dest5.length() - source2.length() + 2);
440             fail("Should throw IndexOutOfBoundsException");
441         } catch (IndexOutOfBoundsException e) {
442             // expected
443         }
444 
445         // issue 1688347, no javadoc about the expected behavior of the exceptional argument.
446         // exceptional source start
447         SpannableString dest6 = new SpannableString("exceptional test");
448         TextUtils.copySpansFrom(source2, -1, source2.length(), Object.class, dest6, 0);
449         spans = dest6.getSpans(0, dest6.length(), Object.class);
450         assertEquals(1, spans.length);
451         dest6 = new SpannableString("exceptional test");
452         TextUtils.copySpansFrom(source2, Integer.MAX_VALUE, source2.length() - 1,
453                     Object.class, dest6, 0);
454         spans = dest6.getSpans(0, dest6.length(), Object.class);
455         assertEquals(0, spans.length);
456 
457         // exceptional source end
458         dest6 = new SpannableString("exceptional test");
459         TextUtils.copySpansFrom(source2, 0, -1, Object.class, dest6, 0);
460         spans = dest6.getSpans(0, dest6.length(), Object.class);
461         assertEquals(0, spans.length);
462         TextUtils.copySpansFrom(source2, 0, Integer.MAX_VALUE, Object.class, dest6, 0);
463         spans = dest6.getSpans(0, dest6.length(), Object.class);
464         assertEquals(1, spans.length);
465 
466         // exceptional class kind
467         dest6 = new SpannableString("exceptional test");
468         TextUtils.copySpansFrom(source2, 0, source2.length(), null, dest6, 0);
469         spans = dest6.getSpans(0, dest6.length(), Object.class);
470         assertEquals(1, spans.length);
471 
472         // exceptional destination offset
473         dest6 = new SpannableString("exceptional test");
474         try {
475             TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest6, -1);
476             fail("Should throw IndexOutOfBoundsException");
477         } catch (IndexOutOfBoundsException e) {
478             // expect
479         }
480         try {
481             TextUtils.copySpansFrom(source2, 0, source2.length(),
482                     Object.class, dest6, Integer.MAX_VALUE);
483             fail("Should throw IndexOutOfBoundsException");
484         } catch (IndexOutOfBoundsException e) {
485             // expect
486         }
487 
488         // exceptional source
489         try {
490             TextUtils.copySpansFrom(null, 0, source2.length(), Object.class, dest6, 0);
491             fail("Should throw NullPointerException");
492         } catch (NullPointerException e) {
493             // expect
494         }
495 
496         // exceptional destination
497         try {
498             TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, null, 0);
499             fail("Should throw NullPointerException");
500         } catch (NullPointerException e) {
501             // expect
502         }
503     }
504 
505     @Test
testEllipsize()506     public void testEllipsize() {
507         TextPaint p = new TextPaint();
508 
509         // turn off kerning. with kerning enabled, different methods of measuring the same text
510         // produce different results.
511         p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG);
512 
513         CharSequence text = "long string to truncate";
514 
515         float textWidth = p.measureText(mEllipsis) + p.measureText("uncate");
516         assertEquals(mEllipsis + "uncate",
517                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.START).toString());
518 
519         textWidth = p.measureText("long str") + p.measureText(mEllipsis);
520         assertEquals("long str" + mEllipsis,
521                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.END).toString());
522 
523         textWidth = p.measureText("long") + p.measureText(mEllipsis) + p.measureText("ate");
524         assertEquals("long" + mEllipsis + "ate",
525                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE).toString());
526 
527         // issue 1688347, ellipsize() is not defined for TruncateAt.MARQUEE.
528         // In the code it looks like this does the same as MIDDLE.
529         // In other methods, MARQUEE is equivalent to END, except for the first line.
530         assertEquals("long" + mEllipsis + "ate",
531                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.MARQUEE).toString());
532 
533         textWidth = p.measureText(mEllipsis);
534         assertEquals("", TextUtils.ellipsize(text, p, textWidth, TruncateAt.END).toString());
535         assertEquals("", TextUtils.ellipsize(text, p, textWidth - 1, TruncateAt.END).toString());
536         assertEquals("", TextUtils.ellipsize(text, p, -1f, TruncateAt.END).toString());
537         assertEquals(text,
538                 TextUtils.ellipsize(text, p, Float.MAX_VALUE, TruncateAt.END).toString());
539 
540         assertEquals("", TextUtils.ellipsize(text, p, textWidth, TruncateAt.START).toString());
541         assertEquals("", TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE).toString());
542 
543         try {
544             TextUtils.ellipsize(text, null, textWidth, TruncateAt.MIDDLE);
545             fail("Should throw NullPointerException");
546         } catch (NullPointerException e) {
547             // expected
548         }
549 
550         try {
551             TextUtils.ellipsize(null, p, textWidth, TruncateAt.MIDDLE);
552             fail("Should throw NullPointerException");
553         } catch (NullPointerException e) {
554             // expected
555         }
556     }
557 
558     @Test
testEllipsize_emoji()559     public void testEllipsize_emoji() {
560         // 2 family emojis (11 code units + 11 code units).
561         final String text = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66"
562                 + "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66";
563 
564         final TextPaint p = new TextPaint();
565         final float width = p.measureText(text);
566 
567         final TextUtils.TruncateAt[] kinds = {TextUtils.TruncateAt.START,
568                 TextUtils.TruncateAt.MIDDLE, TextUtils.TruncateAt.END};
569         for (final TextUtils.TruncateAt kind : kinds) {
570             for (int i = 0; i <= 8; i++) {
571                 float avail = width * i / 7.0f;
572                 final String out = TextUtils.ellipsize(text, p, avail, kind).toString();
573                 assertTrue("kind: " + kind + ", avail: " + avail + ", out length: " + out.length(),
574                         out.length() == text.length()
575                                 || out.length() == text.length() / 2 + 1
576                                 || out.length() == 0);
577             }
578         }
579     }
580 
581     @Test
testEllipsizeCallback()582     public void testEllipsizeCallback() {
583         TextPaint p = new TextPaint();
584 
585         // turn off kerning. with kerning enabled, different methods of measuring the same text
586         // produce different results.
587         p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG);
588 
589         TextUtils.EllipsizeCallback callback = new TextUtils.EllipsizeCallback() {
590             public void ellipsized(final int start, final int end) {
591                 mStart = start;
592                 mEnd = end;
593             }
594         };
595 
596         String text = "long string to truncate";
597 
598         // TruncateAt.START, does not specify preserveLength
599         resetRange();
600         float textWidth = p.measureText(mEllipsis + "uncate");
601         assertEquals(mEllipsis + "uncate",
602                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.START, false,
603                         callback).toString());
604         assertEquals(0, mStart);
605         assertEquals(text.length() - "uncate".length(), mEnd);
606 
607         // TruncateAt.START, specify preserveLength
608         resetRange();
609         int ellipsisNum = text.length() - "uncate".length();
610         assertEquals(getBlankString(true, ellipsisNum) + "uncate",
611                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.START, true,
612                         callback).toString());
613         assertEquals(0, mStart);
614         assertEquals(text.length() - "uncate".length(), mEnd);
615 
616         // TruncateAt.END, specify preserveLength
617         resetRange();
618         textWidth = p.measureText("long str") + p.measureText(mEllipsis);
619         ellipsisNum = text.length() - "long str".length();
620         assertEquals("long str" + getBlankString(true, ellipsisNum),
621                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, true, callback).toString());
622         assertEquals("long str".length(), mStart);
623         assertEquals(text.length(), mEnd);
624 
625         // TruncateAt.MIDDLE, specify preserveLength
626         resetRange();
627         textWidth = p.measureText("long" + mEllipsis + "ate");
628         ellipsisNum = text.length() - "long".length() - "ate".length();
629         assertEquals("long" + getBlankString(true, ellipsisNum) + "ate",
630                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE, true,
631                         callback).toString());
632         assertEquals("long".length(), mStart);
633         assertEquals(text.length() - "ate".length(), mEnd);
634 
635         // TruncateAt.MIDDLE, specify preserveLength, but does not specify callback.
636         resetRange();
637         assertEquals("long" + getBlankString(true, ellipsisNum) + "ate",
638                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE, true,
639                         null).toString());
640         assertEquals(-1, mStart);
641         assertEquals(-1, mEnd);
642 
643         // TruncateAt.MARQUEE, specify preserveLength
644         // issue 1688347, ellipsize() is not defined for TruncateAt.MARQUEE.
645         // In the code it looks like this does the same as MIDDLE.
646         // In other methods, MARQUEE is equivalent to END, except for the first line.
647         resetRange();
648         textWidth = p.measureText("long" + mEllipsis + "ate");
649         ellipsisNum = text.length() - "long".length() - "ate".length();
650         assertEquals("long" + getBlankString(true, ellipsisNum) + "ate",
651                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.MARQUEE, true,
652                         callback).toString());
653         assertEquals("long".length(), mStart);
654         assertEquals(text.length() - "ate".length(), mEnd);
655 
656         // avail is not long enough for ELLIPSIS, and preserveLength is specified.
657         resetRange();
658         textWidth = p.measureText(mEllipsis);
659         assertEquals(getBlankString(false, text.length()),
660                 TextUtils.ellipsize(text, p, textWidth - 1f, TruncateAt.END, true,
661                         callback).toString());
662         assertEquals(0, mStart);
663         assertEquals(text.length(), mEnd);
664 
665         // avail is not long enough for ELLIPSIS, and preserveLength doesn't be specified.
666         resetRange();
667         assertEquals("",
668                 TextUtils.ellipsize(text, p, textWidth - 1f, TruncateAt.END, false,
669                         callback).toString());
670         assertEquals(0, mStart);
671         assertEquals(text.length(), mEnd);
672 
673         // avail is long enough for ELLIPSIS, and preserveLength is specified.
674         resetRange();
675         assertEquals(getBlankString(false, text.length()),
676                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, true, callback).toString());
677         assertEquals(0, mStart);
678         assertEquals(text.length(), mEnd);
679 
680         // avail is long enough for ELLIPSIS, and preserveLength doesn't be specified.
681         resetRange();
682         assertEquals("",
683                 TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, false,
684                         callback).toString());
685         assertEquals(0, mStart);
686         assertEquals(text.length(), mEnd);
687 
688         // avail is long enough for the whole sentence.
689         resetRange();
690         assertEquals(text,
691                 TextUtils.ellipsize(text, p, Float.MAX_VALUE, TruncateAt.END, true,
692                         callback).toString());
693         assertEquals(0, mStart);
694         assertEquals(0, mEnd);
695 
696         textWidth = p.measureText("long str" + mEllipsis);
697         try {
698             TextUtils.ellipsize(text, null, textWidth, TruncateAt.END, true, callback);
699         } catch (NullPointerException e) {
700             // expected
701         }
702 
703         try {
704             TextUtils.ellipsize(null, p, textWidth, TruncateAt.END, true, callback);
705         } catch (NullPointerException e) {
706             // expected
707         }
708     }
709 
710     /**
711      * Get a blank string which is filled up by '\uFEFF'.
712      *
713      * @param isNeedStart - boolean whether need to start with char '\u2026' in the string.
714      * @param len - int length of string.
715      * @return a blank string which is filled up by '\uFEFF'.
716      */
getBlankString(boolean isNeedStart, int len)717     private static String getBlankString(boolean isNeedStart, int len) {
718         StringBuilder buf = new StringBuilder();
719 
720         int i = 0;
721         if (isNeedStart) {
722             buf.append('\u2026');
723             i++;
724         }
725         for (; i < len; i++) {
726             buf.append('\uFEFF');
727         }
728 
729         return buf.toString();
730     }
731 
732     @Test
testEquals()733     public void testEquals() {
734         // compare with itself.
735         // String is a subclass of CharSequence and overrides equals().
736         String string = "same object";
737         assertTrue(TextUtils.equals(string, string));
738 
739         // SpannableString is a subclass of CharSequence and does NOT override equals().
740         SpannableString spanString = new SpannableString("same object");
741         final String url = "www.test_url.com";
742         spanString.setSpan(new URLSpan(url), 0, spanString.length(),
743                 Spanned.SPAN_INCLUSIVE_INCLUSIVE);
744         assertTrue(TextUtils.equals(spanString, spanString));
745 
746         // compare with other objects which have same content.
747         assertTrue(TextUtils.equals("different object", "different object"));
748 
749         SpannableString urlSpanString = new SpannableString("same content");
750         SpannableString bgColorSpanString = new SpannableString(
751                 "same content");
752         URLSpan urlSpan = new URLSpan(url);
753         urlSpanString.setSpan(urlSpan, 0, urlSpanString.length(),
754                 Spanned.SPAN_INCLUSIVE_INCLUSIVE);
755         BackgroundColorSpan bgColorSpan = new BackgroundColorSpan(Color.GREEN);
756         bgColorSpanString.setSpan(bgColorSpan, 0, bgColorSpanString.length(),
757                 Spanned.SPAN_INCLUSIVE_INCLUSIVE);
758 
759         assertTrue(TextUtils.equals(bgColorSpanString, urlSpanString));
760 
761         // compare with other objects which have different content.
762         assertFalse(TextUtils.equals("different content A", "different content B"));
763         assertFalse(TextUtils.equals(spanString, urlSpanString));
764         assertFalse(TextUtils.equals(spanString, bgColorSpanString));
765 
766         // compare with null
767         assertTrue(TextUtils.equals(null, null));
768         assertFalse(TextUtils.equals(spanString, null));
769         assertFalse(TextUtils.equals(null, string));
770     }
771 
772     @Test
testExpandTemplate()773     public void testExpandTemplate() {
774         // ^1 at the start of template string.
775         assertEquals("value1 template to be expanded",
776                 TextUtils.expandTemplate("^1 template to be expanded", "value1").toString());
777         // ^1 at the end of template string.
778         assertEquals("template to be expanded value1",
779                 TextUtils.expandTemplate("template to be expanded ^1", "value1").toString());
780         // ^1 in the middle of template string.
781         assertEquals("template value1 to be expanded",
782                 TextUtils.expandTemplate("template ^1 to be expanded", "value1").toString());
783         // ^1 followed by a '0'
784         assertEquals("template value10 to be expanded",
785                 TextUtils.expandTemplate("template ^10 to be expanded", "value1").toString());
786         // ^1 followed by a 'a'
787         assertEquals("template value1a to be expanded",
788                 TextUtils.expandTemplate("template ^1a to be expanded", "value1").toString());
789         // no ^1
790         assertEquals("template ^a to be expanded",
791                 TextUtils.expandTemplate("template ^a to be expanded", "value1").toString());
792         assertEquals("template to be expanded",
793                 TextUtils.expandTemplate("template to be expanded", "value1").toString());
794         // two consecutive ^ in the input to produce a single ^ in the output.
795         assertEquals("template ^ to be expanded",
796                 TextUtils.expandTemplate("template ^^ to be expanded", "value1").toString());
797         // two ^ with a space in the middle.
798         assertEquals("template ^ ^ to be expanded",
799                 TextUtils.expandTemplate("template ^ ^ to be expanded", "value1").toString());
800         // ^1 follow a '^'
801         assertEquals("template ^1 to be expanded",
802                 TextUtils.expandTemplate("template ^^1 to be expanded", "value1").toString());
803         // ^1 followed by a '^'
804         assertEquals("template value1^ to be expanded",
805                 TextUtils.expandTemplate("template ^1^ to be expanded", "value1").toString());
806 
807         // 9 replacement values
808         final int MAX_SUPPORTED_VALUES_NUM = 9;
809         CharSequence values[] = createCharSequenceArray(MAX_SUPPORTED_VALUES_NUM);
810         String expected = "value1 value2 template value3 value4 to value5 value6" +
811                 " be value7 value8 expanded value9";
812         String template = "^1 ^2 template ^3 ^4 to ^5 ^6 be ^7 ^8 expanded ^9";
813         assertEquals(expected, TextUtils.expandTemplate(template, values).toString());
814 
815         //  only up to 9 replacement values are supported
816         values = createCharSequenceArray(MAX_SUPPORTED_VALUES_NUM + 1);
817         try {
818             TextUtils.expandTemplate(template, values);
819             fail("Should throw IllegalArgumentException!");
820         } catch (IllegalArgumentException e) {
821             // expect
822         }
823     }
824 
825     @Test(expected=IllegalArgumentException.class)
testExpandTemplateCaret0WithValue()826     public void testExpandTemplateCaret0WithValue() {
827         // template string is ^0
828         TextUtils.expandTemplate("template ^0 to be expanded", "value1");
829     }
830 
831     @Test(expected=IllegalArgumentException.class)
testExpandTemplateCaret0NoValues()832     public void testExpandTemplateCaret0NoValues() {
833         // template string is ^0
834         TextUtils.expandTemplate("template ^0 to be expanded");
835     }
836 
837     @Test(expected=IllegalArgumentException.class)
testExpandTemplateNotEnoughValues()838     public void testExpandTemplateNotEnoughValues() {
839         // the template requests 2 values but only 1 is provided
840         TextUtils.expandTemplate("template ^2 to be expanded", "value1");
841     }
842 
843     @Test(expected=NullPointerException.class)
testExpandTemplateNullValues()844     public void testExpandTemplateNullValues() {
845         // values is null
846         TextUtils.expandTemplate("template ^2 to be expanded", (CharSequence[]) null);
847     }
848 
849     @Test(expected=IllegalArgumentException.class)
testExpandTemplateNotEnoughValuesAndFirstIsNull()850     public void testExpandTemplateNotEnoughValuesAndFirstIsNull() {
851         // the template requests 2 values but only one null value is provided
852         TextUtils.expandTemplate("template ^2 to be expanded", (CharSequence) null);
853     }
854 
855     @Test(expected=NullPointerException.class)
testExpandTemplateAllValuesAreNull()856     public void testExpandTemplateAllValuesAreNull() {
857         // the template requests 2 values and 2 values is provided, but all values are null.
858         TextUtils.expandTemplate("template ^2 to be expanded",
859                 (CharSequence) null, (CharSequence) null);
860     }
861 
862     @Test(expected=IllegalArgumentException.class)
testExpandTemplateNoValues()863     public void testExpandTemplateNoValues() {
864         // the template requests 2 values but no value is provided.
865         TextUtils.expandTemplate("template ^2 to be expanded");
866     }
867 
868     @Test(expected=NullPointerException.class)
testExpandTemplateNullTemplate()869     public void testExpandTemplateNullTemplate() {
870         // template is null
871         TextUtils.expandTemplate(null, "value1");
872     }
873 
874     /**
875      * Create a char sequence array with the specified length
876      * @param len the length of the array
877      * @return The char sequence array with the specified length.
878      * The value of each item is "value[index+1]"
879      */
createCharSequenceArray(int len)880     private static CharSequence[] createCharSequenceArray(int len) {
881         CharSequence array[] = new CharSequence[len];
882 
883         for (int i = 0; i < len; i++) {
884             array[i] = "value" + (i + 1);
885         }
886 
887         return array;
888     }
889 
890     @Test
testGetChars()891     public void testGetChars() {
892         char[] destOriginal = "destination".toCharArray();
893         char[] destResult = destOriginal.clone();
894 
895         // check whether GetChars.getChars() is called and with the proper parameters.
896         MockGetChars mockGetChars = new MockGetChars();
897         int start = 1;
898         int end = destResult.length;
899         int destOff = 2;
900         TextUtils.getChars(mockGetChars, start, end, destResult, destOff);
901         assertTrue(mockGetChars.hasCalledGetChars());
902         assertEquals(start, mockGetChars.ReadGetCharsParams().start);
903         assertEquals(end, mockGetChars.ReadGetCharsParams().end);
904         assertEquals(destResult, mockGetChars.ReadGetCharsParams().dest);
905         assertEquals(destOff, mockGetChars.ReadGetCharsParams().destoff);
906 
907         // use MockCharSequence to do the test includes corner cases.
908         MockCharSequence mockCharSequence = new MockCharSequence("source string mock");
909         // get chars to place at the beginning of the destination except the latest one char.
910         destResult = destOriginal.clone();
911         start = 0;
912         end = destResult.length - 1;
913         destOff = 0;
914         TextUtils.getChars(mockCharSequence, start, end, destResult, destOff);
915         // chars before end are copied from the mockCharSequence.
916         for (int i = 0; i < end - start; i++) {
917             assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]);
918         }
919         // chars after end doesn't be changed.
920         for (int i = destOff + (end - start); i < destOriginal.length; i++) {
921             assertEquals(destOriginal[i], destResult[i]);
922         }
923 
924         // get chars to place at the end of the destination except the earliest two chars.
925         destResult = destOriginal.clone();
926         start = 0;
927         end = destResult.length - 2;
928         destOff = 2;
929         TextUtils.getChars(mockCharSequence, start, end, destResult, destOff);
930         // chars before start doesn't be changed.
931         for (int i = 0; i < destOff; i++) {
932             assertEquals(destOriginal[i], destResult[i]);
933         }
934         // chars after start are copied from the mockCharSequence.
935         for (int i = 0; i < end - start; i++) {
936             assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]);
937         }
938 
939         // get chars to place at the end of the destination except the earliest two chars
940         // and the latest one word.
941         destResult = destOriginal.clone();
942         start = 1;
943         end = destResult.length - 2;
944         destOff = 0;
945         TextUtils.getChars(mockCharSequence, start, end, destResult, destOff);
946         for (int i = 0; i < destOff; i++) {
947             assertEquals(destOriginal[i], destResult[i]);
948         }
949         for (int i = 0; i < end - start; i++) {
950             assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]);
951         }
952         for (int i = destOff + (end - start); i < destOriginal.length; i++) {
953             assertEquals(destOriginal[i], destResult[i]);
954         }
955 
956         // get chars to place the whole of the destination
957         destResult = destOriginal.clone();
958         start = 0;
959         end = destResult.length;
960         destOff = 0;
961         TextUtils.getChars(mockCharSequence, start, end, destResult, destOff);
962         for (int i = 0; i < end - start; i++) {
963             assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]);
964         }
965 
966         // exceptional start.
967         end = 2;
968         destOff = 0;
969         destResult = destOriginal.clone();
970         try {
971             TextUtils.getChars(mockCharSequence, -1, end, destResult, destOff);
972             fail("Should throw IndexOutOfBoundsException!");
973         } catch (IndexOutOfBoundsException e) {
974             // expected
975         }
976 
977         destResult = destOriginal.clone();
978         TextUtils.getChars(mockCharSequence, Integer.MAX_VALUE, end, destResult, destOff);
979         for (int i = 0; i < destResult.length; i++) {
980             assertEquals(destOriginal[i], destResult[i]);
981         }
982 
983         // exceptional end.
984         destResult = destOriginal.clone();
985         start = 0;
986         destOff = 0;
987         try {
988             TextUtils.getChars(mockCharSequence, start, destResult.length + 1, destResult, destOff);
989             fail("Should throw IndexOutOfBoundsException!");
990         } catch (IndexOutOfBoundsException e) {
991             // expected
992         }
993 
994         destResult = destOriginal.clone();
995         TextUtils.getChars(mockCharSequence, start, -1, destResult, destOff);
996         for (int i = 0; i < destResult.length; i++) {
997             assertEquals(destOriginal[i], destResult[i]);
998         }
999 
1000         // exceptional destOff.
1001         destResult = destOriginal.clone();
1002         start = 0;
1003         end = 2;
1004         try {
1005             TextUtils.getChars(mockCharSequence, start, end, destResult, Integer.MAX_VALUE);
1006             fail("Should throw IndexOutOfBoundsException!");
1007         } catch (IndexOutOfBoundsException e) {
1008             // expect
1009         }
1010         try {
1011             TextUtils.getChars(mockCharSequence, start, end, destResult, Integer.MIN_VALUE);
1012             fail("Should throw IndexOutOfBoundsException!");
1013         } catch (IndexOutOfBoundsException e) {
1014             // expect
1015         }
1016 
1017         // exceptional source
1018         start = 0;
1019         end = 2;
1020         destOff =0;
1021         try {
1022             TextUtils.getChars(null, start, end, destResult, destOff);
1023             fail("Should throw NullPointerException!");
1024         } catch (NullPointerException e) {
1025             // expected
1026         }
1027 
1028         // exceptional destination
1029         try {
1030             TextUtils.getChars(mockCharSequence, start, end, null, destOff);
1031             fail("Should throw NullPointerException!");
1032         } catch (NullPointerException e) {
1033             // expected
1034         }
1035     }
1036 
1037     /**
1038      * MockGetChars for test.
1039      */
1040     private static class MockGetChars implements GetChars {
1041         private boolean mHasCalledGetChars;
1042         private GetCharsParams mGetCharsParams = new GetCharsParams();
1043 
1044         class GetCharsParams {
1045             int start;
1046             int end;
1047             char[] dest;
1048             int destoff;
1049         }
1050 
hasCalledGetChars()1051         public boolean hasCalledGetChars() {
1052             return mHasCalledGetChars;
1053         }
1054 
reset()1055         public void reset() {
1056             mHasCalledGetChars = false;
1057         }
1058 
ReadGetCharsParams()1059         public GetCharsParams ReadGetCharsParams() {
1060             return mGetCharsParams;
1061         }
1062 
getChars(int start, int end, char[] dest, int destoff)1063         public void getChars(int start, int end, char[] dest, int destoff) {
1064             mHasCalledGetChars = true;
1065             mGetCharsParams.start = start;
1066             mGetCharsParams.end = end;
1067             mGetCharsParams.dest = dest;
1068             mGetCharsParams.destoff = destoff;
1069         }
1070 
charAt(int arg0)1071         public char charAt(int arg0) {
1072             return 0;
1073         }
1074 
length()1075         public int length() {
1076             return 100;
1077         }
1078 
subSequence(int arg0, int arg1)1079         public CharSequence subSequence(int arg0, int arg1) {
1080             return null;
1081         }
1082     }
1083 
1084     /**
1085      * MockCharSequence for test.
1086      */
1087     private static class MockCharSequence implements CharSequence {
1088         private char mText[];
1089 
MockCharSequence(String text)1090         public MockCharSequence(String text) {
1091             mText = text.toCharArray();
1092         }
1093 
charAt(int arg0)1094         public char charAt(int arg0) {
1095             if (arg0 >= 0 && arg0 < mText.length) {
1096                 return mText[arg0];
1097             }
1098             throw new IndexOutOfBoundsException();
1099         }
1100 
length()1101         public int length() {
1102             return mText.length;
1103         }
1104 
subSequence(int arg0, int arg1)1105         public CharSequence subSequence(int arg0, int arg1) {
1106             return null;
1107         }
1108     }
1109 
1110     @Test
testGetOffsetAfter()1111     public void testGetOffsetAfter() {
1112         // the first '\uD800' is index 9, the second 'uD800' is index 16
1113         // the '\uDBFF' is index 26
1114         final int POS_FIRST_D800 = 9;       // the position of the first '\uD800'.
1115         final int POS_SECOND_D800 = 16;
1116         final int POS_FIRST_DBFF = 26;
1117         final int SUPPLEMENTARY_CHARACTERS_OFFSET = 2;  // the offset for a supplementary characters
1118         final int NORMAL_CHARACTERS_OFFSET = 1;
1119         SpannableString text = new SpannableString(
1120                 "string to\uD800\uDB00 get \uD800\uDC00 offset \uDBFF\uDFFF after");
1121         assertEquals(0 + 1, TextUtils.getOffsetAfter(text, 0));
1122         assertEquals(text.length(), TextUtils.getOffsetAfter(text, text.length()));
1123         assertEquals(text.length(), TextUtils.getOffsetAfter(text, text.length() - 1));
1124         assertEquals(POS_FIRST_D800 + NORMAL_CHARACTERS_OFFSET,
1125                 TextUtils.getOffsetAfter(text, POS_FIRST_D800));
1126         assertEquals(POS_SECOND_D800 + SUPPLEMENTARY_CHARACTERS_OFFSET,
1127                 TextUtils.getOffsetAfter(text, POS_SECOND_D800));
1128         assertEquals(POS_FIRST_DBFF + SUPPLEMENTARY_CHARACTERS_OFFSET,
1129                 TextUtils.getOffsetAfter(text, POS_FIRST_DBFF));
1130 
1131         // the CharSequence string has a span.
1132         ReplacementSpan mockReplacementSpan = mock(ReplacementSpan.class);
1133         when(mockReplacementSpan.getSize(any(), any(), anyInt(), anyInt(), any())).thenReturn(0);
1134         text.setSpan(mockReplacementSpan, POS_FIRST_D800 - 1, text.length() - 1,
1135                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1136         assertEquals(text.length() - 1, TextUtils.getOffsetAfter(text, POS_FIRST_D800));
1137 
1138         try {
1139             TextUtils.getOffsetAfter(text, -1);
1140             fail("Should throw IndexOutOfBoundsException!");
1141         } catch (IndexOutOfBoundsException e) {
1142         }
1143 
1144         try {
1145             TextUtils.getOffsetAfter(text, Integer.MAX_VALUE);
1146             fail("Should throw IndexOutOfBoundsException!");
1147         } catch (IndexOutOfBoundsException e) {
1148         }
1149 
1150         try {
1151             TextUtils.getOffsetAfter(null, 0);
1152             fail("Should throw NullPointerException!");
1153         } catch (NullPointerException e) {
1154             // expected
1155         }
1156     }
1157 
1158     @Test
testGetOffsetBefore()1159     public void testGetOffsetBefore() {
1160         // the first '\uDC00' is index 10, the second 'uDC00' is index 17
1161         // the '\uDFFF' is index 27
1162         final int POS_FIRST_DC00 = 10;
1163         final int POS_SECOND_DC00 = 17;
1164         final int POS_FIRST_DFFF = 27;
1165         final int SUPPLYMENTARY_CHARACTERS_OFFSET = 2;
1166         final int NORMAL_CHARACTERS_OFFSET = 1;
1167         SpannableString text = new SpannableString(
1168                 "string to\uD700\uDC00 get \uD800\uDC00 offset \uDBFF\uDFFF before");
1169         assertEquals(0, TextUtils.getOffsetBefore(text, 0));
1170         assertEquals(0, TextUtils.getOffsetBefore(text, 1));
1171         assertEquals(text.length() - 1, TextUtils.getOffsetBefore(text, text.length()));
1172         assertEquals(POS_FIRST_DC00 + 1 - NORMAL_CHARACTERS_OFFSET,
1173                 TextUtils.getOffsetBefore(text, POS_FIRST_DC00 + 1));
1174         assertEquals(POS_SECOND_DC00 + 1 - SUPPLYMENTARY_CHARACTERS_OFFSET,
1175                 TextUtils.getOffsetBefore(text, POS_SECOND_DC00 + 1));
1176         assertEquals(POS_FIRST_DFFF + 1 - SUPPLYMENTARY_CHARACTERS_OFFSET,
1177                 TextUtils.getOffsetBefore(text, POS_FIRST_DFFF + 1));
1178 
1179         // the CharSequence string has a span.
1180         ReplacementSpan mockReplacementSpan = mock(ReplacementSpan.class);
1181         when(mockReplacementSpan.getSize(any(), any(), anyInt(), anyInt(), any())).thenReturn(0);
1182         text.setSpan(mockReplacementSpan, 0, POS_FIRST_DC00 + 1,
1183                 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
1184         assertEquals(0, TextUtils.getOffsetBefore(text, POS_FIRST_DC00));
1185 
1186         try {
1187             TextUtils.getOffsetBefore(text, -1);
1188             fail("Should throw IndexOutOfBoundsException!");
1189         } catch (IndexOutOfBoundsException e) {
1190         }
1191 
1192         try {
1193             TextUtils.getOffsetBefore(text, Integer.MAX_VALUE);
1194             fail("Should throw IndexOutOfBoundsException!");
1195         } catch (IndexOutOfBoundsException e) {
1196         }
1197 
1198         try {
1199             TextUtils.getOffsetBefore(null, POS_FIRST_DC00);
1200             fail("Should throw NullPointerException!");
1201         } catch (NullPointerException e) {
1202             // expected
1203         }
1204     }
1205 
1206     @Test
testGetReverse()1207     public void testGetReverse() {
1208         String source = "string to be reversed";
1209         assertEquals("gnirts", TextUtils.getReverse(source, 0, "string".length()).toString());
1210         assertEquals("desrever",
1211                 TextUtils.getReverse(source, source.length() - "reversed".length(),
1212                         source.length()).toString());
1213         assertEquals("", TextUtils.getReverse(source, 0, 0).toString());
1214 
1215         // issue 1695243, exception is thrown after the result of some cases
1216         // convert to a string, is this expected?
1217         CharSequence result = TextUtils.getReverse(source, -1, "string".length());
1218         try {
1219             result.toString();
1220             fail("Should throw IndexOutOfBoundsException!");
1221         } catch (IndexOutOfBoundsException e) {
1222         }
1223 
1224         TextUtils.getReverse(source, 0, source.length() + 1);
1225         try {
1226             result.toString();
1227             fail("Should throw IndexOutOfBoundsException!");
1228         } catch (IndexOutOfBoundsException e) {
1229         }
1230 
1231         TextUtils.getReverse(source, "string".length(), 0);
1232         try {
1233             result.toString();
1234             fail("Should throw IndexOutOfBoundsException!");
1235         } catch (IndexOutOfBoundsException e) {
1236         }
1237 
1238         TextUtils.getReverse(source, 0, Integer.MAX_VALUE);
1239         try {
1240             result.toString();
1241             fail("Should throw IndexOutOfBoundsException!");
1242         } catch (IndexOutOfBoundsException e) {
1243         }
1244 
1245         TextUtils.getReverse(source, Integer.MIN_VALUE, "string".length());
1246         try {
1247             result.toString();
1248             fail("Should throw IndexOutOfBoundsException!");
1249         } catch (IndexOutOfBoundsException e) {
1250         }
1251 
1252         TextUtils.getReverse(null, 0, "string".length());
1253         try {
1254             result.toString();
1255             fail("Should throw IndexOutOfBoundsException!");
1256         } catch (IndexOutOfBoundsException e) {
1257             // expected
1258         }
1259     }
1260 
1261     @Test
testGetTrimmedLength()1262     public void testGetTrimmedLength() {
1263         assertEquals("normalstring".length(), TextUtils.getTrimmedLength("normalstring"));
1264         assertEquals("normal string".length(), TextUtils.getTrimmedLength("normal string"));
1265         assertEquals("blank before".length(), TextUtils.getTrimmedLength(" \t  blank before"));
1266         assertEquals("blank after".length(), TextUtils.getTrimmedLength("blank after   \n    "));
1267         assertEquals("blank both".length(), TextUtils.getTrimmedLength(" \t   blank both  \n "));
1268 
1269         char[] allTrimmedChars = new char[]{
1270                 '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007',
1271                 '\u0008', '\u0009', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015',
1272                 '\u0016', '\u0017', '\u0018', '\u0019', '\u0020'
1273         };
1274         assertEquals(0, TextUtils.getTrimmedLength(String.valueOf(allTrimmedChars)));
1275     }
1276 
1277     @Test(expected=NullPointerException.class)
testGetTrimmedLengthNull()1278     public void testGetTrimmedLengthNull() {
1279         TextUtils.getTrimmedLength(null);
1280     }
1281 
1282     @Test
testHtmlEncode()1283     public void testHtmlEncode() {
1284         assertEquals("&lt;_html_&gt;\\ &amp;&quot;&#39;string&#39;&quot;",
1285                 TextUtils.htmlEncode("<_html_>\\ &\"'string'\""));
1286     }
1287 
1288     @Test(expected=NullPointerException.class)
testHtmlEncodeNull()1289     public void testHtmlEncodeNull() {
1290          TextUtils.htmlEncode(null);
1291     }
1292 
1293     @Test
testIndexOf1()1294     public void testIndexOf1() {
1295         String searchString = "string to be searched";
1296         final int INDEX_OF_FIRST_R = 2;     // first occurrence of 'r'
1297         final int INDEX_OF_FIRST_T = 1;
1298         final int INDEX_OF_FIRST_D = searchString.length() - 1;
1299 
1300         assertEquals(INDEX_OF_FIRST_T, TextUtils.indexOf(searchString, 't'));
1301         assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(searchString, 'r'));
1302         assertEquals(INDEX_OF_FIRST_D, TextUtils.indexOf(searchString, 'd'));
1303         assertEquals(-1, TextUtils.indexOf(searchString, 'f'));
1304 
1305         StringBuffer stringBuffer = new StringBuffer(searchString);
1306         assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(stringBuffer, 'r'));
1307 
1308         StringBuilder stringBuilder = new StringBuilder(searchString);
1309         assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(stringBuilder, 'r'));
1310 
1311         MockGetChars mockGetChars = new MockGetChars();
1312         assertFalse(mockGetChars.hasCalledGetChars());
1313         TextUtils.indexOf(mockGetChars, 'r');
1314         assertTrue(mockGetChars.hasCalledGetChars());
1315 
1316         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1317         assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(mockCharSequence, 'r'));
1318     }
1319 
1320     @Test
testIndexOf2()1321     public void testIndexOf2() {
1322         String searchString = "string to be searched";
1323         final int INDEX_OF_FIRST_R = 2;
1324         final int INDEX_OF_SECOND_R = 16;
1325 
1326         assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(searchString, 'r', 0));
1327         assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(searchString, 'r', INDEX_OF_FIRST_R + 1));
1328         assertEquals(-1, TextUtils.indexOf(searchString, 'r', searchString.length()));
1329         assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(searchString, 'r', Integer.MIN_VALUE));
1330         assertEquals(-1, TextUtils.indexOf(searchString, 'r', Integer.MAX_VALUE));
1331 
1332         StringBuffer stringBuffer = new StringBuffer(searchString);
1333         assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(stringBuffer, 'r', INDEX_OF_FIRST_R + 1));
1334         try {
1335             TextUtils.indexOf(stringBuffer, 'r', Integer.MIN_VALUE);
1336             fail("Should throw IndexOutOfBoundsException!");
1337         } catch (IndexOutOfBoundsException e) {
1338             // expect
1339         }
1340         assertEquals(-1, TextUtils.indexOf(stringBuffer, 'r', Integer.MAX_VALUE));
1341 
1342         StringBuilder stringBuilder = new StringBuilder(searchString);
1343         assertEquals(INDEX_OF_SECOND_R,
1344                 TextUtils.indexOf(stringBuilder, 'r', INDEX_OF_FIRST_R + 1));
1345 
1346         MockGetChars mockGetChars = new MockGetChars();
1347         TextUtils.indexOf(mockGetChars, 'r', INDEX_OF_FIRST_R + 1);
1348         assertTrue(mockGetChars.hasCalledGetChars());
1349 
1350         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1351         assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(mockCharSequence, 'r',
1352                 INDEX_OF_FIRST_R + 1));
1353     }
1354 
1355     @Test
testIndexOf3()1356     public void testIndexOf3() {
1357         String searchString = "string to be searched";
1358         final int INDEX_OF_FIRST_R = 2;
1359         final int INDEX_OF_SECOND_R = 16;
1360 
1361         assertEquals(INDEX_OF_FIRST_R,
1362                 TextUtils.indexOf(searchString, 'r', 0, searchString.length()));
1363         assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(searchString, 'r',
1364                 INDEX_OF_FIRST_R + 1, searchString.length()));
1365         assertEquals(-1, TextUtils.indexOf(searchString, 'r',
1366                 INDEX_OF_FIRST_R + 1, INDEX_OF_SECOND_R));
1367 
1368         try {
1369             TextUtils.indexOf(searchString, 'r', Integer.MIN_VALUE, INDEX_OF_SECOND_R);
1370             fail("Should throw IndexOutOfBoundsException!");
1371         } catch (IndexOutOfBoundsException e) {
1372             // expect
1373         }
1374         assertEquals(-1,
1375                 TextUtils.indexOf(searchString, 'r', Integer.MAX_VALUE, INDEX_OF_SECOND_R));
1376         assertEquals(-1, TextUtils.indexOf(searchString, 'r', 0, Integer.MIN_VALUE));
1377         try {
1378             TextUtils.indexOf(searchString, 'r', 0, Integer.MAX_VALUE);
1379             fail("Should throw IndexOutOfBoundsException!");
1380         } catch (IndexOutOfBoundsException e) {
1381             // expect
1382         }
1383 
1384         StringBuffer stringBuffer = new StringBuffer(searchString);
1385         assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(stringBuffer, 'r',
1386                 INDEX_OF_FIRST_R + 1, searchString.length()));
1387 
1388         StringBuilder stringBuilder = new StringBuilder(searchString);
1389         assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(stringBuilder, 'r',
1390                 INDEX_OF_FIRST_R + 1, searchString.length()));
1391 
1392         MockGetChars mockGetChars = new MockGetChars();
1393         TextUtils.indexOf(mockGetChars, 'r', INDEX_OF_FIRST_R + 1, searchString.length());
1394         assertTrue(mockGetChars.hasCalledGetChars());
1395 
1396         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1397         assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(mockCharSequence, 'r',
1398                 INDEX_OF_FIRST_R + 1, searchString.length()));
1399     }
1400 
1401     @Test
testIndexOf4()1402     public void testIndexOf4() {
1403         String searchString = "string to be searched by string";
1404         final int SEARCH_INDEX = 13;
1405 
1406         assertEquals(0, TextUtils.indexOf(searchString, "string"));
1407         assertEquals(SEARCH_INDEX, TextUtils.indexOf(searchString, "search"));
1408         assertEquals(-1, TextUtils.indexOf(searchString, "tobe"));
1409         assertEquals(0, TextUtils.indexOf(searchString, ""));
1410 
1411         StringBuffer stringBuffer = new StringBuffer(searchString);
1412         assertEquals(SEARCH_INDEX, TextUtils.indexOf(stringBuffer, "search"));
1413 
1414         StringBuilder stringBuilder = new StringBuilder(searchString);
1415         assertEquals(SEARCH_INDEX, TextUtils.indexOf(stringBuilder, "search"));
1416 
1417         MockGetChars mockGetChars = new MockGetChars();
1418         TextUtils.indexOf(mockGetChars, "search");
1419         assertTrue(mockGetChars.hasCalledGetChars());
1420 
1421         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1422         assertEquals(SEARCH_INDEX, TextUtils.indexOf(mockCharSequence, "search"));
1423     }
1424 
1425     @Test
testIndexOf5()1426     public void testIndexOf5() {
1427         String searchString = "string to be searched by string";
1428         final int INDEX_OF_FIRST_STRING = 0;
1429         final int INDEX_OF_SECOND_STRING = 25;
1430 
1431         assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string", 0));
1432         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string",
1433                 INDEX_OF_FIRST_STRING + 1));
1434         assertEquals(-1, TextUtils.indexOf(searchString, "string", INDEX_OF_SECOND_STRING + 1));
1435         assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string",
1436                 Integer.MIN_VALUE));
1437         assertEquals(-1, TextUtils.indexOf(searchString, "string", Integer.MAX_VALUE));
1438 
1439         assertEquals(1, TextUtils.indexOf(searchString, "", 1));
1440         assertEquals(Integer.MAX_VALUE, TextUtils.indexOf(searchString, "", Integer.MAX_VALUE));
1441 
1442         assertEquals(0, TextUtils.indexOf(searchString, searchString, 0));
1443         assertEquals(-1, TextUtils.indexOf(searchString, searchString + "longer needle", 0));
1444 
1445         StringBuffer stringBuffer = new StringBuffer(searchString);
1446         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer, "string",
1447                 INDEX_OF_FIRST_STRING + 1));
1448         try {
1449             TextUtils.indexOf(stringBuffer, "string", Integer.MIN_VALUE);
1450             fail("Should throw IndexOutOfBoundsException!");
1451         } catch (IndexOutOfBoundsException e) {
1452             // expect
1453         }
1454         assertEquals(-1, TextUtils.indexOf(stringBuffer, "string", Integer.MAX_VALUE));
1455 
1456         StringBuilder stringBuilder = new StringBuilder(searchString);
1457         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuilder, "string",
1458                 INDEX_OF_FIRST_STRING + 1));
1459 
1460         MockGetChars mockGetChars = new MockGetChars();
1461         assertFalse(mockGetChars.hasCalledGetChars());
1462         TextUtils.indexOf(mockGetChars, "string", INDEX_OF_FIRST_STRING + 1);
1463         assertTrue(mockGetChars.hasCalledGetChars());
1464 
1465         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1466         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(mockCharSequence, "string",
1467                 INDEX_OF_FIRST_STRING + 1));
1468     }
1469 
1470     @Test
testIndexOf6()1471     public void testIndexOf6() {
1472         String searchString = "string to be searched by string";
1473         final int INDEX_OF_FIRST_STRING = 0;
1474         final int INDEX_OF_SECOND_STRING = 25;
1475 
1476         assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string", 0,
1477                 searchString.length()));
1478         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string",
1479                 INDEX_OF_FIRST_STRING + 1, searchString.length()));
1480         assertEquals(-1, TextUtils.indexOf(searchString, "string", INDEX_OF_FIRST_STRING + 1,
1481                 INDEX_OF_SECOND_STRING - 1));
1482         assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string",
1483                 Integer.MIN_VALUE, INDEX_OF_SECOND_STRING - 1));
1484         assertEquals(-1, TextUtils.indexOf(searchString, "string", Integer.MAX_VALUE,
1485                 INDEX_OF_SECOND_STRING - 1));
1486 
1487         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string",
1488                 INDEX_OF_FIRST_STRING + 1, Integer.MIN_VALUE));
1489         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string",
1490                 INDEX_OF_FIRST_STRING + 1, Integer.MAX_VALUE));
1491 
1492         StringBuffer stringBuffer = new StringBuffer(searchString);
1493         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer, "string",
1494                 INDEX_OF_FIRST_STRING + 1, searchString.length()));
1495         try {
1496             TextUtils.indexOf(stringBuffer, "string", Integer.MIN_VALUE,
1497                     INDEX_OF_SECOND_STRING - 1);
1498             fail("Should throw IndexOutOfBoundsException!");
1499         } catch (IndexOutOfBoundsException e) {
1500             // expect
1501         }
1502         assertEquals(-1, TextUtils.indexOf(stringBuffer, "string", Integer.MAX_VALUE,
1503                 searchString.length()));
1504         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer,
1505                 "string", INDEX_OF_FIRST_STRING + 1, Integer.MIN_VALUE));
1506         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer,
1507                 "string", INDEX_OF_FIRST_STRING + 1, Integer.MAX_VALUE));
1508 
1509         StringBuilder stringBuilder = new StringBuilder(searchString);
1510         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuilder, "string",
1511                 INDEX_OF_FIRST_STRING + 1, searchString.length()));
1512 
1513         MockGetChars mockGetChars = new MockGetChars();
1514         TextUtils.indexOf(mockGetChars, "string", INDEX_OF_FIRST_STRING + 1, searchString.length());
1515         assertTrue(mockGetChars.hasCalledGetChars());
1516 
1517         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1518         assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(mockCharSequence, "string",
1519                 INDEX_OF_FIRST_STRING + 1, searchString.length()));
1520     }
1521 
1522     @Test
testIsDigitsOnly()1523     public void testIsDigitsOnly() {
1524         assertTrue(TextUtils.isDigitsOnly(""));
1525         assertFalse(TextUtils.isDigitsOnly("no digit"));
1526         assertFalse(TextUtils.isDigitsOnly("character and 56 digits"));
1527         assertTrue(TextUtils.isDigitsOnly("0123456789"));
1528         assertFalse(TextUtils.isDigitsOnly("1234 56789"));
1529 
1530         // U+104A0 OSMANYA DIGIT ZERO
1531         assertTrue(TextUtils.isDigitsOnly(new String(Character.toChars(0x104A0))));
1532         // U+10858 IMPERIAL ARAMAIC NUMBER ONE
1533         assertFalse(TextUtils.isDigitsOnly(new String(Character.toChars(0x10858))));
1534 
1535         assertFalse(TextUtils.isDigitsOnly("\uD801")); // lonely lead surrogate
1536         assertFalse(TextUtils.isDigitsOnly("\uDCA0")); // lonely trailing surrogate
1537     }
1538 
1539     @Test(expected=NullPointerException.class)
testIsDigitsOnlyNull()1540     public void testIsDigitsOnlyNull() {
1541         TextUtils.isDigitsOnly(null);
1542     }
1543 
1544     @Test
testIsEmpty()1545     public void testIsEmpty() {
1546         assertFalse(TextUtils.isEmpty("not empty"));
1547         assertFalse(TextUtils.isEmpty("    "));
1548         assertTrue(TextUtils.isEmpty(""));
1549         assertTrue(TextUtils.isEmpty(null));
1550     }
1551 
1552     @Test
testIsGraphicChar()1553     public void testIsGraphicChar() {
1554         assertTrue(TextUtils.isGraphic('a'));
1555         assertTrue(TextUtils.isGraphic('\uBA00'));
1556 
1557         // LINE_SEPARATOR
1558         assertFalse(TextUtils.isGraphic('\u2028'));
1559 
1560         // PARAGRAPH_SEPARATOR
1561         assertFalse(TextUtils.isGraphic('\u2029'));
1562 
1563         // CONTROL
1564         assertFalse(TextUtils.isGraphic('\u0085'));
1565 
1566         // UNASSIGNED
1567         assertFalse(TextUtils.isGraphic('\uFFFF'));
1568 
1569         // SURROGATE
1570         assertFalse(TextUtils.isGraphic('\uD800'));
1571 
1572         // SPACE_SEPARATOR
1573         assertFalse(TextUtils.isGraphic('\u0020'));
1574     }
1575 
1576     @Test(expected=NullPointerException.class)
testIsGraphicCharNull()1577     public void testIsGraphicCharNull() {
1578         assertFalse(TextUtils.isGraphic((Character) null));
1579     }
1580 
1581     @Test
testIsGraphicCharSequence()1582     public void testIsGraphicCharSequence() {
1583         assertTrue(TextUtils.isGraphic("printable characters"));
1584 
1585         assertFalse(TextUtils.isGraphic("\u2028\u2029\u0085\uFFFF\uD800\u0020"));
1586 
1587         assertTrue(TextUtils.isGraphic("a\u2028\u2029\u0085\uFFFF\uD800\u0020"));
1588 
1589         assertTrue(TextUtils.isGraphic("\uD83D\uDC0C")); // U+1F40C SNAIL
1590         assertFalse(TextUtils.isGraphic("\uDB40\uDC01")); // U+E0000 (unassigned)
1591         assertFalse(TextUtils.isGraphic("\uDB3D")); // unpaired high surrogate
1592         assertFalse(TextUtils.isGraphic("\uDC0C")); // unpaired low surrogate
1593     }
1594 
1595     @Test(expected=NullPointerException.class)
testIsGraphicCharSequenceNull()1596     public void testIsGraphicCharSequenceNull() {
1597         TextUtils.isGraphic(null);
1598     }
1599 
1600     @Test
testJoinIterable()1601     public void testJoinIterable() {
1602         ArrayList<CharSequence> charTokens = new ArrayList<>();
1603         charTokens.add("string1");
1604         charTokens.add("string2");
1605         charTokens.add("string3");
1606         assertEquals("string1|string2|string3", TextUtils.join("|", charTokens));
1607         assertEquals("string1; string2; string3", TextUtils.join("; ", charTokens));
1608         assertEquals("string1string2string3", TextUtils.join("", charTokens));
1609 
1610         // issue 1695243, not clear what is supposed result if the delimiter or tokens are null.
1611         assertEquals("string1nullstring2nullstring3", TextUtils.join(null, charTokens));
1612 
1613         ArrayList<SpannableString> spannableStringTokens = new ArrayList<SpannableString>();
1614         spannableStringTokens.add(new SpannableString("span 1"));
1615         spannableStringTokens.add(new SpannableString("span 2"));
1616         spannableStringTokens.add(new SpannableString("span 3"));
1617         assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens));
1618 
1619         assertEquals("", TextUtils.join("|", new ArrayList<CharSequence>()));
1620     }
1621 
1622     @Test(expected=NullPointerException.class)
testJoinIterableNull()1623     public void testJoinIterableNull() {
1624         TextUtils.join("|", (Iterable) null);
1625     }
1626 
1627     @Test
testJoinArray()1628     public void testJoinArray() {
1629         CharSequence[] charTokens = new CharSequence[] { "string1", "string2", "string3" };
1630         assertEquals("string1|string2|string3", TextUtils.join("|", charTokens));
1631         assertEquals("string1; string2; string3", TextUtils.join("; ", charTokens));
1632         assertEquals("string1string2string3", TextUtils.join("", charTokens));
1633 
1634         // issue 1695243, not clear what is supposed result if the delimiter or tokens are null.
1635         assertEquals("string1nullstring2nullstring3", TextUtils.join(null, charTokens));
1636 
1637         SpannableString[] spannableStringTokens = new SpannableString[] {
1638                 new SpannableString("span 1"),
1639                 new SpannableString("span 2"),
1640                 new SpannableString("span 3") };
1641         assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens));
1642 
1643         assertEquals("", TextUtils.join("|", new String[0]));
1644     }
1645 
1646     @Test(expected=NullPointerException.class)
testJoinArrayNull()1647     public void testJoinArrayNull() {
1648         TextUtils.join("|", (Object[]) null);
1649     }
1650 
1651     @Test
testLastIndexOf1()1652     public void testLastIndexOf1() {
1653         String searchString = "string to be searched";
1654         final int INDEX_OF_LAST_R = 16;
1655         final int INDEX_OF_LAST_T = 7;
1656         final int INDEX_OF_LAST_D = searchString.length() - 1;
1657 
1658         assertEquals(INDEX_OF_LAST_T, TextUtils.lastIndexOf(searchString, 't'));
1659         assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(searchString, 'r'));
1660         assertEquals(INDEX_OF_LAST_D, TextUtils.lastIndexOf(searchString, 'd'));
1661         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'f'));
1662 
1663         StringBuffer stringBuffer = new StringBuffer(searchString);
1664         assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(stringBuffer, 'r'));
1665 
1666         StringBuilder stringBuilder = new StringBuilder(searchString);
1667         assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(stringBuilder, 'r'));
1668 
1669         MockGetChars mockGetChars = new MockGetChars();
1670         TextUtils.lastIndexOf(mockGetChars, 'r');
1671         assertTrue(mockGetChars.hasCalledGetChars());
1672 
1673         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1674         assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(mockCharSequence, 'r'));
1675     }
1676 
1677     @Test
testLastIndexOf2()1678     public void testLastIndexOf2() {
1679         String searchString = "string to be searched";
1680         final int INDEX_OF_FIRST_R = 2;
1681         final int INDEX_OF_SECOND_R = 16;
1682 
1683         assertEquals(INDEX_OF_SECOND_R,
1684                 TextUtils.lastIndexOf(searchString, 'r', searchString.length()));
1685         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0));
1686         assertEquals(INDEX_OF_FIRST_R,
1687                 TextUtils.lastIndexOf(searchString, 'r', INDEX_OF_FIRST_R));
1688         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', Integer.MIN_VALUE));
1689         assertEquals(INDEX_OF_SECOND_R,
1690                 TextUtils.lastIndexOf(searchString, 'r', Integer.MAX_VALUE));
1691 
1692         StringBuffer stringBuffer = new StringBuffer(searchString);
1693         assertEquals(INDEX_OF_FIRST_R,
1694                 TextUtils.lastIndexOf(stringBuffer, 'r', INDEX_OF_FIRST_R));
1695         assertEquals(-1, TextUtils.lastIndexOf(stringBuffer, 'r', Integer.MIN_VALUE));
1696         assertEquals(INDEX_OF_SECOND_R,
1697                 TextUtils.lastIndexOf(stringBuffer, 'r', Integer.MAX_VALUE));
1698 
1699         StringBuilder stringBuilder = new StringBuilder(searchString);
1700         assertEquals(INDEX_OF_FIRST_R,
1701                 TextUtils.lastIndexOf(stringBuilder, 'r', INDEX_OF_FIRST_R));
1702 
1703         MockGetChars mockGetChars = new MockGetChars();
1704         TextUtils.lastIndexOf(mockGetChars, 'r', INDEX_OF_FIRST_R);
1705         assertTrue(mockGetChars.hasCalledGetChars());
1706 
1707         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1708         assertEquals(INDEX_OF_FIRST_R,
1709                 TextUtils.lastIndexOf(mockCharSequence, 'r', INDEX_OF_FIRST_R));
1710     }
1711 
1712     @Test
testLastIndexOf3()1713     public void testLastIndexOf3() {
1714         String searchString = "string to be searched";
1715         final int INDEX_OF_FIRST_R = 2;
1716         final int INDEX_OF_SECOND_R = 16;
1717 
1718         assertEquals(INDEX_OF_SECOND_R, TextUtils.lastIndexOf(searchString, 'r', 0,
1719                 searchString.length()));
1720         assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(searchString, 'r', 0,
1721                 INDEX_OF_SECOND_R - 1));
1722         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0, INDEX_OF_FIRST_R - 1));
1723 
1724         try {
1725             TextUtils.lastIndexOf(searchString, 'r', Integer.MIN_VALUE, INDEX_OF_SECOND_R - 1);
1726             fail("Should throw IndexOutOfBoundsException!");
1727         } catch (IndexOutOfBoundsException e) {
1728             // expect
1729         }
1730         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', Integer.MAX_VALUE,
1731                 INDEX_OF_SECOND_R - 1));
1732         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0, Integer.MIN_VALUE));
1733         assertEquals(INDEX_OF_SECOND_R, TextUtils.lastIndexOf(searchString, 'r', 0,
1734                 Integer.MAX_VALUE));
1735 
1736         StringBuffer stringBuffer = new StringBuffer(searchString);
1737         assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(stringBuffer, 'r', 0,
1738                 INDEX_OF_SECOND_R - 1));
1739 
1740         StringBuilder stringBuilder = new StringBuilder(searchString);
1741         assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(stringBuilder, 'r', 0,
1742                 INDEX_OF_SECOND_R - 1));
1743 
1744         MockGetChars mockGetChars = new MockGetChars();
1745         TextUtils.lastIndexOf(mockGetChars, 'r', 0, INDEX_OF_SECOND_R - 1);
1746         assertTrue(mockGetChars.hasCalledGetChars());
1747 
1748         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1749         assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(mockCharSequence, 'r', 0,
1750                 INDEX_OF_SECOND_R - 1));
1751     }
1752 
1753     @Test
testRegionMatches()1754     public void testRegionMatches() {
1755         assertFalse(TextUtils.regionMatches("one", 0, "two", 0, "one".length()));
1756         assertTrue(TextUtils.regionMatches("one", 0, "one", 0, "one".length()));
1757         try {
1758             TextUtils.regionMatches("one", 0, "one", 0, "one".length() + 1);
1759             fail("Should throw IndexOutOfBoundsException!");
1760         } catch (IndexOutOfBoundsException e) {
1761         }
1762 
1763         String one = "Hello Android, hello World!";
1764         String two = "Hello World";
1765         // match "Hello"
1766         assertTrue(TextUtils.regionMatches(one, 0, two, 0, "Hello".length()));
1767 
1768         // match "Hello A" and "Hello W"
1769         assertFalse(TextUtils.regionMatches(one, 0, two, 0, "Hello A".length()));
1770 
1771         // match "World"
1772         assertTrue(TextUtils.regionMatches(one, "Hello Android, hello ".length(),
1773                 two, "Hello ".length(), "World".length()));
1774         assertFalse(TextUtils.regionMatches(one, "Hello Android, hello ".length(),
1775                 two, 0, "World".length()));
1776 
1777         try {
1778             TextUtils.regionMatches(one, Integer.MIN_VALUE, two, 0, "Hello".length());
1779             fail("Should throw IndexOutOfBoundsException!");
1780         } catch (IndexOutOfBoundsException e) {
1781         }
1782         try {
1783             TextUtils.regionMatches(one, Integer.MAX_VALUE, two, 0, "Hello".length());
1784             fail("Should throw IndexOutOfBoundsException!");
1785         } catch (IndexOutOfBoundsException e) {
1786         }
1787 
1788         try {
1789             TextUtils.regionMatches(one, 0, two, Integer.MIN_VALUE, "Hello".length());
1790             fail("Should throw IndexOutOfBoundsException!");
1791         } catch (IndexOutOfBoundsException e) {
1792         }
1793         try {
1794             TextUtils.regionMatches(one, 0, two, Integer.MAX_VALUE, "Hello".length());
1795             fail("Should throw IndexOutOfBoundsException!");
1796         } catch (IndexOutOfBoundsException e) {
1797         }
1798 
1799         try {
1800             TextUtils.regionMatches(one, 0, two, 0, Integer.MIN_VALUE);
1801             fail("Should throw IndexOutOfBoundsException!");
1802         } catch (IndexOutOfBoundsException e) {
1803         }
1804         try {
1805             TextUtils.regionMatches(one, 0, two, 0, Integer.MAX_VALUE);
1806             fail("Should throw IndexOutOfBoundsException!");
1807         } catch (IndexOutOfBoundsException e) {
1808         }
1809 
1810         try {
1811             TextUtils.regionMatches(null, 0, two, 0, "Hello".length());
1812             fail("Should throw NullPointerException!");
1813         } catch (NullPointerException e) {
1814             // expect
1815         }
1816         try {
1817             TextUtils.regionMatches(one, 0, null, 0, "Hello".length());
1818             fail("Should throw NullPointerException!");
1819         } catch (NullPointerException e) {
1820             // expect
1821         }
1822     }
1823 
1824     @Test
testReplace()1825     public void testReplace() {
1826         String template = "this is a string to be as the template for replacement";
1827 
1828         String sources[] = new String[] { "string" };
1829         CharSequence destinations[] = new CharSequence[] { "text" };
1830         SpannableStringBuilder replacedString = (SpannableStringBuilder) TextUtils.replace(template,
1831                 sources, destinations);
1832         assertEquals("this is a text to be as the template for replacement",
1833                 replacedString.toString());
1834 
1835         sources = new String[] {"is", "the", "for replacement"};
1836         destinations = new CharSequence[] {"was", "", "to be replaced"};
1837         replacedString = (SpannableStringBuilder)TextUtils.replace(template, sources, destinations);
1838         assertEquals("thwas is a string to be as  template to be replaced",
1839                 replacedString.toString());
1840 
1841         sources = new String[] {"is", "for replacement"};
1842         destinations = new CharSequence[] {"was", "", "to be replaced"};
1843         replacedString = (SpannableStringBuilder)TextUtils.replace(template, sources, destinations);
1844         assertEquals("thwas is a string to be as the template ", replacedString.toString());
1845 
1846         sources = new String[] {"is", "the", "for replacement"};
1847         destinations = new CharSequence[] {"was", "to be replaced"};
1848         try {
1849             TextUtils.replace(template, sources, destinations);
1850             fail("Should throw ArrayIndexOutOfBoundsException!");
1851         } catch (ArrayIndexOutOfBoundsException e) {
1852             // expected
1853         }
1854 
1855         try {
1856             TextUtils.replace(null, sources, destinations);
1857             fail("Should throw NullPointerException!");
1858         } catch (NullPointerException e) {
1859             // expected
1860         }
1861         try {
1862             TextUtils.replace(template, null, destinations);
1863             fail("Should throw NullPointerException!");
1864         } catch (NullPointerException e) {
1865             // expected
1866         }
1867         try {
1868             TextUtils.replace(template, sources, null);
1869             fail("Should throw NullPointerException!");
1870         } catch (NullPointerException e) {
1871             // expected
1872         }
1873     }
1874 
1875     @Test
testSplitPattern()1876     public void testSplitPattern() {
1877         assertEquals(0, TextUtils.split("", Pattern.compile("")).length);
1878         assertEquals(0, TextUtils.split("", Pattern.compile("not found")).length);
1879 
1880         String testString = "abccbadecdebz";
1881         assertEquals(calculateCharsCount(testString, "c") + 1,
1882                 TextUtils.split(testString, Pattern.compile("c")).length);
1883         assertEquals(calculateCharsCount(testString, "a") + 1,
1884                 TextUtils.split(testString, Pattern.compile("a")).length);
1885         assertEquals(calculateCharsCount(testString, "z") + 1,
1886                 TextUtils.split(testString, Pattern.compile("z")).length);
1887         assertEquals(calculateCharsCount(testString, "de") + 1,
1888                 TextUtils.split(testString, Pattern.compile("de")).length);
1889         int totalCount = 1 + calculateCharsCount(testString, "a")
1890                 + calculateCharsCount(testString, "b") + calculateCharsCount(testString, "c");
1891         assertEquals(totalCount,
1892                 TextUtils.split(testString, Pattern.compile("[a-c]")).length);
1893         assertEquals(0, TextUtils.split("", Pattern.compile("a")).length);
1894         // issue 1695243, not clear what is supposed result if the pattern string is empty.
1895         assertEquals(
1896                 Arrays.asList("a", "b", "c", "c", "b", "a", "d", "e", "c", "d", "e", "b", "z", ""),
1897                 Arrays.asList(TextUtils.split(testString, Pattern.compile(""))));
1898     }
1899 
1900     @Test(expected=NullPointerException.class)
testSplitPatternNullText()1901     public void testSplitPatternNullText() {
1902         TextUtils.split(null, Pattern.compile("a"));
1903     }
1904 
1905     @Test(expected=NullPointerException.class)
testSplitPatternNullPattern()1906     public void testSplitPatternNullPattern() {
1907             TextUtils.split("abccbadecdebz", (Pattern) null);
1908     }
1909 
1910     /*
1911      * return the appearance count of searched chars in text.
1912      */
calculateCharsCount(CharSequence text, CharSequence searches)1913     private static int calculateCharsCount(CharSequence text, CharSequence searches) {
1914         int count = 0;
1915         int start = TextUtils.indexOf(text, searches, 0);
1916 
1917         while (start != -1) {
1918             count++;
1919             start = TextUtils.indexOf(text, searches, start + 1);
1920         }
1921         return count;
1922     }
1923 
1924     @Test
testSplitString()1925     public void testSplitString() {
1926         assertEquals(0, TextUtils.split("", "").length);
1927         assertEquals(0, TextUtils.split("", "not found").length);
1928 
1929         // The case mentioned in the documentation.
1930         assertEquals(Arrays.asList("a", ""), Arrays.asList(TextUtils.split("a,", ",")));
1931 
1932         String testString = "abccbadecdebz";
1933         assertEquals(calculateCharsCount(testString, "c") + 1,
1934                 TextUtils.split("abccbadecdebz", "c").length);
1935         assertEquals(calculateCharsCount(testString, "a") + 1,
1936                 TextUtils.split("abccbadecdebz", "a").length);
1937         assertEquals(calculateCharsCount(testString, "z") + 1,
1938                 TextUtils.split("abccbadecdebz", "z").length);
1939         assertEquals(calculateCharsCount(testString, "de") + 1,
1940                 TextUtils.split("abccbadecdebz", "de").length);
1941         assertEquals(0, TextUtils.split("", "a").length);
1942         // issue 1695243, not clear what is supposed result if the pattern string is empty.
1943         assertEquals(
1944                 Arrays.asList("a", "b", "c", "c", "b", "a", "d", "e", "c", "d", "e", "b", "z", ""),
1945                 Arrays.asList(TextUtils.split("abccbadecdebz", "")));
1946     }
1947 
1948     @Test(expected=NullPointerException.class)
testSplitStringNullText()1949     public void testSplitStringNullText() {
1950         TextUtils.split(null, "a");
1951     }
1952 
1953     @Test(expected=NullPointerException.class)
testSplitStringNullPattern()1954     public void testSplitStringNullPattern() {
1955         TextUtils.split("abccbadecdebz", (String) null);
1956     }
1957 
1958     @Test
testStringOrSpannedString()1959     public void testStringOrSpannedString() {
1960         assertNull(TextUtils.stringOrSpannedString(null));
1961 
1962         SpannedString spannedString = new SpannedString("Spanned String");
1963         assertSame(spannedString, TextUtils.stringOrSpannedString(spannedString));
1964 
1965         SpannableString spannableString = new SpannableString("Spannable String");
1966         assertEquals("Spannable String",
1967                 TextUtils.stringOrSpannedString(spannableString).toString());
1968         assertEquals(SpannedString.class,
1969                 TextUtils.stringOrSpannedString(spannableString).getClass());
1970 
1971         StringBuffer stringBuffer = new StringBuffer("String Buffer");
1972         assertEquals("String Buffer",
1973                 TextUtils.stringOrSpannedString(stringBuffer).toString());
1974         assertEquals(String.class,
1975                 TextUtils.stringOrSpannedString(stringBuffer).getClass());
1976     }
1977 
1978     @Test
testSubString()1979     public void testSubString() {
1980         String string = "String";
1981         assertSame(string, TextUtils.substring(string, 0, string.length()));
1982         assertEquals("Strin", TextUtils.substring(string, 0, string.length() - 1));
1983         assertEquals("", TextUtils.substring(string, 1, 1));
1984 
1985         try {
1986             TextUtils.substring(string, string.length(), 0);
1987             fail("Should throw IndexOutOfBoundsException!");
1988         } catch (IndexOutOfBoundsException e) {
1989             // expected
1990         }
1991 
1992         try {
1993             TextUtils.substring(string, -1, string.length());
1994             fail("Should throw IndexOutOfBoundsException!");
1995         } catch (IndexOutOfBoundsException e) {
1996             // expected
1997         }
1998 
1999         try {
2000             TextUtils.substring(string, Integer.MAX_VALUE, string.length());
2001             fail("Should throw IndexOutOfBoundsException!");
2002         } catch (IndexOutOfBoundsException e) {
2003             // expected
2004         }
2005 
2006         try {
2007             TextUtils.substring(string, 0, -1);
2008             fail("Should throw IndexOutOfBoundsException!");
2009         } catch (IndexOutOfBoundsException e) {
2010             // expected
2011         }
2012 
2013         try {
2014             TextUtils.substring(string, 0, Integer.MAX_VALUE);
2015             fail("Should throw IndexOutOfBoundsException!");
2016         } catch (IndexOutOfBoundsException e) {
2017             // expected
2018         }
2019 
2020         try {
2021             TextUtils.substring(null, 0, string.length());
2022             fail("Should throw NullPointerException!");
2023         } catch (NullPointerException e) {
2024             // expected
2025         }
2026 
2027         StringBuffer stringBuffer = new StringBuffer("String Buffer");
2028         assertEquals("Strin", TextUtils.substring(stringBuffer, 0, string.length() - 1));
2029         assertEquals("", TextUtils.substring(stringBuffer, 1, 1));
2030 
2031         MockGetChars mockGetChars = new MockGetChars();
2032         TextUtils.substring(mockGetChars, 0, string.length());
2033         assertTrue(mockGetChars.hasCalledGetChars());
2034     }
2035 
2036     @Test
testWriteToParcel()2037     public void testWriteToParcel() {
2038         Parcelable.Creator<CharSequence> creator = TextUtils.CHAR_SEQUENCE_CREATOR;
2039         String string = "String";
2040         Parcel p = Parcel.obtain();
2041         try {
2042             TextUtils.writeToParcel(string, p, 0);
2043             p.setDataPosition(0);
2044             assertEquals(string, creator.createFromParcel(p).toString());
2045         } finally {
2046             p.recycle();
2047         }
2048 
2049         p = Parcel.obtain();
2050         try {
2051             TextUtils.writeToParcel(null, p, 0);
2052             p.setDataPosition(0);
2053             assertNull(creator.createFromParcel(p));
2054         } finally {
2055             p.recycle();
2056         }
2057 
2058         SpannableString spannableString = new SpannableString("Spannable String");
2059         int urlSpanStart = spannableString.length() >> 1;
2060         int urlSpanEnd = spannableString.length();
2061         p = Parcel.obtain();
2062         try {
2063             URLSpan urlSpan = new URLSpan("URL Span");
2064             spannableString.setSpan(urlSpan, urlSpanStart, urlSpanEnd,
2065                     Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2066             TextUtils.writeToParcel(spannableString, p, 0);
2067             p.setDataPosition(0);
2068             SpannableString ret = (SpannableString) creator.createFromParcel(p);
2069             assertEquals("Spannable String", ret.toString());
2070             Object[] spans = ret.getSpans(0, ret.length(), Object.class);
2071             assertEquals(1, spans.length);
2072             assertEquals("URL Span", ((URLSpan) spans[0]).getURL());
2073             assertEquals(urlSpanStart, ret.getSpanStart(spans[0]));
2074             assertEquals(urlSpanEnd, ret.getSpanEnd(spans[0]));
2075             assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, ret.getSpanFlags(spans[0]));
2076         } finally {
2077             p.recycle();
2078         }
2079 
2080         p = Parcel.obtain();
2081         try {
2082             ColorStateList colors = new ColorStateList(new int[][] {
2083                     new int[] {android.R.attr.state_focused}, new int[0]},
2084                     new int[] {Color.rgb(0, 255, 0), Color.BLACK});
2085             int textSize = 20;
2086             TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(
2087                     null, Typeface.ITALIC, textSize, colors, null);
2088             int textAppearanceSpanStart = 0;
2089             int textAppearanceSpanEnd = spannableString.length() >> 1;
2090             spannableString.setSpan(textAppearanceSpan, textAppearanceSpanStart,
2091                     textAppearanceSpanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
2092             TextUtils.writeToParcel(spannableString, p, -1);
2093             p.setDataPosition(0);
2094             SpannableString ret = (SpannableString) creator.createFromParcel(p);
2095             assertEquals("Spannable String", ret.toString());
2096             Object[] spans = ret.getSpans(0, ret.length(), Object.class);
2097             assertEquals(2, spans.length);
2098             assertEquals("URL Span", ((URLSpan) spans[0]).getURL());
2099             assertEquals(urlSpanStart, ret.getSpanStart(spans[0]));
2100             assertEquals(urlSpanEnd, ret.getSpanEnd(spans[0]));
2101             assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, ret.getSpanFlags(spans[0]));
2102             assertEquals(null, ((TextAppearanceSpan) spans[1]).getFamily());
2103 
2104             assertEquals(Typeface.ITALIC, ((TextAppearanceSpan) spans[1]).getTextStyle());
2105             assertEquals(textSize, ((TextAppearanceSpan) spans[1]).getTextSize());
2106 
2107             assertEquals(colors.toString(), ((TextAppearanceSpan) spans[1]).getTextColor().toString());
2108             assertEquals(null, ((TextAppearanceSpan) spans[1]).getLinkTextColor());
2109             assertEquals(textAppearanceSpanStart, ret.getSpanStart(spans[1]));
2110             assertEquals(textAppearanceSpanEnd, ret.getSpanEnd(spans[1]));
2111             assertEquals(Spanned.SPAN_INCLUSIVE_EXCLUSIVE, ret.getSpanFlags(spans[1]));
2112         } finally {
2113             p.recycle();
2114         }
2115 
2116         try {
2117             TextUtils.writeToParcel(spannableString, null, 0);
2118             fail("Should throw NullPointerException!");
2119         } catch (NullPointerException e) {
2120             // expected
2121         }
2122     }
2123 
2124     @Test
testGetCapsMode()2125     public void testGetCapsMode() {
2126         final int CAP_MODE_ALL = TextUtils.CAP_MODE_CHARACTERS
2127                 | TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES;
2128         final int CAP_MODE_CHARACTERS_AND_WORD =
2129                 TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS;
2130         String testString = "Start. Sentence word!No space before\n\t" +
2131                 "Paragraph? (\"\'skip begin\'\"). skip end";
2132 
2133         // CAP_MODE_SENTENCES should be in effect in the whole text.
2134         for (int i = 0; i < testString.length(); i++) {
2135             assertEquals(TextUtils.CAP_MODE_CHARACTERS,
2136                     TextUtils.getCapsMode(testString, i, TextUtils.CAP_MODE_CHARACTERS));
2137         }
2138 
2139         // all modes should be in effect at the start of the text.
2140         assertEquals(TextUtils.CAP_MODE_WORDS,
2141                 TextUtils.getCapsMode(testString, 0, TextUtils.CAP_MODE_WORDS));
2142         // issue 1586346
2143         assertEquals(TextUtils.CAP_MODE_WORDS,
2144                 TextUtils.getCapsMode(testString, 0, TextUtils.CAP_MODE_SENTENCES));
2145         assertEquals(CAP_MODE_CHARACTERS_AND_WORD,
2146                 TextUtils.getCapsMode(testString, 0, CAP_MODE_ALL));
2147 
2148         // all mode should be in effect at the position after "." or "?" or "!" + " ".
2149         int offset = testString.indexOf("Sentence word!");
2150         assertEquals(TextUtils.CAP_MODE_WORDS,
2151                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2152         assertEquals(TextUtils.CAP_MODE_SENTENCES,
2153                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2154         // issue 1586346
2155         assertEquals(CAP_MODE_CHARACTERS_AND_WORD,
2156                 TextUtils.getCapsMode(testString, 0, CAP_MODE_ALL));
2157 
2158         // CAP_MODE_SENTENCES should NOT be in effect at the position after other words + " ".
2159         offset = testString.indexOf("word!");
2160         assertEquals(TextUtils.CAP_MODE_WORDS,
2161                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2162         assertEquals(0,
2163                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2164         // issue 1586346
2165         assertEquals(TextUtils.CAP_MODE_CHARACTERS,
2166                 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
2167 
2168         // if no space after "." or "?" or "!", CAP_MODE_SENTENCES and CAP_MODE_WORDS
2169         // should NOT be in effect.
2170         offset = testString.indexOf("No space before");
2171         assertEquals(0,
2172                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2173         assertEquals(0,
2174                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2175         assertEquals(TextUtils.CAP_MODE_CHARACTERS,
2176                 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
2177 
2178         // all mode should be in effect at a beginning of a new paragraph.
2179         offset = testString.indexOf("Paragraph");
2180         assertEquals(TextUtils.CAP_MODE_WORDS,
2181                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2182         // issue 1586346
2183         assertEquals(TextUtils.CAP_MODE_WORDS,
2184                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2185         assertEquals(CAP_MODE_CHARACTERS_AND_WORD,
2186                 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
2187 
2188         // some special word which means the start of a sentence should be skipped.
2189         offset = testString.indexOf("skip begin");
2190         assertEquals(TextUtils.CAP_MODE_WORDS,
2191                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2192         assertEquals(TextUtils.CAP_MODE_SENTENCES,
2193                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2194         // issue 1586346
2195         assertEquals(TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS,
2196                 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
2197 
2198         // some special word which means the end of a sentence should be skipped.
2199         offset = testString.indexOf("skip end");
2200         assertEquals(TextUtils.CAP_MODE_WORDS,
2201                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2202         assertEquals(TextUtils.CAP_MODE_SENTENCES,
2203                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2204         // issue 1586346
2205         assertEquals(TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS,
2206                 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
2207     }
2208 
2209     @Test
testGetCapsModeException()2210     public void testGetCapsModeException() {
2211         String testString = "Start. Sentence word!No space before\n\t" +
2212                 "Paragraph? (\"\'skip begin\'\"). skip end";
2213 
2214         int offset = testString.indexOf("Sentence word!");
2215         assertEquals(TextUtils.CAP_MODE_CHARACTERS,
2216                 TextUtils.getCapsMode(null, offset, TextUtils.CAP_MODE_CHARACTERS));
2217 
2218         try {
2219             TextUtils.getCapsMode(null, offset, TextUtils.CAP_MODE_SENTENCES);
2220             fail("Should throw NullPointerException!");
2221         } catch (NullPointerException e) {
2222             // expected
2223         }
2224 
2225         assertEquals(0, TextUtils.getCapsMode(testString, -1, TextUtils.CAP_MODE_SENTENCES));
2226 
2227         try {
2228             TextUtils.getCapsMode(testString, testString.length() + 1,
2229                     TextUtils.CAP_MODE_SENTENCES);
2230             fail("Should throw IndexOutOfBoundsException!");
2231         } catch (IndexOutOfBoundsException e) {
2232             // expected
2233         }
2234     }
2235 
2236     @Test
testDumpSpans()2237     public void testDumpSpans() {
2238         StringBuilder builder = new StringBuilder();
2239         StringBuilderPrinter printer = new StringBuilderPrinter(builder);
2240         CharSequence source = "test dump spans";
2241         String prefix = "prefix";
2242 
2243         assertEquals(0, builder.length());
2244         TextUtils.dumpSpans(source, printer, prefix);
2245         assertTrue(builder.length() > 0);
2246 
2247         builder = new StringBuilder();
2248         printer = new StringBuilderPrinter(builder);
2249         assertEquals(0, builder.length());
2250         SpannableString spanned = new SpannableString(source);
2251         spanned.setSpan(new Object(), 0, source.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2252         TextUtils.dumpSpans(spanned, printer, prefix);
2253         assertTrue(builder.length() > 0);
2254     }
2255 
2256     @Test
testGetLayoutDirectionFromLocale()2257     public void testGetLayoutDirectionFromLocale() {
2258         assertEquals(LAYOUT_DIRECTION_LTR,
2259                 TextUtils.getLayoutDirectionFromLocale(null));
2260 
2261         assertEquals(LAYOUT_DIRECTION_LTR,
2262                 TextUtils.getLayoutDirectionFromLocale(Locale.ENGLISH));
2263         assertEquals(LAYOUT_DIRECTION_LTR,
2264                 TextUtils.getLayoutDirectionFromLocale(Locale.CANADA));
2265         assertEquals(LAYOUT_DIRECTION_LTR,
2266                 TextUtils.getLayoutDirectionFromLocale(Locale.CANADA_FRENCH));
2267         assertEquals(LAYOUT_DIRECTION_LTR,
2268                 TextUtils.getLayoutDirectionFromLocale(Locale.FRANCE));
2269         assertEquals(LAYOUT_DIRECTION_LTR,
2270                 TextUtils.getLayoutDirectionFromLocale(Locale.FRENCH));
2271         assertEquals(LAYOUT_DIRECTION_LTR,
2272                 TextUtils.getLayoutDirectionFromLocale(Locale.GERMAN));
2273         assertEquals(LAYOUT_DIRECTION_LTR,
2274                 TextUtils.getLayoutDirectionFromLocale(Locale.GERMANY));
2275         assertEquals(LAYOUT_DIRECTION_LTR,
2276                 TextUtils.getLayoutDirectionFromLocale(Locale.ITALIAN));
2277         assertEquals(LAYOUT_DIRECTION_LTR,
2278                 TextUtils.getLayoutDirectionFromLocale(Locale.ITALY));
2279         assertEquals(LAYOUT_DIRECTION_LTR,
2280                 TextUtils.getLayoutDirectionFromLocale(Locale.UK));
2281         assertEquals(LAYOUT_DIRECTION_LTR,
2282                 TextUtils.getLayoutDirectionFromLocale(Locale.US));
2283 
2284         assertEquals(LAYOUT_DIRECTION_LTR,
2285                 TextUtils.getLayoutDirectionFromLocale(Locale.ROOT));
2286 
2287         assertEquals(LAYOUT_DIRECTION_LTR,
2288                 TextUtils.getLayoutDirectionFromLocale(Locale.CHINA));
2289         assertEquals(LAYOUT_DIRECTION_LTR,
2290                 TextUtils.getLayoutDirectionFromLocale(Locale.CHINESE));
2291         assertEquals(LAYOUT_DIRECTION_LTR,
2292                 TextUtils.getLayoutDirectionFromLocale(Locale.JAPAN));
2293         assertEquals(LAYOUT_DIRECTION_LTR,
2294                 TextUtils.getLayoutDirectionFromLocale(Locale.JAPANESE));
2295         assertEquals(LAYOUT_DIRECTION_LTR,
2296                 TextUtils.getLayoutDirectionFromLocale(Locale.KOREA));
2297         assertEquals(LAYOUT_DIRECTION_LTR,
2298                 TextUtils.getLayoutDirectionFromLocale(Locale.KOREAN));
2299         assertEquals(LAYOUT_DIRECTION_LTR,
2300                 TextUtils.getLayoutDirectionFromLocale(Locale.PRC));
2301         assertEquals(LAYOUT_DIRECTION_LTR,
2302                 TextUtils.getLayoutDirectionFromLocale(Locale.SIMPLIFIED_CHINESE));
2303         assertEquals(LAYOUT_DIRECTION_LTR,
2304                 TextUtils.getLayoutDirectionFromLocale(Locale.TAIWAN));
2305         assertEquals(LAYOUT_DIRECTION_LTR,
2306                 TextUtils.getLayoutDirectionFromLocale(Locale.TRADITIONAL_CHINESE));
2307 
2308         // Some languages always use an RTL script.
2309         for (Locale l : Locale.getAvailableLocales()) {
2310             String languageCode = l.getLanguage();
2311             if (languageCode.equals("ar") ||
2312                     languageCode.equals("fa") ||
2313                     languageCode.equals("iw") ||
2314                     languageCode.equals("he") ||
2315                     languageCode.equals("ps") ||
2316                     languageCode.equals("ur")) {
2317                 int direction = TextUtils.getLayoutDirectionFromLocale(l);
2318                 assertEquals(l.toLanguageTag() + " not RTL: " + direction,
2319                              LAYOUT_DIRECTION_RTL, direction);
2320             }
2321         }
2322 
2323         // Other languages have some cases where they use an RTL script.
2324         String[] tags = {
2325             "pa-Arab",
2326             "pa-Arab-PK",
2327             "ps",
2328             "ps-AF",
2329             "uz-Arab",
2330             "uz-Arab-AF",
2331         };
2332         for (String tag : tags) {
2333             Locale l = Locale.forLanguageTag(tag);
2334             int direction = TextUtils.getLayoutDirectionFromLocale(l);
2335             assertEquals(l.toLanguageTag() + " not RTL: " + direction,
2336                          LAYOUT_DIRECTION_RTL, direction);
2337         }
2338 
2339         // Locale without a real language
2340         Locale locale = Locale.forLanguageTag("zz");
2341         assertEquals(LAYOUT_DIRECTION_LTR,
2342                 TextUtils.getLayoutDirectionFromLocale(locale));
2343     }
2344 }
2345