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