OSDN Git Service

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