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