Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit e57886e6 authored by Roozbeh Pournader's avatar Roozbeh Pournader
Browse files

Don't crash in TextUtil.concat() with paragraph spans

This switches TextUtils.concat() to use SpannableStringBuilder
if there are any spans in the input strings. As a result, we can
avoid crashes when concatenating two CharSequences can result in
paragraph spans that now don't happen to be at paragraph boundaries.

Also document the exact behavior of the method for edge cases based
on the previous implementation.

Change-Id: I9caffbe95ed729b9c10d63a9e7d22b1e8c7185a3
Fixes: 28271770
Test: CTS tests added and old CTS tests continue to pass.
Test: cts-tradefed run cts-dev --module CtsTextTestCases --test android.text.cts.TextUtilsTest
parent 5f0e4365
Loading
Loading
Loading
Loading
+28 −22
Original line number Diff line number Diff line
@@ -1520,6 +1520,18 @@ public class TextUtils {
    /**
     * Returns a CharSequence concatenating the specified CharSequences,
     * retaining their spans if any.
     *
     * If there are no parameters, an empty string will be returned.
     *
     * If the number of parameters is exactly one, that parameter is returned as output, even if it
     * is null.
     *
     * If the number of parameters is at least two, any null CharSequence among the parameters is
     * treated as if it was the string <code>"null"</code>.
     *
     * If there are paragraph spans in the source CharSequences that satisfy paragraph boundary
     * requirements in the sources but would no longer satisfy them in the concatenated
     * CharSequence, they may get extended in the resulting CharSequence or not retained.
     */
    public static CharSequence concat(CharSequence... text) {
        if (text.length == 0) {
@@ -1531,35 +1543,29 @@ public class TextUtils {
        }

        boolean spanned = false;
        for (int i = 0; i < text.length; i++) {
            if (text[i] instanceof Spanned) {
        for (CharSequence piece : text) {
            if (piece instanceof Spanned) {
                spanned = true;
                break;
            }
        }

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < text.length; i++) {
            sb.append(text[i]);
        }

        if (!spanned) {
            return sb.toString();
        if (spanned) {
            final SpannableStringBuilder ssb = new SpannableStringBuilder();
            for (CharSequence piece : text) {
                // If a piece is null, we append the string "null" for compatibility with the
                // behavior of StringBuilder and the behavior of the concat() method in earlier
                // versions of Android.
                ssb.append(piece == null ? "null" : piece);
            }

        SpannableString ss = new SpannableString(sb);
        int off = 0;
        for (int i = 0; i < text.length; i++) {
            int len = text[i].length();

            if (text[i] instanceof Spanned) {
                copySpansFrom((Spanned) text[i], 0, len, Object.class, ss, off);
            return new SpannedString(ssb);
        } else {
            final StringBuilder sb = new StringBuilder();
            for (CharSequence piece : text) {
                sb.append(piece);
            }

            off += len;
            return sb.toString();
        }

        return new SpannedString(ss);
    }

    /**