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