Home | History | Annotate | Download | only in text
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.text;
     18 
     19 import android.annotation.FloatRange;
     20 import android.annotation.NonNull;
     21 import android.annotation.Nullable;
     22 import android.annotation.PluralsRes;
     23 import android.content.Context;
     24 import android.content.res.Resources;
     25 import android.icu.lang.UCharacter;
     26 import android.icu.util.ULocale;
     27 import android.os.Parcel;
     28 import android.os.Parcelable;
     29 import android.os.SystemProperties;
     30 import android.provider.Settings;
     31 import android.text.style.AbsoluteSizeSpan;
     32 import android.text.style.AccessibilityClickableSpan;
     33 import android.text.style.AccessibilityURLSpan;
     34 import android.text.style.AlignmentSpan;
     35 import android.text.style.BackgroundColorSpan;
     36 import android.text.style.BulletSpan;
     37 import android.text.style.CharacterStyle;
     38 import android.text.style.EasyEditSpan;
     39 import android.text.style.ForegroundColorSpan;
     40 import android.text.style.LeadingMarginSpan;
     41 import android.text.style.LocaleSpan;
     42 import android.text.style.MetricAffectingSpan;
     43 import android.text.style.ParagraphStyle;
     44 import android.text.style.QuoteSpan;
     45 import android.text.style.RelativeSizeSpan;
     46 import android.text.style.ReplacementSpan;
     47 import android.text.style.ScaleXSpan;
     48 import android.text.style.SpellCheckSpan;
     49 import android.text.style.StrikethroughSpan;
     50 import android.text.style.StyleSpan;
     51 import android.text.style.SubscriptSpan;
     52 import android.text.style.SuggestionRangeSpan;
     53 import android.text.style.SuggestionSpan;
     54 import android.text.style.SuperscriptSpan;
     55 import android.text.style.TextAppearanceSpan;
     56 import android.text.style.TtsSpan;
     57 import android.text.style.TypefaceSpan;
     58 import android.text.style.URLSpan;
     59 import android.text.style.UnderlineSpan;
     60 import android.text.style.UpdateAppearance;
     61 import android.util.Log;
     62 import android.util.Printer;
     63 import android.view.View;
     64 
     65 import com.android.internal.R;
     66 import com.android.internal.util.ArrayUtils;
     67 import com.android.internal.util.Preconditions;
     68 
     69 import java.lang.reflect.Array;
     70 import java.util.Iterator;
     71 import java.util.List;
     72 import java.util.Locale;
     73 import java.util.regex.Pattern;
     74 
     75 public class TextUtils {
     76     private static final String TAG = "TextUtils";
     77 
     78     /* package */ static final char[] ELLIPSIS_NORMAL = { '\u2026' }; // this is "..."
     79     /** {@hide} */
     80     public static final String ELLIPSIS_STRING = new String(ELLIPSIS_NORMAL);
     81 
     82     /* package */ static final char[] ELLIPSIS_TWO_DOTS = { '\u2025' }; // this is ".."
     83     private static final String ELLIPSIS_TWO_DOTS_STRING = new String(ELLIPSIS_TWO_DOTS);
     84 
     85     private TextUtils() { /* cannot be instantiated */ }
     86 
     87     public static void getChars(CharSequence s, int start, int end,
     88                                 char[] dest, int destoff) {
     89         Class<? extends CharSequence> c = s.getClass();
     90 
     91         if (c == String.class)
     92             ((String) s).getChars(start, end, dest, destoff);
     93         else if (c == StringBuffer.class)
     94             ((StringBuffer) s).getChars(start, end, dest, destoff);
     95         else if (c == StringBuilder.class)
     96             ((StringBuilder) s).getChars(start, end, dest, destoff);
     97         else if (s instanceof GetChars)
     98             ((GetChars) s).getChars(start, end, dest, destoff);
     99         else {
    100             for (int i = start; i < end; i++)
    101                 dest[destoff++] = s.charAt(i);
    102         }
    103     }
    104 
    105     public static int indexOf(CharSequence s, char ch) {
    106         return indexOf(s, ch, 0);
    107     }
    108 
    109     public static int indexOf(CharSequence s, char ch, int start) {
    110         Class<? extends CharSequence> c = s.getClass();
    111 
    112         if (c == String.class)
    113             return ((String) s).indexOf(ch, start);
    114 
    115         return indexOf(s, ch, start, s.length());
    116     }
    117 
    118     public static int indexOf(CharSequence s, char ch, int start, int end) {
    119         Class<? extends CharSequence> c = s.getClass();
    120 
    121         if (s instanceof GetChars || c == StringBuffer.class ||
    122             c == StringBuilder.class || c == String.class) {
    123             final int INDEX_INCREMENT = 500;
    124             char[] temp = obtain(INDEX_INCREMENT);
    125 
    126             while (start < end) {
    127                 int segend = start + INDEX_INCREMENT;
    128                 if (segend > end)
    129                     segend = end;
    130 
    131                 getChars(s, start, segend, temp, 0);
    132 
    133                 int count = segend - start;
    134                 for (int i = 0; i < count; i++) {
    135                     if (temp[i] == ch) {
    136                         recycle(temp);
    137                         return i + start;
    138                     }
    139                 }
    140 
    141                 start = segend;
    142             }
    143 
    144             recycle(temp);
    145             return -1;
    146         }
    147 
    148         for (int i = start; i < end; i++)
    149             if (s.charAt(i) == ch)
    150                 return i;
    151 
    152         return -1;
    153     }
    154 
    155     public static int lastIndexOf(CharSequence s, char ch) {
    156         return lastIndexOf(s, ch, s.length() - 1);
    157     }
    158 
    159     public static int lastIndexOf(CharSequence s, char ch, int last) {
    160         Class<? extends CharSequence> c = s.getClass();
    161 
    162         if (c == String.class)
    163             return ((String) s).lastIndexOf(ch, last);
    164 
    165         return lastIndexOf(s, ch, 0, last);
    166     }
    167 
    168     public static int lastIndexOf(CharSequence s, char ch,
    169                                   int start, int last) {
    170         if (last < 0)
    171             return -1;
    172         if (last >= s.length())
    173             last = s.length() - 1;
    174 
    175         int end = last + 1;
    176 
    177         Class<? extends CharSequence> c = s.getClass();
    178 
    179         if (s instanceof GetChars || c == StringBuffer.class ||
    180             c == StringBuilder.class || c == String.class) {
    181             final int INDEX_INCREMENT = 500;
    182             char[] temp = obtain(INDEX_INCREMENT);
    183 
    184             while (start < end) {
    185                 int segstart = end - INDEX_INCREMENT;
    186                 if (segstart < start)
    187                     segstart = start;
    188 
    189                 getChars(s, segstart, end, temp, 0);
    190 
    191                 int count = end - segstart;
    192                 for (int i = count - 1; i >= 0; i--) {
    193                     if (temp[i] == ch) {
    194                         recycle(temp);
    195                         return i + segstart;
    196                     }
    197                 }
    198 
    199                 end = segstart;
    200             }
    201 
    202             recycle(temp);
    203             return -1;
    204         }
    205 
    206         for (int i = end - 1; i >= start; i--)
    207             if (s.charAt(i) == ch)
    208                 return i;
    209 
    210         return -1;
    211     }
    212 
    213     public static int indexOf(CharSequence s, CharSequence needle) {
    214         return indexOf(s, needle, 0, s.length());
    215     }
    216 
    217     public static int indexOf(CharSequence s, CharSequence needle, int start) {
    218         return indexOf(s, needle, start, s.length());
    219     }
    220 
    221     public static int indexOf(CharSequence s, CharSequence needle,
    222                               int start, int end) {
    223         int nlen = needle.length();
    224         if (nlen == 0)
    225             return start;
    226 
    227         char c = needle.charAt(0);
    228 
    229         for (;;) {
    230             start = indexOf(s, c, start);
    231             if (start > end - nlen) {
    232                 break;
    233             }
    234 
    235             if (start < 0) {
    236                 return -1;
    237             }
    238 
    239             if (regionMatches(s, start, needle, 0, nlen)) {
    240                 return start;
    241             }
    242 
    243             start++;
    244         }
    245         return -1;
    246     }
    247 
    248     public static boolean regionMatches(CharSequence one, int toffset,
    249                                         CharSequence two, int ooffset,
    250                                         int len) {
    251         int tempLen = 2 * len;
    252         if (tempLen < len) {
    253             // Integer overflow; len is unreasonably large
    254             throw new IndexOutOfBoundsException();
    255         }
    256         char[] temp = obtain(tempLen);
    257 
    258         getChars(one, toffset, toffset + len, temp, 0);
    259         getChars(two, ooffset, ooffset + len, temp, len);
    260 
    261         boolean match = true;
    262         for (int i = 0; i < len; i++) {
    263             if (temp[i] != temp[i + len]) {
    264                 match = false;
    265                 break;
    266             }
    267         }
    268 
    269         recycle(temp);
    270         return match;
    271     }
    272 
    273     /**
    274      * Create a new String object containing the given range of characters
    275      * from the source string.  This is different than simply calling
    276      * {@link CharSequence#subSequence(int, int) CharSequence.subSequence}
    277      * in that it does not preserve any style runs in the source sequence,
    278      * allowing a more efficient implementation.
    279      */
    280     public static String substring(CharSequence source, int start, int end) {
    281         if (source instanceof String)
    282             return ((String) source).substring(start, end);
    283         if (source instanceof StringBuilder)
    284             return ((StringBuilder) source).substring(start, end);
    285         if (source instanceof StringBuffer)
    286             return ((StringBuffer) source).substring(start, end);
    287 
    288         char[] temp = obtain(end - start);
    289         getChars(source, start, end, temp, 0);
    290         String ret = new String(temp, 0, end - start);
    291         recycle(temp);
    292 
    293         return ret;
    294     }
    295 
    296     /**
    297      * Returns a string containing the tokens joined by delimiters.
    298      * @param tokens an array objects to be joined. Strings will be formed from
    299      *     the objects by calling object.toString().
    300      */
    301     public static String join(CharSequence delimiter, Object[] tokens) {
    302         StringBuilder sb = new StringBuilder();
    303         boolean firstTime = true;
    304         for (Object token: tokens) {
    305             if (firstTime) {
    306                 firstTime = false;
    307             } else {
    308                 sb.append(delimiter);
    309             }
    310             sb.append(token);
    311         }
    312         return sb.toString();
    313     }
    314 
    315     /**
    316      * Returns a string containing the tokens joined by delimiters.
    317      * @param tokens an array objects to be joined. Strings will be formed from
    318      *     the objects by calling object.toString().
    319      */
    320     public static String join(CharSequence delimiter, Iterable tokens) {
    321         StringBuilder sb = new StringBuilder();
    322         Iterator<?> it = tokens.iterator();
    323         if (it.hasNext()) {
    324             sb.append(it.next());
    325             while (it.hasNext()) {
    326                 sb.append(delimiter);
    327                 sb.append(it.next());
    328             }
    329         }
    330         return sb.toString();
    331     }
    332 
    333     /**
    334      * String.split() returns [''] when the string to be split is empty. This returns []. This does
    335      * not remove any empty strings from the result. For example split("a,", ","  ) returns {"a", ""}.
    336      *
    337      * @param text the string to split
    338      * @param expression the regular expression to match
    339      * @return an array of strings. The array will be empty if text is empty
    340      *
    341      * @throws NullPointerException if expression or text is null
    342      */
    343     public static String[] split(String text, String expression) {
    344         if (text.length() == 0) {
    345             return EMPTY_STRING_ARRAY;
    346         } else {
    347             return text.split(expression, -1);
    348         }
    349     }
    350 
    351     /**
    352      * Splits a string on a pattern. String.split() returns [''] when the string to be
    353      * split is empty. This returns []. This does not remove any empty strings from the result.
    354      * @param text the string to split
    355      * @param pattern the regular expression to match
    356      * @return an array of strings. The array will be empty if text is empty
    357      *
    358      * @throws NullPointerException if expression or text is null
    359      */
    360     public static String[] split(String text, Pattern pattern) {
    361         if (text.length() == 0) {
    362             return EMPTY_STRING_ARRAY;
    363         } else {
    364             return pattern.split(text, -1);
    365         }
    366     }
    367 
    368     /**
    369      * An interface for splitting strings according to rules that are opaque to the user of this
    370      * interface. This also has less overhead than split, which uses regular expressions and
    371      * allocates an array to hold the results.
    372      *
    373      * <p>The most efficient way to use this class is:
    374      *
    375      * <pre>
    376      * // Once
    377      * TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(delimiter);
    378      *
    379      * // Once per string to split
    380      * splitter.setString(string);
    381      * for (String s : splitter) {
    382      *     ...
    383      * }
    384      * </pre>
    385      */
    386     public interface StringSplitter extends Iterable<String> {
    387         public void setString(String string);
    388     }
    389 
    390     /**
    391      * A simple string splitter.
    392      *
    393      * <p>If the final character in the string to split is the delimiter then no empty string will
    394      * be returned for the empty string after that delimeter. That is, splitting <tt>"a,b,"</tt> on
    395      * comma will return <tt>"a", "b"</tt>, not <tt>"a", "b", ""</tt>.
    396      */
    397     public static class SimpleStringSplitter implements StringSplitter, Iterator<String> {
    398         private String mString;
    399         private char mDelimiter;
    400         private int mPosition;
    401         private int mLength;
    402 
    403         /**
    404          * Initializes the splitter. setString may be called later.
    405          * @param delimiter the delimeter on which to split
    406          */
    407         public SimpleStringSplitter(char delimiter) {
    408             mDelimiter = delimiter;
    409         }
    410 
    411         /**
    412          * Sets the string to split
    413          * @param string the string to split
    414          */
    415         public void setString(String string) {
    416             mString = string;
    417             mPosition = 0;
    418             mLength = mString.length();
    419         }
    420 
    421         public Iterator<String> iterator() {
    422             return this;
    423         }
    424 
    425         public boolean hasNext() {
    426             return mPosition < mLength;
    427         }
    428 
    429         public String next() {
    430             int end = mString.indexOf(mDelimiter, mPosition);
    431             if (end == -1) {
    432                 end = mLength;
    433             }
    434             String nextString = mString.substring(mPosition, end);
    435             mPosition = end + 1; // Skip the delimiter.
    436             return nextString;
    437         }
    438 
    439         public void remove() {
    440             throw new UnsupportedOperationException();
    441         }
    442     }
    443 
    444     public static CharSequence stringOrSpannedString(CharSequence source) {
    445         if (source == null)
    446             return null;
    447         if (source instanceof SpannedString)
    448             return source;
    449         if (source instanceof Spanned)
    450             return new SpannedString(source);
    451 
    452         return source.toString();
    453     }
    454 
    455     /**
    456      * Returns true if the string is null or 0-length.
    457      * @param str the string to be examined
    458      * @return true if str is null or zero length
    459      */
    460     public static boolean isEmpty(@Nullable CharSequence str) {
    461         return str == null || str.length() == 0;
    462     }
    463 
    464     /** {@hide} */
    465     public static String nullIfEmpty(@Nullable String str) {
    466         return isEmpty(str) ? null : str;
    467     }
    468 
    469     /** {@hide} */
    470     public static String emptyIfNull(@Nullable String str) {
    471         return str == null ? "" : str;
    472     }
    473 
    474     /** {@hide} */
    475     public static String firstNotEmpty(@Nullable String a, @NonNull String b) {
    476         return !isEmpty(a) ? a : Preconditions.checkStringNotEmpty(b);
    477     }
    478 
    479     /** {@hide} */
    480     public static int length(@Nullable String s) {
    481         return isEmpty(s) ? 0 : s.length();
    482     }
    483 
    484     /**
    485      * Returns the length that the specified CharSequence would have if
    486      * spaces and ASCII control characters were trimmed from the start and end,
    487      * as by {@link String#trim}.
    488      */
    489     public static int getTrimmedLength(CharSequence s) {
    490         int len = s.length();
    491 
    492         int start = 0;
    493         while (start < len && s.charAt(start) <= ' ') {
    494             start++;
    495         }
    496 
    497         int end = len;
    498         while (end > start && s.charAt(end - 1) <= ' ') {
    499             end--;
    500         }
    501 
    502         return end - start;
    503     }
    504 
    505     /**
    506      * Returns true if a and b are equal, including if they are both null.
    507      * <p><i>Note: In platform versions 1.1 and earlier, this method only worked well if
    508      * both the arguments were instances of String.</i></p>
    509      * @param a first CharSequence to check
    510      * @param b second CharSequence to check
    511      * @return true if a and b are equal
    512      */
    513     public static boolean equals(CharSequence a, CharSequence b) {
    514         if (a == b) return true;
    515         int length;
    516         if (a != null && b != null && (length = a.length()) == b.length()) {
    517             if (a instanceof String && b instanceof String) {
    518                 return a.equals(b);
    519             } else {
    520                 for (int i = 0; i < length; i++) {
    521                     if (a.charAt(i) != b.charAt(i)) return false;
    522                 }
    523                 return true;
    524             }
    525         }
    526         return false;
    527     }
    528 
    529     /**
    530      * This function only reverses individual {@code char}s and not their associated
    531      * spans. It doesn't support surrogate pairs (that correspond to non-BMP code points), combining
    532      * sequences or conjuncts either.
    533      * @deprecated Do not use.
    534      */
    535     @Deprecated
    536     public static CharSequence getReverse(CharSequence source, int start, int end) {
    537         return new Reverser(source, start, end);
    538     }
    539 
    540     private static class Reverser
    541     implements CharSequence, GetChars
    542     {
    543         public Reverser(CharSequence source, int start, int end) {
    544             mSource = source;
    545             mStart = start;
    546             mEnd = end;
    547         }
    548 
    549         public int length() {
    550             return mEnd - mStart;
    551         }
    552 
    553         public CharSequence subSequence(int start, int end) {
    554             char[] buf = new char[end - start];
    555 
    556             getChars(start, end, buf, 0);
    557             return new String(buf);
    558         }
    559 
    560         @Override
    561         public String toString() {
    562             return subSequence(0, length()).toString();
    563         }
    564 
    565         public char charAt(int off) {
    566             return (char) UCharacter.getMirror(mSource.charAt(mEnd - 1 - off));
    567         }
    568 
    569         @SuppressWarnings("deprecation")
    570         public void getChars(int start, int end, char[] dest, int destoff) {
    571             TextUtils.getChars(mSource, start + mStart, end + mStart,
    572                                dest, destoff);
    573             AndroidCharacter.mirror(dest, 0, end - start);
    574 
    575             int len = end - start;
    576             int n = (end - start) / 2;
    577             for (int i = 0; i < n; i++) {
    578                 char tmp = dest[destoff + i];
    579 
    580                 dest[destoff + i] = dest[destoff + len - i - 1];
    581                 dest[destoff + len - i - 1] = tmp;
    582             }
    583         }
    584 
    585         private CharSequence mSource;
    586         private int mStart;
    587         private int mEnd;
    588     }
    589 
    590     /** @hide */
    591     public static final int ALIGNMENT_SPAN = 1;
    592     /** @hide */
    593     public static final int FIRST_SPAN = ALIGNMENT_SPAN;
    594     /** @hide */
    595     public static final int FOREGROUND_COLOR_SPAN = 2;
    596     /** @hide */
    597     public static final int RELATIVE_SIZE_SPAN = 3;
    598     /** @hide */
    599     public static final int SCALE_X_SPAN = 4;
    600     /** @hide */
    601     public static final int STRIKETHROUGH_SPAN = 5;
    602     /** @hide */
    603     public static final int UNDERLINE_SPAN = 6;
    604     /** @hide */
    605     public static final int STYLE_SPAN = 7;
    606     /** @hide */
    607     public static final int BULLET_SPAN = 8;
    608     /** @hide */
    609     public static final int QUOTE_SPAN = 9;
    610     /** @hide */
    611     public static final int LEADING_MARGIN_SPAN = 10;
    612     /** @hide */
    613     public static final int URL_SPAN = 11;
    614     /** @hide */
    615     public static final int BACKGROUND_COLOR_SPAN = 12;
    616     /** @hide */
    617     public static final int TYPEFACE_SPAN = 13;
    618     /** @hide */
    619     public static final int SUPERSCRIPT_SPAN = 14;
    620     /** @hide */
    621     public static final int SUBSCRIPT_SPAN = 15;
    622     /** @hide */
    623     public static final int ABSOLUTE_SIZE_SPAN = 16;
    624     /** @hide */
    625     public static final int TEXT_APPEARANCE_SPAN = 17;
    626     /** @hide */
    627     public static final int ANNOTATION = 18;
    628     /** @hide */
    629     public static final int SUGGESTION_SPAN = 19;
    630     /** @hide */
    631     public static final int SPELL_CHECK_SPAN = 20;
    632     /** @hide */
    633     public static final int SUGGESTION_RANGE_SPAN = 21;
    634     /** @hide */
    635     public static final int EASY_EDIT_SPAN = 22;
    636     /** @hide */
    637     public static final int LOCALE_SPAN = 23;
    638     /** @hide */
    639     public static final int TTS_SPAN = 24;
    640     /** @hide */
    641     public static final int ACCESSIBILITY_CLICKABLE_SPAN = 25;
    642     /** @hide */
    643     public static final int ACCESSIBILITY_URL_SPAN = 26;
    644     /** @hide */
    645     public static final int LAST_SPAN = ACCESSIBILITY_URL_SPAN;
    646 
    647     /**
    648      * Flatten a CharSequence and whatever styles can be copied across processes
    649      * into the parcel.
    650      */
    651     public static void writeToParcel(CharSequence cs, Parcel p, int parcelableFlags) {
    652         if (cs instanceof Spanned) {
    653             p.writeInt(0);
    654             p.writeString(cs.toString());
    655 
    656             Spanned sp = (Spanned) cs;
    657             Object[] os = sp.getSpans(0, cs.length(), Object.class);
    658 
    659             // note to people adding to this: check more specific types
    660             // before more generic types.  also notice that it uses
    661             // "if" instead of "else if" where there are interfaces
    662             // so one object can be several.
    663 
    664             for (int i = 0; i < os.length; i++) {
    665                 Object o = os[i];
    666                 Object prop = os[i];
    667 
    668                 if (prop instanceof CharacterStyle) {
    669                     prop = ((CharacterStyle) prop).getUnderlying();
    670                 }
    671 
    672                 if (prop instanceof ParcelableSpan) {
    673                     final ParcelableSpan ps = (ParcelableSpan) prop;
    674                     final int spanTypeId = ps.getSpanTypeIdInternal();
    675                     if (spanTypeId < FIRST_SPAN || spanTypeId > LAST_SPAN) {
    676                         Log.e(TAG, "External class \"" + ps.getClass().getSimpleName()
    677                                 + "\" is attempting to use the frameworks-only ParcelableSpan"
    678                                 + " interface");
    679                     } else {
    680                         p.writeInt(spanTypeId);
    681                         ps.writeToParcelInternal(p, parcelableFlags);
    682                         writeWhere(p, sp, o);
    683                     }
    684                 }
    685             }
    686 
    687             p.writeInt(0);
    688         } else {
    689             p.writeInt(1);
    690             if (cs != null) {
    691                 p.writeString(cs.toString());
    692             } else {
    693                 p.writeString(null);
    694             }
    695         }
    696     }
    697 
    698     private static void writeWhere(Parcel p, Spanned sp, Object o) {
    699         p.writeInt(sp.getSpanStart(o));
    700         p.writeInt(sp.getSpanEnd(o));
    701         p.writeInt(sp.getSpanFlags(o));
    702     }
    703 
    704     public static final Parcelable.Creator<CharSequence> CHAR_SEQUENCE_CREATOR
    705             = new Parcelable.Creator<CharSequence>() {
    706         /**
    707          * Read and return a new CharSequence, possibly with styles,
    708          * from the parcel.
    709          */
    710         public CharSequence createFromParcel(Parcel p) {
    711             int kind = p.readInt();
    712 
    713             String string = p.readString();
    714             if (string == null) {
    715                 return null;
    716             }
    717 
    718             if (kind == 1) {
    719                 return string;
    720             }
    721 
    722             SpannableString sp = new SpannableString(string);
    723 
    724             while (true) {
    725                 kind = p.readInt();
    726 
    727                 if (kind == 0)
    728                     break;
    729 
    730                 switch (kind) {
    731                 case ALIGNMENT_SPAN:
    732                     readSpan(p, sp, new AlignmentSpan.Standard(p));
    733                     break;
    734 
    735                 case FOREGROUND_COLOR_SPAN:
    736                     readSpan(p, sp, new ForegroundColorSpan(p));
    737                     break;
    738 
    739                 case RELATIVE_SIZE_SPAN:
    740                     readSpan(p, sp, new RelativeSizeSpan(p));
    741                     break;
    742 
    743                 case SCALE_X_SPAN:
    744                     readSpan(p, sp, new ScaleXSpan(p));
    745                     break;
    746 
    747                 case STRIKETHROUGH_SPAN:
    748                     readSpan(p, sp, new StrikethroughSpan(p));
    749                     break;
    750 
    751                 case UNDERLINE_SPAN:
    752                     readSpan(p, sp, new UnderlineSpan(p));
    753                     break;
    754 
    755                 case STYLE_SPAN:
    756                     readSpan(p, sp, new StyleSpan(p));
    757                     break;
    758 
    759                 case BULLET_SPAN:
    760                     readSpan(p, sp, new BulletSpan(p));
    761                     break;
    762 
    763                 case QUOTE_SPAN:
    764                     readSpan(p, sp, new QuoteSpan(p));
    765                     break;
    766 
    767                 case LEADING_MARGIN_SPAN:
    768                     readSpan(p, sp, new LeadingMarginSpan.Standard(p));
    769                 break;
    770 
    771                 case URL_SPAN:
    772                     readSpan(p, sp, new URLSpan(p));
    773                     break;
    774 
    775                 case BACKGROUND_COLOR_SPAN:
    776                     readSpan(p, sp, new BackgroundColorSpan(p));
    777                     break;
    778 
    779                 case TYPEFACE_SPAN:
    780                     readSpan(p, sp, new TypefaceSpan(p));
    781                     break;
    782 
    783                 case SUPERSCRIPT_SPAN:
    784                     readSpan(p, sp, new SuperscriptSpan(p));
    785                     break;
    786 
    787                 case SUBSCRIPT_SPAN:
    788                     readSpan(p, sp, new SubscriptSpan(p));
    789                     break;
    790 
    791                 case ABSOLUTE_SIZE_SPAN:
    792                     readSpan(p, sp, new AbsoluteSizeSpan(p));
    793                     break;
    794 
    795                 case TEXT_APPEARANCE_SPAN:
    796                     readSpan(p, sp, new TextAppearanceSpan(p));
    797                     break;
    798 
    799                 case ANNOTATION:
    800                     readSpan(p, sp, new Annotation(p));
    801                     break;
    802 
    803                 case SUGGESTION_SPAN:
    804                     readSpan(p, sp, new SuggestionSpan(p));
    805                     break;
    806 
    807                 case SPELL_CHECK_SPAN:
    808                     readSpan(p, sp, new SpellCheckSpan(p));
    809                     break;
    810 
    811                 case SUGGESTION_RANGE_SPAN:
    812                     readSpan(p, sp, new SuggestionRangeSpan(p));
    813                     break;
    814 
    815                 case EASY_EDIT_SPAN:
    816                     readSpan(p, sp, new EasyEditSpan(p));
    817                     break;
    818 
    819                 case LOCALE_SPAN:
    820                     readSpan(p, sp, new LocaleSpan(p));
    821                     break;
    822 
    823                 case TTS_SPAN:
    824                     readSpan(p, sp, new TtsSpan(p));
    825                     break;
    826 
    827                 case ACCESSIBILITY_CLICKABLE_SPAN:
    828                     readSpan(p, sp, new AccessibilityClickableSpan(p));
    829                     break;
    830 
    831                 case ACCESSIBILITY_URL_SPAN:
    832                     readSpan(p, sp, new AccessibilityURLSpan(p));
    833                     break;
    834 
    835                 default:
    836                     throw new RuntimeException("bogus span encoding " + kind);
    837                 }
    838             }
    839 
    840             return sp;
    841         }
    842 
    843         public CharSequence[] newArray(int size)
    844         {
    845             return new CharSequence[size];
    846         }
    847     };
    848 
    849     /**
    850      * Debugging tool to print the spans in a CharSequence.  The output will
    851      * be printed one span per line.  If the CharSequence is not a Spanned,
    852      * then the entire string will be printed on a single line.
    853      */
    854     public static void dumpSpans(CharSequence cs, Printer printer, String prefix) {
    855         if (cs instanceof Spanned) {
    856             Spanned sp = (Spanned) cs;
    857             Object[] os = sp.getSpans(0, cs.length(), Object.class);
    858 
    859             for (int i = 0; i < os.length; i++) {
    860                 Object o = os[i];
    861                 printer.println(prefix + cs.subSequence(sp.getSpanStart(o),
    862                         sp.getSpanEnd(o)) + ": "
    863                         + Integer.toHexString(System.identityHashCode(o))
    864                         + " " + o.getClass().getCanonicalName()
    865                          + " (" + sp.getSpanStart(o) + "-" + sp.getSpanEnd(o)
    866                          + ") fl=#" + sp.getSpanFlags(o));
    867             }
    868         } else {
    869             printer.println(prefix + cs + ": (no spans)");
    870         }
    871     }
    872 
    873     /**
    874      * Return a new CharSequence in which each of the source strings is
    875      * replaced by the corresponding element of the destinations.
    876      */
    877     public static CharSequence replace(CharSequence template,
    878                                        String[] sources,
    879                                        CharSequence[] destinations) {
    880         SpannableStringBuilder tb = new SpannableStringBuilder(template);
    881 
    882         for (int i = 0; i < sources.length; i++) {
    883             int where = indexOf(tb, sources[i]);
    884 
    885             if (where >= 0)
    886                 tb.setSpan(sources[i], where, where + sources[i].length(),
    887                            Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    888         }
    889 
    890         for (int i = 0; i < sources.length; i++) {
    891             int start = tb.getSpanStart(sources[i]);
    892             int end = tb.getSpanEnd(sources[i]);
    893 
    894             if (start >= 0) {
    895                 tb.replace(start, end, destinations[i]);
    896             }
    897         }
    898 
    899         return tb;
    900     }
    901 
    902     /**
    903      * Replace instances of "^1", "^2", etc. in the
    904      * <code>template</code> CharSequence with the corresponding
    905      * <code>values</code>.  "^^" is used to produce a single caret in
    906      * the output.  Only up to 9 replacement values are supported,
    907      * "^10" will be produce the first replacement value followed by a
    908      * '0'.
    909      *
    910      * @param template the input text containing "^1"-style
    911      * placeholder values.  This object is not modified; a copy is
    912      * returned.
    913      *
    914      * @param values CharSequences substituted into the template.  The
    915      * first is substituted for "^1", the second for "^2", and so on.
    916      *
    917      * @return the new CharSequence produced by doing the replacement
    918      *
    919      * @throws IllegalArgumentException if the template requests a
    920      * value that was not provided, or if more than 9 values are
    921      * provided.
    922      */
    923     public static CharSequence expandTemplate(CharSequence template,
    924                                               CharSequence... values) {
    925         if (values.length > 9) {
    926             throw new IllegalArgumentException("max of 9 values are supported");
    927         }
    928 
    929         SpannableStringBuilder ssb = new SpannableStringBuilder(template);
    930 
    931         try {
    932             int i = 0;
    933             while (i < ssb.length()) {
    934                 if (ssb.charAt(i) == '^') {
    935                     char next = ssb.charAt(i+1);
    936                     if (next == '^') {
    937                         ssb.delete(i+1, i+2);
    938                         ++i;
    939                         continue;
    940                     } else if (Character.isDigit(next)) {
    941                         int which = Character.getNumericValue(next) - 1;
    942                         if (which < 0) {
    943                             throw new IllegalArgumentException(
    944                                 "template requests value ^" + (which+1));
    945                         }
    946                         if (which >= values.length) {
    947                             throw new IllegalArgumentException(
    948                                 "template requests value ^" + (which+1) +
    949                                 "; only " + values.length + " provided");
    950                         }
    951                         ssb.replace(i, i+2, values[which]);
    952                         i += values[which].length();
    953                         continue;
    954                     }
    955                 }
    956                 ++i;
    957             }
    958         } catch (IndexOutOfBoundsException ignore) {
    959             // happens when ^ is the last character in the string.
    960         }
    961         return ssb;
    962     }
    963 
    964     public static int getOffsetBefore(CharSequence text, int offset) {
    965         if (offset == 0)
    966             return 0;
    967         if (offset == 1)
    968             return 0;
    969 
    970         char c = text.charAt(offset - 1);
    971 
    972         if (c >= '\uDC00' && c <= '\uDFFF') {
    973             char c1 = text.charAt(offset - 2);
    974 
    975             if (c1 >= '\uD800' && c1 <= '\uDBFF')
    976                 offset -= 2;
    977             else
    978                 offset -= 1;
    979         } else {
    980             offset -= 1;
    981         }
    982 
    983         if (text instanceof Spanned) {
    984             ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
    985                                                        ReplacementSpan.class);
    986 
    987             for (int i = 0; i < spans.length; i++) {
    988                 int start = ((Spanned) text).getSpanStart(spans[i]);
    989                 int end = ((Spanned) text).getSpanEnd(spans[i]);
    990 
    991                 if (start < offset && end > offset)
    992                     offset = start;
    993             }
    994         }
    995 
    996         return offset;
    997     }
    998 
    999     public static int getOffsetAfter(CharSequence text, int offset) {
   1000         int len = text.length();
   1001 
   1002         if (offset == len)
   1003             return len;
   1004         if (offset == len - 1)
   1005             return len;
   1006 
   1007         char c = text.charAt(offset);
   1008 
   1009         if (c >= '\uD800' && c <= '\uDBFF') {
   1010             char c1 = text.charAt(offset + 1);
   1011 
   1012             if (c1 >= '\uDC00' && c1 <= '\uDFFF')
   1013                 offset += 2;
   1014             else
   1015                 offset += 1;
   1016         } else {
   1017             offset += 1;
   1018         }
   1019 
   1020         if (text instanceof Spanned) {
   1021             ReplacementSpan[] spans = ((Spanned) text).getSpans(offset, offset,
   1022                                                        ReplacementSpan.class);
   1023 
   1024             for (int i = 0; i < spans.length; i++) {
   1025                 int start = ((Spanned) text).getSpanStart(spans[i]);
   1026                 int end = ((Spanned) text).getSpanEnd(spans[i]);
   1027 
   1028                 if (start < offset && end > offset)
   1029                     offset = end;
   1030             }
   1031         }
   1032 
   1033         return offset;
   1034     }
   1035 
   1036     private static void readSpan(Parcel p, Spannable sp, Object o) {
   1037         sp.setSpan(o, p.readInt(), p.readInt(), p.readInt());
   1038     }
   1039 
   1040     /**
   1041      * Copies the spans from the region <code>start...end</code> in
   1042      * <code>source</code> to the region
   1043      * <code>destoff...destoff+end-start</code> in <code>dest</code>.
   1044      * Spans in <code>source</code> that begin before <code>start</code>
   1045      * or end after <code>end</code> but overlap this range are trimmed
   1046      * as if they began at <code>start</code> or ended at <code>end</code>.
   1047      *
   1048      * @throws IndexOutOfBoundsException if any of the copied spans
   1049      * are out of range in <code>dest</code>.
   1050      */
   1051     public static void copySpansFrom(Spanned source, int start, int end,
   1052                                      Class kind,
   1053                                      Spannable dest, int destoff) {
   1054         if (kind == null) {
   1055             kind = Object.class;
   1056         }
   1057 
   1058         Object[] spans = source.getSpans(start, end, kind);
   1059 
   1060         for (int i = 0; i < spans.length; i++) {
   1061             int st = source.getSpanStart(spans[i]);
   1062             int en = source.getSpanEnd(spans[i]);
   1063             int fl = source.getSpanFlags(spans[i]);
   1064 
   1065             if (st < start)
   1066                 st = start;
   1067             if (en > end)
   1068                 en = end;
   1069 
   1070             dest.setSpan(spans[i], st - start + destoff, en - start + destoff,
   1071                          fl);
   1072         }
   1073     }
   1074 
   1075     public enum TruncateAt {
   1076         START,
   1077         MIDDLE,
   1078         END,
   1079         MARQUEE,
   1080         /**
   1081          * @hide
   1082          */
   1083         END_SMALL
   1084     }
   1085 
   1086     public interface EllipsizeCallback {
   1087         /**
   1088          * This method is called to report that the specified region of
   1089          * text was ellipsized away by a call to {@link #ellipsize}.
   1090          */
   1091         public void ellipsized(int start, int end);
   1092     }
   1093 
   1094     /**
   1095      * Returns the original text if it fits in the specified width
   1096      * given the properties of the specified Paint,
   1097      * or, if it does not fit, a truncated
   1098      * copy with ellipsis character added at the specified edge or center.
   1099      */
   1100     public static CharSequence ellipsize(CharSequence text,
   1101                                          TextPaint p,
   1102                                          float avail, TruncateAt where) {
   1103         return ellipsize(text, p, avail, where, false, null);
   1104     }
   1105 
   1106     /**
   1107      * Returns the original text if it fits in the specified width
   1108      * given the properties of the specified Paint,
   1109      * or, if it does not fit, a copy with ellipsis character added
   1110      * at the specified edge or center.
   1111      * If <code>preserveLength</code> is specified, the returned copy
   1112      * will be padded with zero-width spaces to preserve the original
   1113      * length and offsets instead of truncating.
   1114      * If <code>callback</code> is non-null, it will be called to
   1115      * report the start and end of the ellipsized range.  TextDirection
   1116      * is determined by the first strong directional character.
   1117      */
   1118     public static CharSequence ellipsize(CharSequence text,
   1119                                          TextPaint paint,
   1120                                          float avail, TruncateAt where,
   1121                                          boolean preserveLength,
   1122                                          EllipsizeCallback callback) {
   1123         return ellipsize(text, paint, avail, where, preserveLength, callback,
   1124                 TextDirectionHeuristics.FIRSTSTRONG_LTR,
   1125                 (where == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS_STRING : ELLIPSIS_STRING);
   1126     }
   1127 
   1128     /**
   1129      * Returns the original text if it fits in the specified width
   1130      * given the properties of the specified Paint,
   1131      * or, if it does not fit, a copy with ellipsis character added
   1132      * at the specified edge or center.
   1133      * If <code>preserveLength</code> is specified, the returned copy
   1134      * will be padded with zero-width spaces to preserve the original
   1135      * length and offsets instead of truncating.
   1136      * If <code>callback</code> is non-null, it will be called to
   1137      * report the start and end of the ellipsized range.
   1138      *
   1139      * @hide
   1140      */
   1141     public static CharSequence ellipsize(CharSequence text,
   1142             TextPaint paint,
   1143             float avail, TruncateAt where,
   1144             boolean preserveLength,
   1145             EllipsizeCallback callback,
   1146             TextDirectionHeuristic textDir, String ellipsis) {
   1147 
   1148         int len = text.length();
   1149 
   1150         MeasuredText mt = MeasuredText.obtain();
   1151         try {
   1152             float width = setPara(mt, paint, text, 0, text.length(), textDir);
   1153 
   1154             if (width <= avail) {
   1155                 if (callback != null) {
   1156                     callback.ellipsized(0, 0);
   1157                 }
   1158 
   1159                 return text;
   1160             }
   1161 
   1162             // XXX assumes ellipsis string does not require shaping and
   1163             // is unaffected by style
   1164             float ellipsiswid = paint.measureText(ellipsis);
   1165             avail -= ellipsiswid;
   1166 
   1167             int left = 0;
   1168             int right = len;
   1169             if (avail < 0) {
   1170                 // it all goes
   1171             } else if (where == TruncateAt.START) {
   1172                 right = len - mt.breakText(len, false, avail);
   1173             } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
   1174                 left = mt.breakText(len, true, avail);
   1175             } else {
   1176                 right = len - mt.breakText(len, false, avail / 2);
   1177                 avail -= mt.measure(right, len);
   1178                 left = mt.breakText(right, true, avail);
   1179             }
   1180 
   1181             if (callback != null) {
   1182                 callback.ellipsized(left, right);
   1183             }
   1184 
   1185             char[] buf = mt.mChars;
   1186             Spanned sp = text instanceof Spanned ? (Spanned) text : null;
   1187 
   1188             int remaining = len - (right - left);
   1189             if (preserveLength) {
   1190                 if (remaining > 0) { // else eliminate the ellipsis too
   1191                     buf[left++] = ellipsis.charAt(0);
   1192                 }
   1193                 for (int i = left; i < right; i++) {
   1194                     buf[i] = ZWNBS_CHAR;
   1195                 }
   1196                 String s = new String(buf, 0, len);
   1197                 if (sp == null) {
   1198                     return s;
   1199                 }
   1200                 SpannableString ss = new SpannableString(s);
   1201                 copySpansFrom(sp, 0, len, Object.class, ss, 0);
   1202                 return ss;
   1203             }
   1204 
   1205             if (remaining == 0) {
   1206                 return "";
   1207             }
   1208 
   1209             if (sp == null) {
   1210                 StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
   1211                 sb.append(buf, 0, left);
   1212                 sb.append(ellipsis);
   1213                 sb.append(buf, right, len - right);
   1214                 return sb.toString();
   1215             }
   1216 
   1217             SpannableStringBuilder ssb = new SpannableStringBuilder();
   1218             ssb.append(text, 0, left);
   1219             ssb.append(ellipsis);
   1220             ssb.append(text, right, len);
   1221             return ssb;
   1222         } finally {
   1223             MeasuredText.recycle(mt);
   1224         }
   1225     }
   1226 
   1227     /**
   1228      * Formats a list of CharSequences by repeatedly inserting the separator between them,
   1229      * but stopping when the resulting sequence is too wide for the specified width.
   1230      *
   1231      * This method actually tries to fit the maximum number of elements. So if {@code "A, 11 more"
   1232      * fits}, {@code "A, B, 10 more"} doesn't fit, but {@code "A, B, C, 9 more"} fits again (due to
   1233      * the glyphs for the digits being very wide, for example), it returns
   1234      * {@code "A, B, C, 9 more"}. Because of this, this method may be inefficient for very long
   1235      * lists.
   1236      *
   1237      * Note that the elements of the returned value, as well as the string for {@code moreId}, will
   1238      * be bidi-wrapped using {@link BidiFormatter#unicodeWrap} based on the locale of the input
   1239      * Context. If the input {@code Context} is null, the default BidiFormatter from
   1240      * {@link BidiFormatter#getInstance()} will be used.
   1241      *
   1242      * @param context the {@code Context} to get the {@code moreId} resource from. If {@code null},
   1243      *     an ellipsis (U+2026) would be used for {@code moreId}.
   1244      * @param elements the list to format
   1245      * @param separator a separator, such as {@code ", "}
   1246      * @param paint the Paint with which to measure the text
   1247      * @param avail the horizontal width available for the text (in pixels)
   1248      * @param moreId the resource ID for the pluralized string to insert at the end of sequence when
   1249      *     some of the elements don't fit.
   1250      *
   1251      * @return the formatted CharSequence. If even the shortest sequence (e.g. {@code "A, 11 more"})
   1252      *     doesn't fit, it will return an empty string.
   1253      */
   1254 
   1255     public static CharSequence listEllipsize(@Nullable Context context,
   1256             @Nullable List<CharSequence> elements, @NonNull String separator,
   1257             @NonNull TextPaint paint, @FloatRange(from=0.0,fromInclusive=false) float avail,
   1258             @PluralsRes int moreId) {
   1259         if (elements == null) {
   1260             return "";
   1261         }
   1262         final int totalLen = elements.size();
   1263         if (totalLen == 0) {
   1264             return "";
   1265         }
   1266 
   1267         final Resources res;
   1268         final BidiFormatter bidiFormatter;
   1269         if (context == null) {
   1270             res = null;
   1271             bidiFormatter = BidiFormatter.getInstance();
   1272         } else {
   1273             res = context.getResources();
   1274             bidiFormatter = BidiFormatter.getInstance(res.getConfiguration().getLocales().get(0));
   1275         }
   1276 
   1277         final SpannableStringBuilder output = new SpannableStringBuilder();
   1278         final int[] endIndexes = new int[totalLen];
   1279         for (int i = 0; i < totalLen; i++) {
   1280             output.append(bidiFormatter.unicodeWrap(elements.get(i)));
   1281             if (i != totalLen - 1) {  // Insert a separator, except at the very end.
   1282                 output.append(separator);
   1283             }
   1284             endIndexes[i] = output.length();
   1285         }
   1286 
   1287         for (int i = totalLen - 1; i >= 0; i--) {
   1288             // Delete the tail of the string, cutting back to one less element.
   1289             output.delete(endIndexes[i], output.length());
   1290 
   1291             final int remainingElements = totalLen - i - 1;
   1292             if (remainingElements > 0) {
   1293                 CharSequence morePiece = (res == null) ?
   1294                         ELLIPSIS_STRING :
   1295                         res.getQuantityString(moreId, remainingElements, remainingElements);
   1296                 morePiece = bidiFormatter.unicodeWrap(morePiece);
   1297                 output.append(morePiece);
   1298             }
   1299 
   1300             final float width = paint.measureText(output, 0, output.length());
   1301             if (width <= avail) {  // The string fits.
   1302                 return output;
   1303             }
   1304         }
   1305         return "";  // Nothing fits.
   1306     }
   1307 
   1308     /**
   1309      * Converts a CharSequence of the comma-separated form "Andy, Bob,
   1310      * Charles, David" that is too wide to fit into the specified width
   1311      * into one like "Andy, Bob, 2 more".
   1312      *
   1313      * @param text the text to truncate
   1314      * @param p the Paint with which to measure the text
   1315      * @param avail the horizontal width available for the text (in pixels)
   1316      * @param oneMore the string for "1 more" in the current locale
   1317      * @param more the string for "%d more" in the current locale
   1318      *
   1319      * @deprecated Do not use. This is not internationalized, and has known issues
   1320      * with right-to-left text, languages that have more than one plural form, languages
   1321      * that use a different character as a comma-like separator, etc.
   1322      * Use {@link #listEllipsize} instead.
   1323      */
   1324     @Deprecated
   1325     public static CharSequence commaEllipsize(CharSequence text,
   1326                                               TextPaint p, float avail,
   1327                                               String oneMore,
   1328                                               String more) {
   1329         return commaEllipsize(text, p, avail, oneMore, more,
   1330                 TextDirectionHeuristics.FIRSTSTRONG_LTR);
   1331     }
   1332 
   1333     /**
   1334      * @hide
   1335      */
   1336     @Deprecated
   1337     public static CharSequence commaEllipsize(CharSequence text, TextPaint p,
   1338          float avail, String oneMore, String more, TextDirectionHeuristic textDir) {
   1339 
   1340         MeasuredText mt = MeasuredText.obtain();
   1341         try {
   1342             int len = text.length();
   1343             float width = setPara(mt, p, text, 0, len, textDir);
   1344             if (width <= avail) {
   1345                 return text;
   1346             }
   1347 
   1348             char[] buf = mt.mChars;
   1349 
   1350             int commaCount = 0;
   1351             for (int i = 0; i < len; i++) {
   1352                 if (buf[i] == ',') {
   1353                     commaCount++;
   1354                 }
   1355             }
   1356 
   1357             int remaining = commaCount + 1;
   1358 
   1359             int ok = 0;
   1360             String okFormat = "";
   1361 
   1362             int w = 0;
   1363             int count = 0;
   1364             float[] widths = mt.mWidths;
   1365 
   1366             MeasuredText tempMt = MeasuredText.obtain();
   1367             for (int i = 0; i < len; i++) {
   1368                 w += widths[i];
   1369 
   1370                 if (buf[i] == ',') {
   1371                     count++;
   1372 
   1373                     String format;
   1374                     // XXX should not insert spaces, should be part of string
   1375                     // XXX should use plural rules and not assume English plurals
   1376                     if (--remaining == 1) {
   1377                         format = " " + oneMore;
   1378                     } else {
   1379                         format = " " + String.format(more, remaining);
   1380                     }
   1381 
   1382                     // XXX this is probably ok, but need to look at it more
   1383                     tempMt.setPara(format, 0, format.length(), textDir, null);
   1384                     float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
   1385 
   1386                     if (w + moreWid <= avail) {
   1387                         ok = i + 1;
   1388                         okFormat = format;
   1389                     }
   1390                 }
   1391             }
   1392             MeasuredText.recycle(tempMt);
   1393 
   1394             SpannableStringBuilder out = new SpannableStringBuilder(okFormat);
   1395             out.insert(0, text, 0, ok);
   1396             return out;
   1397         } finally {
   1398             MeasuredText.recycle(mt);
   1399         }
   1400     }
   1401 
   1402     private static float setPara(MeasuredText mt, TextPaint paint,
   1403             CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
   1404 
   1405         mt.setPara(text, start, end, textDir, null);
   1406 
   1407         float width;
   1408         Spanned sp = text instanceof Spanned ? (Spanned) text : null;
   1409         int len = end - start;
   1410         if (sp == null) {
   1411             width = mt.addStyleRun(paint, len, null);
   1412         } else {
   1413             width = 0;
   1414             int spanEnd;
   1415             for (int spanStart = 0; spanStart < len; spanStart = spanEnd) {
   1416                 spanEnd = sp.nextSpanTransition(spanStart, len,
   1417                         MetricAffectingSpan.class);
   1418                 MetricAffectingSpan[] spans = sp.getSpans(
   1419                         spanStart, spanEnd, MetricAffectingSpan.class);
   1420                 spans = TextUtils.removeEmptySpans(spans, sp, MetricAffectingSpan.class);
   1421                 width += mt.addStyleRun(paint, spans, spanEnd - spanStart, null);
   1422             }
   1423         }
   1424 
   1425         return width;
   1426     }
   1427 
   1428     // Returns true if the character's presence could affect RTL layout.
   1429     //
   1430     // In order to be fast, the code is intentionally rough and quite conservative in its
   1431     // considering inclusion of any non-BMP or surrogate characters or anything in the bidi
   1432     // blocks or any bidi formatting characters with a potential to affect RTL layout.
   1433     /* package */
   1434     static boolean couldAffectRtl(char c) {
   1435         return (0x0590 <= c && c <= 0x08FF) ||  // RTL scripts
   1436                 c == 0x200E ||  // Bidi format character
   1437                 c == 0x200F ||  // Bidi format character
   1438                 (0x202A <= c && c <= 0x202E) ||  // Bidi format characters
   1439                 (0x2066 <= c && c <= 0x2069) ||  // Bidi format characters
   1440                 (0xD800 <= c && c <= 0xDFFF) ||  // Surrogate pairs
   1441                 (0xFB1D <= c && c <= 0xFDFF) ||  // Hebrew and Arabic presentation forms
   1442                 (0xFE70 <= c && c <= 0xFEFE);  // Arabic presentation forms
   1443     }
   1444 
   1445     // Returns true if there is no character present that may potentially affect RTL layout.
   1446     // Since this calls couldAffectRtl() above, it's also quite conservative, in the way that
   1447     // it may return 'false' (needs bidi) although careful consideration may tell us it should
   1448     // return 'true' (does not need bidi).
   1449     /* package */
   1450     static boolean doesNotNeedBidi(char[] text, int start, int len) {
   1451         final int end = start + len;
   1452         for (int i = start; i < end; i++) {
   1453             if (couldAffectRtl(text[i])) {
   1454                 return false;
   1455             }
   1456         }
   1457         return true;
   1458     }
   1459 
   1460     /* package */ static char[] obtain(int len) {
   1461         char[] buf;
   1462 
   1463         synchronized (sLock) {
   1464             buf = sTemp;
   1465             sTemp = null;
   1466         }
   1467 
   1468         if (buf == null || buf.length < len)
   1469             buf = ArrayUtils.newUnpaddedCharArray(len);
   1470 
   1471         return buf;
   1472     }
   1473 
   1474     /* package */ static void recycle(char[] temp) {
   1475         if (temp.length > 1000)
   1476             return;
   1477 
   1478         synchronized (sLock) {
   1479             sTemp = temp;
   1480         }
   1481     }
   1482 
   1483     /**
   1484      * Html-encode the string.
   1485      * @param s the string to be encoded
   1486      * @return the encoded string
   1487      */
   1488     public static String htmlEncode(String s) {
   1489         StringBuilder sb = new StringBuilder();
   1490         char c;
   1491         for (int i = 0; i < s.length(); i++) {
   1492             c = s.charAt(i);
   1493             switch (c) {
   1494             case '<':
   1495                 sb.append("&lt;"); //$NON-NLS-1$
   1496                 break;
   1497             case '>':
   1498                 sb.append("&gt;"); //$NON-NLS-1$
   1499                 break;
   1500             case '&':
   1501                 sb.append("&amp;"); //$NON-NLS-1$
   1502                 break;
   1503             case '\'':
   1504                 //http://www.w3.org/TR/xhtml1
   1505                 // The named character reference &apos; (the apostrophe, U+0027) was introduced in
   1506                 // XML 1.0 but does not appear in HTML. Authors should therefore use &#39; instead
   1507                 // of &apos; to work as expected in HTML 4 user agents.
   1508                 sb.append("&#39;"); //$NON-NLS-1$
   1509                 break;
   1510             case '"':
   1511                 sb.append("&quot;"); //$NON-NLS-1$
   1512                 break;
   1513             default:
   1514                 sb.append(c);
   1515             }
   1516         }
   1517         return sb.toString();
   1518     }
   1519 
   1520     /**
   1521      * Returns a CharSequence concatenating the specified CharSequences,
   1522      * retaining their spans if any.
   1523      *
   1524      * If there are no parameters, an empty string will be returned.
   1525      *
   1526      * If the number of parameters is exactly one, that parameter is returned as output, even if it
   1527      * is null.
   1528      *
   1529      * If the number of parameters is at least two, any null CharSequence among the parameters is
   1530      * treated as if it was the string <code>"null"</code>.
   1531      *
   1532      * If there are paragraph spans in the source CharSequences that satisfy paragraph boundary
   1533      * requirements in the sources but would no longer satisfy them in the concatenated
   1534      * CharSequence, they may get extended in the resulting CharSequence or not retained.
   1535      */
   1536     public static CharSequence concat(CharSequence... text) {
   1537         if (text.length == 0) {
   1538             return "";
   1539         }
   1540 
   1541         if (text.length == 1) {
   1542             return text[0];
   1543         }
   1544 
   1545         boolean spanned = false;
   1546         for (CharSequence piece : text) {
   1547             if (piece instanceof Spanned) {
   1548                 spanned = true;
   1549                 break;
   1550             }
   1551         }
   1552 
   1553         if (spanned) {
   1554             final SpannableStringBuilder ssb = new SpannableStringBuilder();
   1555             for (CharSequence piece : text) {
   1556                 // If a piece is null, we append the string "null" for compatibility with the
   1557                 // behavior of StringBuilder and the behavior of the concat() method in earlier
   1558                 // versions of Android.
   1559                 ssb.append(piece == null ? "null" : piece);
   1560             }
   1561             return new SpannedString(ssb);
   1562         } else {
   1563             final StringBuilder sb = new StringBuilder();
   1564             for (CharSequence piece : text) {
   1565                 sb.append(piece);
   1566             }
   1567             return sb.toString();
   1568         }
   1569     }
   1570 
   1571     /**
   1572      * Returns whether the given CharSequence contains any printable characters.
   1573      */
   1574     public static boolean isGraphic(CharSequence str) {
   1575         final int len = str.length();
   1576         for (int cp, i=0; i<len; i+=Character.charCount(cp)) {
   1577             cp = Character.codePointAt(str, i);
   1578             int gc = Character.getType(cp);
   1579             if (gc != Character.CONTROL
   1580                     && gc != Character.FORMAT
   1581                     && gc != Character.SURROGATE
   1582                     && gc != Character.UNASSIGNED
   1583                     && gc != Character.LINE_SEPARATOR
   1584                     && gc != Character.PARAGRAPH_SEPARATOR
   1585                     && gc != Character.SPACE_SEPARATOR) {
   1586                 return true;
   1587             }
   1588         }
   1589         return false;
   1590     }
   1591 
   1592     /**
   1593      * Returns whether this character is a printable character.
   1594      *
   1595      * This does not support non-BMP characters and should not be used.
   1596      *
   1597      * @deprecated Use {@link #isGraphic(CharSequence)} instead.
   1598      */
   1599     @Deprecated
   1600     public static boolean isGraphic(char c) {
   1601         int gc = Character.getType(c);
   1602         return     gc != Character.CONTROL
   1603                 && gc != Character.FORMAT
   1604                 && gc != Character.SURROGATE
   1605                 && gc != Character.UNASSIGNED
   1606                 && gc != Character.LINE_SEPARATOR
   1607                 && gc != Character.PARAGRAPH_SEPARATOR
   1608                 && gc != Character.SPACE_SEPARATOR;
   1609     }
   1610 
   1611     /**
   1612      * Returns whether the given CharSequence contains only digits.
   1613      */
   1614     public static boolean isDigitsOnly(CharSequence str) {
   1615         final int len = str.length();
   1616         for (int cp, i = 0; i < len; i += Character.charCount(cp)) {
   1617             cp = Character.codePointAt(str, i);
   1618             if (!Character.isDigit(cp)) {
   1619                 return false;
   1620             }
   1621         }
   1622         return true;
   1623     }
   1624 
   1625     /**
   1626      * @hide
   1627      */
   1628     public static boolean isPrintableAscii(final char c) {
   1629         final int asciiFirst = 0x20;
   1630         final int asciiLast = 0x7E;  // included
   1631         return (asciiFirst <= c && c <= asciiLast) || c == '\r' || c == '\n';
   1632     }
   1633 
   1634     /**
   1635      * @hide
   1636      */
   1637     public static boolean isPrintableAsciiOnly(final CharSequence str) {
   1638         final int len = str.length();
   1639         for (int i = 0; i < len; i++) {
   1640             if (!isPrintableAscii(str.charAt(i))) {
   1641                 return false;
   1642             }
   1643         }
   1644         return true;
   1645     }
   1646 
   1647     /**
   1648      * Capitalization mode for {@link #getCapsMode}: capitalize all
   1649      * characters.  This value is explicitly defined to be the same as
   1650      * {@link InputType#TYPE_TEXT_FLAG_CAP_CHARACTERS}.
   1651      */
   1652     public static final int CAP_MODE_CHARACTERS
   1653             = InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS;
   1654 
   1655     /**
   1656      * Capitalization mode for {@link #getCapsMode}: capitalize the first
   1657      * character of all words.  This value is explicitly defined to be the same as
   1658      * {@link InputType#TYPE_TEXT_FLAG_CAP_WORDS}.
   1659      */
   1660     public static final int CAP_MODE_WORDS
   1661             = InputType.TYPE_TEXT_FLAG_CAP_WORDS;
   1662 
   1663     /**
   1664      * Capitalization mode for {@link #getCapsMode}: capitalize the first
   1665      * character of each sentence.  This value is explicitly defined to be the same as
   1666      * {@link InputType#TYPE_TEXT_FLAG_CAP_SENTENCES}.
   1667      */
   1668     public static final int CAP_MODE_SENTENCES
   1669             = InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
   1670 
   1671     /**
   1672      * Determine what caps mode should be in effect at the current offset in
   1673      * the text.  Only the mode bits set in <var>reqModes</var> will be
   1674      * checked.  Note that the caps mode flags here are explicitly defined
   1675      * to match those in {@link InputType}.
   1676      *
   1677      * @param cs The text that should be checked for caps modes.
   1678      * @param off Location in the text at which to check.
   1679      * @param reqModes The modes to be checked: may be any combination of
   1680      * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
   1681      * {@link #CAP_MODE_SENTENCES}.
   1682      *
   1683      * @return Returns the actual capitalization modes that can be in effect
   1684      * at the current position, which is any combination of
   1685      * {@link #CAP_MODE_CHARACTERS}, {@link #CAP_MODE_WORDS}, and
   1686      * {@link #CAP_MODE_SENTENCES}.
   1687      */
   1688     public static int getCapsMode(CharSequence cs, int off, int reqModes) {
   1689         if (off < 0) {
   1690             return 0;
   1691         }
   1692 
   1693         int i;
   1694         char c;
   1695         int mode = 0;
   1696 
   1697         if ((reqModes&CAP_MODE_CHARACTERS) != 0) {
   1698             mode |= CAP_MODE_CHARACTERS;
   1699         }
   1700         if ((reqModes&(CAP_MODE_WORDS|CAP_MODE_SENTENCES)) == 0) {
   1701             return mode;
   1702         }
   1703 
   1704         // Back over allowed opening punctuation.
   1705 
   1706         for (i = off; i > 0; i--) {
   1707             c = cs.charAt(i - 1);
   1708 
   1709             if (c != '"' && c != '\'' &&
   1710                 Character.getType(c) != Character.START_PUNCTUATION) {
   1711                 break;
   1712             }
   1713         }
   1714 
   1715         // Start of paragraph, with optional whitespace.
   1716 
   1717         int j = i;
   1718         while (j > 0 && ((c = cs.charAt(j - 1)) == ' ' || c == '\t')) {
   1719             j--;
   1720         }
   1721         if (j == 0 || cs.charAt(j - 1) == '\n') {
   1722             return mode | CAP_MODE_WORDS;
   1723         }
   1724 
   1725         // Or start of word if we are that style.
   1726 
   1727         if ((reqModes&CAP_MODE_SENTENCES) == 0) {
   1728             if (i != j) mode |= CAP_MODE_WORDS;
   1729             return mode;
   1730         }
   1731 
   1732         // There must be a space if not the start of paragraph.
   1733 
   1734         if (i == j) {
   1735             return mode;
   1736         }
   1737 
   1738         // Back over allowed closing punctuation.
   1739 
   1740         for (; j > 0; j--) {
   1741             c = cs.charAt(j - 1);
   1742 
   1743             if (c != '"' && c != '\'' &&
   1744                 Character.getType(c) != Character.END_PUNCTUATION) {
   1745                 break;
   1746             }
   1747         }
   1748 
   1749         if (j > 0) {
   1750             c = cs.charAt(j - 1);
   1751 
   1752             if (c == '.' || c == '?' || c == '!') {
   1753                 // Do not capitalize if the word ends with a period but
   1754                 // also contains a period, in which case it is an abbreviation.
   1755 
   1756                 if (c == '.') {
   1757                     for (int k = j - 2; k >= 0; k--) {
   1758                         c = cs.charAt(k);
   1759 
   1760                         if (c == '.') {
   1761                             return mode;
   1762                         }
   1763 
   1764                         if (!Character.isLetter(c)) {
   1765                             break;
   1766                         }
   1767                     }
   1768                 }
   1769 
   1770                 return mode | CAP_MODE_SENTENCES;
   1771             }
   1772         }
   1773 
   1774         return mode;
   1775     }
   1776 
   1777     /**
   1778      * Does a comma-delimited list 'delimitedString' contain a certain item?
   1779      * (without allocating memory)
   1780      *
   1781      * @hide
   1782      */
   1783     public static boolean delimitedStringContains(
   1784             String delimitedString, char delimiter, String item) {
   1785         if (isEmpty(delimitedString) || isEmpty(item)) {
   1786             return false;
   1787         }
   1788         int pos = -1;
   1789         int length = delimitedString.length();
   1790         while ((pos = delimitedString.indexOf(item, pos + 1)) != -1) {
   1791             if (pos > 0 && delimitedString.charAt(pos - 1) != delimiter) {
   1792                 continue;
   1793             }
   1794             int expectedDelimiterPos = pos + item.length();
   1795             if (expectedDelimiterPos == length) {
   1796                 // Match at end of string.
   1797                 return true;
   1798             }
   1799             if (delimitedString.charAt(expectedDelimiterPos) == delimiter) {
   1800                 return true;
   1801             }
   1802         }
   1803         return false;
   1804     }
   1805 
   1806     /**
   1807      * Removes empty spans from the <code>spans</code> array.
   1808      *
   1809      * When parsing a Spanned using {@link Spanned#nextSpanTransition(int, int, Class)}, empty spans
   1810      * will (correctly) create span transitions, and calling getSpans on a slice of text bounded by
   1811      * one of these transitions will (correctly) include the empty overlapping span.
   1812      *
   1813      * However, these empty spans should not be taken into account when layouting or rendering the
   1814      * string and this method provides a way to filter getSpans' results accordingly.
   1815      *
   1816      * @param spans A list of spans retrieved using {@link Spanned#getSpans(int, int, Class)} from
   1817      * the <code>spanned</code>
   1818      * @param spanned The Spanned from which spans were extracted
   1819      * @return A subset of spans where empty spans ({@link Spanned#getSpanStart(Object)}  ==
   1820      * {@link Spanned#getSpanEnd(Object)} have been removed. The initial order is preserved
   1821      * @hide
   1822      */
   1823     @SuppressWarnings("unchecked")
   1824     public static <T> T[] removeEmptySpans(T[] spans, Spanned spanned, Class<T> klass) {
   1825         T[] copy = null;
   1826         int count = 0;
   1827 
   1828         for (int i = 0; i < spans.length; i++) {
   1829             final T span = spans[i];
   1830             final int start = spanned.getSpanStart(span);
   1831             final int end = spanned.getSpanEnd(span);
   1832 
   1833             if (start == end) {
   1834                 if (copy == null) {
   1835                     copy = (T[]) Array.newInstance(klass, spans.length - 1);
   1836                     System.arraycopy(spans, 0, copy, 0, i);
   1837                     count = i;
   1838                 }
   1839             } else {
   1840                 if (copy != null) {
   1841                     copy[count] = span;
   1842                     count++;
   1843                 }
   1844             }
   1845         }
   1846 
   1847         if (copy != null) {
   1848             T[] result = (T[]) Array.newInstance(klass, count);
   1849             System.arraycopy(copy, 0, result, 0, count);
   1850             return result;
   1851         } else {
   1852             return spans;
   1853         }
   1854     }
   1855 
   1856     /**
   1857      * Pack 2 int values into a long, useful as a return value for a range
   1858      * @see #unpackRangeStartFromLong(long)
   1859      * @see #unpackRangeEndFromLong(long)
   1860      * @hide
   1861      */
   1862     public static long packRangeInLong(int start, int end) {
   1863         return (((long) start) << 32) | end;
   1864     }
   1865 
   1866     /**
   1867      * Get the start value from a range packed in a long by {@link #packRangeInLong(int, int)}
   1868      * @see #unpackRangeEndFromLong(long)
   1869      * @see #packRangeInLong(int, int)
   1870      * @hide
   1871      */
   1872     public static int unpackRangeStartFromLong(long range) {
   1873         return (int) (range >>> 32);
   1874     }
   1875 
   1876     /**
   1877      * Get the end value from a range packed in a long by {@link #packRangeInLong(int, int)}
   1878      * @see #unpackRangeStartFromLong(long)
   1879      * @see #packRangeInLong(int, int)
   1880      * @hide
   1881      */
   1882     public static int unpackRangeEndFromLong(long range) {
   1883         return (int) (range & 0x00000000FFFFFFFFL);
   1884     }
   1885 
   1886     /**
   1887      * Return the layout direction for a given Locale
   1888      *
   1889      * @param locale the Locale for which we want the layout direction. Can be null.
   1890      * @return the layout direction. This may be one of:
   1891      * {@link android.view.View#LAYOUT_DIRECTION_LTR} or
   1892      * {@link android.view.View#LAYOUT_DIRECTION_RTL}.
   1893      *
   1894      * Be careful: this code will need to be updated when vertical scripts will be supported
   1895      */
   1896     public static int getLayoutDirectionFromLocale(Locale locale) {
   1897         return ((locale != null && !locale.equals(Locale.ROOT)
   1898                         && ULocale.forLocale(locale).isRightToLeft())
   1899                 // If forcing into RTL layout mode, return RTL as default
   1900                 || SystemProperties.getBoolean(Settings.Global.DEVELOPMENT_FORCE_RTL, false))
   1901             ? View.LAYOUT_DIRECTION_RTL
   1902             : View.LAYOUT_DIRECTION_LTR;
   1903     }
   1904 
   1905     /**
   1906      * Return localized string representing the given number of selected items.
   1907      *
   1908      * @hide
   1909      */
   1910     public static CharSequence formatSelectedCount(int count) {
   1911         return Resources.getSystem().getQuantityString(R.plurals.selected_count, count, count);
   1912     }
   1913 
   1914     /**
   1915      * Returns whether or not the specified spanned text has a style span.
   1916      * @hide
   1917      */
   1918     public static boolean hasStyleSpan(@NonNull Spanned spanned) {
   1919         Preconditions.checkArgument(spanned != null);
   1920         final Class<?>[] styleClasses = {
   1921                 CharacterStyle.class, ParagraphStyle.class, UpdateAppearance.class};
   1922         for (Class<?> clazz : styleClasses) {
   1923             if (spanned.nextSpanTransition(-1, spanned.length(), clazz) < spanned.length()) {
   1924                 return true;
   1925             }
   1926         }
   1927         return false;
   1928     }
   1929 
   1930     /**
   1931      * If the {@code charSequence} is instance of {@link Spanned}, creates a new copy and
   1932      * {@link NoCopySpan}'s are removed from the copy. Otherwise the given {@code charSequence} is
   1933      * returned as it is.
   1934      *
   1935      * @hide
   1936      */
   1937     @Nullable
   1938     public static CharSequence trimNoCopySpans(@Nullable CharSequence charSequence) {
   1939         if (charSequence != null && charSequence instanceof Spanned) {
   1940             // SpannableStringBuilder copy constructor trims NoCopySpans.
   1941             return new SpannableStringBuilder(charSequence);
   1942         }
   1943         return charSequence;
   1944     }
   1945 
   1946     private static Object sLock = new Object();
   1947 
   1948     private static char[] sTemp = null;
   1949 
   1950     private static String[] EMPTY_STRING_ARRAY = new String[]{};
   1951 
   1952     private static final char ZWNBS_CHAR = '\uFEFF';
   1953 }
   1954