blob: 86f1dbfc5f602523bf2a16147221ac3d7ff758c5 [file] [log] [blame]
/*
* 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;
}
}