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