| /* |
| * 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.Manifest.permission; |
| import android.annotation.Nullable; |
| import android.app.AppOpsManager; |
| import android.app.PendingIntent; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ComponentInfo; |
| 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.DownloadSubscriptionResult; |
| 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.UiccCardInfo; |
| import android.telephony.euicc.DownloadableSubscription; |
| import android.telephony.euicc.EuiccInfo; |
| import android.telephony.euicc.EuiccManager; |
| import android.telephony.euicc.EuiccManager.OtaStatus; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.telephony.SubscriptionController; |
| import com.android.internal.telephony.euicc.EuiccConnector.OtaStatusChangedCallback; |
| |
| 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 final String EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION = |
| EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION; |
| |
| private static EuiccController sInstance; |
| |
| private final Context mContext; |
| private final EuiccConnector mConnector; |
| private final SubscriptionManager mSubscriptionManager; |
| private final TelephonyManager mTelephonyManager; |
| 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); |
| mTelephonyManager = (TelephonyManager) |
| context.getSystemService(Context.TELEPHONY_SERVICE); |
| mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); |
| mPackageManager = context.getPackageManager(); |
| } |
| |
| /** |
| * Continue an operation which failed with a user-resolvable error. |
| * |
| * <p>The implementation here makes a key assumption that the resolutionIntent has not been |
| * tampered with. This is guaranteed because: |
| * <UL> |
| * <LI>The intent is wrapped in a PendingIntent created by the phone process which is created |
| * with {@link #EXTRA_OPERATION} already present. This means that the operation cannot be |
| * overridden on the PendingIntent - a caller can only add new extras. |
| * <LI>The resolution activity is restricted by a privileged permission; unprivileged apps |
| * cannot start it directly. So the PendingIntent is the only way to start it. |
| * </UL> |
| */ |
| @Override |
| public void continueOperation(int cardId, 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(cardId, resolutionExtras, callbackIntent); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| /** |
| * Return the EID. |
| * |
| * <p>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(int cardId, String callingPackage) { |
| boolean callerCanReadPhoneStatePrivileged = callerCanReadPhoneStatePrivileged(); |
| long token = Binder.clearCallingIdentity(); |
| try { |
| if (!callerCanReadPhoneStatePrivileged |
| && !canManageSubscriptionOnTargetSim(cardId, callingPackage)) { |
| throw new SecurityException( |
| "Must have carrier privileges on subscription to read EID for cardId=" |
| + cardId); |
| } |
| |
| return blockingGetEidFromEuiccService(cardId); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| /** |
| * Return the current status of OTA update. |
| * |
| * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load, |
| * that IPC should generally be fast. |
| */ |
| @Override |
| public @OtaStatus int getOtaStatus(int cardId) { |
| if (!callerCanWriteEmbeddedSubscriptions()) { |
| throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get OTA status"); |
| } |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return blockingGetOtaStatusFromEuiccService(cardId); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| /** |
| * Start eUICC OTA update on the default eUICC if current eUICC OS is not the latest one. When |
| * OTA is started or finished, the broadcast {@link EuiccManager#ACTION_OTA_STATUS_CHANGED} will |
| * be sent. |
| * |
| * This function will only be called from phone process and isn't exposed to the other apps. |
| * |
| * (see {@link #startOtaUpdatingIfNecessary(int cardId)}). |
| */ |
| public void startOtaUpdatingIfNecessary() { |
| // TODO(b/120796772) Eventually, we should use startOtaUpdatingIfNecessary(cardId) |
| startOtaUpdatingIfNecessary(mTelephonyManager.getCardIdForDefaultEuicc()); |
| } |
| |
| /** |
| * Start eUICC OTA update on the given eUICC if current eUICC OS is not the latest one. |
| */ |
| public void startOtaUpdatingIfNecessary(int cardId) { |
| mConnector.startOtaIfNecessary(cardId, |
| new OtaStatusChangedCallback() { |
| @Override |
| public void onOtaStatusChanged(int status) { |
| sendOtaStatusChangedBroadcast(); |
| } |
| |
| @Override |
| public void onEuiccServiceUnavailable() {} |
| }); |
| } |
| |
| @Override |
| public void getDownloadableSubscriptionMetadata(int cardId, |
| DownloadableSubscription subscription, String callingPackage, |
| PendingIntent callbackIntent) { |
| getDownloadableSubscriptionMetadata(cardId, |
| subscription, false /* forceDeactivateSim */, callingPackage, callbackIntent); |
| } |
| |
| void getDownloadableSubscriptionMetadata(int cardId, 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(cardId, |
| 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(int cardId, |
| GetDownloadableSubscriptionMetadataResult result) { |
| Intent extrasIntent = new Intent(); |
| final int resultCode; |
| switch (result.getResult()) { |
| case EuiccService.RESULT_OK: |
| resultCode = OK; |
| extrasIntent.putExtra( |
| EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION, |
| result.getDownloadableSubscription()); |
| break; |
| case EuiccService.RESULT_MUST_DEACTIVATE_SIM: |
| resultCode = RESOLVABLE_ERROR; |
| addResolutionIntent(extrasIntent, |
| EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, |
| mCallingPackage, |
| 0 /* resolvableErrors */, |
| false /* confirmationCodeRetried */, |
| getOperationForDeactivateSim(), |
| cardId); |
| break; |
| default: |
| resultCode = ERROR; |
| extrasIntent.putExtra( |
| EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, |
| result.getResult()); |
| 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(int cardId, DownloadableSubscription subscription, |
| boolean switchAfterDownload, String callingPackage, Bundle resolvedBundle, |
| PendingIntent callbackIntent) { |
| downloadSubscription(cardId, subscription, switchAfterDownload, callingPackage, |
| false /* forceDeactivateSim */, resolvedBundle, callbackIntent); |
| } |
| |
| void downloadSubscription(int cardId, DownloadableSubscription subscription, |
| boolean switchAfterDownload, String callingPackage, boolean forceDeactivateSim, |
| Bundle resolvedBundle, 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(cardId, token, subscription, switchAfterDownload, |
| forceDeactivateSim, callingPackage, resolvedBundle, callbackIntent); |
| return; |
| } |
| |
| // Without WRITE_EMBEDDED_SUBSCRIPTIONS, we first check whether the caller can manage |
| // subscription on the target SIM (see comments below). If yes, the caller *must* be |
| // whitelisted per the metadata of the profile to be downloaded, so check the metadata; |
| // If no, ask the user's consent before proceed. |
| // On a multi-active SIM device, if the caller can manage the active subscription on the |
| // target SIM, or there is no active subscription on the target SIM and the caller can |
| // manage any active subscription on other SIMs, we perform the download silently. |
| // Otherwise, the user must provide consent. If it's a single-active SIM device, |
| // determine whether the caller can manage the current profile; if so, we can perform |
| // the download silently; if not, the user must provide consent. |
| if (canManageSubscriptionOnTargetSim(cardId, callingPackage)) { |
| mConnector.getDownloadableSubscriptionMetadata(cardId, subscription, |
| forceDeactivateSim, |
| new DownloadSubscriptionGetMetadataCommandCallback(token, subscription, |
| switchAfterDownload, callingPackage, forceDeactivateSim, |
| callbackIntent, false /* withUserConsent */)); |
| } else { |
| Log.i(TAG, "Caller can't manage subscription on target SIM. " |
| + "Ask user's consent first"); |
| Intent extrasIntent = new Intent(); |
| addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES, |
| callingPackage, |
| 0 /* resolvableErrors */, |
| false /* confirmationCodeRetried */, |
| EuiccOperation.forDownloadNoPrivilegesOrDeactivateSimCheckMetadata(token, |
| subscription, switchAfterDownload, callingPackage), cardId); |
| sendResult(callbackIntent, RESOLVABLE_ERROR, extrasIntent); |
| } |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| class DownloadSubscriptionGetMetadataCommandCallback extends GetMetadataCommandCallback { |
| private final boolean mSwitchAfterDownload; |
| private final boolean mForceDeactivateSim; |
| private final boolean mWithUserConsent; |
| |
| DownloadSubscriptionGetMetadataCommandCallback(long callingToken, |
| DownloadableSubscription subscription, boolean switchAfterDownload, |
| String callingPackage, boolean forceDeactivateSim, |
| PendingIntent callbackIntent, boolean withUserConsent) { |
| super(callingToken, subscription, callingPackage, callbackIntent); |
| mSwitchAfterDownload = switchAfterDownload; |
| mForceDeactivateSim = forceDeactivateSim; |
| mWithUserConsent = withUserConsent; |
| } |
| |
| @Override |
| public void onGetMetadataComplete(int cardId, |
| GetDownloadableSubscriptionMetadataResult result) { |
| DownloadableSubscription subscription = result.getDownloadableSubscription(); |
| if (mWithUserConsent) { |
| // We won't get RESULT_MUST_DEACTIVATE_SIM for the case with user consent. |
| if (result.getResult() != EuiccService.RESULT_OK) { |
| // Just propagate the error as normal. |
| super.onGetMetadataComplete(cardId, result); |
| return; |
| } |
| |
| if (checkCarrierPrivilegeInMetadata(subscription, mCallingPackage)) { |
| // Caller can download this profile. Since we already have the user's consent, |
| // proceed to download. |
| downloadSubscriptionPrivileged(cardId, |
| mCallingToken, subscription, mSwitchAfterDownload, mForceDeactivateSim, |
| mCallingPackage, null /* resolvedBundle */, |
| mCallbackIntent); |
| } else { |
| Log.e(TAG, "Caller does not have carrier privilege in metadata."); |
| sendResult(mCallbackIntent, ERROR, null /* extrasIntent */); |
| } |
| } else { // !mWithUserConsent |
| if (result.getResult() == EuiccService.RESULT_MUST_DEACTIVATE_SIM) { |
| // The caller can manage the target SIM. Ask the user's consent to deactivate |
| // the current SIM. |
| Intent extrasIntent = new Intent(); |
| addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, |
| mCallingPackage, |
| 0 /* resolvableErrors */, |
| false /* confirmationCodeRetried */, |
| EuiccOperation.forDownloadNoPrivilegesOrDeactivateSimCheckMetadata( |
| mCallingToken, mSubscription, mSwitchAfterDownload, |
| mCallingPackage), |
| cardId); |
| sendResult(mCallbackIntent, RESOLVABLE_ERROR, extrasIntent); |
| return; |
| } |
| |
| if (result.getResult() != EuiccService.RESULT_OK) { |
| // Just propagate the error as normal. |
| super.onGetMetadataComplete(cardId, result); |
| return; |
| } |
| |
| if (checkCarrierPrivilegeInMetadata(subscription, mCallingPackage)) { |
| // Caller can download this profile per profile metadata. Also, caller can |
| // manage the subscription on the target SIM, which is already checked. |
| downloadSubscriptionPrivileged(cardId, |
| mCallingToken, subscription, mSwitchAfterDownload, mForceDeactivateSim, |
| mCallingPackage, null /* resolvedBundle */, |
| mCallbackIntent); |
| } else { |
| Log.e(TAG, "Caller is not permitted to download this profile per metadata"); |
| sendResult(mCallbackIntent, ERROR, null /* extrasIntent */); |
| } |
| } |
| } |
| } |
| |
| // Already have user consent. Check metadata first before proceed to download. |
| void downloadSubscriptionPrivilegedCheckMetadata(int cardId, final long callingToken, |
| DownloadableSubscription subscription, boolean switchAfterDownload, |
| boolean forceDeactivateSim, final String callingPackage, Bundle resolvedBundle, |
| final PendingIntent callbackIntent) { |
| mConnector.getDownloadableSubscriptionMetadata(cardId, subscription, forceDeactivateSim, |
| new DownloadSubscriptionGetMetadataCommandCallback(callingToken, subscription, |
| switchAfterDownload, callingPackage, forceDeactivateSim, callbackIntent, |
| true /* withUserConsent */)); |
| } |
| |
| // Continue to download subscription without checking anything. |
| void downloadSubscriptionPrivileged(int cardId, final long callingToken, |
| DownloadableSubscription subscription, boolean switchAfterDownload, |
| boolean forceDeactivateSim, final String callingPackage, Bundle resolvedBundle, |
| final PendingIntent callbackIntent) { |
| mConnector.downloadSubscription( |
| cardId, |
| subscription, |
| switchAfterDownload, |
| forceDeactivateSim, |
| resolvedBundle, |
| new EuiccConnector.DownloadCommandCallback() { |
| @Override |
| public void onDownloadComplete(DownloadSubscriptionResult result) { |
| Intent extrasIntent = new Intent(); |
| final int resultCode; |
| switch (result.getResult()) { |
| 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); |
| extrasIntent.putExtra( |
| EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION, |
| subscription); |
| 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, |
| 0 /* resolvableErrors */, |
| false /* confirmationCodeRetried */, |
| EuiccOperation.forDownloadDeactivateSim( |
| callingToken, subscription, switchAfterDownload, |
| callingPackage), |
| cardId); |
| break; |
| case EuiccService.RESULT_RESOLVABLE_ERRORS: |
| // Same value as the deprecated |
| // {@link EuiccService#RESULT_NEED_CONFIRMATION_CODE}. For the |
| // deprecated case, the resolvableErrors is set as 0 in |
| // EuiccService. |
| resultCode = RESOLVABLE_ERROR; |
| boolean retried = false; |
| if (!TextUtils.isEmpty(subscription.getConfirmationCode())) { |
| retried = true; |
| } |
| if (result.getResolvableErrors() != 0) { |
| addResolutionIntent(extrasIntent, |
| EuiccService.ACTION_RESOLVE_RESOLVABLE_ERRORS, |
| callingPackage, |
| result.getResolvableErrors(), |
| retried, |
| EuiccOperation.forDownloadResolvableErrors( |
| callingToken, subscription, switchAfterDownload, |
| callingPackage, result.getResolvableErrors()), |
| cardId); |
| } else { // Deprecated case |
| addResolutionIntent(extrasIntent, |
| EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE, |
| callingPackage, |
| 0 /* resolvableErrors */, |
| retried /* confirmationCodeRetried */, |
| EuiccOperation.forDownloadConfirmationCode( |
| callingToken, subscription, switchAfterDownload, |
| callingPackage), |
| cardId); |
| } |
| break; |
| default: |
| resultCode = ERROR; |
| extrasIntent.putExtra( |
| EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, |
| result.getResult()); |
| break; |
| } |
| |
| sendResult(callbackIntent, resultCode, extrasIntent); |
| } |
| |
| @Override |
| public void onEuiccServiceUnavailable() { |
| sendResult(callbackIntent, ERROR, null /* extrasIntent */); |
| } |
| }); |
| } |
| |
| /** |
| * Blocking call to {@link EuiccService#onGetEuiccProfileInfoList} of the eUICC with card ID |
| * {@code cardId}. |
| * |
| * <p>Does not perform permission checks as this is not an exposed API and is only used within |
| * the phone process. |
| */ |
| public GetEuiccProfileInfoListResult blockingGetEuiccProfileInfoList(int cardId) { |
| final CountDownLatch latch = new CountDownLatch(1); |
| final AtomicReference<GetEuiccProfileInfoListResult> resultRef = new AtomicReference<>(); |
| mConnector.getEuiccProfileInfoList( |
| cardId, |
| 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) { |
| Log.e(TAG, "blockingGetEuiccInfoFromEuiccService got InterruptedException e: " + e); |
| Thread.currentThread().interrupt(); |
| } |
| return resultRef.get(); |
| } |
| |
| @Override |
| public void getDefaultDownloadableSubscriptionList(int cardId, |
| String callingPackage, PendingIntent callbackIntent) { |
| getDefaultDownloadableSubscriptionList(cardId, |
| false /* forceDeactivateSim */, callingPackage, callbackIntent); |
| } |
| |
| void getDefaultDownloadableSubscriptionList(int cardId, |
| 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(cardId, |
| 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(int cardId, |
| GetDefaultDownloadableSubscriptionListResult result) { |
| Intent extrasIntent = new Intent(); |
| final int resultCode; |
| switch (result.getResult()) { |
| case EuiccService.RESULT_OK: |
| resultCode = OK; |
| List<DownloadableSubscription> list = result.getDownloadableSubscriptions(); |
| if (list != null && list.size() > 0) { |
| extrasIntent.putExtra( |
| EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS, |
| list.toArray(new DownloadableSubscription[list.size()])); |
| } |
| break; |
| case EuiccService.RESULT_MUST_DEACTIVATE_SIM: |
| resultCode = RESOLVABLE_ERROR; |
| addResolutionIntent(extrasIntent, |
| EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM, |
| mCallingPackage, |
| 0 /* resolvableErrors */, |
| false /* confirmationCodeRetried */, |
| EuiccOperation.forGetDefaultListDeactivateSim( |
| mCallingToken, mCallingPackage), |
| cardId); |
| break; |
| default: |
| resultCode = ERROR; |
| extrasIntent.putExtra( |
| EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, |
| result.getResult()); |
| break; |
| } |
| |
| sendResult(mCallbackIntent, resultCode, extrasIntent); |
| } |
| |
| @Override |
| public void onEuiccServiceUnavailable() { |
| sendResult(mCallbackIntent, ERROR, null /* extrasIntent */); |
| } |
| } |
| |
| /** |
| * Return the {@link EuiccInfo}. |
| * |
| * <p>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(int cardId) { |
| // No permissions required as EuiccInfo is not sensitive. |
| long token = Binder.clearCallingIdentity(); |
| try { |
| return blockingGetEuiccInfoFromEuiccService(cardId); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| @Override |
| public void deleteSubscription(int cardId, 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; |
| } |
| |
| // For both single active SIM device and multi-active SIM device, if the caller is |
| // system or the caller manage the target subscription, we let it continue. This is |
| // because deleting subscription won't change status of any other subscriptions. |
| if (!callerCanWriteEmbeddedSubscriptions |
| && !mSubscriptionManager.canManageSubscription(sub, callingPackage)) { |
| Log.e(TAG, "No permissions: " + subscriptionId); |
| sendResult(callbackIntent, ERROR, null /* extrasIntent */); |
| return; |
| } |
| |
| deleteSubscriptionPrivileged(cardId, sub.getIccId(), callbackIntent); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| void deleteSubscriptionPrivileged(int cardId, String iccid, |
| final PendingIntent callbackIntent) { |
| mConnector.deleteSubscription( |
| cardId, |
| 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 cardId, int subscriptionId, String callingPackage, |
| PendingIntent callbackIntent) { |
| switchToSubscription(cardId, |
| subscriptionId, false /* forceDeactivateSim */, callingPackage, callbackIntent); |
| } |
| |
| void switchToSubscription(int cardId, 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; |
| boolean passConsent = false; |
| if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) { |
| if (callerCanWriteEmbeddedSubscriptions |
| || canManageActiveSubscriptionOnTargetSim(cardId, callingPackage)) { |
| passConsent = true; |
| } else { |
| 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 sub: " + subscriptionId); |
| sendResult(callbackIntent, ERROR, null /* extrasIntent */); |
| return; |
| } |
| if (callerCanWriteEmbeddedSubscriptions) { |
| passConsent = true; |
| } else { |
| if (!mSubscriptionManager.canManageSubscription(sub, callingPackage)) { |
| Log.e(TAG, "Not permitted to switch to sub: " + subscriptionId); |
| sendResult(callbackIntent, ERROR, null /* extrasIntent */); |
| return; |
| } |
| |
| if (canManageSubscriptionOnTargetSim(cardId, callingPackage)) { |
| passConsent = true; |
| } |
| } |
| iccid = sub.getIccId(); |
| } |
| |
| if (!passConsent) { |
| // Switch needs consent. |
| Intent extrasIntent = new Intent(); |
| addResolutionIntent(extrasIntent, |
| EuiccService.ACTION_RESOLVE_NO_PRIVILEGES, |
| callingPackage, |
| 0 /* resolvableErrors */, |
| false /* confirmationCodeRetried */, |
| EuiccOperation.forSwitchNoPrivileges( |
| token, subscriptionId, callingPackage), |
| cardId); |
| sendResult(callbackIntent, RESOLVABLE_ERROR, extrasIntent); |
| return; |
| } |
| |
| switchToSubscriptionPrivileged(cardId, token, subscriptionId, iccid, forceDeactivateSim, |
| callingPackage, callbackIntent); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } |
| |
| void switchToSubscriptionPrivileged(int cardId, 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(cardId, callingToken, subscriptionId, iccid, |
| forceDeactivateSim, callingPackage, callbackIntent); |
| } |
| |
| void switchToSubscriptionPrivileged(int cardId, final long callingToken, int subscriptionId, |
| @Nullable String iccid, boolean forceDeactivateSim, final String callingPackage, |
| final PendingIntent callbackIntent) { |
| mConnector.switchToSubscription( |
| cardId, |
| 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, |
| 0 /* resolvableErrors */, |
| false /* confirmationCodeRetried */, |
| EuiccOperation.forSwitchDeactivateSim( |
| callingToken, subscriptionId, callingPackage), |
| cardId); |
| 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 cardId, int subscriptionId, String nickname, |
| 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 update nickname to nonexistent sub: " + subscriptionId); |
| sendResult(callbackIntent, ERROR, null /* extrasIntent */); |
| return; |
| } |
| |
| // For both single active SIM device and multi-active SIM device, if the caller is |
| // system or the caller can manage the target subscription, we let it continue. This is |
| // because updating subscription nickname won't affect any other subscriptions. |
| if (!callerCanWriteEmbeddedSubscriptions |
| && !mSubscriptionManager.canManageSubscription(sub, callingPackage)) { |
| Log.e(TAG, "No permissions: " + subscriptionId); |
| sendResult(callbackIntent, ERROR, null /* extrasIntent */); |
| return; |
| } |
| |
| mConnector.updateSubscriptionNickname(cardId, |
| 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; |
| 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 eraseSubscriptions(int cardId, PendingIntent callbackIntent) { |
| if (!callerCanWriteEmbeddedSubscriptions()) { |
| throw new SecurityException( |
| "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to erase subscriptions"); |
| } |
| long token = Binder.clearCallingIdentity(); |
| try { |
| mConnector.eraseSubscriptions(cardId, 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(int cardId, 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(cardId, |
| 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, int resolvableErrors, boolean confirmationCodeRetried, |
| EuiccOperation op, int cardId) { |
| 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(EuiccService.EXTRA_RESOLVABLE_ERRORS, resolvableErrors); |
| intent.putExtra(EuiccService.EXTRA_RESOLUTION_CARD_ID, cardId); |
| intent.putExtra(EuiccService.EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED, |
| confirmationCodeRetried); |
| 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); |
| } |
| } |
| |
| /** |
| * Send broadcast {@link EuiccManager#ACTION_OTA_STATUS_CHANGED} for OTA status |
| * changed. |
| */ |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE) |
| public void sendOtaStatusChangedBroadcast() { |
| Intent intent = new Intent(EuiccManager.ACTION_OTA_STATUS_CHANGED); |
| ComponentInfo bestComponent = mConnector.findBestComponent(mContext.getPackageManager()); |
| if (bestComponent != null) { |
| intent.setPackage(bestComponent.packageName); |
| } |
| mContext.sendBroadcast(intent, permission.WRITE_EMBEDDED_SUBSCRIPTIONS); |
| } |
| |
| @Nullable |
| private SubscriptionInfo getSubscriptionForSubscriptionId(int subscriptionId) { |
| List<SubscriptionInfo> subs = mSubscriptionManager.getAvailableSubscriptionInfoList(); |
| int subCount = (subs != null) ? subs.size() : 0; |
| for (int i = 0; i < subCount; i++) { |
| SubscriptionInfo sub = subs.get(i); |
| if (subscriptionId == sub.getSubscriptionId()) { |
| return sub; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private String blockingGetEidFromEuiccService(int cardId) { |
| CountDownLatch latch = new CountDownLatch(1); |
| AtomicReference<String> eidRef = new AtomicReference<>(); |
| mConnector.getEid(cardId, new EuiccConnector.GetEidCommandCallback() { |
| @Override |
| public void onGetEidComplete(String eid) { |
| eidRef.set(eid); |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onEuiccServiceUnavailable() { |
| latch.countDown(); |
| } |
| }); |
| return awaitResult(latch, eidRef); |
| } |
| |
| private @OtaStatus int blockingGetOtaStatusFromEuiccService(int cardId) { |
| CountDownLatch latch = new CountDownLatch(1); |
| AtomicReference<Integer> statusRef = |
| new AtomicReference<>(EuiccManager.EUICC_OTA_STATUS_UNAVAILABLE); |
| mConnector.getOtaStatus(cardId, new EuiccConnector.GetOtaStatusCommandCallback() { |
| @Override |
| public void onGetOtaStatusComplete(@OtaStatus int status) { |
| statusRef.set(status); |
| latch.countDown(); |
| } |
| |
| @Override |
| public void onEuiccServiceUnavailable() { |
| latch.countDown(); |
| } |
| }); |
| return awaitResult(latch, statusRef); |
| } |
| |
| @Nullable |
| private EuiccInfo blockingGetEuiccInfoFromEuiccService(int cardId) { |
| CountDownLatch latch = new CountDownLatch(1); |
| AtomicReference<EuiccInfo> euiccInfoRef = new AtomicReference<>(); |
| mConnector.getEuiccInfo(cardId, 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> T awaitResult(CountDownLatch latch, AtomicReference<T> resultRef) { |
| try { |
| latch.await(); |
| } catch (InterruptedException e) { |
| Thread.currentThread().interrupt(); |
| } |
| return resultRef.get(); |
| } |
| |
| // Returns whether the caller has carrier privilege on the given subscription. |
| private boolean checkCarrierPrivilegeInMetadata(DownloadableSubscription subscription, |
| String callingPackage) { |
| UiccAccessRule[] rules = null; |
| List<UiccAccessRule> rulesList = subscription.getAccessRules(); |
| if (rulesList != null) { |
| rules = rulesList.toArray(new UiccAccessRule[rulesList.size()]); |
| } |
| if (rules == null) { |
| Log.e(TAG, "No access rules but caller is unprivileged"); |
| return false; |
| } |
| |
| final PackageInfo info; |
| try { |
| info = mPackageManager.getPackageInfo(callingPackage, PackageManager.GET_SIGNATURES); |
| } catch (PackageManager.NameNotFoundException e) { |
| Log.e(TAG, "Calling package valid but gone"); |
| return false; |
| } |
| |
| for (int i = 0; i < rules.length; i++) { |
| if (rules[i].getCarrierPrivilegeStatus(info) |
| == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) { |
| Log.i(TAG, "Calling package has carrier privilege to this profile"); |
| return true; |
| } |
| } |
| Log.e(TAG, "Calling package doesn't have carrier privilege to this profile"); |
| return false; |
| } |
| |
| private boolean supportMultiActiveSlots() { |
| return mTelephonyManager.getPhoneCount() > 1; |
| } |
| |
| // Checks whether the caller can manage the active embedded subscription on the SIM with the |
| // given cardId. |
| private boolean canManageActiveSubscriptionOnTargetSim(int cardId, String callingPackage) { |
| List<SubscriptionInfo> subInfoList = mSubscriptionManager |
| .getActiveSubscriptionInfoList(/* userVisibleOnly */false); |
| if (subInfoList == null || subInfoList.size() == 0) { |
| // No active subscription on any SIM. |
| return false; |
| } |
| for (SubscriptionInfo subInfo : subInfoList) { |
| // If cardId == TelephonyManager.UNSUPPORTED_CARD_ID, we assume it does not support |
| // multiple eSIMs. There are older multi-active SIM devices which do not implement HAL |
| // 1.2 and if they have multiple eSIMs, we let it pass if the app can manage an active |
| // subscription on any eSIM. That's the best we can do here. |
| if ((cardId == TelephonyManager.UNSUPPORTED_CARD_ID || subInfo.getCardId() == cardId) |
| && subInfo.isEmbedded() |
| && mSubscriptionManager.canManageSubscription(subInfo, callingPackage)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // For a multi-active subscriptions phone, checks whether the caller can manage subscription on |
| // the target SIM with the given cardId. The caller can only manage subscription on the target |
| // SIM if it can manage the active subscription on the target SIM or there is no active |
| // subscription on the target SIM, and the caller can manage any active subscription on any |
| // other SIM. The target SIM should be an eUICC. |
| // For a single-active subscription phone, checks whether the caller can manage any active |
| // embedded subscription. |
| private boolean canManageSubscriptionOnTargetSim(int cardId, String callingPackage) { |
| List<SubscriptionInfo> subInfoList = mSubscriptionManager |
| .getActiveSubscriptionInfoList(false /* userVisibleonly */); |
| // No active subscription on any SIM. |
| if (subInfoList == null || subInfoList.size() == 0) { |
| return false; |
| } |
| // If it's a multi-active SIM device, we assume it's above HAL 1.2 which supports cardId. |
| // There are older multi-active SIM devices but don't implement HAL 1.2. In this case, |
| // platform can't even detect UiccCardInfo#isEuicc as true for eSIM, which won't let the |
| // below check pass. That's the best we can do here. |
| if (supportMultiActiveSlots()) { |
| // The target card should be an eUICC. |
| List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo(); |
| if (cardInfos == null || cardInfos.isEmpty()) { |
| return false; |
| } |
| boolean isEuicc = false; |
| for (UiccCardInfo info : cardInfos) { |
| if (info != null && info.getCardId() == cardId && info.isEuicc()) { |
| isEuicc = true; |
| break; |
| } |
| } |
| if (!isEuicc) { |
| Log.i(TAG, "The target SIM is not an eUICC."); |
| return false; |
| } |
| |
| // If the caller can't manage the active embedded subscription on the target SIM, return |
| // false. If the caller can manage the active embedded subscription on the target SIM, |
| // return true directly. |
| for (SubscriptionInfo subInfo : subInfoList) { |
| // subInfo.isEmbedded() can only be true for the target SIM. |
| if (subInfo.isEmbedded() && subInfo.getCardId() == cardId) { |
| return mSubscriptionManager.canManageSubscription(subInfo, callingPackage); |
| } |
| } |
| |
| // There is no active subscription on the target SIM, checks whether the caller can |
| // manage any active subscription on any other SIM. |
| return mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(callingPackage) |
| == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; |
| } else { |
| for (SubscriptionInfo subInfo : subInfoList) { |
| if (subInfo.isEmbedded() |
| && mSubscriptionManager.canManageSubscription(subInfo, callingPackage)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| private boolean callerCanReadPhoneStatePrivileged() { |
| return mContext.checkCallingOrSelfPermission( |
| Manifest.permission.READ_PRIVILEGED_PHONE_STATE) |
| == PackageManager.PERMISSION_GRANTED; |
| } |
| |
| private boolean callerCanWriteEmbeddedSubscriptions() { |
| return mContext.checkCallingOrSelfPermission( |
| Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) |
| == PackageManager.PERMISSION_GRANTED; |
| } |
| } |