EuiccController.java revision 277a5a2aae73ef0233fffc350f3829aee779899f
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.PendingIntent;
21import android.content.Context;
22import android.content.Intent;
23import android.content.pm.PackageManager;
24import android.os.Binder;
25import android.os.ServiceManager;
26import android.service.euicc.DownloadResult;
27import android.service.euicc.GetDownloadableSubscriptionMetadataResult;
28import android.telephony.TelephonyManager;
29import android.telephony.euicc.DownloadableSubscription;
30import android.telephony.euicc.EuiccManager;
31import android.util.Log;
32
33import com.android.internal.annotations.VisibleForTesting;
34
35import java.io.FileDescriptor;
36import java.io.PrintWriter;
37import java.util.concurrent.CountDownLatch;
38import java.util.concurrent.atomic.AtomicReference;
39
40/** Backing implementation of {@link android.telephony.euicc.EuiccManager}. */
41public class EuiccController extends IEuiccController.Stub {
42    private static final String TAG = "EuiccController";
43
44    private static EuiccController sInstance;
45
46    private final Context mContext;
47    private final EuiccConnector mConnector;
48
49    /** Initialize the instance. Should only be called once. */
50    public static EuiccController init(Context context) {
51        synchronized (EuiccController.class) {
52            if (sInstance == null) {
53                sInstance = new EuiccController(context);
54            } else {
55                Log.wtf(TAG, "init() called multiple times! sInstance = " + sInstance);
56            }
57        }
58        return sInstance;
59    }
60
61    /** Get an instance. Assumes one has already been initialized with {@link #init}. */
62    public static EuiccController get() {
63        if (sInstance == null) {
64            synchronized (EuiccController.class) {
65                if (sInstance == null) {
66                    throw new IllegalStateException("get() called before init()");
67                }
68            }
69        }
70        return sInstance;
71    }
72
73    private EuiccController(Context context) {
74        this(context, new EuiccConnector(context));
75        ServiceManager.addService("econtroller", this);
76    }
77
78    @VisibleForTesting
79    public EuiccController(Context context, EuiccConnector connector) {
80        mContext = context;
81        mConnector = connector;
82    }
83
84    /**
85     * Return the EID.
86     *
87     * <p>For API simplicity, this call blocks until completion; while it requires an IPC to load,
88     * that IPC should generally be fast, and the EID shouldn't be needed in the normal course of
89     * operation.
90     */
91    @Override
92    public String getEid() {
93        if (!callerCanReadPhoneStatePrivileged()
94                && !callerHasCarrierPrivilegesForActiveSubscription()) {
95            throw new SecurityException(
96                    "Must have carrier privileges on active subscription to read EID");
97        }
98        long token = Binder.clearCallingIdentity();
99        try {
100            return blockingGetEidFromEuiccService();
101        } finally {
102            Binder.restoreCallingIdentity(token);
103        }
104    }
105
106    @Override
107    public void getDownloadableSubscriptionMetadata(DownloadableSubscription subscription,
108            final PendingIntent callbackIntent) {
109        if (!callerCanWriteEmbeddedSubscriptions()) {
110            throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to get metadata");
111        }
112        long token = Binder.clearCallingIdentity();
113        try {
114            final String subscriptionResultKey =
115                    EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DOWNLOADABLE_SUBSCRIPTION;
116            mConnector.getDownloadableSubscriptionMetadata(subscription,
117                    new EuiccConnector.GetMetadataCommandCallback() {
118                        @Override
119                        public void onGetMetadataComplete(
120                                GetDownloadableSubscriptionMetadataResult result) {
121                            Intent extrasIntent = new Intent();
122                            final int resultCode;
123                            switch (result.result) {
124                                case GetDownloadableSubscriptionMetadataResult.RESULT_OK:
125                                    resultCode = EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK;
126                                    extrasIntent.putExtra(subscriptionResultKey,
127                                            result.subscription);
128                                    break;
129                                case GetDownloadableSubscriptionMetadataResult
130                                        .RESULT_MUST_DEACTIVATE_REMOVABLE_SIM:
131                                    resultCode = EuiccManager
132                                            .EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR;
133                                    // TODO(b/33075886): Pass through the PendingIntent for the
134                                    // resolution action.
135                                    break;
136                                case GetDownloadableSubscriptionMetadataResult.RESULT_GENERIC_ERROR:
137                                    resultCode =
138                                            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
139                                    extrasIntent.putExtra(
140                                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
141                                            result.detailedCode);
142                                    break;
143                                default:
144                                    Log.wtf(TAG, "Unknown result: " + result.result);
145                                    resultCode =
146                                            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
147                                    break;
148                            }
149
150                            sendResult(callbackIntent, resultCode, extrasIntent);
151                        }
152
153                        @Override
154                        public void onEuiccServiceUnavailable() {
155                            sendResult(callbackIntent,
156                                    EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR,
157                                    null /* extrasIntent */);
158                        }
159                    });
160        } finally {
161            Binder.restoreCallingIdentity(token);
162        }
163    }
164
165    @Override
166    public void downloadSubscription(DownloadableSubscription subscription,
167            boolean switchAfterDownload, final PendingIntent callbackIntent) {
168        if (!callerCanWriteEmbeddedSubscriptions()) {
169            // TODO(b/33075886): Allow unprivileged carriers who have carrier privileges on the
170            // active mSubscription (if any) and the mSubscription to be downloaded.
171            throw new SecurityException("Must have WRITE_EMBEDDED_SUBSCRIPTIONS to download");
172        }
173        long token = Binder.clearCallingIdentity();
174        try {
175            mConnector.downloadSubscription(subscription, switchAfterDownload,
176                    new EuiccConnector.DownloadCommandCallback() {
177                        @Override
178                        public void onDownloadComplete(DownloadResult result) {
179                            Intent extrasIntent = new Intent();
180                            final int resultCode;
181                            switch (result.result) {
182                                case DownloadResult.RESULT_OK:
183                                    resultCode = EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK;
184                                    break;
185                                case DownloadResult.RESULT_MUST_DEACTIVATE_REMOVABLE_SIM:
186                                    resultCode = EuiccManager
187                                            .EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR;
188                                    // TODO(b/33075886): Pass through the PendingIntent for the
189                                    // resolution action.
190                                    break;
191                                case DownloadResult.RESULT_GENERIC_ERROR:
192                                    resultCode =
193                                            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
194                                    extrasIntent.putExtra(
195                                            EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE,
196                                            result.detailedCode);
197                                    break;
198                                default:
199                                    Log.wtf(TAG, "Unknown result: " + result.result);
200                                    resultCode =
201                                            EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR;
202                                    break;
203                            }
204
205                            sendResult(callbackIntent, resultCode, extrasIntent);
206                        }
207
208                        @Override
209                        public void onEuiccServiceUnavailable() {
210                            sendResult(callbackIntent,
211                                    EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_GENERIC_ERROR,
212                                    null /* extrasIntent */);
213                        }
214                    });
215        } finally {
216            Binder.restoreCallingIdentity(token);
217        }
218    }
219
220    private void sendResult(PendingIntent callbackIntent, int resultCode, Intent extrasIntent) {
221        try {
222            callbackIntent.send(mContext, resultCode, extrasIntent);
223        } catch (PendingIntent.CanceledException e) {
224            // Caller canceled the callback; do nothing.
225        }
226    }
227
228    @Override
229    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
230        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "Requires DUMP");
231        final long token = Binder.clearCallingIdentity();
232        try {
233            mConnector.dump(fd, pw, args);
234        } finally {
235            Binder.restoreCallingIdentity(token);
236        }
237    }
238
239    @Nullable
240    private String blockingGetEidFromEuiccService() {
241        final CountDownLatch latch = new CountDownLatch(1);
242        final AtomicReference<String> eidRef = new AtomicReference<>();
243        mConnector.getEid(new EuiccConnector.GetEidCommandCallback() {
244            @Override
245            public void onGetEidComplete(String eid) {
246                eidRef.set(eid);
247                latch.countDown();
248            }
249
250            @Override
251            public void onEuiccServiceUnavailable() {
252                latch.countDown();
253            }
254        });
255        try {
256            latch.await();
257        } catch (InterruptedException e) {
258            Thread.currentThread().interrupt();
259        }
260        return eidRef.get();
261    }
262
263    private boolean callerCanReadPhoneStatePrivileged() {
264        return mContext.checkCallingPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
265                == PackageManager.PERMISSION_GRANTED;
266    }
267
268    private boolean callerCanWriteEmbeddedSubscriptions() {
269        return mContext.checkCallingPermission(Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
270                == PackageManager.PERMISSION_GRANTED;
271    }
272
273    /**
274     * Returns whether the caller has carrier privileges for the active mSubscription on this eUICC.
275     */
276    private boolean callerHasCarrierPrivilegesForActiveSubscription() {
277        // TODO(b/36260308): We should plumb a slot ID through here for multi-SIM devices.
278        TelephonyManager tm =
279                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
280        return tm.hasCarrierPrivileges();
281    }
282}
283