• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Nullable;
20 import android.compat.annotation.UnsupportedAppUsage;
21 import android.os.Build;
22 
23 import com.android.internal.util.ArrayUtils;
24 import com.android.internal.util.GrowingArrayUtils;
25 
26 import libcore.util.EmptyArray;
27 
28 import java.lang.reflect.Array;
29 
30 @android.ravenwood.annotation.RavenwoodKeepWholeClass
31 /* package */ abstract class SpannableStringInternal
32 {
SpannableStringInternal(CharSequence source, int start, int end, boolean ignoreNoCopySpan)33     /* package */ SpannableStringInternal(CharSequence source,
34                                           int start, int end, boolean ignoreNoCopySpan) {
35         if (start == 0 && end == source.length())
36             mText = source.toString();
37         else
38             mText = source.toString().substring(start, end);
39 
40         mSpans = EmptyArray.OBJECT;
41         // Invariant: mSpanData.length = mSpans.length * COLUMNS
42         mSpanData = EmptyArray.INT;
43 
44         if (source instanceof Spanned) {
45             if (source instanceof SpannableStringInternal) {
46                 copySpansFromInternal(
47                         (SpannableStringInternal) source, start, end, ignoreNoCopySpan);
48             } else {
49                 copySpansFromSpanned((Spanned) source, start, end, ignoreNoCopySpan);
50             }
51         }
52     }
53 
54     /**
55      * This unused method is left since this is listed in hidden api list.
56      *
57      * Due to backward compatibility reasons, we copy even NoCopySpan by default
58      */
59     @UnsupportedAppUsage
SpannableStringInternal(CharSequence source, int start, int end)60     /* package */ SpannableStringInternal(CharSequence source, int start, int end) {
61         this(source, start, end, false /* ignoreNoCopySpan */);
62     }
63 
64     /**
65      * Copies another {@link Spanned} object's spans between [start, end] into this object.
66      *
67      * @param src Source object to copy from.
68      * @param start Start index in the source object.
69      * @param end End index in the source object.
70      * @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source}
71      */
copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan)72     private void copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
73         Object[] spans = src.getSpans(start, end, Object.class);
74 
75         for (int i = 0; i < spans.length; i++) {
76             if (ignoreNoCopySpan && spans[i] instanceof NoCopySpan) {
77                 continue;
78             }
79             int st = src.getSpanStart(spans[i]);
80             int en = src.getSpanEnd(spans[i]);
81             int fl = src.getSpanFlags(spans[i]);
82 
83             if (st < start)
84                 st = start;
85             if (en > end)
86                 en = end;
87 
88             setSpan(spans[i], st - start, en - start, fl, false/*enforceParagraph*/);
89         }
90     }
91 
92     /**
93      * Copies a {@link SpannableStringInternal} object's spans between [start, end] into this
94      * object.
95      *
96      * @param src Source object to copy from.
97      * @param start Start index in the source object.
98      * @param end End index in the source object.
99      * @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons.
100      */
copySpansFromInternal(SpannableStringInternal src, int start, int end, boolean ignoreNoCopySpan)101     private void copySpansFromInternal(SpannableStringInternal src, int start, int end,
102             boolean ignoreNoCopySpan) {
103         int count = 0;
104         final int[] srcData = src.mSpanData;
105         final Object[] srcSpans = src.mSpans;
106         final int limit = src.mSpanCount;
107         boolean hasNoCopySpan = false;
108 
109         for (int i = 0; i < limit; i++) {
110             int spanStart = srcData[i * COLUMNS + START];
111             int spanEnd = srcData[i * COLUMNS + END];
112             if (isOutOfCopyRange(start, end, spanStart, spanEnd)) continue;
113             if (srcSpans[i] instanceof NoCopySpan) {
114                 hasNoCopySpan = true;
115                 if (ignoreNoCopySpan) {
116                     continue;
117                 }
118             }
119             count++;
120         }
121 
122         if (count == 0) return;
123 
124         if (!hasNoCopySpan && start == 0 && end == src.length()) {
125             mSpans = ArrayUtils.newUnpaddedObjectArray(src.mSpans.length);
126             mSpanData = new int[src.mSpanData.length];
127             mSpanCount = src.mSpanCount;
128             System.arraycopy(src.mSpans, 0, mSpans, 0, src.mSpans.length);
129             System.arraycopy(src.mSpanData, 0, mSpanData, 0, mSpanData.length);
130         } else {
131             mSpanCount = count;
132             mSpans = ArrayUtils.newUnpaddedObjectArray(mSpanCount);
133             mSpanData = new int[mSpans.length * COLUMNS];
134             for (int i = 0, j = 0; i < limit; i++) {
135                 int spanStart = srcData[i * COLUMNS + START];
136                 int spanEnd = srcData[i * COLUMNS + END];
137                 if (isOutOfCopyRange(start, end, spanStart, spanEnd)
138                         || (ignoreNoCopySpan && srcSpans[i] instanceof NoCopySpan)) {
139                     continue;
140                 }
141                 if (spanStart < start) spanStart = start;
142                 if (spanEnd > end) spanEnd = end;
143 
144                 mSpans[j] = srcSpans[i];
145                 mSpanData[j * COLUMNS + START] = spanStart - start;
146                 mSpanData[j * COLUMNS + END] = spanEnd - start;
147                 mSpanData[j * COLUMNS + FLAGS] = srcData[i * COLUMNS + FLAGS];
148                 j++;
149             }
150         }
151     }
152 
153     /**
154      * Checks if [spanStart, spanEnd] interval is excluded from [start, end].
155      *
156      * @return True if excluded, false if included.
157      */
158     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
isOutOfCopyRange(int start, int end, int spanStart, int spanEnd)159     private final boolean isOutOfCopyRange(int start, int end, int spanStart, int spanEnd) {
160         if (spanStart > end || spanEnd < start) return true;
161         if (spanStart != spanEnd && start != end) {
162             if (spanStart == end || spanEnd == start) return true;
163         }
164         return false;
165     }
166 
length()167     public final int length() {
168         return mText.length();
169     }
170 
charAt(int i)171     public final char charAt(int i) {
172         return mText.charAt(i);
173     }
174 
toString()175     public final String toString() {
176         return mText;
177     }
178 
179     /* subclasses must do subSequence() to preserve type */
180 
getChars(int start, int end, char[] dest, int off)181     public final void getChars(int start, int end, char[] dest, int off) {
182         mText.getChars(start, end, dest, off);
183     }
184 
185     @UnsupportedAppUsage
setSpan(Object what, int start, int end, int flags)186     /* package */ void setSpan(Object what, int start, int end, int flags) {
187         setSpan(what, start, end, flags, true/*enforceParagraph*/);
188     }
189 
190     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
isIndexFollowsNextLine(int index)191     private boolean isIndexFollowsNextLine(int index) {
192         return index != 0 && index != length() && charAt(index - 1) != '\n';
193     }
194 
195     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
setSpan(Object what, int start, int end, int flags, boolean enforceParagraph)196     private void setSpan(Object what, int start, int end, int flags, boolean enforceParagraph) {
197         int nstart = start;
198         int nend = end;
199 
200         checkRange("setSpan", start, end);
201 
202         if ((flags & Spannable.SPAN_PARAGRAPH) == Spannable.SPAN_PARAGRAPH) {
203             if (isIndexFollowsNextLine(start)) {
204                 if (!enforceParagraph) {
205                     // do not set the span
206                     return;
207                 }
208                 throw new RuntimeException("PARAGRAPH span must start at paragraph boundary"
209                         + " (" + start + " follows " + charAt(start - 1) + ")");
210             }
211 
212             if (isIndexFollowsNextLine(end)) {
213                 if (!enforceParagraph) {
214                     // do not set the span
215                     return;
216                 }
217                 throw new RuntimeException("PARAGRAPH span must end at paragraph boundary"
218                         + " (" + end + " follows " + charAt(end - 1) + ")");
219             }
220         }
221 
222         int count = mSpanCount;
223         Object[] spans = mSpans;
224         int[] data = mSpanData;
225 
226         for (int i = 0; i < count; i++) {
227             if (spans[i] == what) {
228                 int ostart = data[i * COLUMNS + START];
229                 int oend = data[i * COLUMNS + END];
230 
231                 data[i * COLUMNS + START] = start;
232                 data[i * COLUMNS + END] = end;
233                 data[i * COLUMNS + FLAGS] = flags;
234 
235                 sendSpanChanged(what, ostart, oend, nstart, nend);
236                 return;
237             }
238         }
239 
240         if (mSpanCount + 1 >= mSpans.length) {
241             Object[] newtags = ArrayUtils.newUnpaddedObjectArray(
242                     GrowingArrayUtils.growSize(mSpanCount));
243             int[] newdata = new int[newtags.length * 3];
244 
245             System.arraycopy(mSpans, 0, newtags, 0, mSpanCount);
246             System.arraycopy(mSpanData, 0, newdata, 0, mSpanCount * 3);
247 
248             mSpans = newtags;
249             mSpanData = newdata;
250         }
251 
252         mSpans[mSpanCount] = what;
253         mSpanData[mSpanCount * COLUMNS + START] = start;
254         mSpanData[mSpanCount * COLUMNS + END] = end;
255         mSpanData[mSpanCount * COLUMNS + FLAGS] = flags;
256         mSpanCount++;
257 
258         if (this instanceof Spannable)
259             sendSpanAdded(what, nstart, nend);
260     }
261 
262     @UnsupportedAppUsage
removeSpan(Object what)263     /* package */ void removeSpan(Object what) {
264         removeSpan(what, 0 /* flags */);
265     }
266 
267     /**
268      * @hide
269      */
removeSpan(Object what, int flags)270     public void removeSpan(Object what, int flags) {
271         int count = mSpanCount;
272         Object[] spans = mSpans;
273         int[] data = mSpanData;
274 
275         for (int i = count - 1; i >= 0; i--) {
276             if (spans[i] == what) {
277                 int ostart = data[i * COLUMNS + START];
278                 int oend = data[i * COLUMNS + END];
279 
280                 int c = count - (i + 1);
281 
282                 System.arraycopy(spans, i + 1, spans, i, c);
283                 System.arraycopy(data, (i + 1) * COLUMNS,
284                         data, i * COLUMNS, c * COLUMNS);
285 
286                 mSpanCount--;
287 
288                 if ((flags & Spanned.SPAN_INTERMEDIATE) == 0) {
289                     sendSpanRemoved(what, ostart, oend);
290                 }
291                 return;
292             }
293         }
294     }
295 
296     @UnsupportedAppUsage
getSpanStart(Object what)297     public int getSpanStart(Object what) {
298         int count = mSpanCount;
299         Object[] spans = mSpans;
300         int[] data = mSpanData;
301 
302         for (int i = count - 1; i >= 0; i--) {
303             if (spans[i] == what) {
304                 return data[i * COLUMNS + START];
305             }
306         }
307 
308         return -1;
309     }
310 
311     @UnsupportedAppUsage
getSpanEnd(Object what)312     public int getSpanEnd(Object what) {
313         int count = mSpanCount;
314         Object[] spans = mSpans;
315         int[] data = mSpanData;
316 
317         for (int i = count - 1; i >= 0; i--) {
318             if (spans[i] == what) {
319                 return data[i * COLUMNS + END];
320             }
321         }
322 
323         return -1;
324     }
325 
326     @UnsupportedAppUsage
getSpanFlags(Object what)327     public int getSpanFlags(Object what) {
328         int count = mSpanCount;
329         Object[] spans = mSpans;
330         int[] data = mSpanData;
331 
332         for (int i = count - 1; i >= 0; i--) {
333             if (spans[i] == what) {
334                 return data[i * COLUMNS + FLAGS];
335             }
336         }
337 
338         return 0;
339     }
340 
341     @UnsupportedAppUsage
getSpans(int queryStart, int queryEnd, Class<T> kind)342     public <T> T[] getSpans(int queryStart, int queryEnd, Class<T> kind) {
343         int count = 0;
344 
345         int spanCount = mSpanCount;
346         Object[] spans = mSpans;
347         int[] data = mSpanData;
348         Object[] ret = null;
349         Object ret1 = null;
350 
351         for (int i = 0; i < spanCount; i++) {
352             int spanStart = data[i * COLUMNS + START];
353             int spanEnd = data[i * COLUMNS + END];
354 
355             if (spanStart > queryEnd) {
356                 continue;
357             }
358             if (spanEnd < queryStart) {
359                 continue;
360             }
361 
362             if (spanStart != spanEnd && queryStart != queryEnd) {
363                 if (spanStart == queryEnd) {
364                     continue;
365                 }
366                 if (spanEnd == queryStart) {
367                     continue;
368                 }
369             }
370 
371             // verify span class as late as possible, since it is expensive
372             if (kind != null && kind != Object.class && !kind.isInstance(spans[i])) {
373                 continue;
374             }
375 
376             if (count == 0) {
377                 ret1 = spans[i];
378                 count++;
379             } else {
380                 if (count == 1) {
381                     ret = (Object[]) Array.newInstance(kind, spanCount - i + 1);
382                     ret[0] = ret1;
383                 }
384 
385                 int prio = data[i * COLUMNS + FLAGS] & Spanned.SPAN_PRIORITY;
386                 if (prio != 0) {
387                     int j;
388 
389                     for (j = 0; j < count; j++) {
390                         int p = getSpanFlags(ret[j]) & Spanned.SPAN_PRIORITY;
391 
392                         if (prio > p) {
393                             break;
394                         }
395                     }
396 
397                     System.arraycopy(ret, j, ret, j + 1, count - j);
398                     ret[j] = spans[i];
399                     count++;
400                 } else {
401                     ret[count++] = spans[i];
402                 }
403             }
404         }
405 
406         if (count == 0) {
407             return (T[]) ArrayUtils.emptyArray(kind);
408         }
409         if (count == 1) {
410             ret = (Object[]) Array.newInstance(kind, 1);
411             ret[0] = ret1;
412             return (T[]) ret;
413         }
414         if (count == ret.length) {
415             return (T[]) ret;
416         }
417 
418         Object[] nret = (Object[]) Array.newInstance(kind, count);
419         System.arraycopy(ret, 0, nret, 0, count);
420         return (T[]) nret;
421     }
422 
423     @UnsupportedAppUsage
nextSpanTransition(int start, int limit, Class kind)424     public int nextSpanTransition(int start, int limit, Class kind) {
425         int count = mSpanCount;
426         Object[] spans = mSpans;
427         int[] data = mSpanData;
428 
429         if (kind == null) {
430             kind = Object.class;
431         }
432 
433         for (int i = 0; i < count; i++) {
434             int st = data[i * COLUMNS + START];
435             int en = data[i * COLUMNS + END];
436 
437             if (st > start && st < limit && kind.isInstance(spans[i]))
438                 limit = st;
439             if (en > start && en < limit && kind.isInstance(spans[i]))
440                 limit = en;
441         }
442 
443         return limit;
444     }
445 
446     @UnsupportedAppUsage
sendSpanAdded(Object what, int start, int end)447     private void sendSpanAdded(Object what, int start, int end) {
448         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
449         int n = recip.length;
450 
451         for (int i = 0; i < n; i++) {
452             recip[i].onSpanAdded((Spannable) this, what, start, end);
453         }
454     }
455 
456     @UnsupportedAppUsage
sendSpanRemoved(Object what, int start, int end)457     private void sendSpanRemoved(Object what, int start, int end) {
458         SpanWatcher[] recip = getSpans(start, end, SpanWatcher.class);
459         int n = recip.length;
460 
461         for (int i = 0; i < n; i++) {
462             recip[i].onSpanRemoved((Spannable) this, what, start, end);
463         }
464     }
465 
466     @UnsupportedAppUsage
sendSpanChanged(Object what, int s, int e, int st, int en)467     private void sendSpanChanged(Object what, int s, int e, int st, int en) {
468         SpanWatcher[] recip = getSpans(Math.min(s, st), Math.max(e, en),
469                                        SpanWatcher.class);
470         int n = recip.length;
471 
472         for (int i = 0; i < n; i++) {
473             recip[i].onSpanChanged((Spannable) this, what, s, e, st, en);
474         }
475     }
476 
477     @UnsupportedAppUsage
region(int start, int end)478     private static String region(int start, int end) {
479         return "(" + start + " ... " + end + ")";
480     }
481 
482     @UnsupportedAppUsage
checkRange(final String operation, int start, int end)483     private void checkRange(final String operation, int start, int end) {
484         if (end < start) {
485             throw new IndexOutOfBoundsException(operation + " " +
486                                                 region(start, end) +
487                                                 " has end before start");
488         }
489 
490         int len = length();
491 
492         if (start > len || end > len) {
493             throw new IndexOutOfBoundsException(operation + " " +
494                                                 region(start, end) +
495                                                 " ends beyond length " + len);
496         }
497 
498         if (start < 0 || end < 0) {
499             throw new IndexOutOfBoundsException(operation + " " +
500                                                 region(start, end) +
501                                                 " starts before 0");
502         }
503     }
504 
505     // Same as SpannableStringBuilder
506     @Override
equals(@ullable Object o)507     public boolean equals(@Nullable Object o) {
508         if (o instanceof Spanned &&
509                 toString().equals(o.toString())) {
510             final Spanned other = (Spanned) o;
511             // Check span data
512             final Object[] otherSpans = other.getSpans(0, other.length(), Object.class);
513             final Object[] thisSpans = getSpans(0, length(), Object.class);
514             if (mSpanCount == otherSpans.length) {
515                 for (int i = 0; i < mSpanCount; ++i) {
516                     final Object thisSpan = thisSpans[i];
517                     final Object otherSpan = otherSpans[i];
518                     if (thisSpan == this) {
519                         if (other != otherSpan ||
520                                 getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
521                                 getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
522                                 getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
523                             return false;
524                         }
525                     } else if (!thisSpan.equals(otherSpan) ||
526                             getSpanStart(thisSpan) != other.getSpanStart(otherSpan) ||
527                             getSpanEnd(thisSpan) != other.getSpanEnd(otherSpan) ||
528                             getSpanFlags(thisSpan) != other.getSpanFlags(otherSpan)) {
529                         return false;
530                     }
531                 }
532                 return true;
533             }
534         }
535         return false;
536     }
537 
538     // Same as SpannableStringBuilder
539     @Override
hashCode()540     public int hashCode() {
541         int hash = toString().hashCode();
542         hash = hash * 31 + mSpanCount;
543         for (int i = 0; i < mSpanCount; ++i) {
544             Object span = mSpans[i];
545             if (span != this) {
546                 hash = hash * 31 + span.hashCode();
547             }
548             hash = hash * 31 + getSpanStart(span);
549             hash = hash * 31 + getSpanEnd(span);
550             hash = hash * 31 + getSpanFlags(span);
551         }
552         return hash;
553     }
554 
555     /**
556      * Following two unused methods are left since these are listed in hidden api list.
557      *
558      * Due to backward compatibility reasons, we copy even NoCopySpan by default
559      */
560     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
copySpans(Spanned src, int start, int end)561     private void copySpans(Spanned src, int start, int end) {
562         copySpansFromSpanned(src, start, end, false);
563     }
564 
565     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
copySpans(SpannableStringInternal src, int start, int end)566     private void copySpans(SpannableStringInternal src, int start, int end) {
567         copySpansFromInternal(src, start, end, false);
568     }
569 
570 
571 
572     @UnsupportedAppUsage
573     private String mText;
574     @UnsupportedAppUsage
575     private Object[] mSpans;
576     @UnsupportedAppUsage
577     private int[] mSpanData;
578     @UnsupportedAppUsage
579     private int mSpanCount;
580 
581     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
582     /* package */ static final Object[] EMPTY = new Object[0];
583 
584     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
585     private static final int START = 0;
586     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
587     private static final int END = 1;
588     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
589     private static final int FLAGS = 2;
590     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
591     private static final int COLUMNS = 3;
592 }
593