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

Commit 9a90bb9e authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Add UCS-2 support for SimPhonebookContract"

parents 3a865fa8 407164df
Loading
Loading
Loading
Loading
+2 −14
Original line number Diff line number Diff line
@@ -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();
  }
+0 −14
Original line number Diff line number Diff line
@@ -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 {
+45 −146
Original line number Diff line number Diff line
@@ -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;

@@ -63,7 +60,6 @@ public final class SimPhonebookContract {
     *
     * @hide
     */
    @SystemApi
    public static final String SUBSCRIPTION_ID_PATH_SEGMENT = "subid";

    private SimPhonebookContract() {
@@ -76,7 +72,6 @@ public final class SimPhonebookContract {
     * @hide
     */
    @NonNull
    @SystemApi
    public static String getEfUriPath(@ElementaryFiles.EfType int efType) {
        switch (efType) {
            case EF_ADN:
@@ -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";
        /**
@@ -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";


        /**
@@ -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(
@@ -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. */
@@ -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";
@@ -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. */
@@ -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) {
+0 −51
Original line number Diff line number Diff line
@@ -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;
@@ -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);
    }
}
+41 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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;

/**
@@ -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