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

Commit 5ca8c986 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "EuiccCard #2 (getAllProfiles)"

parents 0b485b9d c17ddea7
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"));
    }
}