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

Commit 0f42ba37 authored by Rafael Higuera Silva's avatar Rafael Higuera Silva
Browse files

Add APIs for available memory on eUICC.

Add new API flag. And add permissions check for:
READ_PHONE_STATE permission and
READ_PHONE_STATE_PRIVILEGED and carrier privilege.

Bug: 312793756
Test: m, atest EuiccServiceTest EuiccConnectorTest EuiccControllerTest EuiccManagerTest
Change-Id: I622ffb45cc3e98ee362b1406db936c7734d99f64
parent 3dbc338c
Loading
Loading
Loading
Loading
+7 −2
Original line number Diff line number Diff line
@@ -18,10 +18,15 @@ flag {
    description: "This flag controls the visibility of the getCarrierRestrictionStatus in carrierRestrictionRules class."
    bug:"313553044"
}

flag {
    name: "carrier_restriction_rules_enhancement"
    namespace: "telephony"
    description: "This flag controls the new enhancements to the existing carrier restrictions rules"
    bug:"317226653"
}
flag {
    name: "esim_available_memory"
    namespace: "telephony"
    description: "This flag controls eSIM available memory feature."
    bug:"318348580"
}
+33 −0
Original line number Diff line number Diff line
@@ -47,6 +47,7 @@ import android.service.euicc.IDownloadSubscriptionCallback;
import android.service.euicc.IEraseSubscriptionsCallback;
import android.service.euicc.IEuiccService;
import android.service.euicc.IEuiccServiceDumpResultCallback;
import android.service.euicc.IGetAvailableMemoryInBytesCallback;
import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
import android.service.euicc.IGetEidCallback;
@@ -155,6 +156,7 @@ public class EuiccConnector extends StateMachine implements ServiceConnection {
    private static final int CMD_START_OTA_IF_NECESSARY = 112;
    private static final int CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS = 113;
    private static final int CMD_DUMP_EUICC_SERVICE = 114;
    private static final int CMD_GET_AVAILABLE_MEMORY_IN_BYTES = 115;

    private static boolean isEuiccCommand(int what) {
        return what >= CMD_GET_EID;
@@ -208,6 +210,13 @@ public class EuiccConnector extends StateMachine implements ServiceConnection {
        void onGetEidComplete(String eid);
    }

    /** Callback class for {@link #getAvailableMemoryInBytes}. */
    @VisibleForTesting(visibility = PACKAGE)
    public interface GetAvailableMemoryInBytesCommandCallback extends BaseEuiccCommandCallback {
        /** Called when the available memory in bytes lookup has completed. */
        void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes);
    }

    /** Callback class for {@link #getOtaStatus}. */
    @VisibleForTesting(visibility = PACKAGE)
    public interface GetOtaStatusCommandCallback extends BaseEuiccCommandCallback {
@@ -436,6 +445,13 @@ public class EuiccConnector extends StateMachine implements ServiceConnection {
        sendMessage(CMD_GET_EID, cardId, 0 /* arg2 */, callback);
    }

    /** Asynchronously fetch the available memory in bytes. */
    @VisibleForTesting(visibility = PACKAGE)
    public void getAvailableMemoryInBytes(
            int cardId, GetAvailableMemoryInBytesCommandCallback callback) {
        sendMessage(CMD_GET_AVAILABLE_MEMORY_IN_BYTES, cardId, 0 /* arg2 */, callback);
    }

    /** Asynchronously get OTA status. */
    @VisibleForTesting(visibility = PACKAGE)
    public void getOtaStatus(int cardId, GetOtaStatusCommandCallback callback) {
@@ -760,6 +776,22 @@ public class EuiccConnector extends StateMachine implements ServiceConnection {
                                    });
                            break;
                        }
                        case CMD_GET_AVAILABLE_MEMORY_IN_BYTES: {
                            mEuiccService.getAvailableMemoryInBytes(slotId,
                                    new IGetAvailableMemoryInBytesCallback.Stub() {
                                        @Override
                                        public void onSuccess(long availableMemoryInBytes) {
                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
                                                ((GetAvailableMemoryInBytesCommandCallback)
                                                        callback)
                                                        .onGetAvailableMemoryInBytesComplete(
                                                                availableMemoryInBytes);
                                                onCommandEnd(callback);
                                            });
                                        }
                                    });
                            break;
                        }
                        case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: {
                            GetMetadataRequest request = (GetMetadataRequest) message.obj;
                            mEuiccService.getDownloadableSubscriptionMetadata(slotId,
@@ -1036,6 +1068,7 @@ public class EuiccConnector extends StateMachine implements ServiceConnection {
            case CMD_GET_OTA_STATUS:
            case CMD_START_OTA_IF_NECESSARY:
            case CMD_DUMP_EUICC_SERVICE:
            case CMD_GET_AVAILABLE_MEMORY_IN_BYTES:
                return (BaseEuiccCommandCallback) message.obj;
            case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA:
                return ((GetMetadataRequest) message.obj).mCallback;
+56 −0
Original line number Diff line number Diff line
@@ -253,6 +253,36 @@ public class EuiccController extends IEuiccController.Stub {
        }
    }

    /**
     * Return the available memory in bytes of the eUICC.
     *
     * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
     * that IPC should generally be fast, and the available memory shouldn't be needed in the normal
     * course of operation.
     */
    @Override
    public long getAvailableMemoryInBytes(int cardId, String callingPackage) {
        boolean callerCanReadPhoneStatePrivileged = callerCanReadPhoneStatePrivileged();
        boolean callerCanReadPhoneState = callerCanReadPhoneState();
        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
        long token = Binder.clearCallingIdentity();
        try {
            if (!callerCanReadPhoneStatePrivileged
                    && !callerCanReadPhoneState
                    && !canManageSubscriptionOnTargetSim(
                            cardId, callingPackage, false, TelephonyManager.INVALID_PORT_INDEX)) {
                throw new SecurityException(
                        "Must have READ_PHONE_STATE permission or READ_PRIVILEGED_PHONE_STATE"
                            + " permission or carrier privileges to read the available memory for"
                            + "cardId="
                                + cardId);
            }
            return blockingGetAvailableMemoryInBytesFromEuiccService(cardId);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    /**
     * Return the current status of OTA update.
     *
@@ -1750,6 +1780,27 @@ public class EuiccController extends IEuiccController.Stub {
        return awaitResult(latch, eidRef);
    }

    private long blockingGetAvailableMemoryInBytesFromEuiccService(int cardId) {
        CountDownLatch latch = new CountDownLatch(1);
        AtomicReference<Long> memoryRef =
                new AtomicReference<>(EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE);
        mConnector.getAvailableMemoryInBytes(
                cardId,
                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
                    @Override
                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
                        memoryRef.set(availableMemoryInBytes);
                        latch.countDown();
                    }

                    @Override
                    public void onEuiccServiceUnavailable() {
                        latch.countDown();
                    }
                });
        return awaitResult(latch, memoryRef);
    }

    private @OtaStatus int blockingGetOtaStatusFromEuiccService(int cardId) {
        CountDownLatch latch = new CountDownLatch(1);
        AtomicReference<Integer> statusRef =
@@ -1957,6 +2008,11 @@ public class EuiccController extends IEuiccController.Stub {
                == PackageManager.PERMISSION_GRANTED;
    }

    private boolean callerCanReadPhoneState() {
        return mContext.checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE)
                == PackageManager.PERMISSION_GRANTED;
    }

    private boolean callerCanWriteEmbeddedSubscriptions() {
        return mContext.checkCallingOrSelfPermission(
                Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+130 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import android.os.UserHandle;
import android.os.test.TestLooper;
import android.service.euicc.EuiccService;
import android.service.euicc.IEuiccService;
import android.service.euicc.IGetAvailableMemoryInBytesCallback;
import android.service.euicc.IGetEidCallback;

import androidx.test.runner.AndroidJUnit4;
@@ -70,6 +71,7 @@ public class EuiccConnectorTest extends TelephonyTest {

    private static final int CARD_ID = 15;
    private static final int PORT_INDEX = 0;
    private static final long AVAILABLE_MEMORY = 123L;

    @Before
    public void setUp() throws Exception {
@@ -135,6 +137,31 @@ public class EuiccConnectorTest extends TelephonyTest {
        assertTrue(called.get());
    }

    @Test
    public void testInitialState_forAvailableMemory_commandRejected() {
        prepareEuiccApp(
                false /* hasPermission */,
                false /* requiresBindPermission */,
                false /* hasPriority */);
        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
        final AtomicBoolean called = new AtomicBoolean(false);
        mConnector.getAvailableMemoryInBytes(
                CARD_ID,
                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
                    @Override
                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
                        fail("Command should have failed");
                    }

                    @Override
                    public void onEuiccServiceUnavailable() {
                        assertTrue("Callback called twice", called.compareAndSet(false, true));
                    }
                });
        mLooper.dispatchAll();
        assertTrue(called.get());
    }

    @Test
    public void testInitialState_switchCommandRejected() {
        prepareEuiccApp(false /* hasPermission */, false /* requiresBindPermission */,
@@ -235,6 +262,47 @@ public class EuiccConnectorTest extends TelephonyTest {
        assertEquals("ABCDE", eidRef.get());
    }

    @Test
    public void testCommandDispatch_forAvailableMemory_success() throws Exception {
        prepareEuiccApp(
                true /* hasPermission */,
                true /* requiresBindPermission */,
                true /* hasPriority */);
        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
        doAnswer(
                new Answer<Void>() {
                    @Override
                    public Void answer(InvocationOnMock invocation) throws Exception {
                        IGetAvailableMemoryInBytesCallback callback =
                                invocation.getArgument(1);
                        callback.onSuccess(AVAILABLE_MEMORY);
                        return null;
                    }
                })
                .when(mEuiccService)
                .getAvailableMemoryInBytes(
                        anyInt(), Mockito.<IGetAvailableMemoryInBytesCallback>any());
        final AtomicReference<Long> availableMemoryInBytesRef = new AtomicReference<>();
        mConnector.getAvailableMemoryInBytes(
                CARD_ID,
                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
                    @Override
                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
                        if (availableMemoryInBytesRef.get() != null) {
                            fail("Callback called twice");
                        }
                        availableMemoryInBytesRef.set(availableMemoryInBytes);
                    }

                    @Override
                    public void onEuiccServiceUnavailable() {
                        fail("Command should have succeeded");
                    }
                });
        mLooper.dispatchAll();
        assertEquals(AVAILABLE_MEMORY, availableMemoryInBytesRef.get().longValue());
    }

    @Test
    public void testCommandDispatch_remoteException() throws Exception {
        prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
@@ -258,6 +326,35 @@ public class EuiccConnectorTest extends TelephonyTest {
        assertTrue(called.get());
    }

    @Test
    public void testCommandDispatch_forAvailableMemory_remoteException() throws Exception {
        prepareEuiccApp(
                true /* hasPermission */,
                true /* requiresBindPermission */,
                true /* hasPriority */);
        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
        doThrow(new RemoteException("failure"))
                .when(mEuiccService)
                .getAvailableMemoryInBytes(
                        anyInt(), Mockito.<IGetAvailableMemoryInBytesCallback>any());
        final AtomicBoolean called = new AtomicBoolean(false);
        mConnector.getAvailableMemoryInBytes(
                CARD_ID,
                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
                    @Override
                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
                        fail("Command should have failed");
                    }

                    @Override
                    public void onEuiccServiceUnavailable() {
                        assertTrue("Callback called twice", called.compareAndSet(false, true));
                    }
                });
        mLooper.dispatchAll();
        assertTrue(called.get());
    }

    @Test
    public void testCommandDispatch_processDied() throws Exception {
        // Kick off the asynchronous command.
@@ -287,6 +384,39 @@ public class EuiccConnectorTest extends TelephonyTest {
        assertTrue(called.get());
    }

    @Test
    public void testCommandDispatch_forAvailableMemory_processDied() throws Exception {
        // Kick off the asynchronous command.
        prepareEuiccApp(
                true /* hasPermission */,
                true /* requiresBindPermission */,
                true /* hasPriority */);
        mConnector = new EuiccConnector(mContext, mLooper.getLooper());
        final AtomicBoolean called = new AtomicBoolean(false);
        mConnector.getAvailableMemoryInBytes(
                CARD_ID,
                new EuiccConnector.GetAvailableMemoryInBytesCommandCallback() {
                    @Override
                    public void onGetAvailableMemoryInBytesComplete(long availableMemoryInBytes) {
                        fail("Unexpected command success callback");
                    }

                    @Override
                    public void onEuiccServiceUnavailable() {
                        assertTrue("Callback called twice", called.compareAndSet(false, true));
                    }
                });
        mLooper.dispatchAll();
        assertFalse(called.get());

        // Now, pretend the remote process died.
        mConnector.onServiceDisconnected(null /* name */);
        mLooper.dispatchAll();

        // Callback should have been called.
        assertTrue(called.get());
    }

    @Test
    public void testLinger() throws Exception {
        prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
+110 −0
Original line number Diff line number Diff line
@@ -134,6 +134,7 @@ public class EuiccControllerTest extends TelephonyTest {
    private static final String ICC_ID = "54321";
    private static final int CARD_ID = 25;
    private static final int REMOVABLE_CARD_ID = 26;
    private static final long AVAILABLE_MEMORY = 123L;

    // Mocked classes
    private EuiccConnector mMockConnector;
@@ -256,6 +257,73 @@ public class EuiccControllerTest extends TelephonyTest {
                TelephonyManager.UNSUPPORTED_CARD_ID));
    }

    @Test(expected = SecurityException.class)
    public void testGetAvailableMemoryInBytes_noPrivileges() throws Exception {
        setGetAvailableMemoryInBytesPermissions(
                false /* hasPhoneState */,
                false /* hasPhoneStatePrivileged */,
                false /* hasCarrierPrivileges */);
        callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID);
    }

    @Test
    public void testGetAvailableMemoryInBytes_withPhoneState() throws Exception {
        setGetAvailableMemoryInBytesPermissions(
                true /* hasPhoneState */,
                false /* hasPhoneStatePrivileged */,
                false /* hasCarrierPrivileges */);
        assertEquals(
                AVAILABLE_MEMORY,
                callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID));
    }

    @Test
    public void testGetAvailableMemoryInBytes_withPhoneStatePrivileged() throws Exception {
        setGetAvailableMemoryInBytesPermissions(
                false /* hasPhoneState */,
                true /* hasPhoneStatePrivileged */,
                false /* hasCarrierPrivileges */);
        assertEquals(
                AVAILABLE_MEMORY,
                callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID));
    }

    @Test
    public void testGetAvailableMemoryInBytes_withCarrierPrivileges() throws Exception {
        setGetAvailableMemoryInBytesPermissions(
                false /* hasPhoneState */,
                false /* hasPhoneStatePrivileged */,
                true /* hasCarrierPrivileges */);
        assertEquals(
                AVAILABLE_MEMORY,
                callGetAvailableMemoryInBytes(true /* success */, AVAILABLE_MEMORY, CARD_ID));
    }

    @Test
    public void testGetAvailableMemoryInBytes_failure() throws Exception {
        setGetAvailableMemoryInBytesPermissions(
                true /* hasPhoneState */,
                false /* hasPhoneStatePrivileged */,
                false /* hasCarrierPrivileges */);
        assertEquals(
                EuiccManager.EUICC_MEMORY_FIELD_UNAVAILABLE,
                callGetAvailableMemoryInBytes(false /* success */, AVAILABLE_MEMORY, CARD_ID));
    }

    @Test
    public void testGetAvailableMemoryInBytes_unsupportedCardId() throws Exception {
        setGetAvailableMemoryInBytesPermissions(
                false /* hasPhoneState */,
                false /* hasPhoneStatePrivileged */,
                true /* hasCarrierPrivileges */);
        assertEquals(
                AVAILABLE_MEMORY,
                callGetAvailableMemoryInBytes(
                        true /* success */,
                        AVAILABLE_MEMORY,
                        TelephonyManager.UNSUPPORTED_CARD_ID));
    }

    @Test(expected = SecurityException.class)
    public void testGetOtaStatus_noPrivileges() {
        setHasWriteEmbeddedPermission(false /* hasPermission */);
@@ -1495,6 +1563,25 @@ public class EuiccControllerTest extends TelephonyTest {
        setHasCarrierPrivilegesOnActiveSubscription(hasCarrierPrivileges);
    }

    private void setGetAvailableMemoryInBytesPermissions(
            boolean hasPhoneState, boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges)
            throws Exception {
        doReturn(
                        hasPhoneState
                                ? PackageManager.PERMISSION_GRANTED
                                : PackageManager.PERMISSION_DENIED)
                .when(mContext)
                .checkCallingOrSelfPermission(Manifest.permission.READ_PHONE_STATE);
        doReturn(
                        hasPhoneStatePrivileged
                                ? PackageManager.PERMISSION_GRANTED
                                : PackageManager.PERMISSION_DENIED)
                .when(mContext)
                .checkCallingOrSelfPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE);
        when(mTelephonyManager.getPhoneCount()).thenReturn(1);
        setHasCarrierPrivilegesOnActiveSubscription(hasCarrierPrivileges);
    }

    private void setHasWriteEmbeddedPermission(boolean hasPermission) {
        doReturn(hasPermission
                ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED)
@@ -1618,6 +1705,29 @@ public class EuiccControllerTest extends TelephonyTest {
        return mController.getEid(cardId, PACKAGE_NAME);
    }

    private long callGetAvailableMemoryInBytes(
            final boolean success, final long availableMemoryInBytes, int cardId) {
        doAnswer(
                new Answer<Void>() {
                    @Override
                    public Void answer(InvocationOnMock invocation) throws Exception {
                        EuiccConnector.GetAvailableMemoryInBytesCommandCallback cb =
                                invocation.getArgument(1 /* resultCallback */);
                        if (success) {
                            cb.onGetAvailableMemoryInBytesComplete(availableMemoryInBytes);
                        } else {
                            cb.onEuiccServiceUnavailable();
                        }
                        return null;
                    }
                })
                .when(mMockConnector)
                .getAvailableMemoryInBytes(
                        anyInt(),
                        Mockito.<EuiccConnector.GetAvailableMemoryInBytesCommandCallback>any());
        return mController.getAvailableMemoryInBytes(cardId, PACKAGE_NAME);
    }

    private int callGetOtaStatus(final boolean success, final int status) {
        doAnswer(new Answer<Void>() {
            @Override