Loading src/java/com/android/internal/telephony/euicc/EuiccConnector.java +30 −0 Original line number Original line Diff line number Diff line Loading @@ -46,6 +46,7 @@ import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback; import android.service.euicc.IGetEidCallback; import android.service.euicc.IGetEidCallback; import android.service.euicc.IGetEuiccInfoCallback; import android.service.euicc.IGetEuiccInfoCallback; import android.service.euicc.IGetEuiccProfileInfoListCallback; import android.service.euicc.IGetEuiccProfileInfoListCallback; import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback; import android.service.euicc.ISwitchToSubscriptionCallback; import android.service.euicc.ISwitchToSubscriptionCallback; import android.service.euicc.IUpdateSubscriptionNicknameCallback; import android.service.euicc.IUpdateSubscriptionNicknameCallback; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager; Loading Loading @@ -130,6 +131,7 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { private static final int CMD_SWITCH_TO_SUBSCRIPTION = 107; private static final int CMD_SWITCH_TO_SUBSCRIPTION = 107; private static final int CMD_UPDATE_SUBSCRIPTION_NICKNAME = 108; private static final int CMD_UPDATE_SUBSCRIPTION_NICKNAME = 108; private static final int CMD_ERASE_SUBSCRIPTIONS = 109; private static final int CMD_ERASE_SUBSCRIPTIONS = 109; private static final int CMD_RETAIN_SUBSCRIPTIONS = 110; private static boolean isEuiccCommand(int what) { private static boolean isEuiccCommand(int what) { return what >= CMD_GET_EID; return what >= CMD_GET_EID; Loading Loading @@ -265,6 +267,13 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { void onEraseComplete(int result); void onEraseComplete(int result); } } /** Callback class for {@link #retainSubscriptions}. */ @VisibleForTesting(visibility = PACKAGE) public interface RetainSubscriptionsCommandCallback extends BaseEuiccCommandCallback { /** Called when the retain command has completed (though it may have failed). */ void onRetainSubscriptionsComplete(int result); } private Context mContext; private Context mContext; private PackageManager mPm; private PackageManager mPm; Loading Loading @@ -432,6 +441,12 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { sendMessage(CMD_ERASE_SUBSCRIPTIONS, callback); sendMessage(CMD_ERASE_SUBSCRIPTIONS, callback); } } /** Asynchronously ensure that all profiles will be retained on the next factory reset. */ @VisibleForTesting(visibility = PACKAGE) public void retainSubscriptions(RetainSubscriptionsCommandCallback callback) { sendMessage(CMD_RETAIN_SUBSCRIPTIONS, callback); } /** /** * State in which no EuiccService is available. * State in which no EuiccService is available. * * Loading Loading @@ -766,6 +781,20 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { }); }); break; break; } } case CMD_RETAIN_SUBSCRIPTIONS: { mEuiccService.retainSubscriptionsForFactoryReset(slotId, new IRetainSubscriptionsForFactoryResetCallback.Stub() { @Override public void onComplete(int result) { sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { ((RetainSubscriptionsCommandCallback) callback) .onRetainSubscriptionsComplete(result); onCommandEnd(callback); }); } }); break; } default: { default: { Log.wtf(TAG, "Unimplemented eUICC command: " + message.what); Log.wtf(TAG, "Unimplemented eUICC command: " + message.what); callback.onEuiccServiceUnavailable(); callback.onEuiccServiceUnavailable(); Loading Loading @@ -807,6 +836,7 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { case CMD_GET_EUICC_PROFILE_INFO_LIST: case CMD_GET_EUICC_PROFILE_INFO_LIST: case CMD_GET_EUICC_INFO: case CMD_GET_EUICC_INFO: case CMD_ERASE_SUBSCRIPTIONS: case CMD_ERASE_SUBSCRIPTIONS: case CMD_RETAIN_SUBSCRIPTIONS: return (BaseEuiccCommandCallback) message.obj; return (BaseEuiccCommandCallback) message.obj; case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: return ((GetMetadataRequest) message.obj).mCallback; return ((GetMetadataRequest) message.obj).mCallback; Loading src/java/com/android/internal/telephony/euicc/EuiccController.java +37 −0 Original line number Original line Diff line number Diff line Loading @@ -808,6 +808,43 @@ public class EuiccController extends IEuiccController.Stub { } } } } @Override public void retainSubscriptionsForFactoryReset(PendingIntent callbackIntent) { mContext.enforceCallingPermission(Manifest.permission.MASTER_CLEAR, "Must have MASTER_CLEAR to retain subscriptions for factory reset"); long token = Binder.clearCallingIdentity(); try { mConnector.retainSubscriptions( new EuiccConnector.RetainSubscriptionsCommandCallback() { @Override public void onRetainSubscriptionsComplete(int result) { Intent extrasIntent = new Intent(); final int resultCode; switch (result) { case EuiccService.RESULT_OK: resultCode = OK; break; default: resultCode = ERROR; extrasIntent.putExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, result); break; } sendResult(callbackIntent, resultCode, extrasIntent); } @Override public void onEuiccServiceUnavailable() { sendResult(callbackIntent, ERROR, null /* extrasIntent */); } }); } finally { Binder.restoreCallingIdentity(token); } } /** Refresh the embedded subscription list and dispatch the given result upon completion. */ /** Refresh the embedded subscription list and dispatch the given result upon completion. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void refreshSubscriptionsAndSendResult( public void refreshSubscriptionsAndSendResult( Loading tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java +54 −0 Original line number Original line Diff line number Diff line Loading @@ -26,7 +26,9 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; Loading Loading @@ -63,6 +65,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Stubber; import java.security.MessageDigest; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException; Loading Loading @@ -722,6 +725,34 @@ public class EuiccControllerTest extends TelephonyTest { assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult); assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult); } } @Test(expected = SecurityException.class) public void testRetainSubscriptionsForFactoryReset_noPrivileges() throws Exception { setHasMasterClearPermission(false); callRetainSubscriptionsForFactoryReset(false /* complete */, 0 /* result */); } @Test public void testRetainSubscriptionsForFactoryReset_serviceUnavailable() throws Exception { setHasMasterClearPermission(true); callRetainSubscriptionsForFactoryReset(false /* complete */, 0 /* result */); verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 0 /* detailedCode */); verify(mMockConnector).retainSubscriptions(any()); } @Test public void testRetainSubscriptionsForFactoryReset_error() throws Exception { setHasMasterClearPermission(true); callRetainSubscriptionsForFactoryReset(true /* complete */, 42 /* result */); verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 42 /* detailedCode */); } @Test public void testRetainSubscriptionsForFactoryReset_success() throws Exception { setHasMasterClearPermission(true); callRetainSubscriptionsForFactoryReset(true /* complete */, EuiccService.RESULT_OK); verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */); } private void setGetEidPermissions( private void setGetEidPermissions( boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges) { boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges) { doReturn(hasPhoneStatePrivileged doReturn(hasPhoneStatePrivileged Loading @@ -738,6 +769,12 @@ public class EuiccControllerTest extends TelephonyTest { .checkCallingPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS); .checkCallingPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS); } } private void setHasMasterClearPermission(boolean hasPermission) { Stubber stubber = hasPermission ? doNothing() : doThrow(new SecurityException()); stubber.when(mContext).enforceCallingPermission( eq(Manifest.permission.MASTER_CLEAR), anyString()); } private void setHasCarrierPrivilegesOnActiveSubscription(boolean hasPrivileges) private void setHasCarrierPrivilegesOnActiveSubscription(boolean hasPrivileges) throws Exception { throws Exception { SubscriptionInfo subInfo = new SubscriptionInfo( SubscriptionInfo subInfo = new SubscriptionInfo( Loading Loading @@ -924,6 +961,23 @@ public class EuiccControllerTest extends TelephonyTest { mController.eraseSubscriptions(resultCallback); mController.eraseSubscriptions(resultCallback); } } private void callRetainSubscriptionsForFactoryReset(final boolean complete, final int result) { PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Exception { EuiccConnector.RetainSubscriptionsCommandCallback cb = invocation.getArgument(0); if (complete) { cb.onRetainSubscriptionsComplete(result); } else { cb.onEuiccServiceUnavailable(); } return null; } }).when(mMockConnector).retainSubscriptions(any()); mController.retainSubscriptionsForFactoryReset(resultCallback); } private void verifyResolutionIntent(String euiccUiAction, @EuiccOperation.Action int action) { private void verifyResolutionIntent(String euiccUiAction, @EuiccOperation.Action int action) { assertEquals(euiccUiAction, mController.mResolutionAction); assertEquals(euiccUiAction, mController.mResolutionAction); assertNotNull(mController.mOp); assertNotNull(mController.mOp); Loading Loading
src/java/com/android/internal/telephony/euicc/EuiccConnector.java +30 −0 Original line number Original line Diff line number Diff line Loading @@ -46,6 +46,7 @@ import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback; import android.service.euicc.IGetEidCallback; import android.service.euicc.IGetEidCallback; import android.service.euicc.IGetEuiccInfoCallback; import android.service.euicc.IGetEuiccInfoCallback; import android.service.euicc.IGetEuiccProfileInfoListCallback; import android.service.euicc.IGetEuiccProfileInfoListCallback; import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback; import android.service.euicc.ISwitchToSubscriptionCallback; import android.service.euicc.ISwitchToSubscriptionCallback; import android.service.euicc.IUpdateSubscriptionNicknameCallback; import android.service.euicc.IUpdateSubscriptionNicknameCallback; import android.telephony.SubscriptionManager; import android.telephony.SubscriptionManager; Loading Loading @@ -130,6 +131,7 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { private static final int CMD_SWITCH_TO_SUBSCRIPTION = 107; private static final int CMD_SWITCH_TO_SUBSCRIPTION = 107; private static final int CMD_UPDATE_SUBSCRIPTION_NICKNAME = 108; private static final int CMD_UPDATE_SUBSCRIPTION_NICKNAME = 108; private static final int CMD_ERASE_SUBSCRIPTIONS = 109; private static final int CMD_ERASE_SUBSCRIPTIONS = 109; private static final int CMD_RETAIN_SUBSCRIPTIONS = 110; private static boolean isEuiccCommand(int what) { private static boolean isEuiccCommand(int what) { return what >= CMD_GET_EID; return what >= CMD_GET_EID; Loading Loading @@ -265,6 +267,13 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { void onEraseComplete(int result); void onEraseComplete(int result); } } /** Callback class for {@link #retainSubscriptions}. */ @VisibleForTesting(visibility = PACKAGE) public interface RetainSubscriptionsCommandCallback extends BaseEuiccCommandCallback { /** Called when the retain command has completed (though it may have failed). */ void onRetainSubscriptionsComplete(int result); } private Context mContext; private Context mContext; private PackageManager mPm; private PackageManager mPm; Loading Loading @@ -432,6 +441,12 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { sendMessage(CMD_ERASE_SUBSCRIPTIONS, callback); sendMessage(CMD_ERASE_SUBSCRIPTIONS, callback); } } /** Asynchronously ensure that all profiles will be retained on the next factory reset. */ @VisibleForTesting(visibility = PACKAGE) public void retainSubscriptions(RetainSubscriptionsCommandCallback callback) { sendMessage(CMD_RETAIN_SUBSCRIPTIONS, callback); } /** /** * State in which no EuiccService is available. * State in which no EuiccService is available. * * Loading Loading @@ -766,6 +781,20 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { }); }); break; break; } } case CMD_RETAIN_SUBSCRIPTIONS: { mEuiccService.retainSubscriptionsForFactoryReset(slotId, new IRetainSubscriptionsForFactoryResetCallback.Stub() { @Override public void onComplete(int result) { sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { ((RetainSubscriptionsCommandCallback) callback) .onRetainSubscriptionsComplete(result); onCommandEnd(callback); }); } }); break; } default: { default: { Log.wtf(TAG, "Unimplemented eUICC command: " + message.what); Log.wtf(TAG, "Unimplemented eUICC command: " + message.what); callback.onEuiccServiceUnavailable(); callback.onEuiccServiceUnavailable(); Loading Loading @@ -807,6 +836,7 @@ public class EuiccConnector extends StateMachine implements ServiceConnection { case CMD_GET_EUICC_PROFILE_INFO_LIST: case CMD_GET_EUICC_PROFILE_INFO_LIST: case CMD_GET_EUICC_INFO: case CMD_GET_EUICC_INFO: case CMD_ERASE_SUBSCRIPTIONS: case CMD_ERASE_SUBSCRIPTIONS: case CMD_RETAIN_SUBSCRIPTIONS: return (BaseEuiccCommandCallback) message.obj; return (BaseEuiccCommandCallback) message.obj; case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: return ((GetMetadataRequest) message.obj).mCallback; return ((GetMetadataRequest) message.obj).mCallback; Loading
src/java/com/android/internal/telephony/euicc/EuiccController.java +37 −0 Original line number Original line Diff line number Diff line Loading @@ -808,6 +808,43 @@ public class EuiccController extends IEuiccController.Stub { } } } } @Override public void retainSubscriptionsForFactoryReset(PendingIntent callbackIntent) { mContext.enforceCallingPermission(Manifest.permission.MASTER_CLEAR, "Must have MASTER_CLEAR to retain subscriptions for factory reset"); long token = Binder.clearCallingIdentity(); try { mConnector.retainSubscriptions( new EuiccConnector.RetainSubscriptionsCommandCallback() { @Override public void onRetainSubscriptionsComplete(int result) { Intent extrasIntent = new Intent(); final int resultCode; switch (result) { case EuiccService.RESULT_OK: resultCode = OK; break; default: resultCode = ERROR; extrasIntent.putExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, result); break; } sendResult(callbackIntent, resultCode, extrasIntent); } @Override public void onEuiccServiceUnavailable() { sendResult(callbackIntent, ERROR, null /* extrasIntent */); } }); } finally { Binder.restoreCallingIdentity(token); } } /** Refresh the embedded subscription list and dispatch the given result upon completion. */ /** Refresh the embedded subscription list and dispatch the given result upon completion. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void refreshSubscriptionsAndSendResult( public void refreshSubscriptionsAndSendResult( Loading
tests/telephonytests/src/com/android/internal/telephony/euicc/EuiccControllerTest.java +54 −0 Original line number Original line Diff line number Diff line Loading @@ -26,7 +26,9 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mockito.Mockito.when; Loading Loading @@ -63,6 +65,7 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer; import org.mockito.stubbing.Stubber; import java.security.MessageDigest; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException; Loading Loading @@ -722,6 +725,34 @@ public class EuiccControllerTest extends TelephonyTest { assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult); assertTrue(mController.mCalledRefreshSubscriptionsAndSendResult); } } @Test(expected = SecurityException.class) public void testRetainSubscriptionsForFactoryReset_noPrivileges() throws Exception { setHasMasterClearPermission(false); callRetainSubscriptionsForFactoryReset(false /* complete */, 0 /* result */); } @Test public void testRetainSubscriptionsForFactoryReset_serviceUnavailable() throws Exception { setHasMasterClearPermission(true); callRetainSubscriptionsForFactoryReset(false /* complete */, 0 /* result */); verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 0 /* detailedCode */); verify(mMockConnector).retainSubscriptions(any()); } @Test public void testRetainSubscriptionsForFactoryReset_error() throws Exception { setHasMasterClearPermission(true); callRetainSubscriptionsForFactoryReset(true /* complete */, 42 /* result */); verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, 42 /* detailedCode */); } @Test public void testRetainSubscriptionsForFactoryReset_success() throws Exception { setHasMasterClearPermission(true); callRetainSubscriptionsForFactoryReset(true /* complete */, EuiccService.RESULT_OK); verifyIntentSent(EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK, 0 /* detailedCode */); } private void setGetEidPermissions( private void setGetEidPermissions( boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges) { boolean hasPhoneStatePrivileged, boolean hasCarrierPrivileges) { doReturn(hasPhoneStatePrivileged doReturn(hasPhoneStatePrivileged Loading @@ -738,6 +769,12 @@ public class EuiccControllerTest extends TelephonyTest { .checkCallingPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS); .checkCallingPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS); } } private void setHasMasterClearPermission(boolean hasPermission) { Stubber stubber = hasPermission ? doNothing() : doThrow(new SecurityException()); stubber.when(mContext).enforceCallingPermission( eq(Manifest.permission.MASTER_CLEAR), anyString()); } private void setHasCarrierPrivilegesOnActiveSubscription(boolean hasPrivileges) private void setHasCarrierPrivilegesOnActiveSubscription(boolean hasPrivileges) throws Exception { throws Exception { SubscriptionInfo subInfo = new SubscriptionInfo( SubscriptionInfo subInfo = new SubscriptionInfo( Loading Loading @@ -924,6 +961,23 @@ public class EuiccControllerTest extends TelephonyTest { mController.eraseSubscriptions(resultCallback); mController.eraseSubscriptions(resultCallback); } } private void callRetainSubscriptionsForFactoryReset(final boolean complete, final int result) { PendingIntent resultCallback = PendingIntent.getBroadcast(mContext, 0, new Intent(), 0); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Exception { EuiccConnector.RetainSubscriptionsCommandCallback cb = invocation.getArgument(0); if (complete) { cb.onRetainSubscriptionsComplete(result); } else { cb.onEuiccServiceUnavailable(); } return null; } }).when(mMockConnector).retainSubscriptions(any()); mController.retainSubscriptionsForFactoryReset(resultCallback); } private void verifyResolutionIntent(String euiccUiAction, @EuiccOperation.Action int action) { private void verifyResolutionIntent(String euiccUiAction, @EuiccOperation.Action int action) { assertEquals(euiccUiAction, mController.mResolutionAction); assertEquals(euiccUiAction, mController.mResolutionAction); assertNotNull(mController.mOp); assertNotNull(mController.mOp); Loading