Loading core/api/current.txt +2 −14 Original line number Diff line number Diff line Loading @@ -34350,30 +34350,18 @@ package android.provider { public static final class SimPhonebookContract.SimRecords { method @NonNull public static android.net.Uri getContentUri(int, int); method @WorkerThread public static int getEncodedNameLength(@NonNull android.content.ContentResolver, @NonNull String); method @NonNull public static android.net.Uri getItemUri(int, int, int); method @NonNull @WorkerThread public static android.provider.SimPhonebookContract.SimRecords.NameValidationResult validateName(@NonNull android.content.ContentResolver, int, int, @NonNull String); field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2"; field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2"; field public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type"; field public static final int ERROR_NAME_UNSUPPORTED = -1; // 0xffffffff field public static final String NAME = "name"; field public static final String PHONE_NUMBER = "phone_number"; field public static final String RECORD_NUMBER = "record_number"; field public static final String SUBSCRIPTION_ID = "subscription_id"; } public static final class SimPhonebookContract.SimRecords.NameValidationResult implements android.os.Parcelable { ctor public SimPhonebookContract.SimRecords.NameValidationResult(@NonNull String, @NonNull String, int, int); method public int describeContents(); method public int getEncodedLength(); method public int getMaxEncodedLength(); method @NonNull public String getName(); method @NonNull public String getSanitizedName(); method public boolean isSupportedCharacter(int); method public boolean isValid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.provider.SimPhonebookContract.SimRecords.NameValidationResult> CREATOR; } public class SyncStateContract { ctor public SyncStateContract(); } core/api/system-current.txt +0 −14 Original line number Diff line number Diff line Loading @@ -8328,22 +8328,8 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean); } public final class SimPhonebookContract { method @NonNull public static String getEfUriPath(int); field public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid"; } public static final class SimPhonebookContract.ElementaryFiles { field public static final String EF_ADN_PATH_SEGMENT = "adn"; field public static final String EF_FDN_PATH_SEGMENT = "fdn"; field public static final String EF_SDN_PATH_SEGMENT = "sdn"; field public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files"; } public static final class SimPhonebookContract.SimRecords { field public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT"; field public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2"; field public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name"; } public static final class Telephony.Carriers implements android.provider.BaseColumns { core/java/android/provider/SimPhonebookContract.java +45 −146 Original line number Diff line number Diff line Loading @@ -29,11 +29,8 @@ import android.annotation.SystemApi; import android.annotation.WorkerThread; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; Loading Loading @@ -63,7 +60,6 @@ public final class SimPhonebookContract { * * @hide */ @SystemApi public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid"; private SimPhonebookContract() { Loading @@ -76,7 +72,6 @@ public final class SimPhonebookContract { * @hide */ @NonNull @SystemApi public static String getEfUriPath(@ElementaryFiles.EfType int efType) { switch (efType) { case EF_ADN: Loading Loading @@ -122,12 +117,12 @@ public final class SimPhonebookContract { * The name for this record. * * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this * exceeds the maximum supported length or contains unsupported characters. * {@link #validateName(ContentResolver, int, int, String)} )} can be used to * check whether the name is supported. * exceeds the maximum supported length. Use * {@link #getEncodedNameLength(ContentResolver, String)} to check how long the name * will be after encoding. * * @see ElementaryFiles#NAME_MAX_LENGTH * @see #validateName(ContentResolver, int, int, String) ) * @see #getEncodedNameLength(ContentResolver, String) */ public static final String NAME = "name"; /** Loading @@ -149,24 +144,31 @@ public final class SimPhonebookContract { public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2"; /** * The path segment that is appended to {@link #getContentUri(int, int)} which indicates * that the following path segment contains a name to be validated. * Value returned from {@link #getEncodedNameLength(ContentResolver, String)} when the name * length could not be determined because the name could not be encoded. */ public static final int ERROR_NAME_UNSUPPORTED = -1; /** * The method name used to get the encoded length of a value for {@link SimRecords#NAME} * column. * * @hide * @see #validateName(ContentResolver, int, int, String) * @see #getEncodedNameLength(ContentResolver, String) * @see ContentResolver#call(String, String, String, Bundle) */ @SystemApi public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name"; public static final String GET_ENCODED_NAME_LENGTH_METHOD_NAME = "get_encoded_name_length"; /** * The key for a cursor extra that contains the result of a validate name query. * Extra key used for an integer value that contains the length in bytes of an encoded * name. * * @hide * @see #validateName(ContentResolver, int, int, String) * @see #getEncodedNameLength(ContentResolver, String) * @see #GET_ENCODED_NAME_LENGTH_METHOD_NAME */ @SystemApi public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT"; public static final String EXTRA_ENCODED_NAME_LENGTH = "android.provider.extra.ENCODED_NAME_LENGTH"; /** Loading Loading @@ -244,32 +246,34 @@ public final class SimPhonebookContract { } /** * Validates a value that is being provided for the {@link #NAME} column. * Returns the number of bytes required to encode the specified name when it is stored * on the SIM. * * <p>The return value can be used to check if the name is valid. If it is not valid then * inserts and updates to the specified elementary file that use the provided name value * will throw an {@link IllegalArgumentException}. * <p>{@link ElementaryFiles#NAME_MAX_LENGTH} is specified in bytes but the encoded name * may require more than 1 byte per character depending on the characters it contains. So * this method can be used to check whether a name exceeds the max length. * * <p>If the specified SIM or elementary file don't exist then * {@link NameValidationResult#getMaxEncodedLength()} will be zero and * {@link NameValidationResult#isValid()} will return false. * @return the number of bytes required by the encoded name or * {@link #ERROR_NAME_UNSUPPORTED} if the name could not be encoded. * @throws IllegalStateException if the provider fails to return the length. * @see SimRecords#NAME * @see ElementaryFiles#NAME_MAX_LENGTH */ @NonNull @WorkerThread public static NameValidationResult validateName( @NonNull ContentResolver resolver, int subscriptionId, @ElementaryFiles.EfType int efType, @NonNull String name) { Bundle queryArgs = new Bundle(); queryArgs.putString(SimRecords.NAME, name); try (Cursor cursor = resolver.query(buildContentUri(subscriptionId, efType) .appendPath(VALIDATE_NAME_PATH_SEGMENT) .build(), null, queryArgs, null)) { NameValidationResult result = cursor.getExtras() .getParcelable(EXTRA_NAME_VALIDATION_RESULT); return result != null ? result : new NameValidationResult(name, "", 0, 0); public static int getEncodedNameLength( @NonNull ContentResolver resolver, @NonNull String name) { name = Objects.requireNonNull(name); Bundle result = resolver.call(AUTHORITY, GET_ENCODED_NAME_LENGTH_METHOD_NAME, name, null); if (result == null || !result.containsKey(EXTRA_ENCODED_NAME_LENGTH)) { throw new IllegalStateException("Provider malfunction: no length was returned."); } int length = result.getInt(EXTRA_ENCODED_NAME_LENGTH, ERROR_NAME_UNSUPPORTED); if (length < 0 && length != ERROR_NAME_UNSUPPORTED) { throw new IllegalStateException( "Provider malfunction: invalid length was returned."); } return length; } private static Uri.Builder buildContentUri( Loading @@ -281,106 +285,6 @@ public final class SimPhonebookContract { .appendPath(getEfUriPath(efType)); } /** Contains details about the validity of a value provided for the {@link #NAME} column. */ public static final class NameValidationResult implements Parcelable { @NonNull public static final Creator<NameValidationResult> CREATOR = new Creator<NameValidationResult>() { @Override public NameValidationResult createFromParcel(@NonNull Parcel in) { return new NameValidationResult(in); } @NonNull @Override public NameValidationResult[] newArray(int size) { return new NameValidationResult[size]; } }; private final String mName; private final String mSanitizedName; private final int mEncodedLength; private final int mMaxEncodedLength; /** Creates a new instance from the provided values. */ public NameValidationResult(@NonNull String name, @NonNull String sanitizedName, int encodedLength, int maxEncodedLength) { this.mName = Objects.requireNonNull(name); this.mSanitizedName = Objects.requireNonNull(sanitizedName); this.mEncodedLength = encodedLength; this.mMaxEncodedLength = maxEncodedLength; } private NameValidationResult(Parcel in) { this(in.readString(), in.readString(), in.readInt(), in.readInt()); } /** Returns the original name that is being validated. */ @NonNull public String getName() { return mName; } /** * Returns a sanitized copy of the original name with all unsupported characters * replaced with spaces. */ @NonNull public String getSanitizedName() { return mSanitizedName; } /** * Returns whether the original name isValid. * * <p>If this returns false then inserts and updates using the name will throw an * {@link IllegalArgumentException} */ public boolean isValid() { return mMaxEncodedLength > 0 && mEncodedLength <= mMaxEncodedLength && Objects.equals( mName, mSanitizedName); } /** Returns whether the character at the specified position is supported by the SIM. */ public boolean isSupportedCharacter(int position) { return mName.charAt(position) == mSanitizedName.charAt(position); } /** * Returns the number of bytes required to save the name. * * <p>This may be more than the number of characters in the name. */ public int getEncodedLength() { return mEncodedLength; } /** * Returns the maximum number of bytes that are supported for the name. * * @see ElementaryFiles#NAME_MAX_LENGTH */ public int getMaxEncodedLength() { return mMaxEncodedLength; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mName); dest.writeString(mSanitizedName); dest.writeInt(mEncodedLength); dest.writeInt(mMaxEncodedLength); } } } /** Constants for metadata about the elementary files of the SIM cards in the phone. */ Loading Loading @@ -446,13 +350,10 @@ public final class SimPhonebookContract { */ public static final int EF_SDN = 3; /** @hide */ @SystemApi public static final String EF_ADN_PATH_SEGMENT = "adn"; /** @hide */ @SystemApi public static final String EF_FDN_PATH_SEGMENT = "fdn"; /** @hide */ @SystemApi public static final String EF_SDN_PATH_SEGMENT = "sdn"; /** The MIME type of CONTENT_URI providing a directory of ADN-like elementary files. */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file"; Loading @@ -464,7 +365,6 @@ public final class SimPhonebookContract { * * @hide */ @SystemApi public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files"; /** Content URI for the ADN-like elementary files available on the device. */ Loading @@ -480,8 +380,7 @@ public final class SimPhonebookContract { * Returns a content uri for a specific elementary file. * * <p>If a SIM with the specified subscriptionId is not present an exception will be thrown. * If the SIM doesn't support the specified elementary file it will have a zero value for * {@link #MAX_RECORDS}. * If the SIM doesn't support the specified elementary file it will return an empty cursor. */ @NonNull public static Uri getItemUri(int subscriptionId, @EfType int efType) { Loading core/tests/coretests/src/android/provider/SimPhonebookContractTest.java +0 −51 Original line number Diff line number Diff line Loading @@ -16,14 +16,8 @@ package android.provider; import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.assertThrows; import android.content.ContentValues; import android.os.Parcel; import android.provider.SimPhonebookContract.SimRecords.NameValidationResult; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; Loading Loading @@ -71,50 +65,5 @@ public class SimPhonebookContractTest { SimPhonebookContract.ElementaryFiles.EF_ADN, -1) ); } @Test public void nameValidationResult_isValid_validNames() { assertThat(new NameValidationResult("", "", 0, 1).isValid()).isTrue(); assertThat(new NameValidationResult("a", "a", 1, 1).isValid()).isTrue(); assertThat(new NameValidationResult("First Last", "First Last", 10, 10).isValid()).isTrue(); assertThat( new NameValidationResult("First Last", "First Last", 10, 100).isValid()).isTrue(); } @Test public void nameValidationResult_isValid_invalidNames() { assertThat(new NameValidationResult("", "", 0, 0).isValid()).isFalse(); assertThat(new NameValidationResult("ab", "ab", 2, 1).isValid()).isFalse(); NameValidationResult unsupportedCharactersResult = new NameValidationResult("A_b_c", "A b c", 5, 5); assertThat(unsupportedCharactersResult.isValid()).isFalse(); assertThat(unsupportedCharactersResult.isSupportedCharacter(0)).isTrue(); assertThat(unsupportedCharactersResult.isSupportedCharacter(1)).isFalse(); assertThat(unsupportedCharactersResult.isSupportedCharacter(2)).isTrue(); assertThat(unsupportedCharactersResult.isSupportedCharacter(3)).isFalse(); assertThat(unsupportedCharactersResult.isSupportedCharacter(4)).isTrue(); } @Test public void nameValidationResult_parcel() { ContentValues values = new ContentValues(); values.put("name", "Name"); values.put("phone_number", "123"); NameValidationResult result; Parcel parcel = Parcel.obtain(); try { parcel.writeParcelable(new NameValidationResult("name", "sanitized name", 1, 2), 0); parcel.setDataPosition(0); result = parcel.readParcelable(NameValidationResult.class.getClassLoader()); } finally { parcel.recycle(); } assertThat(result.getName()).isEqualTo("name"); assertThat(result.getSanitizedName()).isEqualTo("sanitized name"); assertThat(result.getEncodedLength()).isEqualTo(1); assertThat(result.getMaxEncodedLength()).isEqualTo(2); } } telephony/java/com/android/internal/telephony/uicc/IccUtils.java +41 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.internal.telephony.uicc; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; Loading @@ -28,6 +29,7 @@ import com.android.internal.telephony.GsmAlphabet; import com.android.telephony.Rlog; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.List; /** Loading Loading @@ -259,6 +261,41 @@ public class IccUtils { return ret; } /** * Encodes a string to be formatted like the EF[ADN] alpha identifier. * * <p>See javadoc for {@link #adnStringFieldToString(byte[], int, int)} for more details on * the relevant specs. * * <p>This will attempt to encode using the GSM 7-bit alphabet but will fallback to UCS-2 if * there are characters that are not supported by it. * * @return the encoded string including the prefix byte necessary to identify the encoding. * @see #adnStringFieldToString(byte[], int, int) */ @NonNull public static byte[] stringToAdnStringField(@NonNull String alphaTag) { int septets = GsmAlphabet.countGsmSeptetsUsingTables(alphaTag, false, 0, 0); if (septets != -1) { byte[] ret = new byte[septets]; GsmAlphabet.stringToGsm8BitUnpackedField(alphaTag, ret, 0, ret.length); return ret; } // Strictly speaking UCS-2 disallows surrogate characters but it's much more complicated to // validate that the string contains only valid UCS-2 characters. Since the read path // in most modern software will decode "UCS-2" by treating it as UTF-16 this should be fine // (e.g. the adnStringFieldToString has done this for a long time on Android). Also there's // already a precedent in SMS applications to ignore the UCS-2/UTF-16 distinction. byte[] alphaTagBytes = alphaTag.getBytes(StandardCharsets.UTF_16BE); byte[] ret = new byte[alphaTagBytes.length + 1]; // 0x80 tags the remaining bytes as UCS-2 ret[0] = (byte) 0x80; System.arraycopy(alphaTagBytes, 0, ret, 1, alphaTagBytes.length); return ret; } /** * Decodes a string field that's formatted like the EF[ADN] alpha * identifier Loading Loading
core/api/current.txt +2 −14 Original line number Diff line number Diff line Loading @@ -34350,30 +34350,18 @@ package android.provider { public static final class SimPhonebookContract.SimRecords { method @NonNull public static android.net.Uri getContentUri(int, int); method @WorkerThread public static int getEncodedNameLength(@NonNull android.content.ContentResolver, @NonNull String); method @NonNull public static android.net.Uri getItemUri(int, int, int); method @NonNull @WorkerThread public static android.provider.SimPhonebookContract.SimRecords.NameValidationResult validateName(@NonNull android.content.ContentResolver, int, int, @NonNull String); field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sim-contact_v2"; field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2"; field public static final String ELEMENTARY_FILE_TYPE = "elementary_file_type"; field public static final int ERROR_NAME_UNSUPPORTED = -1; // 0xffffffff field public static final String NAME = "name"; field public static final String PHONE_NUMBER = "phone_number"; field public static final String RECORD_NUMBER = "record_number"; field public static final String SUBSCRIPTION_ID = "subscription_id"; } public static final class SimPhonebookContract.SimRecords.NameValidationResult implements android.os.Parcelable { ctor public SimPhonebookContract.SimRecords.NameValidationResult(@NonNull String, @NonNull String, int, int); method public int describeContents(); method public int getEncodedLength(); method public int getMaxEncodedLength(); method @NonNull public String getName(); method @NonNull public String getSanitizedName(); method public boolean isSupportedCharacter(int); method public boolean isValid(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.provider.SimPhonebookContract.SimRecords.NameValidationResult> CREATOR; } public class SyncStateContract { ctor public SyncStateContract(); }
core/api/system-current.txt +0 −14 Original line number Diff line number Diff line Loading @@ -8328,22 +8328,8 @@ package android.provider { method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean); } public final class SimPhonebookContract { method @NonNull public static String getEfUriPath(int); field public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid"; } public static final class SimPhonebookContract.ElementaryFiles { field public static final String EF_ADN_PATH_SEGMENT = "adn"; field public static final String EF_FDN_PATH_SEGMENT = "fdn"; field public static final String EF_SDN_PATH_SEGMENT = "sdn"; field public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files"; } public static final class SimPhonebookContract.SimRecords { field public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT"; field public static final String QUERY_ARG_PIN2 = "android:query-arg-pin2"; field public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name"; } public static final class Telephony.Carriers implements android.provider.BaseColumns {
core/java/android/provider/SimPhonebookContract.java +45 −146 Original line number Diff line number Diff line Loading @@ -29,11 +29,8 @@ import android.annotation.SystemApi; import android.annotation.WorkerThread; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; Loading Loading @@ -63,7 +60,6 @@ public final class SimPhonebookContract { * * @hide */ @SystemApi public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid"; private SimPhonebookContract() { Loading @@ -76,7 +72,6 @@ public final class SimPhonebookContract { * @hide */ @NonNull @SystemApi public static String getEfUriPath(@ElementaryFiles.EfType int efType) { switch (efType) { case EF_ADN: Loading Loading @@ -122,12 +117,12 @@ public final class SimPhonebookContract { * The name for this record. * * <p>An {@link IllegalArgumentException} will be thrown by insert and update if this * exceeds the maximum supported length or contains unsupported characters. * {@link #validateName(ContentResolver, int, int, String)} )} can be used to * check whether the name is supported. * exceeds the maximum supported length. Use * {@link #getEncodedNameLength(ContentResolver, String)} to check how long the name * will be after encoding. * * @see ElementaryFiles#NAME_MAX_LENGTH * @see #validateName(ContentResolver, int, int, String) ) * @see #getEncodedNameLength(ContentResolver, String) */ public static final String NAME = "name"; /** Loading @@ -149,24 +144,31 @@ public final class SimPhonebookContract { public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-contact_v2"; /** * The path segment that is appended to {@link #getContentUri(int, int)} which indicates * that the following path segment contains a name to be validated. * Value returned from {@link #getEncodedNameLength(ContentResolver, String)} when the name * length could not be determined because the name could not be encoded. */ public static final int ERROR_NAME_UNSUPPORTED = -1; /** * The method name used to get the encoded length of a value for {@link SimRecords#NAME} * column. * * @hide * @see #validateName(ContentResolver, int, int, String) * @see #getEncodedNameLength(ContentResolver, String) * @see ContentResolver#call(String, String, String, Bundle) */ @SystemApi public static final String VALIDATE_NAME_PATH_SEGMENT = "validate_name"; public static final String GET_ENCODED_NAME_LENGTH_METHOD_NAME = "get_encoded_name_length"; /** * The key for a cursor extra that contains the result of a validate name query. * Extra key used for an integer value that contains the length in bytes of an encoded * name. * * @hide * @see #validateName(ContentResolver, int, int, String) * @see #getEncodedNameLength(ContentResolver, String) * @see #GET_ENCODED_NAME_LENGTH_METHOD_NAME */ @SystemApi public static final String EXTRA_NAME_VALIDATION_RESULT = "android.provider.extra.NAME_VALIDATION_RESULT"; public static final String EXTRA_ENCODED_NAME_LENGTH = "android.provider.extra.ENCODED_NAME_LENGTH"; /** Loading Loading @@ -244,32 +246,34 @@ public final class SimPhonebookContract { } /** * Validates a value that is being provided for the {@link #NAME} column. * Returns the number of bytes required to encode the specified name when it is stored * on the SIM. * * <p>The return value can be used to check if the name is valid. If it is not valid then * inserts and updates to the specified elementary file that use the provided name value * will throw an {@link IllegalArgumentException}. * <p>{@link ElementaryFiles#NAME_MAX_LENGTH} is specified in bytes but the encoded name * may require more than 1 byte per character depending on the characters it contains. So * this method can be used to check whether a name exceeds the max length. * * <p>If the specified SIM or elementary file don't exist then * {@link NameValidationResult#getMaxEncodedLength()} will be zero and * {@link NameValidationResult#isValid()} will return false. * @return the number of bytes required by the encoded name or * {@link #ERROR_NAME_UNSUPPORTED} if the name could not be encoded. * @throws IllegalStateException if the provider fails to return the length. * @see SimRecords#NAME * @see ElementaryFiles#NAME_MAX_LENGTH */ @NonNull @WorkerThread public static NameValidationResult validateName( @NonNull ContentResolver resolver, int subscriptionId, @ElementaryFiles.EfType int efType, @NonNull String name) { Bundle queryArgs = new Bundle(); queryArgs.putString(SimRecords.NAME, name); try (Cursor cursor = resolver.query(buildContentUri(subscriptionId, efType) .appendPath(VALIDATE_NAME_PATH_SEGMENT) .build(), null, queryArgs, null)) { NameValidationResult result = cursor.getExtras() .getParcelable(EXTRA_NAME_VALIDATION_RESULT); return result != null ? result : new NameValidationResult(name, "", 0, 0); public static int getEncodedNameLength( @NonNull ContentResolver resolver, @NonNull String name) { name = Objects.requireNonNull(name); Bundle result = resolver.call(AUTHORITY, GET_ENCODED_NAME_LENGTH_METHOD_NAME, name, null); if (result == null || !result.containsKey(EXTRA_ENCODED_NAME_LENGTH)) { throw new IllegalStateException("Provider malfunction: no length was returned."); } int length = result.getInt(EXTRA_ENCODED_NAME_LENGTH, ERROR_NAME_UNSUPPORTED); if (length < 0 && length != ERROR_NAME_UNSUPPORTED) { throw new IllegalStateException( "Provider malfunction: invalid length was returned."); } return length; } private static Uri.Builder buildContentUri( Loading @@ -281,106 +285,6 @@ public final class SimPhonebookContract { .appendPath(getEfUriPath(efType)); } /** Contains details about the validity of a value provided for the {@link #NAME} column. */ public static final class NameValidationResult implements Parcelable { @NonNull public static final Creator<NameValidationResult> CREATOR = new Creator<NameValidationResult>() { @Override public NameValidationResult createFromParcel(@NonNull Parcel in) { return new NameValidationResult(in); } @NonNull @Override public NameValidationResult[] newArray(int size) { return new NameValidationResult[size]; } }; private final String mName; private final String mSanitizedName; private final int mEncodedLength; private final int mMaxEncodedLength; /** Creates a new instance from the provided values. */ public NameValidationResult(@NonNull String name, @NonNull String sanitizedName, int encodedLength, int maxEncodedLength) { this.mName = Objects.requireNonNull(name); this.mSanitizedName = Objects.requireNonNull(sanitizedName); this.mEncodedLength = encodedLength; this.mMaxEncodedLength = maxEncodedLength; } private NameValidationResult(Parcel in) { this(in.readString(), in.readString(), in.readInt(), in.readInt()); } /** Returns the original name that is being validated. */ @NonNull public String getName() { return mName; } /** * Returns a sanitized copy of the original name with all unsupported characters * replaced with spaces. */ @NonNull public String getSanitizedName() { return mSanitizedName; } /** * Returns whether the original name isValid. * * <p>If this returns false then inserts and updates using the name will throw an * {@link IllegalArgumentException} */ public boolean isValid() { return mMaxEncodedLength > 0 && mEncodedLength <= mMaxEncodedLength && Objects.equals( mName, mSanitizedName); } /** Returns whether the character at the specified position is supported by the SIM. */ public boolean isSupportedCharacter(int position) { return mName.charAt(position) == mSanitizedName.charAt(position); } /** * Returns the number of bytes required to save the name. * * <p>This may be more than the number of characters in the name. */ public int getEncodedLength() { return mEncodedLength; } /** * Returns the maximum number of bytes that are supported for the name. * * @see ElementaryFiles#NAME_MAX_LENGTH */ public int getMaxEncodedLength() { return mMaxEncodedLength; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mName); dest.writeString(mSanitizedName); dest.writeInt(mEncodedLength); dest.writeInt(mMaxEncodedLength); } } } /** Constants for metadata about the elementary files of the SIM cards in the phone. */ Loading Loading @@ -446,13 +350,10 @@ public final class SimPhonebookContract { */ public static final int EF_SDN = 3; /** @hide */ @SystemApi public static final String EF_ADN_PATH_SEGMENT = "adn"; /** @hide */ @SystemApi public static final String EF_FDN_PATH_SEGMENT = "fdn"; /** @hide */ @SystemApi public static final String EF_SDN_PATH_SEGMENT = "sdn"; /** The MIME type of CONTENT_URI providing a directory of ADN-like elementary files. */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/sim-elementary-file"; Loading @@ -464,7 +365,6 @@ public final class SimPhonebookContract { * * @hide */ @SystemApi public static final String ELEMENTARY_FILES_PATH_SEGMENT = "elementary_files"; /** Content URI for the ADN-like elementary files available on the device. */ Loading @@ -480,8 +380,7 @@ public final class SimPhonebookContract { * Returns a content uri for a specific elementary file. * * <p>If a SIM with the specified subscriptionId is not present an exception will be thrown. * If the SIM doesn't support the specified elementary file it will have a zero value for * {@link #MAX_RECORDS}. * If the SIM doesn't support the specified elementary file it will return an empty cursor. */ @NonNull public static Uri getItemUri(int subscriptionId, @EfType int efType) { Loading
core/tests/coretests/src/android/provider/SimPhonebookContractTest.java +0 −51 Original line number Diff line number Diff line Loading @@ -16,14 +16,8 @@ package android.provider; import static com.google.common.truth.Truth.assertThat; import static org.testng.Assert.assertThrows; import android.content.ContentValues; import android.os.Parcel; import android.provider.SimPhonebookContract.SimRecords.NameValidationResult; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; Loading Loading @@ -71,50 +65,5 @@ public class SimPhonebookContractTest { SimPhonebookContract.ElementaryFiles.EF_ADN, -1) ); } @Test public void nameValidationResult_isValid_validNames() { assertThat(new NameValidationResult("", "", 0, 1).isValid()).isTrue(); assertThat(new NameValidationResult("a", "a", 1, 1).isValid()).isTrue(); assertThat(new NameValidationResult("First Last", "First Last", 10, 10).isValid()).isTrue(); assertThat( new NameValidationResult("First Last", "First Last", 10, 100).isValid()).isTrue(); } @Test public void nameValidationResult_isValid_invalidNames() { assertThat(new NameValidationResult("", "", 0, 0).isValid()).isFalse(); assertThat(new NameValidationResult("ab", "ab", 2, 1).isValid()).isFalse(); NameValidationResult unsupportedCharactersResult = new NameValidationResult("A_b_c", "A b c", 5, 5); assertThat(unsupportedCharactersResult.isValid()).isFalse(); assertThat(unsupportedCharactersResult.isSupportedCharacter(0)).isTrue(); assertThat(unsupportedCharactersResult.isSupportedCharacter(1)).isFalse(); assertThat(unsupportedCharactersResult.isSupportedCharacter(2)).isTrue(); assertThat(unsupportedCharactersResult.isSupportedCharacter(3)).isFalse(); assertThat(unsupportedCharactersResult.isSupportedCharacter(4)).isTrue(); } @Test public void nameValidationResult_parcel() { ContentValues values = new ContentValues(); values.put("name", "Name"); values.put("phone_number", "123"); NameValidationResult result; Parcel parcel = Parcel.obtain(); try { parcel.writeParcelable(new NameValidationResult("name", "sanitized name", 1, 2), 0); parcel.setDataPosition(0); result = parcel.readParcelable(NameValidationResult.class.getClassLoader()); } finally { parcel.recycle(); } assertThat(result.getName()).isEqualTo("name"); assertThat(result.getSanitizedName()).isEqualTo("sanitized name"); assertThat(result.getEncodedLength()).isEqualTo(1); assertThat(result.getMaxEncodedLength()).isEqualTo(2); } }
telephony/java/com/android/internal/telephony/uicc/IccUtils.java +41 −4 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.internal.telephony.uicc; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; Loading @@ -28,6 +29,7 @@ import com.android.internal.telephony.GsmAlphabet; import com.android.telephony.Rlog; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.List; /** Loading Loading @@ -259,6 +261,41 @@ public class IccUtils { return ret; } /** * Encodes a string to be formatted like the EF[ADN] alpha identifier. * * <p>See javadoc for {@link #adnStringFieldToString(byte[], int, int)} for more details on * the relevant specs. * * <p>This will attempt to encode using the GSM 7-bit alphabet but will fallback to UCS-2 if * there are characters that are not supported by it. * * @return the encoded string including the prefix byte necessary to identify the encoding. * @see #adnStringFieldToString(byte[], int, int) */ @NonNull public static byte[] stringToAdnStringField(@NonNull String alphaTag) { int septets = GsmAlphabet.countGsmSeptetsUsingTables(alphaTag, false, 0, 0); if (septets != -1) { byte[] ret = new byte[septets]; GsmAlphabet.stringToGsm8BitUnpackedField(alphaTag, ret, 0, ret.length); return ret; } // Strictly speaking UCS-2 disallows surrogate characters but it's much more complicated to // validate that the string contains only valid UCS-2 characters. Since the read path // in most modern software will decode "UCS-2" by treating it as UTF-16 this should be fine // (e.g. the adnStringFieldToString has done this for a long time on Android). Also there's // already a precedent in SMS applications to ignore the UCS-2/UTF-16 distinction. byte[] alphaTagBytes = alphaTag.getBytes(StandardCharsets.UTF_16BE); byte[] ret = new byte[alphaTagBytes.length + 1]; // 0x80 tags the remaining bytes as UCS-2 ret[0] = (byte) 0x80; System.arraycopy(alphaTagBytes, 0, ret, 1, alphaTagBytes.length); return ret; } /** * Decodes a string field that's formatted like the EF[ADN] alpha * identifier Loading