| /* |
| * 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 static android.telephony.euicc.EuiccCardManager.ResetOption; |
| |
| import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; |
| |
| import android.Manifest; |
| import android.annotation.Nullable; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.ServiceConnection; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.ComponentInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.content.pm.ServiceInfo; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.service.euicc.DownloadSubscriptionResult; |
| import android.service.euicc.EuiccService; |
| import android.service.euicc.GetDefaultDownloadableSubscriptionListResult; |
| import android.service.euicc.GetDownloadableSubscriptionMetadataResult; |
| import android.service.euicc.GetEuiccProfileInfoListResult; |
| import android.service.euicc.IDeleteSubscriptionCallback; |
| import android.service.euicc.IDownloadSubscriptionCallback; |
| import android.service.euicc.IEraseSubscriptionsCallback; |
| import android.service.euicc.IEuiccService; |
| import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback; |
| import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback; |
| import android.service.euicc.IGetEidCallback; |
| import android.service.euicc.IGetEuiccInfoCallback; |
| import android.service.euicc.IGetEuiccProfileInfoListCallback; |
| import android.service.euicc.IGetOtaStatusCallback; |
| import android.service.euicc.IOtaStatusChangedCallback; |
| import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback; |
| import android.service.euicc.ISwitchToSubscriptionCallback; |
| import android.service.euicc.IUpdateSubscriptionNicknameCallback; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.telephony.UiccCardInfo; |
| import android.telephony.euicc.DownloadableSubscription; |
| import android.telephony.euicc.EuiccInfo; |
| import android.telephony.euicc.EuiccManager; |
| import android.telephony.euicc.EuiccManager.OtaStatus; |
| import android.text.TextUtils; |
| import android.util.ArraySet; |
| import android.util.Log; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.content.PackageMonitor; |
| import com.android.internal.util.IState; |
| import com.android.internal.util.State; |
| import com.android.internal.util.StateMachine; |
| |
| import java.io.FileDescriptor; |
| import java.io.PrintWriter; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| |
| /** |
| * State machine which maintains the binding to the EuiccService implementation and issues commands. |
| * |
| * <p>Keeps track of the highest-priority EuiccService implementation to use. When a command comes |
| * in, brings up a binding to that service, issues the command, and lingers the binding as long as |
| * more commands are coming in. The binding is dropped after an idle timeout. |
| */ |
| public class EuiccConnector extends StateMachine implements ServiceConnection { |
| private static final String TAG = "EuiccConnector"; |
| |
| /** |
| * Maximum amount of time to wait for a connection to be established after bindService returns |
| * true or onServiceDisconnected is called (and no package change has occurred which should |
| * force us to reestablish the binding). |
| */ |
| private static final int BIND_TIMEOUT_MILLIS = 30000; |
| |
| /** |
| * Maximum amount of idle time to hold the binding while in {@link ConnectedState}. After this, |
| * the binding is dropped to free up memory as the EuiccService is not expected to be used |
| * frequently as part of ongoing device operation. |
| */ |
| @VisibleForTesting |
| static final int LINGER_TIMEOUT_MILLIS = 60000; |
| |
| /** |
| * Command indicating that a package change has occurred. |
| * |
| * <p>{@link Message#obj} is an optional package name. If set, this package has changed in a |
| * way that will permanently sever any open bindings, and if we're bound to it, the binding must |
| * be forcefully reestablished. |
| */ |
| private static final int CMD_PACKAGE_CHANGE = 1; |
| /** Command indicating that {@link #BIND_TIMEOUT_MILLIS} has been reached. */ |
| private static final int CMD_CONNECT_TIMEOUT = 2; |
| /** Command indicating that {@link #LINGER_TIMEOUT_MILLIS} has been reached. */ |
| private static final int CMD_LINGER_TIMEOUT = 3; |
| /** |
| * Command indicating that the service has connected. |
| * |
| * <p>{@link Message#obj} is the connected {@link IEuiccService} implementation. |
| */ |
| private static final int CMD_SERVICE_CONNECTED = 4; |
| /** Command indicating that the service has disconnected. */ |
| private static final int CMD_SERVICE_DISCONNECTED = 5; |
| /** |
| * Command indicating that a command has completed and the callback should be executed. |
| * |
| * <p>{@link Message#obj} is a {@link Runnable} which will trigger the callback. |
| */ |
| private static final int CMD_COMMAND_COMPLETE = 6; |
| |
| // Commands corresponding with EuiccService APIs. Keep isEuiccCommand in sync with any changes. |
| private static final int CMD_GET_EID = 100; |
| private static final int CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA = 101; |
| private static final int CMD_DOWNLOAD_SUBSCRIPTION = 102; |
| private static final int CMD_GET_EUICC_PROFILE_INFO_LIST = 103; |
| private static final int CMD_GET_DEFAULT_DOWNLOADABLE_SUBSCRIPTION_LIST = 104; |
| private static final int CMD_GET_EUICC_INFO = 105; |
| private static final int CMD_DELETE_SUBSCRIPTION = 106; |
| private static final int CMD_SWITCH_TO_SUBSCRIPTION = 107; |
| private static final int CMD_UPDATE_SUBSCRIPTION_NICKNAME = 108; |
| private static final int CMD_ERASE_SUBSCRIPTIONS = 109; |
| private static final int CMD_RETAIN_SUBSCRIPTIONS = 110; |
| private static final int CMD_GET_OTA_STATUS = 111; |
| private static final int CMD_START_OTA_IF_NECESSARY = 112; |
| private static final int CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS = 113; |
| |
| private static boolean isEuiccCommand(int what) { |
| return what >= CMD_GET_EID; |
| } |
| |
| /** Flags to use when querying PackageManager for Euicc component implementations. */ |
| private static final int EUICC_QUERY_FLAGS = |
| PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING |
| | PackageManager.GET_RESOLVED_FILTER; |
| |
| /** |
| * Return the activity info of the activity to start for the given intent, or null if none |
| * was found. |
| */ |
| public static ActivityInfo findBestActivity(PackageManager packageManager, Intent intent) { |
| List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, |
| EUICC_QUERY_FLAGS); |
| ActivityInfo bestComponent = |
| (ActivityInfo) findBestComponent(packageManager, resolveInfoList); |
| if (bestComponent == null) { |
| Log.w(TAG, "No valid component found for intent: " + intent); |
| } |
| return bestComponent; |
| } |
| |
| /** |
| * Return the component info of the EuiccService to bind to, or null if none were found. |
| */ |
| public static ComponentInfo findBestComponent(PackageManager packageManager) { |
| Intent intent = new Intent(EuiccService.EUICC_SERVICE_INTERFACE); |
| List<ResolveInfo> resolveInfoList = |
| packageManager.queryIntentServices(intent, EUICC_QUERY_FLAGS); |
| ComponentInfo bestComponent = findBestComponent(packageManager, resolveInfoList); |
| if (bestComponent == null) { |
| Log.w(TAG, "No valid EuiccService implementation found"); |
| } |
| return bestComponent; |
| } |
| |
| /** Base class for all command callbacks. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface BaseEuiccCommandCallback { |
| /** Called when a command fails because the service is or became unavailable. */ |
| void onEuiccServiceUnavailable(); |
| } |
| |
| /** Callback class for {@link #getEid}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface GetEidCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the EID lookup has completed. */ |
| void onGetEidComplete(String eid); |
| } |
| |
| /** Callback class for {@link #getOtaStatus}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface GetOtaStatusCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the getting OTA status lookup has completed. */ |
| void onGetOtaStatusComplete(@OtaStatus int status); |
| } |
| |
| /** Callback class for {@link #startOtaIfNecessary}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface OtaStatusChangedCallback extends BaseEuiccCommandCallback { |
| /** |
| * Called when OTA status is changed to {@link EuiccM}. */ |
| void onOtaStatusChanged(int status); |
| } |
| |
| static class GetMetadataRequest { |
| DownloadableSubscription mSubscription; |
| boolean mForceDeactivateSim; |
| GetMetadataCommandCallback mCallback; |
| } |
| |
| /** Callback class for {@link #getDownloadableSubscriptionMetadata}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface GetMetadataCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the metadata lookup has completed (though it may have failed). */ |
| void onGetMetadataComplete(int cardId, GetDownloadableSubscriptionMetadataResult result); |
| } |
| |
| static class DownloadRequest { |
| DownloadableSubscription mSubscription; |
| boolean mSwitchAfterDownload; |
| boolean mForceDeactivateSim; |
| DownloadCommandCallback mCallback; |
| Bundle mResolvedBundle; |
| } |
| |
| /** Callback class for {@link #downloadSubscription}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface DownloadCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the download has completed (though it may have failed). */ |
| void onDownloadComplete(DownloadSubscriptionResult result); |
| } |
| |
| interface GetEuiccProfileInfoListCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the list has completed (though it may have failed). */ |
| void onListComplete(GetEuiccProfileInfoListResult result); |
| } |
| |
| static class GetDefaultListRequest { |
| boolean mForceDeactivateSim; |
| GetDefaultListCommandCallback mCallback; |
| } |
| |
| /** Callback class for {@link #getDefaultDownloadableSubscriptionList}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface GetDefaultListCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the list has completed (though it may have failed). */ |
| void onGetDefaultListComplete(int cardId, |
| GetDefaultDownloadableSubscriptionListResult result); |
| } |
| |
| /** Callback class for {@link #getEuiccInfo}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface GetEuiccInfoCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the EuiccInfo lookup has completed. */ |
| void onGetEuiccInfoComplete(EuiccInfo euiccInfo); |
| } |
| |
| static class DeleteRequest { |
| String mIccid; |
| DeleteCommandCallback mCallback; |
| } |
| |
| /** Callback class for {@link #deleteSubscription}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface DeleteCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the delete has completed (though it may have failed). */ |
| void onDeleteComplete(int result); |
| } |
| |
| static class SwitchRequest { |
| @Nullable String mIccid; |
| boolean mForceDeactivateSim; |
| SwitchCommandCallback mCallback; |
| } |
| |
| /** Callback class for {@link #switchToSubscription}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface SwitchCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the switch has completed (though it may have failed). */ |
| void onSwitchComplete(int result); |
| } |
| |
| static class UpdateNicknameRequest { |
| String mIccid; |
| String mNickname; |
| UpdateNicknameCommandCallback mCallback; |
| } |
| |
| /** Callback class for {@link #updateSubscriptionNickname}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface UpdateNicknameCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the update has completed (though it may have failed). */ |
| void onUpdateNicknameComplete(int result); |
| } |
| |
| /** |
| * Callback class for {@link #eraseSubscriptions} and {@link #eraseSubscriptionsWithOptions}. |
| */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface EraseCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the erase has completed (though it may have failed). */ |
| void onEraseComplete(int result); |
| } |
| |
| /** Callback class for {@link #retainSubscriptions}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public interface RetainSubscriptionsCommandCallback extends BaseEuiccCommandCallback { |
| /** Called when the retain command has completed (though it may have failed). */ |
| void onRetainSubscriptionsComplete(int result); |
| } |
| |
| private Context mContext; |
| private PackageManager mPm; |
| private TelephonyManager mTm; |
| private SubscriptionManager mSm; |
| |
| private final PackageMonitor mPackageMonitor = new EuiccPackageMonitor(); |
| private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) { |
| // On user unlock, new components might become available, so rebind if needed. This |
| // can never make a component unavailable so there's never a need to force a |
| // rebind. |
| sendMessage(CMD_PACKAGE_CHANGE); |
| } |
| } |
| }; |
| |
| /** Set to the current component we should bind to except in {@link UnavailableState}. */ |
| private @Nullable ServiceInfo mSelectedComponent; |
| |
| /** Set to the currently connected EuiccService implementation in {@link ConnectedState}. */ |
| private @Nullable IEuiccService mEuiccService; |
| |
| /** The callbacks for all (asynchronous) commands which are currently in flight. */ |
| private Set<BaseEuiccCommandCallback> mActiveCommandCallbacks = new ArraySet<>(); |
| |
| @VisibleForTesting(visibility = PACKAGE) public UnavailableState mUnavailableState; |
| @VisibleForTesting(visibility = PACKAGE) public AvailableState mAvailableState; |
| @VisibleForTesting(visibility = PACKAGE) public BindingState mBindingState; |
| @VisibleForTesting(visibility = PACKAGE) public DisconnectedState mDisconnectedState; |
| @VisibleForTesting(visibility = PACKAGE) public ConnectedState mConnectedState; |
| |
| EuiccConnector(Context context) { |
| super(TAG); |
| init(context); |
| } |
| |
| @VisibleForTesting(visibility = PACKAGE) |
| public EuiccConnector(Context context, Looper looper) { |
| super(TAG, looper); |
| init(context); |
| } |
| |
| private void init(Context context) { |
| mContext = context; |
| mPm = context.getPackageManager(); |
| mTm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); |
| mSm = (SubscriptionManager) |
| context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); |
| |
| // Unavailable/Available both monitor for package changes and update mSelectedComponent but |
| // do not need to adjust the binding. |
| mUnavailableState = new UnavailableState(); |
| addState(mUnavailableState); |
| mAvailableState = new AvailableState(); |
| addState(mAvailableState, mUnavailableState); |
| |
| mBindingState = new BindingState(); |
| addState(mBindingState); |
| |
| // Disconnected/Connected both monitor for package changes and reestablish the active |
| // binding if necessary. |
| mDisconnectedState = new DisconnectedState(); |
| addState(mDisconnectedState); |
| mConnectedState = new ConnectedState(); |
| addState(mConnectedState, mDisconnectedState); |
| |
| mSelectedComponent = findBestComponent(); |
| setInitialState(mSelectedComponent != null ? mAvailableState : mUnavailableState); |
| |
| mPackageMonitor.register(mContext, null /* thread */, false /* externalStorage */); |
| mContext.registerReceiver( |
| mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED)); |
| |
| start(); |
| } |
| |
| @Override |
| public void onHalting() { |
| mPackageMonitor.unregister(); |
| mContext.unregisterReceiver(mUserUnlockedReceiver); |
| } |
| |
| /** Asynchronously fetch the EID. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void getEid(int cardId, GetEidCommandCallback callback) { |
| sendMessage(CMD_GET_EID, cardId, 0 /* arg2 */, callback); |
| } |
| |
| /** Asynchronously get OTA status. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void getOtaStatus(int cardId, GetOtaStatusCommandCallback callback) { |
| sendMessage(CMD_GET_OTA_STATUS, cardId, 0 /* arg2 */, callback); |
| } |
| |
| /** Asynchronously perform OTA update. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void startOtaIfNecessary(int cardId, OtaStatusChangedCallback callback) { |
| sendMessage(CMD_START_OTA_IF_NECESSARY, cardId, 0 /* arg2 */, callback); |
| } |
| |
| /** Asynchronously fetch metadata for the given downloadable subscription. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void getDownloadableSubscriptionMetadata(int cardId, |
| DownloadableSubscription subscription, |
| boolean forceDeactivateSim, GetMetadataCommandCallback callback) { |
| GetMetadataRequest request = |
| new GetMetadataRequest(); |
| request.mSubscription = subscription; |
| request.mForceDeactivateSim = forceDeactivateSim; |
| request.mCallback = callback; |
| sendMessage(CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA, cardId, 0 /* arg2 */, request); |
| } |
| |
| /** Asynchronously download the given subscription. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void downloadSubscription(int cardId, DownloadableSubscription subscription, |
| boolean switchAfterDownload, boolean forceDeactivateSim, |
| Bundle resolvedBundle, DownloadCommandCallback callback) { |
| DownloadRequest request = new DownloadRequest(); |
| request.mSubscription = subscription; |
| request.mSwitchAfterDownload = switchAfterDownload; |
| request.mForceDeactivateSim = forceDeactivateSim; |
| request.mResolvedBundle = resolvedBundle; |
| request.mCallback = callback; |
| sendMessage(CMD_DOWNLOAD_SUBSCRIPTION, cardId, 0 /* arg2 */, request); |
| } |
| |
| void getEuiccProfileInfoList(int cardId, GetEuiccProfileInfoListCommandCallback callback) { |
| sendMessage(CMD_GET_EUICC_PROFILE_INFO_LIST, cardId, 0 /* arg2 */, callback); |
| } |
| |
| /** Asynchronously fetch the default downloadable subscription list. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void getDefaultDownloadableSubscriptionList(int cardId, |
| boolean forceDeactivateSim, GetDefaultListCommandCallback callback) { |
| GetDefaultListRequest request = new GetDefaultListRequest(); |
| request.mForceDeactivateSim = forceDeactivateSim; |
| request.mCallback = callback; |
| sendMessage(CMD_GET_DEFAULT_DOWNLOADABLE_SUBSCRIPTION_LIST, cardId, 0 /* arg2 */, request); |
| } |
| |
| /** Asynchronously fetch the {@link EuiccInfo}. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void getEuiccInfo(int cardId, GetEuiccInfoCommandCallback callback) { |
| sendMessage(CMD_GET_EUICC_INFO, cardId, 0 /* arg2 */, callback); |
| } |
| |
| /** Asynchronously delete the given subscription. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void deleteSubscription(int cardId, String iccid, DeleteCommandCallback callback) { |
| DeleteRequest request = new DeleteRequest(); |
| request.mIccid = iccid; |
| request.mCallback = callback; |
| sendMessage(CMD_DELETE_SUBSCRIPTION, cardId, 0 /* arg2 */, request); |
| } |
| |
| /** Asynchronously switch to the given subscription. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void switchToSubscription(int cardId, @Nullable String iccid, boolean forceDeactivateSim, |
| SwitchCommandCallback callback) { |
| SwitchRequest request = new SwitchRequest(); |
| request.mIccid = iccid; |
| request.mForceDeactivateSim = forceDeactivateSim; |
| request.mCallback = callback; |
| sendMessage(CMD_SWITCH_TO_SUBSCRIPTION, cardId, 0 /* arg2 */, request); |
| } |
| |
| /** Asynchronously update the nickname of the given subscription. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void updateSubscriptionNickname(int cardId, |
| String iccid, String nickname, UpdateNicknameCommandCallback callback) { |
| UpdateNicknameRequest request = new UpdateNicknameRequest(); |
| request.mIccid = iccid; |
| request.mNickname = nickname; |
| request.mCallback = callback; |
| sendMessage(CMD_UPDATE_SUBSCRIPTION_NICKNAME, cardId, 0 /* arg2 */, request); |
| } |
| |
| /** Asynchronously erase operational profiles on the eUICC. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void eraseSubscriptions(int cardId, EraseCommandCallback callback) { |
| sendMessage(CMD_ERASE_SUBSCRIPTIONS, cardId, 0 /* arg2 */, callback); |
| } |
| |
| /** Asynchronously erase specific profiles on the eUICC. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void eraseSubscriptionsWithOptions( |
| int cardId, @ResetOption int options, EraseCommandCallback callback) { |
| sendMessage(CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS, cardId, options, callback); |
| } |
| |
| /** Asynchronously ensure that all profiles will be retained on the next factory reset. */ |
| @VisibleForTesting(visibility = PACKAGE) |
| public void retainSubscriptions(int cardId, RetainSubscriptionsCommandCallback callback) { |
| sendMessage(CMD_RETAIN_SUBSCRIPTIONS, cardId, 0 /* arg2 */, callback); |
| } |
| |
| /** |
| * State in which no EuiccService is available. |
| * |
| * <p>All incoming commands will be rejected through |
| * {@link BaseEuiccCommandCallback#onEuiccServiceUnavailable()}. |
| * |
| * <p>Package state changes will lead to transitions between {@link UnavailableState} and |
| * {@link AvailableState} depending on whether an EuiccService becomes unavailable or |
| * available. |
| */ |
| private class UnavailableState extends State { |
| @Override |
| public boolean processMessage(Message message) { |
| if (message.what == CMD_PACKAGE_CHANGE) { |
| mSelectedComponent = findBestComponent(); |
| if (mSelectedComponent != null) { |
| transitionTo(mAvailableState); |
| } else if (getCurrentState() != mUnavailableState) { |
| transitionTo(mUnavailableState); |
| } |
| updateSubscriptionInfoListForAllAccessibleEuiccs(); |
| return HANDLED; |
| } else if (isEuiccCommand(message.what)) { |
| BaseEuiccCommandCallback callback = getCallback(message); |
| callback.onEuiccServiceUnavailable(); |
| return HANDLED; |
| } |
| |
| return NOT_HANDLED; |
| } |
| } |
| |
| /** |
| * State in which a EuiccService is available, but no binding is established or in the process |
| * of being established. |
| * |
| * <p>If a command is received, this state will defer the message and enter {@link BindingState} |
| * to bring up the binding. |
| */ |
| private class AvailableState extends State { |
| @Override |
| public boolean processMessage(Message message) { |
| if (isEuiccCommand(message.what)) { |
| deferMessage(message); |
| transitionTo(mBindingState); |
| return HANDLED; |
| } |
| |
| return NOT_HANDLED; |
| } |
| } |
| |
| /** |
| * State in which we are binding to the current EuiccService. |
| * |
| * <p>This is a transient state. If bindService returns true, we enter {@link DisconnectedState} |
| * while waiting for the binding to be established. If it returns false, we move back to |
| * {@link AvailableState}. |
| * |
| * <p>Any received messages will be deferred. |
| */ |
| private class BindingState extends State { |
| @Override |
| public void enter() { |
| if (createBinding()) { |
| transitionTo(mDisconnectedState); |
| } else { |
| // createBinding() should generally not return false since we've already performed |
| // Intent resolution, but it's always possible that the package state changes |
| // asynchronously. Transition to available for now, and if the package state has |
| // changed, we'll process that event and move to mUnavailableState as needed. |
| transitionTo(mAvailableState); |
| } |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| deferMessage(message); |
| return HANDLED; |
| } |
| } |
| |
| /** |
| * State in which a binding is established, but not currently connected. |
| * |
| * <p>We wait up to {@link #BIND_TIMEOUT_MILLIS} for the binding to establish. If it doesn't, |
| * we go back to {@link AvailableState} to try again. |
| * |
| * <p>Package state changes will cause us to unbind and move to {@link BindingState} to |
| * reestablish the binding if the selected component has changed or if a forced rebind is |
| * necessary. |
| * |
| * <p>Any received commands will be deferred. |
| */ |
| private class DisconnectedState extends State { |
| @Override |
| public void enter() { |
| sendMessageDelayed(CMD_CONNECT_TIMEOUT, BIND_TIMEOUT_MILLIS); |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| if (message.what == CMD_SERVICE_CONNECTED) { |
| mEuiccService = (IEuiccService) message.obj; |
| transitionTo(mConnectedState); |
| return HANDLED; |
| } else if (message.what == CMD_PACKAGE_CHANGE) { |
| ServiceInfo bestComponent = findBestComponent(); |
| String affectedPackage = (String) message.obj; |
| boolean isSameComponent; |
| if (bestComponent == null) { |
| isSameComponent = mSelectedComponent != null; |
| } else { |
| isSameComponent = mSelectedComponent == null |
| || Objects.equals( |
| bestComponent.getComponentName(), |
| mSelectedComponent.getComponentName()); |
| } |
| boolean forceRebind = bestComponent != null |
| && Objects.equals(bestComponent.packageName, affectedPackage); |
| if (!isSameComponent || forceRebind) { |
| unbind(); |
| mSelectedComponent = bestComponent; |
| if (mSelectedComponent == null) { |
| transitionTo(mUnavailableState); |
| } else { |
| transitionTo(mBindingState); |
| } |
| } |
| updateSubscriptionInfoListForAllAccessibleEuiccs(); |
| return HANDLED; |
| } else if (message.what == CMD_CONNECT_TIMEOUT) { |
| transitionTo(mAvailableState); |
| return HANDLED; |
| } else if (isEuiccCommand(message.what)) { |
| deferMessage(message); |
| return HANDLED; |
| } |
| |
| return NOT_HANDLED; |
| } |
| } |
| |
| /** |
| * State in which the binding is connected. |
| * |
| * <p>Commands will be processed as long as we're in this state. We wait up to |
| * {@link #LINGER_TIMEOUT_MILLIS} between commands; if this timeout is reached, we will drop the |
| * binding until the next command is received. |
| */ |
| private class ConnectedState extends State { |
| @Override |
| public void enter() { |
| removeMessages(CMD_CONNECT_TIMEOUT); |
| sendMessageDelayed(CMD_LINGER_TIMEOUT, LINGER_TIMEOUT_MILLIS); |
| } |
| |
| @Override |
| public boolean processMessage(Message message) { |
| if (message.what == CMD_SERVICE_DISCONNECTED) { |
| mEuiccService = null; |
| transitionTo(mDisconnectedState); |
| return HANDLED; |
| } else if (message.what == CMD_LINGER_TIMEOUT) { |
| unbind(); |
| transitionTo(mAvailableState); |
| return HANDLED; |
| } else if (message.what == CMD_COMMAND_COMPLETE) { |
| Runnable runnable = (Runnable) message.obj; |
| runnable.run(); |
| return HANDLED; |
| } else if (isEuiccCommand(message.what)) { |
| final BaseEuiccCommandCallback callback = getCallback(message); |
| onCommandStart(callback); |
| final int cardId = message.arg1; |
| final int slotId = getSlotIdFromCardId(cardId); |
| try { |
| switch (message.what) { |
| case CMD_GET_EID: { |
| mEuiccService.getEid(slotId, |
| new IGetEidCallback.Stub() { |
| @Override |
| public void onSuccess(String eid) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((GetEidCommandCallback) callback) |
| .onGetEidComplete(eid); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: { |
| GetMetadataRequest request = (GetMetadataRequest) message.obj; |
| mEuiccService.getDownloadableSubscriptionMetadata(slotId, |
| request.mSubscription, |
| request.mForceDeactivateSim, |
| new IGetDownloadableSubscriptionMetadataCallback.Stub() { |
| @Override |
| public void onComplete( |
| GetDownloadableSubscriptionMetadataResult result) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((GetMetadataCommandCallback) callback) |
| .onGetMetadataComplete(cardId, result); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_DOWNLOAD_SUBSCRIPTION: { |
| DownloadRequest request = (DownloadRequest) message.obj; |
| mEuiccService.downloadSubscription(slotId, |
| request.mSubscription, |
| request.mSwitchAfterDownload, |
| request.mForceDeactivateSim, |
| request.mResolvedBundle, |
| new IDownloadSubscriptionCallback.Stub() { |
| @Override |
| public void onComplete(DownloadSubscriptionResult result) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((DownloadCommandCallback) callback) |
| .onDownloadComplete(result); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_GET_EUICC_PROFILE_INFO_LIST: { |
| mEuiccService.getEuiccProfileInfoList(slotId, |
| new IGetEuiccProfileInfoListCallback.Stub() { |
| @Override |
| public void onComplete( |
| GetEuiccProfileInfoListResult result) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((GetEuiccProfileInfoListCommandCallback) callback) |
| .onListComplete(result); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_GET_DEFAULT_DOWNLOADABLE_SUBSCRIPTION_LIST: { |
| GetDefaultListRequest request = (GetDefaultListRequest) message.obj; |
| mEuiccService.getDefaultDownloadableSubscriptionList(slotId, |
| request.mForceDeactivateSim, |
| new IGetDefaultDownloadableSubscriptionListCallback.Stub() { |
| @Override |
| public void onComplete( |
| GetDefaultDownloadableSubscriptionListResult result |
| ) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((GetDefaultListCommandCallback) callback) |
| .onGetDefaultListComplete(cardId, result); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_GET_EUICC_INFO: { |
| mEuiccService.getEuiccInfo(slotId, |
| new IGetEuiccInfoCallback.Stub() { |
| @Override |
| public void onSuccess(EuiccInfo euiccInfo) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((GetEuiccInfoCommandCallback) callback) |
| .onGetEuiccInfoComplete(euiccInfo); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_DELETE_SUBSCRIPTION: { |
| DeleteRequest request = (DeleteRequest) message.obj; |
| mEuiccService.deleteSubscription(slotId, request.mIccid, |
| new IDeleteSubscriptionCallback.Stub() { |
| @Override |
| public void onComplete(int result) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((DeleteCommandCallback) callback) |
| .onDeleteComplete(result); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_SWITCH_TO_SUBSCRIPTION: { |
| SwitchRequest request = (SwitchRequest) message.obj; |
| mEuiccService.switchToSubscription(slotId, request.mIccid, |
| request.mForceDeactivateSim, |
| new ISwitchToSubscriptionCallback.Stub() { |
| @Override |
| public void onComplete(int result) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((SwitchCommandCallback) callback) |
| .onSwitchComplete(result); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_UPDATE_SUBSCRIPTION_NICKNAME: { |
| UpdateNicknameRequest request = (UpdateNicknameRequest) message.obj; |
| mEuiccService.updateSubscriptionNickname(slotId, request.mIccid, |
| request.mNickname, |
| new IUpdateSubscriptionNicknameCallback.Stub() { |
| @Override |
| public void onComplete(int result) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((UpdateNicknameCommandCallback) callback) |
| .onUpdateNicknameComplete(result); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_ERASE_SUBSCRIPTIONS: { |
| mEuiccService.eraseSubscriptions(slotId, |
| new IEraseSubscriptionsCallback.Stub() { |
| @Override |
| public void onComplete(int result) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((EraseCommandCallback) callback) |
| .onEraseComplete(result); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS: { |
| mEuiccService.eraseSubscriptionsWithOptions(slotId, |
| message.arg2 /* options */, |
| new IEraseSubscriptionsCallback.Stub() { |
| @Override |
| public void onComplete(int result) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((EraseCommandCallback) callback) |
| .onEraseComplete(result); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_RETAIN_SUBSCRIPTIONS: { |
| mEuiccService.retainSubscriptionsForFactoryReset(slotId, |
| new IRetainSubscriptionsForFactoryResetCallback.Stub() { |
| @Override |
| public void onComplete(int result) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((RetainSubscriptionsCommandCallback) callback) |
| .onRetainSubscriptionsComplete(result); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_GET_OTA_STATUS: { |
| mEuiccService.getOtaStatus(slotId, |
| new IGetOtaStatusCallback.Stub() { |
| @Override |
| public void onSuccess(@OtaStatus int status) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((GetOtaStatusCommandCallback) callback) |
| .onGetOtaStatusComplete(status); |
| onCommandEnd(callback); |
| }); |
| } |
| }); |
| break; |
| } |
| case CMD_START_OTA_IF_NECESSARY: { |
| mEuiccService.startOtaIfNecessary(slotId, |
| new IOtaStatusChangedCallback.Stub() { |
| @Override |
| public void onOtaStatusChanged(int status) |
| throws RemoteException { |
| if (status == EuiccManager.EUICC_OTA_IN_PROGRESS) { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((OtaStatusChangedCallback) callback) |
| .onOtaStatusChanged(status); |
| }); |
| } else { |
| sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> { |
| ((OtaStatusChangedCallback) callback) |
| .onOtaStatusChanged(status); |
| onCommandEnd(callback); |
| }); |
| } |
| } |
| }); |
| break; |
| } |
| default: { |
| Log.wtf(TAG, "Unimplemented eUICC command: " + message.what); |
| callback.onEuiccServiceUnavailable(); |
| onCommandEnd(callback); |
| return HANDLED; |
| } |
| } |
| } catch (Exception e) { |
| // If this is a RemoteException, we expect to be disconnected soon. For other |
| // exceptions, this is a bug in the EuiccService implementation, but we must |
| // not let it crash the phone process. |
| Log.w(TAG, "Exception making binder call to EuiccService", e); |
| callback.onEuiccServiceUnavailable(); |
| onCommandEnd(callback); |
| } |
| |
| return HANDLED; |
| } |
| |
| return NOT_HANDLED; |
| } |
| |
| @Override |
| public void exit() { |
| removeMessages(CMD_LINGER_TIMEOUT); |
| // Dispatch callbacks for all in-flight commands; they will no longer succeed. (The |
| // remote process cannot possibly trigger a callback at this stage because the |
| // connection has dropped). |
| for (BaseEuiccCommandCallback callback : mActiveCommandCallbacks) { |
| callback.onEuiccServiceUnavailable(); |
| } |
| mActiveCommandCallbacks.clear(); |
| } |
| } |
| |
| private static BaseEuiccCommandCallback getCallback(Message message) { |
| switch (message.what) { |
| case CMD_GET_EID: |
| case CMD_GET_EUICC_PROFILE_INFO_LIST: |
| case CMD_GET_EUICC_INFO: |
| case CMD_ERASE_SUBSCRIPTIONS: |
| case CMD_ERASE_SUBSCRIPTIONS_WITH_OPTIONS: |
| case CMD_RETAIN_SUBSCRIPTIONS: |
| case CMD_GET_OTA_STATUS: |
| case CMD_START_OTA_IF_NECESSARY: |
| return (BaseEuiccCommandCallback) message.obj; |
| case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: |
| return ((GetMetadataRequest) message.obj).mCallback; |
| case CMD_DOWNLOAD_SUBSCRIPTION: |
| return ((DownloadRequest) message.obj).mCallback; |
| case CMD_GET_DEFAULT_DOWNLOADABLE_SUBSCRIPTION_LIST: |
| return ((GetDefaultListRequest) message.obj).mCallback; |
| case CMD_DELETE_SUBSCRIPTION: |
| return ((DeleteRequest) message.obj).mCallback; |
| case CMD_SWITCH_TO_SUBSCRIPTION: |
| return ((SwitchRequest) message.obj).mCallback; |
| case CMD_UPDATE_SUBSCRIPTION_NICKNAME: |
| return ((UpdateNicknameRequest) message.obj).mCallback; |
| default: |
| throw new IllegalArgumentException("Unsupported message: " + message.what); |
| } |
| } |
| |
| /** |
| * Gets the slot ID from the card ID. |
| */ |
| private int getSlotIdFromCardId(int cardId) { |
| if (cardId == TelephonyManager.UNSUPPORTED_CARD_ID |
| || cardId == TelephonyManager.UNINITIALIZED_CARD_ID) { |
| return SubscriptionManager.INVALID_SIM_SLOT_INDEX; |
| } |
| TelephonyManager tm = (TelephonyManager) |
| mContext.getSystemService(Context.TELEPHONY_SERVICE); |
| List<UiccCardInfo> infos = tm.getUiccCardsInfo(); |
| if (infos == null || infos.size() == 0) { |
| return SubscriptionManager.INVALID_SIM_SLOT_INDEX; |
| } |
| int slotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX; |
| for (UiccCardInfo info : infos) { |
| if (info.getCardId() == cardId) { |
| slotId = info.getSlotIndex(); |
| } |
| } |
| return slotId; |
| } |
| |
| /** Call this at the beginning of the execution of any command. */ |
| private void onCommandStart(BaseEuiccCommandCallback callback) { |
| mActiveCommandCallbacks.add(callback); |
| removeMessages(CMD_LINGER_TIMEOUT); |
| } |
| |
| /** Call this at the end of execution of any command (whether or not it succeeded). */ |
| private void onCommandEnd(BaseEuiccCommandCallback callback) { |
| if (!mActiveCommandCallbacks.remove(callback)) { |
| Log.wtf(TAG, "Callback already removed from mActiveCommandCallbacks"); |
| } |
| if (mActiveCommandCallbacks.isEmpty()) { |
| sendMessageDelayed(CMD_LINGER_TIMEOUT, LINGER_TIMEOUT_MILLIS); |
| } |
| } |
| |
| /** Return the service info of the EuiccService to bind to, or null if none were found. */ |
| @Nullable |
| private ServiceInfo findBestComponent() { |
| return (ServiceInfo) findBestComponent(mPm); |
| } |
| |
| /** |
| * Bring up a binding to the currently-selected component. |
| * |
| * <p>Returns true if we've successfully bound to the service. |
| */ |
| private boolean createBinding() { |
| if (mSelectedComponent == null) { |
| Log.wtf(TAG, "Attempting to create binding but no component is selected"); |
| return false; |
| } |
| Intent intent = new Intent(EuiccService.EUICC_SERVICE_INTERFACE); |
| intent.setComponent(mSelectedComponent.getComponentName()); |
| // We bind this as a foreground service because it is operating directly on the SIM, and we |
| // do not want it subjected to power-savings restrictions while doing so. |
| return mContext.bindService(intent, this, |
| Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE); |
| } |
| |
| private void unbind() { |
| mEuiccService = null; |
| mContext.unbindService(this); |
| } |
| |
| private static ComponentInfo findBestComponent( |
| PackageManager packageManager, List<ResolveInfo> resolveInfoList) { |
| int bestPriority = Integer.MIN_VALUE; |
| ComponentInfo bestComponent = null; |
| if (resolveInfoList != null) { |
| for (ResolveInfo resolveInfo : resolveInfoList) { |
| if (!isValidEuiccComponent(packageManager, resolveInfo)) { |
| continue; |
| } |
| |
| if (resolveInfo.filter.getPriority() > bestPriority) { |
| bestPriority = resolveInfo.filter.getPriority(); |
| bestComponent = resolveInfo.getComponentInfo(); |
| } |
| } |
| } |
| |
| return bestComponent; |
| } |
| |
| private static boolean isValidEuiccComponent( |
| PackageManager packageManager, ResolveInfo resolveInfo) { |
| ComponentInfo componentInfo = resolveInfo.getComponentInfo(); |
| String packageName = componentInfo.getComponentName().getPackageName(); |
| |
| // Verify that the app is privileged (via granting of a privileged permission). |
| if (packageManager.checkPermission( |
| Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS, packageName) |
| != PackageManager.PERMISSION_GRANTED) { |
| Log.wtf(TAG, "Package " + packageName |
| + " does not declare WRITE_EMBEDDED_SUBSCRIPTIONS"); |
| return false; |
| } |
| |
| // Verify that only the system can access the component. |
| final String permission; |
| if (componentInfo instanceof ServiceInfo) { |
| permission = ((ServiceInfo) componentInfo).permission; |
| } else if (componentInfo instanceof ActivityInfo) { |
| permission = ((ActivityInfo) componentInfo).permission; |
| } else { |
| throw new IllegalArgumentException("Can only verify services/activities"); |
| } |
| if (!TextUtils.equals(permission, Manifest.permission.BIND_EUICC_SERVICE)) { |
| Log.wtf(TAG, "Package " + packageName |
| + " does not require the BIND_EUICC_SERVICE permission"); |
| return false; |
| } |
| |
| // Verify that the component declares a priority. |
| if (resolveInfo.filter == null || resolveInfo.filter.getPriority() == 0) { |
| Log.wtf(TAG, "Package " + packageName + " does not specify a priority"); |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void onServiceConnected(ComponentName name, IBinder service) { |
| IEuiccService euiccService = IEuiccService.Stub.asInterface(service); |
| sendMessage(CMD_SERVICE_CONNECTED, euiccService); |
| } |
| |
| @Override |
| public void onServiceDisconnected(ComponentName name) { |
| sendMessage(CMD_SERVICE_DISCONNECTED); |
| } |
| |
| private class EuiccPackageMonitor extends PackageMonitor { |
| @Override |
| public void onPackageAdded(String packageName, int reason) { |
| sendPackageChange(packageName, true /* forceUnbindForThisPackage */); |
| } |
| |
| @Override |
| public void onPackageRemoved(String packageName, int reason) { |
| sendPackageChange(packageName, true /* forceUnbindForThisPackage */); |
| } |
| |
| @Override |
| public void onPackageUpdateFinished(String packageName, int uid) { |
| sendPackageChange(packageName, true /* forceUnbindForThisPackage */); |
| } |
| |
| @Override |
| public void onPackageModified(String packageName) { |
| sendPackageChange(packageName, false /* forceUnbindForThisPackage */); |
| } |
| |
| @Override |
| public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { |
| if (doit) { |
| for (String packageName : packages) { |
| sendPackageChange(packageName, true /* forceUnbindForThisPackage */); |
| } |
| } |
| return super.onHandleForceStop(intent, packages, uid, doit); |
| } |
| |
| private void sendPackageChange(String packageName, boolean forceUnbindForThisPackage) { |
| sendMessage(CMD_PACKAGE_CHANGE, forceUnbindForThisPackage ? packageName : null); |
| } |
| } |
| |
| @Override |
| protected void unhandledMessage(Message msg) { |
| IState state = getCurrentState(); |
| Log.wtf(TAG, "Unhandled message " + msg.what + " in state " |
| + (state == null ? "null" : state.getName())); |
| } |
| |
| @Override |
| public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { |
| super.dump(fd, pw, args); |
| pw.println("mSelectedComponent=" + mSelectedComponent); |
| pw.println("mEuiccService=" + mEuiccService); |
| pw.println("mActiveCommandCount=" + mActiveCommandCallbacks.size()); |
| } |
| |
| private void updateSubscriptionInfoListForAllAccessibleEuiccs() { |
| if (mTm.getCardIdForDefaultEuicc() == TelephonyManager.UNSUPPORTED_CARD_ID) { |
| // Device does not support card ID |
| mSm.requestEmbeddedSubscriptionInfoListRefresh(); |
| } else { |
| for (UiccCardInfo cardInfo : mTm.getUiccCardsInfo()) { |
| if (cardInfo.isEuicc()) { |
| mSm.requestEmbeddedSubscriptionInfoListRefresh(cardInfo.getCardId()); |
| } |
| } |
| } |
| } |
| } |