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