From e57886e659031b2400038d9cf00e48ab423e66f8 Mon Sep 17 00:00:00 2001 From: Roozbeh Pournader Date: Tue, 2 May 2017 18:10:10 -0700 Subject: [PATCH] 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 --- core/java/android/text/TextUtils.java | 50 +++++++++++++++------------ 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 081deebaed774..1be0b331d43ea 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -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 "null". + * + * 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) { + 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); + } + return new SpannedString(ssb); + } else { + final StringBuilder sb = new StringBuilder(); + for (CharSequence piece : text) { + sb.append(piece); + } return sb.toString(); } - - 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); - } - - off += len; - } - - return new SpannedString(ss); } /**