EuiccService.java revision 707c372f09bcf2d4b7ee662f5dfe354f8aca1054
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 android.service.euicc;
17
18import android.annotation.CallSuper;
19import android.annotation.Nullable;
20import android.app.Service;
21import android.content.Intent;
22import android.os.IBinder;
23import android.os.RemoteException;
24import android.telephony.euicc.DownloadableSubscription;
25import android.telephony.euicc.EuiccInfo;
26import android.util.ArraySet;
27
28import java.util.concurrent.LinkedBlockingQueue;
29import java.util.concurrent.ThreadFactory;
30import java.util.concurrent.ThreadPoolExecutor;
31import java.util.concurrent.TimeUnit;
32import java.util.concurrent.atomic.AtomicInteger;
33
34/**
35 * Service interface linking the system with an eUICC local profile assistant (LPA) application.
36 *
37 * <p>An LPA consists of two separate components (which may both be implemented in the same APK):
38 * the LPA backend, and the LPA UI or LUI.
39 *
40 * <p>To implement the LPA backend, you must extend this class and declare this service in your
41 * manifest file. The service must require the
42 * {@link android.Manifest.permission#BIND_EUICC_SERVICE} permission and include an intent filter
43 * with the {@link #EUICC_SERVICE_INTERFACE} action. The priority of the intent filter must be set
44 * to a non-zero value in case multiple implementations are present on the device. For example:
45 *
46 * <pre>{@code
47 * <service android:name=".MyEuiccService"
48 *          android:permission="android.permission.BIND_EUICC_SERVICE">
49 *     <intent-filter android:priority="100">
50 *         <action android:name="android.service.euicc.EuiccService" />
51 *     </intent-filter>
52 * </service>
53 * }</pre>
54 *
55 * <p>To implement the LUI, you must provide an activity for the following actions:
56 *
57 * <ul>
58 * <li>{@link #ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS}
59 * <li>{@link #ACTION_PROVISION_EMBEDDED_SUBSCRIPTION}
60 * </ul>
61 *
62 * <p>As with the service, each activity must require the
63 * {@link android.Manifest.permission#BIND_EUICC_SERVICE} permission. Each should have an intent
64 * filter with the appropriate action, the {@link #CATEGORY_EUICC_UI} category, and a non-zero
65 * priority.
66 *
67 * TODO(b/35851809): Make this a SystemApi.
68 * @hide
69 */
70public abstract class EuiccService extends Service {
71    /** Action which must be included in this service's intent filter. */
72    public static final String EUICC_SERVICE_INTERFACE = "android.service.euicc.EuiccService";
73
74    /** Category which must be defined to all UI actions, for efficient lookup. */
75    public static final String CATEGORY_EUICC_UI = "android.service.euicc.category.EUICC_UI";
76
77    // LUI actions. These are passthroughs of the corresponding EuiccManager actions.
78
79    /** @see android.telephony.euicc.EuiccManager#ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS */
80    public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS =
81            "android.service.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
82    /** @see android.telephony.euicc.EuiccManager#ACTION_PROVISION_EMBEDDED_SUBSCRIPTION */
83    public static final String ACTION_PROVISION_EMBEDDED_SUBSCRIPTION =
84            "android.service.euicc.action.PROVISION_EMBEDDED_SUBSCRIPTION";
85
86    // LUI resolution actions. These are called by the platform to resolve errors in situations that
87    // require user interaction.
88    // TODO(b/33075886): Define extras for any input parameters to these dialogs once they are
89    // more scoped out.
90    /** Alert the user that this action will result in an active SIM being deactivated. */
91    public static final String ACTION_RESOLVE_DEACTIVATE_SIM =
92            "android.service.euicc.action.RESOLVE_DEACTIVATE_SIM";
93    /**
94     * Alert the user about a download/switch being done for an app that doesn't currently have
95     * carrier privileges.
96     */
97    public static final String ACTION_RESOLVE_NO_PRIVILEGES =
98            "android.service.euicc.action.RESOLVE_NO_PRIVILEGES";
99
100    /** Result code for a successful operation. */
101    public static final int RESULT_OK = 0;
102    /** Result code indicating that an active SIM must be deactivated to perform the operation. */
103    public static final int RESULT_MUST_DEACTIVATE_SIM = -1;
104    // New predefined codes should have negative values.
105
106    /** Start of implementation-specific error results. */
107    public static final int RESULT_FIRST_USER = 1;
108
109    /**
110     * List of all valid resolution actions for validation purposes.
111     * @hide
112     */
113    public static final ArraySet<String> RESOLUTION_ACTIONS;
114    static {
115        RESOLUTION_ACTIONS = new ArraySet<>();
116        RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM);
117        RESOLUTION_ACTIONS.add(EuiccService.ACTION_RESOLVE_NO_PRIVILEGES);
118    }
119
120    /** Boolean extra for resolution actions indicating whether the user granted consent. */
121    public static final String RESOLUTION_EXTRA_CONSENT = "consent";
122
123    private final IEuiccService.Stub mStubWrapper;
124
125    private ThreadPoolExecutor mExecutor;
126
127    public EuiccService() {
128        mStubWrapper = new IEuiccServiceWrapper();
129    }
130
131    @Override
132    @CallSuper
133    public void onCreate() {
134        super.onCreate();
135        // We use a oneway AIDL interface to avoid blocking phone process binder threads on IPCs to
136        // an external process, but doing so means the requests are serialized by binder, which is
137        // not desired. Spin up a background thread pool to allow requests to be parallelized.
138        // TODO(b/38206971): Consider removing this if basic card-level functions like listing
139        // profiles are moved to the platform.
140        mExecutor = new ThreadPoolExecutor(
141                4 /* corePoolSize */,
142                4 /* maxPoolSize */,
143                30, TimeUnit.SECONDS, /* keepAliveTime */
144                new LinkedBlockingQueue<>(), /* workQueue */
145                new ThreadFactory() {
146                    private final AtomicInteger mCount = new AtomicInteger(1);
147
148                    @Override
149                    public Thread newThread(Runnable r) {
150                        return new Thread(r, "EuiccService #" + mCount.getAndIncrement());
151                    }
152                }
153        );
154        mExecutor.allowCoreThreadTimeOut(true);
155    }
156
157    @Override
158    @CallSuper
159    public void onDestroy() {
160        mExecutor.shutdownNow();
161        super.onDestroy();
162    }
163
164    /**
165     * If overriding this method, call through to the super method for any unknown actions.
166     * {@inheritDoc}
167     */
168    @Override
169    @CallSuper
170    public IBinder onBind(Intent intent) {
171        return mStubWrapper;
172    }
173
174    /**
175     * Return the EID of the eUICC.
176     *
177     * @param slotId ID of the SIM slot being queried. This is currently not populated but is here
178     *     to future-proof the APIs.
179     * @return the EID.
180     * @see android.telephony.euicc.EuiccManager#getEid
181     */
182    // TODO(b/36260308): Update doc when we have multi-SIM support.
183    public abstract String onGetEid(int slotId);
184
185    /**
186     * Populate {@link DownloadableSubscription} metadata for the given downloadable subscription.
187     *
188     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
189     *     but is here to future-proof the APIs.
190     * @param subscription A subscription whose metadata needs to be populated.
191     * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
192     *     eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM)}
193     *     should be returned to allow the user to consent to this operation first.
194     * @return The result of the operation.
195     * @see android.telephony.euicc.EuiccManager#getDownloadableSubscriptionMetadata
196     */
197    public abstract GetDownloadableSubscriptionMetadataResult onGetDownloadableSubscriptionMetadata(
198            int slotId, DownloadableSubscription subscription, boolean forceDeactivateSim);
199
200    /**
201     * Return metadata for subscriptions which are available for download for this device.
202     *
203     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
204     *     but is here to future-proof the APIs.
205     * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
206     *     eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM)}
207     *     should be returned to allow the user to consent to this operation first.
208     * @return The result of the list operation.
209     * @see android.telephony.euicc.EuiccManager#getDefaultDownloadableSubscriptionList
210     */
211    public abstract GetDefaultDownloadableSubscriptionListResult
212            onGetDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim);
213
214    /**
215     * Download the given subscription.
216     *
217     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
218     *     but is here to future-proof the APIs.
219     * @param subscription The subscription to download.
220     * @param switchAfterDownload If true, the subscription should be enabled upon successful
221     *     download.
222     * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
223     *     eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM}
224     *     should be returned to allow the user to consent to this operation first.
225     * @return the result of the download operation. May be one of the predefined {@code RESULT_}
226     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
227     * @see android.telephony.euicc.EuiccManager#downloadSubscription
228     */
229    public abstract int onDownloadSubscription(int slotId,
230            DownloadableSubscription subscription, boolean switchAfterDownload,
231            boolean forceDeactivateSim);
232
233    /**
234     * Return a list of all @link EuiccProfileInfo}s.
235     *
236     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
237     *     but is here to future-proof the APIs.
238     * @return The result of the operation.
239     * @see android.telephony.SubscriptionManager#getAvailableSubscriptionInfoList
240     * @see android.telephony.SubscriptionManager#getAccessibleSubscriptionInfoList
241     */
242    public abstract GetEuiccProfileInfoListResult onGetEuiccProfileInfoList(int slotId);
243
244    /**
245     * Return info about the eUICC chip/device.
246     *
247     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
248     *     but is here to future-proof the APIs.
249     * @return the {@link EuiccInfo} for the eUICC chip/device.
250     * @see android.telephony.euicc.EuiccManager#getEuiccInfo
251     */
252    public abstract EuiccInfo onGetEuiccInfo(int slotId);
253
254    /**
255     * Delete the given subscription.
256     *
257     * <p>If the subscription is currently active, it should be deactivated first (equivalent to a
258     * physical SIM being ejected).
259     *
260     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
261     *     but is here to future-proof the APIs.
262     * @param iccid the ICCID of the subscription to delete.
263     * @return the result of the delete operation. May be one of the predefined {@code RESULT_}
264     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
265     * @see android.telephony.euicc.EuiccManager#deleteSubscription
266     */
267    public abstract int onDeleteSubscription(int slotId, String iccid);
268
269    /**
270     * Switch to the given subscription.
271     *
272     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
273     *     but is here to future-proof the APIs.
274     * @param iccid the ICCID of the subscription to enable. May be null, in which case the current
275     *     profile should be deactivated and no profile should be activated to replace it - this is
276     *     equivalent to a physical SIM being ejected.
277     * @param forceDeactivateSim If true, and if an active SIM must be deactivated to access the
278     *     eUICC, perform this action automatically. Otherwise, {@link #RESULT_MUST_DEACTIVATE_SIM}
279     *     should be returned to allow the user to consent to this operation first.
280     * @return the result of the switch operation. May be one of the predefined {@code RESULT_}
281     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
282     * @see android.telephony.euicc.EuiccManager#switchToSubscription
283     */
284    public abstract int onSwitchToSubscription(int slotId, @Nullable String iccid,
285            boolean forceDeactivateSim);
286
287    /**
288     * Update the nickname of the given subscription.
289     *
290     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
291     *     but is here to future-proof the APIs.
292     * @param iccid the ICCID of the subscription to update.
293     * @param nickname the new nickname to apply.
294     * @return the result of the update operation. May be one of the predefined {@code RESULT_}
295     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
296     * @see android.telephony.euicc.EuiccManager#updateSubscriptionNickname
297     */
298    public abstract int onUpdateSubscriptionNickname(int slotId, String iccid,
299            String nickname);
300
301    /**
302     * Erase all of the subscriptions on the device.
303     *
304     * <p>This is intended to be used for device resets. As such, the reset should be performed even
305     * if an active SIM must be deactivated in order to access the eUICC.
306     *
307     * @param slotId ID of the SIM slot to use for the operation. This is currently not populated
308     *     but is here to future-proof the APIs.
309     * @return the result of the erase operation. May be one of the predefined {@code RESULT_}
310     *     constants or any implementation-specific code starting with {@link #RESULT_FIRST_USER}.
311     * @see android.telephony.euicc.EuiccManager#eraseSubscriptions
312     */
313    public abstract int onEraseSubscriptions(int slotId);
314
315    /**
316     * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
317     */
318    private class IEuiccServiceWrapper extends IEuiccService.Stub {
319        @Override
320        public void downloadSubscription(int slotId, DownloadableSubscription subscription,
321                boolean switchAfterDownload, boolean forceDeactivateSim,
322                IDownloadSubscriptionCallback callback) {
323            mExecutor.execute(new Runnable() {
324                @Override
325                public void run() {
326                    int result = EuiccService.this.onDownloadSubscription(
327                            slotId, subscription, switchAfterDownload, forceDeactivateSim);
328                    try {
329                        callback.onComplete(result);
330                    } catch (RemoteException e) {
331                        // Can't communicate with the phone process; ignore.
332                    }
333                }
334            });
335        }
336
337        @Override
338        public void getEid(int slotId, IGetEidCallback callback) {
339            mExecutor.execute(new Runnable() {
340                @Override
341                public void run() {
342                    String eid = EuiccService.this.onGetEid(slotId);
343                    try {
344                        callback.onSuccess(eid);
345                    } catch (RemoteException e) {
346                        // Can't communicate with the phone process; ignore.
347                    }
348                }
349            });
350        }
351
352        @Override
353        public void getDownloadableSubscriptionMetadata(int slotId,
354                DownloadableSubscription subscription,
355                boolean forceDeactivateSim,
356                IGetDownloadableSubscriptionMetadataCallback callback) {
357            mExecutor.execute(new Runnable() {
358                @Override
359                public void run() {
360                    GetDownloadableSubscriptionMetadataResult result =
361                            EuiccService.this.onGetDownloadableSubscriptionMetadata(
362                                    slotId, subscription, forceDeactivateSim);
363                    try {
364                        callback.onComplete(result);
365                    } catch (RemoteException e) {
366                        // Can't communicate with the phone process; ignore.
367                    }
368                }
369            });
370        }
371
372        @Override
373        public void getDefaultDownloadableSubscriptionList(int slotId, boolean forceDeactivateSim,
374                IGetDefaultDownloadableSubscriptionListCallback callback) {
375            mExecutor.execute(new Runnable() {
376                @Override
377                public void run() {
378                    GetDefaultDownloadableSubscriptionListResult result =
379                            EuiccService.this.onGetDefaultDownloadableSubscriptionList(
380                                    slotId, forceDeactivateSim);
381                    try {
382                        callback.onComplete(result);
383                    } catch (RemoteException e) {
384                        // Can't communicate with the phone process; ignore.
385                    }
386                }
387            });
388        }
389
390        @Override
391        public void getEuiccProfileInfoList(int slotId, IGetEuiccProfileInfoListCallback callback) {
392            mExecutor.execute(new Runnable() {
393                @Override
394                public void run() {
395                    GetEuiccProfileInfoListResult result =
396                            EuiccService.this.onGetEuiccProfileInfoList(slotId);
397                    try {
398                        callback.onComplete(result);
399                    } catch (RemoteException e) {
400                        // Can't communicate with the phone process; ignore.
401                    }
402                }
403            });
404        }
405
406        @Override
407        public void getEuiccInfo(int slotId, IGetEuiccInfoCallback callback) {
408            mExecutor.execute(new Runnable() {
409                @Override
410                public void run() {
411                    EuiccInfo euiccInfo = EuiccService.this.onGetEuiccInfo(slotId);
412                    try {
413                        callback.onSuccess(euiccInfo);
414                    } catch (RemoteException e) {
415                        // Can't communicate with the phone process; ignore.
416                    }
417                }
418            });
419
420        }
421
422        @Override
423        public void deleteSubscription(int slotId, String iccid,
424                IDeleteSubscriptionCallback callback) {
425            mExecutor.execute(new Runnable() {
426                @Override
427                public void run() {
428                    int result = EuiccService.this.onDeleteSubscription(slotId, iccid);
429                    try {
430                        callback.onComplete(result);
431                    } catch (RemoteException e) {
432                        // Can't communicate with the phone process; ignore.
433                    }
434                }
435            });
436        }
437
438        @Override
439        public void switchToSubscription(int slotId, String iccid, boolean forceDeactivateSim,
440                ISwitchToSubscriptionCallback callback) {
441            mExecutor.execute(new Runnable() {
442                @Override
443                public void run() {
444                    int result =
445                            EuiccService.this.onSwitchToSubscription(
446                                    slotId, iccid, forceDeactivateSim);
447                    try {
448                        callback.onComplete(result);
449                    } catch (RemoteException e) {
450                        // Can't communicate with the phone process; ignore.
451                    }
452                }
453            });
454        }
455
456        @Override
457        public void updateSubscriptionNickname(int slotId, String iccid, String nickname,
458                IUpdateSubscriptionNicknameCallback callback) {
459            mExecutor.execute(new Runnable() {
460                @Override
461                public void run() {
462                    int result =
463                            EuiccService.this.onUpdateSubscriptionNickname(slotId, iccid, nickname);
464                    try {
465                        callback.onComplete(result);
466                    } catch (RemoteException e) {
467                        // Can't communicate with the phone process; ignore.
468                    }
469                }
470            });
471        }
472
473        @Override
474        public void eraseSubscriptions(int slotId, IEraseSubscriptionsCallback callback) {
475            mExecutor.execute(new Runnable() {
476                @Override
477                public void run() {
478                    int result = EuiccService.this.onEraseSubscriptions(slotId);
479                    try {
480                        callback.onComplete(result);
481                    } catch (RemoteException e) {
482                        // Can't communicate with the phone process; ignore.
483                    }
484                }
485            });
486        }
487    }
488}
489