• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.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)
1577     @SuppressWarnings("NullArgumentForNonNullParameter")
testIsGraphicCharNull()1578     public void testIsGraphicCharNull() {
1579         assertFalse(TextUtils.isGraphic((Character) null));
1580     }
1581 
1582     @Test
testIsGraphicCharSequence()1583     public void testIsGraphicCharSequence() {
1584         assertTrue(TextUtils.isGraphic("printable characters"));
1585 
1586         assertFalse(TextUtils.isGraphic("\u2028\u2029\u0085\uFFFF\uD800\u0020"));
1587 
1588         assertTrue(TextUtils.isGraphic("a\u2028\u2029\u0085\uFFFF\uD800\u0020"));
1589 
1590         assertTrue(TextUtils.isGraphic("\uD83D\uDC0C")); // U+1F40C SNAIL
1591         assertFalse(TextUtils.isGraphic("\uDB40\uDC01")); // U+E0000 (unassigned)
1592         assertFalse(TextUtils.isGraphic("\uDB3D")); // unpaired high surrogate
1593         assertFalse(TextUtils.isGraphic("\uDC0C")); // unpaired low surrogate
1594     }
1595 
1596     @Test(expected=NullPointerException.class)
testIsGraphicCharSequenceNull()1597     public void testIsGraphicCharSequenceNull() {
1598         TextUtils.isGraphic(null);
1599     }
1600 
1601     @Test
testJoinIterable()1602     public void testJoinIterable() {
1603         ArrayList<CharSequence> charTokens = new ArrayList<>();
1604         charTokens.add("string1");
1605         charTokens.add("string2");
1606         charTokens.add("string3");
1607         assertEquals("string1|string2|string3", TextUtils.join("|", charTokens));
1608         assertEquals("string1; string2; string3", TextUtils.join("; ", charTokens));
1609         assertEquals("string1string2string3", TextUtils.join("", charTokens));
1610 
1611         // issue 1695243, not clear what is supposed result if the delimiter or tokens are null.
1612         assertEquals("string1nullstring2nullstring3", TextUtils.join(null, charTokens));
1613 
1614         ArrayList<SpannableString> spannableStringTokens = new ArrayList<SpannableString>();
1615         spannableStringTokens.add(new SpannableString("span 1"));
1616         spannableStringTokens.add(new SpannableString("span 2"));
1617         spannableStringTokens.add(new SpannableString("span 3"));
1618         assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens));
1619 
1620         assertEquals("", TextUtils.join("|", new ArrayList<CharSequence>()));
1621     }
1622 
1623     @Test(expected=NullPointerException.class)
testJoinIterableNull()1624     public void testJoinIterableNull() {
1625         TextUtils.join("|", (Iterable) null);
1626     }
1627 
1628     @Test
testJoinArray()1629     public void testJoinArray() {
1630         CharSequence[] charTokens = new CharSequence[] { "string1", "string2", "string3" };
1631         assertEquals("string1|string2|string3", TextUtils.join("|", charTokens));
1632         assertEquals("string1; string2; string3", TextUtils.join("; ", charTokens));
1633         assertEquals("string1string2string3", TextUtils.join("", charTokens));
1634 
1635         // issue 1695243, not clear what is supposed result if the delimiter or tokens are null.
1636         assertEquals("string1nullstring2nullstring3", TextUtils.join(null, charTokens));
1637 
1638         SpannableString[] spannableStringTokens = new SpannableString[] {
1639                 new SpannableString("span 1"),
1640                 new SpannableString("span 2"),
1641                 new SpannableString("span 3") };
1642         assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens));
1643 
1644         assertEquals("", TextUtils.join("|", new String[0]));
1645     }
1646 
1647     @Test(expected=NullPointerException.class)
testJoinArrayNull()1648     public void testJoinArrayNull() {
1649         TextUtils.join("|", (Object[]) null);
1650     }
1651 
1652     @Test
testLastIndexOf1()1653     public void testLastIndexOf1() {
1654         String searchString = "string to be searched";
1655         final int INDEX_OF_LAST_R = 16;
1656         final int INDEX_OF_LAST_T = 7;
1657         final int INDEX_OF_LAST_D = searchString.length() - 1;
1658 
1659         assertEquals(INDEX_OF_LAST_T, TextUtils.lastIndexOf(searchString, 't'));
1660         assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(searchString, 'r'));
1661         assertEquals(INDEX_OF_LAST_D, TextUtils.lastIndexOf(searchString, 'd'));
1662         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'f'));
1663 
1664         StringBuffer stringBuffer = new StringBuffer(searchString);
1665         assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(stringBuffer, 'r'));
1666 
1667         StringBuilder stringBuilder = new StringBuilder(searchString);
1668         assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(stringBuilder, 'r'));
1669 
1670         MockGetChars mockGetChars = new MockGetChars();
1671         TextUtils.lastIndexOf(mockGetChars, 'r');
1672         assertTrue(mockGetChars.hasCalledGetChars());
1673 
1674         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1675         assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(mockCharSequence, 'r'));
1676     }
1677 
1678     @Test
testLastIndexOf2()1679     public void testLastIndexOf2() {
1680         String searchString = "string to be searched";
1681         final int INDEX_OF_FIRST_R = 2;
1682         final int INDEX_OF_SECOND_R = 16;
1683 
1684         assertEquals(INDEX_OF_SECOND_R,
1685                 TextUtils.lastIndexOf(searchString, 'r', searchString.length()));
1686         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0));
1687         assertEquals(INDEX_OF_FIRST_R,
1688                 TextUtils.lastIndexOf(searchString, 'r', INDEX_OF_FIRST_R));
1689         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', Integer.MIN_VALUE));
1690         assertEquals(INDEX_OF_SECOND_R,
1691                 TextUtils.lastIndexOf(searchString, 'r', Integer.MAX_VALUE));
1692 
1693         StringBuffer stringBuffer = new StringBuffer(searchString);
1694         assertEquals(INDEX_OF_FIRST_R,
1695                 TextUtils.lastIndexOf(stringBuffer, 'r', INDEX_OF_FIRST_R));
1696         assertEquals(-1, TextUtils.lastIndexOf(stringBuffer, 'r', Integer.MIN_VALUE));
1697         assertEquals(INDEX_OF_SECOND_R,
1698                 TextUtils.lastIndexOf(stringBuffer, 'r', Integer.MAX_VALUE));
1699 
1700         StringBuilder stringBuilder = new StringBuilder(searchString);
1701         assertEquals(INDEX_OF_FIRST_R,
1702                 TextUtils.lastIndexOf(stringBuilder, 'r', INDEX_OF_FIRST_R));
1703 
1704         MockGetChars mockGetChars = new MockGetChars();
1705         TextUtils.lastIndexOf(mockGetChars, 'r', INDEX_OF_FIRST_R);
1706         assertTrue(mockGetChars.hasCalledGetChars());
1707 
1708         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1709         assertEquals(INDEX_OF_FIRST_R,
1710                 TextUtils.lastIndexOf(mockCharSequence, 'r', INDEX_OF_FIRST_R));
1711     }
1712 
1713     @Test
testLastIndexOf3()1714     public void testLastIndexOf3() {
1715         String searchString = "string to be searched";
1716         final int INDEX_OF_FIRST_R = 2;
1717         final int INDEX_OF_SECOND_R = 16;
1718 
1719         assertEquals(INDEX_OF_SECOND_R, TextUtils.lastIndexOf(searchString, 'r', 0,
1720                 searchString.length()));
1721         assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(searchString, 'r', 0,
1722                 INDEX_OF_SECOND_R - 1));
1723         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0, INDEX_OF_FIRST_R - 1));
1724 
1725         try {
1726             TextUtils.lastIndexOf(searchString, 'r', Integer.MIN_VALUE, INDEX_OF_SECOND_R - 1);
1727             fail("Should throw IndexOutOfBoundsException!");
1728         } catch (IndexOutOfBoundsException e) {
1729             // expect
1730         }
1731         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', Integer.MAX_VALUE,
1732                 INDEX_OF_SECOND_R - 1));
1733         assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0, Integer.MIN_VALUE));
1734         assertEquals(INDEX_OF_SECOND_R, TextUtils.lastIndexOf(searchString, 'r', 0,
1735                 Integer.MAX_VALUE));
1736 
1737         StringBuffer stringBuffer = new StringBuffer(searchString);
1738         assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(stringBuffer, 'r', 0,
1739                 INDEX_OF_SECOND_R - 1));
1740 
1741         StringBuilder stringBuilder = new StringBuilder(searchString);
1742         assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(stringBuilder, 'r', 0,
1743                 INDEX_OF_SECOND_R - 1));
1744 
1745         MockGetChars mockGetChars = new MockGetChars();
1746         TextUtils.lastIndexOf(mockGetChars, 'r', 0, INDEX_OF_SECOND_R - 1);
1747         assertTrue(mockGetChars.hasCalledGetChars());
1748 
1749         MockCharSequence mockCharSequence = new MockCharSequence(searchString);
1750         assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(mockCharSequence, 'r', 0,
1751                 INDEX_OF_SECOND_R - 1));
1752     }
1753 
1754     @Test
testRegionMatches()1755     public void testRegionMatches() {
1756         assertFalse(TextUtils.regionMatches("one", 0, "two", 0, "one".length()));
1757         assertTrue(TextUtils.regionMatches("one", 0, "one", 0, "one".length()));
1758         try {
1759             TextUtils.regionMatches("one", 0, "one", 0, "one".length() + 1);
1760             fail("Should throw IndexOutOfBoundsException!");
1761         } catch (IndexOutOfBoundsException e) {
1762         }
1763 
1764         String one = "Hello Android, hello World!";
1765         String two = "Hello World";
1766         // match "Hello"
1767         assertTrue(TextUtils.regionMatches(one, 0, two, 0, "Hello".length()));
1768 
1769         // match "Hello A" and "Hello W"
1770         assertFalse(TextUtils.regionMatches(one, 0, two, 0, "Hello A".length()));
1771 
1772         // match "World"
1773         assertTrue(TextUtils.regionMatches(one, "Hello Android, hello ".length(),
1774                 two, "Hello ".length(), "World".length()));
1775         assertFalse(TextUtils.regionMatches(one, "Hello Android, hello ".length(),
1776                 two, 0, "World".length()));
1777 
1778         try {
1779             TextUtils.regionMatches(one, Integer.MIN_VALUE, two, 0, "Hello".length());
1780             fail("Should throw IndexOutOfBoundsException!");
1781         } catch (IndexOutOfBoundsException e) {
1782         }
1783         try {
1784             TextUtils.regionMatches(one, Integer.MAX_VALUE, two, 0, "Hello".length());
1785             fail("Should throw IndexOutOfBoundsException!");
1786         } catch (IndexOutOfBoundsException e) {
1787         }
1788 
1789         try {
1790             TextUtils.regionMatches(one, 0, two, Integer.MIN_VALUE, "Hello".length());
1791             fail("Should throw IndexOutOfBoundsException!");
1792         } catch (IndexOutOfBoundsException e) {
1793         }
1794         try {
1795             TextUtils.regionMatches(one, 0, two, Integer.MAX_VALUE, "Hello".length());
1796             fail("Should throw IndexOutOfBoundsException!");
1797         } catch (IndexOutOfBoundsException e) {
1798         }
1799 
1800         try {
1801             TextUtils.regionMatches(one, 0, two, 0, Integer.MIN_VALUE);
1802             fail("Should throw IndexOutOfBoundsException!");
1803         } catch (IndexOutOfBoundsException e) {
1804         }
1805         try {
1806             TextUtils.regionMatches(one, 0, two, 0, Integer.MAX_VALUE);
1807             fail("Should throw IndexOutOfBoundsException!");
1808         } catch (IndexOutOfBoundsException e) {
1809         }
1810 
1811         try {
1812             TextUtils.regionMatches(null, 0, two, 0, "Hello".length());
1813             fail("Should throw NullPointerException!");
1814         } catch (NullPointerException e) {
1815             // expect
1816         }
1817         try {
1818             TextUtils.regionMatches(one, 0, null, 0, "Hello".length());
1819             fail("Should throw NullPointerException!");
1820         } catch (NullPointerException e) {
1821             // expect
1822         }
1823     }
1824 
1825     @Test
testReplace()1826     public void testReplace() {
1827         String template = "this is a string to be as the template for replacement";
1828 
1829         String sources[] = new String[] { "string" };
1830         CharSequence destinations[] = new CharSequence[] { "text" };
1831         SpannableStringBuilder replacedString = (SpannableStringBuilder) TextUtils.replace(template,
1832                 sources, destinations);
1833         assertEquals("this is a text to be as the template for replacement",
1834                 replacedString.toString());
1835 
1836         sources = new String[] {"is", "the", "for replacement"};
1837         destinations = new CharSequence[] {"was", "", "to be replaced"};
1838         replacedString = (SpannableStringBuilder)TextUtils.replace(template, sources, destinations);
1839         assertEquals("thwas is a string to be as  template to be replaced",
1840                 replacedString.toString());
1841 
1842         sources = new String[] {"is", "for replacement"};
1843         destinations = new CharSequence[] {"was", "", "to be replaced"};
1844         replacedString = (SpannableStringBuilder)TextUtils.replace(template, sources, destinations);
1845         assertEquals("thwas is a string to be as the template ", replacedString.toString());
1846 
1847         sources = new String[] {"is", "the", "for replacement"};
1848         destinations = new CharSequence[] {"was", "to be replaced"};
1849         try {
1850             TextUtils.replace(template, sources, destinations);
1851             fail("Should throw ArrayIndexOutOfBoundsException!");
1852         } catch (ArrayIndexOutOfBoundsException e) {
1853             // expected
1854         }
1855 
1856         try {
1857             TextUtils.replace(null, sources, destinations);
1858             fail("Should throw NullPointerException!");
1859         } catch (NullPointerException e) {
1860             // expected
1861         }
1862         try {
1863             TextUtils.replace(template, null, destinations);
1864             fail("Should throw NullPointerException!");
1865         } catch (NullPointerException e) {
1866             // expected
1867         }
1868         try {
1869             TextUtils.replace(template, sources, null);
1870             fail("Should throw NullPointerException!");
1871         } catch (NullPointerException e) {
1872             // expected
1873         }
1874     }
1875 
1876     @Test
testSplitPattern()1877     public void testSplitPattern() {
1878         assertEquals(0, TextUtils.split("", Pattern.compile("")).length);
1879         assertEquals(0, TextUtils.split("", Pattern.compile("not found")).length);
1880 
1881         String testString = "abccbadecdebz";
1882         assertEquals(calculateCharsCount(testString, "c") + 1,
1883                 TextUtils.split(testString, Pattern.compile("c")).length);
1884         assertEquals(calculateCharsCount(testString, "a") + 1,
1885                 TextUtils.split(testString, Pattern.compile("a")).length);
1886         assertEquals(calculateCharsCount(testString, "z") + 1,
1887                 TextUtils.split(testString, Pattern.compile("z")).length);
1888         assertEquals(calculateCharsCount(testString, "de") + 1,
1889                 TextUtils.split(testString, Pattern.compile("de")).length);
1890         int totalCount = 1 + calculateCharsCount(testString, "a")
1891                 + calculateCharsCount(testString, "b") + calculateCharsCount(testString, "c");
1892         assertEquals(totalCount,
1893                 TextUtils.split(testString, Pattern.compile("[a-c]")).length);
1894         assertEquals(0, TextUtils.split("", Pattern.compile("a")).length);
1895         // issue 1695243, not clear what is supposed result if the pattern string is empty.
1896         assertEquals(
1897                 Arrays.asList("a", "b", "c", "c", "b", "a", "d", "e", "c", "d", "e", "b", "z", ""),
1898                 Arrays.asList(TextUtils.split(testString, Pattern.compile(""))));
1899     }
1900 
1901     @Test(expected=NullPointerException.class)
testSplitPatternNullText()1902     public void testSplitPatternNullText() {
1903         TextUtils.split(null, Pattern.compile("a"));
1904     }
1905 
1906     @Test(expected=NullPointerException.class)
testSplitPatternNullPattern()1907     public void testSplitPatternNullPattern() {
1908             TextUtils.split("abccbadecdebz", (Pattern) null);
1909     }
1910 
1911     /*
1912      * return the appearance count of searched chars in text.
1913      */
calculateCharsCount(CharSequence text, CharSequence searches)1914     private static int calculateCharsCount(CharSequence text, CharSequence searches) {
1915         int count = 0;
1916         int start = TextUtils.indexOf(text, searches, 0);
1917 
1918         while (start != -1) {
1919             count++;
1920             start = TextUtils.indexOf(text, searches, start + 1);
1921         }
1922         return count;
1923     }
1924 
1925     @Test
testSplitString()1926     public void testSplitString() {
1927         assertEquals(0, TextUtils.split("", "").length);
1928         assertEquals(0, TextUtils.split("", "not found").length);
1929 
1930         // The case mentioned in the documentation.
1931         assertEquals(Arrays.asList("a", ""), Arrays.asList(TextUtils.split("a,", ",")));
1932 
1933         String testString = "abccbadecdebz";
1934         assertEquals(calculateCharsCount(testString, "c") + 1,
1935                 TextUtils.split("abccbadecdebz", "c").length);
1936         assertEquals(calculateCharsCount(testString, "a") + 1,
1937                 TextUtils.split("abccbadecdebz", "a").length);
1938         assertEquals(calculateCharsCount(testString, "z") + 1,
1939                 TextUtils.split("abccbadecdebz", "z").length);
1940         assertEquals(calculateCharsCount(testString, "de") + 1,
1941                 TextUtils.split("abccbadecdebz", "de").length);
1942         assertEquals(0, TextUtils.split("", "a").length);
1943         // issue 1695243, not clear what is supposed result if the pattern string is empty.
1944         assertEquals(
1945                 Arrays.asList("a", "b", "c", "c", "b", "a", "d", "e", "c", "d", "e", "b", "z", ""),
1946                 Arrays.asList(TextUtils.split("abccbadecdebz", "")));
1947     }
1948 
1949     @Test(expected=NullPointerException.class)
testSplitStringNullText()1950     public void testSplitStringNullText() {
1951         TextUtils.split(null, "a");
1952     }
1953 
1954     @Test(expected=NullPointerException.class)
testSplitStringNullPattern()1955     public void testSplitStringNullPattern() {
1956         TextUtils.split("abccbadecdebz", (String) null);
1957     }
1958 
1959     @Test
testStringOrSpannedString()1960     public void testStringOrSpannedString() {
1961         assertNull(TextUtils.stringOrSpannedString(null));
1962 
1963         SpannedString spannedString = new SpannedString("Spanned String");
1964         assertSame(spannedString, TextUtils.stringOrSpannedString(spannedString));
1965 
1966         SpannableString spannableString = new SpannableString("Spannable String");
1967         assertEquals("Spannable String",
1968                 TextUtils.stringOrSpannedString(spannableString).toString());
1969         assertEquals(SpannedString.class,
1970                 TextUtils.stringOrSpannedString(spannableString).getClass());
1971 
1972         StringBuffer stringBuffer = new StringBuffer("String Buffer");
1973         assertEquals("String Buffer",
1974                 TextUtils.stringOrSpannedString(stringBuffer).toString());
1975         assertEquals(String.class,
1976                 TextUtils.stringOrSpannedString(stringBuffer).getClass());
1977     }
1978 
1979     @Test
testSubString()1980     public void testSubString() {
1981         String string = "String";
1982         assertSame(string, TextUtils.substring(string, 0, string.length()));
1983         assertEquals("Strin", TextUtils.substring(string, 0, string.length() - 1));
1984         assertEquals("", TextUtils.substring(string, 1, 1));
1985 
1986         try {
1987             TextUtils.substring(string, string.length(), 0);
1988             fail("Should throw IndexOutOfBoundsException!");
1989         } catch (IndexOutOfBoundsException e) {
1990             // expected
1991         }
1992 
1993         try {
1994             TextUtils.substring(string, -1, string.length());
1995             fail("Should throw IndexOutOfBoundsException!");
1996         } catch (IndexOutOfBoundsException e) {
1997             // expected
1998         }
1999 
2000         try {
2001             TextUtils.substring(string, Integer.MAX_VALUE, string.length());
2002             fail("Should throw IndexOutOfBoundsException!");
2003         } catch (IndexOutOfBoundsException e) {
2004             // expected
2005         }
2006 
2007         try {
2008             TextUtils.substring(string, 0, -1);
2009             fail("Should throw IndexOutOfBoundsException!");
2010         } catch (IndexOutOfBoundsException e) {
2011             // expected
2012         }
2013 
2014         try {
2015             TextUtils.substring(string, 0, Integer.MAX_VALUE);
2016             fail("Should throw IndexOutOfBoundsException!");
2017         } catch (IndexOutOfBoundsException e) {
2018             // expected
2019         }
2020 
2021         try {
2022             TextUtils.substring(null, 0, string.length());
2023             fail("Should throw NullPointerException!");
2024         } catch (NullPointerException e) {
2025             // expected
2026         }
2027 
2028         StringBuffer stringBuffer = new StringBuffer("String Buffer");
2029         assertEquals("Strin", TextUtils.substring(stringBuffer, 0, string.length() - 1));
2030         assertEquals("", TextUtils.substring(stringBuffer, 1, 1));
2031 
2032         MockGetChars mockGetChars = new MockGetChars();
2033         TextUtils.substring(mockGetChars, 0, string.length());
2034         assertTrue(mockGetChars.hasCalledGetChars());
2035     }
2036 
2037     @Test
testWriteToParcel()2038     public void testWriteToParcel() {
2039         Parcelable.Creator<CharSequence> creator = TextUtils.CHAR_SEQUENCE_CREATOR;
2040         String string = "String";
2041         Parcel p = Parcel.obtain();
2042         try {
2043             TextUtils.writeToParcel(string, p, 0);
2044             p.setDataPosition(0);
2045             assertEquals(string, creator.createFromParcel(p).toString());
2046         } finally {
2047             p.recycle();
2048         }
2049 
2050         p = Parcel.obtain();
2051         try {
2052             TextUtils.writeToParcel(null, p, 0);
2053             p.setDataPosition(0);
2054             assertNull(creator.createFromParcel(p));
2055         } finally {
2056             p.recycle();
2057         }
2058 
2059         SpannableString spannableString = new SpannableString("Spannable String");
2060         int urlSpanStart = spannableString.length() >> 1;
2061         int urlSpanEnd = spannableString.length();
2062         p = Parcel.obtain();
2063         try {
2064             URLSpan urlSpan = new URLSpan("URL Span");
2065             spannableString.setSpan(urlSpan, urlSpanStart, urlSpanEnd,
2066                     Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2067             TextUtils.writeToParcel(spannableString, p, 0);
2068             p.setDataPosition(0);
2069             SpannableString ret = (SpannableString) creator.createFromParcel(p);
2070             assertEquals("Spannable String", ret.toString());
2071             Object[] spans = ret.getSpans(0, ret.length(), Object.class);
2072             assertEquals(1, spans.length);
2073             assertEquals("URL Span", ((URLSpan) spans[0]).getURL());
2074             assertEquals(urlSpanStart, ret.getSpanStart(spans[0]));
2075             assertEquals(urlSpanEnd, ret.getSpanEnd(spans[0]));
2076             assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, ret.getSpanFlags(spans[0]));
2077         } finally {
2078             p.recycle();
2079         }
2080 
2081         p = Parcel.obtain();
2082         try {
2083             ColorStateList colors = new ColorStateList(new int[][] {
2084                     new int[] {android.R.attr.state_focused}, new int[0]},
2085                     new int[] {Color.rgb(0, 255, 0), Color.BLACK});
2086             int textSize = 20;
2087             TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan(
2088                     null, Typeface.ITALIC, textSize, colors, null);
2089             int textAppearanceSpanStart = 0;
2090             int textAppearanceSpanEnd = spannableString.length() >> 1;
2091             spannableString.setSpan(textAppearanceSpan, textAppearanceSpanStart,
2092                     textAppearanceSpanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
2093             TextUtils.writeToParcel(spannableString, p, -1);
2094             p.setDataPosition(0);
2095             SpannableString ret = (SpannableString) creator.createFromParcel(p);
2096             assertEquals("Spannable String", ret.toString());
2097             Object[] spans = ret.getSpans(0, ret.length(), Object.class);
2098             assertEquals(2, spans.length);
2099             assertEquals("URL Span", ((URLSpan) spans[0]).getURL());
2100             assertEquals(urlSpanStart, ret.getSpanStart(spans[0]));
2101             assertEquals(urlSpanEnd, ret.getSpanEnd(spans[0]));
2102             assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, ret.getSpanFlags(spans[0]));
2103             assertEquals(null, ((TextAppearanceSpan) spans[1]).getFamily());
2104 
2105             assertEquals(Typeface.ITALIC, ((TextAppearanceSpan) spans[1]).getTextStyle());
2106             assertEquals(textSize, ((TextAppearanceSpan) spans[1]).getTextSize());
2107 
2108             assertEquals(colors.toString(), ((TextAppearanceSpan) spans[1]).getTextColor().toString());
2109             assertEquals(null, ((TextAppearanceSpan) spans[1]).getLinkTextColor());
2110             assertEquals(textAppearanceSpanStart, ret.getSpanStart(spans[1]));
2111             assertEquals(textAppearanceSpanEnd, ret.getSpanEnd(spans[1]));
2112             assertEquals(Spanned.SPAN_INCLUSIVE_EXCLUSIVE, ret.getSpanFlags(spans[1]));
2113         } finally {
2114             p.recycle();
2115         }
2116 
2117         try {
2118             TextUtils.writeToParcel(spannableString, null, 0);
2119             fail("Should throw NullPointerException!");
2120         } catch (NullPointerException e) {
2121             // expected
2122         }
2123     }
2124 
2125     @Test
testGetCapsMode()2126     public void testGetCapsMode() {
2127         final int CAP_MODE_ALL = TextUtils.CAP_MODE_CHARACTERS
2128                 | TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES;
2129         final int CAP_MODE_CHARACTERS_AND_WORD =
2130                 TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS;
2131         String testString = "Start. Sentence word!No space before\n\t" +
2132                 "Paragraph? (\"\'skip begin\'\"). skip end";
2133 
2134         // CAP_MODE_SENTENCES should be in effect in the whole text.
2135         for (int i = 0; i < testString.length(); i++) {
2136             assertEquals(TextUtils.CAP_MODE_CHARACTERS,
2137                     TextUtils.getCapsMode(testString, i, TextUtils.CAP_MODE_CHARACTERS));
2138         }
2139 
2140         // all modes should be in effect at the start of the text.
2141         assertEquals(TextUtils.CAP_MODE_WORDS,
2142                 TextUtils.getCapsMode(testString, 0, TextUtils.CAP_MODE_WORDS));
2143         // issue 1586346
2144         assertEquals(TextUtils.CAP_MODE_WORDS,
2145                 TextUtils.getCapsMode(testString, 0, TextUtils.CAP_MODE_SENTENCES));
2146         assertEquals(CAP_MODE_CHARACTERS_AND_WORD,
2147                 TextUtils.getCapsMode(testString, 0, CAP_MODE_ALL));
2148 
2149         // all mode should be in effect at the position after "." or "?" or "!" + " ".
2150         int offset = testString.indexOf("Sentence word!");
2151         assertEquals(TextUtils.CAP_MODE_WORDS,
2152                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2153         assertEquals(TextUtils.CAP_MODE_SENTENCES,
2154                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2155         // issue 1586346
2156         assertEquals(CAP_MODE_CHARACTERS_AND_WORD,
2157                 TextUtils.getCapsMode(testString, 0, CAP_MODE_ALL));
2158 
2159         // CAP_MODE_SENTENCES should NOT be in effect at the position after other words + " ".
2160         offset = testString.indexOf("word!");
2161         assertEquals(TextUtils.CAP_MODE_WORDS,
2162                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2163         assertEquals(0,
2164                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2165         // issue 1586346
2166         assertEquals(TextUtils.CAP_MODE_CHARACTERS,
2167                 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
2168 
2169         // if no space after "." or "?" or "!", CAP_MODE_SENTENCES and CAP_MODE_WORDS
2170         // should NOT be in effect.
2171         offset = testString.indexOf("No space before");
2172         assertEquals(0,
2173                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2174         assertEquals(0,
2175                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2176         assertEquals(TextUtils.CAP_MODE_CHARACTERS,
2177                 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
2178 
2179         // all mode should be in effect at a beginning of a new paragraph.
2180         offset = testString.indexOf("Paragraph");
2181         assertEquals(TextUtils.CAP_MODE_WORDS,
2182                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2183         // issue 1586346
2184         assertEquals(TextUtils.CAP_MODE_WORDS,
2185                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2186         assertEquals(CAP_MODE_CHARACTERS_AND_WORD,
2187                 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
2188 
2189         // some special word which means the start of a sentence should be skipped.
2190         offset = testString.indexOf("skip begin");
2191         assertEquals(TextUtils.CAP_MODE_WORDS,
2192                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2193         assertEquals(TextUtils.CAP_MODE_SENTENCES,
2194                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2195         // issue 1586346
2196         assertEquals(TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS,
2197                 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
2198 
2199         // some special word which means the end of a sentence should be skipped.
2200         offset = testString.indexOf("skip end");
2201         assertEquals(TextUtils.CAP_MODE_WORDS,
2202                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS));
2203         assertEquals(TextUtils.CAP_MODE_SENTENCES,
2204                 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES));
2205         // issue 1586346
2206         assertEquals(TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS,
2207                 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL));
2208     }
2209 
2210     @Test
testGetCapsModeException()2211     public void testGetCapsModeException() {
2212         String testString = "Start. Sentence word!No space before\n\t" +
2213                 "Paragraph? (\"\'skip begin\'\"). skip end";
2214 
2215         int offset = testString.indexOf("Sentence word!");
2216         assertEquals(TextUtils.CAP_MODE_CHARACTERS,
2217                 TextUtils.getCapsMode(null, offset, TextUtils.CAP_MODE_CHARACTERS));
2218 
2219         try {
2220             TextUtils.getCapsMode(null, offset, TextUtils.CAP_MODE_SENTENCES);
2221             fail("Should throw NullPointerException!");
2222         } catch (NullPointerException e) {
2223             // expected
2224         }
2225 
2226         assertEquals(0, TextUtils.getCapsMode(testString, -1, TextUtils.CAP_MODE_SENTENCES));
2227 
2228         try {
2229             TextUtils.getCapsMode(testString, testString.length() + 1,
2230                     TextUtils.CAP_MODE_SENTENCES);
2231             fail("Should throw IndexOutOfBoundsException!");
2232         } catch (IndexOutOfBoundsException e) {
2233             // expected
2234         }
2235     }
2236 
2237     @Test
testDumpSpans()2238     public void testDumpSpans() {
2239         StringBuilder builder = new StringBuilder();
2240         StringBuilderPrinter printer = new StringBuilderPrinter(builder);
2241         CharSequence source = "test dump spans";
2242         String prefix = "prefix";
2243 
2244         assertEquals(0, builder.length());
2245         TextUtils.dumpSpans(source, printer, prefix);
2246         assertTrue(builder.length() > 0);
2247 
2248         builder = new StringBuilder();
2249         printer = new StringBuilderPrinter(builder);
2250         assertEquals(0, builder.length());
2251         SpannableString spanned = new SpannableString(source);
2252         spanned.setSpan(new Object(), 0, source.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
2253         TextUtils.dumpSpans(spanned, printer, prefix);
2254         assertTrue(builder.length() > 0);
2255     }
2256 
2257     @Test
testGetLayoutDirectionFromLocale()2258     public void testGetLayoutDirectionFromLocale() {
2259         assertEquals(LAYOUT_DIRECTION_LTR,
2260                 TextUtils.getLayoutDirectionFromLocale(null));
2261 
2262         assertEquals(LAYOUT_DIRECTION_LTR,
2263                 TextUtils.getLayoutDirectionFromLocale(Locale.ENGLISH));
2264         assertEquals(LAYOUT_DIRECTION_LTR,
2265                 TextUtils.getLayoutDirectionFromLocale(Locale.CANADA));
2266         assertEquals(LAYOUT_DIRECTION_LTR,
2267                 TextUtils.getLayoutDirectionFromLocale(Locale.CANADA_FRENCH));
2268         assertEquals(LAYOUT_DIRECTION_LTR,
2269                 TextUtils.getLayoutDirectionFromLocale(Locale.FRANCE));
2270         assertEquals(LAYOUT_DIRECTION_LTR,
2271                 TextUtils.getLayoutDirectionFromLocale(Locale.FRENCH));
2272         assertEquals(LAYOUT_DIRECTION_LTR,
2273                 TextUtils.getLayoutDirectionFromLocale(Locale.GERMAN));
2274         assertEquals(LAYOUT_DIRECTION_LTR,
2275                 TextUtils.getLayoutDirectionFromLocale(Locale.GERMANY));
2276         assertEquals(LAYOUT_DIRECTION_LTR,
2277                 TextUtils.getLayoutDirectionFromLocale(Locale.ITALIAN));
2278         assertEquals(LAYOUT_DIRECTION_LTR,
2279                 TextUtils.getLayoutDirectionFromLocale(Locale.ITALY));
2280         assertEquals(LAYOUT_DIRECTION_LTR,
2281                 TextUtils.getLayoutDirectionFromLocale(Locale.UK));
2282         assertEquals(LAYOUT_DIRECTION_LTR,
2283                 TextUtils.getLayoutDirectionFromLocale(Locale.US));
2284 
2285         assertEquals(LAYOUT_DIRECTION_LTR,
2286                 TextUtils.getLayoutDirectionFromLocale(Locale.ROOT));
2287 
2288         assertEquals(LAYOUT_DIRECTION_LTR,
2289                 TextUtils.getLayoutDirectionFromLocale(Locale.CHINA));
2290         assertEquals(LAYOUT_DIRECTION_LTR,
2291                 TextUtils.getLayoutDirectionFromLocale(Locale.CHINESE));
2292         assertEquals(LAYOUT_DIRECTION_LTR,
2293                 TextUtils.getLayoutDirectionFromLocale(Locale.JAPAN));
2294         assertEquals(LAYOUT_DIRECTION_LTR,
2295                 TextUtils.getLayoutDirectionFromLocale(Locale.JAPANESE));
2296         assertEquals(LAYOUT_DIRECTION_LTR,
2297                 TextUtils.getLayoutDirectionFromLocale(Locale.KOREA));
2298         assertEquals(LAYOUT_DIRECTION_LTR,
2299                 TextUtils.getLayoutDirectionFromLocale(Locale.KOREAN));
2300         assertEquals(LAYOUT_DIRECTION_LTR,
2301                 TextUtils.getLayoutDirectionFromLocale(Locale.PRC));
2302         assertEquals(LAYOUT_DIRECTION_LTR,
2303                 TextUtils.getLayoutDirectionFromLocale(Locale.SIMPLIFIED_CHINESE));
2304         assertEquals(LAYOUT_DIRECTION_LTR,
2305                 TextUtils.getLayoutDirectionFromLocale(Locale.TAIWAN));
2306         assertEquals(LAYOUT_DIRECTION_LTR,
2307                 TextUtils.getLayoutDirectionFromLocale(Locale.TRADITIONAL_CHINESE));
2308 
2309         // Some languages always use an RTL script.
2310         for (Locale l : Locale.getAvailableLocales()) {
2311             String languageCode = l.getLanguage();
2312             if (languageCode.equals("ar") ||
2313                     languageCode.equals("fa") ||
2314                     languageCode.equals("iw") ||
2315                     languageCode.equals("he") ||
2316                     languageCode.equals("ps") ||
2317                     languageCode.equals("ur")) {
2318                 int direction = TextUtils.getLayoutDirectionFromLocale(l);
2319                 assertEquals(l.toLanguageTag() + " not RTL: " + direction,
2320                              LAYOUT_DIRECTION_RTL, direction);
2321             }
2322         }
2323 
2324         // Other languages have some cases where they use an RTL script.
2325         String[] tags = {
2326             "pa-Arab",
2327             "pa-Arab-PK",
2328             "ps",
2329             "ps-AF",
2330             "uz-Arab",
2331             "uz-Arab-AF",
2332         };
2333         for (String tag : tags) {
2334             Locale l = Locale.forLanguageTag(tag);
2335             int direction = TextUtils.getLayoutDirectionFromLocale(l);
2336             assertEquals(l.toLanguageTag() + " not RTL: " + direction,
2337                          LAYOUT_DIRECTION_RTL, direction);
2338         }
2339 
2340         // Locale without a real language
2341         Locale locale = Locale.forLanguageTag("zz");
2342         assertEquals(LAYOUT_DIRECTION_LTR,
2343                 TextUtils.getLayoutDirectionFromLocale(locale));
2344     }
2345 }
2346