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