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