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