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

Commit 08d3d312 authored by Jeff Davidson's avatar Jeff Davidson
Browse files

Allow carrier-privileged apps to download profiles.

If an app without the privileged permission requests a profile
download, the metadata is fetched from the LPA (which now includes
UiccAccessRules[]). If that profile whitelists the calling app, we
permit the download.

We also check the carrier privileges of the current profile and
require user consent if the current app doesn't have it, to prevent a
silent switch from one carrier to a different one. This check is not
fully implemented yet - the carrier privilege check is using the
profile instead of the metadata, since the SubscriptionManager changes
to store the current profile's metadata are not yet implemented, and
the resolution flows are not yet implemented. These will be addressed
in follow-ups.

Bug: 33075886
Test: Unit tests
Change-Id: Ia3db683932945bfc668f42ebf28a586d7a5805af
parent 2adc3534
Loading
Loading
Loading
Loading
+183 −93
Original line number Diff line number Diff line
@@ -17,15 +17,18 @@ package com.android.internal.telephony.euicc;

import android.Manifest;
import android.annotation.Nullable;
import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.ServiceManager;
import android.service.euicc.DownloadResult;
import android.service.euicc.GetDownloadableSubscriptionMetadataResult;
import android.telephony.TelephonyManager;
import android.telephony.UiccAccessRule;
import android.telephony.euicc.DownloadableSubscription;
import android.telephony.euicc.EuiccManager;
import android.util.Log;
@@ -41,10 +44,19 @@ import java.util.concurrent.atomic.AtomicReference;
public class EuiccController extends IEuiccController.Stub {
    private static final String TAG = "EuiccController";

    // Aliases so line lengths stay short.
    private static final int OK = EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK;
    private static final int RESOLVABLE_ERROR =
            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR;
    private static final int GENERIC_ERROR =
            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;

    private static EuiccController sInstance;

    private final Context mContext;
    private final EuiccConnector mConnector;
    private final AppOpsManager mAppOpsManager;
    private final PackageManager mPackageManager;

    /** Initialize the instance. Should only be called once. */
    public static EuiccController init(Context context) {
@@ -79,6 +91,8 @@ public class EuiccController extends IEuiccController.Stub {
    public EuiccController(Context context, EuiccConnector connector) {
        mContext = context;
        mConnector = connector;
        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
        mPackageManager = context.getPackageManager();
    }

    /**
@@ -105,16 +119,26 @@ public class EuiccController extends IEuiccController.Stub {

    @Override
    public void getDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
            final PendingIntent callbackIntent) {
            PendingIntent callbackIntent) {
        if (!callerCanWriteEmbeddedSubscriptions()) {
            throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get metadata");
        }
        long token = Binder.clearCallingIdentity();
        try {
            final String subscriptionResultKey =
                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION;
            mConnector.getDownloadableSubscriptionMetadata(subscription,
                    new EuiccConnector.GetMetadataCommandCallback() {
            mConnector.getDownloadableSubscriptionMetadata(
                    subscription, new GetMetadataCommandCallback(callbackIntent));
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    class GetMetadataCommandCallback implements EuiccConnector.GetMetadataCommandCallback {
        protected final PendingIntent mCallbackIntent;

        GetMetadataCommandCallback(PendingIntent callbackIntent) {
            mCallbackIntent = callbackIntent;
        }

        @Override
        public void onGetMetadataComplete(
                GetDownloadableSubscriptionMetadataResult result) {
@@ -122,56 +146,130 @@ public class EuiccController extends IEuiccController.Stub {
            final int resultCode;
            switch (result.result) {
                case GetDownloadableSubscriptionMetadataResult.RESULT_OK:
                                    resultCode = EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK;
                                    extrasIntent.putExtra(subscriptionResultKey,
                    resultCode = OK;
                    extrasIntent.putExtra(
                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION,
                            result.subscription);
                    break;
                                case GetDownloadableSubscriptionMetadataResult
                                        .RESULT_MUST_DEACTIVATE_REMOVABLE_SIM:
                                    resultCode = EuiccManager
                                            .EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR;
                case GetDownloadableSubscriptionMetadataResult.RESULT_MUST_DEACTIVATE_REMOVABLE_SIM:
                    resultCode = RESOLVABLE_ERROR;
                    // TODO(b/33075886): Pass through the PendingIntent for the
                    // resolution action.
                    break;
                case GetDownloadableSubscriptionMetadataResult.RESULT_GENERIC_ERROR:
                                    resultCode =
                                            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
                    resultCode = GENERIC_ERROR;
                    extrasIntent.putExtra(
                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
                            result.detailedCode);
                    break;
                default:
                    Log.wtf(TAG, "Unknown result: " + result.result);
                                    resultCode =
                                            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
                    resultCode = GENERIC_ERROR;
                    break;
            }

                            sendResult(callbackIntent, resultCode, extrasIntent);
            sendResult(mCallbackIntent, resultCode, extrasIntent);
        }

        @Override
        public void onEuiccServiceUnavailable() {
                            sendResult(callbackIntent,
                                    EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR,
                                    null /* extrasIntent */);
            sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
        }
                    });
    }

    @Override
    public void downloadSubscription(DownloadableSubscription subscription,
            boolean switchAfterDownload, String callingPackage, PendingIntent callbackIntent) {
        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);

        long token = Binder.clearCallingIdentity();
        try {
            if (callerCanWriteEmbeddedSubscriptions) {
                // With WRITE_EMBEDDED_SUBSCRIPTIONS, we can skip profile-specific permission checks
                // and move straight to the profile download.
                downloadSubscriptionPrivileged(subscription, switchAfterDownload, callbackIntent);
                return;
            }
            // Without WRITE_EMBEDDED_SUBSCRIPTIONS, the caller *must* be whitelisted per the
            // metadata of the profile to be downloaded, so check the metadata first.
            mConnector.getDownloadableSubscriptionMetadata(subscription,
                    new DownloadSubscriptionGetMetadataCommandCallback(
                            switchAfterDownload, callbackIntent, callingPackage));
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    class DownloadSubscriptionGetMetadataCommandCallback extends GetMetadataCommandCallback {
        private final boolean mSwitchAfterDownload;
        private final String mCallingPackage;

        DownloadSubscriptionGetMetadataCommandCallback(
                boolean switchAfterDownload, PendingIntent callbackIntent, String callingPackage) {
            super(callbackIntent);
            mSwitchAfterDownload = switchAfterDownload;
            mCallingPackage = callingPackage;
        }

        @Override
    public void downloadSubscription(DownloadableSubscription subscription,
            boolean switchAfterDownload, final PendingIntent callbackIntent) {
        if (!callerCanWriteEmbeddedSubscriptions()) {
            // TODO(b/33075886): Allow unprivileged carriers who have carrier privileges on the
            // active mSubscription (if any) and the mSubscription to be downloaded.
            throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to download");
        public void onGetMetadataComplete(
                GetDownloadableSubscriptionMetadataResult result) {
            if (result.result != GetDownloadableSubscriptionMetadataResult.RESULT_OK) {
                // Just propagate the error as normal.
                // TODO(b/33075886): Pass through the PendingIntent for the resolution action. We
                // want to retry the parent download operation after resolution, not the get
                // metadata call.
                super.onGetMetadataComplete(result);
                return;
            }
        long token = Binder.clearCallingIdentity();

            DownloadableSubscription subscription = result.subscription;
            UiccAccessRule[] rules = subscription.getAccessRules();
            if (rules == null) {
                Log.e(TAG, "No access rules but caller is unprivileged");
                sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
                return;
            }

            final PackageInfo info;
            try {
                info = mPackageManager.getPackageInfo(
                        mCallingPackage, PackageManager.GET_SIGNATURES);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e(TAG, "Calling package valid but gone");
                sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
                return;
            }

            for (int i = 0; i < rules.length; i++) {
                if (rules[i].getCarrierPrivilegeStatus(info)
                        == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
                    // TODO(b/33075886): For consistency, this should check the privilege rules in
                    // the metadata, not the profile itself.
                    TelephonyManager tm =
                            (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
                    if (tm.checkCarrierPrivilegesForPackage(mCallingPackage)
                            == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
                        // Permission verified - move on to the download.
                        downloadSubscriptionPrivileged(
                                subscription, mSwitchAfterDownload, mCallbackIntent);
                    } else {
                        // Switch might still be permitted, but the user must consent first.
                        // TODO(b/33075886): Pass through the PendingIntent for the resolution
                        // action.
                        sendResult(mCallbackIntent, RESOLVABLE_ERROR, null /* extrasIntent */);
                    }
                    return;
                }
            }
            Log.e(TAG, "Caller is not permitted to download this profile");
            sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
        }
    }

    private void downloadSubscriptionPrivileged(DownloadableSubscription subscription,
            boolean switchAfterDownload, final PendingIntent callbackIntent) {
        mConnector.downloadSubscription(subscription, switchAfterDownload,
                new EuiccConnector.DownloadCommandCallback() {
                    @Override
@@ -180,25 +278,22 @@ public class EuiccController extends IEuiccController.Stub {
                        final int resultCode;
                        switch (result.result) {
                            case DownloadResult.RESULT_OK:
                                    resultCode = EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK;
                                resultCode = OK;
                                break;
                            case DownloadResult.RESULT_MUST_DEACTIVATE_REMOVABLE_SIM:
                                    resultCode = EuiccManager
                                            .EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR;
                                resultCode = RESOLVABLE_ERROR;
                                // TODO(b/33075886): Pass through the PendingIntent for the
                                // resolution action.
                                break;
                            case DownloadResult.RESULT_GENERIC_ERROR:
                                    resultCode =
                                            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
                                resultCode = GENERIC_ERROR;
                                extrasIntent.putExtra(
                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
                                        result.detailedCode);
                                break;
                            default:
                                Log.wtf(TAG, "Unknown result: " + result.result);
                                    resultCode =
                                            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
                                resultCode = GENERIC_ERROR;
                                break;
                        }

@@ -207,14 +302,9 @@ public class EuiccController extends IEuiccController.Stub {

                    @Override
                    public void onEuiccServiceUnavailable() {
                            sendResult(callbackIntent,
                                    EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR,
                                    null /* extrasIntent */);
                        sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
                    }
                });
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    private void sendResult(PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
+111 −13
Original line number Diff line number Diff line
@@ -18,9 +18,12 @@ package com.android.internal.telephony.euicc;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -30,12 +33,16 @@ import android.app.PendingIntent;
import android.content.IIntentReceiver;
import android.content.IIntentSender;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Bundle;
import android.os.RemoteException;
import android.service.euicc.DownloadResult;
import android.service.euicc.GetDownloadableSubscriptionMetadataResult;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.TelephonyManager;
import android.telephony.UiccAccessRule;
import android.telephony.euicc.DownloadableSubscription;
import android.telephony.euicc.EuiccManager;

@@ -51,11 +58,33 @@ import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

@RunWith(AndroidJUnit4.class)
public class EuiccControllerTest extends TelephonyTest {
    private static final DownloadableSubscription SUBSCRIPTION =
            DownloadableSubscription.forActivationCode("abcde");

    private static final String PACKAGE_NAME = "test.package";
    private static final String CARRIER_NAME = "test name";
    private static final byte[] SIGNATURE_BYTES = new byte[] {1, 2, 3, 4, 5};

    private static final DownloadableSubscription SUBSCRIPTION_WITH_METADATA =
            DownloadableSubscription.forActivationCode("abcde");
    static {
        try {
            SUBSCRIPTION_WITH_METADATA.setCarrierName("test name");
            UiccAccessRule rule = new UiccAccessRule(
                    MessageDigest.getInstance("SHA-256").digest(SIGNATURE_BYTES),
                    PACKAGE_NAME,
                    0);
            SUBSCRIPTION_WITH_METADATA.setAccessRules(new UiccAccessRule[] { rule });
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("SHA-256 must exist");
        }
    }

    @Mock private EuiccConnector mMockConnector;
    private EuiccController mController;

@@ -122,17 +151,15 @@ public class EuiccControllerTest extends TelephonyTest {
    @Test
    public void testGetDownloadableSubscriptionMetadata_success() throws Exception {
        setHasWriteEmbeddedPermission(true);
        DownloadableSubscription subscription = DownloadableSubscription.forActivationCode("abcde");
        subscription.setCarrierName("test name");
        GetDownloadableSubscriptionMetadataResult result =
                GetDownloadableSubscriptionMetadataResult.success(subscription);
                GetDownloadableSubscriptionMetadataResult.success(SUBSCRIPTION_WITH_METADATA);
        callGetDownloadableSubscriptionMetadata(SUBSCRIPTION, true /* complete */, result);
        Intent intent = verifyIntentSent(
                EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
        DownloadableSubscription receivedSubscription = intent.getParcelableExtra(
                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION);
        assertNotNull(receivedSubscription);
        assertEquals("test name", receivedSubscription.getCarrierName());
        assertEquals(CARRIER_NAME, receivedSubscription.getCarrierName());
    }

    @Test
@@ -140,7 +167,7 @@ public class EuiccControllerTest extends TelephonyTest {
        setHasWriteEmbeddedPermission(true);
        callDownloadSubscription(
                SUBSCRIPTION, true /* switchAfterDownload */, false /* complete */,
                null /* result */);
                null /* result */, "whatever" /* callingPackage */);
        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR,
                0 /* detailedCode */);
    }
@@ -150,7 +177,7 @@ public class EuiccControllerTest extends TelephonyTest {
        setHasWriteEmbeddedPermission(true);
        DownloadResult result = DownloadResult.genericError(42);
        callDownloadSubscription(SUBSCRIPTION, false /* switchAfterDownload */, true /* complete */,
                result);
                result, "whatever" /* callingPackage */);
        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR,
                42 /* detailedCode */);
    }
@@ -160,12 +187,76 @@ public class EuiccControllerTest extends TelephonyTest {
        setHasWriteEmbeddedPermission(true);
        DownloadResult result = DownloadResult.success();
        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */, true /* complete */,
                result);
                result, "whatever" /* callingPackage */);
        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
    }

    // TODO(b/33075886): Add security tests once carrier privilege checks are implemented.
    // TODO(b/33075886): Add resolvable error tests once resolvable errors are implemented.
    @Test
    public void testDownloadSubscription_noPrivileges_getMetadata_serviceUnavailable()
            throws Exception {
        setHasWriteEmbeddedPermission(false);
        prepareGetDownloadableSubscriptionMetadataCall(false /* complete */, null /* result */);
        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */, true /* complete */,
                DownloadResult.success(), PACKAGE_NAME /* callingPackage */);
        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR,
                0 /* detailedCode */);
        verify(mMockConnector, never()).downloadSubscription(
                Mockito.any(), anyBoolean(), Mockito.any());
    }

    @Test
    public void testDownloadSubscription_noPrivileges_getMetadata_genericError()
            throws Exception {
        setHasWriteEmbeddedPermission(false);
        GetDownloadableSubscriptionMetadataResult result =
                GetDownloadableSubscriptionMetadataResult.genericError(42);
        prepareGetDownloadableSubscriptionMetadataCall(true /* complete */, result);
        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */, true /* complete */,
                DownloadResult.success(), PACKAGE_NAME /* callingPackage */);
        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR,
                42 /* detailedCode */);
        verify(mMockConnector, never()).downloadSubscription(
                Mockito.any(), anyBoolean(), Mockito.any());
    }

    @Test
    public void testDownloadSubscription_noPrivileges_hasCarrierPrivileges() throws Exception {
        setHasWriteEmbeddedPermission(false);
        GetDownloadableSubscriptionMetadataResult result =
                GetDownloadableSubscriptionMetadataResult.success(SUBSCRIPTION_WITH_METADATA);
        prepareGetDownloadableSubscriptionMetadataCall(true /* complete */, result);
        PackageInfo pi = new PackageInfo();
        pi.packageName = PACKAGE_NAME;
        pi.signatures = new Signature[] { new Signature(SIGNATURE_BYTES) };
        when(mPackageManager.getPackageInfo(eq(PACKAGE_NAME), anyInt())).thenReturn(pi);
        // TODO(b/33075886): This should mock the current profile metadata, not privileges.
        when(mTelephonyManager.checkCarrierPrivilegesForPackage(PACKAGE_NAME))
                .thenReturn(TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS);
        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */, true /* complete */,
                DownloadResult.success(), PACKAGE_NAME /* callingPackage */);
        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */);
    }

    @Test
    public void testDownloadSubscription_noPrivileges_noCarrierPrivileges() throws Exception {
        setHasWriteEmbeddedPermission(false);
        GetDownloadableSubscriptionMetadataResult result =
                GetDownloadableSubscriptionMetadataResult.success(SUBSCRIPTION_WITH_METADATA);
        prepareGetDownloadableSubscriptionMetadataCall(true /* complete */, result);
        PackageInfo pi = new PackageInfo();
        pi.packageName = PACKAGE_NAME;
        pi.signatures = new Signature[] { new Signature(new byte[] { 5, 4, 3, 2, 1 }) };
        when(mPackageManager.getPackageInfo(eq(PACKAGE_NAME), anyInt())).thenReturn(pi);
        callDownloadSubscription(SUBSCRIPTION, true /* switchAfterDownload */, true /* complete */,
                DownloadResult.success(), PACKAGE_NAME /* callingPackage */);
        verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR,
                0 /* detailedCode */);
        verify(mTelephonyManager, never()).checkCarrierPrivilegesForPackage(PACKAGE_NAME);
        verify(mMockConnector, never()).downloadSubscription(
                Mockito.any(), anyBoolean(), Mockito.any());
    }

    // TODO(b/33075886): Add resolvable error tests once resolvable errors are implemented

    private void setGetEidPermissions(
            boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges) {
@@ -199,9 +290,8 @@ public class EuiccControllerTest extends TelephonyTest {
        return mController.getEid();
    }

    private void callGetDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
    private void prepareGetDownloadableSubscriptionMetadataCall(
            final boolean complete, final GetDownloadableSubscriptionMetadataResult result) {
        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
        doAnswer(new Answer<Void>() {
            @Override
            public Void answer(InvocationOnMock invocation) throws Exception {
@@ -216,11 +306,18 @@ public class EuiccControllerTest extends TelephonyTest {
        }).when(mMockConnector).getDownloadableSubscriptionMetadata(
                Mockito.<DownloadableSubscription>any(),
                Mockito.<EuiccConnector.GetMetadataCommandCallback>any());
    }

    private void callGetDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
            boolean complete, GetDownloadableSubscriptionMetadataResult result) {
        prepareGetDownloadableSubscriptionMetadataCall(complete, result);
        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
        mController.getDownloadableSubscriptionMetadata(subscription, resultCallback);
    }

    private void callDownloadSubscription(DownloadableSubscription subscription,
            boolean switchAfterDownload, final boolean complete, final DownloadResult result) {
            boolean switchAfterDownload, final boolean complete, final DownloadResult result,
            String callingPackage) {
        PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0);
        doAnswer(new Answer<Void>() {
            @Override
@@ -237,7 +334,8 @@ public class EuiccControllerTest extends TelephonyTest {
                Mockito.<DownloadableSubscription>any(),
                Mockito.eq(switchAfterDownload),
                Mockito.<EuiccConnector.DownloadCommandCallback>any());
        mController.downloadSubscription(subscription, switchAfterDownload, resultCallback);
        mController.downloadSubscription(subscription, switchAfterDownload, callingPackage,
                resultCallback);
    }

    private Intent verifyIntentSent(int resultCode, int detailedCode)