1 /* 2 * Copyright (C) 2018 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 */ 16 17 package com.android.ons; 18 19 import android.app.PendingIntent; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.os.PersistableBundle; 26 import android.telephony.CarrierConfigManager; 27 import android.telephony.SubscriptionInfo; 28 import android.telephony.SubscriptionManager; 29 import android.telephony.euicc.DownloadableSubscription; 30 import android.telephony.euicc.EuiccManager; 31 import android.util.Log; 32 import android.util.Pair; 33 34 import com.android.internal.annotations.VisibleForTesting; 35 36 import java.util.Stack; 37 38 public class ONSProfileDownloader { 39 40 interface IONSProfileDownloaderListener { onDownloadComplete(int primarySubId)41 void onDownloadComplete(int primarySubId); onDownloadError(int pSIMSubId, DownloadRetryResultCode resultCode, int detailErrCode)42 void onDownloadError(int pSIMSubId, DownloadRetryResultCode resultCode, int detailErrCode); 43 } 44 45 private static final String TAG = ONSProfileDownloader.class.getName(); 46 public static final String ACTION_ONS_ESIM_DOWNLOAD = "com.android.ons.action.ESIM_DOWNLOAD"; 47 48 @VisibleForTesting protected static final String PARAM_PRIMARY_SUBID = "PrimarySubscriptionID"; 49 @VisibleForTesting protected static final String PARAM_REQUEST_TYPE = "REQUEST"; 50 @VisibleForTesting protected static final int REQUEST_CODE_DOWNLOAD_SUB = 1; 51 52 private final Handler mHandler; 53 private final Context mContext; 54 private final CarrierConfigManager mCarrierConfigManager; 55 private final EuiccManager mEuiccManager; 56 private final SubscriptionManager mSubManager; 57 private final ONSProfileConfigurator mONSProfileConfig; 58 private IONSProfileDownloaderListener mListener; 59 60 // Subscription Id of the CBRS PSIM for which opportunistic eSIM is being downloaded. Used to 61 // ignore duplicate download requests when download is in progress. 62 private int mDownloadingPSimSubId; 63 64 protected enum DownloadRetryResultCode { 65 DOWNLOAD_SUCCESSFUL, 66 ERR_UNRESOLVABLE, 67 ERR_MEMORY_FULL, 68 ERR_INSTALL_ESIM_PROFILE_FAILED, 69 ERR_RETRY_DOWNLOAD 70 }; 71 ONSProfileDownloader(Context context, CarrierConfigManager carrierConfigManager, EuiccManager euiccManager, SubscriptionManager subManager, ONSProfileConfigurator onsProfileConfigurator, IONSProfileDownloaderListener listener)72 public ONSProfileDownloader(Context context, CarrierConfigManager carrierConfigManager, 73 EuiccManager euiccManager, SubscriptionManager subManager, 74 ONSProfileConfigurator onsProfileConfigurator, 75 IONSProfileDownloaderListener listener) { 76 mContext = context; 77 mListener = listener; 78 mEuiccManager = euiccManager; 79 mSubManager = subManager; 80 mONSProfileConfig = onsProfileConfigurator; 81 mCarrierConfigManager = carrierConfigManager; 82 83 mHandler = new DownloadHandler(); 84 } 85 86 class DownloadHandler extends Handler { DownloadHandler()87 DownloadHandler() { 88 super(Looper.myLooper()); 89 } 90 91 @Override handleMessage(Message msg)92 public void handleMessage(Message msg) { 93 switch (msg.what) { 94 // Received Response for download request. REQUEST_CODE_DOWNLOAD_SUB was sent to LPA 95 // as part of request intent. 96 case REQUEST_CODE_DOWNLOAD_SUB: { 97 Log.d(TAG, "REQUEST_CODE_DOWNLOAD_SUB callback received"); 98 99 //Clear downloading subscription flag. Indicates no download in progress. 100 synchronized (this) { 101 mDownloadingPSimSubId = -1; 102 } 103 104 int pSIMSubId = ((Intent) msg.obj).getIntExtra(PARAM_PRIMARY_SUBID, 0); 105 int detailedErrCode = ((Intent) msg.obj).getIntExtra( 106 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_DETAILED_CODE, 0); 107 int operationCode = ((Intent) msg.obj).getIntExtra( 108 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_OPERATION_CODE, 0); 109 int errorCode = ((Intent) msg.obj).getIntExtra( 110 EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_ERROR_CODE, 0); 111 112 Log.d(TAG, "Result Code : " + detailedErrCode); 113 Log.d(TAG, "Operation Code : " + operationCode); 114 Log.d(TAG, "Error Code : " + errorCode); 115 116 DownloadRetryResultCode resultCode = mapDownloaderErrorCode(msg.arg1, 117 detailedErrCode, operationCode, errorCode); 118 Log.d(TAG, "DownloadRetryResultCode: " + resultCode); 119 120 switch (resultCode) { 121 case DOWNLOAD_SUCCESSFUL: 122 mListener.onDownloadComplete(pSIMSubId); 123 break; 124 125 case ERR_UNRESOLVABLE: 126 mListener.onDownloadError(pSIMSubId, resultCode, detailedErrCode); 127 Log.e(TAG, "Unresolvable download error: " 128 + getUnresolvableErrorDescription(errorCode)); 129 break; 130 131 default: 132 mListener.onDownloadError(pSIMSubId, resultCode, detailedErrCode); 133 break; 134 } 135 } 136 break; 137 } 138 } 139 140 @VisibleForTesting mapDownloaderErrorCode(int resultCode, int detailedErrCode, int operationCode, int errorCode)141 protected DownloadRetryResultCode mapDownloaderErrorCode(int resultCode, 142 int detailedErrCode, 143 int operationCode, 144 int errorCode) { 145 146 if (operationCode == EuiccManager.OPERATION_SMDX_SUBJECT_REASON_CODE) { 147 //SMDP Error codes handling 148 Pair<String, String> errCode = decodeSmdxSubjectAndReasonCode(detailedErrCode); 149 Log.e(TAG, " Subject Code: " + errCode.first + " Reason Code: " 150 + errCode.second); 151 152 //8.1 - eUICC, 4.8 - Insufficient Memory 153 // eUICC does not have sufficient space for this Profile. 154 if (errCode.equals(Pair.create("8.1.0", "4.8"))) { 155 return DownloadRetryResultCode.ERR_MEMORY_FULL; 156 } 157 158 //8.8.5 - Download order, 4.10 - Time to Live Expired 159 //The Download order has expired 160 if (errCode.equals(Pair.create("8.8.5", "4.10"))) { 161 return DownloadRetryResultCode.ERR_RETRY_DOWNLOAD; 162 } 163 164 //All other errors are unresolvable or retry after SIM State Change 165 return DownloadRetryResultCode.ERR_UNRESOLVABLE; 166 167 } 168 169 switch (errorCode) { 170 171 //Success Cases 172 case EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_OK: { 173 return DownloadRetryResultCode.DOWNLOAD_SUCCESSFUL; 174 } 175 176 //Low eUICC memory cases 177 case EuiccManager.ERROR_EUICC_INSUFFICIENT_MEMORY: { 178 Log.d(TAG, "Download ERR: EUICC_INSUFFICIENT_MEMORY"); 179 return DownloadRetryResultCode.ERR_MEMORY_FULL; 180 } 181 182 //Temporary download error cases 183 case EuiccManager.ERROR_TIME_OUT: 184 case EuiccManager.ERROR_CONNECTION_ERROR: 185 case EuiccManager.ERROR_OPERATION_BUSY: { 186 return DownloadRetryResultCode.ERR_RETRY_DOWNLOAD; 187 } 188 189 //Profile installation failure cases 190 case EuiccManager.ERROR_INSTALL_PROFILE: { 191 return DownloadRetryResultCode.ERR_INSTALL_ESIM_PROFILE_FAILED; 192 } 193 194 default: { 195 return DownloadRetryResultCode.ERR_UNRESOLVABLE; 196 } 197 } 198 } 199 } 200 getUnresolvableErrorDescription(int errorCode)201 private String getUnresolvableErrorDescription(int errorCode) { 202 switch (errorCode) { 203 case EuiccManager.ERROR_INVALID_ACTIVATION_CODE: 204 return "ERROR_INVALID_ACTIVATION_CODE"; 205 206 case EuiccManager.ERROR_UNSUPPORTED_VERSION: 207 return "ERROR_UNSUPPORTED_VERSION"; 208 209 case EuiccManager.ERROR_INSTALL_PROFILE: 210 return "ERROR_INSTALL_PROFILE"; 211 212 case EuiccManager.ERROR_SIM_MISSING: 213 return "ERROR_SIM_MISSING"; 214 215 case EuiccManager.ERROR_ADDRESS_MISSING: 216 return "ERROR_ADDRESS_MISSING"; 217 218 case EuiccManager.ERROR_CERTIFICATE_ERROR: 219 return "ERROR_CERTIFICATE_ERROR"; 220 221 case EuiccManager.ERROR_NO_PROFILES_AVAILABLE: 222 return "ERROR_NO_PROFILES_AVAILABLE"; 223 224 case EuiccManager.ERROR_CARRIER_LOCKED: 225 return "ERROR_CARRIER_LOCKED"; 226 } 227 228 return "Unknown"; 229 } 230 231 protected enum DownloadProfileResult { 232 SUCCESS, 233 DUPLICATE_REQUEST, 234 INVALID_SMDP_ADDRESS 235 } 236 downloadProfile(int primarySubId)237 protected DownloadProfileResult downloadProfile(int primarySubId) { 238 Log.d(TAG, "downloadProfile"); 239 240 //Get SMDP address from carrier configuration. 241 String smdpAddress = getSMDPServerAddress(primarySubId); 242 if (smdpAddress == null || smdpAddress.length() <= 0) { 243 return DownloadProfileResult.INVALID_SMDP_ADDRESS; 244 } 245 246 synchronized (this) { 247 if (mDownloadingPSimSubId == primarySubId) { 248 Log.d(TAG, "Download already in progress."); 249 return DownloadProfileResult.DUPLICATE_REQUEST; 250 } 251 252 mDownloadingPSimSubId = primarySubId; 253 } 254 255 //Generate Activation code 1${SM-DP+ FQDN}$ 256 String activationCode = "1$" + smdpAddress + "$"; 257 Intent intent = new Intent(mContext, ONSProfileResultReceiver.class); 258 intent.setAction(ACTION_ONS_ESIM_DOWNLOAD); 259 intent.putExtra(PARAM_REQUEST_TYPE, REQUEST_CODE_DOWNLOAD_SUB); 260 intent.putExtra(PARAM_PRIMARY_SUBID, primarySubId); 261 PendingIntent callbackIntent = PendingIntent.getBroadcast(mContext, 262 REQUEST_CODE_DOWNLOAD_SUB, intent, PendingIntent.FLAG_MUTABLE); 263 264 Log.d(TAG, "Download Request sent to EUICC Manager"); 265 // TODO: Check with euicc team if card specific EUICC Manager should be used to download. 266 // Once the eSIM profile is downloaded, port is auto decided by eUICC to activate the 267 // subscription. If there is no available port, an 268 // {@link #EMBEDDED_SUBSCRIPTION_RESULT_RESOLVABLE_ERROR} will be returned in the callback 269 // intent to prompt the user to disable an already-active subscription. However, ONS will 270 // not show any prompt to the user and silently fails to activate the subscription. 271 // ONS will try to provision again when carrier configuration change event is received. 272 SubscriptionInfo primarySubInfo = mSubManager.getActiveSubscriptionInfo(primarySubId); 273 if (primarySubInfo != null && primarySubInfo.isEmbedded()) { 274 EuiccManager euiccManager = mEuiccManager.createForCardId(primarySubInfo.getCardId()); 275 euiccManager.downloadSubscription(DownloadableSubscription.forActivationCode( 276 activationCode), true, callbackIntent); 277 } else { 278 mEuiccManager.downloadSubscription(DownloadableSubscription.forActivationCode( 279 activationCode), true, callbackIntent); 280 } 281 282 return DownloadProfileResult.SUCCESS; 283 } 284 285 /** 286 * Retrieves SMDP+ server address of the given subscription from carrier configuration. 287 * 288 * @param subscriptionId subscription Id of the primary SIM. 289 * @return FQDN of SMDP+ server. 290 */ getSMDPServerAddress(int subscriptionId)291 private String getSMDPServerAddress(int subscriptionId) { 292 PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subscriptionId); 293 if (config != null) { 294 return config.getString(CarrierConfigManager.KEY_SMDP_SERVER_ADDRESS_STRING); 295 } else { 296 return null; 297 } 298 } 299 300 /** 301 * Given encoded error code described in 302 * {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE} decode it 303 * into SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] from GSMA (SGP.22 v2.2) 304 * 305 * @param resultCode from 306 * {@link android.telephony.euicc.EuiccManager#OPERATION_SMDX_SUBJECT_REASON_CODE} 307 * 308 * @return a pair containing SubjectCode[5.2.6.1] and ReasonCode[5.2.6.2] from GSMA (SGP.22 309 * v2.2) 310 */ 311 @VisibleForTesting decodeSmdxSubjectAndReasonCode(int resultCode)312 protected static Pair<String, String> decodeSmdxSubjectAndReasonCode(int resultCode) { 313 final int numOfSections = 6; 314 final int bitsPerSection = 4; 315 final int sectionMask = 0xF; 316 317 final Stack<Integer> sections = new Stack<>(); 318 319 // Extracting each section of digits backwards. 320 for (int i = 0; i < numOfSections; ++i) { 321 int sectionDigit = resultCode & sectionMask; 322 sections.push(sectionDigit); 323 resultCode = resultCode >>> bitsPerSection; 324 } 325 326 String subjectCode = sections.pop() + "." + sections.pop() + "." + sections.pop(); 327 String reasonCode = sections.pop() + "." + sections.pop() + "." + sections.pop(); 328 329 // drop the leading zeros, e.g. 0.1 -> 1, 0.0.3 -> 3, 0.5.1 -> 5.1 330 subjectCode = subjectCode.replaceAll("^(0\\.)*", ""); 331 reasonCode = reasonCode.replaceAll("^(0\\.)*", ""); 332 333 return Pair.create(subjectCode, reasonCode); 334 } 335 336 /** 337 * Callback to receive result for subscription activate request and process the same. 338 * @param intent 339 * @param resultCode 340 */ onCallbackIntentReceived(Intent intent, int resultCode)341 public void onCallbackIntentReceived(Intent intent, int resultCode) { 342 Message msg = new Message(); 343 msg.what = REQUEST_CODE_DOWNLOAD_SUB; 344 msg.arg1 = resultCode; 345 msg.obj = intent; 346 mHandler.sendMessage(msg); 347 } 348 } 349