/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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.Bundle; import android.os.ServiceManager; import android.provider.Settings; import android.service.euicc.EuiccService; import android.service.euicc.GetDefaultDownloadableSubscriptionListResult; import android.service.euicc.GetDownloadableSubscriptionMetadataResult; import android.service.euicc.GetEuiccProfileInfoListResult; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UiccAccessRule; import android.telephony.euicc.DownloadableSubscription; import android.telephony.euicc.EuiccInfo; import android.telephony.euicc.EuiccManager; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telephony.SubscriptionController; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; /** Backing implementation of {@link android.telephony.euicc.EuiccManager}. */ public class EuiccController extends IEuiccController.Stub { private static final String TAG = "EuiccController"; /** Extra set on resolution intents containing the {@link EuiccOperation}. */ @VisibleForTesting static final String EXTRA_OPERATION = "operation"; // 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 ERROR = EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR; private static EuiccController sInstance; private final Context mContext; private final EuiccConnector mConnector; private final SubscriptionManager mSubscriptionManager; private final AppOpsManager mAppOpsManager; private final PackageManager mPackageManager; /** Initialize the instance. Should only be called once. */ public static EuiccController init(Context context) { synchronized (EuiccController.class) { if (sInstance == null) { sInstance = new EuiccController(context); } else { Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance); } } return sInstance; } /** Get an instance. Assumes one has already been initialized with {@link #init}. */ public static EuiccController get() { if (sInstance == null) { synchronized (EuiccController.class) { if (sInstance == null) { throw new IllegalStateException("get() called before init()"); } } } return sInstance; } private EuiccController(Context context) { this(context, new EuiccConnector(context)); ServiceManager.addService("econtroller", this); } @VisibleForTesting public EuiccController(Context context, EuiccConnector connector) { mContext = context; mConnector = connector; mSubscriptionManager = (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mPackageManager = context.getPackageManager(); } /** * Continue an operation which failed with a user-resolvable error. * *

The implementation here makes a key assumption that the resolutionIntent has not been * tampered with. This is guaranteed because: *

*/ @Override public void continueOperation(Intent resolutionIntent, Bundle resolutionExtras) { if (!callerCanWriteEmbeddedSubscriptions()) { throw new SecurityException( "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to continue operation"); } long token = Binder.clearCallingIdentity(); try { EuiccOperation op = resolutionIntent.getParcelableExtra(EXTRA_OPERATION); if (op == null) { throw new IllegalArgumentException("Invalid resolution intent"); } PendingIntent callbackIntent = resolutionIntent.getParcelableExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT); op.continueOperation(resolutionExtras, callbackIntent); } finally { Binder.restoreCallingIdentity(token); } } /** * Return the EID. * *

For API simplicity, this call blocks until completion; while it requires an IPC to load, * that IPC should generally be fast, and the EID shouldn't be needed in the normal course of * operation. */ @Override public String getEid() { if (!callerCanReadPhoneStatePrivileged() && !callerHasCarrierPrivilegesForActiveSubscription()) { throw new SecurityException( "Must have carrier privileges on active subscription to read EID"); } long token = Binder.clearCallingIdentity(); try { return blockingGetEidFromEuiccService(); } finally { Binder.restoreCallingIdentity(token); } } @Override public void getDownloadableSubscriptionMetadata(DownloadableSubscription subscription, String callingPackage, PendingIntent callbackIntent) { getDownloadableSubscriptionMetadata( subscription, false /* forceDeactivateSim */, callingPackage, callbackIntent); } void getDownloadableSubscriptionMetadata(DownloadableSubscription subscription, boolean forceDeactivateSim, String callingPackage, PendingIntent callbackIntent) { if (!callerCanWriteEmbeddedSubscriptions()) { throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get metadata"); } mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage); long token = Binder.clearCallingIdentity(); try { mConnector.getDownloadableSubscriptionMetadata( subscription, forceDeactivateSim, new GetMetadataCommandCallback( token, subscription, callingPackage, callbackIntent)); } finally { Binder.restoreCallingIdentity(token); } } class GetMetadataCommandCallback implements EuiccConnector.GetMetadataCommandCallback { protected final long mCallingToken; protected final DownloadableSubscription mSubscription; protected final String mCallingPackage; protected final PendingIntent mCallbackIntent; GetMetadataCommandCallback( long callingToken, DownloadableSubscription subscription, String callingPackage, PendingIntent callbackIntent) { mCallingToken = callingToken; mSubscription = subscription; mCallingPackage = callingPackage; mCallbackIntent = callbackIntent; } @Override public void onGetMetadataComplete( GetDownloadableSubscriptionMetadataResult result) { Intent extrasIntent = new Intent(); final int resultCode; switch (result.result) { case EuiccService.RESULT_OK: resultCode = OK; extrasIntent.putExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION, result.subscription); break; case EuiccService.RESULT_MUST_DEACTIVATE_SIM: resultCode = RESOLVABLE_ERROR; addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, mCallingPackage, getOperationForDeactivateSim()); break; default: resultCode = ERROR; extrasIntent.putExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, result.result); break; } sendResult(mCallbackIntent, resultCode, extrasIntent); } @Override public void onEuiccServiceUnavailable() { sendResult(mCallbackIntent, ERROR, null /* extrasIntent */); } protected EuiccOperation getOperationForDeactivateSim() { return EuiccOperation.forGetMetadataDeactivateSim( mCallingToken, mSubscription, mCallingPackage); } } @Override public void downloadSubscription(DownloadableSubscription subscription, boolean switchAfterDownload, String callingPackage, PendingIntent callbackIntent) { downloadSubscription(subscription, switchAfterDownload, callingPackage, false /* forceDeactivateSim */, callbackIntent); } void downloadSubscription(DownloadableSubscription subscription, boolean switchAfterDownload, String callingPackage, boolean forceDeactivateSim, 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(token, subscription, switchAfterDownload, forceDeactivateSim, callingPackage, 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, forceDeactivateSim, new DownloadSubscriptionGetMetadataCommandCallback(token, subscription, switchAfterDownload, callingPackage, forceDeactivateSim, callbackIntent)); } finally { Binder.restoreCallingIdentity(token); } } class DownloadSubscriptionGetMetadataCommandCallback extends GetMetadataCommandCallback { private final boolean mSwitchAfterDownload; private final boolean mForceDeactivateSim; DownloadSubscriptionGetMetadataCommandCallback(long callingToken, DownloadableSubscription subscription, boolean switchAfterDownload, String callingPackage, boolean forceDeactivateSim, PendingIntent callbackIntent) { super(callingToken, subscription, callingPackage, callbackIntent); mSwitchAfterDownload = switchAfterDownload; mForceDeactivateSim = forceDeactivateSim; } @Override public void onGetMetadataComplete( GetDownloadableSubscriptionMetadataResult result) { if (result.result == EuiccService.RESULT_MUST_DEACTIVATE_SIM) { // If we need to deactivate the current SIM to even check permissions, go ahead and // require that the user resolve the stronger permission dialog. Intent extrasIntent = new Intent(); addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES, mCallingPackage, EuiccOperation.forDownloadNoPrivileges( mCallingToken, mSubscription, mSwitchAfterDownload, mCallingPackage)); sendResult(mCallbackIntent, RESOLVABLE_ERROR, extrasIntent); return; } if (result.result != EuiccService.RESULT_OK) { // Just propagate the error as normal. super.onGetMetadataComplete(result); return; } DownloadableSubscription subscription = result.subscription; UiccAccessRule[] rules = subscription.getAccessRules(); if (rules == null) { Log.e(TAG, "No access rules but caller is unprivileged"); sendResult(mCallbackIntent, 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, ERROR, null /* extrasIntent */); return; } for (int i = 0; i < rules.length; i++) { if (rules[i].getCarrierPrivilegeStatus(info) == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { // Caller can download this profile. Now, determine whether the caller can also // manage the current profile; if so, we can perform the download silently; if // not, the user must provide consent. if (canManageActiveSubscription(mCallingPackage)) { downloadSubscriptionPrivileged( mCallingToken, subscription, mSwitchAfterDownload, mForceDeactivateSim, mCallingPackage, mCallbackIntent); return; } // Switch might still be permitted, but the user must consent first. Intent extrasIntent = new Intent(); addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES, mCallingPackage, EuiccOperation.forDownloadNoPrivileges( mCallingToken, subscription, mSwitchAfterDownload, mCallingPackage)); sendResult(mCallbackIntent, RESOLVABLE_ERROR, extrasIntent); return; } } Log.e(TAG, "Caller is not permitted to download this profile"); sendResult(mCallbackIntent, ERROR, null /* extrasIntent */); } @Override protected EuiccOperation getOperationForDeactivateSim() { return EuiccOperation.forDownloadDeactivateSim( mCallingToken, mSubscription, mSwitchAfterDownload, mCallingPackage); } } void downloadSubscriptionPrivileged(final long callingToken, DownloadableSubscription subscription, boolean switchAfterDownload, boolean forceDeactivateSim, final String callingPackage, final PendingIntent callbackIntent) { mConnector.downloadSubscription( subscription, switchAfterDownload, forceDeactivateSim, new EuiccConnector.DownloadCommandCallback() { @Override public void onDownloadComplete(int result) { Intent extrasIntent = new Intent(); final int resultCode; switch (result) { case EuiccService.RESULT_OK: resultCode = OK; // Now that a profile has been successfully downloaded, mark the // eUICC as provisioned so it appears in settings UI as appropriate. Settings.Global.putInt( mContext.getContentResolver(), Settings.Global.EUICC_PROVISIONED, 1); if (!switchAfterDownload) { // Since we're not switching, nothing will trigger a // subscription list refresh on its own, so request one here. refreshSubscriptionsAndSendResult( callbackIntent, resultCode, extrasIntent); return; } break; case EuiccService.RESULT_MUST_DEACTIVATE_SIM: resultCode = RESOLVABLE_ERROR; addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, callingPackage, EuiccOperation.forDownloadDeactivateSim( callingToken, subscription, switchAfterDownload, callingPackage)); 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 */); } }); } /** * Blocking call to {@link EuiccService#onGetEuiccProfileInfoList}. * *

Does not perform permission checks as this is not an exposed API and is only used within * the phone process. */ public GetEuiccProfileInfoListResult blockingGetEuiccProfileInfoList() { final CountDownLatch latch = new CountDownLatch(1); final AtomicReference resultRef = new AtomicReference<>(); mConnector.getEuiccProfileInfoList( new EuiccConnector.GetEuiccProfileInfoListCommandCallback() { @Override public void onListComplete(GetEuiccProfileInfoListResult result) { resultRef.set(result); latch.countDown(); } @Override public void onEuiccServiceUnavailable() { latch.countDown(); } }); try { latch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return resultRef.get(); } @Override public void getDefaultDownloadableSubscriptionList( String callingPackage, PendingIntent callbackIntent) { getDefaultDownloadableSubscriptionList( false /* forceDeactivateSim */, callingPackage, callbackIntent); } void getDefaultDownloadableSubscriptionList( boolean forceDeactivateSim, String callingPackage, PendingIntent callbackIntent) { if (!callerCanWriteEmbeddedSubscriptions()) { throw new SecurityException( "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get default list"); } mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage); long token = Binder.clearCallingIdentity(); try { mConnector.getDefaultDownloadableSubscriptionList( forceDeactivateSim, new GetDefaultListCommandCallback( token, callingPackage, callbackIntent)); } finally { Binder.restoreCallingIdentity(token); } } class GetDefaultListCommandCallback implements EuiccConnector.GetDefaultListCommandCallback { final long mCallingToken; final String mCallingPackage; final PendingIntent mCallbackIntent; GetDefaultListCommandCallback(long callingToken, String callingPackage, PendingIntent callbackIntent) { mCallingToken = callingToken; mCallingPackage = callingPackage; mCallbackIntent = callbackIntent; } @Override public void onGetDefaultListComplete(GetDefaultDownloadableSubscriptionListResult result) { Intent extrasIntent = new Intent(); final int resultCode; switch (result.result) { case EuiccService.RESULT_OK: resultCode = OK; extrasIntent.putExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS, result.subscriptions); break; case EuiccService.RESULT_MUST_DEACTIVATE_SIM: resultCode = RESOLVABLE_ERROR; addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, mCallingPackage, EuiccOperation.forGetDefaultListDeactivateSim( mCallingToken, mCallingPackage)); break; default: resultCode = ERROR; extrasIntent.putExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, result.result); break; } sendResult(mCallbackIntent, resultCode, extrasIntent); } @Override public void onEuiccServiceUnavailable() { sendResult(mCallbackIntent, ERROR, null /* extrasIntent */); } } /** * Return the {@link EuiccInfo}. * *

For API simplicity, this call blocks until completion; while it requires an IPC to load, * that IPC should generally be fast, and this info shouldn't be needed in the normal course of * operation. */ @Override public EuiccInfo getEuiccInfo() { // No permissions required as EuiccInfo is not sensitive. long token = Binder.clearCallingIdentity(); try { return blockingGetEuiccInfoFromEuiccService(); } finally { Binder.restoreCallingIdentity(token); } } @Override public void deleteSubscription(int subscriptionId, String callingPackage, PendingIntent callbackIntent) { boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions(); mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage); long token = Binder.clearCallingIdentity(); try { SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId); if (sub == null) { Log.e(TAG, "Cannot delete nonexistent subscription: " + subscriptionId); sendResult(callbackIntent, ERROR, null /* extrasIntent */); return; } if (!callerCanWriteEmbeddedSubscriptions && !sub.canManageSubscription(mContext, callingPackage)) { Log.e(TAG, "No permissions: " + subscriptionId); sendResult(callbackIntent, ERROR, null /* extrasIntent */); return; } deleteSubscriptionPrivileged(sub.getIccId(), callbackIntent); } finally { Binder.restoreCallingIdentity(token); } } void deleteSubscriptionPrivileged(String iccid, final PendingIntent callbackIntent) { mConnector.deleteSubscription( iccid, new EuiccConnector.DeleteCommandCallback() { @Override public void onDeleteComplete(int result) { Intent extrasIntent = new Intent(); final int resultCode; switch (result) { case EuiccService.RESULT_OK: resultCode = OK; refreshSubscriptionsAndSendResult( callbackIntent, resultCode, extrasIntent); return; 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 */); } }); } @Override public void switchToSubscription(int subscriptionId, String callingPackage, PendingIntent callbackIntent) { switchToSubscription( subscriptionId, false /* forceDeactivateSim */, callingPackage, callbackIntent); } void switchToSubscription(int subscriptionId, boolean forceDeactivateSim, String callingPackage, PendingIntent callbackIntent) { boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions(); mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage); long token = Binder.clearCallingIdentity(); try { if (callerCanWriteEmbeddedSubscriptions) { // Assume that if a privileged caller is calling us, we don't need to prompt the // user about changing carriers, because the caller would only be acting in response // to user action. forceDeactivateSim = true; } final String iccid; if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { // Switch to "no" subscription. Only the system can do this. if (!callerCanWriteEmbeddedSubscriptions) { Log.e(TAG, "Not permitted to switch to empty subscription"); sendResult(callbackIntent, ERROR, null /* extrasIntent */); return; } iccid = null; } else { SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId); if (sub == null) { Log.e(TAG, "Cannot switch to nonexistent subscription: " + subscriptionId); sendResult(callbackIntent, ERROR, null /* extrasIntent */); return; } if (!callerCanWriteEmbeddedSubscriptions && !sub.canManageSubscription(mContext, callingPackage)) { Log.e(TAG, "Not permitted to switch to subscription: " + subscriptionId); sendResult(callbackIntent, ERROR, null /* extrasIntent */); return; } iccid = sub.getIccId(); } if (!callerCanWriteEmbeddedSubscriptions && !canManageActiveSubscription(callingPackage)) { // Switch needs consent. Intent extrasIntent = new Intent(); addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES, callingPackage, EuiccOperation.forSwitchNoPrivileges( token, subscriptionId, callingPackage)); sendResult(callbackIntent, RESOLVABLE_ERROR, extrasIntent); return; } switchToSubscriptionPrivileged(token, subscriptionId, iccid, forceDeactivateSim, callingPackage, callbackIntent); } finally { Binder.restoreCallingIdentity(token); } } void switchToSubscriptionPrivileged(final long callingToken, int subscriptionId, boolean forceDeactivateSim, final String callingPackage, final PendingIntent callbackIntent) { String iccid = null; SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId); if (sub != null) { iccid = sub.getIccId(); } switchToSubscriptionPrivileged(callingToken, subscriptionId, iccid, forceDeactivateSim, callingPackage, callbackIntent); } void switchToSubscriptionPrivileged(final long callingToken, int subscriptionId, @Nullable String iccid, boolean forceDeactivateSim, final String callingPackage, final PendingIntent callbackIntent) { mConnector.switchToSubscription( iccid, forceDeactivateSim, new EuiccConnector.SwitchCommandCallback() { @Override public void onSwitchComplete(int result) { Intent extrasIntent = new Intent(); final int resultCode; switch (result) { case EuiccService.RESULT_OK: resultCode = OK; break; case EuiccService.RESULT_MUST_DEACTIVATE_SIM: resultCode = RESOLVABLE_ERROR; addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, callingPackage, EuiccOperation.forSwitchDeactivateSim( callingToken, subscriptionId, callingPackage)); 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 */); } }); } @Override public void updateSubscriptionNickname(int subscriptionId, String nickname, PendingIntent callbackIntent) { if (!callerCanWriteEmbeddedSubscriptions()) { throw new SecurityException( "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to update nickname"); } long token = Binder.clearCallingIdentity(); try { SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId); if (sub == null) { Log.e(TAG, "Cannot update nickname to nonexistent subscription: " + subscriptionId); sendResult(callbackIntent, ERROR, null /* extrasIntent */); return; } mConnector.updateSubscriptionNickname( sub.getIccId(), nickname, new EuiccConnector.UpdateNicknameCommandCallback() { @Override public void onUpdateNicknameComplete(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); } } @Override public void eraseSubscriptions(PendingIntent callbackIntent) { if (!callerCanWriteEmbeddedSubscriptions()) { throw new SecurityException( "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to erase subscriptions"); } long token = Binder.clearCallingIdentity(); try { mConnector.eraseSubscriptions(new EuiccConnector.EraseCommandCallback() { @Override public void onEraseComplete(int result) { Intent extrasIntent = new Intent(); final int resultCode; switch (result) { case EuiccService.RESULT_OK: resultCode = OK; refreshSubscriptionsAndSendResult( callbackIntent, resultCode, extrasIntent); return; 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); } } @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. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void refreshSubscriptionsAndSendResult( PendingIntent callbackIntent, int resultCode, Intent extrasIntent) { SubscriptionController.getInstance() .requestEmbeddedSubscriptionInfoListRefresh( () -> sendResult(callbackIntent, resultCode, extrasIntent)); } /** Dispatch the given callback intent with the given result code and data. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void sendResult(PendingIntent callbackIntent, int resultCode, Intent extrasIntent) { try { callbackIntent.send(mContext, resultCode, extrasIntent); } catch (PendingIntent.CanceledException e) { // Caller canceled the callback; do nothing. } } /** Add a resolution intent to the given extras intent. */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) public void addResolutionIntent(Intent extrasIntent, String resolutionAction, String callingPackage, EuiccOperation op) { Intent intent = new Intent(EuiccManager.ACTION_RESOLVE_ERROR); intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION, resolutionAction); intent.putExtra(EuiccService.EXTRA_RESOLUTION_CALLING_PACKAGE, callingPackage); intent.putExtra(EXTRA_OPERATION, op); PendingIntent resolutionIntent = PendingIntent.getActivity( mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_ONE_SHOT); extrasIntent.putExtra( EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT, resolutionIntent); } @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Requires DUMP"); final long token = Binder.clearCallingIdentity(); try { mConnector.dump(fd, pw, args); } finally { Binder.restoreCallingIdentity(token); } } @Nullable private SubscriptionInfo getSubscriptionForSubscriptionId(int subscriptionId) { List subs = mSubscriptionManager.getAvailableSubscriptionInfoList(); int subCount = subs.size(); for (int i = 0; i < subCount; i++) { SubscriptionInfo sub = subs.get(i); if (subscriptionId == sub.getSubscriptionId()) { return sub; } } return null; } @Nullable private String blockingGetEidFromEuiccService() { CountDownLatch latch = new CountDownLatch(1); AtomicReference eidRef = new AtomicReference<>(); mConnector.getEid(new EuiccConnector.GetEidCommandCallback() { @Override public void onGetEidComplete(String eid) { eidRef.set(eid); latch.countDown(); } @Override public void onEuiccServiceUnavailable() { latch.countDown(); } }); return awaitResult(latch, eidRef); } @Nullable private EuiccInfo blockingGetEuiccInfoFromEuiccService() { CountDownLatch latch = new CountDownLatch(1); AtomicReference euiccInfoRef = new AtomicReference<>(); mConnector.getEuiccInfo(new EuiccConnector.GetEuiccInfoCommandCallback() { @Override public void onGetEuiccInfoComplete(EuiccInfo euiccInfo) { euiccInfoRef.set(euiccInfo); latch.countDown(); } @Override public void onEuiccServiceUnavailable() { latch.countDown(); } }); return awaitResult(latch, euiccInfoRef); } private static T awaitResult(CountDownLatch latch, AtomicReference resultRef) { try { latch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } return resultRef.get(); } private boolean canManageActiveSubscription(String callingPackage) { // TODO(b/36260308): We should plumb a slot ID through here for multi-SIM devices. List subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList(); if (subInfoList == null) { return false; } int size = subInfoList.size(); for (int subIndex = 0; subIndex < size; subIndex++) { SubscriptionInfo subInfo = subInfoList.get(subIndex); if (subInfo.isEmbedded() && subInfo.canManageSubscription(mContext, callingPackage)) { return true; } } return false; } private boolean callerCanReadPhoneStatePrivileged() { return mContext.checkCallingPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; } private boolean callerCanWriteEmbeddedSubscriptions() { return mContext.checkCallingPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) == PackageManager.PERMISSION_GRANTED; } /** * Returns whether the caller has carrier privileges for the active mSubscription on this eUICC. */ private boolean callerHasCarrierPrivilegesForActiveSubscription() { // TODO(b/36260308): We should plumb a slot ID through here for multi-SIM devices. TelephonyManager tm = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); return tm.hasCarrierPrivileges(); } }