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