From abedec1601cd141dba95650cb68b7e2e425f774b Mon Sep 17 00:00:00 2001 From: Jordan Liu Date: Tue, 9 Nov 2021 15:36:21 -0800 Subject: [PATCH] Add portIndex for switch subscription API Bug: 205504646 Test: manual (builds, will not be functional until LPA implementation) Change-Id: Ic289538f5df17c03b2f8da7f4966c583d4557586 Merged-in: Ic289538f5df17c03b2f8da7f4966c583d4557586 --- core/api/current.txt | 7 +- core/api/system-current.txt | 4 +- .../android/service/euicc/EuiccService.java | 42 ++++++-- .../android/service/euicc/IEuiccService.aidl | 2 +- .../android/telephony/euicc/EuiccManager.java | 95 +++++++++++++++++++ .../telephony/euicc/IEuiccController.aidl | 5 + .../telephony/euicc/IResultCallback.aidl | 23 +++++ 7 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 telephony/java/com/android/internal/telephony/euicc/IResultCallback.aidl diff --git a/core/api/current.txt b/core/api/current.txt index 4036c988f3e55..a09a3597dab80 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -42283,7 +42283,8 @@ package android.telephony.euicc { method public boolean isEnabled(); method public boolean isSimPortAvailable(int); method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException; - method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent); + method @Deprecated @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent); + method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, int, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.euicc.EuiccManager.ResultListener); method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void updateSubscriptionNickname(int, @Nullable String, @NonNull android.app.PendingIntent); field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS"; field public static final String ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE = "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE"; @@ -42329,6 +42330,10 @@ package android.telephony.euicc { field public static final int OPERATION_SYSTEM = 1; // 0x1 } + public static interface EuiccManager.ResultListener { + method public void onComplete(int, @Nullable android.content.Intent); + } + } package android.telephony.gsm { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index cbfd86e5478fa..17286265b5559 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -9641,7 +9641,8 @@ package android.service.euicc { method @android.telephony.euicc.EuiccManager.OtaStatus public abstract int onGetOtaStatus(int); method public abstract int onRetainSubscriptionsForFactoryReset(int); method public abstract void onStartOtaIfNecessary(int, android.service.euicc.EuiccService.OtaStatusChangedCallback); - method public abstract int onSwitchToSubscription(int, @Nullable String, boolean); + method @Deprecated public abstract int onSwitchToSubscription(int, @Nullable String, boolean); + method public int onSwitchToSubscriptionWithPort(int, int, @Nullable String, boolean); method public abstract int onUpdateSubscriptionNickname(int, String, String); field public static final String ACTION_BIND_CARRIER_PROVISIONING_SERVICE = "android.service.euicc.action.BIND_CARRIER_PROVISIONING_SERVICE"; field public static final String ACTION_DELETE_SUBSCRIPTION_PRIVILEGED = "android.service.euicc.action.DELETE_SUBSCRIPTION_PRIVILEGED"; @@ -9663,6 +9664,7 @@ package android.service.euicc { field public static final String EXTRA_RESOLUTION_CONFIRMATION_CODE = "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE"; field public static final String EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED = "android.service.euicc.extra.RESOLUTION_CONFIRMATION_CODE_RETRIED"; field public static final String EXTRA_RESOLUTION_CONSENT = "android.service.euicc.extra.RESOLUTION_CONSENT"; + field public static final String EXTRA_RESOLUTION_PORT_INDEX = "android.service.euicc.extra.RESOLUTION_PORT_INDEX"; field public static final String EXTRA_RESOLVABLE_ERRORS = "android.service.euicc.extra.RESOLVABLE_ERRORS"; field public static final int RESOLVABLE_ERROR_CONFIRMATION_CODE = 1; // 0x1 field public static final int RESOLVABLE_ERROR_POLICY_RULES = 2; // 0x2 diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java index fcbb008c79b31..7d857a2dc1b48 100644 --- a/telephony/java/android/service/euicc/EuiccService.java +++ b/telephony/java/android/service/euicc/EuiccService.java @@ -255,6 +255,12 @@ public abstract class EuiccService extends Service { public static final String EXTRA_RESOLUTION_CARD_ID = "android.service.euicc.extra.RESOLUTION_CARD_ID"; + /** + * Intent extra set for resolution requests containing an int indicating the current port index. + */ + public static final String EXTRA_RESOLUTION_PORT_INDEX = + "android.service.euicc.extra.RESOLUTION_PORT_INDEX"; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = { "RESULT_" }, value = { @@ -579,9 +585,32 @@ public abstract class EuiccService extends Service { * @return the result of the switch operation. May be one of the predefined {@code RESULT_} * constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}. * @see android.telephony.euicc.EuiccManager#switchToSubscription + * + * @deprecated prefer {@link #onSwitchToSubscriptionWithPort(int, int, String, boolean)} */ - public abstract @Result int onSwitchToSubscription(int slotId, @Nullable String iccid, - boolean forceDeactivateSim); + @Deprecated public abstract @Result int onSwitchToSubscription(int slotId, + @Nullable String iccid, boolean forceDeactivateSim); + + /** + * Switch to the given subscription. + * + * @param slotId ID of the SIM slot to use for the operation. + * @param portIndex which port on the eUICC to use + * @param iccid the ICCID of the subscription to enable. May be null, in which case the current + * profile should be deactivated and no profile should be activated to replace it - this is + * equivalent to a physical SIM being ejected. + * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the + * eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM} + * should be returned to allow the user to consent to this operation first. + * @return the result of the switch operation. May be one of the predefined {@code RESULT_} + * constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}. + * @see android.telephony.euicc.EuiccManager#switchToSubscription + */ + public @Result int onSwitchToSubscriptionWithPort(int slotId, int portIndex, + @Nullable String iccid, boolean forceDeactivateSim) { + // stub implementation, LPA needs to implement this + throw new UnsupportedOperationException("LPA must override onSwitchToSubscriptionWithPort"); + } /** * Update the nickname of the given subscription. @@ -821,16 +850,15 @@ public abstract class EuiccService extends Service { } }); } - @Override - public void switchToSubscription(int slotId, String iccid, boolean forceDeactivateSim, - ISwitchToSubscriptionCallback callback) { + public void switchToSubscription(int slotId, int portIndex, String iccid, + boolean forceDeactivateSim, ISwitchToSubscriptionCallback callback) { mExecutor.execute(new Runnable() { @Override public void run() { int result = - EuiccService.this.onSwitchToSubscription( - slotId, iccid, forceDeactivateSim); + EuiccService.this.onSwitchToSubscriptionWithPort( + slotId, portIndex, iccid, forceDeactivateSim); try { callback.onComplete(result); } catch (RemoteException e) { diff --git a/telephony/java/android/service/euicc/IEuiccService.aidl b/telephony/java/android/service/euicc/IEuiccService.aidl index bb7b569f17f93..aa30c9e88462c 100644 --- a/telephony/java/android/service/euicc/IEuiccService.aidl +++ b/telephony/java/android/service/euicc/IEuiccService.aidl @@ -48,7 +48,7 @@ oneway interface IEuiccService { in IGetDefaultDownloadableSubscriptionListCallback callback); void getEuiccInfo(int slotId, in IGetEuiccInfoCallback callback); void deleteSubscription(int slotId, String iccid, in IDeleteSubscriptionCallback callback); - void switchToSubscription(int slotId, String iccid, boolean forceDeactivateSim, + void switchToSubscription(int slotId, int portIndex, String iccid, boolean forceDeactivateSim, in ISwitchToSubscriptionCallback callback); void updateSubscriptionNickname(int slotId, String iccid, String nickname, in IUpdateSubscriptionNicknameCallback callback); diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java index 45022a6e4d8c8..aa514b99dad3f 100644 --- a/telephony/java/android/telephony/euicc/EuiccManager.java +++ b/telephony/java/android/telephony/euicc/EuiccManager.java @@ -16,6 +16,7 @@ package android.telephony.euicc; import android.Manifest; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +29,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager; +import android.os.Binder; import android.os.Bundle; import android.os.RemoteException; import android.telephony.TelephonyFrameworkInitializer; @@ -35,11 +37,13 @@ import android.telephony.TelephonyManager; import android.telephony.euicc.EuiccCardManager.ResetOption; import com.android.internal.telephony.euicc.IEuiccController; +import com.android.internal.telephony.euicc.IResultCallback; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.List; +import java.util.concurrent.Executor; import java.util.stream.Collectors; /** @@ -214,6 +218,20 @@ public class EuiccManager { public static final String ACTION_START_EUICC_ACTIVATION = "android.telephony.euicc.action.START_EUICC_ACTIVATION"; + /** + * Result codes passed to the ResultListener by + * {@link #switchToSubscription(int, int, Executor, ResultListener)} + * + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"EMBEDDED_SUBSCRIPTION_RESULT_"}, value = { + EMBEDDED_SUBSCRIPTION_RESULT_OK, + EMBEDDED_SUBSCRIPTION_RESULT_ERROR, + EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR + }) + public @interface ResultCode{} + /** * Result code for an operation indicating that the operation succeeded. */ @@ -1125,7 +1143,12 @@ public class EuiccManager { * permission, or the calling app must be authorized to manage the active subscription on * the target eUICC. * @param callbackIntent a PendingIntent to launch when the operation completes. + * + * @deprecated From T, callers should use + * {@link #switchToSubscription(int, int, Executor, ResultListener)} instead to specify a port + * index on the card to switch to. */ + @Deprecated @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) public void switchToSubscription(int subscriptionId, PendingIntent callbackIntent) { if (!isEnabled()) { @@ -1140,6 +1163,71 @@ public class EuiccManager { } } + /** + * Switch to (enable) the given subscription. + * + *

Requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, + * or the calling app must be authorized to manage both the currently-active subscription and + * the subscription to be enabled according to the subscription metadata. Without the former, + * an {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback + * intent to prompt the user to accept the download. + * + *

On a multi-active SIM device, requires the + * {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} permission, or a calling app + * only if the targeted eUICC does not currently have an active subscription or the calling app + * is authorized to manage the active subscription on the target eUICC, and the calling app is + * authorized to manage any active subscription on any SIM. Without it, an + * {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback + * intent to prompt the user to accept the download. The caller should also be authorized to + * manage the subscription to be enabled. + * + * @param subscriptionId the ID of the subscription to enable. May be + * {@link android.telephony.SubscriptionManager#INVALID_SUBSCRIPTION_ID} to deactivate the + * current profile without activating another profile to replace it. If it's a disable + * operation, requires the {@code android.Manifest.permission#WRITE_EMBEDDED_SUBSCRIPTIONS} + * permission, or the calling app must be authorized to manage the active subscription on + * the target eUICC. + * @param portIndex the index of the port to target for the enabled subscription + * @param executor an Executor on which to run the callback + * @param callback a {@link ResultListener} which will run when the operation completes + */ + @RequiresPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS) + public void switchToSubscription(int subscriptionId, int portIndex, + @NonNull @CallbackExecutor Executor executor, + @NonNull ResultListener callback) { + if (!isEnabled()) { + sendUnavailableErrorToCallback(executor, callback); + return; + } + try { + IResultCallback internalCallback = new IResultCallback.Stub() { + @Override + public void onComplete(int result, Intent resultIntent) { + executor.execute(() -> Binder.withCleanCallingIdentity( + () -> callback.onComplete(result, resultIntent))); + } + }; + getIEuiccController().switchToSubscriptionWithPort(mCardId, portIndex, + subscriptionId, mContext.getOpPackageName(), internalCallback); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Callback to receive the result of an EuiccManager API. + */ + public interface ResultListener { + /** + * Called on completion of some operation. + * @param resultCode representing success or specific failure of the operation + * (See {@link ResultCode}) + * @param resultIntent an intent used to start a resolution activity when an error + * occurs that can be resolved by the user + */ + void onComplete(@ResultCode int resultCode, @Nullable Intent resultIntent); + } + /** * Update the nickname for the given subscription. * @@ -1411,6 +1499,13 @@ public class EuiccManager { } } + private static void sendUnavailableErrorToCallback(@NonNull Executor executor, + ResultListener callback) { + Integer result = EMBEDDED_SUBSCRIPTION_RESULT_ERROR; + executor.execute(() -> + Binder.withCleanCallingIdentity(() -> callback.onComplete(result, null))); + } + private static IEuiccController getIEuiccController() { return IEuiccController.Stub.asInterface( TelephonyFrameworkInitializer diff --git a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl index 944ce3486ca6c..7f5982f128e35 100644 --- a/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl +++ b/telephony/java/com/android/internal/telephony/euicc/IEuiccController.aidl @@ -21,6 +21,9 @@ import android.content.Intent; import android.os.Bundle; import android.telephony.euicc.DownloadableSubscription; import android.telephony.euicc.EuiccInfo; + +import com.android.internal.telephony.euicc.IResultCallback; + import java.util.List; /** @hide */ @@ -42,6 +45,8 @@ interface IEuiccController { in PendingIntent callbackIntent); oneway void switchToSubscription(int cardId, int subscriptionId, String callingPackage, in PendingIntent callbackIntent); + oneway void switchToSubscriptionWithPort(int cardId, int portIndex, int subscriptionId, + String callingPackage, in IResultCallback callback); oneway void updateSubscriptionNickname(int cardId, int subscriptionId, String nickname, String callingPackage, in PendingIntent callbackIntent); oneway void eraseSubscriptions(int cardId, in PendingIntent callbackIntent); diff --git a/telephony/java/com/android/internal/telephony/euicc/IResultCallback.aidl b/telephony/java/com/android/internal/telephony/euicc/IResultCallback.aidl new file mode 100644 index 0000000000000..69f479c683d13 --- /dev/null +++ b/telephony/java/com/android/internal/telephony/euicc/IResultCallback.aidl @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2021 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.content.Intent; + +/** @hide */ +oneway interface IResultCallback { + void onComplete(int resultCode, in Intent resultIntent); +}