blob: b922568706cfffb413633fc9335b4e034401ccd6 [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 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.IEuiccServiceDumpResultCallback;
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.PackageChangeReceiver;
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.telephony.util.TelephonyUtils;
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).
*/
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 final int CMD_DUMP_EUICC_SERVICE = 114;
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_DIRECT_BOOT_AUTO
| 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);
}
/** Callback class for {@link #dumpEuiccService(DumpEuiccCommandCallback)} }*/
@VisibleForTesting(visibility = PACKAGE)
public interface DumpEuiccServiceCommandCallback extends BaseEuiccCommandCallback {
/** Called when the retain command has completed (though it may have failed). */
void onDumpEuiccServiceComplete(String logs);
}
private Context mContext;
private PackageManager mPm;
private TelephonyManager mTm;
private SubscriptionManager mSm;
private final PackageChangeReceiver 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);
start();
mPackageMonitor.register(mContext, null /* thread */, null /* user */);
mContext.registerReceiver(
mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
}
@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);
}
/** Asynchronously calls the currently bound EuiccService implementation to dump its states */
@VisibleForTesting(visibility = PACKAGE)
public void dumpEuiccService(DumpEuiccServiceCommandCallback callback) {
sendMessage(CMD_DUMP_EUICC_SERVICE, TelephonyManager.UNSUPPORTED_CARD_ID /* ignored */,
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(new ComponentName(bestComponent.packageName,
bestComponent.name),
new ComponentName(mSelectedComponent.packageName, mSelectedComponent.name));
}
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;
}
case CMD_DUMP_EUICC_SERVICE: {
mEuiccService.dump(new IEuiccServiceDumpResultCallback.Stub() {
@Override
public void onComplete(String logs)
throws RemoteException {
sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
((DumpEuiccServiceCommandCallback) callback)
.onDumpEuiccServiceComplete(logs);
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:
case CMD_DUMP_EUICC_SERVICE:
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(new ComponentName(mSelectedComponent.packageName,
mSelectedComponent.name));
// 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 = TelephonyUtils.getComponentInfo(resolveInfo);
}
}
}
return bestComponent;
}
private static boolean isValidEuiccComponent(
PackageManager packageManager, ResolveInfo resolveInfo) {
ComponentInfo componentInfo = TelephonyUtils.getComponentInfo(resolveInfo);
String packageName = new ComponentName(componentInfo.packageName, componentInfo.name)
.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 PackageChangeReceiver {
@Override
public void onPackageAdded(String packageName) {
sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
}
@Override
public void onPackageRemoved(String packageName) {
sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
}
@Override
public void onPackageUpdateFinished(String packageName) {
sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
}
@Override
public void onPackageModified(String packageName) {
sendPackageChange(packageName, false /* forceUnbindForThisPackage */);
}
@Override
public void onHandleForceStop(String[] packages, boolean doit) {
if (doit) {
for (String packageName : packages) {
sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
}
}
}
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());
}
}
}
}
}