/*
* 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.
*
*
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 CREATOR = new Creator() {
@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;
}
}