EuiccController.java revision 08d3d312d13c3194d8434aeb8c92ea220f4d0e2c
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.ServiceManager;
28import android.service.euicc.DownloadResult;
29import android.service.euicc.GetDownloadableSubscriptionMetadataResult;
30import android.telephony.TelephonyManager;
31import android.telephony.UiccAccessRule;
32import android.telephony.euicc.DownloadableSubscription;
33import android.telephony.euicc.EuiccManager;
34import android.util.Log;
35
36import com.android.internal.annotations.VisibleForTesting;
37
38import java.io.FileDescriptor;
39import java.io.PrintWriter;
40import java.util.concurrent.CountDownLatch;
41import java.util.concurrent.atomic.AtomicReference;
42
43/** Backing implementation of {@link android.telephony.euicc.EuiccManager}. */
44public class EuiccController extends IEuiccController.Stub {
45    private static final String TAG = "EuiccController";
46
47    // Aliases so line lengths stay short.
48    private static final int OK = EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK;
49    private static final int RESOLVABLE_ERROR =
50            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR;
51    private static final int GENERIC_ERROR =
52            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
53
54    private static EuiccController sInstance;
55
56    private final Context mContext;
57    private final EuiccConnector mConnector;
58    private final AppOpsManager mAppOpsManager;
59    private final PackageManager mPackageManager;
60
61    /** Initialize the instance. Should only be called once. */
62    public static EuiccController init(Context context) {
63        synchronized (EuiccController.class) {
64            if (sInstance == null) {
65                sInstance = new EuiccController(context);
66            } else {
67                Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance);
68            }
69        }
70        return sInstance;
71    }
72
73    /** Get an instance. Assumes one has already been initialized with {@link #init}. */
74    public static EuiccController get() {
75        if (sInstance == null) {
76            synchronized (EuiccController.class) {
77                if (sInstance == null) {
78                    throw new IllegalStateException("get() called before init()");
79                }
80            }
81        }
82        return sInstance;
83    }
84
85    private EuiccController(Context context) {
86        this(context, new EuiccConnector(context));
87        ServiceManager.addService("econtroller", this);
88    }
89
90    @VisibleForTesting
91    public EuiccController(Context context, EuiccConnector connector) {
92        mContext = context;
93        mConnector = connector;
94        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
95        mPackageManager = context.getPackageManager();
96    }
97
98    /**
99     * Return the EID.
100     *
101     * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
102     * that IPC should generally be fast, and the EID shouldn't be needed in the normal course of
103     * operation.
104     */
105    @Override
106    public String getEid() {
107        if (!callerCanReadPhoneStatePrivileged()
108                && !callerHasCarrierPrivilegesForActiveSubscription()) {
109            throw new SecurityException(
110                    "Must have carrier privileges on active subscription to read EID");
111        }
112        long token = Binder.clearCallingIdentity();
113        try {
114            return blockingGetEidFromEuiccService();
115        } finally {
116            Binder.restoreCallingIdentity(token);
117        }
118    }
119
120    @Override
121    public void getDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
122            PendingIntent callbackIntent) {
123        if (!callerCanWriteEmbeddedSubscriptions()) {
124            throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get metadata");
125        }
126        long token = Binder.clearCallingIdentity();
127        try {
128            mConnector.getDownloadableSubscriptionMetadata(
129                    subscription, new GetMetadataCommandCallback(callbackIntent));
130        } finally {
131            Binder.restoreCallingIdentity(token);
132        }
133    }
134
135    class GetMetadataCommandCallback implements EuiccConnector.GetMetadataCommandCallback {
136        protected final PendingIntent mCallbackIntent;
137
138        GetMetadataCommandCallback(PendingIntent callbackIntent) {
139            mCallbackIntent = callbackIntent;
140        }
141
142        @Override
143        public void onGetMetadataComplete(
144                GetDownloadableSubscriptionMetadataResult result) {
145            Intent extrasIntent = new Intent();
146            final int resultCode;
147            switch (result.result) {
148                case GetDownloadableSubscriptionMetadataResult.RESULT_OK:
149                    resultCode = OK;
150                    extrasIntent.putExtra(
151                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION,
152                            result.subscription);
153                    break;
154                case GetDownloadableSubscriptionMetadataResult.RESULT_MUST_DEACTIVATE_REMOVABLE_SIM:
155                    resultCode = RESOLVABLE_ERROR;
156                    // TODO(b/33075886): Pass through the PendingIntent for the
157                    // resolution action.
158                    break;
159                case GetDownloadableSubscriptionMetadataResult.RESULT_GENERIC_ERROR:
160                    resultCode = GENERIC_ERROR;
161                    extrasIntent.putExtra(
162                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
163                            result.detailedCode);
164                    break;
165                default:
166                    Log.wtf(TAG, "Unknown result: " + result.result);
167                    resultCode = GENERIC_ERROR;
168                    break;
169            }
170
171            sendResult(mCallbackIntent, resultCode, extrasIntent);
172        }
173
174        @Override
175        public void onEuiccServiceUnavailable() {
176            sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
177        }
178    }
179
180    @Override
181    public void downloadSubscription(DownloadableSubscription subscription,
182            boolean switchAfterDownload, String callingPackage, PendingIntent callbackIntent) {
183        boolean callerCanWriteEmbeddedSubscriptions = callerCanWriteEmbeddedSubscriptions();
184        mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackage);
185
186        long token = Binder.clearCallingIdentity();
187        try {
188            if (callerCanWriteEmbeddedSubscriptions) {
189                // With WRITE_EMBEDDED_SUBSCRIPTIONS, we can skip profile-specific permission checks
190                // and move straight to the profile download.
191                downloadSubscriptionPrivileged(subscription, switchAfterDownload, callbackIntent);
192                return;
193            }
194            // Without WRITE_EMBEDDED_SUBSCRIPTIONS, the caller *must* be whitelisted per the
195            // metadata of the profile to be downloaded, so check the metadata first.
196            mConnector.getDownloadableSubscriptionMetadata(subscription,
197                    new DownloadSubscriptionGetMetadataCommandCallback(
198                            switchAfterDownload, callbackIntent, callingPackage));
199        } finally {
200            Binder.restoreCallingIdentity(token);
201        }
202    }
203
204    class DownloadSubscriptionGetMetadataCommandCallback extends GetMetadataCommandCallback {
205        private final boolean mSwitchAfterDownload;
206        private final String mCallingPackage;
207
208        DownloadSubscriptionGetMetadataCommandCallback(
209                boolean switchAfterDownload, PendingIntent callbackIntent, String callingPackage) {
210            super(callbackIntent);
211            mSwitchAfterDownload = switchAfterDownload;
212            mCallingPackage = callingPackage;
213        }
214
215        @Override
216        public void onGetMetadataComplete(
217                GetDownloadableSubscriptionMetadataResult result) {
218            if (result.result != GetDownloadableSubscriptionMetadataResult.RESULT_OK) {
219                // Just propagate the error as normal.
220                // TODO(b/33075886): Pass through the PendingIntent for the resolution action. We
221                // want to retry the parent download operation after resolution, not the get
222                // metadata call.
223                super.onGetMetadataComplete(result);
224                return;
225            }
226
227            DownloadableSubscription subscription = result.subscription;
228            UiccAccessRule[] rules = subscription.getAccessRules();
229            if (rules == null) {
230                Log.e(TAG, "No access rules but caller is unprivileged");
231                sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
232                return;
233            }
234
235            final PackageInfo info;
236            try {
237                info = mPackageManager.getPackageInfo(
238                        mCallingPackage, PackageManager.GET_SIGNATURES);
239            } catch (PackageManager.NameNotFoundException e) {
240                Log.e(TAG, "Calling package valid but gone");
241                sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
242                return;
243            }
244
245            for (int i = 0; i < rules.length; i++) {
246                if (rules[i].getCarrierPrivilegeStatus(info)
247                        == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
248                    // TODO(b/33075886): For consistency, this should check the privilege rules in
249                    // the metadata, not the profile itself.
250                    TelephonyManager tm =
251                            (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
252                    if (tm.checkCarrierPrivilegesForPackage(mCallingPackage)
253                            == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
254                        // Permission verified - move on to the download.
255                        downloadSubscriptionPrivileged(
256                                subscription, mSwitchAfterDownload, mCallbackIntent);
257                    } else {
258                        // Switch might still be permitted, but the user must consent first.
259                        // TODO(b/33075886): Pass through the PendingIntent for the resolution
260                        // action.
261                        sendResult(mCallbackIntent, RESOLVABLE_ERROR, null /* extrasIntent */);
262                    }
263                    return;
264                }
265            }
266            Log.e(TAG, "Caller is not permitted to download this profile");
267            sendResult(mCallbackIntent, GENERIC_ERROR, null /* extrasIntent */);
268        }
269    }
270
271    private void downloadSubscriptionPrivileged(DownloadableSubscription subscription,
272            boolean switchAfterDownload, final PendingIntent callbackIntent) {
273        mConnector.downloadSubscription(subscription, switchAfterDownload,
274                new EuiccConnector.DownloadCommandCallback() {
275                    @Override
276                    public void onDownloadComplete(DownloadResult result) {
277                        Intent extrasIntent = new Intent();
278                        final int resultCode;
279                        switch (result.result) {
280                            case DownloadResult.RESULT_OK:
281                                resultCode = OK;
282                                break;
283                            case DownloadResult.RESULT_MUST_DEACTIVATE_REMOVABLE_SIM:
284                                resultCode = RESOLVABLE_ERROR;
285                                // TODO(b/33075886): Pass through the PendingIntent for the
286                                // resolution action.
287                                break;
288                            case DownloadResult.RESULT_GENERIC_ERROR:
289                                resultCode = GENERIC_ERROR;
290                                extrasIntent.putExtra(
291                                        EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
292                                        result.detailedCode);
293                                break;
294                            default:
295                                Log.wtf(TAG, "Unknown result: " + result.result);
296                                resultCode = GENERIC_ERROR;
297                                break;
298                        }
299
300                        sendResult(callbackIntent, resultCode, extrasIntent);
301                    }
302
303                    @Override
304                    public void onEuiccServiceUnavailable() {
305                        sendResult(callbackIntent, GENERIC_ERROR, null /* extrasIntent */);
306                    }
307                });
308    }
309
310    private void sendResult(PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
311        try {
312            callbackIntent.send(mContext, resultCode, extrasIntent);
313        } catch (PendingIntent.CanceledException e) {
314            // Caller canceled the callback; do nothing.
315        }
316    }
317
318    @Override
319    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
320        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Requires DUMP");
321        final long token = Binder.clearCallingIdentity();
322        try {
323            mConnector.dump(fd, pw, args);
324        } finally {
325            Binder.restoreCallingIdentity(token);
326        }
327    }
328
329    @Nullable
330    private String blockingGetEidFromEuiccService() {
331        final CountDownLatch latch = new CountDownLatch(1);
332        final AtomicReference<String> eidRef = new AtomicReference<>();
333        mConnector.getEid(new EuiccConnector.GetEidCommandCallback() {
334            @Override
335            public void onGetEidComplete(String eid) {
336                eidRef.set(eid);
337                latch.countDown();
338            }
339
340            @Override
341            public void onEuiccServiceUnavailable() {
342                latch.countDown();
343            }
344        });
345        try {
346            latch.await();
347        } catch (InterruptedException e) {
348            Thread.currentThread().interrupt();
349        }
350        return eidRef.get();
351    }
352
353    private boolean callerCanReadPhoneStatePrivileged() {
354        return mContext.checkCallingPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
355                == PackageManager.PERMISSION_GRANTED;
356    }
357
358    private boolean callerCanWriteEmbeddedSubscriptions() {
359        return mContext.checkCallingPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
360                == PackageManager.PERMISSION_GRANTED;
361    }
362
363    /**
364     * Returns whether the caller has carrier privileges for the active mSubscription on this eUICC.
365     */
366    private boolean callerHasCarrierPrivilegesForActiveSubscription() {
367        // TODO(b/36260308): We should plumb a slot ID through here for multi-SIM devices.
368        TelephonyManager tm =
369                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
370        return tm.hasCarrierPrivileges();
371    }
372}
373