EuiccController.java revision 9af6424827eb9e023e037dd5aefd229235aceddb
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                                    SubscriptionController.getInstance()
401                                            .requestEmbeddedSubscriptionInfoListRefresh();
402                                }
403                                break;
404                            case EuiccService.RESULT_MUST_DEACTIVATE_SIM:
405                                resultCode = RESOLVABLE_ERROR;
406                                addResolutionIntent(extrasIntent,
407                                        EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
408                                        callingPackage,
409                                        EuiccOperation.forDownloadDeactivateSim(
410                                                callingToken, subscription, switchAfterDownload,
411                                                callingPackage));
412                                break;
413                            default:
414                                resultCode = ERROR;
415                                extrasIntent.putExtra(
416                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
417                                        result);
418                                break;
419                        }
420
421                        sendResult(callbackIntent, resultCode, extrasIntent);
422                    }
423
424                    @Override
425                    public void onEuiccServiceUnavailable() {
426                        sendResult(callbackIntent, ERROR, null /* extrasIntent */);
427                    }
428                });
429    }
430
431    /**
432     * Blocking call to {@link EuiccService#onGetEuiccProfileInfoList}.
433     *
434     * <p>Does not perform permission checks as this is not an exposed API and is only used within
435     * the phone process.
436     */
437    public GetEuiccProfileInfoListResult blockingGetEuiccProfileInfoList() {
438        final CountDownLatch latch = new CountDownLatch(1);
439        final AtomicReference<GetEuiccProfileInfoListResult> resultRef = new AtomicReference<>();
440        mConnector.getEuiccProfileInfoList(
441                new EuiccConnector.GetEuiccProfileInfoListCommandCallback() {
442                    @Override
443                    public void onListComplete(GetEuiccProfileInfoListResult result) {
444                        resultRef.set(result);
445                        latch.countDown();
446                    }
447
448                    @Override
449                    public void onEuiccServiceUnavailable() {
450                        latch.countDown();
451                    }
452                });
453        try {
454            latch.await();
455        } catch (InterruptedException e) {
456            Thread.currentThread().interrupt();
457        }
458        return resultRef.get();
459    }
460
461    @Override
462    public void getDefaultDownloadableSubscriptionList(
463            String callingPackage, PendingIntent callbackIntent) {
464        getDefaultDownloadableSubscriptionList(
465                false /* forceDeactivateSim */, callingPackage, callbackIntent);
466    }
467
468    void getDefaultDownloadableSubscriptionList(
469            boolean forceDeactivateSim, String callingPackage, PendingIntent callbackIntent) {
470        if (!callerCanWriteEmbeddedSubscriptions()) {
471            throw new SecurityException(
472                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get default list");
473        }
474        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
475        long token = Binder.clearCallingIdentity();
476        try {
477            mConnector.getDefaultDownloadableSubscriptionList(
478                    forceDeactivateSim, new GetDefaultListCommandCallback(
479                            token, callingPackage, callbackIntent));
480        } finally {
481            Binder.restoreCallingIdentity(token);
482        }
483    }
484
485    class GetDefaultListCommandCallback implements EuiccConnector.GetDefaultListCommandCallback {
486        final long mCallingToken;
487        final String mCallingPackage;
488        final PendingIntent mCallbackIntent;
489
490        GetDefaultListCommandCallback(long callingToken, String callingPackage,
491                PendingIntent callbackIntent) {
492            mCallingToken = callingToken;
493            mCallingPackage = callingPackage;
494            mCallbackIntent = callbackIntent;
495        }
496
497        @Override
498        public void onGetDefaultListComplete(GetDefaultDownloadableSubscriptionListResult result) {
499            Intent extrasIntent = new Intent();
500            final int resultCode;
501            switch (result.result) {
502                case EuiccService.RESULT_OK:
503                    resultCode = OK;
504                    extrasIntent.putExtra(
505                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTIONS,
506                            result.subscriptions);
507                    break;
508                case EuiccService.RESULT_MUST_DEACTIVATE_SIM:
509                    resultCode = RESOLVABLE_ERROR;
510                    addResolutionIntent(extrasIntent,
511                            EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
512                            mCallingPackage,
513                            EuiccOperation.forGetDefaultListDeactivateSim(
514                                    mCallingToken, mCallingPackage));
515                    break;
516                default:
517                    resultCode = ERROR;
518                    extrasIntent.putExtra(
519                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
520                            result.result);
521                    break;
522            }
523
524            sendResult(mCallbackIntent, resultCode, extrasIntent);
525        }
526
527        @Override
528        public void onEuiccServiceUnavailable() {
529            sendResult(mCallbackIntent, ERROR, null /* extrasIntent */);
530        }
531    }
532
533    /**
534     * Return the {@link EuiccInfo}.
535     *
536     * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
537     * that IPC should generally be fast, and this info shouldn't be needed in the normal course of
538     * operation.
539     */
540    @Override
541    public EuiccInfo getEuiccInfo() {
542        // No permissions required as EuiccInfo is not sensitive.
543        long token = Binder.clearCallingIdentity();
544        try {
545            return blockingGetEuiccInfoFromEuiccService();
546        } finally {
547            Binder.restoreCallingIdentity(token);
548        }
549    }
550
551    @Override
552    public void deleteSubscription(int subscriptionId, String callingPackage,
553            PendingIntent callbackIntent) {
554        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
555        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
556
557        long token = Binder.clearCallingIdentity();
558        try {
559            SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId);
560            if (sub == null) {
561                Log.e(TAG, "Cannot delete nonexistent subscription: " + subscriptionId);
562                sendResult(callbackIntent, ERROR, null /* extrasIntent */);
563                return;
564            }
565
566            if (!callerCanWriteEmbeddedSubscriptions
567                    && !sub.canManageSubscription(mContext, callingPackage)) {
568                Log.e(TAG, "No permissions: " + subscriptionId);
569                sendResult(callbackIntent, ERROR, null /* extrasIntent */);
570                return;
571            }
572
573            deleteSubscriptionPrivileged(sub.getIccId(), callbackIntent);
574        } finally {
575            Binder.restoreCallingIdentity(token);
576        }
577    }
578
579    void deleteSubscriptionPrivileged(String iccid, final PendingIntent callbackIntent) {
580        mConnector.deleteSubscription(
581                iccid,
582                new EuiccConnector.DeleteCommandCallback() {
583                    @Override
584                    public void onDeleteComplete(int result) {
585                        Intent extrasIntent = new Intent();
586                        final int resultCode;
587                        switch (result) {
588                            case EuiccService.RESULT_OK:
589                                resultCode = OK;
590                                SubscriptionController.getInstance()
591                                        .requestEmbeddedSubscriptionInfoListRefresh();
592                                break;
593                            default:
594                                resultCode = ERROR;
595                                extrasIntent.putExtra(
596                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
597                                        result);
598                                break;
599                        }
600
601                        sendResult(callbackIntent, resultCode, extrasIntent);
602                    }
603
604                    @Override
605                    public void onEuiccServiceUnavailable() {
606                        sendResult(callbackIntent, ERROR, null /* extrasIntent */);
607                    }
608                });
609    }
610
611    @Override
612    public void switchToSubscription(int subscriptionId, String callingPackage,
613            PendingIntent callbackIntent) {
614        switchToSubscription(
615                subscriptionId, false /* forceDeactivateSim */, callingPackage, callbackIntent);
616    }
617
618    void switchToSubscription(int subscriptionId, boolean forceDeactivateSim, String callingPackage,
619            PendingIntent callbackIntent) {
620        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
621        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
622
623        long token = Binder.clearCallingIdentity();
624        try {
625            final String iccid;
626            if (subscriptionId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
627                // Switch to "no" subscription. Only the system can do this.
628                if (!callerCanWriteEmbeddedSubscriptions) {
629                    Log.e(TAG, "Not permitted to switch to empty subscription");
630                    sendResult(callbackIntent, ERROR, null /* extrasIntent */);
631                    return;
632                }
633                iccid = null;
634            } else {
635                SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId);
636                if (sub == null) {
637                    Log.e(TAG, "Cannot switch to nonexistent subscription: " + subscriptionId);
638                    sendResult(callbackIntent, ERROR, null /* extrasIntent */);
639                    return;
640                }
641                if (!callerCanWriteEmbeddedSubscriptions
642                        && !sub.canManageSubscription(mContext, callingPackage)) {
643                    Log.e(TAG, "Not permitted to switch to subscription: " + subscriptionId);
644                    sendResult(callbackIntent, ERROR, null /* extrasIntent */);
645                    return;
646                }
647                iccid = sub.getIccId();
648            }
649
650            if (!callerCanWriteEmbeddedSubscriptions
651                    && !canManageActiveSubscription(callingPackage)) {
652                // Switch needs consent.
653                Intent extrasIntent = new Intent();
654                addResolutionIntent(extrasIntent,
655                        EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
656                        callingPackage,
657                        EuiccOperation.forSwitchNoPrivileges(
658                                token, subscriptionId, callingPackage));
659                sendResult(callbackIntent, RESOLVABLE_ERROR, extrasIntent);
660                return;
661            }
662
663            switchToSubscriptionPrivileged(token, subscriptionId, iccid, forceDeactivateSim,
664                    callingPackage, callbackIntent);
665        } finally {
666            Binder.restoreCallingIdentity(token);
667        }
668    }
669
670    void switchToSubscriptionPrivileged(final long callingToken, int subscriptionId,
671            boolean forceDeactivateSim, final String callingPackage,
672            final PendingIntent callbackIntent) {
673        String iccid = null;
674        SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId);
675        if (sub != null) {
676            iccid = sub.getIccId();
677        }
678        switchToSubscriptionPrivileged(callingToken, subscriptionId, iccid, forceDeactivateSim,
679                callingPackage, callbackIntent);
680    }
681
682    void switchToSubscriptionPrivileged(final long callingToken, int subscriptionId,
683            @Nullable String iccid, boolean forceDeactivateSim, final String callingPackage,
684            final PendingIntent callbackIntent) {
685        mConnector.switchToSubscription(
686                iccid,
687                forceDeactivateSim,
688                new EuiccConnector.SwitchCommandCallback() {
689                    @Override
690                    public void onSwitchComplete(int result) {
691                        Intent extrasIntent = new Intent();
692                        final int resultCode;
693                        switch (result) {
694                            case EuiccService.RESULT_OK:
695                                resultCode = OK;
696                                break;
697                            case EuiccService.RESULT_MUST_DEACTIVATE_SIM:
698                                resultCode = RESOLVABLE_ERROR;
699                                addResolutionIntent(extrasIntent,
700                                        EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
701                                        callingPackage,
702                                        EuiccOperation.forSwitchDeactivateSim(
703                                                callingToken, subscriptionId, callingPackage));
704                                break;
705                            default:
706                                resultCode = ERROR;
707                                extrasIntent.putExtra(
708                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
709                                        result);
710                                break;
711                        }
712
713                        sendResult(callbackIntent, resultCode, extrasIntent);
714                    }
715
716                    @Override
717                    public void onEuiccServiceUnavailable() {
718                        sendResult(callbackIntent, ERROR, null /* extrasIntent */);
719                    }
720                });
721    }
722
723    @Override
724    public void updateSubscriptionNickname(int subscriptionId, String nickname,
725            PendingIntent callbackIntent) {
726        if (!callerCanWriteEmbeddedSubscriptions()) {
727            throw new SecurityException(
728                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to update nickname");
729        }
730        long token = Binder.clearCallingIdentity();
731        try {
732            SubscriptionInfo sub = getSubscriptionForSubscriptionId(subscriptionId);
733            if (sub == null) {
734                Log.e(TAG, "Cannot update nickname to nonexistent subscription: " + subscriptionId);
735                sendResult(callbackIntent, ERROR, null /* extrasIntent */);
736                return;
737            }
738            mConnector.updateSubscriptionNickname(
739                    sub.getIccId(), nickname,
740                    new EuiccConnector.UpdateNicknameCommandCallback() {
741                        @Override
742                        public void onUpdateNicknameComplete(int result) {
743                            Intent extrasIntent = new Intent();
744                            final int resultCode;
745                            switch (result) {
746                                case EuiccService.RESULT_OK:
747                                    resultCode = OK;
748                                    break;
749                                default:
750                                    resultCode = ERROR;
751                                    extrasIntent.putExtra(
752                                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
753                                            result);
754                                    break;
755                            }
756
757                            sendResult(callbackIntent, resultCode, extrasIntent);
758                        }
759
760                        @Override
761                        public void onEuiccServiceUnavailable() {
762                            sendResult(callbackIntent, ERROR, null /* extrasIntent */);
763                        }
764                    });
765        } finally {
766            Binder.restoreCallingIdentity(token);
767        }
768    }
769
770    @Override
771    public void eraseSubscriptions(PendingIntent callbackIntent) {
772        if (!callerCanWriteEmbeddedSubscriptions()) {
773            throw new SecurityException(
774                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to erase subscriptions");
775        }
776        long token = Binder.clearCallingIdentity();
777        try {
778            mConnector.eraseSubscriptions(new EuiccConnector.EraseCommandCallback() {
779                @Override
780                public void onEraseComplete(int result) {
781                    Intent extrasIntent = new Intent();
782                    final int resultCode;
783                    switch (result) {
784                        case EuiccService.RESULT_OK:
785                            resultCode = OK;
786                            SubscriptionController.getInstance()
787                                    .requestEmbeddedSubscriptionInfoListRefresh();
788                            break;
789                        default:
790                            resultCode = ERROR;
791                            extrasIntent.putExtra(
792                                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
793                                    result);
794                            break;
795                    }
796
797                    sendResult(callbackIntent, resultCode, extrasIntent);
798                }
799
800                @Override
801                public void onEuiccServiceUnavailable() {
802                    sendResult(callbackIntent, ERROR, null /* extrasIntent */);
803                }
804            });
805        } finally {
806            Binder.restoreCallingIdentity(token);
807        }
808    }
809
810
811    /** Dispatch the given callback intent with the given result code and data. */
812    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
813    public void sendResult(PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
814        try {
815            callbackIntent.send(mContext, resultCode, extrasIntent);
816        } catch (PendingIntent.CanceledException e) {
817            // Caller canceled the callback; do nothing.
818        }
819    }
820
821    /** Add a resolution intent to the given extras intent. */
822    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
823    public void addResolutionIntent(Intent extrasIntent, String resolutionAction,
824            String callingPackage, EuiccOperation op) {
825        Intent intent = new Intent(EuiccManager.ACTION_RESOLVE_ERROR);
826        intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION,
827                resolutionAction);
828        intent.putExtra(EuiccService.EXTRA_RESOLUTION_CALLING_PACKAGE, callingPackage);
829        intent.putExtra(EXTRA_OPERATION, op);
830        PendingIntent resolutionIntent = PendingIntent.getActivity(
831                mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_ONE_SHOT);
832        extrasIntent.putExtra(
833                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT, resolutionIntent);
834    }
835
836    @Override
837    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
838        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Requires DUMP");
839        final long token = Binder.clearCallingIdentity();
840        try {
841            mConnector.dump(fd, pw, args);
842        } finally {
843            Binder.restoreCallingIdentity(token);
844        }
845    }
846
847    @Nullable
848    private SubscriptionInfo getSubscriptionForSubscriptionId(int subscriptionId) {
849        List<SubscriptionInfo> subs = mSubscriptionManager.getAvailableSubscriptionInfoList();
850        int subCount = subs.size();
851        for (int i = 0; i < subCount; i++) {
852            SubscriptionInfo sub = subs.get(i);
853            if (subscriptionId == sub.getSubscriptionId()) {
854                return sub;
855            }
856        }
857        return null;
858    }
859
860    @Nullable
861    private String blockingGetEidFromEuiccService() {
862        CountDownLatch latch = new CountDownLatch(1);
863        AtomicReference<String> eidRef = new AtomicReference<>();
864        mConnector.getEid(new EuiccConnector.GetEidCommandCallback() {
865            @Override
866            public void onGetEidComplete(String eid) {
867                eidRef.set(eid);
868                latch.countDown();
869            }
870
871            @Override
872            public void onEuiccServiceUnavailable() {
873                latch.countDown();
874            }
875        });
876        return awaitResult(latch, eidRef);
877    }
878
879    @Nullable
880    private EuiccInfo blockingGetEuiccInfoFromEuiccService() {
881        CountDownLatch latch = new CountDownLatch(1);
882        AtomicReference<EuiccInfo> euiccInfoRef = new AtomicReference<>();
883        mConnector.getEuiccInfo(new EuiccConnector.GetEuiccInfoCommandCallback() {
884            @Override
885            public void onGetEuiccInfoComplete(EuiccInfo euiccInfo) {
886                euiccInfoRef.set(euiccInfo);
887                latch.countDown();
888            }
889
890            @Override
891            public void onEuiccServiceUnavailable() {
892                latch.countDown();
893            }
894        });
895        return awaitResult(latch, euiccInfoRef);
896    }
897
898    private static <T> T awaitResult(CountDownLatch latch, AtomicReference<T> resultRef) {
899        try {
900            latch.await();
901        } catch (InterruptedException e) {
902            Thread.currentThread().interrupt();
903        }
904        return resultRef.get();
905    }
906
907    private boolean canManageActiveSubscription(String callingPackage) {
908        // TODO(b/36260308): We should plumb a slot ID through here for multi-SIM devices.
909        List<SubscriptionInfo> subInfoList = mSubscriptionManager.getActiveSubscriptionInfoList();
910        if (subInfoList == null) {
911            return false;
912        }
913        int size = subInfoList.size();
914        for (int subIndex = 0; subIndex < size; subIndex++) {
915            SubscriptionInfo subInfo = subInfoList.get(subIndex);
916            if (subInfo.isEmbedded() && subInfo.canManageSubscription(mContext, callingPackage)) {
917                return true;
918            }
919        }
920        return false;
921    }
922
923    private boolean callerCanReadPhoneStatePrivileged() {
924        return mContext.checkCallingPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
925                == PackageManager.PERMISSION_GRANTED;
926    }
927
928    private boolean callerCanWriteEmbeddedSubscriptions() {
929        return mContext.checkCallingPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
930                == PackageManager.PERMISSION_GRANTED;
931    }
932
933    /**
934     * Returns whether the caller has carrier privileges for the active mSubscription on this eUICC.
935     */
936    private boolean callerHasCarrierPrivilegesForActiveSubscription() {
937        // TODO(b/36260308): We should plumb a slot ID through here for multi-SIM devices.
938        TelephonyManager tm =
939                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
940        return tm.hasCarrierPrivileges();
941    }
942}
943