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