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

Commit c17ddea7 authored by Jun Yin's avatar Jun Yin
Browse files

EuiccCard #2 (getAllProfiles)

EuiccCard extends UiccCard to provide eUICC functionality defined by
GSMA RSP specifications.

This CL adds the implementation for getAllProfiles. Other implementation
of methods are added in following up CLs.

The patch #1 is the existing code and can be compared with the patch #2
to see the actual diff.

Bug: 38206971
Test: unit test
Change-Id: I1253bd235103392d4d8105cc9dd2019876f92eb0
parent 08346c7d
Loading
Loading
Loading
Loading
+4 −13
Original line number Diff line number Diff line
@@ -343,11 +343,11 @@ public class SubscriptionInfoUpdater extends Handler {
        String iccId = mIccId[slotId];
        if (iccId == null) {
            IccRecords records = mPhone[slotId].getIccCard().getIccRecords();
            if (stripIccIdSuffix(records.getFullIccId()) == null) {
            if (IccUtils.stripTrailingFs(records.getFullIccId()) == null) {
                logd("handleSimLocked: IccID null");
                return;
            }
            mIccId[slotId] = stripIccIdSuffix(records.getFullIccId());
            mIccId[slotId] = IccUtils.stripTrailingFs(records.getFullIccId());
        } else {
            logd("NOT Querying IccId its already set sIccid[" + slotId + "]=" + iccId);
        }
@@ -390,11 +390,11 @@ public class SubscriptionInfoUpdater extends Handler {
            logd("handleSimLoaded: IccRecords null");
            return;
        }
        if (stripIccIdSuffix(records.getFullIccId()) == null) {
        if (IccUtils.stripTrailingFs(records.getFullIccId()) == null) {
            logd("handleSimLoaded: IccID null");
            return;
        }
        mIccId[slotId] = stripIccIdSuffix(records.getFullIccId());
        mIccId[slotId] = IccUtils.stripTrailingFs(records.getFullIccId());

        if (isAllIccIdQueryDone()) {
            updateSubscriptionInfoByIccId();
@@ -896,15 +896,6 @@ public class SubscriptionInfoUpdater extends Handler {
        }
    }

    // Remove trailing F's from full hexadecimal IccId, as they should be considered padding
    private String stripIccIdSuffix(String hexIccId) {
        if (hexIccId == null) {
            return null;
        } else {
            return hexIccId.replaceAll("(?i)f*$", "");
        }
    }

    public void dispose() {
        logd("[dispose]");
        mContext.unregisterReceiver(sReceiver);
+137 −1
Original line number Diff line number Diff line
@@ -19,7 +19,10 @@ package com.android.internal.telephony.uicc.euicc;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Handler;
import android.service.carrier.CarrierIdentifier;
import android.service.euicc.EuiccProfileInfo;
import android.telephony.Rlog;
import android.telephony.UiccAccessRule;
import android.telephony.euicc.EuiccCardManager;
import android.telephony.euicc.EuiccNotification;
import android.telephony.euicc.EuiccRulesAuthTable;
@@ -28,6 +31,8 @@ import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.uicc.IccCardStatus;
import com.android.internal.telephony.uicc.IccUtils;
import com.android.internal.telephony.uicc.UiccCard;
import com.android.internal.telephony.uicc.asn1.Asn1Decoder;
import com.android.internal.telephony.uicc.asn1.Asn1Node;
import com.android.internal.telephony.uicc.asn1.InvalidAsn1DataException;
import com.android.internal.telephony.uicc.asn1.TagNotFoundException;
import com.android.internal.telephony.uicc.euicc.apdu.ApduSender;
@@ -36,11 +41,15 @@ import com.android.internal.telephony.uicc.euicc.apdu.RequestProvider;
import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;
import com.android.internal.telephony.uicc.euicc.async.AsyncResultHelper;

import java.util.List;

/**
 * This represents an eUICC card to perform profile management operations asynchronously. This class
 * includes methods defined by different versions of GSMA Spec (SGP.22).
 */
public class EuiccCard extends UiccCard {
    private static final String LOG_TAG = "EuiccCard";

    private static final String ISD_R_AID = "A0000005591010FFFFFFFF8900000100";

    private static final EuiccSpecVersion SGP_2_0 = new EuiccSpecVersion(2, 0, 0);
@@ -88,7 +97,32 @@ public class EuiccCard extends UiccCard {
     * @since 1.1.0 [GSMA SGP.22]
     */
    public void getAllProfiles(AsyncResultCallback<EuiccProfileInfo[]> callback, Handler handler) {
        // TODO: to be implemented.
        sendApdu(
                newRequestProvider((RequestBuilder requestBuilder) ->
                        requestBuilder.addStoreData(Asn1Node.newBuilder(Tags.TAG_GET_PROFILES)
                                .addChildAsBytes(Tags.TAG_TAG_LIST, Tags.EUICC_PROFILE_TAGS)
                                .build().toHex())),
                (byte[] response) -> {
                    List<Asn1Node> profileNodes = new Asn1Decoder(response).nextNode()
                            .getChild(Tags.TAG_CTX_COMP_0).getChildren(Tags.TAG_PROFILE_INFO);
                    int size = profileNodes.size();
                    EuiccProfileInfo[] profiles = new EuiccProfileInfo[size];
                    int profileCount = 0;
                    for (int i = 0; i < size; i++) {
                        Asn1Node profileNode = profileNodes.get(i);
                        if (!profileNode.hasChild(Tags.TAG_ICCID)) {
                            loge("Profile must have an ICCID.");
                            continue;
                        }
                        EuiccProfileInfo.Builder profileBuilder = new EuiccProfileInfo.Builder();
                        buildProfile(profileNode, profileBuilder);

                        EuiccProfileInfo profile = profileBuilder.build();
                        profiles[profileCount++] = profile;
                    }
                    return profiles;
                },
                callback, handler);
    }

    /**
@@ -436,4 +470,106 @@ public class EuiccCard extends UiccCard {
            }
        }, handler);
    }

    private static void buildProfile(Asn1Node profileNode, EuiccProfileInfo.Builder profileBuilder)
            throws TagNotFoundException, InvalidAsn1DataException {
        String strippedIccIdString =
                stripTrailingFs(profileNode.getChild(Tags.TAG_ICCID).asBytes());
        profileBuilder.setIccid(strippedIccIdString);

        if (profileNode.hasChild(Tags.TAG_NICKNAME)) {
            profileBuilder.setNickname(profileNode.getChild(Tags.TAG_NICKNAME).asString());
        }

        if (profileNode.hasChild(Tags.TAG_SERVICE_PROVIDER_NAME)) {
            profileBuilder.setServiceProviderName(
                    profileNode.getChild(Tags.TAG_SERVICE_PROVIDER_NAME).asString());
        }

        if (profileNode.hasChild(Tags.TAG_PROFILE_NAME)) {
            profileBuilder.setProfileName(
                    profileNode.getChild(Tags.TAG_PROFILE_NAME).asString());
        }

        if (profileNode.hasChild(Tags.TAG_OPERATOR_ID)) {
            profileBuilder.setCarrierIdentifier(
                    buildCarrierIdentifier(profileNode.getChild(Tags.TAG_OPERATOR_ID)));
        }

        if (profileNode.hasChild(Tags.TAG_PROFILE_STATE)) {
            // noinspection WrongConstant
            profileBuilder.setState(profileNode.getChild(Tags.TAG_PROFILE_STATE).asInteger());
        } else {
            profileBuilder.setState(EuiccProfileInfo.PROFILE_STATE_DISABLED);
        }

        if (profileNode.hasChild(Tags.TAG_PROFILE_CLASS)) {
            // noinspection WrongConstant
            profileBuilder.setProfileClass(
                    profileNode.getChild(Tags.TAG_PROFILE_CLASS).asInteger());
        } else {
            profileBuilder.setProfileClass(EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL);
        }

        if (profileNode.hasChild(Tags.TAG_PROFILE_POLICY_RULE)) {
            // noinspection WrongConstant
            profileBuilder.setPolicyRules(
                    profileNode.getChild(Tags.TAG_PROFILE_POLICY_RULE).asBits());
        }

        if (profileNode.hasChild(Tags.TAG_CARRIER_PRIVILEGE_RULES)) {
            List<Asn1Node> refArDoNodes = profileNode.getChild(Tags.TAG_CARRIER_PRIVILEGE_RULES)
                    .getChildren(Tags.TAG_REF_AR_DO);
            profileBuilder.setUiccAccessRule(buildUiccAccessRule(refArDoNodes));
        }
    }

    private static CarrierIdentifier buildCarrierIdentifier(Asn1Node node)
            throws InvalidAsn1DataException, TagNotFoundException {
        String gid1 = null;
        if (node.hasChild(Tags.TAG_CTX_1)) {
            gid1 = IccUtils.bytesToHexString(node.getChild(Tags.TAG_CTX_1).asBytes());
        }
        String gid2 = null;
        if (node.hasChild(Tags.TAG_CTX_2)) {
            gid2 = IccUtils.bytesToHexString(node.getChild(Tags.TAG_CTX_2).asBytes());
        }
        return new CarrierIdentifier(node.getChild(Tags.TAG_CTX_0).asBytes(), gid1, gid2);
    }

    @Nullable
    private static UiccAccessRule[] buildUiccAccessRule(List<Asn1Node> nodes)
            throws InvalidAsn1DataException, TagNotFoundException {
        if (nodes.isEmpty()) {
            return null;
        }
        int count = nodes.size();
        UiccAccessRule[] rules = new UiccAccessRule[count];
        for (int i = 0; i < count; i++) {
            Asn1Node node = nodes.get(i);
            Asn1Node refDoNode = node.getChild(Tags.TAG_REF_DO);
            byte[] signature = refDoNode.getChild(Tags.TAG_DEVICE_APP_ID_REF_DO).asBytes();

            String packageName = null;
            if (refDoNode.hasChild(Tags.TAG_PKG_REF_DO)) {
                packageName = refDoNode.getChild(Tags.TAG_PKG_REF_DO).asString();
            }
            long accessType = 0;
            if (node.hasChild(Tags.TAG_AR_DO, Tags.TAG_PERM_AR_DO)) {
                Asn1Node permArDoNode = node.getChild(Tags.TAG_AR_DO, Tags.TAG_PERM_AR_DO);
                accessType = permArDoNode.asRawLong();
            }
            rules[i] = new UiccAccessRule(signature, packageName, accessType);
        }
        return rules;
    }

    /** Strip all the trailing 'F' characters of an iccId. */
    private static String stripTrailingFs(byte[] iccId) {
        return IccUtils.stripTrailingFs(IccUtils.bchToString(iccId, 0, iccId.length));
    }

    private static void loge(String message) {
        Rlog.e(LOG_TAG, message);
    }
}
+182 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony.uicc.euicc;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.os.Handler;
import android.os.HandlerThread;
import android.service.euicc.EuiccProfileInfo;
import android.util.ExceptionUtils;
import android.util.Log;

import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.TelephonyTest;
import com.android.internal.telephony.uicc.IccCardApplicationStatus;
import com.android.internal.telephony.uicc.IccCardStatus;
import com.android.internal.telephony.uicc.euicc.apdu.LogicalChannelMocker;
import com.android.internal.telephony.uicc.euicc.async.AsyncResultCallback;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class EuiccCardTest extends TelephonyTest {
    private static final long WAIT_TIMEOUT_MLLIS = 5000;

    private static class ResultCaptor<T> extends AsyncResultCallback<T> {
        public T result;
        public Throwable exception;

        private CountDownLatch mLatch;

        private ResultCaptor() {
            mLatch = new CountDownLatch(1);
        }

        public void await() {
            try {
                mLatch.await(WAIT_TIMEOUT_MLLIS, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                fail("Execution is interrupted: " + e);
            }
        }

        @Override
        public void onResult(T r) {
            result = r;
            mLatch.countDown();
        }

        @Override
        public void onException(Throwable e) {
            exception = e;
            mLatch.countDown();
        }
    }

    private class UiccCardHandlerThread extends HandlerThread {
        private UiccCardHandlerThread(String name) {
            super(name);
        }

        @Override
        public void onLooperPrepared() {
            mEuiccCard = new EuiccCard(mContextFixture.getTestDouble(), mMockCi, mMockIccCardStatus,
                    0 /* phoneId */);
            mHandler = new Handler(mTestHandlerThread.getLooper());
            setReady(true);
        }
    }

    @Mock
    private CommandsInterface mMockCi;
    @Mock
    private IccCardStatus mMockIccCardStatus;

    private UiccCardHandlerThread mTestHandlerThread;
    private Handler mHandler;

    private EuiccCard mEuiccCard;

    @Before
    public void setUp() throws Exception {
        super.setUp(getClass().getSimpleName());

        mMockIccCardStatus.mApplications = new IccCardApplicationStatus[]{};
        mMockIccCardStatus.mCdmaSubscriptionAppIndex =
                mMockIccCardStatus.mImsSubscriptionAppIndex =
                        mMockIccCardStatus.mGsmUmtsSubscriptionAppIndex = -1;
        mMockIccCardStatus.mCardState = IccCardStatus.CardState.CARDSTATE_PRESENT;

        mTestHandlerThread = new UiccCardHandlerThread(getClass().getSimpleName());
        mTestHandlerThread.start();

        waitUntilReady();
    }

    @After
    public void tearDown() throws Exception {
        mTestHandlerThread.quit();
        super.tearDown();
    }

    private void assertUnexpectedException(Throwable e) {
        if (e != null) {
            fail("Unexpected exception: " + ExceptionUtils.getCompleteMessage(e) + "\n-----\n"
                    + Log.getStackTraceString(e.getCause()) + "-----");
        }
    }

    @Test
    public void testGetAllProfiles() {
        int channel = mockLogicalChannelResponses(
                "BF2D14A012E3105A0A896700000000004523019F7001019000");

        ResultCaptor<EuiccProfileInfo[]> resultCaptor = new ResultCaptor<>();
        mEuiccCard.getAllProfiles(resultCaptor, mHandler);
        resultCaptor.await();

        assertUnexpectedException(resultCaptor.exception);
        EuiccProfileInfo[] profiles = resultCaptor.result;
        assertEquals(1, profiles.length);
        assertEquals("98760000000000543210", profiles[0].getIccid());
        assertEquals(EuiccProfileInfo.PROFILE_STATE_ENABLED, profiles[0].getState());
        verifyStoreData(channel, "BF2D0D5C0B5A909192B79F709599BF76");
    }

    @Test
    public void testFSuffix() {
        // iccID is 987600000000005432FF.
        int channel = mockLogicalChannelResponses(
                "BF2D14A012E3105A0A896700000000004523FF9F7001019000");

        ResultCaptor<EuiccProfileInfo[]> resultCaptor = new ResultCaptor<>();
        mEuiccCard.getAllProfiles(resultCaptor, mHandler);
        resultCaptor.await();

        assertUnexpectedException(resultCaptor.exception);
        EuiccProfileInfo[] profiles = resultCaptor.result;
        assertEquals(1, profiles.length);
        assertEquals("987600000000005432", profiles[0].getIccid());
        assertEquals(EuiccProfileInfo.PROFILE_STATE_ENABLED, profiles[0].getState());
        verifyStoreData(channel, "BF2D0D5C0B5A909192B79F709599BF76");
    }

    private void verifyStoreData(int channel, String command) {
        verify(mMockCi, times(1))
                .iccTransmitApduLogicalChannel(eq(channel), eq(0x80 | channel), eq(0xE2), eq(0x91),
                        eq(0), eq(command.length() / 2), eq(command), any());
    }

    private int mockLogicalChannelResponses(Object... responses) {
        int channel = LogicalChannelMocker.mockOpenLogicalChannelResponse(mMockCi,
                "E00582030200009000");
        LogicalChannelMocker.mockSendToLogicalChannel(mMockCi, channel, responses);
        LogicalChannelMocker.mockCloseLogicalChannel(mMockCi, channel);
        return channel;
    }
}
+11 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.internal.telephony.uicc.euicc.asn1;

import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

import android.test.suitebuilder.annotation.SmallTest;

@@ -241,4 +242,14 @@ public class IccUtilsTest {
        IccUtils.bcdToBytes("12345", output);
        assertArrayEquals(new byte[] {0x21, 0x43, 0x05, 0}, output);
    }

    @SmallTest
    @Test
    public void testStripTrailingFs() {
        assertNull(IccUtils.stripTrailingFs(null));
        assertEquals("", IccUtils.stripTrailingFs(""));
        assertEquals("1234", IccUtils.stripTrailingFs("1234"));
        assertEquals("1234", IccUtils.stripTrailingFs("1234ff"));
        assertEquals("1234", IccUtils.stripTrailingFs("1234FF"));
    }
}