| /* |
| * 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.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, |
| }) |
| @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(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; |
| |
| /** |
| * {@link EuiccManager#getDownloadableSubscriptionMetadata} failed with |
| * {@link EuiccService#RESULT_MUST_DEACTIVATE_SIM}. |
| */ |
| public 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. |
| */ |
| public 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, or because we cannot determine the |
| * privileges without deactivating the current SIM first. |
| */ |
| public static EuiccOperation forDownloadNoPrivileges(long callingToken, |
| DownloadableSubscription subscription, boolean switchAfterDownload, |
| String callingPackage) { |
| return new EuiccOperation(ACTION_DOWNLOAD_NO_PRIVILEGES, callingToken, |
| subscription, 0 /* subscriptionId */, switchAfterDownload, callingPackage); |
| } |
| |
| 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) { |
| mAction = action; |
| mCallingToken = callingToken; |
| mDownloadableSubscription = downloadableSubscription; |
| mSubscriptionId = subscriptionId; |
| mSwitchAfterDownload = switchAfterDownload; |
| mCallingPackage = callingPackage; |
| } |
| |
| EuiccOperation(Parcel in) { |
| mAction = in.readInt(); |
| mCallingToken = in.readLong(); |
| mDownloadableSubscription = in.readTypedObject(DownloadableSubscription.CREATOR); |
| mSubscriptionId = in.readInt(); |
| mSwitchAfterDownload = in.readBoolean(); |
| mCallingPackage = in.readString(); |
| } |
| |
| @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); |
| } |
| |
| /** |
| * 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(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( |
| resolutionExtras.getBoolean(EuiccService.RESOLUTION_EXTRA_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_DOWNLOAD_DEACTIVATE_SIM: |
| resolvedDownloadDeactivateSim( |
| resolutionExtras.getBoolean(EuiccService.RESOLUTION_EXTRA_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_DOWNLOAD_NO_PRIVILEGES: |
| resolvedDownloadNoPrivileges( |
| resolutionExtras.getBoolean(EuiccService.RESOLUTION_EXTRA_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_GET_DEFAULT_LIST_DEACTIVATE_SIM: |
| resolvedGetDefaultListDeactivateSim( |
| resolutionExtras.getBoolean(EuiccService.RESOLUTION_EXTRA_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_SWITCH_DEACTIVATE_SIM: |
| resolvedSwitchDeactivateSim( |
| resolutionExtras.getBoolean(EuiccService.RESOLUTION_EXTRA_CONSENT), |
| callbackIntent); |
| break; |
| case ACTION_SWITCH_NO_PRIVILEGES: |
| resolvedSwitchNoPrivileges( |
| resolutionExtras.getBoolean(EuiccService.RESOLUTION_EXTRA_CONSENT), |
| callbackIntent); |
| break; |
| default: |
| Log.wtf(TAG, "Unknown action: " + mAction); |
| break; |
| } |
| } |
| |
| private void resolvedGetMetadataDeactivateSim( |
| 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( |
| mDownloadableSubscription, |
| true /* forceDeactivateSim */, |
| mCallingPackage, |
| callbackIntent); |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private void resolvedDownloadDeactivateSim( |
| 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( |
| mDownloadableSubscription, |
| mSwitchAfterDownload, |
| mCallingPackage, |
| true /* forceDeactivateSim */, |
| callbackIntent); |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private void resolvedDownloadNoPrivileges(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( |
| token, |
| mDownloadableSubscription, |
| mSwitchAfterDownload, |
| true /* forceDeactivateSim */, |
| mCallingPackage, |
| callbackIntent); |
| } finally { |
| Binder.restoreCallingIdentity(token); |
| } |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private void resolvedGetDefaultListDeactivateSim( |
| 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( |
| true /* forceDeactivateSim */, mCallingPackage, callbackIntent); |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private void resolvedSwitchDeactivateSim( |
| 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( |
| mSubscriptionId, |
| true /* forceDeactivateSim */, |
| mCallingPackage, |
| callbackIntent); |
| } else { |
| // User has not consented; fail the operation. |
| fail(callbackIntent); |
| } |
| } |
| |
| private void resolvedSwitchNoPrivileges(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( |
| 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; |
| } |
| } |