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