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
diff --git a/flags/uicc.aconfig b/flags/uicc.aconfig
index 5b74d1a..c1b860f 100644
--- a/flags/uicc.aconfig
+++ b/flags/uicc.aconfig
@@ -18,10 +18,15 @@
     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"
-}
\ No newline at end of file
+}
+flag {
+    name: "esim_available_memory"
+    namespace: "telephony"
+    description: "This flag controls eSIM available memory feature."
+    bug:"318348580"
+}
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
index c417a34..3c444ef 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccConnector.java
@@ -47,6 +47,7 @@
 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 @@
     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 @@
         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 @@
         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 @@
                                     });
                             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 @@
             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;
diff --git a/src/java/com/android/internal/telephony/euicc/EuiccController.java b/src/java/com/android/internal/telephony/euicc/EuiccController.java
index 400f8be..0069977 100644
--- a/src/java/com/android/internal/telephony/euicc/EuiccController.java
+++ b/src/java/com/android/internal/telephony/euicc/EuiccController.java
@@ -254,6 +254,36 @@
     }
 
     /**
+     * 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.
      *
      * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
@@ -1750,6 +1780,27 @@
         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 @@
                 == 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)
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
index d4850c8..ca4576f 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccConnectorTest.java
@@ -40,6 +40,7 @@
 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 @@
 
     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 {
@@ -136,6 +138,31 @@
     }
 
     @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 */,
                 false /* hasPriority */);
@@ -236,6 +263,47 @@
     }
 
     @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 */,
                 true /* hasPriority */);
@@ -259,6 +327,35 @@
     }
 
     @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.
         prepareEuiccApp(true /* hasPermission */, true /* requiresBindPermission */,
@@ -288,6 +385,39 @@
     }
 
     @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 */,
                 true /* hasPriority */);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
index a493247..d95a046 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java
@@ -134,6 +134,7 @@
     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;
@@ -257,6 +258,73 @@
     }
 
     @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 */);
         callGetOtaStatus(true /* success */, 1 /* status */);
@@ -1495,6 +1563,25 @@
         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 @@
         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