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