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