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;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertSame;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 
27 import android.os.Parcel;
28 import android.platform.test.annotations.Presubmit;
29 import android.test.MoreAsserts;
30 import android.text.style.StyleSpan;
31 import android.text.util.Rfc822Token;
32 import android.text.util.Rfc822Tokenizer;
33 import android.view.View;
34 
35 import androidx.test.filters.LargeTest;
36 import androidx.test.filters.SmallTest;
37 import androidx.test.runner.AndroidJUnit4;
38 
39 import com.google.android.collect.Lists;
40 
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Locale;
47 
48 /**
49  * TextUtilsTest tests {@link TextUtils}.
50  */
51 @Presubmit
52 @SmallTest
53 @RunWith(AndroidJUnit4.class)
54 public class TextUtilsTest {
55 
56     @Test
testBasic()57     public void testBasic() {
58         assertEquals("", TextUtils.concat());
59         assertEquals("foo", TextUtils.concat("foo"));
60         assertEquals("foobar", TextUtils.concat("foo", "bar"));
61         assertEquals("foobarbaz", TextUtils.concat("foo", "bar", "baz"));
62 
63         SpannableString foo = new SpannableString("foo");
64         foo.setSpan("foo", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
65 
66         SpannableString bar = new SpannableString("bar");
67         bar.setSpan("bar", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
68 
69         SpannableString baz = new SpannableString("baz");
70         baz.setSpan("baz", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
71 
72         assertEquals("foo", TextUtils.concat(foo).toString());
73         assertEquals("foobar", TextUtils.concat(foo, bar).toString());
74         assertEquals("foobarbaz", TextUtils.concat(foo, bar, baz).toString());
75 
76         assertEquals(1, ((Spanned) TextUtils.concat(foo)).getSpanStart("foo"));
77 
78         assertEquals(1, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("foo"));
79         assertEquals(4, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("bar"));
80 
81         assertEquals(1, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("foo"));
82         assertEquals(4, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("bar"));
83         assertEquals(7, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("baz"));
84 
85         assertTrue(TextUtils.concat("foo", "bar") instanceof String);
86         assertTrue(TextUtils.concat(foo, bar) instanceof SpannedString);
87     }
88 
89     @Test
testTemplateString()90     public void testTemplateString() {
91         CharSequence result;
92 
93         result = TextUtils.expandTemplate("This is a ^1 of the ^2 broadcast ^3.",
94                                           "test", "emergency", "system");
95         assertEquals("This is a test of the emergency broadcast system.",
96                      result.toString());
97 
98         result = TextUtils.expandTemplate("^^^1^^^2^3^a^1^^b^^^c",
99                                           "one", "two", "three");
100         assertEquals("^one^twothree^aone^b^^c",
101                      result.toString());
102 
103         result = TextUtils.expandTemplate("^");
104         assertEquals("^", result.toString());
105 
106         result = TextUtils.expandTemplate("^^");
107         assertEquals("^", result.toString());
108 
109         result = TextUtils.expandTemplate("^^^");
110         assertEquals("^^", result.toString());
111 
112         result = TextUtils.expandTemplate("shorter ^1 values ^2.", "a", "");
113         assertEquals("shorter a values .", result.toString());
114 
115         try {
116             TextUtils.expandTemplate("Only ^1 value given, but ^2 used.", "foo");
117             fail();
118         } catch (IllegalArgumentException e) {
119         }
120 
121         try {
122             TextUtils.expandTemplate("^1 value given, and ^0 used.", "foo");
123             fail();
124         } catch (IllegalArgumentException e) {
125         }
126 
127         result = TextUtils.expandTemplate("^1 value given, and ^9 used.",
128                                           "one", "two", "three", "four", "five",
129                                           "six", "seven", "eight", "nine");
130         assertEquals("one value given, and nine used.", result.toString());
131 
132         try {
133             TextUtils.expandTemplate("^1 value given, and ^10 used.",
134                                      "one", "two", "three", "four", "five",
135                                      "six", "seven", "eight", "nine", "ten");
136             fail();
137         } catch (IllegalArgumentException e) {
138         }
139 
140         // putting carets in the values: expansion is not recursive.
141 
142         result = TextUtils.expandTemplate("^2", "foo", "^^");
143         assertEquals("^^", result.toString());
144 
145         result = TextUtils.expandTemplate("^^2", "foo", "1");
146         assertEquals("^2", result.toString());
147 
148         result = TextUtils.expandTemplate("^1", "value with ^2 in it", "foo");
149         assertEquals("value with ^2 in it", result.toString());
150     }
151 
152     /** Fail unless text+spans contains a span 'spanName' with the given start and end. */
checkContains(Spanned text, String[] spans, String spanName, int start, int end)153     private void checkContains(Spanned text, String[] spans, String spanName,
154                                int start, int end) {
155         for (String i: spans) {
156             if (i.equals(spanName)) {
157                 assertEquals(start, text.getSpanStart(i));
158                 assertEquals(end, text.getSpanEnd(i));
159                 return;
160             }
161         }
162         fail();
163     }
164 
165     @Test
testTemplateSpan()166     public void testTemplateSpan() {
167         SpannableString template;
168         Spanned result;
169         String[] spans;
170 
171         // ordinary replacement
172 
173         template = new SpannableString("a^1b");
174         template.setSpan("before", 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
175         template.setSpan("during", 1, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
176         template.setSpan("after", 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
177         template.setSpan("during+after", 1, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
178 
179         result = (Spanned) TextUtils.expandTemplate(template, "foo");
180         assertEquals(5, result.length());
181         spans = result.getSpans(0, result.length(), String.class);
182 
183         // value is one character longer, so span endpoints should change.
184         assertEquals(4, spans.length);
185         checkContains(result, spans, "before", 0, 1);
186         checkContains(result, spans, "during", 1, 4);
187         checkContains(result, spans, "after", 4, 5);
188         checkContains(result, spans, "during+after", 1, 5);
189 
190 
191         // replacement with empty string
192 
193         result = (Spanned) TextUtils.expandTemplate(template, "");
194         assertEquals(2, result.length());
195         spans = result.getSpans(0, result.length(), String.class);
196 
197         // the "during" span should disappear.
198         assertEquals(3, spans.length);
199         checkContains(result, spans, "before", 0, 1);
200         checkContains(result, spans, "after", 1, 2);
201         checkContains(result, spans, "during+after", 1, 2);
202     }
203 
204     @Test
testStringSplitterSimple()205     public void testStringSplitterSimple() {
206         stringSplitterTestHelper("a,b,cde", new String[] {"a", "b", "cde"});
207     }
208 
209     @Test
testStringSplitterEmpty()210     public void testStringSplitterEmpty() {
211         stringSplitterTestHelper("", new String[] {});
212     }
213 
214     @Test
testStringSplitterWithLeadingEmptyString()215     public void testStringSplitterWithLeadingEmptyString() {
216         stringSplitterTestHelper(",a,b,cde", new String[] {"", "a", "b", "cde"});
217     }
218 
219     @Test
testStringSplitterWithInternalEmptyString()220     public void testStringSplitterWithInternalEmptyString() {
221         stringSplitterTestHelper("a,b,,cde", new String[] {"a", "b", "", "cde"});
222     }
223 
224     @Test
testStringSplitterWithTrailingEmptyString()225     public void testStringSplitterWithTrailingEmptyString() {
226         // A single trailing emtpy string should be ignored.
227         stringSplitterTestHelper("a,b,cde,", new String[] {"a", "b", "cde"});
228     }
229 
stringSplitterTestHelper(String string, String[] expectedStrings)230     private void stringSplitterTestHelper(String string, String[] expectedStrings) {
231         TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(',');
232         splitter.setString(string);
233         List<String> strings = Lists.newArrayList();
234         for (String s : splitter) {
235             strings.add(s);
236         }
237         MoreAsserts.assertEquals(expectedStrings, strings.toArray(new String[]{}));
238     }
239 
240     @Test
testTrim()241     public void testTrim() {
242         String[] strings = { "abc", " abc", "  abc", "abc ", "abc  ",
243                              " abc ", "  abc  ", "\nabc\n", "\nabc", "abc\n" };
244 
245         for (String s : strings) {
246             assertEquals(s.trim().length(), TextUtils.getTrimmedLength(s));
247         }
248     }
249 
250     @Test
testRfc822TokenizerFullAddress()251     public void testRfc822TokenizerFullAddress() {
252         Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("Foo Bar (something) <foo@google.com>");
253         assertNotNull(tokens);
254         assertEquals(1, tokens.length);
255         assertEquals("foo@google.com", tokens[0].getAddress());
256         assertEquals("Foo Bar", tokens[0].getName());
257         assertEquals("something",tokens[0].getComment());
258     }
259 
260     @Test
testRfc822TokenizeItemWithError()261     public void testRfc822TokenizeItemWithError() {
262         Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("\"Foo Bar\\");
263         assertNotNull(tokens);
264         assertEquals(1, tokens.length);
265         assertEquals("Foo Bar", tokens[0].getAddress());
266     }
267 
268     @Test
testRfc822FindToken()269     public void testRfc822FindToken() {
270         Rfc822Tokenizer tokenizer = new Rfc822Tokenizer();
271         //                0           1         2           3         4
272         //                0 1234 56789012345678901234 5678 90123456789012345
273         String address = "\"Foo\" <foo@google.com>, \"Bar\" <bar@google.com>";
274         assertEquals(0, tokenizer.findTokenStart(address, 21));
275         assertEquals(22, tokenizer.findTokenEnd(address, 21));
276         assertEquals(24, tokenizer.findTokenStart(address, 25));
277         assertEquals(46, tokenizer.findTokenEnd(address, 25));
278     }
279 
280     @Test
testRfc822FindTokenWithError()281     public void testRfc822FindTokenWithError() {
282         assertEquals(9, new Rfc822Tokenizer().findTokenEnd("\"Foo Bar\\", 0));
283     }
284 
285     @LargeTest
286     @Test
testEllipsize()287     public void testEllipsize() {
288         CharSequence s1 = "The quick brown fox jumps over \u00FEhe lazy dog.";
289         CharSequence s2 = new Wrapper(s1);
290         Spannable s3 = new SpannableString(s1);
291         s3.setSpan(new StyleSpan(0), 5, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
292         TextPaint p = new TextPaint();
293         p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG);
294 
295         for (int i = 0; i < 100; i++) {
296             for (int j = 0; j < 3; j++) {
297                 TextUtils.TruncateAt kind = null;
298 
299                 switch (j) {
300                 case 0:
301                     kind = TextUtils.TruncateAt.START;
302                     break;
303 
304                 case 1:
305                     kind = TextUtils.TruncateAt.END;
306                     break;
307 
308                 case 2:
309                     kind = TextUtils.TruncateAt.MIDDLE;
310                     break;
311                 }
312 
313                 String out1 = TextUtils.ellipsize(s1, p, i, kind).toString();
314                 String out2 = TextUtils.ellipsize(s2, p, i, kind).toString();
315                 String out3 = TextUtils.ellipsize(s3, p, i, kind).toString();
316 
317                 String keep1 = TextUtils.ellipsize(s1, p, i, kind, true, null).toString();
318                 String keep2 = TextUtils.ellipsize(s2, p, i, kind, true, null).toString();
319                 String keep3 = TextUtils.ellipsize(s3, p, i, kind, true, null).toString();
320 
321                 String trim1 = keep1.replace("\uFEFF", "");
322 
323                 // Are all normal output strings identical?
324                 assertEquals("wid " + i + " pass " + j, out1, out2);
325                 assertEquals("wid " + i + " pass " + j, out2, out3);
326 
327                 // Are preserved output strings identical?
328                 assertEquals("wid " + i + " pass " + j, keep1, keep2);
329                 assertEquals("wid " + i + " pass " + j, keep2, keep3);
330 
331                 // Does trimming padding from preserved yield normal?
332                 assertEquals("wid " + i + " pass " + j, out1, trim1);
333 
334                 // Did preserved output strings preserve length?
335                 assertEquals("wid " + i + " pass " + j, keep1.length(), s1.length());
336 
337                 // Does the output string actually fit in the space?
338                 assertTrue("wid " + i + " pass " + j, p.measureText(out1) <= i);
339 
340                 // Is the padded output the same width as trimmed output?
341                 assertTrue("wid " + i + " pass " + j, p.measureText(keep1) == p.measureText(out1));
342             }
343         }
344     }
345 
346     @Test
testEllipsize_multiCodepoint()347     public void testEllipsize_multiCodepoint() {
348         final TextPaint paint = new TextPaint();
349         final float wordWidth = paint.measureText("MMMM");
350 
351         // Establish the ground rules first, for single-codepoint cases.
352         final String ellipsis = "."; // one full stop character
353         assertEquals(
354                 "MM.\uFEFF",
355                 TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth,
356                         TextUtils.TruncateAt.END, true /* preserve length */,
357                         null /* no callback */, TextDirectionHeuristics.LTR,
358                         ellipsis));
359         assertEquals(
360                 "MM.",
361                 TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth,
362                         TextUtils.TruncateAt.END, false /* preserve length */,
363                         null /* no callback */, TextDirectionHeuristics.LTR,
364                         ellipsis));
365         assertEquals(
366                 "M.",
367                 TextUtils.ellipsize("MM", paint, 0.45f * wordWidth,
368                         TextUtils.TruncateAt.END, true /* preserve length */,
369                         null /* no callback */, TextDirectionHeuristics.LTR,
370                         ellipsis));
371         assertEquals(
372                 "M.",
373                 TextUtils.ellipsize("MM", paint, 0.45f * wordWidth,
374                         TextUtils.TruncateAt.END, false /* preserve length */,
375                         null /* no callback */, TextDirectionHeuristics.LTR,
376                         ellipsis));
377 
378         // Now check the differences for multi-codepoint ellipsis.
379         final String longEllipsis = ".."; // two full stop characters
380         assertEquals(
381                 "MM..",
382                 TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth,
383                         TextUtils.TruncateAt.END, true /* preserve length */,
384                         null /* no callback */, TextDirectionHeuristics.LTR,
385                         longEllipsis));
386         assertEquals(
387                 "MM..",
388                 TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth,
389                         TextUtils.TruncateAt.END, false /* preserve length */,
390                         null /* no callback */, TextDirectionHeuristics.LTR,
391                         longEllipsis));
392         assertEquals(
393                 "M\uFEFF",
394                 TextUtils.ellipsize("MM", paint, 0.45f * wordWidth,
395                         TextUtils.TruncateAt.END, true /* preserve length */,
396                         null /* no callback */, TextDirectionHeuristics.LTR,
397                         longEllipsis));
398         assertEquals(
399                 "M..",
400                 TextUtils.ellipsize("MM", paint, 0.45f * wordWidth,
401                         TextUtils.TruncateAt.END, false /* preserve length */,
402                         null /* no callback */, TextDirectionHeuristics.LTR,
403                         longEllipsis));
404     }
405 
406     @Test
testDelimitedStringContains()407     public void testDelimitedStringContains() {
408         assertFalse(TextUtils.delimitedStringContains("", ',', null));
409         assertFalse(TextUtils.delimitedStringContains(null, ',', ""));
410         // Whole match
411         assertTrue(TextUtils.delimitedStringContains("gps", ',', "gps"));
412         // At beginning.
413         assertTrue(TextUtils.delimitedStringContains("gps,gpsx,network,mock", ',', "gps"));
414         assertTrue(TextUtils.delimitedStringContains("gps,network,mock", ',', "gps"));
415         // In middle, both without, before & after a false match.
416         assertTrue(TextUtils.delimitedStringContains("network,gps,mock", ',', "gps"));
417         assertTrue(TextUtils.delimitedStringContains("network,gps,gpsx,mock", ',', "gps"));
418         assertTrue(TextUtils.delimitedStringContains("network,gpsx,gps,mock", ',', "gps"));
419         // At the end.
420         assertTrue(TextUtils.delimitedStringContains("network,mock,gps", ',', "gps"));
421         assertTrue(TextUtils.delimitedStringContains("network,mock,gpsx,gps", ',', "gps"));
422         // Not present (but with a false match)
423         assertFalse(TextUtils.delimitedStringContains("network,mock,gpsx", ',', "gps"));
424     }
425 
426     @Test
testCharSequenceCreator()427     public void testCharSequenceCreator() {
428         Parcel p = Parcel.obtain();
429         CharSequence text;
430         try {
431             TextUtils.writeToParcel(null, p, 0);
432             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
433             assertNull("null CharSequence should generate null from parcel", text);
434         } finally {
435             p.recycle();
436         }
437         p = Parcel.obtain();
438         try {
439             TextUtils.writeToParcel("test", p, 0);
440             p.setDataPosition(0);
441             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
442             assertEquals("conversion to/from parcel failed", "test", text);
443         } finally {
444             p.recycle();
445         }
446     }
447 
448     @Test
testCharSequenceCreatorNull()449     public void testCharSequenceCreatorNull() {
450         Parcel p;
451         CharSequence text;
452         p = Parcel.obtain();
453         try {
454             TextUtils.writeToParcel(null, p, 0);
455             p.setDataPosition(0);
456             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
457             assertNull("null CharSequence should generate null from parcel", text);
458         } finally {
459             p.recycle();
460         }
461     }
462 
463     @Test
testCharSequenceCreatorSpannable()464     public void testCharSequenceCreatorSpannable() {
465         Parcel p;
466         CharSequence text;
467         p = Parcel.obtain();
468         try {
469             TextUtils.writeToParcel(new SpannableString("test"), p, 0);
470             p.setDataPosition(0);
471             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
472             assertEquals("conversion to/from parcel failed", "test", text.toString());
473         } finally {
474             p.recycle();
475         }
476     }
477 
478     @Test
testCharSequenceCreatorString()479     public void testCharSequenceCreatorString() {
480         Parcel p;
481         CharSequence text;
482         p = Parcel.obtain();
483         try {
484             TextUtils.writeToParcel("test", p, 0);
485             p.setDataPosition(0);
486             text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p);
487             assertEquals("conversion to/from parcel failed", "test", text.toString());
488         } finally {
489             p.recycle();
490         }
491     }
492 
493     /**
494      * CharSequence wrapper for testing the cases where text is copied into
495      * a char array instead of working from a String or a Spanned.
496      */
497     private static class Wrapper implements CharSequence {
498         private CharSequence mString;
499 
Wrapper(CharSequence s)500         public Wrapper(CharSequence s) {
501             mString = s;
502         }
503 
504         @Override
length()505         public int length() {
506             return mString.length();
507         }
508 
509         @Override
charAt(int off)510         public char charAt(int off) {
511             return mString.charAt(off);
512         }
513 
514         @Override
toString()515         public String toString() {
516             return mString.toString();
517         }
518 
519         @Override
subSequence(int start, int end)520         public CharSequence subSequence(int start, int end) {
521             return new Wrapper(mString.subSequence(start, end));
522         }
523     }
524 
525     @Test
testRemoveEmptySpans()526     public void testRemoveEmptySpans() {
527         MockSpanned spanned = new MockSpanned();
528 
529         spanned.test();
530         spanned.addSpan().test();
531         spanned.addSpan().test();
532         spanned.addSpan().test();
533         spanned.addEmptySpan().test();
534         spanned.addSpan().test();
535         spanned.addEmptySpan().test();
536         spanned.addEmptySpan().test();
537         spanned.addSpan().test();
538 
539         spanned.clear();
540         spanned.addEmptySpan().test();
541         spanned.addEmptySpan().test();
542         spanned.addEmptySpan().test();
543         spanned.addSpan().test();
544         spanned.addEmptySpan().test();
545         spanned.addSpan().test();
546 
547         spanned.clear();
548         spanned.addSpan().test();
549         spanned.addEmptySpan().test();
550         spanned.addSpan().test();
551         spanned.addEmptySpan().test();
552         spanned.addSpan().test();
553         spanned.addSpan().test();
554     }
555 
556     protected static class MockSpanned implements Spanned {
557 
558         private List<Object> allSpans = new ArrayList<Object>();
559         private List<Object> nonEmptySpans = new ArrayList<Object>();
560 
clear()561         public void clear() {
562             allSpans.clear();
563             nonEmptySpans.clear();
564         }
565 
addSpan()566         public MockSpanned addSpan() {
567             Object o = new Object();
568             allSpans.add(o);
569             nonEmptySpans.add(o);
570             return this;
571         }
572 
addEmptySpan()573         public MockSpanned addEmptySpan() {
574             Object o = new Object();
575             allSpans.add(o);
576             return this;
577         }
578 
test()579         public void test() {
580             Object[] nonEmpty = TextUtils.removeEmptySpans(allSpans.toArray(), this, Object.class);
581             assertEquals("Mismatched array size", nonEmptySpans.size(), nonEmpty.length);
582             for (int i=0; i<nonEmpty.length; i++) {
583                 assertEquals("Span differ", nonEmptySpans.get(i), nonEmpty[i]);
584             }
585         }
586 
587         @Override
charAt(int arg0)588         public char charAt(int arg0) {
589             return 0;
590         }
591 
592         @Override
length()593         public int length() {
594             return 0;
595         }
596 
597         @Override
subSequence(int arg0, int arg1)598         public CharSequence subSequence(int arg0, int arg1) {
599             return null;
600         }
601 
602         @Override
getSpans(int start, int end, Class<T> type)603         public <T> T[] getSpans(int start, int end, Class<T> type) {
604             return null;
605         }
606 
607         @Override
getSpanStart(Object tag)608         public int getSpanStart(Object tag) {
609             return 0;
610         }
611 
612         @Override
getSpanEnd(Object tag)613         public int getSpanEnd(Object tag) {
614             return nonEmptySpans.contains(tag) ? 1 : 0;
615         }
616 
617         @Override
getSpanFlags(Object tag)618         public int getSpanFlags(Object tag) {
619             return 0;
620         }
621 
622         @Override
nextSpanTransition(int start, int limit, Class type)623         public int nextSpanTransition(int start, int limit, Class type) {
624             return 0;
625         }
626     }
627 
628     @Test
testGetLayoutDirectionFromLocale()629     public void testGetLayoutDirectionFromLocale() {
630         assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(null));
631         assertEquals(View.LAYOUT_DIRECTION_LTR,
632                 TextUtils.getLayoutDirectionFromLocale(Locale.ROOT));
633         assertEquals(View.LAYOUT_DIRECTION_LTR,
634                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en")));
635         assertEquals(View.LAYOUT_DIRECTION_LTR,
636                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en-US")));
637         assertEquals(View.LAYOUT_DIRECTION_LTR,
638                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az")));
639         assertEquals(View.LAYOUT_DIRECTION_LTR,
640                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-AZ")));
641         assertEquals(View.LAYOUT_DIRECTION_LTR,
642                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-Latn")));
643         assertEquals(View.LAYOUT_DIRECTION_LTR,
644                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en-EG")));
645         assertEquals(View.LAYOUT_DIRECTION_LTR,
646                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ar-Latn")));
647 
648         assertEquals(View.LAYOUT_DIRECTION_RTL,
649                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ar")));
650         assertEquals(View.LAYOUT_DIRECTION_RTL,
651                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("fa")));
652         assertEquals(View.LAYOUT_DIRECTION_RTL,
653                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("he")));
654         assertEquals(View.LAYOUT_DIRECTION_RTL,
655                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("iw")));
656         assertEquals(View.LAYOUT_DIRECTION_RTL,
657                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ur")));
658         assertEquals(View.LAYOUT_DIRECTION_RTL,
659                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("dv")));
660         assertEquals(View.LAYOUT_DIRECTION_RTL,
661                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-Arab")));
662         assertEquals(View.LAYOUT_DIRECTION_RTL,
663                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-IR")));
664         assertEquals(View.LAYOUT_DIRECTION_RTL,
665                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("fa-US")));
666         assertEquals(View.LAYOUT_DIRECTION_RTL,
667                 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("tr-Arab")));
668     }
669 
670     @Test
testToUpperCase()671     public void testToUpperCase() {
672         {
673             final CharSequence result = TextUtils.toUpperCase(null, "abc", false);
674             assertEquals(StringBuilder.class, result.getClass());
675             assertEquals("ABC", result.toString());
676         }
677         {
678             final SpannableString str = new SpannableString("abc");
679             Object span = new Object();
680             str.setSpan(span, 1, 2, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
681 
682             final CharSequence result = TextUtils.toUpperCase(null, str, true /* copySpans */);
683             assertEquals(SpannableStringBuilder.class, result.getClass());
684             assertEquals("ABC", result.toString());
685             final Spanned spanned = (Spanned) result;
686             final Object[] resultSpans = spanned.getSpans(0, result.length(), Object.class);
687             assertEquals(1, resultSpans.length);
688             assertSame(span, resultSpans[0]);
689             assertEquals(1, spanned.getSpanStart(span));
690             assertEquals(2, spanned.getSpanEnd(span));
691             assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, spanned.getSpanFlags(span));
692         }
693         {
694             final Locale turkish = new Locale("tr", "TR");
695             final CharSequence result = TextUtils.toUpperCase(turkish, "i", false);
696             assertEquals(StringBuilder.class, result.getClass());
697             assertEquals("İ", result.toString());
698         }
699         {
700             final String str = "ABC";
701             assertSame(str, TextUtils.toUpperCase(null, str, false));
702         }
703         {
704             final SpannableString str = new SpannableString("ABC");
705             str.setSpan(new Object(), 1, 2, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
706             assertSame(str, TextUtils.toUpperCase(null, str, true /* copySpans */));
707         }
708     }
709 
710     // Copied from cts/tests/tests/widget/src/android/widget/cts/TextViewTest.java and modified
711     // for the TextUtils.toUpperCase method.
712     @Test
testToUpperCase_SpansArePreserved()713     public void testToUpperCase_SpansArePreserved() {
714         final Locale greek = new Locale("el", "GR");
715         final String lowerString = "ι\u0301ριδα";  // ίριδα with first letter decomposed
716         final String upperString = "ΙΡΙΔΑ";  // uppercased
717         // expected lowercase to uppercase index map
718         final int[] indexMap = {0, 1, 1, 2, 3, 4, 5};
719         final int flags = Spanned.SPAN_INCLUSIVE_INCLUSIVE;
720 
721         final Spannable source = new SpannableString(lowerString);
722         source.setSpan(new Object(), 0, 1, flags);
723         source.setSpan(new Object(), 1, 2, flags);
724         source.setSpan(new Object(), 2, 3, flags);
725         source.setSpan(new Object(), 3, 4, flags);
726         source.setSpan(new Object(), 4, 5, flags);
727         source.setSpan(new Object(), 5, 6, flags);
728         source.setSpan(new Object(), 0, 2, flags);
729         source.setSpan(new Object(), 1, 3, flags);
730         source.setSpan(new Object(), 2, 4, flags);
731         source.setSpan(new Object(), 0, 6, flags);
732         final Object[] sourceSpans = source.getSpans(0, source.length(), Object.class);
733 
734         final CharSequence uppercase = TextUtils.toUpperCase(greek, source, true /* copySpans */);
735         assertEquals(SpannableStringBuilder.class, uppercase.getClass());
736         final Spanned result = (Spanned) uppercase;
737 
738         assertEquals(upperString, result.toString());
739         final Object[] resultSpans = result.getSpans(0, result.length(), Object.class);
740         assertEquals(sourceSpans.length, resultSpans.length);
741         for (int i = 0; i < sourceSpans.length; i++) {
742             assertSame(sourceSpans[i], resultSpans[i]);
743             final Object span = sourceSpans[i];
744             assertEquals(indexMap[source.getSpanStart(span)], result.getSpanStart(span));
745             assertEquals(indexMap[source.getSpanEnd(span)], result.getSpanEnd(span));
746             assertEquals(source.getSpanFlags(span), result.getSpanFlags(span));
747         }
748     }
749 
750     @Test
testTrimToSize()751     public void testTrimToSize() {
752         final String testString = "a\uD800\uDC00a";
753         assertEquals("Should return text as it is if size is longer than length",
754                 testString, TextUtils.trimToSize(testString, 5));
755         assertEquals("Should return text as it is if size is equal to length",
756                 testString, TextUtils.trimToSize(testString, 4));
757         assertEquals("Should trim text",
758                 "a\uD800\uDC00", TextUtils.trimToSize(testString, 3));
759         assertEquals("Should trim surrogate pairs if size is in the middle of a pair",
760                 "a", TextUtils.trimToSize(testString, 2));
761         assertEquals("Should trim text",
762                 "a", TextUtils.trimToSize(testString, 1));
763         assertEquals("Should handle null",
764                 null, TextUtils.trimToSize(null, 1));
765 
766         assertEquals("Should trim high surrogate if invalid surrogate",
767                 "a\uD800", TextUtils.trimToSize("a\uD800\uD800", 2));
768         assertEquals("Should trim low surrogate if invalid surrogate",
769                 "a\uDC00", TextUtils.trimToSize("a\uDC00\uDC00", 2));
770     }
771 
772     @Test(expected = IllegalArgumentException.class)
testTrimToSizeThrowsExceptionForNegativeSize()773     public void testTrimToSizeThrowsExceptionForNegativeSize() {
774         TextUtils.trimToSize("", -1);
775     }
776 
777     @Test(expected = IllegalArgumentException.class)
testTrimToSizeThrowsExceptionForZeroSize()778     public void testTrimToSizeThrowsExceptionForZeroSize() {
779         TextUtils.trimToSize("abc", 0);
780     }
781 
782     @Test
length()783     public void length() {
784         assertEquals(0, TextUtils.length(null));
785         assertEquals(0, TextUtils.length(""));
786         assertEquals(2, TextUtils.length("  "));
787         assertEquals(6, TextUtils.length("Hello!"));
788     }
789 
790     @Test
testTrimToLengthWithEllipsis()791     public void testTrimToLengthWithEllipsis() {
792         assertEquals("ABC...", TextUtils.trimToLengthWithEllipsis("ABCDEF", 3));
793         assertEquals("ABC", TextUtils.trimToLengthWithEllipsis("ABC", 3));
794         assertEquals("", TextUtils.trimToLengthWithEllipsis("", 3));
795     }
796 }
797