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