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