blob: 3baadd4c64c5a7e5b26ec6856845bb0a1f68189f [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Roozbeh Pournader3bfce332016-06-17 15:03:56 -070019import android.annotation.FloatRange;
20import android.annotation.NonNull;
Scott Kennedy6cd132f2015-02-19 10:36:12 -080021import android.annotation.Nullable;
Roozbeh Pournader3bfce332016-06-17 15:03:56 -070022import android.annotation.PluralsRes;
23import android.content.Context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080024import android.content.res.Resources;
Roozbeh Pournader9559c202016-12-13 10:59:50 -080025import android.icu.lang.UCharacter;
Roozbeh Pournader463b4822015-08-06 16:04:45 -070026import android.icu.util.ULocale;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.os.Parcel;
28import android.os.Parcelable;
Amith Yamasanid8415f42013-08-07 20:15:10 -070029import android.os.SystemProperties;
30import android.provider.Settings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.text.style.AbsoluteSizeSpan;
Phil Weaver193520e2016-12-13 09:39:06 -080032import android.text.style.AccessibilityClickableSpan;
33import android.text.style.AccessibilityURLSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.text.style.AlignmentSpan;
35import android.text.style.BackgroundColorSpan;
36import android.text.style.BulletSpan;
37import android.text.style.CharacterStyle;
Luca Zanoline6d36822011-08-30 18:04:34 +010038import android.text.style.EasyEditSpan;
Gilles Debunne0eea6682011-08-29 13:30:31 -070039import android.text.style.ForegroundColorSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040import android.text.style.LeadingMarginSpan;
Victoria Leasedf8ef4b2012-08-17 15:34:01 -070041import android.text.style.LocaleSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.text.style.MetricAffectingSpan;
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +010043import android.text.style.ParagraphStyle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.text.style.QuoteSpan;
45import android.text.style.RelativeSizeSpan;
46import android.text.style.ReplacementSpan;
47import android.text.style.ScaleXSpan;
Gilles Debunne28294cc2011-08-24 12:02:05 -070048import android.text.style.SpellCheckSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import android.text.style.StrikethroughSpan;
50import android.text.style.StyleSpan;
51import android.text.style.SubscriptSpan;
Gilles Debunne28294cc2011-08-24 12:02:05 -070052import android.text.style.SuggestionRangeSpan;
Gilles Debunnea00972a2011-04-13 16:07:31 -070053import android.text.style.SuggestionSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054import android.text.style.SuperscriptSpan;
55import android.text.style.TextAppearanceSpan;
Niels Egberts4f4ead42014-06-23 12:01:14 +010056import android.text.style.TtsSpan;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057import android.text.style.TypefaceSpan;
58import android.text.style.URLSpan;
59import android.text.style.UnderlineSpan;
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +010060import android.text.style.UpdateAppearance;
Victoria Lease577ba532013-04-19 13:12:15 -070061import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062import android.util.Printer;
Fabrice Di Megliod3d9f3f2012-09-18 12:55:32 -070063import android.view.View;
Raph Levien8d2aa192014-05-14 15:46:47 -070064
Doug Feltcb3791202011-07-07 11:57:48 -070065import com.android.internal.R;
66import com.android.internal.util.ArrayUtils;
Eugene Susla6ed45d82017-01-22 13:52:51 -080067import com.android.internal.util.Preconditions;
Raph Levien8d2aa192014-05-14 15:46:47 -070068
Gilles Debunne1e3ac182011-03-08 14:22:34 -080069import java.lang.reflect.Array;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070import java.util.Iterator;
Roozbeh Pournader3bfce332016-06-17 15:03:56 -070071import java.util.List;
Fabrice Di Megliod3d9f3f2012-09-18 12:55:32 -070072import java.util.Locale;
Doug Felte8e45f22010-03-29 14:58:40 -070073import java.util.regex.Pattern;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074
75public class TextUtils {
Victoria Lease577ba532013-04-19 13:12:15 -070076 private static final String TAG = "TextUtils";
77
Neil Fullerd29bdb22015-02-06 10:03:08 +000078 /* package */ static final char[] ELLIPSIS_NORMAL = { '\u2026' }; // this is "..."
Roozbeh Pournader0ad4dcc2016-05-26 18:51:59 -070079 /** {@hide} */
80 public static final String ELLIPSIS_STRING = new String(ELLIPSIS_NORMAL);
Neil Fullerd29bdb22015-02-06 10:03:08 +000081
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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084
Fabrice Di Megliocb332642011-09-23 19:08:04 -070085 private TextUtils() { /* cannot be instantiated */ }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086
87 public static void getChars(CharSequence s, int start, int end,
88 char[] dest, int destoff) {
Gilles Debunne1e3ac182011-03-08 14:22:34 -080089 Class<? extends CharSequence> c = s.getClass();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080090
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) {
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800110 Class<? extends CharSequence> c = s.getClass();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111
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) {
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800119 Class<? extends CharSequence> c = s.getClass();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800120
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) {
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800160 Class<? extends CharSequence> c = s.getClass();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800161
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
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800177 Class<? extends CharSequence> c = s.getClass();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178
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) {
Raph Levien8d2aa192014-05-14 15:46:47 -0700251 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);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257
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();
Andreas Gampea8a58ff2016-05-18 11:58:39 -0700322 Iterator<?> it = tokens.iterator();
323 if (it.hasNext()) {
324 sb.append(it.next());
325 while (it.hasNext()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800326 sb.append(delimiter);
Andreas Gampea8a58ff2016-05-18 11:58:39 -0700327 sb.append(it.next());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800329 }
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 */
Scott Kennedy6cd132f2015-02-19 10:36:12 -0800460 public static boolean isEmpty(@Nullable CharSequence str) {
Amin Shaikhd4196c92017-02-06 17:04:47 -0800461 return str == null || str.length() == 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800462 }
463
Jeff Sharkey5cc0df22015-06-17 19:44:05 -0700464 /** {@hide} */
465 public static String nullIfEmpty(@Nullable String str) {
466 return isEmpty(str) ? null : str;
467 }
468
Eugene Susla6ed45d82017-01-22 13:52:51 -0800469 /** {@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
Eugene Susla36e866b2017-02-23 18:24:39 -0800479 /** {@hide} */
480 public static int length(@Nullable String s) {
481 return isEmpty(s) ? 0 : s.length();
482 }
483
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800484 /**
485 * Returns the length that the specified CharSequence would have if
Roozbeh Pournader3efda952015-08-11 09:55:57 -0700486 * spaces and ASCII control characters were trimmed from the start and end,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800487 * 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
Clara Bayarrid608a0a2016-04-27 11:53:22 +0100529 /**
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.
Roozbeh Pournader3efda952015-08-11 09:55:57 -0700534 */
535 @Deprecated
Clara Bayarrid608a0a2016-04-27 11:53:22 +0100536 public static CharSequence getReverse(CharSequence source, int start, int end) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800537 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
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800560 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800561 public String toString() {
562 return subSequence(0, length()).toString();
563 }
564
565 public char charAt(int off) {
Roozbeh Pournader9559c202016-12-13 10:59:50 -0800566 return (char) UCharacter.getMirror(mSource.charAt(mEnd - 1 - off));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800567 }
568
Roozbeh Pournader9559c202016-12-13 10:59:50 -0800569 @SuppressWarnings("deprecation")
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800570 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 */
Victoria Lease577ba532013-04-19 13:12:15 -0700593 public static final int FIRST_SPAN = ALIGNMENT_SPAN;
594 /** @hide */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800595 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;
satokadb43582011-03-09 10:08:47 +0900628 /** @hide */
Gilles Debunnea00972a2011-04-13 16:07:31 -0700629 public static final int SUGGESTION_SPAN = 19;
Gilles Debunne28294cc2011-08-24 12:02:05 -0700630 /** @hide */
631 public static final int SPELL_CHECK_SPAN = 20;
632 /** @hide */
633 public static final int SUGGESTION_RANGE_SPAN = 21;
Luca Zanoline6d36822011-08-30 18:04:34 +0100634 /** @hide */
635 public static final int EASY_EDIT_SPAN = 22;
Victoria Leasedf8ef4b2012-08-17 15:34:01 -0700636 /** @hide */
637 public static final int LOCALE_SPAN = 23;
Victoria Lease577ba532013-04-19 13:12:15 -0700638 /** @hide */
Niels Egberts4f4ead42014-06-23 12:01:14 +0100639 public static final int TTS_SPAN = 24;
640 /** @hide */
Phil Weaver193520e2016-12-13 09:39:06 -0800641 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800646
647 /**
648 * Flatten a CharSequence and whatever styles can be copied across processes
649 * into the parcel.
650 */
Alan Viverettea70d4a92015-06-02 16:11:00 -0700651 public static void writeToParcel(CharSequence cs, Parcel p, int parcelableFlags) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652 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) {
Alan Viverettea70d4a92015-06-02 16:11:00 -0700673 final ParcelableSpan ps = (ParcelableSpan) prop;
674 final int spanTypeId = ps.getSpanTypeIdInternal();
Victoria Lease577ba532013-04-19 13:12:15 -0700675 if (spanTypeId < FIRST_SPAN || spanTypeId > LAST_SPAN) {
Alan Viverettea70d4a92015-06-02 16:11:00 -0700676 Log.e(TAG, "External class \"" + ps.getClass().getSimpleName()
Victoria Lease577ba532013-04-19 13:12:15 -0700677 + "\" is attempting to use the frameworks-only ParcelableSpan"
678 + " interface");
679 } else {
680 p.writeInt(spanTypeId);
Alan Viverettea70d4a92015-06-02 16:11:00 -0700681 ps.writeToParcelInternal(p, parcelableFlags);
Victoria Lease577ba532013-04-19 13:12:15 -0700682 writeWhere(p, sp, o);
683 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 }
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 */
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800710 public CharSequence createFromParcel(Parcel p) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800711 int kind = p.readInt();
712
Martin Wallgrencee20512011-04-07 14:45:43 +0200713 String string = p.readString();
714 if (string == null) {
715 return null;
716 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800717
Martin Wallgrencee20512011-04-07 14:45:43 +0200718 if (kind == 1) {
719 return string;
720 }
721
722 SpannableString sp = new SpannableString(string);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800723
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
Gilles Debunnea00972a2011-04-13 16:07:31 -0700803 case SUGGESTION_SPAN:
804 readSpan(p, sp, new SuggestionSpan(p));
805 break;
806
Gilles Debunne28294cc2011-08-24 12:02:05 -0700807 case SPELL_CHECK_SPAN:
808 readSpan(p, sp, new SpellCheckSpan(p));
809 break;
810
811 case SUGGESTION_RANGE_SPAN:
Gilles Debunne0eea6682011-08-29 13:30:31 -0700812 readSpan(p, sp, new SuggestionRangeSpan(p));
Gilles Debunne28294cc2011-08-24 12:02:05 -0700813 break;
Gilles Debunnee90bed12011-08-30 14:28:27 -0700814
Luca Zanoline6d36822011-08-30 18:04:34 +0100815 case EASY_EDIT_SPAN:
Luca Zanolin1b15ba52013-02-20 14:31:37 +0000816 readSpan(p, sp, new EasyEditSpan(p));
Luca Zanoline6d36822011-08-30 18:04:34 +0100817 break;
818
Victoria Leasedf8ef4b2012-08-17 15:34:01 -0700819 case LOCALE_SPAN:
820 readSpan(p, sp, new LocaleSpan(p));
821 break;
822
Niels Egberts4f4ead42014-06-23 12:01:14 +0100823 case TTS_SPAN:
824 readSpan(p, sp, new TtsSpan(p));
825 break;
826
Phil Weaver193520e2016-12-13 09:39:06 -0800827 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800835 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(),
Gilles Debunne1e3ac182011-03-08 14:22:34 -0800887 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800888 }
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
Daisuke Miyakawac1d27482009-05-25 17:37:41 +09001040 /**
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 */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001051 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,
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001080 /**
1081 * @hide
1082 */
1083 END_SMALL
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001084 }
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 /**
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,
Doug Felte8e45f22010-03-29 14:58:40 -07001109 * or, if it does not fit, a copy with ellipsis character added
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001110 * 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
Doug Feltcb3791202011-07-07 11:57:48 -07001115 * report the start and end of the ellipsized range. TextDirection
1116 * is determined by the first strong directional character.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001117 */
1118 public static CharSequence ellipsize(CharSequence text,
Doug Felte8e45f22010-03-29 14:58:40 -07001119 TextPaint paint,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120 float avail, TruncateAt where,
1121 boolean preserveLength,
1122 EllipsizeCallback callback) {
Doug Feltcb3791202011-07-07 11:57:48 -07001123 return ellipsize(text, paint, avail, where, preserveLength, callback,
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001124 TextDirectionHeuristics.FIRSTSTRONG_LTR,
Neil Fullerd29bdb22015-02-06 10:03:08 +00001125 (where == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS_STRING : ELLIPSIS_STRING);
Doug Feltcb3791202011-07-07 11:57:48 -07001126 }
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,
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001146 TextDirectionHeuristic textDir, String ellipsis) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001147
1148 int len = text.length();
1149
Doug Felte8e45f22010-03-29 14:58:40 -07001150 MeasuredText mt = MeasuredText.obtain();
1151 try {
Doug Feltcb3791202011-07-07 11:57:48 -07001152 float width = setPara(mt, paint, text, 0, text.length(), textDir);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001153
Doug Felte8e45f22010-03-29 14:58:40 -07001154 if (width <= avail) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155 if (callback != null) {
1156 callback.ellipsized(0, 0);
1157 }
1158
1159 return text;
1160 }
1161
Doug Felte8e45f22010-03-29 14:58:40 -07001162 // XXX assumes ellipsis string does not require shaping and
1163 // is unaffected by style
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001164 float ellipsiswid = paint.measureText(ellipsis);
Doug Felte8e45f22010-03-29 14:58:40 -07001165 avail -= ellipsiswid;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001166
Doug Felte8e45f22010-03-29 14:58:40 -07001167 int left = 0;
1168 int right = len;
1169 if (avail < 0) {
1170 // it all goes
1171 } else if (where == TruncateAt.START) {
Gilles Debunnec70e7a02012-02-23 18:05:55 -08001172 right = len - mt.breakText(len, false, avail);
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001173 } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) {
Gilles Debunnec70e7a02012-02-23 18:05:55 -08001174 left = mt.breakText(len, true, avail);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001175 } else {
Gilles Debunnec70e7a02012-02-23 18:05:55 -08001176 right = len - mt.breakText(len, false, avail / 2);
Doug Felte8e45f22010-03-29 14:58:40 -07001177 avail -= mt.measure(right, len);
Gilles Debunnec70e7a02012-02-23 18:05:55 -08001178 left = mt.breakText(right, true, avail);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001179 }
1180
1181 if (callback != null) {
1182 callback.ellipsized(left, right);
1183 }
1184
Doug Felte8e45f22010-03-29 14:58:40 -07001185 char[] buf = mt.mChars;
1186 Spanned sp = text instanceof Spanned ? (Spanned) text : null;
1187
1188 int remaining = len - (right - left);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001189 if (preserveLength) {
Doug Felte8e45f22010-03-29 14:58:40 -07001190 if (remaining > 0) { // else eliminate the ellipsis too
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001191 buf[left++] = ellipsis.charAt(0);
Doug Felte8e45f22010-03-29 14:58:40 -07001192 }
1193 for (int i = left; i < right; i++) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001194 buf[i] = ZWNBS_CHAR;
Doug Felte8e45f22010-03-29 14:58:40 -07001195 }
1196 String s = new String(buf, 0, len);
1197 if (sp == null) {
1198 return s;
1199 }
1200 SpannableString ss = new SpannableString(s);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001201 copySpansFrom(sp, 0, len, Object.class, ss, 0);
1202 return ss;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001203 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001204
Doug Felte8e45f22010-03-29 14:58:40 -07001205 if (remaining == 0) {
1206 return "";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001207 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001208
Doug Felte8e45f22010-03-29 14:58:40 -07001209 if (sp == null) {
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001210 StringBuilder sb = new StringBuilder(remaining + ellipsis.length());
Doug Felte8e45f22010-03-29 14:58:40 -07001211 sb.append(buf, 0, left);
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001212 sb.append(ellipsis);
Doug Felte8e45f22010-03-29 14:58:40 -07001213 sb.append(buf, right, len - right);
1214 return sb.toString();
1215 }
1216
1217 SpannableStringBuilder ssb = new SpannableStringBuilder();
1218 ssb.append(text, 0, left);
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001219 ssb.append(ellipsis);
Doug Felte8e45f22010-03-29 14:58:40 -07001220 ssb.append(text, right, len);
1221 return ssb;
1222 } finally {
1223 MeasuredText.recycle(mt);
1224 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001225 }
1226
1227 /**
Roozbeh Pournader3bfce332016-06-17 15:03:56 -07001228 * 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 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001309 * 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
Roozbeh Pournader3bfce332016-06-17 15:03:56 -07001315 * @param avail the horizontal width available for the text (in pixels)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001316 * @param oneMore the string for "1 more" in the current locale
1317 * @param more the string for "%d more" in the current locale
Roozbeh Pournader3bfce332016-06-17 15:03:56 -07001318 *
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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001323 */
Roozbeh Pournader3bfce332016-06-17 15:03:56 -07001324 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001325 public static CharSequence commaEllipsize(CharSequence text,
1326 TextPaint p, float avail,
1327 String oneMore,
1328 String more) {
Doug Feltcb3791202011-07-07 11:57:48 -07001329 return commaEllipsize(text, p, avail, oneMore, more,
1330 TextDirectionHeuristics.FIRSTSTRONG_LTR);
1331 }
1332
1333 /**
1334 * @hide
1335 */
Roozbeh Pournader3bfce332016-06-17 15:03:56 -07001336 @Deprecated
Doug Feltcb3791202011-07-07 11:57:48 -07001337 public static CharSequence commaEllipsize(CharSequence text, TextPaint p,
1338 float avail, String oneMore, String more, TextDirectionHeuristic textDir) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001339
Doug Felte8e45f22010-03-29 14:58:40 -07001340 MeasuredText mt = MeasuredText.obtain();
1341 try {
1342 int len = text.length();
Doug Feltcb3791202011-07-07 11:57:48 -07001343 float width = setPara(mt, p, text, 0, len, textDir);
Doug Felte8e45f22010-03-29 14:58:40 -07001344 if (width <= avail) {
1345 return text;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001346 }
1347
Doug Felte8e45f22010-03-29 14:58:40 -07001348 char[] buf = mt.mChars;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001349
Doug Felte8e45f22010-03-29 14:58:40 -07001350 int commaCount = 0;
1351 for (int i = 0; i < len; i++) {
1352 if (buf[i] == ',') {
1353 commaCount++;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001354 }
1355 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001356
Doug Felte8e45f22010-03-29 14:58:40 -07001357 int remaining = commaCount + 1;
1358
1359 int ok = 0;
Doug Felte8e45f22010-03-29 14:58:40 -07001360 String okFormat = "";
1361
1362 int w = 0;
1363 int count = 0;
1364 float[] widths = mt.mWidths;
1365
Doug Felte8e45f22010-03-29 14:58:40 -07001366 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
Raph Levien70616ec2015-03-04 10:41:30 -08001383 tempMt.setPara(format, 0, format.length(), textDir, null);
Brian Muramatsu4c8ad6e2011-01-27 18:13:39 -08001384 float moreWid = tempMt.addStyleRun(p, tempMt.mLen, null);
Doug Felte8e45f22010-03-29 14:58:40 -07001385
1386 if (w + moreWid <= avail) {
1387 ok = i + 1;
Doug Felte8e45f22010-03-29 14:58:40 -07001388 okFormat = format;
1389 }
1390 }
1391 }
1392 MeasuredText.recycle(tempMt);
1393
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001394 SpannableStringBuilder out = new SpannableStringBuilder(okFormat);
1395 out.insert(0, text, 0, ok);
1396 return out;
Doug Felte8e45f22010-03-29 14:58:40 -07001397 } finally {
1398 MeasuredText.recycle(mt);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001399 }
1400 }
1401
Doug Felte8e45f22010-03-29 14:58:40 -07001402 private static float setPara(MeasuredText mt, TextPaint paint,
Doug Feltcb3791202011-07-07 11:57:48 -07001403 CharSequence text, int start, int end, TextDirectionHeuristic textDir) {
Doug Felte8e45f22010-03-29 14:58:40 -07001404
Raph Levien70616ec2015-03-04 10:41:30 -08001405 mt.setPara(text, start, end, textDir, null);
Doug Felte8e45f22010-03-29 14:58:40 -07001406
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);
Gilles Debunne1e3ac182011-03-08 14:22:34 -08001420 spans = TextUtils.removeEmptySpans(spans, sp, MetricAffectingSpan.class);
Doug Felte8e45f22010-03-29 14:58:40 -07001421 width += mt.addStyleRun(paint, spans, spanEnd - spanStart, null);
1422 }
1423 }
1424
1425 return width;
1426 }
1427
Roozbeh Pournader8823c852016-06-09 18:36:47 -07001428 // 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.
Doug Felte8e45f22010-03-29 14:58:40 -07001433 /* package */
Roozbeh Pournader8823c852016-06-09 18:36:47 -07001434 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
Doug Felte8e45f22010-03-29 14:58:40 -07001443 }
1444
Roozbeh Pournader8823c852016-06-09 18:36:47 -07001445 // 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).
Doug Felte8e45f22010-03-29 14:58:40 -07001449 /* package */
1450 static boolean doesNotNeedBidi(char[] text, int start, int len) {
Roozbeh Pournader8823c852016-06-09 18:36:47 -07001451 final int end = start + len;
1452 for (int i = start; i < end; i++) {
1453 if (couldAffectRtl(text[i])) {
Doug Felte8e45f22010-03-29 14:58:40 -07001454 return false;
1455 }
1456 }
1457 return true;
1458 }
1459
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001460 /* 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)
Adam Lesinski776abc22014-03-07 11:30:59 -05001469 buf = ArrayUtils.newUnpaddedCharArray(len);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001470
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 '\'':
Marc Blankf4832da2012-02-13 10:11:50 -08001504 //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$
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001509 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.
Roozbeh Pournadere57886e2017-05-02 18:10:10 -07001523 *
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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001535 */
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;
Roozbeh Pournadere57886e2017-05-02 18:10:10 -07001546 for (CharSequence piece : text) {
1547 if (piece instanceof Spanned) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001548 spanned = true;
1549 break;
1550 }
1551 }
1552
Roozbeh Pournadere57886e2017-05-02 18:10:10 -07001553 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001567 return sb.toString();
1568 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001569 }
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();
Roozbeh Pournadera93880e2015-08-10 17:23:05 -07001576 for (int cp, i=0; i<len; i+=Character.charCount(cp)) {
Roozbeh Pournader1cc2acf2015-08-11 10:37:07 -07001577 cp = Character.codePointAt(str, i);
Roozbeh Pournadera93880e2015-08-10 17:23:05 -07001578 int gc = Character.getType(cp);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001579 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.
Roozbeh Pournadera93880e2015-08-10 17:23:05 -07001594 *
1595 * This does not support non-BMP characters and should not be used.
1596 *
1597 * @deprecated Use {@link #isGraphic(CharSequence)} instead.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001598 */
Roozbeh Pournadera93880e2015-08-10 17:23:05 -07001599 @Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001600 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();
Roozbeh Pournader3efda952015-08-11 09:55:57 -07001616 for (int cp, i = 0; i < len; i += Character.charCount(cp)) {
1617 cp = Character.codePointAt(str, i);
1618 if (!Character.isDigit(cp)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001619 return false;
1620 }
1621 }
1622 return true;
1623 }
1624
1625 /**
Daisuke Miyakawa973afa92009-12-03 10:43:45 +09001626 * @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 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001648 * 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;
Doug Felte8e45f22010-03-29 14:58:40 -07001654
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001655 /**
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;
Doug Felte8e45f22010-03-29 14:58:40 -07001662
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001663 /**
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;
Doug Felte8e45f22010-03-29 14:58:40 -07001670
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001671 /**
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}.
Doug Felte8e45f22010-03-29 14:58:40 -07001676 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001677 * @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}.
Mark Wagner60919952010-03-01 09:24:59 -08001682 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001683 * @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) {
Mark Wagner60919952010-03-01 09:24:59 -08001689 if (off < 0) {
1690 return 0;
1691 }
1692
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001693 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 }
Doug Felte8e45f22010-03-29 14:58:40 -07001776
Brad Fitzpatrick11fe1812010-09-10 16:07:52 -07001777 /**
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
Gilles Debunne1e3ac182011-03-08 14:22:34 -08001806 /**
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
Gilles Debunne6c488de2012-03-01 16:20:35 -08001856 /**
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
Fabrice Di Megliod3d9f3f2012-09-18 12:55:32 -07001886 /**
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) {
Roozbeh Pournader463b4822015-08-06 16:04:45 -07001897 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;
Fabrice Di Megliod3d9f3f2012-09-18 12:55:32 -07001903 }
1904
Jeff Sharkeyf491c722015-06-11 09:16:19 -07001905 /**
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
Abodunrinwa Tokiea6cb122017-04-28 22:14:13 +01001914 /**
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
Felipe Lemea8fce3b2017-04-04 14:22:12 -07001930 /**
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
Eugene Susla4a34f9c2017-05-16 14:16:38 -07001946 /**
1947 * Prepends {@code start} and appends {@code end} to a given {@link StringBuilder}
1948 *
1949 * @hide
1950 */
1951 public static void wrap(StringBuilder builder, String start, String end) {
1952 builder.insert(0, start);
1953 builder.append(end);
1954 }
1955
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001956 private static Object sLock = new Object();
Fabrice Di Megliod3d9f3f2012-09-18 12:55:32 -07001957
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001958 private static char[] sTemp = null;
Fabrice Di Megliocb332642011-09-23 19:08:04 -07001959
1960 private static String[] EMPTY_STRING_ARRAY = new String[]{};
1961
1962 private static final char ZWNBS_CHAR = '\uFEFF';
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001963}