diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index d0fd2b393633..509f4d996ae9 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -350,6 +350,53 @@ public class TextUtils { return ret; } + + /** + * Returns the longest prefix of a string for which the UTF-8 encoding fits into the given + * number of bytes, with the additional guarantee that the string is not truncated in the middle + * of a valid surrogate pair. + * + *

Unpaired surrogates are counted as taking 3 bytes of storage. However, a subsequent + * attempt to actually encode a string containing unpaired surrogates is likely to be rejected + * by the UTF-8 implementation. + * + * (copied from google/thirdparty) + * + * @param str a string + * @param maxbytes the maximum number of UTF-8 encoded bytes + * @return the beginning of the string, so that it uses at most maxbytes bytes in UTF-8 + * @throws IndexOutOfBoundsException if maxbytes is negative + * + * @hide + */ + public static String truncateStringForUtf8Storage(String str, int maxbytes) { + if (maxbytes < 0) { + throw new IndexOutOfBoundsException(); + } + + int bytes = 0; + for (int i = 0, len = str.length(); i < len; i++) { + char c = str.charAt(i); + if (c < 0x80) { + bytes += 1; + } else if (c < 0x800) { + bytes += 2; + } else if (c < Character.MIN_SURROGATE + || c > Character.MAX_SURROGATE + || str.codePointAt(i) < Character.MIN_SUPPLEMENTARY_CODE_POINT) { + bytes += 3; + } else { + bytes += 4; + i += (bytes > maxbytes) ? 0 : 1; + } + if (bytes > maxbytes) { + return str.substring(0, i); + } + } + return str; + } + + /** * Returns a string containing the tokens joined by delimiters. * diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java index a0fc34923a4d..c4bcfd4cf117 100644 --- a/core/tests/coretests/src/android/text/TextUtilsTest.java +++ b/core/tests/coretests/src/android/text/TextUtilsTest.java @@ -43,6 +43,7 @@ import com.google.android.collect.Lists; import org.junit.Test; import org.junit.runner.RunWith; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Locale; @@ -781,6 +782,81 @@ public class TextUtilsTest { TextUtils.trimToSize("abc", 0); } + @Test + public void truncateStringForUtf8Storage() { + assertEquals("", TextUtils.truncateStringForUtf8Storage("abc", 0)); + + //================ long normal case ================ + StringBuilder builder = new StringBuilder(); + + int n = 50; + for (int i = 0; i < 2 * n; i++) { + builder.append("哈"); + } + String initial = builder.toString(); + String result = TextUtils.truncateStringForUtf8Storage(initial, n); + + // Result should be the beginning of initial + assertTrue(initial.startsWith(result)); + + // Result should take less than n bytes in UTF-8 + assertTrue(result.getBytes(StandardCharsets.UTF_8).length <= n); + + // result + the next codePoint should take strictly more than + // n bytes in UTF-8 + assertTrue(initial.substring(0, initial.offsetByCodePoints(result.length(), 1)) + .getBytes(StandardCharsets.UTF_8).length > n); + + // =================== short normal case ===================== + String s = "sf\u20ACgk\u00E9ls\u00E9fg"; + result = TextUtils.truncateStringForUtf8Storage(s, 100); + assertEquals(s, result); + } + + @Test + public void testTruncateInMiddleOfSurrogate() { + StringBuilder builder = new StringBuilder(); + String beginning = "a"; + builder.append(beginning); + builder.append(Character.toChars(0x1D11E)); + + String result = TextUtils.truncateStringForUtf8Storage(builder.toString(), 3); + + // \u1D11E is a surrogate and needs 4 bytes in UTF-8. beginning == "a" uses + // only 1 bytes in UTF8 + // As we allow only 3 bytes for the whole string, so just 2 for this + // codePoint, there is not enough place and the string will be truncated + // just before it + assertEquals(beginning, result); + } + + @Test + public void testTruncateInMiddleOfChar() { + StringBuilder builder = new StringBuilder(); + String beginning = "a"; + builder.append(beginning); + builder.append(Character.toChars(0x20AC)); + + String result = TextUtils.truncateStringForUtf8Storage(builder.toString(), 3); + + // Like above, \u20AC uses 3 bytes in UTF-8, with "beginning", that makes + // 4 bytes so it is too big and should be truncated + assertEquals(beginning, result); + } + + @Test + public void testTruncateSubString() { + String test = "sdgkl;hjsl;gjhdgkljdfhglkdj"; + String sub = test.substring(10, 20); + String res = TextUtils.truncateStringForUtf8Storage(sub, 255); + assertEquals(sub, res); + } + + @Test(expected = IndexOutOfBoundsException.class) + public void truncateStringForUtf8StorageThrowsExceptionForNegativeSize() { + TextUtils.truncateStringForUtf8Storage("abc", -1); + } + @Test public void length() { assertEquals(0, TextUtils.length(null)); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index a5adf52051b9..b4825153bf0a 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -11487,7 +11487,25 @@ public class TelephonyManager { if (SubscriptionManager.isValidPhoneId(phoneId)) { List newList = updateTelephonyProperty( TelephonyProperties.operator_alpha(), phoneId, name); - TelephonyProperties.operator_alpha(newList); + try { + TelephonyProperties.operator_alpha(newList); + } catch (IllegalArgumentException e) { //property value is longer than the byte limit + Log.e(TAG, "setNetworkOperatorNameForPhone: ", e); + + int numberOfEntries = newList.size(); + int maxOperatorLength = //save 1 byte for joiner " , " + (SystemProperties.PROP_VALUE_MAX - numberOfEntries) / numberOfEntries; + + //examine and truncate every operator and retry + for (int i = 0; i < newList.size(); i++) { + if (newList.get(i) != null) { + newList.set(i, TextUtils + .truncateStringForUtf8Storage(newList.get(i), maxOperatorLength)); + } + } + TelephonyProperties.operator_alpha(newList); + Log.e(TAG, "successfully truncated operator_alpha: " + newList); + } } }