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