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