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