| /* |
| * 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.annotation.IntDef; |
| import android.annotation.Nullable; |
| import android.app.PendingIntent; |
| import android.os.Binder; |
| import android.os.Bundle; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.service.euicc.EuiccService; |
| import android.telephony.euicc.DownloadableSubscription; |
| import android.telephony.euicc.EuiccManager; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| |
| /** |
| * Representation of an {@link EuiccController} operation which failed with a resolvable error. |
| * |
| * <p>This class tracks the operation which failed and the reason for failure. Once the error is |
| * resolved, the operation can be resumed with {@link #continueOperation}. |
| */ |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) |
| public class EuiccOperation implements Parcelable { |
| private static final String TAG = "EuiccOperation"; |
| |
| public static final Creator<EuiccOperation> CREATOR = new Creator<EuiccOperation>() { |
| @Override |
| public EuiccOperation createFromParcel(Parcel in) { |
| return new EuiccOperation(in); |
| } |
| |
| @Override |
| public EuiccOperation[] newArray(int size) { |
| return new EuiccOperation[size]; |
| } |
| }; |
| |
| @VisibleForTesting |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef({ |
| ACTION_GET_METADATA_DEACTIVATE_SIM, |
| ACTION_DOWNLOAD_DEACTIVATE_SIM, |
| ACTION_DOWNLOAD_NO_PRIVILEGES, |
| ACTION_GET_DEFAULT_LIST_DEACTIVATE_SIM, |
| ACTION_SWITCH_DEACTIVATE_SIM, |
| ACTION_SWITCH_NO_PRIVILEGES, |
| ACTION_DOWNLOAD_RESOLVABLE_ERRORS, |
| }) |
| @interface Action {} |
| |
| @VisibleForTesting |
| static final int ACTION_GET_METADATA_DEACTIVATE_SIM = 1; |
| @VisibleForTesting |
| static final int ACTION_DOWNLOAD_DEACTIVATE_SIM = 2; |
| @VisibleForTesting |
| static final int ACTION_DOWNLOAD_NO_PRIVILEGES = 3; |
| @VisibleForTesting |
| static final int ACTION_GET_DEFAULT_LIST_DEACTIVATE_SIM = 4; |
| @VisibleForTesting |
| static final int ACTION_SWITCH_DEACTIVATE_SIM = 5; |
| @VisibleForTesting |
| static final int ACTION_SWITCH_NO_PRIVILEGES = 6; |
| @VisibleForTesting |
| static final int ACTION_DOWNLOAD_RESOLVABLE_ERRORS = 7; |
| /** |
| * @deprecated Use ACTION_DOWNLOAD_RESOLVABLE_ERRORS and pass the resolvable errors in bit map. |
| */ |
| @VisibleForTesting |
| @Deprecated |
| static final int ACTION_DOWNLOAD_CONFIRMATION_CODE = 8; |
| /** |
| * ACTION_DOWNLOAD_CHECK_METADATA can be used for either NO_PRIVILEGES or DEACTIVATE_SIM. |
| */ |
| @VisibleForTesting |
| static final int ACTION_DOWNLOAD_NO_PRIVILEGES_OR_DEACTIVATE_SIM_CHECK_METADATA = 9; |
| |
| @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) |
| public final @Action int mAction; |
| |
| private final long mCallingToken; |
| |
| @Nullable |
| private final DownloadableSubscription mDownloadableSubscription; |
| private final int mSubscriptionId; |
| private final boolean mSwitchAfterDownload; |
| @Nullable |
| private final String mCallingPackage; |
| @Nullable |
| private final int mResolvableErrors; |
| |
| /** |
| * {@link EuiccManager#getDownloadableSubscriptionMetadata} failed with |
| * {@link EuiccService#RESULT_MUST_DEACTIVATE_SIM}. |
| */ |
| static EuiccOperation forGetMetadataDeactivateSim(long callingToken, |
| DownloadableSubscription subscription, String callingPackage) { |
| return new EuiccOperation(ACTION_GET_METADATA_DEACTIVATE_SIM, callingToken, |
| subscription, 0 /* subscriptionId */, false /* switchAfterDownload */, |
| callingPackage); |
| } |
| |
| /** |
| * {@link EuiccManager#downloadSubscription} failed with a mustDeactivateSim error. Should only |
| * be used for privileged callers; for unprivileged callers, use |
| * {@link #forDownloadNoPrivileges} to avoid a double prompt. |
| */ |
| static EuiccOperation forDownloadDeactivateSim(long callingToken, |
| DownloadableSubscription subscription, boolean switchAfterDownload, |
| String callingPackage) { |
| return new EuiccOperation(ACTION_DOWNLOAD_DEACTIVATE_SIM, callingToken, |
| subscription, 0 /* subscriptionId */, switchAfterDownload, callingPackage); |
| } |
| |
| /** |
| * {@link EuiccManager#downloadSubscription} failed because the calling app does not have |
| * permission to manage the current active subscription. |
| */ |
| static EuiccOperation forDownloadNoPrivileges(long callingToken, |
| DownloadableSubscription subscription, boolean switchAfterDownload, |
| String callingPackage) { |
| return new EuiccOperation(ACTION_DOWNLOAD_NO_PRIVILEGES, callingToken, |
| subscription, 0 /* subscriptionId */, switchAfterDownload, callingPackage); |
| } |
| |
| /** |
| * {@link EuiccManager#downloadSubscription} failed because the caller can't manage the target |
| * SIM, or we cannot determine the privileges without deactivating the current SIM first. |
| */ |
| static EuiccOperation forDownloadNoPrivilegesOrDeactivateSimCheckMetadata(long callingToken, |
| DownloadableSubscription subscription, boolean switchAfterDownload, |
| String callingPackage) { |
| return new EuiccOperation(ACTION_DOWNLOAD_NO_PRIVILEGES_OR_DEACTIVATE_SIM_CHECK_METADATA, |
| callingToken, subscription, 0 /* subscriptionId */, |
| switchAfterDownload, callingPackage); |
| } |
| |
| /** |
| * {@link EuiccManager#downloadSubscription} failed with |
| * {@link EuiccService#RESULT_NEED_CONFIRMATION_CODE} error. |
| * |
| * @deprecated Use |
| * {@link #forDownloadResolvableErrors(long, DownloadableSubscription, boolean, String, int)} |
| * instead. |
| */ |
| @Deprecated |
| public static EuiccOperation forDownloadConfirmationCode(long callingToken, |
| DownloadableSubscription subscription, boolean switchAfterDownload, |
| String callingPackage) { |
| return new EuiccOperation(ACTION_DOWNLOAD_CONFIRMATION_CODE, callingToken, |
| subscription, 0 /* subscriptionId */, switchAfterDownload, callingPackage); |
| } |
| |
| /** |
| * {@link EuiccManager#downloadSubscription} failed with |
| * {@link EuiccService#RESULT_RESOLVABLE_ERRORS} error. |
| */ |
| static EuiccOperation forDownloadResolvableErrors(long callingToken, |
| DownloadableSubscription subscription, boolean switchAfterDownload, |
| String callingPackage, int resolvableErrors) { |
| return new EuiccOperation(ACTION_DOWNLOAD_RESOLVABLE_ERRORS, callingToken, |
| subscription, 0 /* subscriptionId */, switchAfterDownload, |
| callingPackage, resolvableErrors); |
| } |
| |
| static EuiccOperation forGetDefaultListDeactivateSim(long callingToken, String callingPackage) { |
| return new EuiccOperation(ACTION_GET_DEFAULT_LIST_DEACTIVATE_SIM, callingToken, |
| null /* downloadableSubscription */, 0 /* subscriptionId */, |
| false /* switchAfterDownload */, callingPackage); |
| } |
| |
| static EuiccOperation forSwitchDeactivateSim(long callingToken, int subscriptionId, |
| String callingPackage) { |
| return new EuiccOperation(ACTION_SWITCH_DEACTIVATE_SIM, callingToken, |
| null /* downloadableSubscription */, subscriptionId, |
| false /* switchAfterDownload */, callingPackage); |
| } |
| |
| static EuiccOperation forSwitchNoPrivileges(long callingToken, int subscriptionId, |
| String callingPackage) { |
| return new EuiccOperation(ACTION_SWITCH_NO_PRIVILEGES, callingToken, |
| null /* downloadableSubscription */, subscriptionId, |
| false /* switchAfterDownload */, callingPackage); |
| } |
| |
| EuiccOperation(@Action int action, |
| long callingToken, |
| @Nullable DownloadableSubscription downloadableSubscription, |
| int subscriptionId, |
| boolean switchAfterDownload, |
| String callingPackage, |
| int resolvableErrors) { |
| mAction = action; |
| mCallingToken = callingToken; |
| mDownloadableSubscription = downloadableSubscription; |
| mSubscriptionId = subscriptionId; |
| mSwitchAfterDownload = switchAfterDownload; |
| mCallingPackage = callingPackage; |
| mResolvableErrors = resolvableErrors; |
| } |
| |
| EuiccOperation(@Action int action, |
| long callingToken, |
| @Nullable DownloadableSubscription downloadableSubscription, |
| int subscriptionId, |
| boolean switchAfterDownload, |
| String callingPackage) { |
| mAction = action; |
| mCallingToken = callingToken; |
| mDownloadableSubscription = downloadableSubscription; |
| mSubscriptionId = subscriptionId; |
| mSwitchAfterDownload = switchAfterDownload; |
| mCallingPackage = callingPackage; |
| mResolvableErrors = 0; |
| } |
| |
| EuiccOperation(Parcel in) { |
| mAction = in.readInt(); |
| mCallingToken = in.readLong(); |
| mDownloadableSubscription = in.readTypedObject(DownloadableSubscription.CREATOR); |
| mSubscriptionId = in.readInt(); |
| mSwitchAfterDownload = in.readBoolean(); |
| mCallingPackage = in.readString(); |
| mResolvableErrors = in.readInt(); |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeInt(mAction); |
| dest.writeLong(mCallingToken); |
| dest.writeTypedObject(mDownloadableSubscription, flags); |
| dest.writeInt(mSubscriptionId); |
| dest.writeBoolean(mSwitchAfterDownload); |
| dest.writeString(mCallingPackage); |
| dest.writeInt(mResolvableErrors); |
| } |
| |
| /** |
| * Resume this operation based on the results of the resolution activity. |
| * |
| * @param resolutionExtras The resolution extras as provided to |
| * {@link EuiccManager#continueOperation}. |
| * @param callbackIntent The callback intent to trigger after the operation completes. |
| */ |
| public void continueOperation(int cardId, Bundle resolutionExtras, |
| PendingIntent callbackIntent) { |
| // Restore the identity of the caller. We should err on the side of caution and redo any |
| // permission checks before continuing with the operation in case the caller state has |
| // changed. Resolution flows can re-clear the identity if required. |
| Binder.restoreCallingIdentity(mCallingToken); |
| |
| switch (mAction) { |
| case ACTION_GET_METADATA_DEACTIVATE_SIM: |
| resolvedGetMetadataDeactivateSim(cardId, |
| resolutionExtras.getBoolean(EuiccService.EXTRA_RESOLUTION_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_DOWNLOAD_DEACTIVATE_SIM: |
| resolvedDownloadDeactivateSim(cardId, |
| resolutionExtras.getBoolean(EuiccService.EXTRA_RESOLUTION_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_DOWNLOAD_NO_PRIVILEGES: |
| resolvedDownloadNoPrivileges(cardId, |
| resolutionExtras.getBoolean(EuiccService.EXTRA_RESOLUTION_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_DOWNLOAD_NO_PRIVILEGES_OR_DEACTIVATE_SIM_CHECK_METADATA: |
| resolvedDownloadNoPrivilegesOrDeactivateSimCheckMetadata(cardId, |
| resolutionExtras.getBoolean(EuiccService.EXTRA_RESOLUTION_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_DOWNLOAD_CONFIRMATION_CODE: // Deprecated case |
| resolvedDownloadConfirmationCode(cardId, |
| resolutionExtras.getString(EuiccService.EXTRA_RESOLUTION_CONFIRMATION_CODE), |
| callbackIntent); |
| break; |
| case ACTION_DOWNLOAD_RESOLVABLE_ERRORS: |
| resolvedDownloadResolvableErrors(cardId, resolutionExtras, callbackIntent); |
| break; |
| case ACTION_GET_DEFAULT_LIST_DEACTIVATE_SIM: |
| resolvedGetDefaultListDeactivateSim(cardId, |
| resolutionExtras.getBoolean(EuiccService.EXTRA_RESOLUTION_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_SWITCH_DEACTIVATE_SIM: |
| resolvedSwitchDeactivateSim(cardId, |
| resolutionExtras.getBoolean(EuiccService.EXTRA_RESOLUTION_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_SWITCH_NO_PRIVILEGES: |
| resolvedSwitchNoPrivileges(cardId, |
| resolutionExtras.getBoolean(EuiccService.EXTRA_RESOLUTION_CONSENT), |
| callbackIntent); |
| break; |
| default: |
| Log.wtf(TAG, "Unknown action: " + mAction); |
| break; |
| } |
| } |
| |
| private void resolvedGetMetadataDeactivateSim(int cardId, boolean consent, |
| PendingIntent callbackIntent) { |
| if (consent) { |
| // User has consented; perform the lookup, but this time, tell the LPA to deactivate any |
| // required active SIMs. |
| EuiccController.get().getDownloadableSubscriptionMetadata( |
| cardId, |
| mDownloadableSubscription, |
| true /* forceDeactivateSim */, |
| mCallingPackage, |
| callbackIntent); |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private void resolvedDownloadDeactivateSim(int cardId, boolean consent, |
| PendingIntent callbackIntent) { |
| if (consent) { |
| // User has consented; perform the download, but this time, tell the LPA to deactivate |
| // any required active SIMs. |
| EuiccController.get().downloadSubscription( |
| cardId, |
| mDownloadableSubscription, |
| mSwitchAfterDownload, |
| mCallingPackage, |
| true /* forceDeactivateSim */, |
| null /* resolvedBundle */, |
| callbackIntent); |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private void resolvedDownloadNoPrivileges(int cardId, boolean consent, |
| PendingIntent callbackIntent) { |
| if (consent) { |
| // User has consented; perform the download with full privileges. |
| long token = Binder.clearCallingIdentity(); |
| try { |
| // Note: We turn on "forceDeactivateSim" here under the assumption that the |
| // privilege prompt should also cover permission to deactivate an active SIM, as |
| // the privilege prompt makes it clear that we're switching from the current |
| // carrier. |
| EuiccController.get().downloadSubscriptionPrivileged( |
| cardId, |
| token, |
| mDownloadableSubscription, |
| mSwitchAfterDownload, |
| true /* forceDeactivateSim */, |
| mCallingPackage, |
| null /* resolvedBundle */, |
| callbackIntent); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private void resolvedDownloadNoPrivilegesOrDeactivateSimCheckMetadata(int cardId, |
| boolean consent, PendingIntent callbackIntent) { |
| if (consent) { |
| // User has consented; perform the download with full privileges. |
| long token = Binder.clearCallingIdentity(); |
| try { |
| // Note: We turn on "forceDeactivateSim" here under the assumption that the |
| // privilege prompt should also cover permission to deactivate an active SIM, as |
| // the privilege prompt makes it clear that we're switching from the current |
| // carrier. |
| EuiccController.get().downloadSubscriptionPrivilegedCheckMetadata( |
| cardId, |
| token, |
| mDownloadableSubscription, |
| mSwitchAfterDownload, |
| true /* forceDeactivateSim */, |
| mCallingPackage, |
| null /* resolvedBundle */, |
| callbackIntent); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| /** |
| * @deprecated The resolvable errors in download step are solved by |
| * {@link #resolvedDownloadResolvableErrors(Bundle, PendingIntent)} from Q. |
| */ |
| @Deprecated |
| private void resolvedDownloadConfirmationCode(int cardId, String confirmationCode, |
| PendingIntent callbackIntent) { |
| if (TextUtils.isEmpty(confirmationCode)) { |
| fail(callbackIntent); |
| } else { |
| mDownloadableSubscription.setConfirmationCode(confirmationCode); |
| EuiccController.get().downloadSubscription( |
| cardId, |
| mDownloadableSubscription, |
| mSwitchAfterDownload, |
| mCallingPackage, |
| true /* forceDeactivateSim */, |
| null, |
| callbackIntent); |
| } |
| } |
| |
| private void resolvedDownloadResolvableErrors(int cardId, Bundle resolvedBundle, |
| PendingIntent callbackIntent) { |
| boolean pass = true; |
| String confirmationCode = null; |
| if ((mResolvableErrors & EuiccService.RESOLVABLE_ERROR_POLICY_RULES) != 0) { |
| if (!resolvedBundle.getBoolean(EuiccService.EXTRA_RESOLUTION_ALLOW_POLICY_RULES)) { |
| pass = false; |
| } |
| } |
| if ((mResolvableErrors & EuiccService.RESOLVABLE_ERROR_CONFIRMATION_CODE) != 0) { |
| confirmationCode = resolvedBundle.getString( |
| EuiccService.EXTRA_RESOLUTION_CONFIRMATION_CODE); |
| // The check here just makes sure the entered confirmation code is non-empty. The actual |
| // check to valid the confirmation code is done by LPA on the ensuing download attemp. |
| if (TextUtils.isEmpty(confirmationCode)) { |
| pass = false; |
| } |
| } |
| |
| if (!pass) { |
| fail(callbackIntent); |
| } else { |
| mDownloadableSubscription.setConfirmationCode(confirmationCode); |
| EuiccController.get().downloadSubscription( |
| cardId, |
| mDownloadableSubscription, |
| mSwitchAfterDownload, |
| mCallingPackage, |
| true /* forceDeactivateSim */, |
| resolvedBundle, |
| callbackIntent); |
| } |
| } |
| |
| private void resolvedGetDefaultListDeactivateSim(int cardId, boolean consent, |
| PendingIntent callbackIntent) { |
| if (consent) { |
| // User has consented; perform the lookup, but this time, tell the LPA to deactivate any |
| // required active SIMs. |
| EuiccController.get().getDefaultDownloadableSubscriptionList( |
| cardId, |
| true /* forceDeactivateSim */, |
| mCallingPackage, |
| callbackIntent); |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private void resolvedSwitchDeactivateSim(int cardId, boolean consent, |
| PendingIntent callbackIntent) { |
| if (consent) { |
| // User has consented; perform the switch, but this time, tell the LPA to deactivate any |
| // required active SIMs. |
| EuiccController.get().switchToSubscription( |
| cardId, |
| mSubscriptionId, |
| true /* forceDeactivateSim */, |
| mCallingPackage, |
| callbackIntent); |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private void resolvedSwitchNoPrivileges(int cardId, boolean consent, |
| PendingIntent callbackIntent) { |
| if (consent) { |
| // User has consented; perform the switch with full privileges. |
| long token = Binder.clearCallingIdentity(); |
| try { |
| // Note: We turn on "forceDeactivateSim" here under the assumption that the |
| // privilege prompt should also cover permission to deactivate an active SIM, as |
| // the privilege prompt makes it clear that we're switching from the current |
| // carrier. Also note that in practice, we'd need to deactivate the active SIM to |
| // even reach this point, because we cannot fetch the metadata needed to check the |
| // privileges without doing so. |
| EuiccController.get().switchToSubscriptionPrivileged( |
| cardId, |
| token, |
| mSubscriptionId, |
| true /* forceDeactivateSim */, |
| mCallingPackage, |
| callbackIntent); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private static void fail(PendingIntent callbackIntent) { |
| EuiccController.get().sendResult( |
| callbackIntent, |
| EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, |
| null /* extrasIntent */); |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| } |