1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.internal.telephony.euicc;
17
18import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
19
20import android.Manifest;
21import android.annotation.Nullable;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.IntentFilter;
27import android.content.ServiceConnection;
28import android.content.pm.ActivityInfo;
29import android.content.pm.ComponentInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.ResolveInfo;
32import android.content.pm.ServiceInfo;
33import android.os.IBinder;
34import android.os.Looper;
35import android.os.Message;
36import android.service.euicc.EuiccService;
37import android.service.euicc.GetDefaultDownloadableSubscriptionListResult;
38import android.service.euicc.GetDownloadableSubscriptionMetadataResult;
39import android.service.euicc.GetEuiccProfileInfoListResult;
40import android.service.euicc.IDeleteSubscriptionCallback;
41import android.service.euicc.IDownloadSubscriptionCallback;
42import android.service.euicc.IEraseSubscriptionsCallback;
43import android.service.euicc.IEuiccService;
44import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
45import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
46import android.service.euicc.IGetEidCallback;
47import android.service.euicc.IGetEuiccInfoCallback;
48import android.service.euicc.IGetEuiccProfileInfoListCallback;
49import android.service.euicc.IRetainSubscriptionsForFactoryResetCallback;
50import android.service.euicc.ISwitchToSubscriptionCallback;
51import android.service.euicc.IUpdateSubscriptionNicknameCallback;
52import android.telephony.SubscriptionManager;
53import android.telephony.euicc.DownloadableSubscription;
54import android.telephony.euicc.EuiccInfo;
55import android.text.TextUtils;
56import android.util.ArraySet;
57import android.util.Log;
58
59import com.android.internal.annotations.VisibleForTesting;
60import com.android.internal.content.PackageMonitor;
61import com.android.internal.util.IState;
62import com.android.internal.util.State;
63import com.android.internal.util.StateMachine;
64
65import java.io.FileDescriptor;
66import java.io.PrintWriter;
67import java.util.List;
68import java.util.Objects;
69import java.util.Set;
70
71/**
72 * State machine which maintains the binding to the EuiccService implementation and issues commands.
73 *
74 * <p>Keeps track of the highest-priority EuiccService implementation to use. When a command comes
75 * in, brings up a binding to that service, issues the command, and lingers the binding as long as
76 * more commands are coming in. The binding is dropped after an idle timeout.
77 */
78public class EuiccConnector extends StateMachine implements ServiceConnection {
79    private static final String TAG = "EuiccConnector";
80
81    /**
82     * Maximum amount of time to wait for a connection to be established after bindService returns
83     * true or onServiceDisconnected is called (and no package change has occurred which should
84     * force us to reestablish the binding).
85     */
86    private static final int BIND_TIMEOUT_MILLIS = 30000;
87
88    /**
89     * Maximum amount of idle time to hold the binding while in {@link ConnectedState}. After this,
90     * the binding is dropped to free up memory as the EuiccService is not expected to be used
91     * frequently as part of ongoing device operation.
92     */
93    @VisibleForTesting
94    static final int LINGER_TIMEOUT_MILLIS = 60000;
95
96    /**
97     * Command indicating that a package change has occurred.
98     *
99     * <p>{@link Message#obj} is an optional package name. If set, this package has changed in a
100     * way that will permanently sever any open bindings, and if we're bound to it, the binding must
101     * be forcefully reestablished.
102     */
103    private static final int CMD_PACKAGE_CHANGE = 1;
104    /** Command indicating that {@link #BIND_TIMEOUT_MILLIS} has been reached. */
105    private static final int CMD_CONNECT_TIMEOUT = 2;
106    /** Command indicating that {@link #LINGER_TIMEOUT_MILLIS} has been reached. */
107    private static final int CMD_LINGER_TIMEOUT = 3;
108    /**
109     * Command indicating that the service has connected.
110     *
111     * <p>{@link Message#obj} is the connected {@link IEuiccService} implementation.
112     */
113    private static final int CMD_SERVICE_CONNECTED = 4;
114    /** Command indicating that the service has disconnected. */
115    private static final int CMD_SERVICE_DISCONNECTED = 5;
116    /**
117     * Command indicating that a command has completed and the callback should be executed.
118     *
119     * <p>{@link Message#obj} is a {@link Runnable} which will trigger the callback.
120     */
121    private static final int CMD_COMMAND_COMPLETE = 6;
122
123    // Commands corresponding with EuiccService APIs. Keep isEuiccCommand in sync with any changes.
124    private static final int CMD_GET_EID = 100;
125    private static final int CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA = 101;
126    private static final int CMD_DOWNLOAD_SUBSCRIPTION = 102;
127    private static final int CMD_GET_EUICC_PROFILE_INFO_LIST = 103;
128    private static final int CMD_GET_DEFAULT_DOWNLOADABLE_SUBSCRIPTION_LIST = 104;
129    private static final int CMD_GET_EUICC_INFO = 105;
130    private static final int CMD_DELETE_SUBSCRIPTION = 106;
131    private static final int CMD_SWITCH_TO_SUBSCRIPTION = 107;
132    private static final int CMD_UPDATE_SUBSCRIPTION_NICKNAME = 108;
133    private static final int CMD_ERASE_SUBSCRIPTIONS = 109;
134    private static final int CMD_RETAIN_SUBSCRIPTIONS = 110;
135
136    private static boolean isEuiccCommand(int what) {
137        return what >= CMD_GET_EID;
138    }
139
140    /** Flags to use when querying PackageManager for Euicc component implementations. */
141    private static final int EUICC_QUERY_FLAGS =
142            PackageManager.MATCH_SYSTEM_ONLY | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
143                    | PackageManager.GET_RESOLVED_FILTER;
144
145    /**
146     * Return the activity info of the activity to start for the given intent, or null if none
147     * was found.
148     */
149    public static ActivityInfo findBestActivity(PackageManager packageManager, Intent intent) {
150        List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent,
151                EUICC_QUERY_FLAGS);
152        ActivityInfo bestComponent =
153                (ActivityInfo) findBestComponent(packageManager, resolveInfoList);
154        if (bestComponent == null) {
155            Log.w(TAG, "No valid component found for intent: " + intent);
156        }
157        return bestComponent;
158    }
159
160    /**
161     * Return the component info of the EuiccService to bind to, or null if none were found.
162     */
163    public static ComponentInfo findBestComponent(PackageManager packageManager) {
164        Intent intent = new Intent(EuiccService.EUICC_SERVICE_INTERFACE);
165        List<ResolveInfo> resolveInfoList =
166                packageManager.queryIntentServices(intent, EUICC_QUERY_FLAGS);
167        ComponentInfo bestComponent = findBestComponent(packageManager, resolveInfoList);
168        if (bestComponent == null) {
169            Log.w(TAG, "No valid EuiccService implementation found");
170        }
171        return bestComponent;
172    }
173
174    /** Base class for all command callbacks. */
175    @VisibleForTesting(visibility = PACKAGE)
176    public interface BaseEuiccCommandCallback {
177        /** Called when a command fails because the service is or became unavailable. */
178        void onEuiccServiceUnavailable();
179    }
180
181    /** Callback class for {@link #getEid}. */
182    @VisibleForTesting(visibility = PACKAGE)
183    public interface GetEidCommandCallback extends BaseEuiccCommandCallback {
184        /** Called when the EID lookup has completed. */
185        void onGetEidComplete(String eid);
186    }
187
188    static class GetMetadataRequest {
189        DownloadableSubscription mSubscription;
190        boolean mForceDeactivateSim;
191        GetMetadataCommandCallback mCallback;
192    }
193
194    /** Callback class for {@link #getDownloadableSubscriptionMetadata}. */
195    @VisibleForTesting(visibility = PACKAGE)
196    public interface GetMetadataCommandCallback extends BaseEuiccCommandCallback {
197        /** Called when the metadata lookup has completed (though it may have failed). */
198        void onGetMetadataComplete(GetDownloadableSubscriptionMetadataResult result);
199    }
200
201    static class DownloadRequest {
202        DownloadableSubscription mSubscription;
203        boolean mSwitchAfterDownload;
204        boolean mForceDeactivateSim;
205        DownloadCommandCallback mCallback;
206    }
207
208    /** Callback class for {@link #downloadSubscription}. */
209    @VisibleForTesting(visibility = PACKAGE)
210    public interface DownloadCommandCallback extends BaseEuiccCommandCallback {
211        /** Called when the download has completed (though it may have failed). */
212        void onDownloadComplete(int result);
213    }
214
215    interface GetEuiccProfileInfoListCommandCallback extends BaseEuiccCommandCallback {
216        /** Called when the list has completed (though it may have failed). */
217        void onListComplete(GetEuiccProfileInfoListResult result);
218    }
219
220    static class GetDefaultListRequest {
221        boolean mForceDeactivateSim;
222        GetDefaultListCommandCallback mCallback;
223    }
224
225    /** Callback class for {@link #getDefaultDownloadableSubscriptionList}. */
226    @VisibleForTesting(visibility = PACKAGE)
227    public interface GetDefaultListCommandCallback extends BaseEuiccCommandCallback {
228        /** Called when the list has completed (though it may have failed). */
229        void onGetDefaultListComplete(GetDefaultDownloadableSubscriptionListResult result);
230    }
231
232    /** Callback class for {@link #getEuiccInfo}. */
233    @VisibleForTesting(visibility = PACKAGE)
234    public interface GetEuiccInfoCommandCallback extends BaseEuiccCommandCallback {
235        /** Called when the EuiccInfo lookup has completed. */
236        void onGetEuiccInfoComplete(EuiccInfo euiccInfo);
237    }
238
239    static class DeleteRequest {
240        String mIccid;
241        DeleteCommandCallback mCallback;
242    }
243
244    /** Callback class for {@link #deleteSubscription}. */
245    @VisibleForTesting(visibility = PACKAGE)
246    public interface DeleteCommandCallback extends BaseEuiccCommandCallback {
247        /** Called when the delete has completed (though it may have failed). */
248        void onDeleteComplete(int result);
249    }
250
251    static class SwitchRequest {
252        @Nullable String mIccid;
253        boolean mForceDeactivateSim;
254        SwitchCommandCallback mCallback;
255    }
256
257    /** Callback class for {@link #switchToSubscription}. */
258    @VisibleForTesting(visibility = PACKAGE)
259    public interface SwitchCommandCallback extends BaseEuiccCommandCallback {
260        /** Called when the switch has completed (though it may have failed). */
261        void onSwitchComplete(int result);
262    }
263
264    static class UpdateNicknameRequest {
265        String mIccid;
266        String mNickname;
267        UpdateNicknameCommandCallback mCallback;
268    }
269
270    /** Callback class for {@link #updateSubscriptionNickname}. */
271    @VisibleForTesting(visibility = PACKAGE)
272    public interface UpdateNicknameCommandCallback extends BaseEuiccCommandCallback {
273        /** Called when the update has completed (though it may have failed). */
274        void onUpdateNicknameComplete(int result);
275    }
276
277    /** Callback class for {@link #eraseSubscriptions}. */
278    @VisibleForTesting(visibility = PACKAGE)
279    public interface EraseCommandCallback extends BaseEuiccCommandCallback {
280        /** Called when the erase has completed (though it may have failed). */
281        void onEraseComplete(int result);
282    }
283
284    /** Callback class for {@link #retainSubscriptions}. */
285    @VisibleForTesting(visibility = PACKAGE)
286    public interface RetainSubscriptionsCommandCallback extends BaseEuiccCommandCallback {
287        /** Called when the retain command has completed (though it may have failed). */
288        void onRetainSubscriptionsComplete(int result);
289    }
290
291    private Context mContext;
292    private PackageManager mPm;
293
294    private final PackageMonitor mPackageMonitor = new EuiccPackageMonitor();
295    private final BroadcastReceiver mUserUnlockedReceiver = new BroadcastReceiver() {
296        @Override
297        public void onReceive(Context context, Intent intent) {
298            if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())) {
299                // On user unlock, new components might become available, so rebind if needed. This
300                // can never make a component unavailable so there's never a need to force a
301                // rebind.
302                sendMessage(CMD_PACKAGE_CHANGE);
303            }
304        }
305    };
306
307    /** Set to the current component we should bind to except in {@link UnavailableState}. */
308    private @Nullable ServiceInfo mSelectedComponent;
309
310    /** Set to the currently connected EuiccService implementation in {@link ConnectedState}. */
311    private @Nullable IEuiccService mEuiccService;
312
313    /** The callbacks for all (asynchronous) commands which are currently in flight. */
314    private Set<BaseEuiccCommandCallback> mActiveCommandCallbacks = new ArraySet<>();
315
316    @VisibleForTesting(visibility = PACKAGE) public UnavailableState mUnavailableState;
317    @VisibleForTesting(visibility = PACKAGE) public AvailableState mAvailableState;
318    @VisibleForTesting(visibility = PACKAGE) public BindingState mBindingState;
319    @VisibleForTesting(visibility = PACKAGE) public DisconnectedState mDisconnectedState;
320    @VisibleForTesting(visibility = PACKAGE) public ConnectedState mConnectedState;
321
322    EuiccConnector(Context context) {
323        super(TAG);
324        init(context);
325    }
326
327    @VisibleForTesting(visibility = PACKAGE)
328    public EuiccConnector(Context context, Looper looper) {
329        super(TAG, looper);
330        init(context);
331    }
332
333    private void init(Context context) {
334        mContext = context;
335        mPm = context.getPackageManager();
336
337        // Unavailable/Available both monitor for package changes and update mSelectedComponent but
338        // do not need to adjust the binding.
339        mUnavailableState = new UnavailableState();
340        addState(mUnavailableState);
341        mAvailableState = new AvailableState();
342        addState(mAvailableState, mUnavailableState);
343
344        mBindingState = new BindingState();
345        addState(mBindingState);
346
347        // Disconnected/Connected both monitor for package changes and reestablish the active
348        // binding if necessary.
349        mDisconnectedState = new DisconnectedState();
350        addState(mDisconnectedState);
351        mConnectedState = new ConnectedState();
352        addState(mConnectedState, mDisconnectedState);
353
354        mSelectedComponent = findBestComponent();
355        setInitialState(mSelectedComponent != null ? mAvailableState : mUnavailableState);
356
357        mPackageMonitor.register(mContext, null /* thread */, false /* externalStorage */);
358        mContext.registerReceiver(
359                mUserUnlockedReceiver, new IntentFilter(Intent.ACTION_USER_UNLOCKED));
360
361        start();
362    }
363
364    @Override
365    public void onHalting() {
366        mPackageMonitor.unregister();
367        mContext.unregisterReceiver(mUserUnlockedReceiver);
368    }
369
370    /** Asynchronously fetch the EID. */
371    @VisibleForTesting(visibility = PACKAGE)
372    public void getEid(GetEidCommandCallback callback) {
373        sendMessage(CMD_GET_EID, callback);
374    }
375
376    /** Asynchronously fetch metadata for the given downloadable subscription. */
377    @VisibleForTesting(visibility = PACKAGE)
378    public void getDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
379            boolean forceDeactivateSim, GetMetadataCommandCallback callback) {
380        GetMetadataRequest request =
381                new GetMetadataRequest();
382        request.mSubscription = subscription;
383        request.mForceDeactivateSim = forceDeactivateSim;
384        request.mCallback = callback;
385        sendMessage(CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA, request);
386    }
387
388    /** Asynchronously download the given subscription. */
389    @VisibleForTesting(visibility = PACKAGE)
390    public void downloadSubscription(DownloadableSubscription subscription,
391            boolean switchAfterDownload, boolean forceDeactivateSim,
392            DownloadCommandCallback callback) {
393        DownloadRequest request = new DownloadRequest();
394        request.mSubscription = subscription;
395        request.mSwitchAfterDownload = switchAfterDownload;
396        request.mForceDeactivateSim = forceDeactivateSim;
397        request.mCallback = callback;
398        sendMessage(CMD_DOWNLOAD_SUBSCRIPTION, request);
399    }
400
401    void getEuiccProfileInfoList(GetEuiccProfileInfoListCommandCallback callback) {
402        sendMessage(CMD_GET_EUICC_PROFILE_INFO_LIST, callback);
403    }
404
405    /** Asynchronously fetch the default downloadable subscription list. */
406    @VisibleForTesting(visibility = PACKAGE)
407    public void getDefaultDownloadableSubscriptionList(
408            boolean forceDeactivateSim, GetDefaultListCommandCallback callback) {
409        GetDefaultListRequest request = new GetDefaultListRequest();
410        request.mForceDeactivateSim = forceDeactivateSim;
411        request.mCallback = callback;
412        sendMessage(CMD_GET_DEFAULT_DOWNLOADABLE_SUBSCRIPTION_LIST, request);
413    }
414
415    /** Asynchronously fetch the {@link EuiccInfo}. */
416    @VisibleForTesting(visibility = PACKAGE)
417    public void getEuiccInfo(GetEuiccInfoCommandCallback callback) {
418        sendMessage(CMD_GET_EUICC_INFO, callback);
419    }
420
421    /** Asynchronously delete the given subscription. */
422    @VisibleForTesting(visibility = PACKAGE)
423    public void deleteSubscription(String iccid, DeleteCommandCallback callback) {
424        DeleteRequest request = new DeleteRequest();
425        request.mIccid = iccid;
426        request.mCallback = callback;
427        sendMessage(CMD_DELETE_SUBSCRIPTION, request);
428    }
429
430    /** Asynchronously switch to the given subscription. */
431    @VisibleForTesting(visibility = PACKAGE)
432    public void switchToSubscription(@Nullable String iccid, boolean forceDeactivateSim,
433            SwitchCommandCallback callback) {
434        SwitchRequest request = new SwitchRequest();
435        request.mIccid = iccid;
436        request.mForceDeactivateSim = forceDeactivateSim;
437        request.mCallback = callback;
438        sendMessage(CMD_SWITCH_TO_SUBSCRIPTION, request);
439    }
440
441    /** Asynchronously update the nickname of the given subscription. */
442    @VisibleForTesting(visibility = PACKAGE)
443    public void updateSubscriptionNickname(
444            String iccid, String nickname, UpdateNicknameCommandCallback callback) {
445        UpdateNicknameRequest request = new UpdateNicknameRequest();
446        request.mIccid = iccid;
447        request.mNickname = nickname;
448        request.mCallback = callback;
449        sendMessage(CMD_UPDATE_SUBSCRIPTION_NICKNAME, request);
450    }
451
452    /** Asynchronously erase all profiles on the eUICC. */
453    @VisibleForTesting(visibility = PACKAGE)
454    public void eraseSubscriptions(EraseCommandCallback callback) {
455        sendMessage(CMD_ERASE_SUBSCRIPTIONS, callback);
456    }
457
458    /** Asynchronously ensure that all profiles will be retained on the next factory reset. */
459    @VisibleForTesting(visibility = PACKAGE)
460    public void retainSubscriptions(RetainSubscriptionsCommandCallback callback) {
461        sendMessage(CMD_RETAIN_SUBSCRIPTIONS, callback);
462    }
463
464    /**
465     * State in which no EuiccService is available.
466     *
467     * <p>All incoming commands will be rejected through
468     * {@link BaseEuiccCommandCallback#onEuiccServiceUnavailable()}.
469     *
470     * <p>Package state changes will lead to transitions between {@link UnavailableState} and
471     * {@link AvailableState} depending on whether an EuiccService becomes unavailable or
472     * available.
473     */
474    private class UnavailableState extends State {
475        @Override
476        public boolean processMessage(Message message) {
477            if (message.what == CMD_PACKAGE_CHANGE) {
478                mSelectedComponent = findBestComponent();
479                if (mSelectedComponent != null) {
480                    transitionTo(mAvailableState);
481                } else if (getCurrentState() != mUnavailableState) {
482                    transitionTo(mUnavailableState);
483                }
484                return HANDLED;
485            } else if (isEuiccCommand(message.what)) {
486                BaseEuiccCommandCallback callback = getCallback(message);
487                callback.onEuiccServiceUnavailable();
488                return HANDLED;
489            }
490
491            return NOT_HANDLED;
492        }
493    }
494
495    /**
496     * State in which a EuiccService is available, but no binding is established or in the process
497     * of being established.
498     *
499     * <p>If a command is received, this state will defer the message and enter {@link BindingState}
500     * to bring up the binding.
501     */
502    private class AvailableState extends State {
503        @Override
504        public boolean processMessage(Message message) {
505            if (isEuiccCommand(message.what)) {
506                deferMessage(message);
507                transitionTo(mBindingState);
508                return HANDLED;
509            }
510
511            return NOT_HANDLED;
512        }
513    }
514
515    /**
516     * State in which we are binding to the current EuiccService.
517     *
518     * <p>This is a transient state. If bindService returns true, we enter {@link DisconnectedState}
519     * while waiting for the binding to be established. If it returns false, we move back to
520     * {@link AvailableState}.
521     *
522     * <p>Any received messages will be deferred.
523     */
524    private class BindingState extends State {
525        @Override
526        public void enter() {
527            if (createBinding()) {
528                transitionTo(mDisconnectedState);
529            } else {
530                // createBinding() should generally not return false since we've already performed
531                // Intent resolution, but it's always possible that the package state changes
532                // asynchronously. Transition to available for now, and if the package state has
533                // changed, we'll process that event and move to mUnavailableState as needed.
534                transitionTo(mAvailableState);
535            }
536        }
537
538        @Override
539        public boolean processMessage(Message message) {
540            deferMessage(message);
541            return HANDLED;
542        }
543    }
544
545    /**
546     * State in which a binding is established, but not currently connected.
547     *
548     * <p>We wait up to {@link #BIND_TIMEOUT_MILLIS} for the binding to establish. If it doesn't,
549     * we go back to {@link AvailableState} to try again.
550     *
551     * <p>Package state changes will cause us to unbind and move to {@link BindingState} to
552     * reestablish the binding if the selected component has changed or if a forced rebind is
553     * necessary.
554     *
555     * <p>Any received commands will be deferred.
556     */
557    private class DisconnectedState extends State {
558        @Override
559        public void enter() {
560            sendMessageDelayed(CMD_CONNECT_TIMEOUT, BIND_TIMEOUT_MILLIS);
561        }
562
563        @Override
564        public boolean processMessage(Message message) {
565            if (message.what == CMD_SERVICE_CONNECTED) {
566                mEuiccService = (IEuiccService) message.obj;
567                transitionTo(mConnectedState);
568                return HANDLED;
569            } else if (message.what == CMD_PACKAGE_CHANGE) {
570                ServiceInfo bestComponent = findBestComponent();
571                String affectedPackage = (String) message.obj;
572                boolean isSameComponent;
573                if (bestComponent == null) {
574                    isSameComponent = mSelectedComponent != null;
575                } else {
576                    isSameComponent = mSelectedComponent == null
577                            || Objects.equals(
578                                    bestComponent.getComponentName(),
579                                    mSelectedComponent.getComponentName());
580                }
581                boolean forceRebind = bestComponent != null
582                        && Objects.equals(bestComponent.packageName, affectedPackage);
583                if (!isSameComponent || forceRebind) {
584                    unbind();
585                    mSelectedComponent = bestComponent;
586                    if (mSelectedComponent == null) {
587                        transitionTo(mUnavailableState);
588                    } else {
589                        transitionTo(mBindingState);
590                    }
591                }
592                return HANDLED;
593            } else if (message.what == CMD_CONNECT_TIMEOUT) {
594                transitionTo(mAvailableState);
595                return HANDLED;
596            } else if (isEuiccCommand(message.what)) {
597                deferMessage(message);
598                return HANDLED;
599            }
600
601            return NOT_HANDLED;
602        }
603    }
604
605    /**
606     * State in which the binding is connected.
607     *
608     * <p>Commands will be processed as long as we're in this state. We wait up to
609     * {@link #LINGER_TIMEOUT_MILLIS} between commands; if this timeout is reached, we will drop the
610     * binding until the next command is received.
611     */
612    private class ConnectedState extends State {
613        @Override
614        public void enter() {
615            removeMessages(CMD_CONNECT_TIMEOUT);
616            sendMessageDelayed(CMD_LINGER_TIMEOUT, LINGER_TIMEOUT_MILLIS);
617        }
618
619        @Override
620        public boolean processMessage(Message message) {
621            if (message.what == CMD_SERVICE_DISCONNECTED) {
622                mEuiccService = null;
623                transitionTo(mDisconnectedState);
624                return HANDLED;
625            } else if (message.what == CMD_LINGER_TIMEOUT) {
626                unbind();
627                transitionTo(mAvailableState);
628                return HANDLED;
629            } else if (message.what == CMD_COMMAND_COMPLETE) {
630                Runnable runnable = (Runnable) message.obj;
631                runnable.run();
632                return HANDLED;
633            } else if (isEuiccCommand(message.what)) {
634                final BaseEuiccCommandCallback callback = getCallback(message);
635                onCommandStart(callback);
636                // TODO(b/36260308): Plumb through an actual SIM slot ID.
637                int slotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
638                try {
639                    switch (message.what) {
640                        case CMD_GET_EID: {
641                            mEuiccService.getEid(slotId,
642                                    new IGetEidCallback.Stub() {
643                                        @Override
644                                        public void onSuccess(String eid) {
645                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
646                                                ((GetEidCommandCallback) callback)
647                                                        .onGetEidComplete(eid);
648                                                onCommandEnd(callback);
649                                            });
650                                        }
651                                    });
652                            break;
653                        }
654                        case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA: {
655                            GetMetadataRequest request = (GetMetadataRequest) message.obj;
656                            mEuiccService.getDownloadableSubscriptionMetadata(slotId,
657                                    request.mSubscription,
658                                    request.mForceDeactivateSim,
659                                    new IGetDownloadableSubscriptionMetadataCallback.Stub() {
660                                        @Override
661                                        public void onComplete(
662                                                GetDownloadableSubscriptionMetadataResult result) {
663                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
664                                                ((GetMetadataCommandCallback) callback)
665                                                        .onGetMetadataComplete(result);
666                                                onCommandEnd(callback);
667                                            });
668                                        }
669                                    });
670                            break;
671                        }
672                        case CMD_DOWNLOAD_SUBSCRIPTION: {
673                            DownloadRequest request = (DownloadRequest) message.obj;
674                            mEuiccService.downloadSubscription(slotId,
675                                    request.mSubscription,
676                                    request.mSwitchAfterDownload,
677                                    request.mForceDeactivateSim,
678                                    new IDownloadSubscriptionCallback.Stub() {
679                                        @Override
680                                        public void onComplete(int result) {
681                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
682                                                ((DownloadCommandCallback) callback)
683                                                        .onDownloadComplete(result);
684                                                onCommandEnd(callback);
685                                            });
686                                        }
687                                    });
688                            break;
689                        }
690                        case CMD_GET_EUICC_PROFILE_INFO_LIST: {
691                            mEuiccService.getEuiccProfileInfoList(slotId,
692                                    new IGetEuiccProfileInfoListCallback.Stub() {
693                                        @Override
694                                        public void onComplete(
695                                                GetEuiccProfileInfoListResult result) {
696                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
697                                                ((GetEuiccProfileInfoListCommandCallback) callback)
698                                                        .onListComplete(result);
699                                                onCommandEnd(callback);
700                                            });
701                                        }
702                                    });
703                            break;
704                        }
705                        case CMD_GET_DEFAULT_DOWNLOADABLE_SUBSCRIPTION_LIST: {
706                            GetDefaultListRequest request = (GetDefaultListRequest) message.obj;
707                            mEuiccService.getDefaultDownloadableSubscriptionList(slotId,
708                                    request.mForceDeactivateSim,
709                                    new IGetDefaultDownloadableSubscriptionListCallback.Stub() {
710                                        @Override
711                                        public void onComplete(
712                                                GetDefaultDownloadableSubscriptionListResult result
713                                        ) {
714                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
715                                                ((GetDefaultListCommandCallback) callback)
716                                                        .onGetDefaultListComplete(result);
717                                                onCommandEnd(callback);
718                                            });
719                                        }
720                                    });
721                            break;
722                        }
723                        case CMD_GET_EUICC_INFO: {
724                            mEuiccService.getEuiccInfo(slotId,
725                                    new IGetEuiccInfoCallback.Stub() {
726                                        @Override
727                                        public void onSuccess(EuiccInfo euiccInfo) {
728                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
729                                                ((GetEuiccInfoCommandCallback) callback)
730                                                        .onGetEuiccInfoComplete(euiccInfo);
731                                                onCommandEnd(callback);
732                                            });
733                                        }
734                                    });
735                            break;
736                        }
737                        case CMD_DELETE_SUBSCRIPTION: {
738                            DeleteRequest request = (DeleteRequest) message.obj;
739                            mEuiccService.deleteSubscription(slotId, request.mIccid,
740                                    new IDeleteSubscriptionCallback.Stub() {
741                                        @Override
742                                        public void onComplete(int result) {
743                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
744                                                ((DeleteCommandCallback) callback)
745                                                        .onDeleteComplete(result);
746                                                onCommandEnd(callback);
747                                            });
748                                        }
749                                    });
750                            break;
751                        }
752                        case CMD_SWITCH_TO_SUBSCRIPTION: {
753                            SwitchRequest request = (SwitchRequest) message.obj;
754                            mEuiccService.switchToSubscription(slotId, request.mIccid,
755                                    request.mForceDeactivateSim,
756                                    new ISwitchToSubscriptionCallback.Stub() {
757                                        @Override
758                                        public void onComplete(int result) {
759                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
760                                                ((SwitchCommandCallback) callback)
761                                                        .onSwitchComplete(result);
762                                                onCommandEnd(callback);
763                                            });
764                                        }
765                                    });
766                            break;
767                        }
768                        case CMD_UPDATE_SUBSCRIPTION_NICKNAME: {
769                            UpdateNicknameRequest request = (UpdateNicknameRequest) message.obj;
770                            mEuiccService.updateSubscriptionNickname(slotId, request.mIccid,
771                                    request.mNickname,
772                                    new IUpdateSubscriptionNicknameCallback.Stub() {
773                                        @Override
774                                        public void onComplete(int result) {
775                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
776                                                ((UpdateNicknameCommandCallback) callback)
777                                                        .onUpdateNicknameComplete(result);
778                                                onCommandEnd(callback);
779                                            });
780                                        }
781                                    });
782                            break;
783                        }
784                        case CMD_ERASE_SUBSCRIPTIONS: {
785                            mEuiccService.eraseSubscriptions(slotId,
786                                    new IEraseSubscriptionsCallback.Stub() {
787                                        @Override
788                                        public void onComplete(int result) {
789                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
790                                                ((EraseCommandCallback) callback)
791                                                        .onEraseComplete(result);
792                                                onCommandEnd(callback);
793                                            });
794                                        }
795                                    });
796                            break;
797                        }
798                        case CMD_RETAIN_SUBSCRIPTIONS: {
799                            mEuiccService.retainSubscriptionsForFactoryReset(slotId,
800                                    new IRetainSubscriptionsForFactoryResetCallback.Stub() {
801                                        @Override
802                                        public void onComplete(int result) {
803                                            sendMessage(CMD_COMMAND_COMPLETE, (Runnable) () -> {
804                                                ((RetainSubscriptionsCommandCallback) callback)
805                                                        .onRetainSubscriptionsComplete(result);
806                                                onCommandEnd(callback);
807                                            });
808                                        }
809                                    });
810                            break;
811                        }
812                        default: {
813                            Log.wtf(TAG, "Unimplemented eUICC command: " + message.what);
814                            callback.onEuiccServiceUnavailable();
815                            onCommandEnd(callback);
816                            return HANDLED;
817                        }
818                    }
819                } catch (Exception e) {
820                    // If this is a RemoteException, we expect to be disconnected soon. For other
821                    // exceptions, this is a bug in the EuiccService implementation, but we must
822                    // not let it crash the phone process.
823                    Log.w(TAG, "Exception making binder call to EuiccService", e);
824                    callback.onEuiccServiceUnavailable();
825                    onCommandEnd(callback);
826                }
827
828                return HANDLED;
829            }
830
831            return NOT_HANDLED;
832        }
833
834        @Override
835        public void exit() {
836            removeMessages(CMD_LINGER_TIMEOUT);
837            // Dispatch callbacks for all in-flight commands; they will no longer succeed. (The
838            // remote process cannot possibly trigger a callback at this stage because the
839            // connection has dropped).
840            for (BaseEuiccCommandCallback callback : mActiveCommandCallbacks) {
841                callback.onEuiccServiceUnavailable();
842            }
843            mActiveCommandCallbacks.clear();
844        }
845    }
846
847    private static BaseEuiccCommandCallback getCallback(Message message) {
848        switch (message.what) {
849            case CMD_GET_EID:
850            case CMD_GET_EUICC_PROFILE_INFO_LIST:
851            case CMD_GET_EUICC_INFO:
852            case CMD_ERASE_SUBSCRIPTIONS:
853            case CMD_RETAIN_SUBSCRIPTIONS:
854                return (BaseEuiccCommandCallback) message.obj;
855            case CMD_GET_DOWNLOADABLE_SUBSCRIPTION_METADATA:
856                return ((GetMetadataRequest) message.obj).mCallback;
857            case CMD_DOWNLOAD_SUBSCRIPTION:
858                return ((DownloadRequest) message.obj).mCallback;
859            case CMD_GET_DEFAULT_DOWNLOADABLE_SUBSCRIPTION_LIST:
860                return ((GetDefaultListRequest) message.obj).mCallback;
861            case CMD_DELETE_SUBSCRIPTION:
862                return ((DeleteRequest) message.obj).mCallback;
863            case CMD_SWITCH_TO_SUBSCRIPTION:
864                return ((SwitchRequest) message.obj).mCallback;
865            case CMD_UPDATE_SUBSCRIPTION_NICKNAME:
866                return ((UpdateNicknameRequest) message.obj).mCallback;
867            default:
868                throw new IllegalArgumentException("Unsupported message: " + message.what);
869        }
870    }
871
872    /** Call this at the beginning of the execution of any command. */
873    private void onCommandStart(BaseEuiccCommandCallback callback) {
874        mActiveCommandCallbacks.add(callback);
875        removeMessages(CMD_LINGER_TIMEOUT);
876    }
877
878    /** Call this at the end of execution of any command (whether or not it succeeded). */
879    private void onCommandEnd(BaseEuiccCommandCallback callback) {
880        if (!mActiveCommandCallbacks.remove(callback)) {
881            Log.wtf(TAG, "Callback already removed from mActiveCommandCallbacks");
882        }
883        if (mActiveCommandCallbacks.isEmpty()) {
884            sendMessageDelayed(CMD_LINGER_TIMEOUT, LINGER_TIMEOUT_MILLIS);
885        }
886    }
887
888    /** Return the service info of the EuiccService to bind to, or null if none were found. */
889    @Nullable
890    private ServiceInfo findBestComponent() {
891        return (ServiceInfo) findBestComponent(mPm);
892    }
893
894    /**
895     * Bring up a binding to the currently-selected component.
896     *
897     * <p>Returns true if we've successfully bound to the service.
898     */
899    private boolean createBinding() {
900        if (mSelectedComponent == null) {
901            Log.wtf(TAG, "Attempting to create binding but no component is selected");
902            return false;
903        }
904        Intent intent = new Intent(EuiccService.EUICC_SERVICE_INTERFACE);
905        intent.setComponent(mSelectedComponent.getComponentName());
906        // We bind this as a foreground service because it is operating directly on the SIM, and we
907        // do not want it subjected to power-savings restrictions while doing so.
908        return mContext.bindService(intent, this,
909                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE);
910    }
911
912    private void unbind() {
913        mEuiccService = null;
914        mContext.unbindService(this);
915    }
916
917    private static ComponentInfo findBestComponent(
918            PackageManager packageManager, List<ResolveInfo> resolveInfoList) {
919        int bestPriority = Integer.MIN_VALUE;
920        ComponentInfo bestComponent = null;
921        if (resolveInfoList != null) {
922            for (ResolveInfo resolveInfo : resolveInfoList) {
923                if (!isValidEuiccComponent(packageManager, resolveInfo)) {
924                    continue;
925                }
926
927                if (resolveInfo.filter.getPriority() > bestPriority) {
928                    bestPriority = resolveInfo.filter.getPriority();
929                    bestComponent = resolveInfo.getComponentInfo();
930                }
931            }
932        }
933
934        return bestComponent;
935    }
936
937    private static boolean isValidEuiccComponent(
938            PackageManager packageManager, ResolveInfo resolveInfo) {
939        ComponentInfo componentInfo = resolveInfo.getComponentInfo();
940        String packageName = componentInfo.getComponentName().getPackageName();
941
942        // Verify that the app is privileged (via granting of a privileged permission).
943        if (packageManager.checkPermission(
944                Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS, packageName)
945                        != PackageManager.PERMISSION_GRANTED) {
946            Log.wtf(TAG, "Package " + packageName
947                    + " does not declare WRITE_EMBEDDED_SUBSCRIPTIONS");
948            return false;
949        }
950
951        // Verify that only the system can access the component.
952        final String permission;
953        if (componentInfo instanceof ServiceInfo) {
954            permission = ((ServiceInfo) componentInfo).permission;
955        } else if (componentInfo instanceof ActivityInfo) {
956            permission = ((ActivityInfo) componentInfo).permission;
957        } else {
958            throw new IllegalArgumentException("Can only verify services/activities");
959        }
960        if (!TextUtils.equals(permission, Manifest.permission.BIND_EUICC_SERVICE)) {
961            Log.wtf(TAG, "Package " + packageName
962                    + " does not require the BIND_EUICC_SERVICE permission");
963            return false;
964        }
965
966        // Verify that the component declares a priority.
967        if (resolveInfo.filter == null || resolveInfo.filter.getPriority() == 0) {
968            Log.wtf(TAG, "Package " + packageName + " does not specify a priority");
969            return false;
970        }
971        return true;
972    }
973
974    @Override
975    public void onServiceConnected(ComponentName name, IBinder service) {
976        IEuiccService euiccService = IEuiccService.Stub.asInterface(service);
977        sendMessage(CMD_SERVICE_CONNECTED, euiccService);
978    }
979
980    @Override
981    public void onServiceDisconnected(ComponentName name) {
982        sendMessage(CMD_SERVICE_DISCONNECTED);
983    }
984
985    private class EuiccPackageMonitor extends PackageMonitor {
986        @Override
987        public void onPackageAdded(String packageName, int reason) {
988            sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
989        }
990
991        @Override
992        public void onPackageRemoved(String packageName, int reason) {
993            sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
994        }
995
996        @Override
997        public void onPackageUpdateFinished(String packageName, int uid) {
998            sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
999        }
1000
1001        @Override
1002        public void onPackageModified(String packageName) {
1003            sendPackageChange(packageName, false /* forceUnbindForThisPackage */);
1004        }
1005
1006        @Override
1007        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
1008            if (doit) {
1009                for (String packageName : packages) {
1010                    sendPackageChange(packageName, true /* forceUnbindForThisPackage */);
1011                }
1012            }
1013            return super.onHandleForceStop(intent, packages, uid, doit);
1014        }
1015
1016        private void sendPackageChange(String packageName, boolean forceUnbindForThisPackage) {
1017            sendMessage(CMD_PACKAGE_CHANGE, forceUnbindForThisPackage ? packageName : null);
1018        }
1019    }
1020
1021    @Override
1022    protected void unhandledMessage(Message msg) {
1023        IState state = getCurrentState();
1024        Log.wtf(TAG, "Unhandled message " + msg.what + " in state "
1025                + (state == null ? "null" : state.getName()));
1026    }
1027
1028    @Override
1029    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1030        super.dump(fd, pw, args);
1031        pw.println("mSelectedComponent=" + mSelectedComponent);
1032        pw.println("mEuiccService=" + mEuiccService);
1033        pw.println("mActiveCommandCount=" + mActiveCommandCallbacks.size());
1034    }
1035}
1036