1 /* 2 * Copyright (C) 2023 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.libraries.entitlement; 18 19 import android.content.Context; 20 import android.telephony.SubscriptionManager; 21 import android.telephony.TelephonyManager; 22 import android.text.TextUtils; 23 import android.util.Log; 24 25 import androidx.annotation.IntDef; 26 import androidx.annotation.NonNull; 27 import androidx.annotation.Nullable; 28 29 import com.android.libraries.entitlement.EsimOdsaOperation.OdsaServiceStatus; 30 import com.android.libraries.entitlement.http.HttpConstants; 31 import com.android.libraries.entitlement.odsa.AcquireConfigurationOperation.AcquireConfigurationRequest; 32 import com.android.libraries.entitlement.odsa.AcquireConfigurationOperation.AcquireConfigurationResponse; 33 import com.android.libraries.entitlement.odsa.AcquireTemporaryTokenOperation.AcquireTemporaryTokenRequest; 34 import com.android.libraries.entitlement.odsa.AcquireTemporaryTokenOperation.AcquireTemporaryTokenResponse; 35 import com.android.libraries.entitlement.odsa.CheckEligibilityOperation; 36 import com.android.libraries.entitlement.odsa.CheckEligibilityOperation.CheckEligibilityRequest; 37 import com.android.libraries.entitlement.odsa.CheckEligibilityOperation.CheckEligibilityResponse; 38 import com.android.libraries.entitlement.odsa.DownloadInfo; 39 import com.android.libraries.entitlement.odsa.GetPhoneNumberOperation.GetPhoneNumberRequest; 40 import com.android.libraries.entitlement.odsa.GetPhoneNumberOperation.GetPhoneNumberResponse; 41 import com.android.libraries.entitlement.odsa.ManageServiceOperation.ManageServiceRequest; 42 import com.android.libraries.entitlement.odsa.ManageServiceOperation.ManageServiceResponse; 43 import com.android.libraries.entitlement.odsa.ManageSubscriptionOperation.ManageSubscriptionRequest; 44 import com.android.libraries.entitlement.odsa.ManageSubscriptionOperation.ManageSubscriptionResponse; 45 import com.android.libraries.entitlement.odsa.MessageInfo; 46 import com.android.libraries.entitlement.odsa.OdsaResponse; 47 import com.android.libraries.entitlement.odsa.PlanOffer; 48 import com.android.libraries.entitlement.utils.Ts43Constants; 49 import com.android.libraries.entitlement.utils.Ts43XmlDoc; 50 51 import com.google.common.base.Strings; 52 import com.google.common.collect.ImmutableList; 53 54 import java.lang.annotation.Retention; 55 import java.lang.annotation.RetentionPolicy; 56 import java.net.MalformedURLException; 57 import java.net.URL; 58 import java.time.Instant; 59 import java.time.OffsetDateTime; 60 import java.time.format.DateTimeParseException; 61 import java.util.Arrays; 62 import java.util.Base64; 63 import java.util.Collections; 64 import java.util.List; 65 import java.util.Objects; 66 67 /** TS43 operations described in GSMA Service Entitlement Configuration spec. */ 68 public class Ts43Operation { 69 private static final String TAG = "Ts43"; 70 71 /** 72 * The normal token retrieved via {@link Ts43Authentication#getAuthToken(int, String, String, 73 * String)} or {@link Ts43Authentication#getAuthToken(URL)}. 74 */ 75 public static final int TOKEN_TYPE_NORMAL = 1; 76 77 /** 78 * The temporary token retrieved via {@link 79 * Ts43Operation#acquireTemporaryToken(AcquireTemporaryTokenRequest)}. 80 */ 81 public static final int TOKEN_TYPE_TEMPORARY = 2; 82 83 @Retention(RetentionPolicy.SOURCE) 84 @IntDef({TOKEN_TYPE_NORMAL, TOKEN_TYPE_TEMPORARY}) 85 public @interface TokenType { 86 } 87 88 /** The application context. */ 89 @NonNull 90 private final Context mContext; 91 92 /** 93 * The TS.43 entitlement version to use. For example, {@code "9.0"}. If {@code null}, version 94 * {@code "2.0"} will be used by default. 95 */ 96 @NonNull 97 private final String mEntitlementVersion; 98 99 /** The entitlement server address. */ 100 @NonNull 101 private final URL mEntitlementServerAddress; 102 103 /** 104 * The authentication token used for TS.43 operation. This token could be automatically updated 105 * after each TS.43 operation if the server provides the new token in the operation's HTTP 106 * response. 107 */ 108 @Nullable 109 private String mAuthToken; 110 111 /** 112 * The temporary token retrieved from {@link 113 * #acquireTemporaryToken(AcquireTemporaryTokenRequest)}. 114 */ 115 @Nullable 116 private String mTemporaryToken; 117 118 /** 119 * Token type. When token type is {@link #TOKEN_TYPE_NORMAL}, {@link #mAuthToken} is used. When 120 * toke type is {@link #TOKEN_TYPE_TEMPORARY}, {@link #mTemporaryToken} is used. 121 */ 122 @TokenType 123 private int mTokenType; 124 125 private final ServiceEntitlement mServiceEntitlement; 126 127 /** IMEI of the device. */ 128 private final String mImei; 129 130 /** 131 * Constructor of Ts43Operation. 132 * 133 * @param slotIndex The logical SIM slot index involved in ODSA operation. 134 * @param entitlementServerAddress The entitlement server address. 135 * @param entitlementVersion The TS.43 entitlement version to use. For example, 136 * {@code "9.0"}. If {@code null}, version {@code "2.0"} will be used 137 * by default. 138 * @param authToken The authentication token. 139 * @param tokenType The token type. Can be {@link #TOKEN_TYPE_NORMAL} or 140 * {@link #TOKEN_TYPE_TEMPORARY}. 141 */ Ts43Operation( @onNull Context context, int slotIndex, @NonNull URL entitlementServerAddress, @Nullable String entitlementVersion, @NonNull String authToken, @TokenType int tokenType)142 public Ts43Operation( 143 @NonNull Context context, 144 int slotIndex, 145 @NonNull URL entitlementServerAddress, 146 @Nullable String entitlementVersion, 147 @NonNull String authToken, 148 @TokenType int tokenType) { 149 mContext = context; 150 mEntitlementServerAddress = entitlementServerAddress; 151 if (entitlementVersion != null) { 152 mEntitlementVersion = entitlementVersion; 153 } else { 154 mEntitlementVersion = Ts43Constants.DEFAULT_ENTITLEMENT_VERSION; 155 } 156 157 if (tokenType == TOKEN_TYPE_NORMAL) { 158 mAuthToken = authToken; 159 } else if (tokenType == TOKEN_TYPE_TEMPORARY) { 160 mTemporaryToken = authToken; 161 } else { 162 throw new IllegalArgumentException("Invalid token type " + tokenType); 163 } 164 mTokenType = tokenType; 165 166 CarrierConfig carrierConfig = 167 CarrierConfig.builder().setServerUrl(mEntitlementServerAddress.toString()).build(); 168 169 mServiceEntitlement = 170 new ServiceEntitlement( 171 mContext, carrierConfig, SubscriptionManager.getSubscriptionId(slotIndex)); 172 173 String imei = null; 174 TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); 175 if (telephonyManager != null) { 176 if (slotIndex < 0 || slotIndex >= telephonyManager.getActiveModemCount()) { 177 throw new IllegalArgumentException("getAuthToken: invalid slot index " + slotIndex); 178 } 179 imei = telephonyManager.getImei(slotIndex); 180 } 181 mImei = Strings.nullToEmpty(imei); 182 } 183 184 /** 185 * To verify if end-user is allowed to invoke the ODSA application as described in GSMA Service 186 * Entitlement Configuration section 6.2 and 6.5.2. 187 * 188 * @return {@code true} if the end-user is allowed to perform ODSA operation. 189 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 190 * error from the server, the error code can be retrieved by 191 * {@link ServiceEntitlementException#getHttpStatus()} 192 */ 193 @NonNull checkEligibility( @onNull CheckEligibilityRequest checkEligibilityRequest)194 public CheckEligibilityResponse checkEligibility( 195 @NonNull CheckEligibilityRequest checkEligibilityRequest) 196 throws ServiceEntitlementException { 197 Objects.requireNonNull(checkEligibilityRequest); 198 199 ServiceEntitlementRequest.Builder builder = 200 ServiceEntitlementRequest.builder() 201 .setEntitlementVersion(mEntitlementVersion) 202 .setTerminalId(mImei); 203 204 if (mTokenType == TOKEN_TYPE_NORMAL) { 205 builder.setAuthenticationToken(mAuthToken); 206 } else if (mTokenType == TOKEN_TYPE_TEMPORARY) { 207 builder.setTemporaryToken(mTemporaryToken); 208 } 209 210 String notificationToken = checkEligibilityRequest.notificationToken(); 211 if (!TextUtils.isEmpty(notificationToken)) { 212 builder.setNotificationToken(notificationToken); 213 } 214 int notificationAction = checkEligibilityRequest.notificationAction(); 215 if (Ts43Constants.isValidNotificationAction(notificationAction)) { 216 builder.setNotificationAction(notificationAction); 217 } 218 219 ServiceEntitlementRequest request = builder.build(); 220 221 EsimOdsaOperation operation = 222 EsimOdsaOperation.builder() 223 .setOperation(EsimOdsaOperation.OPERATION_CHECK_ELIGIBILITY) 224 .setCompanionTerminalId(checkEligibilityRequest.companionTerminalId()) 225 .setCompanionTerminalVendor( 226 checkEligibilityRequest.companionTerminalVendor()) 227 .setCompanionTerminalModel(checkEligibilityRequest.companionTerminalModel()) 228 .setCompanionTerminalSoftwareVersion( 229 checkEligibilityRequest.companionTerminalSoftwareVersion()) 230 .setCompanionTerminalFriendlyName( 231 checkEligibilityRequest.companionTerminalFriendlyName()) 232 .build(); 233 234 String rawXml; 235 try { 236 rawXml = 237 mServiceEntitlement.performEsimOdsa(checkEligibilityRequest.appId(), request, 238 operation); 239 } catch (ServiceEntitlementException e) { 240 Log.w(TAG, "manageSubscription: Failed to perform ODSA operation. e=" + e); 241 throw e; 242 } 243 244 // Build the response of check eligibility operation. Refer to GSMA Service Entitlement 245 // Configuration section 6.5.2. 246 CheckEligibilityResponse.Builder responseBuilder = CheckEligibilityResponse.builder(); 247 248 Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml); 249 250 try { 251 processGeneralResult(ts43XmlDoc, responseBuilder); 252 } catch (MalformedURLException e) { 253 throw new ServiceEntitlementException( 254 ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE, 255 "checkEligibility: Malformed URL " + rawXml); 256 } 257 258 // Parse the eligibility 259 String eligibilityString = 260 ts43XmlDoc.get( 261 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 262 Ts43XmlDoc.Parm.PRIMARY_APP_ELIGIBILITY); 263 if (TextUtils.isEmpty(eligibilityString)) { 264 eligibilityString = 265 ts43XmlDoc.get( 266 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 267 Ts43XmlDoc.Parm.COMPANION_APP_ELIGIBILITY); 268 } 269 270 int eligibility = CheckEligibilityOperation.ELIGIBILITY_RESULT_UNKNOWN; 271 if (!TextUtils.isEmpty(eligibilityString)) { 272 switch (eligibilityString) { 273 case Ts43XmlDoc.ParmValues.DISABLED: 274 eligibility = CheckEligibilityOperation.ELIGIBILITY_RESULT_DISABLED; 275 break; 276 case Ts43XmlDoc.ParmValues.ENABLED: 277 eligibility = CheckEligibilityOperation.ELIGIBILITY_RESULT_ENABLED; 278 break; 279 case Ts43XmlDoc.ParmValues.INCOMPATIBLE: 280 eligibility = CheckEligibilityOperation.ELIGIBILITY_RESULT_INCOMPATIBLE; 281 break; 282 } 283 } 284 responseBuilder.setAppEligibility(eligibility); 285 286 // Parse companion device services 287 String companionDeviceServices = 288 ts43XmlDoc.get( 289 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 290 Ts43XmlDoc.Parm.COMPANION_DEVICE_SERVICES); 291 292 if (!TextUtils.isEmpty(companionDeviceServices)) { 293 List<String> companionDeviceServicesList = 294 Arrays.asList(companionDeviceServices.split("\\s*,\\s*")); 295 responseBuilder.setCompanionDeviceServices( 296 ImmutableList.copyOf(companionDeviceServicesList)); 297 } 298 299 // Parse notEnabledURL 300 URL notEnabledURL = null; 301 String notEnabledURLString = 302 ts43XmlDoc.get( 303 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 304 Ts43XmlDoc.Parm.NOT_ENABLED_URL); 305 306 try { 307 notEnabledURL = new URL(notEnabledURLString); 308 responseBuilder.setNotEnabledUrl(notEnabledURL); 309 } catch (MalformedURLException e) { 310 Log.w(TAG, "checkEligibility: malformed URL " + notEnabledURLString); 311 } 312 313 // Parse notEnabledUserData 314 String notEnabledUserData = 315 ts43XmlDoc.get( 316 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 317 Ts43XmlDoc.Parm.NOT_ENABLED_USER_DATA); 318 319 if (!TextUtils.isEmpty(notEnabledUserData)) { 320 responseBuilder.setNotEnabledUserData(notEnabledUserData); 321 } 322 323 // Parse notEnabledContentsType 324 String notEnabledContentsTypeString = 325 ts43XmlDoc.get( 326 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 327 Ts43XmlDoc.Parm.NOT_ENABLED_CONTENTS_TYPE); 328 329 int notEnabledContentsType = HttpConstants.ContentType.UNKNOWN; 330 if (!TextUtils.isEmpty(notEnabledContentsTypeString)) { 331 switch (notEnabledContentsTypeString) { 332 case Ts43XmlDoc.ParmValues.CONTENTS_TYPE_XML: 333 notEnabledContentsType = HttpConstants.ContentType.XML; 334 break; 335 case Ts43XmlDoc.ParmValues.CONTENTS_TYPE_JSON: 336 notEnabledContentsType = HttpConstants.ContentType.JSON; 337 break; 338 } 339 } 340 responseBuilder.setNotEnabledContentsType(notEnabledContentsType); 341 342 return responseBuilder.build(); 343 } 344 345 /** 346 * To request for subscription-related action on a primary or companion device as described in 347 * GSMA Service Entitlement Configuration section 6.2 and 6.5.3. 348 * 349 * @param manageSubscriptionRequest The manage subscription request. 350 * @return The response of manage subscription request. 351 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 352 * error from the server, the error code can be retrieved by 353 * {@link ServiceEntitlementException#getHttpStatus()} 354 */ 355 @NonNull manageSubscription( @onNull ManageSubscriptionRequest manageSubscriptionRequest)356 public ManageSubscriptionResponse manageSubscription( 357 @NonNull ManageSubscriptionRequest manageSubscriptionRequest) 358 throws ServiceEntitlementException { 359 Objects.requireNonNull(manageSubscriptionRequest); 360 361 ServiceEntitlementRequest.Builder builder = 362 ServiceEntitlementRequest.builder() 363 .setEntitlementVersion(mEntitlementVersion) 364 .setTerminalId(mImei) 365 .setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_XML); 366 367 if (mTokenType == TOKEN_TYPE_NORMAL) { 368 builder.setAuthenticationToken(mAuthToken); 369 } else if (mTokenType == TOKEN_TYPE_TEMPORARY) { 370 builder.setTemporaryToken(mTemporaryToken); 371 } 372 373 String notificationToken = manageSubscriptionRequest.notificationToken(); 374 if (!TextUtils.isEmpty(notificationToken)) { 375 builder.setNotificationToken(notificationToken); 376 } 377 int notificationAction = manageSubscriptionRequest.notificationAction(); 378 if (Ts43Constants.isValidNotificationAction(notificationAction)) { 379 builder.setNotificationAction(notificationAction); 380 } 381 382 ServiceEntitlementRequest request = builder.build(); 383 384 EsimOdsaOperation operation = 385 EsimOdsaOperation.builder() 386 .setOperation(EsimOdsaOperation.OPERATION_MANAGE_SUBSCRIPTION) 387 .setOperationType(manageSubscriptionRequest.operationType()) 388 .setCompanionTerminalId(manageSubscriptionRequest.companionTerminalId()) 389 .setCompanionTerminalVendor( 390 manageSubscriptionRequest.companionTerminalVendor()) 391 .setCompanionTerminalModel( 392 manageSubscriptionRequest.companionTerminalModel()) 393 .setCompanionTerminalSoftwareVersion( 394 manageSubscriptionRequest.companionTerminalSoftwareVersion()) 395 .setCompanionTerminalFriendlyName( 396 manageSubscriptionRequest.companionTerminalFriendlyName()) 397 .setCompanionTerminalService( 398 manageSubscriptionRequest.companionTerminalService()) 399 .setCompanionTerminalIccid( 400 manageSubscriptionRequest.companionTerminalIccid()) 401 .setCompanionTerminalEid(manageSubscriptionRequest.companionTerminalEid()) 402 .setTerminalIccid(manageSubscriptionRequest.terminalIccid()) 403 .setTerminalEid(manageSubscriptionRequest.terminalEid()) 404 .setTargetTerminalId(manageSubscriptionRequest.targetTerminalId()) 405 // non TS.43 standard support 406 .setTargetTerminalIds(manageSubscriptionRequest.targetTerminalIds()) 407 .setTargetTerminalIccid(manageSubscriptionRequest.targetTerminalIccid()) 408 .setTargetTerminalEid(manageSubscriptionRequest.targetTerminalEid()) 409 // non TS.43 standard support 410 .setTargetTerminalSerialNumber( 411 manageSubscriptionRequest.targetTerminalSerialNumber()) 412 // non TS.43 standard support 413 .setTargetTerminalModel(manageSubscriptionRequest.targetTerminalModel()) 414 .setOldTerminalId(manageSubscriptionRequest.oldTerminalId()) 415 .setOldTerminalIccid(manageSubscriptionRequest.oldTerminalIccid()) 416 .setMessageResponse(manageSubscriptionRequest.messageResponse()) 417 .setMessageButton(manageSubscriptionRequest.messageButton()) 418 .build(); 419 420 String rawXml; 421 try { 422 rawXml = 423 mServiceEntitlement.performEsimOdsa( 424 manageSubscriptionRequest.appId(), request, operation); 425 } catch (ServiceEntitlementException e) { 426 Log.w(TAG, "manageSubscription: Failed to perform ODSA operation. e=" + e); 427 throw e; 428 } 429 430 // Build the response of manage subscription operation. Refer to GSMA Service Entitlement 431 // Configuration section 6.5.3. 432 ManageSubscriptionResponse.Builder responseBuilder = ManageSubscriptionResponse.builder(); 433 434 Ts43XmlDoc ts43XmlDoc; 435 try { 436 ts43XmlDoc = new Ts43XmlDoc(rawXml); 437 processGeneralResult(ts43XmlDoc, responseBuilder); 438 } catch (MalformedURLException e) { 439 throw new ServiceEntitlementException( 440 ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE, 441 "manageSubscription: Malformed URL " + rawXml); 442 } 443 444 int subscriptionResult = ManageSubscriptionResponse.SUBSCRIPTION_RESULT_UNKNOWN; 445 446 // Parse subscription result. 447 String subscriptionResultString = 448 ts43XmlDoc.get( 449 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 450 Ts43XmlDoc.Parm.SUBSCRIPTION_RESULT); 451 452 if (!TextUtils.isEmpty(subscriptionResultString)) { 453 switch (subscriptionResultString) { 454 case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET: 455 subscriptionResult = 456 ManageSubscriptionResponse.SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET; 457 458 String subscriptionServiceURLString = 459 ts43XmlDoc.get( 460 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 461 Ts43XmlDoc.Parm.SUBSCRIPTION_SERVICE_URL); 462 463 if (!TextUtils.isEmpty(subscriptionServiceURLString)) { 464 try { 465 responseBuilder.setSubscriptionServiceUrl( 466 new URL(subscriptionServiceURLString)); 467 468 String subscriptionServiceUserDataString = 469 ts43XmlDoc.get( 470 ImmutableList.of( 471 Ts43XmlDoc.CharacteristicType.APPLICATION), 472 Ts43XmlDoc.Parm.SUBSCRIPTION_SERVICE_USER_DATA); 473 if (!TextUtils.isEmpty(subscriptionServiceUserDataString)) { 474 responseBuilder.setSubscriptionServiceUserData( 475 subscriptionServiceUserDataString); 476 } 477 478 String subscriptionServiceContentsTypeString = 479 ts43XmlDoc.get( 480 ImmutableList.of( 481 Ts43XmlDoc.CharacteristicType.APPLICATION), 482 Ts43XmlDoc.Parm.SUBSCRIPTION_SERVICE_CONTENTS_TYPE); 483 if (!TextUtils.isEmpty(subscriptionServiceContentsTypeString)) { 484 int contentsType = HttpConstants.ContentType.UNKNOWN; 485 switch (subscriptionServiceContentsTypeString) { 486 case Ts43XmlDoc.ParmValues.CONTENTS_TYPE_XML: 487 contentsType = HttpConstants.ContentType.XML; 488 break; 489 case Ts43XmlDoc.ParmValues.CONTENTS_TYPE_JSON: 490 contentsType = HttpConstants.ContentType.JSON; 491 break; 492 } 493 responseBuilder.setSubscriptionServiceContentsType(contentsType); 494 } 495 } catch (MalformedURLException e) { 496 Log.w(TAG, "Malformed URL received. " + subscriptionServiceURLString); 497 } 498 } 499 break; 500 case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_DOWNLOAD_PROFILE: 501 subscriptionResult = 502 ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DOWNLOAD_PROFILE; 503 DownloadInfo downloadInfo = 504 parseDownloadInfo( 505 ImmutableList.of( 506 Ts43XmlDoc.CharacteristicType.APPLICATION, 507 Ts43XmlDoc.CharacteristicType.DOWNLOAD_INFO), 508 ts43XmlDoc); 509 if (downloadInfo != null) { 510 responseBuilder.setDownloadInfo(downloadInfo); 511 } 512 break; 513 case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_DONE: 514 subscriptionResult = ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DONE; 515 break; 516 case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_DELAYED_DOWNLOAD: 517 subscriptionResult = 518 ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DELAYED_DOWNLOAD; 519 break; 520 case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_DISMISS: 521 subscriptionResult = ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DISMISS; 522 break; 523 case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_DELETE_PROFILE_IN_USE: 524 subscriptionResult = 525 ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DELETE_PROFILE_IN_USE; 526 break; 527 case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_REDOWNLOADABLE_PROFILE_IS_MANDATORY: 528 subscriptionResult = 529 ManageSubscriptionResponse 530 .SUBSCRIPTION_RESULT_REDOWNLOADABLE_PROFILE_IS_MANDATORY; 531 break; 532 case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_REQUIRES_USER_INPUT: 533 subscriptionResult = 534 ManageSubscriptionResponse.SUBSCRIPTION_RESULT_REQUIRES_USER_INPUT; 535 break; 536 } 537 } 538 539 responseBuilder.setSubscriptionResult(subscriptionResult); 540 return responseBuilder.build(); 541 } 542 543 /** 544 * To activate/deactivate the service on the primary or companion device as described in GSMA 545 * Service Entitlement Configuration section 6.2 and 6.5.4. This is an optional operation. 546 * 547 * @param manageServiceRequest The manage service request. 548 * @return The response of manage service request. 549 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 550 * error from the server, the error code can be retrieved by 551 * {@link ServiceEntitlementException#getHttpStatus()} 552 */ 553 @NonNull manageService(@onNull ManageServiceRequest manageServiceRequest)554 public ManageServiceResponse manageService(@NonNull ManageServiceRequest manageServiceRequest) 555 throws ServiceEntitlementException { 556 Objects.requireNonNull(manageServiceRequest); 557 558 ServiceEntitlementRequest.Builder builder = 559 ServiceEntitlementRequest.builder() 560 .setEntitlementVersion(mEntitlementVersion) 561 .setTerminalId(mImei); 562 563 if (mTokenType == TOKEN_TYPE_NORMAL) { 564 builder.setAuthenticationToken(mAuthToken); 565 } else if (mTokenType == TOKEN_TYPE_TEMPORARY) { 566 builder.setTemporaryToken(mTemporaryToken); 567 } 568 569 ServiceEntitlementRequest request = builder.build(); 570 571 EsimOdsaOperation operation = 572 EsimOdsaOperation.builder() 573 .setOperation(EsimOdsaOperation.OPERATION_MANAGE_SERVICE) 574 .setOperationType(manageServiceRequest.operationType()) 575 .setCompanionTerminalId(manageServiceRequest.companionTerminalId()) 576 .setCompanionTerminalVendor(manageServiceRequest.companionTerminalVendor()) 577 .setCompanionTerminalModel(manageServiceRequest.companionTerminalModel()) 578 .setCompanionTerminalSoftwareVersion( 579 manageServiceRequest.companionTerminalSoftwareVersion()) 580 .setCompanionTerminalFriendlyName( 581 manageServiceRequest.companionTerminalFriendlyName()) 582 .setCompanionTerminalService( 583 manageServiceRequest.companionTerminalService()) 584 .setCompanionTerminalIccid(manageServiceRequest.companionTerminalIccid()) 585 .build(); 586 587 String rawXml; 588 try { 589 rawXml = 590 mServiceEntitlement.performEsimOdsa(manageServiceRequest.appId(), request, 591 operation); 592 } catch (ServiceEntitlementException e) { 593 Log.w(TAG, "manageService: Failed to perform ODSA operation. e=" + e); 594 throw e; 595 } 596 597 // Build the response of manage service operation. Refer to GSMA Service Entitlement 598 // Configuration section 6.5.4. 599 ManageServiceResponse.Builder responseBuilder = ManageServiceResponse.builder(); 600 601 Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml); 602 603 try { 604 processGeneralResult(ts43XmlDoc, responseBuilder); 605 } catch (MalformedURLException e) { 606 throw new ServiceEntitlementException( 607 ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE, 608 "manageService: Malformed URL " + rawXml); 609 } 610 611 // Parse service status. 612 String serviceStatusString = 613 ts43XmlDoc.get( 614 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 615 Ts43XmlDoc.Parm.SERVICE_STATUS); 616 617 if (!TextUtils.isEmpty(serviceStatusString)) { 618 responseBuilder.setServiceStatus(getServiceStatusFromString(serviceStatusString)); 619 } 620 621 return responseBuilder.build(); 622 } 623 624 /** 625 * To provide service related data about a primary or companion device as described in GSMA 626 * Service Entitlement Configuration section 6.2 and 6.5.5. 627 * 628 * @param acquireConfigurationRequest The acquire configuration request. 629 * @return The response of acquire configuration request. 630 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 631 * error from the server, the error code can be retrieved by 632 * {@link ServiceEntitlementException#getHttpStatus()} 633 */ 634 @NonNull acquireConfiguration( @onNull AcquireConfigurationRequest acquireConfigurationRequest)635 public AcquireConfigurationResponse acquireConfiguration( 636 @NonNull AcquireConfigurationRequest acquireConfigurationRequest) 637 throws ServiceEntitlementException { 638 Objects.requireNonNull(acquireConfigurationRequest); 639 640 ServiceEntitlementRequest.Builder builder = ServiceEntitlementRequest.builder() 641 .setEntitlementVersion(mEntitlementVersion) 642 .setTerminalId(mImei) 643 .setAuthenticationToken(mAuthToken); 644 645 String notificationToken = acquireConfigurationRequest.notificationToken(); 646 if (!TextUtils.isEmpty(notificationToken)) { 647 builder.setNotificationToken(notificationToken); 648 } 649 int notificationAction = acquireConfigurationRequest.notificationAction(); 650 if (Ts43Constants.isValidNotificationAction(notificationAction)) { 651 builder.setNotificationAction(notificationAction); 652 } 653 654 ServiceEntitlementRequest request = builder.build(); 655 656 EsimOdsaOperation operation = 657 EsimOdsaOperation.builder() 658 .setOperation(EsimOdsaOperation.OPERATION_ACQUIRE_CONFIGURATION) 659 .setCompanionTerminalId(acquireConfigurationRequest.companionTerminalId()) 660 .setCompanionTerminalIccid( 661 acquireConfigurationRequest.companionTerminalIccid()) 662 .setCompanionTerminalEid(acquireConfigurationRequest.companionTerminalEid()) 663 .setTerminalIccid(acquireConfigurationRequest.terminalIccid()) 664 .setTerminalEid(acquireConfigurationRequest.terminalEid()) 665 .setTargetTerminalId(acquireConfigurationRequest.targetTerminalId()) 666 .setTargetTerminalIccid(acquireConfigurationRequest.targetTerminalIccid()) 667 .setTargetTerminalEid(acquireConfigurationRequest.targetTerminalEid()) 668 .build(); 669 670 String rawXml; 671 try { 672 rawXml = 673 mServiceEntitlement.performEsimOdsa( 674 acquireConfigurationRequest.appId(), request, operation); 675 } catch (ServiceEntitlementException e) { 676 Log.w(TAG, "acquireConfiguration: Failed to perform ODSA operation. e=" + e); 677 throw e; 678 } 679 680 AcquireConfigurationResponse.Builder responseBuilder = 681 AcquireConfigurationResponse.builder(); 682 AcquireConfigurationResponse.Configuration.Builder configBuilder = 683 AcquireConfigurationResponse.Configuration.builder(); 684 685 Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml); 686 687 try { 688 processGeneralResult(ts43XmlDoc, responseBuilder); 689 } catch (MalformedURLException e) { 690 throw new ServiceEntitlementException( 691 ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE, 692 "manageSubscription: Malformed URL " + rawXml); 693 } 694 695 // Parse service status. 696 String serviceStatusString = 697 ts43XmlDoc.get( 698 ImmutableList.of( 699 Ts43XmlDoc.CharacteristicType.APPLICATION, 700 Ts43XmlDoc.CharacteristicType.PRIMARY_CONFIGURATION), 701 Ts43XmlDoc.Parm.SERVICE_STATUS); 702 703 if (!TextUtils.isEmpty(serviceStatusString)) { 704 configBuilder.setServiceStatus(getServiceStatusFromString(serviceStatusString)); 705 } 706 707 // Parse ICCID 708 String iccIdString = 709 ts43XmlDoc.get( 710 ImmutableList.of( 711 Ts43XmlDoc.CharacteristicType.APPLICATION, 712 Ts43XmlDoc.CharacteristicType.PRIMARY_CONFIGURATION), 713 Ts43XmlDoc.Parm.ICCID); 714 715 if (!TextUtils.isEmpty(iccIdString)) { 716 configBuilder.setIccid(iccIdString); 717 } 718 719 // Parse polling interval 720 String pollingIntervalString = 721 ts43XmlDoc.get( 722 ImmutableList.of( 723 Ts43XmlDoc.CharacteristicType.APPLICATION, 724 Ts43XmlDoc.CharacteristicType.PRIMARY_CONFIGURATION), 725 Ts43XmlDoc.Parm.POLLING_INTERVAL); 726 727 if (!TextUtils.isEmpty(pollingIntervalString)) { 728 try { 729 configBuilder.setPollingInterval(Integer.parseInt(pollingIntervalString)); 730 } catch (NumberFormatException e) { 731 Log.w( 732 TAG, "acquireConfiguration: Failed to parse polling interval " 733 + pollingIntervalString); 734 } 735 } 736 737 // Parse download info 738 DownloadInfo downloadInfo = 739 parseDownloadInfo( 740 ImmutableList.of( 741 Ts43XmlDoc.CharacteristicType.APPLICATION, 742 Ts43XmlDoc.CharacteristicType.PRIMARY_CONFIGURATION, 743 Ts43XmlDoc.CharacteristicType.DOWNLOAD_INFO), 744 ts43XmlDoc); 745 if (downloadInfo != null) { 746 configBuilder.setDownloadInfo(downloadInfo); 747 } 748 749 // Parse message info 750 MessageInfo messageInfo = 751 parseMessageInfo( 752 ImmutableList.of( 753 Ts43XmlDoc.CharacteristicType.APPLICATION, 754 Ts43XmlDoc.CharacteristicType.PRIMARY_CONFIGURATION, 755 Ts43XmlDoc.CharacteristicType.MSG), 756 ts43XmlDoc); 757 if (messageInfo != null) { 758 configBuilder.setMessageInfo(messageInfo); 759 } 760 761 // TODO: Support different type of configuration. 762 configBuilder.setType( 763 AcquireConfigurationResponse.Configuration.CONFIGURATION_TYPE_PRIMARY); 764 765 // TODO: Support multiple configurations. 766 return responseBuilder.setConfigurations(ImmutableList.of(configBuilder.build())).build(); 767 } 768 769 /** 770 * Acquire available mobile plans to be offered by the MNO to a specific user or MDM as 771 * described in GSMA Service Entitlement Configuration section 6.2 and 6.5.6. 772 * 773 * @return List of mobile plans. Empty list if not available. 774 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 775 * error from the server, the error code can be retrieved by 776 * {@link ServiceEntitlementException#getHttpStatus()} 777 */ 778 @NonNull acquirePlans()779 public List<PlanOffer> acquirePlans() throws ServiceEntitlementException { 780 return Collections.emptyList(); 781 } 782 783 /** 784 * To request a temporary token used to establish trust between ECS and the client as described 785 * in GSMA Service Entitlement Configuration section 6.2 and 6.5.7. 786 * 787 * @param acquireTemporaryTokenRequest The acquire temporary token request. 788 * @return The temporary token response. 789 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 790 * error from the server, the error code can be retrieved by 791 * {@link ServiceEntitlementException#getHttpStatus()} 792 */ 793 @NonNull 794 @SuppressWarnings("AndroidJdkLibsChecker") // java.time.Instant acquireTemporaryToken( @onNull AcquireTemporaryTokenRequest acquireTemporaryTokenRequest)795 public AcquireTemporaryTokenResponse acquireTemporaryToken( 796 @NonNull AcquireTemporaryTokenRequest acquireTemporaryTokenRequest) 797 throws ServiceEntitlementException { 798 Objects.requireNonNull(acquireTemporaryTokenRequest); 799 800 ServiceEntitlementRequest request = 801 ServiceEntitlementRequest.builder() 802 .setEntitlementVersion(mEntitlementVersion) 803 .setTerminalId(mImei) 804 .setAuthenticationToken(mAuthToken) 805 .build(); 806 807 EsimOdsaOperation operation = 808 EsimOdsaOperation.builder() 809 .setOperation(EsimOdsaOperation.OPERATION_ACQUIRE_TEMPORARY_TOKEN) 810 .setOperationTargets(acquireTemporaryTokenRequest.operationTargets()) 811 .setCompanionTerminalId(acquireTemporaryTokenRequest.companionTerminalId()) 812 .build(); 813 814 String rawXml; 815 try { 816 rawXml = 817 mServiceEntitlement.performEsimOdsa( 818 acquireTemporaryTokenRequest.appId(), request, operation); 819 } catch (ServiceEntitlementException e) { 820 Log.w(TAG, "acquireTemporaryToken: Failed to perform ODSA operation. e=" + e); 821 throw e; 822 } 823 824 Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml); 825 AcquireTemporaryTokenResponse.Builder responseBuilder = 826 AcquireTemporaryTokenResponse.builder(); 827 828 try { 829 processGeneralResult(ts43XmlDoc, responseBuilder); 830 } catch (MalformedURLException e) { 831 throw new ServiceEntitlementException( 832 ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE, 833 "AcquireTemporaryTokenResponse: Malformed URL " + rawXml); 834 } 835 836 // Parse the operation targets. 837 String operationTargets = 838 Strings.nullToEmpty( 839 ts43XmlDoc.get( 840 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 841 Ts43XmlDoc.Parm.OPERATION_TARGETS)); 842 843 if (operationTargets != null) { 844 List<String> operationTargetsList = Arrays.asList(operationTargets.split("\\s*,\\s*")); 845 responseBuilder.setOperationTargets(ImmutableList.copyOf(operationTargetsList)); 846 } 847 848 // Parse the temporary token 849 String temporaryToken = 850 ts43XmlDoc.get( 851 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 852 Ts43XmlDoc.Parm.TEMPORARY_TOKEN); 853 854 if (temporaryToken == null) { 855 throw new ServiceEntitlementException( 856 ServiceEntitlementException.ERROR_TOKEN_NOT_AVAILABLE, 857 "temporary token is not available."); 858 } 859 860 responseBuilder.setTemporaryToken(temporaryToken); 861 862 String temporaryTokenExpiry = 863 ts43XmlDoc.get( 864 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 865 Ts43XmlDoc.Parm.TEMPORARY_TOKEN_EXPIRY); 866 867 // Parse the token expiration time. 868 Instant expiry; 869 try { 870 expiry = OffsetDateTime.parse(temporaryTokenExpiry).toInstant(); 871 responseBuilder.setTemporaryTokenExpiry(expiry); 872 } catch (DateTimeParseException e) { 873 Log.w(TAG, "Failed to parse temporaryTokenExpiry: " + temporaryTokenExpiry); 874 } 875 876 return responseBuilder.build(); 877 } 878 879 /** 880 * Get the phone number as described in GSMA Service Entitlement Configuration section 6.2 and 881 * 6.5.8. 882 * 883 * @param getPhoneNumberRequest The get phone number request. 884 * @return The phone number response from the network. 885 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 886 * error from the server, the error code can be retrieved by 887 * {@link ServiceEntitlementException#getHttpStatus()} 888 */ 889 @NonNull getPhoneNumber( @onNull GetPhoneNumberRequest getPhoneNumberRequest)890 public GetPhoneNumberResponse getPhoneNumber( 891 @NonNull GetPhoneNumberRequest getPhoneNumberRequest) 892 throws ServiceEntitlementException { 893 ServiceEntitlementRequest.Builder builder = 894 ServiceEntitlementRequest.builder() 895 .setEntitlementVersion(mEntitlementVersion); 896 897 if (!TextUtils.isEmpty(getPhoneNumberRequest.terminalId())) { 898 builder.setTerminalId(getPhoneNumberRequest.terminalId()); 899 } else { 900 builder.setTerminalId(mImei); 901 } 902 903 if (mTokenType == TOKEN_TYPE_NORMAL) { 904 builder.setAuthenticationToken(mAuthToken); 905 } else if (mTokenType == TOKEN_TYPE_TEMPORARY) { 906 builder.setTemporaryToken(mTemporaryToken); 907 } 908 909 ServiceEntitlementRequest request = builder.build(); 910 911 EsimOdsaOperation operation = 912 EsimOdsaOperation.builder() 913 .setOperation(EsimOdsaOperation.OPERATION_GET_PHONE_NUMBER) 914 .build(); 915 916 String rawXml; 917 try { 918 rawXml = 919 mServiceEntitlement.performEsimOdsa( 920 Ts43Constants.APP_PHONE_NUMBER_INFORMATION, request, operation); 921 } catch (ServiceEntitlementException e) { 922 Log.w(TAG, "getPhoneNumber: Failed to perform ODSA operation. e=" + e); 923 throw e; 924 } 925 926 // Build the response of get phone number operation. Refer to GSMA Service Entitlement 927 // Configuration section 6.5.8. 928 GetPhoneNumberResponse.Builder responseBuilder = GetPhoneNumberResponse.builder(); 929 930 Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml); 931 932 try { 933 processGeneralResult(ts43XmlDoc, responseBuilder); 934 } catch (MalformedURLException e) { 935 throw new ServiceEntitlementException( 936 ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE, 937 "getPhoneNumber: Malformed URL " + rawXml); 938 } 939 940 // Parse msisdn. 941 String msisdn = 942 ts43XmlDoc.get( 943 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 944 Ts43XmlDoc.Parm.MSISDN); 945 946 if (!TextUtils.isEmpty(msisdn)) { 947 responseBuilder.setMsisdn(msisdn); 948 } 949 950 return responseBuilder.build(); 951 } 952 953 /** 954 * Parse the download info from {@link ManageSubscriptionResponse}. 955 * 956 * @param characteristics The XML nodes to search activation code. 957 * @param ts43XmlDoc The XML format http response. 958 * @return The download info. 959 */ 960 @Nullable 961 @SuppressWarnings("AndroidJdkLibsChecker") // java.util.Base64 parseDownloadInfo( @onNull ImmutableList<String> characteristics, @NonNull Ts43XmlDoc ts43XmlDoc)962 private DownloadInfo parseDownloadInfo( 963 @NonNull ImmutableList<String> characteristics, @NonNull Ts43XmlDoc ts43XmlDoc) { 964 String activationCode = 965 Strings.nullToEmpty( 966 ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.PROFILE_ACTIVATION_CODE)); 967 String smdpAddress = 968 Strings.nullToEmpty( 969 ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.PROFILE_SMDP_ADDRESS)); 970 String iccid = 971 Strings.nullToEmpty(ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.PROFILE_ICCID)); 972 973 // DownloadInfo should contain either activationCode or smdpAddress + iccid 974 if (!activationCode.isEmpty()) { 975 // decode the activation code, which is in base64 format 976 try { 977 activationCode = new String(Base64.getDecoder().decode(activationCode)); 978 } catch (IllegalArgumentException e) { 979 Log.w(TAG, "Failed to decode the activation code " + activationCode); 980 return null; 981 } 982 return DownloadInfo.builder() 983 .setProfileActivationCode(activationCode) 984 .setProfileIccid(iccid) 985 .build(); 986 } else if (!smdpAddress.isEmpty() && !iccid.isEmpty()) { 987 return DownloadInfo.builder() 988 .setProfileIccid(iccid) 989 .setProfileSmdpAddresses( 990 ImmutableList.copyOf(Arrays.asList(smdpAddress.split("\\s*,\\s*")))) 991 .build(); 992 } else { 993 Log.w( 994 TAG, 995 "Failed to parse download info. activationCode=" 996 + activationCode 997 + ", smdpAddress=" 998 + smdpAddress 999 + ", iccid=" 1000 + iccid); 1001 return null; 1002 } 1003 } 1004 1005 /** 1006 * Parse the MSG info from {@link AcquireConfigurationResponse}. 1007 * 1008 * @param characteristics The XML nodes to search. 1009 * @param ts43XmlDoc The XML format http response. 1010 * @return The MSG info. 1011 */ 1012 @Nullable parseMessageInfo( @onNull ImmutableList<String> characteristics, @NonNull Ts43XmlDoc ts43XmlDoc)1013 private MessageInfo parseMessageInfo( 1014 @NonNull ImmutableList<String> characteristics, @NonNull Ts43XmlDoc ts43XmlDoc) { 1015 String message = 1016 Strings.nullToEmpty(ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.MESSAGE)); 1017 String acceptButton = 1018 Strings.nullToEmpty(ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.ACCEPT_BUTTON)); 1019 String acceptButtonLabel = 1020 Strings.nullToEmpty( 1021 ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.ACCEPT_BUTTON_LABEL)); 1022 String rejectButton = 1023 Strings.nullToEmpty(ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.REJECT_BUTTON)); 1024 String rejectButtonLabel = 1025 Strings.nullToEmpty( 1026 ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.REJECT_BUTTON_LABEL)); 1027 String acceptFreetext = 1028 Strings.nullToEmpty( 1029 ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.ACCEPT_FREETEXT)); 1030 1031 // MessageInfo should contain message, accept button, reject button, and accept freetext 1032 if (!message.isEmpty() && !acceptButton.isEmpty() && !rejectButton.isEmpty() 1033 && !acceptFreetext.isEmpty()) { 1034 return MessageInfo.builder() 1035 .setMessage(message) 1036 .setAcceptButton(acceptButton) 1037 .setAcceptButtonLabel(acceptButtonLabel) 1038 .setRejectButton(rejectButton) 1039 .setRejectButtonLabel(rejectButtonLabel) 1040 .setAcceptFreetext(acceptFreetext) 1041 .build(); 1042 } else { 1043 Log.w( 1044 TAG, 1045 "Failed to parse message info. message=" 1046 + message 1047 + ", acceptButton=" 1048 + acceptButton 1049 + ", acceptButtonLabel=" 1050 + acceptButtonLabel 1051 + ", rejectButton=" 1052 + rejectButton 1053 + ", rejectButtonLabel=" 1054 + rejectButtonLabel 1055 + ", acceptFreetext=" 1056 + acceptFreetext); 1057 return null; 1058 } 1059 } 1060 1061 /** 1062 * Process the common ODSA result from HTTP response. 1063 * 1064 * @param ts43XmlDoc The TS.43 ODSA operation response in XLM format. 1065 * @param builder The response builder. 1066 * @throws MalformedURLException when HTTP response is not well formatted. 1067 */ processGeneralResult( @onNull Ts43XmlDoc ts43XmlDoc, @NonNull OdsaResponse.Builder builder)1068 private void processGeneralResult( 1069 @NonNull Ts43XmlDoc ts43XmlDoc, @NonNull OdsaResponse.Builder builder) 1070 throws MalformedURLException { 1071 // Now start to parse the result from HTTP response. 1072 // Parse the operation result. 1073 String operationResult = 1074 ts43XmlDoc.get( 1075 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 1076 Ts43XmlDoc.Parm.OPERATION_RESULT); 1077 1078 builder.setOperationResult(EsimOdsaOperation.OPERATION_RESULT_UNKNOWN); 1079 if (!TextUtils.isEmpty(operationResult)) { 1080 switch (operationResult) { 1081 case Ts43XmlDoc.ParmValues.OPERATION_RESULT_SUCCESS: 1082 builder.setOperationResult(EsimOdsaOperation.OPERATION_RESULT_SUCCESS); 1083 break; 1084 case Ts43XmlDoc.ParmValues.OPERATION_RESULT_ERROR_GENERAL: 1085 builder.setOperationResult(EsimOdsaOperation.OPERATION_RESULT_ERROR_GENERAL); 1086 break; 1087 case Ts43XmlDoc.ParmValues.OPERATION_RESULT_ERROR_INVALID_OPERATION: 1088 builder.setOperationResult( 1089 EsimOdsaOperation.OPERATION_RESULT_ERROR_INVALID_OPERATION); 1090 break; 1091 case Ts43XmlDoc.ParmValues.OPERATION_RESULT_ERROR_INVALID_PARAMETER: 1092 builder.setOperationResult( 1093 EsimOdsaOperation.OPERATION_RESULT_ERROR_INVALID_PARAMETER); 1094 break; 1095 case Ts43XmlDoc.ParmValues.OPERATION_RESULT_WARNING_NOT_SUPPORTED_OPERATION: 1096 builder.setOperationResult( 1097 EsimOdsaOperation.OPERATION_RESULT_WARNING_NOT_SUPPORTED_OPERATION); 1098 break; 1099 case Ts43XmlDoc.ParmValues.OPERATION_RESULT_ERROR_INVALID_MSG_RESPONSE: 1100 builder.setOperationResult( 1101 EsimOdsaOperation.OPERATION_RESULT_ERROR_INVALID_MSG_RESPONSE); 1102 break; 1103 } 1104 } 1105 1106 // Parse the general error URL 1107 String generalErrorUrl = 1108 ts43XmlDoc.get( 1109 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 1110 Ts43XmlDoc.Parm.GENERAL_ERROR_URL); 1111 if (!TextUtils.isEmpty(generalErrorUrl)) { 1112 builder.setGeneralErrorUrl(new URL(generalErrorUrl)); 1113 } 1114 1115 // Parse the general error URL user data 1116 String generalErrorUserData = 1117 ts43XmlDoc.get( 1118 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 1119 Ts43XmlDoc.Parm.GENERAL_ERROR_USER_DATA); 1120 if (!TextUtils.isEmpty(generalErrorUserData)) { 1121 builder.setGeneralErrorUserData(generalErrorUserData); 1122 } 1123 1124 // Parse the general error text 1125 String generalErrorText = 1126 ts43XmlDoc.get( 1127 ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION), 1128 Ts43XmlDoc.Parm.GENERAL_ERROR_TEXT); 1129 if (!TextUtils.isEmpty(generalErrorText)) { 1130 builder.setGeneralErrorText(generalErrorText); 1131 } 1132 1133 // Parse the token for next operation. 1134 String token = 1135 ts43XmlDoc.get( 1136 ImmutableList.of(Ts43XmlDoc.CharacteristicType.TOKEN), 1137 Ts43XmlDoc.Parm.TOKEN); 1138 if (!TextUtils.isEmpty(token)) { 1139 // Some servers issue the new token in operation result for next operation to use. 1140 // We need to save it. 1141 mAuthToken = token; 1142 Log.d(TAG, "processGeneralResult: Token replaced."); 1143 } 1144 } 1145 1146 /** 1147 * Get the service status from string as described in GSMA Service Entitlement Configuration 1148 * section 6.5.4. 1149 * 1150 * @param serviceStatusString Service status in string format defined in GSMA Service 1151 * Entitlement Configuration section 6.5.4. 1152 * @return The converted service status. {@link EsimOdsaOperation#SERVICE_STATUS_UNKNOWN} if not 1153 * able to convert. 1154 */ 1155 @OdsaServiceStatus getServiceStatusFromString(@onNull String serviceStatusString)1156 private int getServiceStatusFromString(@NonNull String serviceStatusString) { 1157 switch (serviceStatusString) { 1158 case Ts43XmlDoc.ParmValues.SERVICE_STATUS_ACTIVATED: 1159 return EsimOdsaOperation.SERVICE_STATUS_ACTIVATED; 1160 case Ts43XmlDoc.ParmValues.SERVICE_STATUS_ACTIVATING: 1161 return EsimOdsaOperation.SERVICE_STATUS_ACTIVATING; 1162 case Ts43XmlDoc.ParmValues.SERVICE_STATUS_DEACTIVATED: 1163 return EsimOdsaOperation.SERVICE_STATUS_DEACTIVATED; 1164 case Ts43XmlDoc.ParmValues.SERVICE_STATUS_DEACTIVATED_NO_REUSE: 1165 return EsimOdsaOperation.SERVICE_STATUS_DEACTIVATED_NO_REUSE; 1166 } 1167 return EsimOdsaOperation.SERVICE_STATUS_UNKNOWN; 1168 } 1169 }