Loading src/java/com/android/internal/telephony/euicc/EuiccController.java +183 −93 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading Loading @@ -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(); } /** Loading @@ -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) { Loading @@ -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 Loading @@ -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; } Loading @@ -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) { Loading tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java +111 −13 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading @@ -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 */); } Loading @@ -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 */); } Loading @@ -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) { Loading Loading @@ -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 { Loading @@ -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 Loading @@ -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) Loading Loading
src/java/com/android/internal/telephony/euicc/EuiccController.java +183 −93 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading Loading @@ -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(); } /** Loading @@ -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) { Loading @@ -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 Loading @@ -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; } Loading @@ -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) { Loading
tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java +111 −13 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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 Loading @@ -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 */); } Loading @@ -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 */); } Loading @@ -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) { Loading Loading @@ -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 { Loading @@ -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 Loading @@ -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) Loading