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