EuiccController.java revision 953d76b6865b1f76c6fadbbd69898fe064b6c157
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.internal.telephony.euicc;
17
18import android.Manifest;
19import android.annotation.Nullable;
20import android.app.AppOpsManager;
21import android.app.PendingIntent;
22import android.content.Context;
23import android.content.Intent;
24import android.content.pm.PackageInfo;
25import android.content.pm.PackageManager;
26import android.os.Binder;
27import android.os.Bundle;
28import android.os.ServiceManager;
29import android.service.euicc.DownloadResult;
30import android.service.euicc.EuiccService;
31import android.service.euicc.GetDownloadableSubscriptionMetadataResult;
32import android.telephony.TelephonyManager;
33import android.telephony.UiccAccessRule;
34import android.telephony.euicc.DownloadableSubscription;
35import android.telephony.euicc.EuiccManager;
36import android.util.Log;
37
38import com.android.internal.annotations.VisibleForTesting;
39
40import java.io.FileDescriptor;
41import java.io.PrintWriter;
42import java.util.concurrent.CountDownLatch;
43import java.util.concurrent.atomic.AtomicReference;
44
45/** Backing implementation of {@link android.telephony.euicc.EuiccManager}. */
46public class EuiccController extends IEuiccController.Stub {
47    private static final String TAG = "EuiccController";
48
49    /** Extra set on resolution intents containing the {@link EuiccOperation}. */
50    @VisibleForTesting
51    static final String EXTRA_OPERATION = "operation";
52
53    // Aliases so line lengths stay short.
54    private static final int OK = EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK;
55    private static final int RESOLVABLE_ERROR =
56            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR;
57    private static final int GENERIC_ERROR =
58            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
59
60    private static EuiccController sInstance;
61
62    private final Context mContext;
63    private final EuiccConnector mConnector;
64    private final AppOpsManager mAppOpsManager;
65    private final PackageManager mPackageManager;
66
67    /** Initialize the instance. Should only be called once. */
68    public static EuiccController init(Context context) {
69        synchronized (EuiccController.class) {
70            if (sInstance == null) {
71                sInstance = new EuiccController(context);
72            } else {
73                Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance);
74            }
75        }
76        return sInstance;
77    }
78
79    /** Get an instance. Assumes one has already been initialized with {@link #init}. */
80    public static EuiccController get() {
81        if (sInstance == null) {
82            synchronized (EuiccController.class) {
83                if (sInstance == null) {
84                    throw new IllegalStateException("get() called before init()");
85                }
86            }
87        }
88        return sInstance;
89    }
90
91    private EuiccController(Context context) {
92        this(context, new EuiccConnector(context));
93        ServiceManager.addService("econtroller", this);
94    }
95
96    @VisibleForTesting
97    public EuiccController(Context context, EuiccConnector connector) {
98        mContext = context;
99        mConnector = connector;
100        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
101        mPackageManager = context.getPackageManager();
102    }
103
104    /**
105     * Continue an operation which failed with a user-resolvable error.
106     *
107     * <p>The implementation here makes a key assumption that the resolutionIntent has not been
108     * tampered with. This is guaranteed because:
109     * <UL>
110     * <LI>The intent is wrapped in a PendingIntent created by the phone process which is created
111     * with {@link #EXTRA_OPERATION} already present. This means that the operation cannot be
112     * overridden on the PendingIntent - a caller can only add new extras.
113     * <LI>The resolution activity is restricted by a privileged permission; unprivileged apps
114     * cannot start it directly. So the PendingIntent is the only way to start it.
115     * </UL>
116     */
117    @Override
118    public void continueOperation(Intent resolutionIntent, Bundle resolutionExtras) {
119        if (!callerCanWriteEmbeddedSubscriptions()) {
120            throw new SecurityException(
121                    "Must have WRITE_EMBEDDED_SUBSCRIPTIONS to continue operation");
122        }
123        long token = Binder.clearCallingIdentity();
124        try {
125            EuiccOperation op = resolutionIntent.getParcelableExtra(EXTRA_OPERATION);
126            if (op == null) {
127                throw new IllegalArgumentException("Invalid resolution intent");
128            }
129
130            PendingIntent callbackIntent =
131                    resolutionIntent.getParcelableExtra(
132                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_CALLBACK_INTENT);
133            op.continueOperation(resolutionExtras, callbackIntent);
134        } finally {
135            Binder.restoreCallingIdentity(token);
136        }
137    }
138
139    /**
140     * Return the EID.
141     *
142     * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
143     * that IPC should generally be fast, and the EID shouldn't be needed in the normal course of
144     * operation.
145     */
146    @Override
147    public String getEid() {
148        if (!callerCanReadPhoneStatePrivileged()
149                && !callerHasCarrierPrivilegesForActiveSubscription()) {
150            throw new SecurityException(
151                    "Must have carrier privileges on active subscription to read EID");
152        }
153        long token = Binder.clearCallingIdentity();
154        try {
155            return blockingGetEidFromEuiccService();
156        } finally {
157            Binder.restoreCallingIdentity(token);
158        }
159    }
160
161    @Override
162    public void getDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
163            PendingIntent callbackIntent) {
164        getDownloadableSubscriptionMetadata(
165                subscription, false /* forceDeactivateSim */, callbackIntent);
166    }
167
168    void getDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
169            boolean forceDeactivateSim, PendingIntent callbackIntent) {
170        if (!callerCanWriteEmbeddedSubscriptions()) {
171            throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get metadata");
172        }
173        long token = Binder.clearCallingIdentity();
174        try {
175            mConnector.getDownloadableSubscriptionMetadata(
176                    subscription, forceDeactivateSim,
177                    new GetMetadataCommandCallback(token, subscription, callbackIntent));
178        } finally {
179            Binder.restoreCallingIdentity(token);
180        }
181    }
182
183    class GetMetadataCommandCallback implements EuiccConnector.GetMetadataCommandCallback {
184        protected final long mCallingToken;
185        protected final DownloadableSubscription mSubscription;
186        protected final PendingIntent mCallbackIntent;
187
188        GetMetadataCommandCallback(
189                long callingToken,
190                DownloadableSubscription subscription,
191                PendingIntent callbackIntent) {
192            mCallingToken = callingToken;
193            mSubscription = subscription;
194            mCallbackIntent = callbackIntent;
195        }
196
197        @Override
198        public void onGetMetadataComplete(
199                GetDownloadableSubscriptionMetadataResult result) {
200            Intent extrasIntent = new Intent();
201            final int resultCode;
202            switch (result.result) {
203                case GetDownloadableSubscriptionMetadataResult.RESULT_OK:
204                    resultCode = OK;
205                    extrasIntent.putExtra(
206                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION,
207                            result.subscription);
208                    break;
209                case GetDownloadableSubscriptionMetadataResult.RESULT_MUST_DEACTIVATE_SIM:
210                    resultCode = RESOLVABLE_ERROR;
211                    addResolutionIntent(extrasIntent,
212                            EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
213                            getOperationForDeactivateSim());
214                    break;
215                case GetDownloadableSubscriptionMetadataResult.RESULT_GENERIC_ERROR:
216                    resultCode = GENERIC_ERROR;
217                    extrasIntent.putExtra(
218                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
219                            result.detailedCode);
220                    break;
221                default:
222                    Log.wtf(TAG, "Unknown result: " + result.result);
223                    resultCode = GENERIC_ERROR;
224                    break;
225            }
226
227            sendResult(mCallbackIntent, resultCode, extrasIntent);
228        }
229
230        @Override
231        public void onEuiccServiceUnavailable() {
232            sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
233        }
234
235        protected EuiccOperation getOperationForDeactivateSim() {
236            return EuiccOperation.forGetMetadataDeactivateSim(mCallingToken, mSubscription);
237        }
238    }
239
240    @Override
241    public void downloadSubscription(DownloadableSubscription subscription,
242            boolean switchAfterDownload, String callingPackage, PendingIntent callbackIntent) {
243        downloadSubscription(subscription, switchAfterDownload, callingPackage,
244                false /* forceDeactivateSim */, callbackIntent);
245    }
246
247    void downloadSubscription(DownloadableSubscription subscription,
248            boolean switchAfterDownload, String callingPackage, boolean forceDeactivateSim,
249            PendingIntent callbackIntent) {
250        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
251        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
252
253        long token = Binder.clearCallingIdentity();
254        try {
255            if (callerCanWriteEmbeddedSubscriptions) {
256                // With WRITE_EMBEDDED_SUBSCRIPTIONS, we can skip profile-specific permission checks
257                // and move straight to the profile download.
258                downloadSubscriptionPrivileged(token, subscription, switchAfterDownload,
259                        forceDeactivateSim, callingPackage, callbackIntent);
260                return;
261            }
262            // Without WRITE_EMBEDDED_SUBSCRIPTIONS, the caller *must* be whitelisted per the
263            // metadata of the profile to be downloaded, so check the metadata first.
264            mConnector.getDownloadableSubscriptionMetadata(subscription,
265                    forceDeactivateSim,
266                    new DownloadSubscriptionGetMetadataCommandCallback(token, subscription,
267                            switchAfterDownload, callingPackage, forceDeactivateSim,
268                            callbackIntent));
269        } finally {
270            Binder.restoreCallingIdentity(token);
271        }
272    }
273
274    class DownloadSubscriptionGetMetadataCommandCallback extends GetMetadataCommandCallback {
275        private final boolean mSwitchAfterDownload;
276        private final String mCallingPackage;
277        private final boolean mForceDeactivateSim;
278
279        DownloadSubscriptionGetMetadataCommandCallback(long callingToken,
280                DownloadableSubscription subscription, boolean switchAfterDownload,
281                String callingPackage, boolean forceDeactivateSim,
282                PendingIntent callbackIntent) {
283            super(callingToken, subscription, callbackIntent);
284            mSwitchAfterDownload = switchAfterDownload;
285            mCallingPackage = callingPackage;
286            mForceDeactivateSim = forceDeactivateSim;
287        }
288
289        @Override
290        public void onGetMetadataComplete(
291                GetDownloadableSubscriptionMetadataResult result) {
292            if (result.result != GetDownloadableSubscriptionMetadataResult.RESULT_OK) {
293                // Just propagate the error as normal.
294                super.onGetMetadataComplete(result);
295                return;
296            }
297
298            DownloadableSubscription subscription = result.subscription;
299            UiccAccessRule[] rules = subscription.getAccessRules();
300            if (rules == null) {
301                Log.e(TAG, "No access rules but caller is unprivileged");
302                sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
303                return;
304            }
305
306            final PackageInfo info;
307            try {
308                info = mPackageManager.getPackageInfo(
309                        mCallingPackage, PackageManager.GET_SIGNATURES);
310            } catch (PackageManager.NameNotFoundException e) {
311                Log.e(TAG, "Calling package valid but gone");
312                sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
313                return;
314            }
315
316            for (int i = 0; i < rules.length; i++) {
317                if (rules[i].getCarrierPrivilegeStatus(info)
318                        == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
319                    // TODO(b/33075886): For consistency, this should check the privilege rules in
320                    // the metadata, not the profile itself.
321                    TelephonyManager tm =
322                            (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
323                    if (tm.checkCarrierPrivilegesForPackage(mCallingPackage)
324                            == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
325                        // Permission verified - move on to the download.
326                        downloadSubscriptionPrivileged(
327                                mCallingToken, subscription, mSwitchAfterDownload,
328                                mForceDeactivateSim, mCallingPackage, mCallbackIntent);
329                    } else {
330                        // Switch might still be permitted, but the user must consent first.
331                        Intent extrasIntent = new Intent();
332                        addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
333                                EuiccOperation.forDownloadNoPrivileges(
334                                        mCallingToken, subscription, mSwitchAfterDownload));
335                        sendResult(mCallbackIntent, RESOLVABLE_ERROR, extrasIntent);
336                    }
337                    return;
338                }
339            }
340            Log.e(TAG, "Caller is not permitted to download this profile");
341            sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
342        }
343
344        @Override
345        protected EuiccOperation getOperationForDeactivateSim() {
346            return EuiccOperation.forDownloadDeactivateSim(
347                    mCallingToken, mSubscription, mSwitchAfterDownload, mCallingPackage);
348        }
349    }
350
351    void downloadSubscriptionPrivileged(final long callingToken,
352            DownloadableSubscription subscription, boolean switchAfterDownload,
353            boolean forceDeactivateSim, final String callingPackage,
354            final PendingIntent callbackIntent) {
355        mConnector.downloadSubscription(
356                subscription,
357                switchAfterDownload,
358                forceDeactivateSim,
359                new EuiccConnector.DownloadCommandCallback() {
360                    @Override
361                    public void onDownloadComplete(DownloadResult result) {
362                        Intent extrasIntent = new Intent();
363                        final int resultCode;
364                        switch (result.result) {
365                            case DownloadResult.RESULT_OK:
366                                resultCode = OK;
367                                break;
368                            case DownloadResult.RESULT_MUST_DEACTIVATE_SIM:
369                                resultCode = RESOLVABLE_ERROR;
370                                addResolutionIntent(extrasIntent,
371                                        EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
372                                        EuiccOperation.forDownloadDeactivateSim(
373                                                callingToken, subscription, switchAfterDownload,
374                                                callingPackage));
375                                break;
376                            case DownloadResult.RESULT_GENERIC_ERROR:
377                                resultCode = GENERIC_ERROR;
378                                extrasIntent.putExtra(
379                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
380                                        result.detailedCode);
381                                break;
382                            default:
383                                Log.wtf(TAG, "Unknown result: " + result.result);
384                                resultCode = GENERIC_ERROR;
385                                break;
386                        }
387
388                        sendResult(callbackIntent, resultCode, extrasIntent);
389                    }
390
391                    @Override
392                    public void onEuiccServiceUnavailable() {
393                        sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
394                    }
395                });
396    }
397
398    void sendResult(PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
399        try {
400            callbackIntent.send(mContext, resultCode, extrasIntent);
401        } catch (PendingIntent.CanceledException e) {
402            // Caller canceled the callback; do nothing.
403        }
404    }
405
406    /** Add a resolution intent to the given extras intent. */
407    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
408    public void addResolutionIntent(Intent extrasIntent, String resolutionAction,
409            EuiccOperation op) {
410        Intent intent = new Intent(EuiccManager.ACTION_RESOLVE_ERROR);
411        intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION,
412                resolutionAction);
413        intent.putExtra(EXTRA_OPERATION, op);
414        PendingIntent resolutionIntent = PendingIntent.getActivity(
415                mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_ONE_SHOT);
416        extrasIntent.putExtra(
417                EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_INTENT, resolutionIntent);
418    }
419
420    @Override
421    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
422        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Requires DUMP");
423        final long token = Binder.clearCallingIdentity();
424        try {
425            mConnector.dump(fd, pw, args);
426        } finally {
427            Binder.restoreCallingIdentity(token);
428        }
429    }
430
431    @Nullable
432    private String blockingGetEidFromEuiccService() {
433        final CountDownLatch latch = new CountDownLatch(1);
434        final AtomicReference<String> eidRef = new AtomicReference<>();
435        mConnector.getEid(new EuiccConnector.GetEidCommandCallback() {
436            @Override
437            public void onGetEidComplete(String eid) {
438                eidRef.set(eid);
439                latch.countDown();
440            }
441
442            @Override
443            public void onEuiccServiceUnavailable() {
444                latch.countDown();
445            }
446        });
447        try {
448            latch.await();
449        } catch (InterruptedException e) {
450            Thread.currentThread().interrupt();
451        }
452        return eidRef.get();
453    }
454
455    private boolean callerCanReadPhoneStatePrivileged() {
456        return mContext.checkCallingPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
457                == PackageManager.PERMISSION_GRANTED;
458    }
459
460    private boolean callerCanWriteEmbeddedSubscriptions() {
461        return mContext.checkCallingPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
462                == PackageManager.PERMISSION_GRANTED;
463    }
464
465    /**
466     * Returns whether the caller has carrier privileges for the active mSubscription on this eUICC.
467     */
468    private boolean callerHasCarrierPrivilegesForActiveSubscription() {
469        // TODO(b/36260308): We should plumb a slot ID through here for multi-SIM devices.
470        TelephonyManager tm =
471                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
472        return tm.hasCarrierPrivileges();
473    }
474}
475