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