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