EuiccController.java revision 8f32416b4c5f5657e7ccf3a1ee031598a0203fd9
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 android.Manifest;
19import android.annotation.Nullable;
20import android.app.AppOpsManager;
21import android.app.PendingIntent;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageInfo;
25import android.content.pm.PackageManager;
26import android.os.Binder;
27import android.os.Bundle;
28import android.os.ServiceManager;
29import android.service.euicc.DeleteResult;
30import android.service.euicc.DownloadResult;
31import android.service.euicc.EraseResult;
32import android.service.euicc.EuiccService;
33import android.service.euicc.GetDefaultDownloadableSubscriptionListResult;
34import android.service.euicc.GetDownloadableSubscriptionMetadataResult;
35import android.service.euicc.GetEuiccProfileInfoListResult;
36import android.service.euicc.SwitchResult;
37import android.service.euicc.UpdateNicknameResult;
38import android.telephony.SubscriptionInfo;
39import android.telephony.SubscriptionManager;
40import android.telephony.TelephonyManager;
41import android.telephony.UiccAccessRule;
42import android.telephony.euicc.DownloadableSubscription;
43import android.telephony.euicc.EuiccInfo;
44import android.telephony.euicc.EuiccManager;
45import android.util.Log;
46
47import com.android.internal.annotations.VisibleForTesting;
48import com.android.internal.telephony.SubscriptionController;
49
50import java.io.FileDescriptor;
51import java.io.PrintWriter;
52import java.util.List;
53import java.util.concurrent.CountDownLatch;
54import java.util.concurrent.atomic.AtomicReference;
55
56/** Backing implementation of {@link android.telephony.euicc.EuiccManager}. */
57public class EuiccController extends IEuiccController.Stub {
58    private static final String TAG = "EuiccController";
59
60    /** Extra set on resolution intents containing the {@link EuiccOperation}. */
61    @VisibleForTesting
62    static final String EXTRA_OPERATION = "operation";
63
64    // Aliases so line lengths stay short.
65    private static final int OK = EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK;
66    private static final int RESOLVABLE_ERROR =
67            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR;
68    private static final int GENERIC_ERROR =
69            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
70
71    private static EuiccController sInstance;
72
73    private final Context mContext;
74    private final EuiccConnector mConnector;
75    private final SubscriptionManager mSubscriptionManager;
76    private final AppOpsManager mAppOpsManager;
77    private final PackageManager mPackageManager;
78
79    /** Initialize the instance. Should only be called once. */
80    public static EuiccController init(Context context) {
81        synchronized (EuiccController.class) {
82            if (sInstance == null) {
83                sInstance = new EuiccController(context);
84            } else {
85                Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance);
86            }
87        }
88        return sInstance;
89    }
90
91    /** Get an instance. Assumes one has already been initialized with {@link #init}. */
92    public static EuiccController get() {
93        if (sInstance == null) {
94            synchronized (EuiccController.class) {
95                if (sInstance == null) {
96                    throw new IllegalStateException("get() called before init()");
97                }
98            }
99        }
100        return sInstance;
101    }
102
103    private EuiccController(Context context) {
104        this(context, new EuiccConnector(context));
105        ServiceManager.addService("econtroller", this);
106    }
107
108    @VisibleForTesting
109    public EuiccController(Context context, EuiccConnector connector) {
110        mContext = context;
111        mConnector = connector;
112        mSubscriptionManager = (SubscriptionManager)
113                context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
114        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
115        mPackageManager = context.getPackageManager();
116    }
117
118    /**
119     * Continue an operation which failed with a user-resolvable error.
120     *
121     * <p>The implementation here makes a key assumption that the resolutionIntent has not been
122     * tampered with. This is guaranteed because:
123     * <UL>
124     * <LI>The intent is wrapped in a PendingIntent created by the phone process which is created
125     * with {@link #EXTRA_OPERATION} already present. This means that the operation cannot be
126     * overridden on the PendingIntent - a caller can only add new extras.
127     * <LI>The resolution activity is restricted by a privileged permission; unprivileged apps
128     * cannot start it directly. So the PendingIntent is the only way to start it.
129     * </UL>
130     */
131    @Override
132    public void continueOperation(Intent resolutionIntent, Bundle resolutionExtras) {
133        if (!callerCanWriteEmbeddedSubscriptions()) {
134            throw new SecurityException(
135                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to continue operation");
136        }
137        long token = Binder.clearCallingIdentity();
138        try {
139            EuiccOperation op = resolutionIntent.getParcelableExtra(EXTRA_OPERATION);
140            if (op == null) {
141                throw new IllegalArgumentException("Invalid resolution intent");
142            }
143
144            PendingIntent callbackIntent =
145                    resolutionIntent.getParcelableExtra(
146                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT);
147            op.continueOperation(resolutionExtras, callbackIntent);
148        } finally {
149            Binder.restoreCallingIdentity(token);
150        }
151    }
152
153    /**
154     * Return the EID.
155     *
156     * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
157     * that IPC should generally be fast, and the EID shouldn't be needed in the normal course of
158     * operation.
159     */
160    @Override
161    public String getEid() {
162        if (!callerCanReadPhoneStatePrivileged()
163                && !callerHasCarrierPrivilegesForActiveSubscription()) {
164            throw new SecurityException(
165                    "Must have carrier privileges on active subscription to read EID");
166        }
167        long token = Binder.clearCallingIdentity();
168        try {
169            return blockingGetEidFromEuiccService();
170        } finally {
171            Binder.restoreCallingIdentity(token);
172        }
173    }
174
175    @Override
176    public void getDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
177            PendingIntent callbackIntent) {
178        getDownloadableSubscriptionMetadata(
179                subscription, false /* forceDeactivateSim */, callbackIntent);
180    }
181
182    void getDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
183            boolean forceDeactivateSim, PendingIntent callbackIntent) {
184        if (!callerCanWriteEmbeddedSubscriptions()) {
185            throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get metadata");
186        }
187        long token = Binder.clearCallingIdentity();
188        try {
189            mConnector.getDownloadableSubscriptionMetadata(
190                    subscription, forceDeactivateSim,
191                    new GetMetadataCommandCallback(token, subscription, callbackIntent));
192        } finally {
193            Binder.restoreCallingIdentity(token);
194        }
195    }
196
197    class GetMetadataCommandCallback implements EuiccConnector.GetMetadataCommandCallback {
198        protected final long mCallingToken;
199        protected final DownloadableSubscription mSubscription;
200        protected final PendingIntent mCallbackIntent;
201
202        GetMetadataCommandCallback(
203                long callingToken,
204                DownloadableSubscription subscription,
205                PendingIntent callbackIntent) {
206            mCallingToken = callingToken;
207            mSubscription = subscription;
208            mCallbackIntent = callbackIntent;
209        }
210
211        @Override
212        public void onGetMetadataComplete(
213                GetDownloadableSubscriptionMetadataResult result) {
214            Intent extrasIntent = new Intent();
215            final int resultCode;
216            switch (result.result) {
217                case GetDownloadableSubscriptionMetadataResult.RESULT_OK:
218                    resultCode = OK;
219                    extrasIntent.putExtra(
220                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION,
221                            result.subscription);
222                    break;
223                case GetDownloadableSubscriptionMetadataResult.RESULT_MUST_DEACTIVATE_SIM:
224                    resultCode = RESOLVABLE_ERROR;
225                    addResolutionIntent(extrasIntent,
226                            EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
227                            getOperationForDeactivateSim());
228                    break;
229                case GetDownloadableSubscriptionMetadataResult.RESULT_GENERIC_ERROR:
230                    resultCode = GENERIC_ERROR;
231                    extrasIntent.putExtra(
232                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
233                            result.detailedCode);
234                    break;
235                default:
236                    Log.wtf(TAG, "Unknown result: " + result.result);
237                    resultCode = GENERIC_ERROR;
238                    break;
239            }
240
241            sendResult(mCallbackIntent, resultCode, extrasIntent);
242        }
243
244        @Override
245        public void onEuiccServiceUnavailable() {
246            sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
247        }
248
249        protected EuiccOperation getOperationForDeactivateSim() {
250            return EuiccOperation.forGetMetadataDeactivateSim(mCallingToken, mSubscription);
251        }
252    }
253
254    @Override
255    public void downloadSubscription(DownloadableSubscription subscription,
256            boolean switchAfterDownload, String callingPackage, PendingIntent callbackIntent) {
257        downloadSubscription(subscription, switchAfterDownload, callingPackage,
258                false /* forceDeactivateSim */, callbackIntent);
259    }
260
261    void downloadSubscription(DownloadableSubscription subscription,
262            boolean switchAfterDownload, String callingPackage, boolean forceDeactivateSim,
263            PendingIntent callbackIntent) {
264        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
265        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
266
267        long token = Binder.clearCallingIdentity();
268        try {
269            if (callerCanWriteEmbeddedSubscriptions) {
270                // With WRITE_EMBEDDED_SUBSCRIPTIONS, we can skip profile-specific permission checks
271                // and move straight to the profile download.
272                downloadSubscriptionPrivileged(token, subscription, switchAfterDownload,
273                        forceDeactivateSim, callingPackage, callbackIntent);
274                return;
275            }
276            // Without WRITE_EMBEDDED_SUBSCRIPTIONS, the caller *must* be whitelisted per the
277            // metadata of the profile to be downloaded, so check the metadata first.
278            mConnector.getDownloadableSubscriptionMetadata(subscription,
279                    forceDeactivateSim,
280                    new DownloadSubscriptionGetMetadataCommandCallback(token, subscription,
281                            switchAfterDownload, callingPackage, forceDeactivateSim,
282                            callbackIntent));
283        } finally {
284            Binder.restoreCallingIdentity(token);
285        }
286    }
287
288    class DownloadSubscriptionGetMetadataCommandCallback extends GetMetadataCommandCallback {
289        private final boolean mSwitchAfterDownload;
290        private final String mCallingPackage;
291        private final boolean mForceDeactivateSim;
292
293        DownloadSubscriptionGetMetadataCommandCallback(long callingToken,
294                DownloadableSubscription subscription, boolean switchAfterDownload,
295                String callingPackage, boolean forceDeactivateSim,
296                PendingIntent callbackIntent) {
297            super(callingToken, subscription, callbackIntent);
298            mSwitchAfterDownload = switchAfterDownload;
299            mCallingPackage = callingPackage;
300            mForceDeactivateSim = forceDeactivateSim;
301        }
302
303        @Override
304        public void onGetMetadataComplete(
305                GetDownloadableSubscriptionMetadataResult result) {
306            if (result.result != GetDownloadableSubscriptionMetadataResult.RESULT_OK) {
307                // Just propagate the error as normal.
308                super.onGetMetadataComplete(result);
309                return;
310            }
311
312            DownloadableSubscription subscription = result.subscription;
313            UiccAccessRule[] rules = subscription.getAccessRules();
314            if (rules == null) {
315                Log.e(TAG, "No access rules but caller is unprivileged");
316                sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
317                return;
318            }
319
320            final PackageInfo info;
321            try {
322                info = mPackageManager.getPackageInfo(
323                        mCallingPackage, PackageManager.GET_SIGNATURES);
324            } catch (PackageManager.NameNotFoundException e) {
325                Log.e(TAG, "Calling package valid but gone");
326                sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
327                return;
328            }
329
330            for (int i = 0; i < rules.length; i++) {
331                if (rules[i].getCarrierPrivilegeStatus(info)
332                        == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
333                    // Caller can download this profile. Now, determine whether the caller can also
334                    // manage the current profile; if so, we can perform the download silently; if
335                    // not, the user must provide consent.
336                    if (canManageActiveSubscription(mCallingPackage)) {
337                        downloadSubscriptionPrivileged(
338                                mCallingToken, subscription, mSwitchAfterDownload,
339                                mForceDeactivateSim, mCallingPackage, mCallbackIntent);
340                        return;
341                    }
342
343                    // Switch might still be permitted, but the user must consent first.
344                    Intent extrasIntent = new Intent();
345                    addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
346                            EuiccOperation.forDownloadNoPrivileges(
347                                    mCallingToken, subscription, mSwitchAfterDownload,
348                                    mCallingPackage));
349                    sendResult(mCallbackIntent, RESOLVABLE_ERROR, extrasIntent);
350                    return;
351                }
352            }
353            Log.e(TAG, "Caller is not permitted to download this profile");
354            sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
355        }
356
357        @Override
358        protected EuiccOperation getOperationForDeactivateSim() {
359            return EuiccOperation.forDownloadDeactivateSim(
360                    mCallingToken, mSubscription, mSwitchAfterDownload, mCallingPackage);
361        }
362    }
363
364    void downloadSubscriptionPrivileged(final long callingToken,
365            DownloadableSubscription subscription, boolean switchAfterDownload,
366            boolean forceDeactivateSim, final String callingPackage,
367            final PendingIntent callbackIntent) {
368        mConnector.downloadSubscription(
369                subscription,
370                switchAfterDownload,
371                forceDeactivateSim,
372                new EuiccConnector.DownloadCommandCallback() {
373                    @Override
374                    public void onDownloadComplete(DownloadResult result) {
375                        Intent extrasIntent = new Intent();
376                        final int resultCode;
377                        switch (result.result) {
378                            case DownloadResult.RESULT_OK:
379                                resultCode = OK;
380                                if (!switchAfterDownload) {
381                                    // Since we're not switching, nothing will trigger a
382                                    // subscription list refresh on its own, so request one here.
383                                    SubscriptionController.getInstance()
384                                            .requestEmbeddedSubscriptionInfoListRefresh();
385                                }
386                                break;
387                            case DownloadResult.RESULT_MUST_DEACTIVATE_SIM:
388                                resultCode = RESOLVABLE_ERROR;
389                                addResolutionIntent(extrasIntent,
390                                        EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
391                                        EuiccOperation.forDownloadDeactivateSim(
392                                                callingToken, subscription, switchAfterDownload,
393                                                callingPackage));
394                                break;
395                            case DownloadResult.RESULT_GENERIC_ERROR:
396                                resultCode = GENERIC_ERROR;
397                                extrasIntent.putExtra(
398                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
399                                        result.detailedCode);
400                                break;
401                            default:
402                                Log.wtf(TAG, "Unknown result: " + result.result);
403                                resultCode = GENERIC_ERROR;
404                                break;
405                        }
406
407                        sendResult(callbackIntent, resultCode, extrasIntent);
408                    }
409
410                    @Override
411                    public void onEuiccServiceUnavailable() {
412                        sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
413                    }
414                });
415    }
416
417    /**
418     * Blocking call to {@link EuiccService#onGetEuiccProfileInfoList}.
419     *
420     * <p>Does not perform permission checks as this is not an exposed API and is only used within
421     * the phone process.
422     */
423    public GetEuiccProfileInfoListResult blockingGetEuiccProfileInfoList() {
424        final CountDownLatch latch = new CountDownLatch(1);
425        final AtomicReference<GetEuiccProfileInfoListResult> resultRef = new AtomicReference<>();
426        mConnector.getEuiccProfileInfoList(
427                new EuiccConnector.GetEuiccProfileInfoListCommandCallback() {
428                    @Override
429                    public void onListComplete(GetEuiccProfileInfoListResult result) {
430                        resultRef.set(result);
431                        latch.countDown();
432                    }
433
434                    @Override
435                    public void onEuiccServiceUnavailable() {
436                        latch.countDown();
437                    }
438                });
439        try {
440            latch.await();
441        } catch (InterruptedException e) {
442            Thread.currentThread().interrupt();
443        }
444        return resultRef.get();
445    }
446
447    @Override
448    public void getDefaultDownloadableSubscriptionList(PendingIntent callbackIntent) {
449        getDefaultDownloadableSubscriptionList(false /* forceDeactivateSim */, callbackIntent);
450    }
451
452    void getDefaultDownloadableSubscriptionList(
453            boolean forceDeactivateSim, PendingIntent callbackIntent) {
454        if (!callerCanWriteEmbeddedSubscriptions()) {
455            throw new SecurityException(
456                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get default list");
457        }
458        long token = Binder.clearCallingIdentity();
459        try {
460            mConnector.getDefaultDownloadableSubscriptionList(
461                    forceDeactivateSim, new GetDefaultListCommandCallback(token, callbackIntent));
462        } finally {
463            Binder.restoreCallingIdentity(token);
464        }
465    }
466
467    class GetDefaultListCommandCallback implements EuiccConnector.GetDefaultListCommandCallback {
468        protected final long mCallingToken;
469        protected final PendingIntent mCallbackIntent;
470
471        GetDefaultListCommandCallback(long callingToken, PendingIntent callbackIntent) {
472            mCallingToken = callingToken;
473            mCallbackIntent = callbackIntent;
474        }
475
476        @Override
477        public void onGetDefaultListComplete(GetDefaultDownloadableSubscriptionListResult result) {
478            Intent extrasIntent = new Intent();
479            final int resultCode;
480            switch (result.result) {
481                case GetDefaultDownloadableSubscriptionListResult.RESULT_OK:
482                    resultCode = OK;
483                    extrasIntent.putExtra(
484                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS,
485                            result.subscriptions);
486                    break;
487                case GetDefaultDownloadableSubscriptionListResult.RESULT_MUST_DEACTIVATE_SIM:
488                    resultCode = RESOLVABLE_ERROR;
489                    addResolutionIntent(extrasIntent,
490                            EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
491                            EuiccOperation.forGetDefaultListDeactivateSim(mCallingToken));
492                    break;
493                case GetDefaultDownloadableSubscriptionListResult.RESULT_GENERIC_ERROR:
494                    resultCode = GENERIC_ERROR;
495                    extrasIntent.putExtra(
496                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
497                            result.detailedCode);
498                    break;
499                default:
500                    Log.wtf(TAG, "Unknown result: " + result.result);
501                    resultCode = GENERIC_ERROR;
502                    break;
503            }
504
505            sendResult(mCallbackIntent, resultCode, extrasIntent);
506        }
507
508        @Override
509        public void onEuiccServiceUnavailable() {
510            sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
511        }
512    }
513
514    /**
515     * Return the {@link EuiccInfo}.
516     *
517     * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
518     * that IPC should generally be fast, and this info shouldn't be needed in the normal course of
519     * operation.
520     */
521    @Override
522    public EuiccInfo getEuiccInfo() {
523        // No permissions required as EuiccInfo is not sensitive.
524        long token = Binder.clearCallingIdentity();
525        try {
526            return blockingGetEuiccInfoFromEuiccService();
527        } finally {
528            Binder.restoreCallingIdentity(token);
529        }
530    }
531
532    @Override
533    public void deleteSubscription(int subscriptionId, String callingPackage,
534            PendingIntent callbackIntent) {
535        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
536        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
537
538        long token = Binder.clearCallingIdentity();
539        try {
540            SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId);
541            if (sub == null) {
542                Log.e(TAG, "Cannot delete nonexistent subscription: " + subscriptionId);
543                sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
544                return;
545            }
546
547            if (!callerCanWriteEmbeddedSubscriptions
548                    && !sub.canManageSubscription(mContext, callingPackage)) {
549                Log.e(TAG, "No permissions: " + subscriptionId);
550                sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
551                return;
552            }
553
554            deleteSubscriptionPrivileged(sub.getIccId(), callbackIntent);
555        } finally {
556            Binder.restoreCallingIdentity(token);
557        }
558    }
559
560    void deleteSubscriptionPrivileged(String iccid, final PendingIntent callbackIntent) {
561        mConnector.deleteSubscription(
562                iccid,
563                new EuiccConnector.DeleteCommandCallback() {
564                    @Override
565                    public void onDeleteComplete(DeleteResult result) {
566                        Intent extrasIntent = new Intent();
567                        final int resultCode;
568                        switch (result.result) {
569                            case DeleteResult.RESULT_OK:
570                                resultCode = OK;
571                                SubscriptionController.getInstance()
572                                        .requestEmbeddedSubscriptionInfoListRefresh();
573                                break;
574                            case DeleteResult.RESULT_GENERIC_ERROR:
575                                resultCode = GENERIC_ERROR;
576                                extrasIntent.putExtra(
577                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
578                                        result.detailedCode);
579                                break;
580                            default:
581                                Log.wtf(TAG, "Unknown result: " + result.result);
582                                resultCode = GENERIC_ERROR;
583                                break;
584                        }
585
586                        sendResult(callbackIntent, resultCode, extrasIntent);
587                    }
588
589                    @Override
590                    public void onEuiccServiceUnavailable() {
591                        sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
592                    }
593                });
594    }
595
596    @Override
597    public void switchToSubscription(int subscriptionId, String callingPackage,
598            PendingIntent callbackIntent) {
599        switchToSubscription(
600                subscriptionId, false /* forceDeactivateSim */, callingPackage, callbackIntent);
601    }
602
603    void switchToSubscription(int subscriptionId, boolean forceDeactivateSim, String callingPackage,
604            PendingIntent callbackIntent) {
605        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
606        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
607
608        long token = Binder.clearCallingIdentity();
609        try {
610            final String iccid;
611            if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
612                // Switch to "no" subscription. Only the system can do this.
613                if (!callerCanWriteEmbeddedSubscriptions) {
614                    Log.e(TAG, "Not permitted to switch to empty subscription");
615                    sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
616                    return;
617                }
618                iccid = null;
619            } else {
620                SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId);
621                if (sub == null) {
622                    Log.e(TAG, "Cannot switch to nonexistent subscription: " + subscriptionId);
623                    sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
624                    return;
625                }
626                if (!callerCanWriteEmbeddedSubscriptions
627                        && !sub.canManageSubscription(mContext, callingPackage)) {
628                    Log.e(TAG, "Not permitted to switch to subscription: " + subscriptionId);
629                    sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
630                    return;
631                }
632                iccid = sub.getIccId();
633            }
634
635            if (!callerCanWriteEmbeddedSubscriptions
636                    && !canManageActiveSubscription(callingPackage)) {
637                // Switch needs consent.
638                Intent extrasIntent = new Intent();
639                addResolutionIntent(extrasIntent,
640                        EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
641                        EuiccOperation.forSwitchNoPrivileges(
642                                token, subscriptionId, callingPackage));
643                sendResult(callbackIntent, RESOLVABLE_ERROR, extrasIntent);
644                return;
645            }
646
647            switchToSubscriptionPrivileged(token, subscriptionId, iccid, forceDeactivateSim,
648                    callingPackage, callbackIntent);
649        } finally {
650            Binder.restoreCallingIdentity(token);
651        }
652    }
653
654    void switchToSubscriptionPrivileged(final long callingToken, int subscriptionId,
655            boolean forceDeactivateSim, final String callingPackage,
656            final PendingIntent callbackIntent) {
657        String iccid = null;
658        SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId);
659        if (sub != null) {
660            iccid = sub.getIccId();
661        }
662        switchToSubscriptionPrivileged(callingToken, subscriptionId, iccid, forceDeactivateSim,
663                callingPackage, callbackIntent);
664    }
665
666    void switchToSubscriptionPrivileged(final long callingToken, int subscriptionId,
667            @Nullable String iccid, boolean forceDeactivateSim, final String callingPackage,
668            final PendingIntent callbackIntent) {
669        mConnector.switchToSubscription(
670                iccid,
671                forceDeactivateSim,
672                new EuiccConnector.SwitchCommandCallback() {
673                    @Override
674                    public void onSwitchComplete(SwitchResult result) {
675                        Intent extrasIntent = new Intent();
676                        final int resultCode;
677                        switch (result.result) {
678                            case SwitchResult.RESULT_OK:
679                                resultCode = OK;
680                                break;
681                            case SwitchResult.RESULT_MUST_DEACTIVATE_SIM:
682                                resultCode = RESOLVABLE_ERROR;
683                                addResolutionIntent(extrasIntent,
684                                        EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
685                                        EuiccOperation.forSwitchDeactivateSim(
686                                                callingToken, subscriptionId, callingPackage));
687                                break;
688                            case SwitchResult.RESULT_GENERIC_ERROR:
689                                resultCode = GENERIC_ERROR;
690                                extrasIntent.putExtra(
691                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
692                                        result.detailedCode);
693                                break;
694                            default:
695                                Log.wtf(TAG, "Unknown result: " + result.result);
696                                resultCode = GENERIC_ERROR;
697                                break;
698                        }
699
700                        sendResult(callbackIntent, resultCode, extrasIntent);
701                    }
702
703                    @Override
704                    public void onEuiccServiceUnavailable() {
705                        sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
706                    }
707                });
708    }
709
710    @Override
711    public void updateSubscriptionNickname(int subscriptionId, String nickname,
712            PendingIntent callbackIntent) {
713        if (!callerCanWriteEmbeddedSubscriptions()) {
714            throw new SecurityException(
715                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to update nickname");
716        }
717        long token = Binder.clearCallingIdentity();
718        try {
719            SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId);
720            if (sub == null) {
721                Log.e(TAG, "Cannot update nickname to nonexistent subscription: " + subscriptionId);
722                sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
723                return;
724            }
725            mConnector.updateSubscriptionNickname(
726                    sub.getIccId(), nickname,
727                    new EuiccConnector.UpdateNicknameCommandCallback() {
728                        @Override
729                        public void onUpdateNicknameComplete(UpdateNicknameResult result) {
730                            Intent extrasIntent = new Intent();
731                            final int resultCode;
732                            switch (result.result) {
733                                case UpdateNicknameResult.RESULT_OK:
734                                    resultCode = OK;
735                                    break;
736                                case UpdateNicknameResult.RESULT_GENERIC_ERROR:
737                                    resultCode = GENERIC_ERROR;
738                                    extrasIntent.putExtra(
739                                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
740                                            result.detailedCode);
741                                    break;
742                                default:
743                                    Log.wtf(TAG, "Unknown result: " + result.result);
744                                    resultCode = GENERIC_ERROR;
745                                    break;
746                            }
747
748                            sendResult(callbackIntent, resultCode, extrasIntent);
749                        }
750
751                        @Override
752                        public void onEuiccServiceUnavailable() {
753                            sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
754                        }
755                    });
756        } finally {
757            Binder.restoreCallingIdentity(token);
758        }
759    }
760
761    @Override
762    public void eraseSubscriptions(PendingIntent callbackIntent) {
763        if (!callerCanWriteEmbeddedSubscriptions()) {
764            throw new SecurityException(
765                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to erase subscriptions");
766        }
767        long token = Binder.clearCallingIdentity();
768        try {
769            mConnector.eraseSubscriptions(new EuiccConnector.EraseCommandCallback() {
770                @Override
771                public void onEraseComplete(EraseResult result) {
772                    Intent extrasIntent = new Intent();
773                    final int resultCode;
774                    switch (result.result) {
775                        case EraseResult.RESULT_OK:
776                            resultCode = OK;
777                            break;
778                        case EraseResult.RESULT_GENERIC_ERROR:
779                            resultCode = GENERIC_ERROR;
780                            extrasIntent.putExtra(
781                                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
782                                    result.detailedCode);
783                            break;
784                        default:
785                            Log.wtf(TAG, "Unknown result: " + result.result);
786                            resultCode = GENERIC_ERROR;
787                            break;
788                    }
789
790                    sendResult(callbackIntent, resultCode, extrasIntent);
791                }
792
793                @Override
794                public void onEuiccServiceUnavailable() {
795                    sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
796                }
797            });
798        } finally {
799            Binder.restoreCallingIdentity(token);
800        }
801    }
802
803
804    /** Dispatch the given callback intent with the given result code and data. */
805    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
806    public void sendResult(PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
807        try {
808            callbackIntent.send(mContext, resultCode, extrasIntent);
809        } catch (PendingIntent.CanceledException e) {
810            // Caller canceled the callback; do nothing.
811        }
812    }
813
814    /** Add a resolution intent to the given extras intent. */
815    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
816    public void addResolutionIntent(Intent extrasIntent, String resolutionAction,
817            EuiccOperation op) {
818        Intent intent = new Intent(EuiccManager.ACTION_RESOLVE_ERROR);
819        intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION,
820                resolutionAction);
821        intent.putExtra(EXTRA_OPERATION, op);
822        PendingIntent resolutionIntent = PendingIntent.getActivity(
823                mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_ONE_SHOT);
824        extrasIntent.putExtra(
825                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT, resolutionIntent);
826    }
827
828    @Override
829    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
830        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Requires DUMP");
831        final long token = Binder.clearCallingIdentity();
832        try {
833            mConnector.dump(fd, pw, args);
834        } finally {
835            Binder.restoreCallingIdentity(token);
836        }
837    }
838
839    @Nullable
840    private SubscriptionInfo getSubscriptionForSubscriptionId(int subscriptionId) {
841        List<SubscriptionInfo> subs = mSubscriptionManager.getAvailableSubscriptionInfoList();
842        int subCount = subs.size();
843        for (int i = 0; i < subCount; i++) {
844            SubscriptionInfo sub = subs.get(i);
845            if (subscriptionId == sub.getSubscriptionId()) {
846                return sub;
847            }
848        }
849        return null;
850    }
851
852    @Nullable
853    private String blockingGetEidFromEuiccService() {
854        CountDownLatch latch = new CountDownLatch(1);
855        AtomicReference<String> eidRef = new AtomicReference<>();
856        mConnector.getEid(new EuiccConnector.GetEidCommandCallback() {
857            @Override
858            public void onGetEidComplete(String eid) {
859                eidRef.set(eid);
860                latch.countDown();
861            }
862
863            @Override
864            public void onEuiccServiceUnavailable() {
865                latch.countDown();
866            }
867        });
868        return awaitResult(latch, eidRef);
869    }
870
871    @Nullable
872    private EuiccInfo blockingGetEuiccInfoFromEuiccService() {
873        CountDownLatch latch = new CountDownLatch(1);
874        AtomicReference<EuiccInfo> euiccInfoRef = new AtomicReference<>();
875        mConnector.getEuiccInfo(new EuiccConnector.GetEuiccInfoCommandCallback() {
876            @Override
877            public void onGetEuiccInfoComplete(EuiccInfo euiccInfo) {
878                euiccInfoRef.set(euiccInfo);
879                latch.countDown();
880            }
881
882            @Override
883            public void onEuiccServiceUnavailable() {
884                latch.countDown();
885            }
886        });
887        return awaitResult(latch, euiccInfoRef);
888    }
889
890    private static <T> T awaitResult(CountDownLatch latch, AtomicReference<T> resultRef) {
891        try {
892            latch.await();
893        } catch (InterruptedException e) {
894            Thread.currentThread().interrupt();
895        }
896        return resultRef.get();
897    }
898
899    private boolean canManageActiveSubscription(String callingPackage) {
900        // TODO(b/36260308): We should plumb a slot ID through here for multi-SIM devices.
901        List<SubscriptionInfo> subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
902        int size = subInfoList.size();
903        for (int subIndex = 0; subIndex < size; subIndex++) {
904            SubscriptionInfo subInfo = subInfoList.get(subIndex);
905            if (subInfo.isEmbedded() && subInfo.canManageSubscription(mContext, callingPackage)) {
906                return true;
907            }
908        }
909        return false;
910    }
911
912    private boolean callerCanReadPhoneStatePrivileged() {
913        return mContext.checkCallingPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
914                == PackageManager.PERMISSION_GRANTED;
915    }
916
917    private boolean callerCanWriteEmbeddedSubscriptions() {
918        return mContext.checkCallingPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
919                == PackageManager.PERMISSION_GRANTED;
920    }
921
922    /**
923     * Returns whether the caller has carrier privileges for the active mSubscription on this eUICC.
924     */
925    private boolean callerHasCarrierPrivilegesForActiveSubscription() {
926        // TODO(b/36260308): We should plumb a slot ID through here for multi-SIM devices.
927        TelephonyManager tm =
928                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
929        return tm.hasCarrierPrivileges();
930    }
931}
932