aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-05-10 15:44:57 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-05-10 15:44:57 +0000
commit70f20a4192a48accf1b3c210a932c06466cda80d (patch)
tree96805df0d6e5ebd69702d50d5c50b7b6f8456353
parent8e355def9a181678491c8e8cdb7e007cd7001d33 (diff)
parent7ec98147c9ecc867ab512ef61da4a6c0d3f0c084 (diff)
downloadservice_entitlement-busytown-mac-infra-release.tar.gz
Snap for 11819167 from 7ec98147c9ecc867ab512ef61da4a6c0d3f0c084 to busytown-mac-infra-releasebusytown-mac-infra-release
Change-Id: I64b2bfe518668c5a5f86928b4d44e9aa57f4c3d6
-rw-r--r--Android.bp6
-rw-r--r--java/com/android/libraries/entitlement/CarrierConfig.java22
-rw-r--r--java/com/android/libraries/entitlement/EapAkaHelper.java2
-rw-r--r--java/com/android/libraries/entitlement/EsimOdsaOperation.java493
-rw-r--r--java/com/android/libraries/entitlement/ServiceEntitlement.java165
-rw-r--r--java/com/android/libraries/entitlement/ServiceEntitlementException.java61
-rw-r--r--java/com/android/libraries/entitlement/ServiceEntitlementRequest.java51
-rw-r--r--java/com/android/libraries/entitlement/Ts43Authentication.java294
-rw-r--r--java/com/android/libraries/entitlement/Ts43Operation.java1076
-rw-r--r--java/com/android/libraries/entitlement/eapaka/EapAkaApi.java818
-rw-r--r--java/com/android/libraries/entitlement/http/HttpClient.java9
-rw-r--r--java/com/android/libraries/entitlement/http/HttpConstants.java9
-rw-r--r--java/com/android/libraries/entitlement/http/HttpRequest.java14
-rw-r--r--java/com/android/libraries/entitlement/http/HttpResponse.java17
-rw-r--r--java/com/android/libraries/entitlement/odsa/AcquireConfigurationOperation.java513
-rw-r--r--java/com/android/libraries/entitlement/odsa/AcquireTemporaryTokenOperation.java200
-rw-r--r--java/com/android/libraries/entitlement/odsa/CheckEligibilityOperation.java367
-rw-r--r--java/com/android/libraries/entitlement/odsa/CompanionDeviceInfo.java100
-rw-r--r--java/com/android/libraries/entitlement/odsa/DownloadInfo.java94
-rw-r--r--java/com/android/libraries/entitlement/odsa/GetPhoneNumberOperation.java102
-rw-r--r--java/com/android/libraries/entitlement/odsa/ManageServiceOperation.java270
-rw-r--r--java/com/android/libraries/entitlement/odsa/ManageSubscriptionOperation.java732
-rw-r--r--java/com/android/libraries/entitlement/odsa/OdsaResponse.java84
-rw-r--r--java/com/android/libraries/entitlement/odsa/PlanOffer.java78
-rw-r--r--java/com/android/libraries/entitlement/utils/DebugUtils.java24
-rw-r--r--java/com/android/libraries/entitlement/utils/HttpConstants.java40
-rw-r--r--java/com/android/libraries/entitlement/utils/Ts43Constants.java166
-rw-r--r--java/com/android/libraries/entitlement/utils/Ts43XmlDoc.java230
-rw-r--r--tests/Android.bp2
-rw-r--r--tests/src/com/android/libraries/entitlement/ServiceEntitlementTest.java116
-rw-r--r--tests/src/com/android/libraries/entitlement/Ts43AuthenticationTest.java201
-rw-r--r--tests/src/com/android/libraries/entitlement/Ts43OperationTest.java390
-rw-r--r--tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java892
33 files changed, 7178 insertions, 460 deletions
diff --git a/Android.bp b/Android.bp
index dbedea3..333f0b3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -22,6 +22,7 @@ java_defaults {
libs: [
"androidx.annotation_annotation",
"auto_value_annotations",
+ "error_prone_annotations",
],
plugins: ["auto_value_plugin"],
sdk_version: "system_current",
@@ -37,7 +38,6 @@ java_library {
srcs: [
"java/com/android/libraries/entitlement/eapaka/*.java",
"java/com/android/libraries/entitlement/http/*.java",
- "java/com/android/libraries/entitlement/utils/*.java",
],
static_libs: [
"guava",
@@ -71,6 +71,8 @@ java_library {
srcs: [
"java/com/android/libraries/entitlement/ServiceEntitlement.java",
"java/com/android/libraries/entitlement/EapAkaHelper.java",
+ "java/com/android/libraries/entitlement/Ts43Authentication.java",
+ "java/com/android/libraries/entitlement/Ts43Operation.java",
],
static_libs: [
"guava",
@@ -95,6 +97,8 @@ java_library {
"java/com/android/libraries/entitlement/EsimOdsaOperation.java",
"java/com/android/libraries/entitlement/ServiceEntitlementException.java",
"java/com/android/libraries/entitlement/ServiceEntitlementRequest.java",
+ "java/com/android/libraries/entitlement/odsa/*.java",
+ "java/com/android/libraries/entitlement/utils/*.java",
],
static_libs: [
"guava",
diff --git a/java/com/android/libraries/entitlement/CarrierConfig.java b/java/com/android/libraries/entitlement/CarrierConfig.java
index 44a4170..d0a63f8 100644
--- a/java/com/android/libraries/entitlement/CarrierConfig.java
+++ b/java/com/android/libraries/entitlement/CarrierConfig.java
@@ -32,9 +32,23 @@ public abstract class CarrierConfig {
/** Default value of {@link #timeoutInSec} if not set. */
public static final int DEFAULT_TIMEOUT_IN_SEC = 30;
+ public static final String CLIENT_TS_43_IMS_ENTITLEMENT = "IMS-Entitlement";
+ public static final String CLIENT_TS_43_COMPANION_ODSA = "Companion-ODSA";
+ public static final String CLIENT_TS_43_PRIMARY_ODSA = "Primary-ODSA";
+ public static final String CLIENT_TS_43_SERVER_ODSA = "Server-ODSA";
+
/** The carrier's entitlement server URL. See {@link Builder#setServerUrl}. */
public abstract String serverUrl();
+ /**
+ * Client-ts43 attribute. Used to set the User-Agent header in HTTP requests as defined in TS.43
+ * section 2.2.
+ */
+ public abstract String clientTs43();
+
+ /** Returns {@code true} if HTTP POST, instead of GET, should be used for TS.43 requests. */
+ public abstract boolean useHttpPost();
+
/** Client side timeout for HTTP connection. See {@link Builder#setTimeoutInSec}. */
public abstract int timeoutInSec();
@@ -46,6 +60,8 @@ public abstract class CarrierConfig {
public static Builder builder() {
return new AutoValue_CarrierConfig.Builder()
.setServerUrl("")
+ .setClientTs43("")
+ .setUseHttpPost(false)
.setTimeoutInSec(DEFAULT_TIMEOUT_IN_SEC);
}
@@ -60,6 +76,12 @@ public abstract class CarrierConfig {
*/
public abstract Builder setServerUrl(String url);
+ /** Sets the Client-ts43 attribute. Used to set the User-Agent header in HTTP requests. */
+ public abstract Builder setClientTs43(String clientTs43);
+
+ /** Set to {@code true} to use HTTP POST instead of GET for TS.43 requests. */
+ public abstract Builder setUseHttpPost(boolean useHttpPost);
+
/**
* Sets the client side timeout for HTTP connection. Default to
* {@link DEFAULT_TIMEOUT_IN_SEC}.
diff --git a/java/com/android/libraries/entitlement/EapAkaHelper.java b/java/com/android/libraries/entitlement/EapAkaHelper.java
index e5af73e..f29cb0f 100644
--- a/java/com/android/libraries/entitlement/EapAkaHelper.java
+++ b/java/com/android/libraries/entitlement/EapAkaHelper.java
@@ -87,7 +87,7 @@ public class EapAkaHelper {
EapAkaResponse eapAkaResponse = getEapAkaResponse(challenge);
return (eapAkaResponse == null)
? null
- : eapAkaResponse.response(); // Would be null on synchrinization failure
+ : eapAkaResponse.response(); // Would be null on synchronization failure
}
/**
diff --git a/java/com/android/libraries/entitlement/EsimOdsaOperation.java b/java/com/android/libraries/entitlement/EsimOdsaOperation.java
index 9a3eae6..e7b28f6 100644
--- a/java/com/android/libraries/entitlement/EsimOdsaOperation.java
+++ b/java/com/android/libraries/entitlement/EsimOdsaOperation.java
@@ -16,189 +16,312 @@
package com.android.libraries.entitlement;
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringDef;
+
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
- * HTTP request parameters specific to on device service actiavation (ODSA). See GSMA spec TS.43
+ * HTTP request parameters specific to on device service activation (ODSA). See GSMA spec TS.43
* section 6.2.
*/
@AutoValue
public abstract class EsimOdsaOperation {
- /**
- * OSDA operation: CheckEligibility.
- */
+ /** ODSA operation unknown. For initialization only. */
+ public static final String OPERATION_UNKNOWN = "";
+
+ /** ODSA operation: CheckEligibility. */
public static final String OPERATION_CHECK_ELIGIBILITY = "CheckEligibility";
- /**
- * OSDA operation: ManageSubscription.
- */
+
+ /** ODSA operation: ManageSubscription. */
public static final String OPERATION_MANAGE_SUBSCRIPTION = "ManageSubscription";
- /**
- * OSDA operation: ManageService.
- */
+
+ /** ODSA operation: ManageService. */
public static final String OPERATION_MANAGE_SERVICE = "ManageService";
- /**
- * OSDA operation: AcquireConfiguration.
- */
+
+ /** ODSA operation: AcquireConfiguration. */
public static final String OPERATION_ACQUIRE_CONFIGURATION = "AcquireConfiguration";
- /**
- * OSDA operation: AcquireTemporaryToken.
- */
+
+ /** ODSA operation: AcquireTemporaryToken. */
public static final String OPERATION_ACQUIRE_TEMPORARY_TOKEN = "AcquireTemporaryToken";
- /**
- * Indicates that operation_type is not set.
- */
+ /** ODSA operation: GetPhoneNumber */
+ public static final String OPERATION_GET_PHONE_NUMBER = "GetPhoneNumber";
+
+ /** ODSA operation: AcquirePlan */
+ public static final String OPERATION_ACQUIRE_PLAN = "AcquirePlan";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ OPERATION_UNKNOWN,
+ OPERATION_CHECK_ELIGIBILITY,
+ OPERATION_MANAGE_SUBSCRIPTION,
+ OPERATION_MANAGE_SERVICE,
+ OPERATION_ACQUIRE_CONFIGURATION,
+ OPERATION_ACQUIRE_PLAN,
+ OPERATION_ACQUIRE_TEMPORARY_TOKEN,
+ OPERATION_GET_PHONE_NUMBER
+ })
+ public @interface OdsaOperation {
+ }
+
+ /** eSIM device’s service is unknown. */
+ public static final int SERVICE_STATUS_UNKNOWN = -1;
+
+ /** eSIM device’s service is activated. */
+ public static final int SERVICE_STATUS_ACTIVATED = 1;
+
+ /** eSIM device’s service is being activated. */
+ public static final int SERVICE_STATUS_ACTIVATING = 2;
+
+ /** eSIM device’s service is not activated. */
+ public static final int SERVICE_STATUS_DEACTIVATED = 3;
+
+ /** eSIM device’s service is not activated and the associated ICCID should not be reused. */
+ public static final int SERVICE_STATUS_DEACTIVATED_NO_REUSE = 4;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ SERVICE_STATUS_UNKNOWN,
+ SERVICE_STATUS_ACTIVATED,
+ SERVICE_STATUS_ACTIVATING,
+ SERVICE_STATUS_DEACTIVATED,
+ SERVICE_STATUS_DEACTIVATED_NO_REUSE
+ })
+ public @interface OdsaServiceStatus {
+ }
+
+ /** Indicates that operation_type is not set. */
public static final int OPERATION_TYPE_NOT_SET = -1;
- /**
- * To activate a subscription, used by {@link #OPERATION_MANAGE_SUBSCRIPTION}.
- */
+
+ /** To activate a subscription, used by {@link #OPERATION_MANAGE_SUBSCRIPTION}. */
public static final int OPERATION_TYPE_SUBSCRIBE = 0;
- /**
- * To cancel a subscription, used by {@link #OPERATION_MANAGE_SUBSCRIPTION}.
- */
+
+ /** To cancel a subscription, used by {@link #OPERATION_MANAGE_SUBSCRIPTION}. */
public static final int OPERATION_TYPE_UNSUBSCRIBE = 1;
- /**
- * To manage an existing subscription, for {@link #OPERATION_MANAGE_SUBSCRIPTION}.
- */
+
+ /** To manage an existing subscription, for {@link #OPERATION_MANAGE_SUBSCRIPTION}. */
public static final int OPERATION_TYPE_CHANGE_SUBSCRIPTION = 2;
+
/**
* To transfer a subscription from an existing device, used by {@link
* #OPERATION_MANAGE_SUBSCRIPTION}.
*/
public static final int OPERATION_TYPE_TRANSFER_SUBSCRIPTION = 3;
+
/**
* To inform the network of a subscription update, used by
* {@link #OPERATION_MANAGE_SUBSCRIPTION}.
*/
public static final int OPERATION_TYPE_UPDATE_SUBSCRIPTION = 4;
- /**
- * To activate a service, used by {@link #OPERATION_MANAGE_SERVICE}.
- */
+
+ /** To activate a service, used by {@link #OPERATION_MANAGE_SERVICE}. */
public static final int OPERATION_TYPE_ACTIVATE_SERVICE = 10;
- /**
- * To deactivate a service, used by {@link #OPERATION_MANAGE_SERVICE}.
- */
+
+ /** To deactivate a service, used by {@link #OPERATION_MANAGE_SERVICE}. */
public static final int OPERATION_TYPE_DEACTIVATE_SERVICE = 11;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ OPERATION_TYPE_NOT_SET,
+ OPERATION_TYPE_SUBSCRIBE,
+ OPERATION_TYPE_UNSUBSCRIBE,
+ OPERATION_TYPE_CHANGE_SUBSCRIPTION,
+ OPERATION_TYPE_TRANSFER_SUBSCRIPTION,
+ OPERATION_TYPE_UPDATE_SUBSCRIPTION,
+ OPERATION_TYPE_ACTIVATE_SERVICE,
+ OPERATION_TYPE_DEACTIVATE_SERVICE
+ })
+ public @interface OdsaOperationType {
+ }
+
+ /** Operation result unknown. */
+ public static final int OPERATION_RESULT_UNKNOWN = -1;
+
+ /** Operation was a success. */
+ public static final int OPERATION_RESULT_SUCCESS = 1;
+
+ /** There was a general error during processing. */
+ public static final int OPERATION_RESULT_ERROR_GENERAL = 100;
+
+ /** An invalid operation value was provided in request. */
+ public static final int OPERATION_RESULT_ERROR_INVALID_OPERATION = 101;
+
+ /** An invalid parameter name or value was provided in request. */
+ public static final int OPERATION_RESULT_ERROR_INVALID_PARAMETER = 102;
+
/**
- * Indicates the companion device carries the same MSISDN as the primary device.
- */
- public static final String COMPANION_SERVICE_SHAERED_NUMBER = "SharedNumber";
- /**
- * Indicates the companion device carries a different MSISDN as the primary device.
+ * The optional operation is not supported by the carrier. Device should continue with the flow.
+ * This error only applies to optional operations (for example ManageService).
*/
+ public static final int OPERATION_RESULT_WARNING_NOT_SUPPORTED_OPERATION = 103;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ OPERATION_RESULT_UNKNOWN,
+ OPERATION_RESULT_SUCCESS,
+ OPERATION_RESULT_ERROR_GENERAL,
+ OPERATION_RESULT_ERROR_INVALID_OPERATION,
+ OPERATION_RESULT_ERROR_INVALID_PARAMETER,
+ OPERATION_RESULT_WARNING_NOT_SUPPORTED_OPERATION
+ })
+ public @interface OdsaOperationResult {
+ }
+
+ /** Companion service unknown. For initialization only. */
+ public static final String COMPANION_SERVICE_UNKNOWN = "";
+
+ /** Indicates the companion device carries the same MSISDN as the primary device. */
+ public static final String COMPANION_SERVICE_SHARED_NUMBER = "SharedNumber";
+
+ /** Indicates the companion device carries a different MSISDN as the primary device. */
public static final String COMPANION_SERVICE_DIFFERENT_NUMBER = "DiffNumber";
- /**
- * Returns the eSIM ODSA operation. Used by HTTP parameter "operation".
- */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ COMPANION_SERVICE_UNKNOWN,
+ COMPANION_SERVICE_SHARED_NUMBER,
+ COMPANION_SERVICE_DIFFERENT_NUMBER
+ })
+ public @interface CompanionService {
+ }
+
+ /** Returns the ODSA operation. Used by HTTP parameter {@code operation}. */
public abstract String operation();
/**
- * Returns the detiled type of the eSIM ODSA operation. Used by HTTP parameter
- * "operation_type".
+ * Returns the detailed type of the ODSA operation. Used by HTTP parameter
+ * {@code operation_type}.
*/
public abstract int operationType();
/**
* Returns the comma separated list of operation targets used with temporary token from
- * AcquireTemporaryToken operation. Used by HTTP parameter "operation_targets".
+ * AcquireTemporaryToken operation. Used by HTTP parameter {@code operation_targets}.
*/
public abstract ImmutableList<String> operationTargets();
/**
* Returns the unique identifier of the companion device, like IMEI. Used by HTTP parameter
- * "companion_terminal_id".
+ * {@code
+ * companion_terminal_id}.
*/
public abstract String companionTerminalId();
/**
- * Returns the OEM of the companion device. Used by HTTP parameter "companion_terminal_vendor".
+ * Returns the OEM of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_vendor}.
*/
public abstract String companionTerminalVendor();
/**
- * Returns the model of the companion device. Used by HTTP parameter
- * "companion_terminal_model".
+ * Returns the model of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_model}.
*/
public abstract String companionTerminalModel();
/**
- * Returns the software version of the companion device. Used by HTTP parameter
- * "companion_terminal_sw_version".
+ * Returns the software version of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_sw_version}.
*/
public abstract String companionTerminalSoftwareVersion();
/**
- * Returns the user-friendly version of the companion device. Used by HTTP parameter
- * "companion_terminal_friendly_name".
+ * Returns the user-friendly version of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_friendly_name}.
*/
public abstract String companionTerminalFriendlyName();
/**
* Returns the service type of the companion device, e.g. if the MSISDN is same as the primary
- * device. Used by HTTP parameter "companion_terminal_service".
+ * device. Used by HTTP parameter {@code companion_terminal_service}.
*/
public abstract String companionTerminalService();
/**
- * Returns the ICCID of the companion device. Used by HTTP parameter
- * "companion_terminal_iccid".
+ * Returns the ICCID of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_iccid}.
*/
public abstract String companionTerminalIccid();
/**
- * Returns the EID of the companion device. Used by HTTP parameter "companion_terminal_eid".
+ * Returns the EID of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_eid}.
*/
public abstract String companionTerminalEid();
/**
- * Returns the ICCID of the primary device eSIM. Used by HTTP parameter "terminal_iccid".
+ * Returns the ICCID of the primary device eSIM. Used by HTTP parameter {@code terminal_iccid}.
*/
public abstract String terminalIccid();
/**
- * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
- * "terminal_eid".
+ * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter {@code
+ * terminal_eid}.
*/
public abstract String terminalEid();
/**
* Returns the unique identifier of the primary device eSIM, like the IMEI associated with the
- * eSIM. Used by HTTP parameter "target_terminal_id".
+ * eSIM. Used by HTTP parameter {@code target_terminal_id}.
*/
public abstract String targetTerminalId();
/**
- * Returns the ICCID primary device eSIM. Used by HTTP parameter "target_terminal_iccid".
+ * Returns the unique identifiers of the primary device eSIM if more than one, like the IMEIs on
+ * dual-SIM devices. Used by HTTP parameter {@code target_terminal_imeis}.
+ *
+ * <p>This is a non-standard params required by some carriers.
+ */
+ @NonNull
+ public abstract ImmutableList<String> targetTerminalIds();
+
+ /**
+ * Returns the ICCID primary device eSIM. Used by HTTP parameter {@code target_terminal_iccid}.
*/
public abstract String targetTerminalIccid();
/**
- * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
- * "target_terminal_eid".
+ * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter {@code
+ * target_terminal_eid}.
*/
public abstract String targetTerminalEid();
-
/**
- * Returns the unique identifier of the old device eSIM, like the IMEI associated with the
- * eSIM. Used by HTTP parameter "old_terminal_id".
+ * Returns the serial number of primary device. Used by HTTP parameter
+ * {@code target_terminal_sn}.
+ *
+ * <p>This is a non-standard params required by some carriers.
*/
- public abstract String oldTerminalId();
+ @NonNull
+ public abstract String targetTerminalSerialNumber();
/**
- * Returns the ICCID of old device eSIM. Used by HTTP parameter "old_terminal_iccid".
+ * Returns the model of primary device. Used by HTTP parameter {@code target_terminal_model}.
+ *
+ * <p>This is a non-standard params required by some carriers.
*/
- public abstract String oldTerminalIccid();
+ @NonNull
+ public abstract String targetTerminalModel();
/**
- * Returns a new {@link Builder} object.
+ * Returns the unique identifier of the old device eSIM, like the IMEI associated with the eSIM.
+ * Used by HTTP parameter {@code old_terminal_id}.
*/
+ public abstract String oldTerminalId();
+
+ /** Returns the ICCID of old device eSIM. Used by HTTP parameter {@code old_terminal_iccid}. */
+ public abstract String oldTerminalIccid();
+
+ /** Returns a new {@link Builder} object. */
public static Builder builder() {
return new AutoValue_EsimOdsaOperation.Builder()
- .setOperation("")
+ .setOperation(OPERATION_UNKNOWN)
.setOperationType(OPERATION_TYPE_NOT_SET)
.setOperationTargets(ImmutableList.of())
.setCompanionTerminalId("")
@@ -206,14 +329,17 @@ public abstract class EsimOdsaOperation {
.setCompanionTerminalModel("")
.setCompanionTerminalSoftwareVersion("")
.setCompanionTerminalFriendlyName("")
- .setCompanionTerminalService("")
+ .setCompanionTerminalService(COMPANION_SERVICE_UNKNOWN)
.setCompanionTerminalIccid("")
.setCompanionTerminalEid("")
.setTerminalIccid("")
.setTerminalEid("")
.setTargetTerminalId("")
+ .setTargetTerminalIds(ImmutableList.of())
.setTargetTerminalIccid("")
.setTargetTerminalEid("")
+ .setTargetTerminalSerialNumber("")
+ .setTargetTerminalModel("")
.setOldTerminalId("")
.setOldTerminalIccid("");
}
@@ -222,26 +348,33 @@ public abstract class EsimOdsaOperation {
* Builder.
*
* <p>For ODSA, the rule of which parameters are required varies or each
- * operation/opeation_type. The Javadoc below gives high-level description, but please refer to
- * GMSA spec TS.43 section 6.2 for details.
+ * operation/operation_type.
+ * The Javadoc below gives high-level description, but please refer to GSMA spec TS.43 section
+ * 6.2
+ * for details.
*/
@AutoValue.Builder
public abstract static class Builder {
/**
- * Sets the eSIM ODSA operation. Used by HTTP parameter "operation".
- *
- * <p>Required.
+ * Sets the eSIM ODSA operation. Used by HTTP parameter {@code operation}.
*
+ * @param operation ODSA operation.
+ * @return The builder.
* @see #OPERATION_CHECK_ELIGIBILITY
* @see #OPERATION_MANAGE_SUBSCRIPTION
* @see #OPERATION_MANAGE_SERVICE
* @see #OPERATION_ACQUIRE_CONFIGURATION
+ * @see #OPERATION_ACQUIRE_TEMPORARY_TOKEN
+ * @see #OPERATION_GET_PHONE_NUMBER
+ * @see #OPERATION_ACQUIRE_PLAN
*/
- public abstract Builder setOperation(String value);
+ @NonNull
+ public abstract Builder setOperation(@NonNull @OdsaOperation String operation);
/**
- * Sets the detiled type of the eSIM ODSA operation. Used by HTTP parameter "operation_type"
- * if set.
+ * Sets the detailed type of the eSIM ODSA operation. Used by HTTP parameter
+ * "operation_type" if
+ * set.
*
* <p>Required by some operation.
*
@@ -253,136 +386,252 @@ public abstract class EsimOdsaOperation {
* @see #OPERATION_TYPE_ACTIVATE_SERVICE
* @see #OPERATION_TYPE_DEACTIVATE_SERVICE
*/
- public abstract Builder setOperationType(int value);
+ @NonNull
+ public abstract Builder setOperationType(@OdsaOperationType int operationType);
/**
* Sets the operation targets to be used with temporary token from AcquireTemporaryToken
- * operation. Used by HTTP parameter "operation_targets" if set.
+ * operation. Used by HTTP parameter {@code operation_targets} if set.
*/
- public abstract Builder setOperationTargets(ImmutableList<String> value);
+ @NonNull
+ public abstract Builder setOperationTargets(
+ @NonNull @OdsaOperation ImmutableList<String> operationTargets);
/**
* Sets the unique identifier of the companion device, like IMEI. Used by HTTP parameter
- * "companion_terminal_id" if set.
+ * {@code
+ * companion_terminal_id} if set.
*
* <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalId The unique identifier of the companion device.
+ * @return The builder.
*/
- public abstract Builder setCompanionTerminalId(String value);
+ @NonNull
+ public abstract Builder setCompanionTerminalId(@NonNull String companionTerminalId);
/**
- * Sets the OEM of the companion device. Used by HTTP parameter "companion_terminal_vendor"
- * if set.
+ * Sets the OEM of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_vendor} if set.
*
* <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalVendor The OEM of the companion device.
+ * @return The builder.
*/
- public abstract Builder setCompanionTerminalVendor(String value);
+ @NonNull
+ public abstract Builder setCompanionTerminalVendor(@NonNull String companionTerminalVendor);
/**
- * Sets the model of the companion device. Used by HTTP parameter "companion_terminal_model"
- * if set.
+ * Sets the model of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_model} if set.
*
* <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalModel The model of the companion device.
+ * @return The builder.
*/
- public abstract Builder setCompanionTerminalModel(String value);
+ @NonNull
+ public abstract Builder setCompanionTerminalModel(@NonNull String companionTerminalModel);
/**
- * Sets the software version of the companion device. Used by HTTP parameter
- * "companion_terminal_sw_version" if set.
+ * Sets the software version of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_sw_version} if set.
*
* <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalSoftwareVersion The software version of the companion device.
+ * @return The builder.
*/
- public abstract Builder setCompanionTerminalSoftwareVersion(String value);
+ @NonNull
+ public abstract Builder setCompanionTerminalSoftwareVersion(
+ @NonNull String companionTerminalSoftwareVersion);
/**
- * Sets the user-friendly version of the companion device. Used by HTTP parameter
- * "companion_terminal_friendly_name" if set.
+ * Sets the user-friendly version of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_friendly_name} if set.
*
* <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalFriendlyName The user-friendly version of the companion device.
+ * @return The builder.
*/
- public abstract Builder setCompanionTerminalFriendlyName(String value);
+ @NonNull
+ public abstract Builder setCompanionTerminalFriendlyName(
+ @NonNull String companionTerminalFriendlyName);
/**
* Sets the service type of the companion device, e.g. if the MSISDN is same as the primary
- * device. Used by HTTP parameter "companion_terminal_service" if set.
+ * device. Used by HTTP parameter {@code companion_terminal_service} if set.
*
* <p>Used by companion device ODSA operation.
*
- * @see #COMPANION_SERVICE_SHAERED_NUMBER
+ * @param companionTerminalService The service type of the companion device.
+ * @return The builder.
+ * @see #COMPANION_SERVICE_SHARED_NUMBER
* @see #COMPANION_SERVICE_DIFFERENT_NUMBER
*/
- public abstract Builder setCompanionTerminalService(String value);
+ @NonNull
+ public abstract Builder setCompanionTerminalService(
+ @NonNull @CompanionService String companionTerminalService);
/**
- * Sets the ICCID of the companion device. Used by HTTP parameter "companion_terminal_iccid"
- * if set.
+ * Sets the ICCID of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_iccid} if set.
*
* <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalIccid The ICCID of the companion device.
+ * @return The builder.
*/
- public abstract Builder setCompanionTerminalIccid(String value);
+ @NonNull
+ public abstract Builder setCompanionTerminalIccid(@NonNull String companionTerminalIccid);
/**
- * Sets the eUICC identifier (EID) of the companion device. Used by HTTP parameter
- * "companion_terminal_eid" if set.
+ * Sets the eUICC identifier (EID) of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_eid} if set.
*
* <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalEid The eUICC identifier (EID) of the companion device.
+ * @return The builder.
*/
- public abstract Builder setCompanionTerminalEid(String value);
+ @NonNull
+ public abstract Builder setCompanionTerminalEid(@NonNull String companionTerminalEid);
/**
* Sets the ICCID of the primary device eSIM in case of primary SIM not present. Used by
- * HTTP parameter "terminal_eid" if set.
+ * HTTP
+ * parameter {@code terminal_eid} if set.
*
* <p>Used by primary device ODSA operation.
+ *
+ * @param terminalIccid The ICCID of the primary device eSIM in case of primary SIM not
+ * present.
+ * @return The builder.
*/
- public abstract Builder setTerminalIccid(String value);
+ @NonNull
+ public abstract Builder setTerminalIccid(@NonNull String terminalIccid);
/**
* Sets the eUICC identifier (EID) of the primary device eSIM in case of primary SIM not
- * present. Used by HTTP parameter "terminal_eid" if set.
+ * present. Used by HTTP parameter {@code terminal_eid} if set.
*
* <p>Used by primary device ODSA operation.
+ *
+ * @param terminalEid The eUICC identifier (EID) of the primary device eSIM in case of
+ * primary
+ * SIM not present.
+ * @return The builder.
*/
- public abstract Builder setTerminalEid(String value);
+ @NonNull
+ public abstract Builder setTerminalEid(@NonNull String terminalEid);
/**
* Sets the unique identifier of the primary device eSIM in case of multiple SIM, like the
- * IMEI associated with the eSIM. Used by HTTP parameter "target_terminal_id" if set.
+ * IMEI
+ * associated with the eSIM. Used by HTTP parameter {@code target_terminal_id} if set.
*
* <p>Used by primary device ODSA operation.
+ *
+ * @param targetTerminalId The unique identifier of the primary device eSIM in case of
+ * multiple
+ * SIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalId(@NonNull String targetTerminalId);
+
+ /**
+ * Sets the unique identifiers of the primary device eSIM if more than one, like the IMEIs
+ * on
+ * dual-SIM devices. Used by HTTP parameter {@code target_terminal_imeis}.
+ *
+ * <p>This is a non-standard params required by some carriers.
+ *
+ * @param targetTerminalIds The unique identifiers of the primary device eSIM if more than
+ * one.
+ * @return The builder.
*/
- public abstract Builder setTargetTerminalId(String value);
+ public abstract Builder setTargetTerminalIds(
+ @NonNull ImmutableList<String> targetTerminalIds);
/**
- * Sets the ICCID primary device eSIM in case of multiple SIM. Used by HTTP parameter
- * "target_terminal_iccid" if set.
+ * Sets the ICCID primary device eSIM in case of multiple SIM. Used by HTTP parameter {@code
+ * target_terminal_iccid} if set.
*
* <p>Used by primary device ODSA operation.
+ *
+ * @param targetTerminalIccid The ICCID primary device eSIM in case of multiple SIM.
+ * @return The builder.
*/
- public abstract Builder setTargetTerminalIccid(String value);
+ @NonNull
+ public abstract Builder setTargetTerminalIccid(@NonNull String targetTerminalIccid);
/**
* Sets the eUICC identifier (EID) of the primary device eSIM in case of multiple SIM. Used
- * by HTTP parameter "target_terminal_eid" if set.
+ * by
+ * HTTP parameter {@code target_terminal_eid} if set.
*
* <p>Used by primary device ODSA operation.
+ *
+ * @param terminalEid The eUICC identifier (EID) of the primary device eSIM in case of
+ * multiple
+ * SIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalEid(@NonNull String terminalEid);
+
+ /**
+ * Sets the serial number of primary device. Used by HTTP parameter
+ * {@code target_terminal_sn}.
+ *
+ * @param targetTerminalSerialNumber The serial number of primary device.
+ * <p>This is a non-standard params required by some
+ * carriers.
+ * @return The builder.
*/
- public abstract Builder setTargetTerminalEid(String value);
+ @NonNull
+ public abstract Builder setTargetTerminalSerialNumber(
+ @NonNull String targetTerminalSerialNumber);
+
+ /**
+ * Sets the model of primary device. Used by HTTP parameter {@code target_terminal_model}.
+ *
+ * @param targetTerminalModel The model of primary device.
+ * <p>This is a non-standard params required by some carriers.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalModel(@NonNull String targetTerminalModel);
/**
* Sets the unique identifier of the old device eSIM, like the IMEI associated with the
- * eSIM. Used by HTTP parameter "old_terminal_id" if set.
+ * eSIM.
+ * Used by HTTP parameter {@code old_terminal_id} if set.
*
* <p>Used by primary device ODSA operation.
+ *
+ * @param oldTerminalId The unique identifier of the old device eSIM.
+ * @return The builder.
*/
- public abstract Builder setOldTerminalId(String value);
+ @NonNull
+ public abstract Builder setOldTerminalId(@NonNull String oldTerminalId);
/**
- * Sets the ICCID old device eSIM. Used by HTTP parameter "old_terminal_iccid" if set.
+ * Sets the ICCID old device eSIM. Used by HTTP parameter {@code old_terminal_iccid} if set.
*
* <p>Used by primary device ODSA operation.
+ *
+ * @param oldTerminalIccid The ICCID old device eSIM.
+ * @return The builder.
*/
- public abstract Builder setOldTerminalIccid(String value);
+ @NonNull
+ public abstract Builder setOldTerminalIccid(@NonNull String oldTerminalIccid);
+ /** Returns the {@link EsimOdsaOperation} object. */
+ @NonNull
public abstract EsimOdsaOperation build();
}
}
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlement.java b/java/com/android/libraries/entitlement/ServiceEntitlement.java
index e050cd3..af30de6 100644
--- a/java/com/android/libraries/entitlement/ServiceEntitlement.java
+++ b/java/com/android/libraries/entitlement/ServiceEntitlement.java
@@ -18,67 +18,93 @@ package com.android.libraries.entitlement;
import android.content.Context;
-import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.libraries.entitlement.eapaka.EapAkaApi;
+import com.android.libraries.entitlement.http.HttpResponse;
+import com.android.libraries.entitlement.utils.DebugUtils;
+import com.android.libraries.entitlement.utils.Ts43Constants;
import com.google.common.collect.ImmutableList;
import java.util.List;
/**
- * Implemnets protocol for carrier service entitlement configuration query and operation, based on
+ * Implements protocol for carrier service entitlement configuration query and operation, based on
* GSMA TS.43 spec.
*/
public class ServiceEntitlement {
/**
* App ID for Voice-Over-LTE entitlement.
*/
- public static final String APP_VOLTE = "ap2003";
+ public static final String APP_VOLTE = Ts43Constants.APP_VOLTE;
/**
* App ID for Voice-Over-WiFi entitlement.
*/
- public static final String APP_VOWIFI = "ap2004";
+ public static final String APP_VOWIFI = Ts43Constants.APP_VOWIFI;
/**
* App ID for SMS-Over-IP entitlement.
*/
- public static final String APP_SMSOIP = "ap2005";
+ public static final String APP_SMSOIP = Ts43Constants.APP_SMSOIP;
/**
- * App ID for on device service activation (OSDA) for companion device.
+ * App ID for on device service activation (ODSA) for companion device.
*/
- public static final String APP_ODSA_COMPANION = "ap2006";
+ public static final String APP_ODSA_COMPANION = Ts43Constants.APP_ODSA_COMPANION;
/**
- * App ID for on device service activation (OSDA) for primary device.
+ * App ID for on device service activation (ODSA) for primary device.
*/
- public static final String APP_ODSA_PRIMARY = "ap2009";
+ public static final String APP_ODSA_PRIMARY = Ts43Constants.APP_ODSA_PRIMARY;
/**
- * App ID for premium network slice entitlement
+ * App ID for data plan information entitlement.
*/
- public static final String APP_PREMIUM_NETWORK_SLICE = "ap2012";
+ public static final String APP_DATA_PLAN_BOOST = Ts43Constants.APP_DATA_PLAN_BOOST;
+
+ /**
+ * App ID for server initiated requests, entitlement and activation.
+ */
+ public static final String APP_ODSA_SERVER_INITIATED_REQUESTS =
+ Ts43Constants.APP_ODSA_SERVER_INITIATED_REQUESTS;
+
+ /**
+ * App ID for direct carrier billing.
+ */
+ public static final String APP_DIRECT_CARRIER_BILLING =
+ Ts43Constants.APP_DIRECT_CARRIER_BILLING;
+
+ /**
+ * App ID for private user identity.
+ */
+ public static final String APP_PRIVATE_USER_IDENTITY = Ts43Constants.APP_PRIVATE_USER_IDENTITY;
+
+ /**
+ * App ID for phone number information.
+ */
+ public static final String APP_PHONE_NUMBER_INFORMATION =
+ Ts43Constants.APP_PHONE_NUMBER_INFORMATION;
+
+ /**
+ * App ID for satellite entitlement.
+ */
+ public static final String APP_SATELLITE_ENTITLEMENT = Ts43Constants.APP_SATELLITE_ENTITLEMENT;
private final CarrierConfig carrierConfig;
private final EapAkaApi eapAkaApi;
-
+ private ServiceEntitlementRequest mOidcRequest;
/**
* Creates an instance for service entitlement configuration query and operation for the
* carrier.
*
* @param context context of application
* @param carrierConfig carrier specific configs used in the queries and operations.
- * @param simSubscriptionId the subscroption ID of the carrier's SIM on device. This indicates
+ * @param simSubscriptionId the subscription ID of the carrier's SIM on device. This indicates
* which SIM to retrieve IMEI/IMSI from and perform EAP-AKA
* authentication with. See
* {@link android.telephony.SubscriptionManager}
- * for how to get the subscroption ID.
+ * for how to get the subscription ID.
*/
public ServiceEntitlement(Context context, CarrierConfig carrierConfig, int simSubscriptionId) {
- this(
- context,
- carrierConfig,
- simSubscriptionId,
- /* saveHttpHistory= */ false,
- /* bypassEapAkaResponse= */ "");
+ this(context, carrierConfig, simSubscriptionId, /* saveHttpHistory= */ false);
}
/**
@@ -87,9 +113,9 @@ public class ServiceEntitlement {
*
* @param context context of application
* @param carrierConfig carrier specific configs used in the queries and operations.
- * @param simSubscriptionId the subscroption ID of the carrier's SIM on device. This indicates
+ * @param simSubscriptionId the subscription ID of the carrier's SIM on device. This indicates
* which SIM to retrieve IMEI/IMSI from and perform EAP-AKA authentication with. See {@link
- * android.telephony.SubscriptionManager} for how to get the subscroption ID.
+ * android.telephony.SubscriptionManager} for how to get the subscription ID.
* @param saveHttpHistory set to {@code true} to save the history of request and response which
* can later be retrieved by {@code getHistory()}. Intended for debugging.
*/
@@ -103,7 +129,7 @@ public class ServiceEntitlement {
carrierConfig,
simSubscriptionId,
saveHttpHistory,
- /* bypassEapAkaResponse= */ "");
+ DebugUtils.getBypassEapAkaResponse());
}
/**
@@ -112,9 +138,9 @@ public class ServiceEntitlement {
*
* @param context context of application
* @param carrierConfig carrier specific configs used in the queries and operations.
- * @param simSubscriptionId the subscroption ID of the carrier's SIM on device. This indicates
+ * @param simSubscriptionId the subscription ID of the carrier's SIM on device. This indicates
* which SIM to retrieve IMEI/IMSI from and perform EAP-AKA authentication with. See {@link
- * android.telephony.SubscriptionManager} for how to get the subscroption ID.
+ * android.telephony.SubscriptionManager} for how to get the subscription ID.
* @param saveHttpHistory set to {@code true} to save the history of request and response which
* can later be retrieved by {@code getHistory()}. Intended for debugging.
* @param bypassEapAkaResponse set to non empty string to bypass EAP-AKA authentication.
@@ -155,14 +181,14 @@ public class ServiceEntitlement {
* <li>"token": not set, or {@code request.authenticationToken()} if it's not empty.
* <li>"IMSI": if "token" is set, set to {@link android.telephony.TelephonyManager#getImei}.
* <li>"EAP_ID": if "token" is not set, set this parameter to trigger embedded EAP-AKA
- * authentication as decribed in TS.43 section 2.6.1. Its value is derived from IMSI as per
+ * authentication as described in TS.43 section 2.6.1. Its value is derived from IMSI as per
* GSMA spec RCC.14 section C.2.
* <li>"terminal_id": IMEI, or {@code request.terminalId()} if it's not empty.
* <li>"terminal_vendor": {@link android.os.Build#MANUFACTURER}, or {@code
* request.terminalVendor()} if it's not empty.
* <li>"terminal_model": {@link android.os.Build#MODEL}, or {@code request.terminalModel()} if
* it's not empty.
- * <li>"terminal_sw_version": {@llink android.os.Build.VERSION#BASE_OS}, or {@code
+ * <li>"terminal_sw_version": {@link android.os.Build.VERSION#BASE_OS}, or {@code
* request.terminalSoftwareVersion()} if it's not empty.
* <li>"app_name": not set, or {@code request.appName()} if it's not empty.
* <li>"app_version": not set, or {@code request.appVersion()} if it's not empty.
@@ -176,10 +202,10 @@ public class ServiceEntitlement {
* @param appId an app ID string defined in TS.43 section 2.2, e.g. {@link #APP_VOWIFI}.
* @param request contains parameters that can be used in the HTTP request.
*/
- @Nullable
+ @NonNull
public String queryEntitlementStatus(String appId, ServiceEntitlementRequest request)
throws ServiceEntitlementException {
- return eapAkaApi.queryEntitlementStatus(ImmutableList.of(appId), carrierConfig, request);
+ return queryEntitlementStatus(ImmutableList.of(appId), request);
}
/**
@@ -187,13 +213,29 @@ public class ServiceEntitlement {
* request/response. For on device service activation (ODSA) of eSIM for companion/primary
* devices, use {@link #performEsimOdsa} instead.
*
- * <p>Same with {@link #queryEntitlementStatus(String, ServiceEntitlementRequest)} except that
+ * <p>Same as {@link #queryEntitlementStatus(String, ServiceEntitlementRequest)} except that
* multiple "app" parameters will be set in the HTTP request, in the order as they appear in
* parameter {@code appIds}.
*/
+ @NonNull
public String queryEntitlementStatus(ImmutableList<String> appIds,
ServiceEntitlementRequest request)
throws ServiceEntitlementException {
+ return getEntitlementStatusResponse(appIds, request).body();
+ }
+
+ /**
+ * Retrieves service entitlement configurations for multiple app IDs in one HTTP
+ * request/response. For on device service activation (ODSA) of eSIM for companion/primary
+ * devices, use {@link #performEsimOdsa} instead.
+ *
+ * <p>Same as {@link #queryEntitlementStatus(ImmutableList, ServiceEntitlementRequest)}
+ * except that it returns the full HTTP response instead of just the body.
+ */
+ @NonNull
+ public HttpResponse getEntitlementStatusResponse(ImmutableList<String> appIds,
+ ServiceEntitlementRequest request)
+ throws ServiceEntitlementException {
return eapAkaApi.queryEntitlementStatus(appIds, carrierConfig, request);
}
@@ -207,16 +249,79 @@ public class ServiceEntitlement {
* needed, and returns the raw configuration doc as a string. Additional parameters from {@code
* operation} are set to the HTTP request. See {@link EsimOdsaOperation} for details.
*/
+ @NonNull
public String performEsimOdsa(
String appId, ServiceEntitlementRequest request, EsimOdsaOperation operation)
throws ServiceEntitlementException {
+ return getEsimOdsaResponse(appId, request, operation).body();
+ }
+
+ /**
+ * Retrieves the HTTP response after performing on device service activation (ODSA) of eSIM for
+ * companion/primary devices.
+ *
+ * <p>Same as {@link #performEsimOdsa(String, ServiceEntitlementRequest, EsimOdsaOperation)}
+ * except that it returns the full HTTP response instead of just the body.
+ */
+ @NonNull
+ public HttpResponse getEsimOdsaResponse(
+ String appId, ServiceEntitlementRequest request, EsimOdsaOperation operation)
+ throws ServiceEntitlementException {
return eapAkaApi.performEsimOdsaOperation(appId, carrierConfig, request, operation);
}
/**
+ * Retrieves the endpoint for OpenID Connect(OIDC) authentication.
+ *
+ * <p>Implementation based on section 2.8.2 of TS.43
+ *
+ * <p>The user should call {@link #queryEntitlementStatusFromOidc(String url)} with the
+ * authentication result to retrieve the service entitlement configuration.
+ *
+ * @param appId an app ID string defined in TS.43 section 2.2
+ * @param request contains parameters that can be used in the HTTP request
+ */
+ @NonNull
+ public String acquireOidcAuthenticationEndpoint(String appId, ServiceEntitlementRequest request)
+ throws ServiceEntitlementException {
+ mOidcRequest = request;
+ return eapAkaApi.acquireOidcAuthenticationEndpoint(appId, carrierConfig, request);
+ }
+
+ /**
+ * Retrieves the service entitlement configuration from OIDC authentication result.
+ *
+ * <p>Implementation based on section 2.8.2 of TS.43.
+ *
+ * <p>{@link #acquireOidcAuthenticationEndpoint} must be called before calling this method.
+ *
+ * @param url the redirect url from OIDC authentication result.
+ */
+ @NonNull
+ public String queryEntitlementStatusFromOidc(String url) throws ServiceEntitlementException {
+ return getEntitlementStatusResponseFromOidc(url).body();
+ }
+
+ /**
+ * Retrieves the HTTP response containing the service entitlement configuration from
+ * OIDC authentication result.
+ *
+ * <p>Same as {@link #queryEntitlementStatusFromOidc(String)} except that it returns the
+ * full HTTP response instead of just the body.
+ *
+ * @param url the redirect url from OIDC authentication result.
+ */
+ @NonNull
+ public HttpResponse getEntitlementStatusResponseFromOidc(String url)
+ throws ServiceEntitlementException {
+ return eapAkaApi.queryEntitlementStatusFromOidc(url, carrierConfig, mOidcRequest);
+ }
+
+ /**
* Retrieves the history of past HTTP request and responses if {@code saveHttpHistory} was set
* in constructor.
*/
+ @NonNull
public List<String> getHistory() {
return eapAkaApi.getHistory();
}
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlementException.java b/java/com/android/libraries/entitlement/ServiceEntitlementException.java
index 45b1b9b..612e2e7 100644
--- a/java/com/android/libraries/entitlement/ServiceEntitlementException.java
+++ b/java/com/android/libraries/entitlement/ServiceEntitlementException.java
@@ -16,6 +16,11 @@
package com.android.libraries.entitlement;
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Indicates errors happened in retrieving service entitlement configuration.
*/
@@ -25,28 +30,38 @@ public class ServiceEntitlementException extends Exception {
*/
public static final int ERROR_UNKNOWN = 0;
+ /**
+ * Failure to compose JSON when making POST requests.
+ */
+ public static final int ERROR_JSON_COMPOSE_FAILURE = 1;
+
// Android telephony related failures
/**
* Android telephony is unable to provide info like IMSI, e.g. when modem crashed.
*/
public static final int ERROR_PHONE_NOT_AVAILABLE = 10;
- // EAP-AKA authentication related falures
+ // EAP-AKA authentication related failures
/**
* SIM not returning a response to the EAP-AKA challenge, e.g. when the challenge is invalid.
- * This can happen only when an embedded EAP-AKA challange is conducted, as per GMSA spec TS.43
+ * This can happen only when an embedded EAP-AKA challenge is conducted, as per GSMA spec TS.43
* section 2.6.1.
*/
public static final int ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE = 20;
/**
- * EAP-AKA synchronization failure that cannot be recoverd even after the "Sequence number
+ * EAP-AKA synchronization failure that cannot be recovered even after the "Sequence number
* synchronization" procedure as defined in RFC 4187.
*/
public static final int ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE = 21;
+ /**
+ * EAP-AKA failure that happens when the client fails to authenticate within the maximum number
+ * of attempts
+ */
+ public static final int ERROR_EAP_AKA_FAILURE = 22;
// HTTP related failures
/**
- * Cannot connect to the entitlment server, e.g. due to weak mobile network and Wi-Fi
+ * Cannot connect to the entitlement server, e.g. due to weak mobile network and Wi-Fi
* connection.
*/
public static final int ERROR_SERVER_NOT_CONNECTABLE = 30;
@@ -62,19 +77,40 @@ public class ServiceEntitlementException extends Exception {
*/
public static final int ERROR_MALFORMED_HTTP_RESPONSE = 32;
+ // ODSA errors
+ /**
+ * HTTP response does not contain the authentication token.
+ */
+ public static final int ERROR_TOKEN_NOT_AVAILABLE = 60;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ERROR_UNKNOWN,
+ ERROR_PHONE_NOT_AVAILABLE,
+ ERROR_ICC_AUTHENTICATION_NOT_AVAILABLE,
+ ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE,
+ ERROR_EAP_AKA_FAILURE,
+ ERROR_SERVER_NOT_CONNECTABLE,
+ ERROR_HTTP_STATUS_NOT_SUCCESS,
+ ERROR_MALFORMED_HTTP_RESPONSE,
+ ERROR_TOKEN_NOT_AVAILABLE
+ })
+ public @interface ErrorCode {}
+
/**
* Default HTTP status if not been specified.
*/
- private static final int HTTP_STATUS_UNSPECIFIED = 0;
+ public static final int HTTP_STATUS_UNSPECIFIED = 0;
/**
* An empty string if Retry-After header in HTTP response not been specified.
*/
- private static final String RETRY_AFTER_UNSPECIFIED = "";
+ public static final String RETRY_AFTER_UNSPECIFIED = "";
- private int mError;
- private int mHttpStatus;
- private String mRetryAfter;
+ @ErrorCode
+ private final int mError;
+ private final int mHttpStatus;
+ private final String mRetryAfter;
public ServiceEntitlementException(int error, String message) {
this(error, HTTP_STATUS_UNSPECIFIED, RETRY_AFTER_UNSPECIFIED, message);
@@ -109,8 +145,9 @@ public class ServiceEntitlementException extends Exception {
}
/**
- * Returns the error code, see {@link #ERROR_*}. {@link #ERROR_UNKNOWN} if not been specified.
+ * Returns the error code. {@link #ERROR_UNKNOWN} if not been specified.
*/
+ @ErrorCode
public int getErrorCode() {
return mError;
}
@@ -127,8 +164,8 @@ public class ServiceEntitlementException extends Exception {
* Returns the "Retry-After" header in HTTP response, often set with HTTP status code 503; an
* empty string if unavailable.
*
- * @return the HTTP-date or a number of seconds to delay, as defiend in RFC 7231:
- * https://tools.ietf.org/html/rfc7231#section-7.1.3
+ * @return the HTTP-date or a number of seconds to delay, as defined in RFC 7231:
+ * <a href="https://tools.ietf.org/html/rfc7231#section-7.1.3">...</a>
*/
public String getRetryAfter() {
return mRetryAfter;
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java b/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
index 59e70ef..38206de 100644
--- a/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
+++ b/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
@@ -19,30 +19,24 @@ package com.android.libraries.entitlement;
import android.os.Build;
import android.os.Build.VERSION;
+import com.android.libraries.entitlement.utils.Ts43Constants;
+
import com.google.auto.value.AutoValue;
/**
- * Service entitlement HTTP request parameters, as defiend in GSMA spec TS.43 section 2.2.
+ * Service entitlement HTTP request parameters, as defined in GSMA spec TS.43 section 2.2.
*/
@AutoValue
public abstract class ServiceEntitlementRequest {
- /** Disables notification token. */
- public static final int NOTICATION_ACTION_DISABLE = 0;
- /** Enables FCM notification token. */
- public static final int NOTICATION_ACTION_ENABLE_FCM = 2;
/** Accepts the content type in XML format. */
public static final String ACCEPT_CONTENT_TYPE_XML = "text/vnd.wap.connectivity-xml";
/** Accepts the content type in JSON format. */
public static final String ACCEPT_CONTENT_TYPE_JSON =
- "application/vnd.gsma.eap-relay.v1.0+json";
- /** Accepts the content type in JSON or XML format. */
+ "application/json";
public static final String ACCEPT_CONTENT_TYPE_JSON_AND_XML =
- "application/vnd.gsma.eap-relay.v1.0+json, text/vnd.wap.connectivity-xml";
+ "application/json, text/vnd.wap.connectivity-xml";
/** Default value of configuration version. */
public static final int DEFAULT_CONFIGURATION_VERSION = 0;
- /** Default value of entitlement version. */
- public static final String DEFAULT_ENTITLEMENT_VERSION = "2.0";
-
/**
* Returns the version of configuration currently stored on the client. Used by HTTP parameter
@@ -107,10 +101,8 @@ public abstract class ServiceEntitlementRequest {
/**
* Returns the action associated with the FCM registration token. Used by HTTP parameter
* "notif_action".
- *
- * @see #NOTICATION_ACTION_ENABLE_FCM
- * @see #NOTICATION_ACTION_DISABLE
*/
+ @Ts43Constants.NotificationAction
public abstract int notificationAction();
/**
@@ -123,10 +115,9 @@ public abstract class ServiceEntitlementRequest {
public abstract String acceptContentType();
/**
- * Returns the network identifier for premium network. Used for premium network slice
- * entitlement.
+ * Returns the boost type for premium network. Used for premium network slice entitlement.
*/
- public abstract String networkIdentifier();
+ public abstract String boostType();
/**
* Returns a new {@link Builder} object.
@@ -134,19 +125,19 @@ public abstract class ServiceEntitlementRequest {
public static Builder builder() {
return new AutoValue_ServiceEntitlementRequest.Builder()
.setConfigurationVersion(DEFAULT_CONFIGURATION_VERSION)
- .setEntitlementVersion(DEFAULT_ENTITLEMENT_VERSION)
+ .setEntitlementVersion(Ts43Constants.DEFAULT_ENTITLEMENT_VERSION)
.setAuthenticationToken("")
.setTemporaryToken("")
.setTerminalId("")
.setTerminalVendor(Build.MANUFACTURER)
.setTerminalModel(Build.MODEL)
- .setTerminalSoftwareVersion(VERSION.BASE_OS)
+ .setTerminalSoftwareVersion(VERSION.RELEASE)
.setAppName("")
.setAppVersion("")
.setNotificationToken("")
- .setNotificationAction(NOTICATION_ACTION_ENABLE_FCM)
+ .setNotificationAction(Ts43Constants.NOTIFICATION_ACTION_ENABLE_FCM)
.setAcceptContentType(ACCEPT_CONTENT_TYPE_JSON_AND_XML)
- .setNetworkIdentifier("");
+ .setBoostType("");
}
/**
@@ -167,7 +158,8 @@ public abstract class ServiceEntitlementRequest {
* Sets the current version of the entitlement specification. Used by HTTP parameter
* "entitlement_version".
*
- * <p>If not set, default to {@link #DEFAULT_ENTITLEMENT_VERSION} base on TS.43-v5.0.
+ * <p>If not set, default to {@link Ts43Constants#DEFAULT_ENTITLEMENT_VERSION} base on
+ * TS.43-v5.0.
*/
public abstract Builder setEntitlementVersion(String value);
@@ -243,13 +235,10 @@ public abstract class ServiceEntitlementRequest {
* Sets the action associated with the FCM registration token. Used by HTTP parameter
* "notif_action".
*
- * <p>Required if a token is set with {@link #setNotificationToken}, and default to {@link
- * #NOTICATION_ACTION_ENABLE_FCM}; otherwise ignored.
- *
- * @see #NOTICATION_ACTION_ENABLE_FCM
- * @see #NOTICATION_ACTION_DISABLE
+ * <p>Required if a token is set with {@link #setNotificationToken}, and default to
+ * {@link Ts43Constants#NOTIFICATION_ACTION_ENABLE_FCM}; otherwise ignored.
*/
- public abstract Builder setNotificationAction(int value);
+ public abstract Builder setNotificationAction(@Ts43Constants.NotificationAction int value);
/**
* Sets the configuration document format the caller accepts, e.g. XML or JSON. Used by HTTP
@@ -264,12 +253,12 @@ public abstract class ServiceEntitlementRequest {
public abstract Builder setAcceptContentType(String contentType);
/**
- * Sets the network identifier for premium network. Used by HTTP parameter
- * "network_identifier" in case of premium network slice entitlement.
+ * Sets the boost type for premium network. Used by HTTP parameter
+ * "boost_type" in case of premium network slice entitlement.
*
* <p>Optional.
*/
- public abstract Builder setNetworkIdentifier(String value);
+ public abstract Builder setBoostType(String value);
public abstract ServiceEntitlementRequest build();
}
diff --git a/java/com/android/libraries/entitlement/Ts43Authentication.java b/java/com/android/libraries/entitlement/Ts43Authentication.java
new file mode 100644
index 0000000..29d0cb9
--- /dev/null
+++ b/java/com/android/libraries/entitlement/Ts43Authentication.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement;
+
+import static com.google.common.base.Strings.nullToEmpty;
+
+import android.content.Context;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.libraries.entitlement.http.HttpResponse;
+import com.android.libraries.entitlement.utils.Ts43Constants;
+import com.android.libraries.entitlement.utils.Ts43Constants.AppId;
+import com.android.libraries.entitlement.utils.Ts43XmlDoc;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+import java.net.URL;
+import java.util.Objects;
+
+/**
+ * The class responsible for TS.43 authentication process.
+ */
+public class Ts43Authentication {
+ private static final String TAG = "Ts43Auth";
+
+ /**
+ * The authentication token for TS.43 operation.
+ */
+ @AutoValue
+ public abstract static class Ts43AuthToken {
+ /**
+ * Indicating the validity of token is not available.
+ */
+ public static long VALIDITY_NOT_AVAILABLE = -1;
+
+ /**
+ * The authentication token for TS.43 operations.
+ */
+ @NonNull
+ public abstract String token();
+
+ /**
+ * The list of cookies from the {@code Set-Cookie} header of the TS.43 response.
+ */
+ @NonNull
+ public abstract ImmutableList<String> cookies();
+
+ /**
+ * Indicates the validity of the token. Note this value is server dependent. The client is
+ * expected to interpret this value itself.
+ */
+ public abstract long validity();
+
+ /**
+ * Create the {@link Ts43AuthToken} object.
+ *
+ * @param token The authentication token for TS.43 operations.
+ * @param cookie The list of cookies from the {@code Set-Cookie} header.
+ * @param validity Indicates the validity of the token. Note this value is server
+ * dependent. If not available, set to {@link #VALIDITY_NOT_AVAILABLE}.
+ *
+ * @return The {@link Ts43AuthToken} object.
+ */
+ public static Ts43AuthToken create(@NonNull String token,
+ @NonNull ImmutableList<String> cookie, long validity) {
+ return new AutoValue_Ts43Authentication_Ts43AuthToken(token, cookie, validity);
+ }
+ }
+
+ /**
+ * The application context.
+ */
+ @NonNull
+ private final Context mContext;
+
+ /**
+ * The entitlement server address.
+ */
+ @NonNull
+ private final URL mEntitlementServerAddress;
+
+ /**
+ * The TS.43 entitlement version to use. For example, {@code "9.0"}.
+ */
+ @NonNull
+ private final String mEntitlementVersion;
+
+ /**
+ * For test mocking only.
+ */
+ @VisibleForTesting
+ private ServiceEntitlement mServiceEntitlement;
+
+ /**
+ * Ts43Authentication constructor.
+ *
+ * @param context The application context.
+ * @param entitlementServerAddress The entitlement server address.
+ * @param entitlementVersion The TS.43 entitlement version to use. For example, {@code "9.0"}.
+ * If {@code null}, version {@code "2.0"} will be used by default.
+ *
+ * @throws NullPointerException wWhen {@code context} or {@code entitlementServerAddress} is
+ * {@code null}.
+ */
+ public Ts43Authentication(@NonNull Context context, @NonNull URL entitlementServerAddress,
+ @Nullable String entitlementVersion) {
+ Objects.requireNonNull(context, "context is null");
+ Objects.requireNonNull(entitlementServerAddress, "entitlementServerAddress is null.");
+
+ mContext = context;
+ mEntitlementServerAddress = entitlementServerAddress;
+
+ if (entitlementVersion != null) {
+ mEntitlementVersion = entitlementVersion;
+ } else {
+ mEntitlementVersion = Ts43Constants.DEFAULT_ENTITLEMENT_VERSION;
+ }
+ }
+
+ /**
+ * Get the authentication token for TS.43 operations with EAP-AKA described in TS.43
+ * Service Entitlement Configuration section 2.8.1.
+ *
+ * @param slotIndex The logical SIM slot index involved in ODSA operation.
+ * See {@link SubscriptionInfo#getSubscriptionId()}.
+
+ * @param appId Application id. For example, {@link Ts43Constants#APP_VOWIFI} for VoWifi,
+ * {@link Ts43Constants#APP_ODSA_PRIMARY} for ODSA primary device. Refer GSMA to Service
+ * Entitlement Configuration section 2.3.
+ * @param appName The calling client's package name. Used for {@code app_name} in HTTP GET
+ * request in GSMA TS.43 Service Entitlement Configuration section 2.3.
+ * @param appVersion The calling client's version. Used for {@code app_version} in HTTP GET
+ * request in GSMA TS.43 Service Entitlement Configuration section 2.3.
+ *
+ * @return The authentication token.
+ *
+ * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
+ * error from the server, the error code can be retrieved by
+ * {@link ServiceEntitlementException#getHttpStatus()}.
+ * @throws IllegalArgumentException when {@code slotIndex} or {@code appId} is invalid.
+ * @throws NullPointerException when {@code context}, {@code entitlementServerAddress}, or
+ * {@code appId} is {@code null}.
+ */
+ @NonNull
+ public Ts43AuthToken getAuthToken(int slotIndex, @NonNull @AppId String appId,
+ @Nullable String appName, @Nullable String appVersion)
+ throws ServiceEntitlementException {
+ Objects.requireNonNull(appId, "appId is null");
+
+ if (!Ts43Constants.isValidAppId(appId)) {
+ throw new IllegalArgumentException("getAuthToken: invalid app id " + appId);
+ }
+
+ String imei = null;
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ if (telephonyManager != null) {
+ if (slotIndex < 0 || slotIndex >= telephonyManager.getActiveModemCount()) {
+ throw new IllegalArgumentException("getAuthToken: invalid slot index " + slotIndex);
+ }
+ imei = telephonyManager.getImei(slotIndex);
+ }
+
+ // Build the HTTP request. The default params are specified in
+ // ServiceEntitlementRequest.builder() already.
+ ServiceEntitlementRequest request =
+ ServiceEntitlementRequest.builder()
+ .setEntitlementVersion(mEntitlementVersion)
+ .setTerminalId(imei)
+ .setAppName(appName)
+ .setAppVersion(appVersion)
+ .build();
+ CarrierConfig carrierConfig = CarrierConfig.builder()
+ .setServerUrl(mEntitlementServerAddress.toString())
+ .build();
+
+ if (mServiceEntitlement == null) {
+ mServiceEntitlement = new ServiceEntitlement(mContext, carrierConfig,
+ SubscriptionManager.getSubscriptionId(slotIndex));
+ }
+
+ // Get the full HTTP response instead of just the body so we can reuse the same cookies.
+ HttpResponse response;
+ String rawXml;
+ try {
+ response = mServiceEntitlement.getEntitlementStatusResponse(
+ ImmutableList.of(appId), request);
+ rawXml = response == null ? null : response.body();
+ Log.d(TAG, "getAuthToken: rawXml=" + rawXml);
+ } catch (ServiceEntitlementException e) {
+ Log.w(TAG, "Failed to get authentication token. e=" + e);
+ throw e;
+ }
+
+ ImmutableList<String> cookies = response == null ? ImmutableList.of() : response.cookies();
+
+ Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml);
+ String authToken = ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.TOKEN), Ts43XmlDoc.Parm.TOKEN);
+ if (TextUtils.isEmpty(authToken)) {
+ Log.w(TAG, "Failed to parse authentication token");
+ throw new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_TOKEN_NOT_AVAILABLE,
+ "Failed to parse authentication token");
+ }
+
+ String validityString = nullToEmpty(ts43XmlDoc.get(ImmutableList.of(
+ Ts43XmlDoc.CharacteristicType.TOKEN), Ts43XmlDoc.Parm.VALIDITY));
+ long validity;
+ try {
+ validity = Long.parseLong(validityString);
+ } catch (NumberFormatException e) {
+ validity = Ts43AuthToken.VALIDITY_NOT_AVAILABLE;
+ }
+
+ return Ts43AuthToken.create(authToken, cookies, validity);
+ }
+
+ /**
+ * Get the URL of OIDC (OpenID Connect) server as described in TS.43 Service Entitlement
+ * Configuration section 2.8.2.
+ *
+ * The caller is expected to present the content of the URL to the user to proceed the
+ * authentication process. After that the caller can call {@link #getAuthToken(URL)}
+ * to get the authentication token.
+ *
+ * @param slotIndex The logical SIM slot index involved in ODSA operation.
+ * @param entitlementServerAddress The entitlement server address.
+ * @param entitlementVersion The TS.43 entitlement version to use. For example, {@code "9.0"}.
+ * @param appId Application id. For example, {@link Ts43Constants#APP_VOWIFI} for VoWifi,
+ * {@link Ts43Constants#APP_ODSA_PRIMARY} for ODSA primary device. Refer GSMA to Service
+ * Entitlement Configuration section 2.3.
+ * @param appName The calling client's package name. Used for {@code app_name} in HTTP GET
+ * request in GSMA TS.43 Service Entitlement Configuration section 2.3.
+ * @param appVersion The calling client's version. Used for {@code app_version} in HTTP GET
+ * request in GSMA TS.43 Service Entitlement Configuration section 2.3.
+ *
+ * @return The URL of OIDC server with all the required parameters for client to launch a
+ * user interface for users to interact with the authentication process. The parameters in URL
+ * include {@code client_id}, {@code redirect_uri}, {@code state}, and {@code nonce}.
+ *
+ * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
+ * error from the server, the error code can be retrieved by
+ * {@link ServiceEntitlementException#getHttpStatus()}
+ */
+ @NonNull
+ public URL getOidcAuthServer(@NonNull Context context, int slotIndex,
+ @NonNull URL entitlementServerAddress, @Nullable String entitlementVersion,
+ @NonNull @AppId String appId, @Nullable String appName, @Nullable String appVersion)
+ throws ServiceEntitlementException {
+ return null;
+ }
+
+ /**
+ * Get the authentication token for TS.43 operations with OIDC (OpenID Connect) described in
+ * TS.43 Service Entitlement Configuration section 2.8.2.
+ *
+ * @param aesUrl The AES URL used to retrieve auth token. The parameters in the URL include
+ * the OIDC auth code {@code code} and {@code state}.
+ *
+ * @return The authentication token.
+ *
+ * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
+ * error from the server, the error code can be retrieved by
+ * {@link ServiceEntitlementException#getHttpStatus()}
+ */
+ @NonNull
+ public Ts43AuthToken getAuthToken(@NonNull URL aesUrl)
+ throws ServiceEntitlementException {
+ return null;
+ }
+}
diff --git a/java/com/android/libraries/entitlement/Ts43Operation.java b/java/com/android/libraries/entitlement/Ts43Operation.java
new file mode 100644
index 0000000..e603e86
--- /dev/null
+++ b/java/com/android/libraries/entitlement/Ts43Operation.java
@@ -0,0 +1,1076 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement;
+
+import android.content.Context;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.libraries.entitlement.EsimOdsaOperation.OdsaServiceStatus;
+import com.android.libraries.entitlement.http.HttpConstants;
+import com.android.libraries.entitlement.odsa.AcquireConfigurationOperation.AcquireConfigurationRequest;
+import com.android.libraries.entitlement.odsa.AcquireConfigurationOperation.AcquireConfigurationResponse;
+import com.android.libraries.entitlement.odsa.AcquireTemporaryTokenOperation.AcquireTemporaryTokenRequest;
+import com.android.libraries.entitlement.odsa.AcquireTemporaryTokenOperation.AcquireTemporaryTokenResponse;
+import com.android.libraries.entitlement.odsa.CheckEligibilityOperation;
+import com.android.libraries.entitlement.odsa.CheckEligibilityOperation.CheckEligibilityRequest;
+import com.android.libraries.entitlement.odsa.CheckEligibilityOperation.CheckEligibilityResponse;
+import com.android.libraries.entitlement.odsa.DownloadInfo;
+import com.android.libraries.entitlement.odsa.GetPhoneNumberOperation.GetPhoneNumberRequest;
+import com.android.libraries.entitlement.odsa.GetPhoneNumberOperation.GetPhoneNumberResponse;
+import com.android.libraries.entitlement.odsa.ManageServiceOperation.ManageServiceRequest;
+import com.android.libraries.entitlement.odsa.ManageServiceOperation.ManageServiceResponse;
+import com.android.libraries.entitlement.odsa.ManageSubscriptionOperation.ManageSubscriptionRequest;
+import com.android.libraries.entitlement.odsa.ManageSubscriptionOperation.ManageSubscriptionResponse;
+import com.android.libraries.entitlement.odsa.OdsaResponse;
+import com.android.libraries.entitlement.odsa.PlanOffer;
+import com.android.libraries.entitlement.utils.Ts43Constants;
+import com.android.libraries.entitlement.utils.Ts43XmlDoc;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeParseException;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/** TS43 operations described in GSMA Service Entitlement Configuration spec. */
+public class Ts43Operation {
+ private static final String TAG = "Ts43";
+
+ /**
+ * The normal token retrieved via {@link Ts43Authentication#getAuthToken(int, String, String,
+ * String)} or {@link Ts43Authentication#getAuthToken(URL)}.
+ */
+ public static final int TOKEN_TYPE_NORMAL = 1;
+
+ /**
+ * The temporary token retrieved via {@link
+ * Ts43Operation#acquireTemporaryToken(AcquireTemporaryTokenRequest)}.
+ */
+ public static final int TOKEN_TYPE_TEMPORARY = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({TOKEN_TYPE_NORMAL, TOKEN_TYPE_TEMPORARY})
+ public @interface TokenType {
+ }
+
+ /** The application context. */
+ @NonNull
+ private final Context mContext;
+
+ /**
+ * The TS.43 entitlement version to use. For example, {@code "9.0"}. If {@code null}, version
+ * {@code "2.0"} will be used by default.
+ */
+ @NonNull
+ private final String mEntitlementVersion;
+
+ /** The entitlement server address. */
+ @NonNull
+ private final URL mEntitlementServerAddress;
+
+ /**
+ * The authentication token used for TS.43 operation. This token could be automatically updated
+ * after each TS.43 operation if the server provides the new token in the operation's HTTP
+ * response.
+ */
+ @Nullable
+ private String mAuthToken;
+
+ /**
+ * The temporary token retrieved from {@link
+ * #acquireTemporaryToken(AcquireTemporaryTokenRequest)}.
+ */
+ @Nullable
+ private String mTemporaryToken;
+
+ /**
+ * Token type. When token type is {@link #TOKEN_TYPE_NORMAL}, {@link #mAuthToken} is used. When
+ * toke type is {@link #TOKEN_TYPE_TEMPORARY}, {@link #mTemporaryToken} is used.
+ */
+ @TokenType
+ private int mTokenType;
+
+ private final ServiceEntitlement mServiceEntitlement;
+
+ /** IMEI of the device. */
+ private final String mImei;
+
+ /**
+ * Constructor of Ts43Operation.
+ *
+ * @param slotIndex The logical SIM slot index involved in ODSA operation.
+ * @param entitlementServerAddress The entitlement server address.
+ * @param entitlementVersion The TS.43 entitlement version to use. For example,
+ * {@code "9.0"}. If {@code null}, version {@code "2.0"} will be used
+ * by default.
+ * @param authToken The authentication token.
+ * @param tokenType The token type. Can be {@link #TOKEN_TYPE_NORMAL} or
+ * {@link #TOKEN_TYPE_TEMPORARY}.
+ */
+ public Ts43Operation(
+ @NonNull Context context,
+ int slotIndex,
+ @NonNull URL entitlementServerAddress,
+ @Nullable String entitlementVersion,
+ @NonNull String authToken,
+ @TokenType int tokenType) {
+ mContext = context;
+ mEntitlementServerAddress = entitlementServerAddress;
+ if (entitlementVersion != null) {
+ mEntitlementVersion = entitlementVersion;
+ } else {
+ mEntitlementVersion = Ts43Constants.DEFAULT_ENTITLEMENT_VERSION;
+ }
+
+ if (tokenType == TOKEN_TYPE_NORMAL) {
+ mAuthToken = authToken;
+ } else if (tokenType == TOKEN_TYPE_TEMPORARY) {
+ mTemporaryToken = authToken;
+ } else {
+ throw new IllegalArgumentException("Invalid token type " + tokenType);
+ }
+ mTokenType = tokenType;
+
+ CarrierConfig carrierConfig =
+ CarrierConfig.builder().setServerUrl(mEntitlementServerAddress.toString()).build();
+
+ mServiceEntitlement =
+ new ServiceEntitlement(
+ mContext, carrierConfig, SubscriptionManager.getSubscriptionId(slotIndex));
+
+ String imei = null;
+ TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+ if (telephonyManager != null) {
+ if (slotIndex < 0 || slotIndex >= telephonyManager.getActiveModemCount()) {
+ throw new IllegalArgumentException("getAuthToken: invalid slot index " + slotIndex);
+ }
+ imei = telephonyManager.getImei(slotIndex);
+ }
+ mImei = Strings.nullToEmpty(imei);
+ }
+
+ /**
+ * To verify if end-user is allowed to invoke the ODSA application as described in GSMA Service
+ * Entitlement Configuration section 6.2 and 6.5.2.
+ *
+ * @return {@code true} if the end-user is allowed to perform ODSA operation.
+ * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
+ * error from the server, the error code can be retrieved by
+ * {@link ServiceEntitlementException#getHttpStatus()}
+ */
+ @NonNull
+ public CheckEligibilityResponse checkEligibility(
+ @NonNull CheckEligibilityRequest checkEligibilityRequest)
+ throws ServiceEntitlementException {
+ Objects.requireNonNull(checkEligibilityRequest);
+
+ ServiceEntitlementRequest.Builder builder =
+ ServiceEntitlementRequest.builder()
+ .setEntitlementVersion(mEntitlementVersion)
+ .setTerminalId(mImei);
+
+ if (mTokenType == TOKEN_TYPE_NORMAL) {
+ builder.setAuthenticationToken(mAuthToken);
+ } else if (mTokenType == TOKEN_TYPE_TEMPORARY) {
+ builder.setTemporaryToken(mTemporaryToken);
+ }
+
+ String notificationToken = checkEligibilityRequest.notificationToken();
+ if (!TextUtils.isEmpty(notificationToken)) {
+ builder.setNotificationToken(notificationToken);
+ }
+ int notificationAction = checkEligibilityRequest.notificationAction();
+ if (Ts43Constants.isValidNotificationAction(notificationAction)) {
+ builder.setNotificationAction(notificationAction);
+ }
+
+ ServiceEntitlementRequest request = builder.build();
+
+ EsimOdsaOperation operation =
+ EsimOdsaOperation.builder()
+ .setOperation(EsimOdsaOperation.OPERATION_CHECK_ELIGIBILITY)
+ .setCompanionTerminalId(checkEligibilityRequest.companionTerminalId())
+ .setCompanionTerminalVendor(
+ checkEligibilityRequest.companionTerminalVendor())
+ .setCompanionTerminalModel(checkEligibilityRequest.companionTerminalModel())
+ .setCompanionTerminalSoftwareVersion(
+ checkEligibilityRequest.companionTerminalSoftwareVersion())
+ .setCompanionTerminalFriendlyName(
+ checkEligibilityRequest.companionTerminalFriendlyName())
+ .build();
+
+ String rawXml;
+ try {
+ rawXml =
+ mServiceEntitlement.performEsimOdsa(checkEligibilityRequest.appId(), request,
+ operation);
+ } catch (ServiceEntitlementException e) {
+ Log.w(TAG, "manageSubscription: Failed to perform ODSA operation. e=" + e);
+ throw e;
+ }
+
+ // Build the response of check eligibility operation. Refer to GSMA Service Entitlement
+ // Configuration section 6.5.2.
+ CheckEligibilityResponse.Builder responseBuilder = CheckEligibilityResponse.builder();
+
+ Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml);
+
+ try {
+ processGeneralResult(ts43XmlDoc, responseBuilder);
+ } catch (MalformedURLException e) {
+ throw new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE,
+ "checkEligibility: Malformed URL " + rawXml);
+ }
+
+ // Parse the eligibility
+ String eligibilityString =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.PRIMARY_APP_ELIGIBILITY);
+ if (TextUtils.isEmpty(eligibilityString)) {
+ eligibilityString =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.COMPANION_APP_ELIGIBILITY);
+ }
+
+ int eligibility = CheckEligibilityOperation.ELIGIBILITY_RESULT_UNKNOWN;
+ if (!TextUtils.isEmpty(eligibilityString)) {
+ switch (eligibilityString) {
+ case Ts43XmlDoc.ParmValues.DISABLED:
+ eligibility = CheckEligibilityOperation.ELIGIBILITY_RESULT_DISABLED;
+ break;
+ case Ts43XmlDoc.ParmValues.ENABLED:
+ eligibility = CheckEligibilityOperation.ELIGIBILITY_RESULT_ENABLED;
+ break;
+ case Ts43XmlDoc.ParmValues.INCOMPATIBLE:
+ eligibility = CheckEligibilityOperation.ELIGIBILITY_RESULT_INCOMPATIBLE;
+ break;
+ }
+ }
+ responseBuilder.setAppEligibility(eligibility);
+
+ // Parse companion device services
+ String companionDeviceServices =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.COMPANION_DEVICE_SERVICES);
+
+ if (!TextUtils.isEmpty(companionDeviceServices)) {
+ List<String> companionDeviceServicesList =
+ Arrays.asList(companionDeviceServices.split("\\s*,\\s*"));
+ responseBuilder.setCompanionDeviceServices(
+ ImmutableList.copyOf(companionDeviceServicesList));
+ }
+
+ // Parse notEnabledURL
+ URL notEnabledURL = null;
+ String notEnabledURLString =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.NOT_ENABLED_URL);
+
+ try {
+ notEnabledURL = new URL(notEnabledURLString);
+ responseBuilder.setNotEnabledUrl(notEnabledURL);
+ } catch (MalformedURLException e) {
+ Log.w(TAG, "checkEligibility: malformed URL " + notEnabledURLString);
+ }
+
+ // Parse notEnabledUserData
+ String notEnabledUserData =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.NOT_ENABLED_USER_DATA);
+
+ if (!TextUtils.isEmpty(notEnabledUserData)) {
+ responseBuilder.setNotEnabledUserData(notEnabledUserData);
+ }
+
+ // Parse notEnabledContentsType
+ String notEnabledContentsTypeString =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.NOT_ENABLED_CONTENTS_TYPE);
+
+ int notEnabledContentsType = HttpConstants.ContentType.UNKNOWN;
+ if (!TextUtils.isEmpty(notEnabledContentsTypeString)) {
+ switch (notEnabledContentsTypeString) {
+ case Ts43XmlDoc.ParmValues.CONTENTS_TYPE_XML:
+ notEnabledContentsType = HttpConstants.ContentType.XML;
+ break;
+ case Ts43XmlDoc.ParmValues.CONTENTS_TYPE_JSON:
+ notEnabledContentsType = HttpConstants.ContentType.JSON;
+ break;
+ }
+ }
+ responseBuilder.setNotEnabledContentsType(notEnabledContentsType);
+
+ return responseBuilder.build();
+ }
+
+ /**
+ * To request for subscription-related action on a primary or companion device as described in
+ * GSMA Service Entitlement Configuration section 6.2 and 6.5.3.
+ *
+ * @param manageSubscriptionRequest The manage subscription request.
+ * @return The response of manage subscription request.
+ * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
+ * error from the server, the error code can be retrieved by
+ * {@link ServiceEntitlementException#getHttpStatus()}
+ */
+ @NonNull
+ public ManageSubscriptionResponse manageSubscription(
+ @NonNull ManageSubscriptionRequest manageSubscriptionRequest)
+ throws ServiceEntitlementException {
+ Objects.requireNonNull(manageSubscriptionRequest);
+
+ ServiceEntitlementRequest.Builder builder =
+ ServiceEntitlementRequest.builder()
+ .setEntitlementVersion(mEntitlementVersion)
+ .setTerminalId(mImei)
+ .setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_XML);
+
+ if (mTokenType == TOKEN_TYPE_NORMAL) {
+ builder.setAuthenticationToken(mAuthToken);
+ } else if (mTokenType == TOKEN_TYPE_TEMPORARY) {
+ builder.setTemporaryToken(mTemporaryToken);
+ }
+
+ String notificationToken = manageSubscriptionRequest.notificationToken();
+ if (!TextUtils.isEmpty(notificationToken)) {
+ builder.setNotificationToken(notificationToken);
+ }
+ int notificationAction = manageSubscriptionRequest.notificationAction();
+ if (Ts43Constants.isValidNotificationAction(notificationAction)) {
+ builder.setNotificationAction(notificationAction);
+ }
+
+ ServiceEntitlementRequest request = builder.build();
+
+ EsimOdsaOperation operation =
+ EsimOdsaOperation.builder()
+ .setOperation(EsimOdsaOperation.OPERATION_MANAGE_SUBSCRIPTION)
+ .setOperationType(manageSubscriptionRequest.operationType())
+ .setCompanionTerminalId(manageSubscriptionRequest.companionTerminalId())
+ .setCompanionTerminalVendor(
+ manageSubscriptionRequest.companionTerminalVendor())
+ .setCompanionTerminalModel(
+ manageSubscriptionRequest.companionTerminalModel())
+ .setCompanionTerminalSoftwareVersion(
+ manageSubscriptionRequest.companionTerminalSoftwareVersion())
+ .setCompanionTerminalFriendlyName(
+ manageSubscriptionRequest.companionTerminalFriendlyName())
+ .setCompanionTerminalService(
+ manageSubscriptionRequest.companionTerminalService())
+ .setCompanionTerminalIccid(
+ manageSubscriptionRequest.companionTerminalIccid())
+ .setCompanionTerminalEid(manageSubscriptionRequest.companionTerminalEid())
+ .setTerminalIccid(manageSubscriptionRequest.terminalIccid())
+ .setTerminalEid(manageSubscriptionRequest.terminalEid())
+ .setTargetTerminalId(manageSubscriptionRequest.targetTerminalId())
+ // non TS.43 standard support
+ .setTargetTerminalIds(manageSubscriptionRequest.targetTerminalIds())
+ .setTargetTerminalIccid(manageSubscriptionRequest.targetTerminalIccid())
+ .setTargetTerminalEid(manageSubscriptionRequest.targetTerminalEid())
+ // non TS.43 standard support
+ .setTargetTerminalSerialNumber(
+ manageSubscriptionRequest.targetTerminalSerialNumber())
+ // non TS.43 standard support
+ .setTargetTerminalModel(manageSubscriptionRequest.targetTerminalModel())
+ .setOldTerminalId(manageSubscriptionRequest.oldTerminalId())
+ .setOldTerminalIccid(manageSubscriptionRequest.oldTerminalIccid())
+ .build();
+
+ String rawXml;
+ try {
+ rawXml =
+ mServiceEntitlement.performEsimOdsa(
+ manageSubscriptionRequest.appId(), request, operation);
+ } catch (ServiceEntitlementException e) {
+ Log.w(TAG, "manageSubscription: Failed to perform ODSA operation. e=" + e);
+ throw e;
+ }
+
+ // Build the response of manage subscription operation. Refer to GSMA Service Entitlement
+ // Configuration section 6.5.3.
+ ManageSubscriptionResponse.Builder responseBuilder = ManageSubscriptionResponse.builder();
+
+ Ts43XmlDoc ts43XmlDoc;
+ try {
+ ts43XmlDoc = new Ts43XmlDoc(rawXml);
+ processGeneralResult(ts43XmlDoc, responseBuilder);
+ } catch (MalformedURLException e) {
+ throw new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE,
+ "manageSubscription: Malformed URL " + rawXml);
+ }
+
+ int subscriptionResult = ManageSubscriptionResponse.SUBSCRIPTION_RESULT_UNKNOWN;
+
+ // Parse subscription result.
+ String subscriptionResultString =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.SUBSCRIPTION_RESULT);
+
+ if (!TextUtils.isEmpty(subscriptionResultString)) {
+ switch (subscriptionResultString) {
+ case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET:
+ subscriptionResult =
+ ManageSubscriptionResponse.SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET;
+
+ String subscriptionServiceURLString =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.SUBSCRIPTION_SERVICE_URL);
+
+ if (!TextUtils.isEmpty(subscriptionServiceURLString)) {
+ try {
+ responseBuilder.setSubscriptionServiceUrl(
+ new URL(subscriptionServiceURLString));
+
+ String subscriptionServiceUserDataString =
+ ts43XmlDoc.get(
+ ImmutableList.of(
+ Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.SUBSCRIPTION_SERVICE_USER_DATA);
+ if (!TextUtils.isEmpty(subscriptionServiceUserDataString)) {
+ responseBuilder.setSubscriptionServiceUserData(
+ subscriptionServiceUserDataString);
+ }
+
+ String subscriptionServiceContentsTypeString =
+ ts43XmlDoc.get(
+ ImmutableList.of(
+ Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.SUBSCRIPTION_SERVICE_CONTENTS_TYPE);
+ if (!TextUtils.isEmpty(subscriptionServiceContentsTypeString)) {
+ int contentsType = HttpConstants.ContentType.UNKNOWN;
+ switch (subscriptionServiceContentsTypeString) {
+ case Ts43XmlDoc.ParmValues.CONTENTS_TYPE_XML:
+ contentsType = HttpConstants.ContentType.XML;
+ break;
+ case Ts43XmlDoc.ParmValues.CONTENTS_TYPE_JSON:
+ contentsType = HttpConstants.ContentType.JSON;
+ break;
+ }
+ responseBuilder.setSubscriptionServiceContentsType(contentsType);
+ }
+ } catch (MalformedURLException e) {
+ Log.w(TAG, "Malformed URL received. " + subscriptionServiceURLString);
+ }
+ }
+ break;
+ case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_DOWNLOAD_PROFILE:
+ subscriptionResult =
+ ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DOWNLOAD_PROFILE;
+ DownloadInfo downloadInfo =
+ parseDownloadInfo(
+ ImmutableList.of(
+ Ts43XmlDoc.CharacteristicType.APPLICATION,
+ Ts43XmlDoc.CharacteristicType.DOWNLOAD_INFO),
+ ts43XmlDoc);
+ if (downloadInfo != null) {
+ responseBuilder.setDownloadInfo(downloadInfo);
+ }
+ break;
+ case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_DONE:
+ subscriptionResult = ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DONE;
+ break;
+ case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_DELAYED_DOWNLOAD:
+ subscriptionResult =
+ ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DELAYED_DOWNLOAD;
+ break;
+ case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_DISMISS:
+ subscriptionResult = ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DISMISS;
+ break;
+ case Ts43XmlDoc.ParmValues.SUBSCRIPTION_RESULT_DELETE_PROFILE_IN_USE:
+ subscriptionResult =
+ ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DELETE_PROFILE_IN_USE;
+ break;
+ }
+ }
+
+ responseBuilder.setSubscriptionResult(subscriptionResult);
+ return responseBuilder.build();
+ }
+
+ /**
+ * To activate/deactivate the service on the primary or companion device as described in GSMA
+ * Service Entitlement Configuration section 6.2 and 6.5.4. This is an optional operation.
+ *
+ * @param manageServiceRequest The manage service request.
+ * @return The response of manage service request.
+ * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
+ * error from the server, the error code can be retrieved by
+ * {@link ServiceEntitlementException#getHttpStatus()}
+ */
+ @NonNull
+ public ManageServiceResponse manageService(@NonNull ManageServiceRequest manageServiceRequest)
+ throws ServiceEntitlementException {
+ Objects.requireNonNull(manageServiceRequest);
+
+ ServiceEntitlementRequest.Builder builder =
+ ServiceEntitlementRequest.builder()
+ .setEntitlementVersion(mEntitlementVersion)
+ .setTerminalId(mImei);
+
+ if (mTokenType == TOKEN_TYPE_NORMAL) {
+ builder.setAuthenticationToken(mAuthToken);
+ } else if (mTokenType == TOKEN_TYPE_TEMPORARY) {
+ builder.setTemporaryToken(mTemporaryToken);
+ }
+
+ ServiceEntitlementRequest request = builder.build();
+
+ EsimOdsaOperation operation =
+ EsimOdsaOperation.builder()
+ .setOperation(EsimOdsaOperation.OPERATION_MANAGE_SERVICE)
+ .setOperationType(manageServiceRequest.operationType())
+ .setCompanionTerminalId(manageServiceRequest.companionTerminalId())
+ .setCompanionTerminalVendor(manageServiceRequest.companionTerminalVendor())
+ .setCompanionTerminalModel(manageServiceRequest.companionTerminalModel())
+ .setCompanionTerminalSoftwareVersion(
+ manageServiceRequest.companionTerminalSoftwareVersion())
+ .setCompanionTerminalFriendlyName(
+ manageServiceRequest.companionTerminalFriendlyName())
+ .setCompanionTerminalService(
+ manageServiceRequest.companionTerminalService())
+ .setCompanionTerminalIccid(manageServiceRequest.companionTerminalIccid())
+ .build();
+
+ String rawXml;
+ try {
+ rawXml =
+ mServiceEntitlement.performEsimOdsa(manageServiceRequest.appId(), request,
+ operation);
+ } catch (ServiceEntitlementException e) {
+ Log.w(TAG, "manageService: Failed to perform ODSA operation. e=" + e);
+ throw e;
+ }
+
+ // Build the response of manage service operation. Refer to GSMA Service Entitlement
+ // Configuration section 6.5.4.
+ ManageServiceResponse.Builder responseBuilder = ManageServiceResponse.builder();
+
+ Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml);
+
+ try {
+ processGeneralResult(ts43XmlDoc, responseBuilder);
+ } catch (MalformedURLException e) {
+ throw new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE,
+ "manageService: Malformed URL " + rawXml);
+ }
+
+ // Parse service status.
+ String serviceStatusString =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.SERVICE_STATUS);
+
+ if (!TextUtils.isEmpty(serviceStatusString)) {
+ responseBuilder.setServiceStatus(getServiceStatusFromString(serviceStatusString));
+ }
+
+ return responseBuilder.build();
+ }
+
+ /**
+ * To provide service related data about a primary or companion device as described in GSMA
+ * Service Entitlement Configuration section 6.2 and 6.5.5.
+ *
+ * @param acquireConfigurationRequest The acquire configuration request.
+ * @return The response of acquire configuration request.
+ * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
+ * error from the server, the error code can be retrieved by
+ * {@link ServiceEntitlementException#getHttpStatus()}
+ */
+ @NonNull
+ public AcquireConfigurationResponse acquireConfiguration(
+ @NonNull AcquireConfigurationRequest acquireConfigurationRequest)
+ throws ServiceEntitlementException {
+ Objects.requireNonNull(acquireConfigurationRequest);
+
+ ServiceEntitlementRequest.Builder builder = ServiceEntitlementRequest.builder()
+ .setEntitlementVersion(mEntitlementVersion)
+ .setTerminalId(mImei)
+ .setAuthenticationToken(mAuthToken);
+
+ String notificationToken = acquireConfigurationRequest.notificationToken();
+ if (!TextUtils.isEmpty(notificationToken)) {
+ builder.setNotificationToken(notificationToken);
+ }
+ int notificationAction = acquireConfigurationRequest.notificationAction();
+ if (Ts43Constants.isValidNotificationAction(notificationAction)) {
+ builder.setNotificationAction(notificationAction);
+ }
+
+ ServiceEntitlementRequest request = builder.build();
+
+ EsimOdsaOperation operation =
+ EsimOdsaOperation.builder()
+ .setOperation(EsimOdsaOperation.OPERATION_ACQUIRE_CONFIGURATION)
+ .setCompanionTerminalId(acquireConfigurationRequest.companionTerminalId())
+ .setCompanionTerminalIccid(
+ acquireConfigurationRequest.companionTerminalIccid())
+ .setCompanionTerminalEid(acquireConfigurationRequest.companionTerminalEid())
+ .setTerminalIccid(acquireConfigurationRequest.terminalIccid())
+ .setTerminalEid(acquireConfigurationRequest.terminalEid())
+ .setTargetTerminalId(acquireConfigurationRequest.targetTerminalId())
+ .setTargetTerminalIccid(acquireConfigurationRequest.targetTerminalIccid())
+ .setTargetTerminalEid(acquireConfigurationRequest.targetTerminalEid())
+ .build();
+
+ String rawXml;
+ try {
+ rawXml =
+ mServiceEntitlement.performEsimOdsa(
+ acquireConfigurationRequest.appId(), request, operation);
+ } catch (ServiceEntitlementException e) {
+ Log.w(TAG, "acquireConfiguration: Failed to perform ODSA operation. e=" + e);
+ throw e;
+ }
+
+ AcquireConfigurationResponse.Builder responseBuilder =
+ AcquireConfigurationResponse.builder();
+ AcquireConfigurationResponse.Configuration.Builder configBuilder =
+ AcquireConfigurationResponse.Configuration.builder();
+
+ Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml);
+
+ try {
+ processGeneralResult(ts43XmlDoc, responseBuilder);
+ } catch (MalformedURLException e) {
+ throw new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE,
+ "manageSubscription: Malformed URL " + rawXml);
+ }
+
+ // Parse service status.
+ String serviceStatusString =
+ ts43XmlDoc.get(
+ ImmutableList.of(
+ Ts43XmlDoc.CharacteristicType.APPLICATION,
+ Ts43XmlDoc.CharacteristicType.PRIMARY_CONFIGURATION),
+ Ts43XmlDoc.Parm.SERVICE_STATUS);
+
+ if (!TextUtils.isEmpty(serviceStatusString)) {
+ configBuilder.setServiceStatus(getServiceStatusFromString(serviceStatusString));
+ }
+
+ // Parse ICCID
+ String iccIdString =
+ ts43XmlDoc.get(
+ ImmutableList.of(
+ Ts43XmlDoc.CharacteristicType.APPLICATION,
+ Ts43XmlDoc.CharacteristicType.PRIMARY_CONFIGURATION),
+ Ts43XmlDoc.Parm.ICCID);
+
+ if (!TextUtils.isEmpty(iccIdString)) {
+ configBuilder.setIccid(iccIdString);
+ }
+
+ // Parse polling interval
+ String pollingIntervalString =
+ ts43XmlDoc.get(
+ ImmutableList.of(
+ Ts43XmlDoc.CharacteristicType.APPLICATION,
+ Ts43XmlDoc.CharacteristicType.PRIMARY_CONFIGURATION),
+ Ts43XmlDoc.Parm.POLLING_INTERVAL);
+
+ if (!TextUtils.isEmpty(pollingIntervalString)) {
+ try {
+ configBuilder.setPollingInterval(Integer.parseInt(pollingIntervalString));
+ } catch (NumberFormatException e) {
+ Log.w(
+ TAG, "acquireConfiguration: Failed to parse polling interval "
+ + pollingIntervalString);
+ }
+ }
+
+ // Parse download info
+ DownloadInfo downloadInfo =
+ parseDownloadInfo(
+ ImmutableList.of(
+ Ts43XmlDoc.CharacteristicType.APPLICATION,
+ Ts43XmlDoc.CharacteristicType.PRIMARY_CONFIGURATION,
+ Ts43XmlDoc.CharacteristicType.DOWNLOAD_INFO),
+ ts43XmlDoc);
+ if (downloadInfo != null) {
+ configBuilder.setDownloadInfo(downloadInfo);
+ }
+
+ // TODO: Support different type of configuration.
+ configBuilder.setType(
+ AcquireConfigurationResponse.Configuration.CONFIGURATION_TYPE_PRIMARY);
+
+ // TODO: Support multiple configurations.
+ return responseBuilder.setConfigurations(ImmutableList.of(configBuilder.build())).build();
+ }
+
+ /**
+ * Acquire available mobile plans to be offered by the MNO to a specific user or MDM as
+ * described in GSMA Service Entitlement Configuration section 6.2 and 6.5.6.
+ *
+ * @return List of mobile plans. Empty list if not available.
+ * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
+ * error from the server, the error code can be retrieved by
+ * {@link ServiceEntitlementException#getHttpStatus()}
+ */
+ @NonNull
+ public List<PlanOffer> acquirePlans() throws ServiceEntitlementException {
+ return Collections.emptyList();
+ }
+
+ /**
+ * To request a temporary token used to establish trust between ECS and the client as described
+ * in GSMA Service Entitlement Configuration section 6.2 and 6.5.7.
+ *
+ * @param acquireTemporaryTokenRequest The acquire temporary token request.
+ * @return The temporary token response.
+ * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
+ * error from the server, the error code can be retrieved by
+ * {@link ServiceEntitlementException#getHttpStatus()}
+ */
+ @NonNull
+ @SuppressWarnings("AndroidJdkLibsChecker") // java.time.Instant
+ public AcquireTemporaryTokenResponse acquireTemporaryToken(
+ @NonNull AcquireTemporaryTokenRequest acquireTemporaryTokenRequest)
+ throws ServiceEntitlementException {
+ Objects.requireNonNull(acquireTemporaryTokenRequest);
+
+ ServiceEntitlementRequest request =
+ ServiceEntitlementRequest.builder()
+ .setEntitlementVersion(mEntitlementVersion)
+ .setTerminalId(mImei)
+ .setAuthenticationToken(mAuthToken)
+ .build();
+
+ EsimOdsaOperation operation =
+ EsimOdsaOperation.builder()
+ .setOperation(EsimOdsaOperation.OPERATION_ACQUIRE_TEMPORARY_TOKEN)
+ .setOperationTargets(acquireTemporaryTokenRequest.operationTargets())
+ .setCompanionTerminalId(acquireTemporaryTokenRequest.companionTerminalId())
+ .build();
+
+ String rawXml;
+ try {
+ rawXml =
+ mServiceEntitlement.performEsimOdsa(
+ acquireTemporaryTokenRequest.appId(), request, operation);
+ } catch (ServiceEntitlementException e) {
+ Log.w(TAG, "acquireTemporaryToken: Failed to perform ODSA operation. e=" + e);
+ throw e;
+ }
+
+ Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml);
+ AcquireTemporaryTokenResponse.Builder responseBuilder =
+ AcquireTemporaryTokenResponse.builder();
+
+ try {
+ processGeneralResult(ts43XmlDoc, responseBuilder);
+ } catch (MalformedURLException e) {
+ throw new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE,
+ "AcquireTemporaryTokenResponse: Malformed URL " + rawXml);
+ }
+
+ // Parse the operation targets.
+ String operationTargets =
+ Strings.nullToEmpty(
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.OPERATION_TARGETS));
+
+ if (operationTargets != null) {
+ List<String> operationTargetsList = Arrays.asList(operationTargets.split("\\s*,\\s*"));
+ responseBuilder.setOperationTargets(ImmutableList.copyOf(operationTargetsList));
+ }
+
+ // Parse the temporary token
+ String temporaryToken =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.TEMPORARY_TOKEN);
+
+ if (temporaryToken == null) {
+ throw new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_TOKEN_NOT_AVAILABLE,
+ "temporary token is not available.");
+ }
+
+ responseBuilder.setTemporaryToken(temporaryToken);
+
+ String temporaryTokenExpiry =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.TEMPORARY_TOKEN_EXPIRY);
+
+ // Parse the token expiration time.
+ Instant expiry;
+ try {
+ expiry = OffsetDateTime.parse(temporaryTokenExpiry).toInstant();
+ responseBuilder.setTemporaryTokenExpiry(expiry);
+ } catch (DateTimeParseException e) {
+ Log.w(TAG, "Failed to parse temporaryTokenExpiry: " + temporaryTokenExpiry);
+ }
+
+ return responseBuilder.build();
+ }
+
+ /**
+ * Get the phone number as described in GSMA Service Entitlement Configuration section 6.2 and
+ * 6.5.8.
+ *
+ * @param getPhoneNumberRequest The get phone number request.
+ * @return The phone number response from the network.
+ * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
+ * error from the server, the error code can be retrieved by
+ * {@link ServiceEntitlementException#getHttpStatus()}
+ */
+ @NonNull
+ public GetPhoneNumberResponse getPhoneNumber(
+ @NonNull GetPhoneNumberRequest getPhoneNumberRequest)
+ throws ServiceEntitlementException {
+ ServiceEntitlementRequest.Builder builder =
+ ServiceEntitlementRequest.builder()
+ .setEntitlementVersion(mEntitlementVersion);
+
+ if (!TextUtils.isEmpty(getPhoneNumberRequest.terminalId())) {
+ builder.setTerminalId(getPhoneNumberRequest.terminalId());
+ } else {
+ builder.setTerminalId(mImei);
+ }
+
+ if (mTokenType == TOKEN_TYPE_NORMAL) {
+ builder.setAuthenticationToken(mAuthToken);
+ } else if (mTokenType == TOKEN_TYPE_TEMPORARY) {
+ builder.setTemporaryToken(mTemporaryToken);
+ }
+
+ ServiceEntitlementRequest request = builder.build();
+
+ EsimOdsaOperation operation =
+ EsimOdsaOperation.builder()
+ .setOperation(EsimOdsaOperation.OPERATION_GET_PHONE_NUMBER)
+ .build();
+
+ String rawXml;
+ try {
+ rawXml =
+ mServiceEntitlement.performEsimOdsa(
+ Ts43Constants.APP_PHONE_NUMBER_INFORMATION, request, operation);
+ } catch (ServiceEntitlementException e) {
+ Log.w(TAG, "getPhoneNumber: Failed to perform ODSA operation. e=" + e);
+ throw e;
+ }
+
+ // Build the response of get phone number operation. Refer to GSMA Service Entitlement
+ // Configuration section 6.5.8.
+ GetPhoneNumberResponse.Builder responseBuilder = GetPhoneNumberResponse.builder();
+
+ Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml);
+
+ try {
+ processGeneralResult(ts43XmlDoc, responseBuilder);
+ } catch (MalformedURLException e) {
+ throw new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE,
+ "getPhoneNumber: Malformed URL " + rawXml);
+ }
+
+ // Parse msisdn.
+ String msisdn =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.MSISDN);
+
+ if (!TextUtils.isEmpty(msisdn)) {
+ responseBuilder.setMsisdn(msisdn);
+ }
+
+ return responseBuilder.build();
+ }
+
+ /**
+ * Parse the download info from {@link ManageSubscriptionResponse}.
+ *
+ * @param characteristics The XML nodes to search activation code.
+ * @param ts43XmlDoc The XML format http response.
+ * @return The download info.
+ */
+ @Nullable
+ @SuppressWarnings("AndroidJdkLibsChecker") // java.util.Base64
+ private DownloadInfo parseDownloadInfo(
+ @NonNull ImmutableList<String> characteristics, @NonNull Ts43XmlDoc ts43XmlDoc) {
+ String activationCode =
+ Strings.nullToEmpty(
+ ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.PROFILE_ACTIVATION_CODE));
+ String smdpAddress =
+ Strings.nullToEmpty(
+ ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.PROFILE_SMDP_ADDRESS));
+ String iccid =
+ Strings.nullToEmpty(ts43XmlDoc.get(characteristics, Ts43XmlDoc.Parm.PROFILE_ICCID));
+
+ // DownloadInfo should contain either activationCode or smdpAddress + iccid
+ if (!activationCode.isEmpty()) {
+ // decode the activation code, which is in base64 format
+ try {
+ activationCode = new String(Base64.getDecoder().decode(activationCode));
+ } catch (IllegalArgumentException e) {
+ Log.w(TAG, "Failed to decode the activation code " + activationCode);
+ return null;
+ }
+ return DownloadInfo.builder()
+ .setProfileActivationCode(activationCode)
+ .setProfileIccid(iccid)
+ .build();
+ } else if (!smdpAddress.isEmpty() && !iccid.isEmpty()) {
+ return DownloadInfo.builder()
+ .setProfileIccid(iccid)
+ .setProfileSmdpAddresses(
+ ImmutableList.copyOf(Arrays.asList(smdpAddress.split("\\s*,\\s*"))))
+ .build();
+ } else {
+ Log.w(
+ TAG,
+ "Failed to parse download info. activationCode="
+ + activationCode
+ + ", smdpAddress="
+ + smdpAddress
+ + ", iccid="
+ + iccid);
+ return null;
+ }
+ }
+
+ /**
+ * Process the common ODSA result from HTTP response.
+ *
+ * @param ts43XmlDoc The TS.43 ODSA operation response in XLM format.
+ * @param builder The response builder.
+ * @throws MalformedURLException when HTTP response is not well formatted.
+ */
+ private void processGeneralResult(
+ @NonNull Ts43XmlDoc ts43XmlDoc, @NonNull OdsaResponse.Builder builder)
+ throws MalformedURLException {
+ // Now start to parse the result from HTTP response.
+ // Parse the operation result.
+ String operationResult =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.OPERATION_RESULT);
+
+ builder.setOperationResult(EsimOdsaOperation.OPERATION_RESULT_UNKNOWN);
+ if (!TextUtils.isEmpty(operationResult)) {
+ switch (operationResult) {
+ case Ts43XmlDoc.ParmValues.OPERATION_RESULT_SUCCESS:
+ builder.setOperationResult(EsimOdsaOperation.OPERATION_RESULT_SUCCESS);
+ break;
+ case Ts43XmlDoc.ParmValues.OPERATION_RESULT_ERROR_GENERAL:
+ builder.setOperationResult(EsimOdsaOperation.OPERATION_RESULT_ERROR_GENERAL);
+ break;
+ case Ts43XmlDoc.ParmValues.OPERATION_RESULT_ERROR_INVALID_OPERATION:
+ builder.setOperationResult(
+ EsimOdsaOperation.OPERATION_RESULT_ERROR_INVALID_OPERATION);
+ break;
+ case Ts43XmlDoc.ParmValues.OPERATION_RESULT_ERROR_INVALID_PARAMETER:
+ builder.setOperationResult(
+ EsimOdsaOperation.OPERATION_RESULT_ERROR_INVALID_PARAMETER);
+ break;
+ case Ts43XmlDoc.ParmValues.OPERATION_RESULT_WARNING_NOT_SUPPORTED_OPERATION:
+ builder.setOperationResult(
+ EsimOdsaOperation.OPERATION_RESULT_WARNING_NOT_SUPPORTED_OPERATION);
+ break;
+ }
+ }
+
+ // Parse the general error URL
+ String generalErrorUrl =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.GENERAL_ERROR_URL);
+ if (!TextUtils.isEmpty(generalErrorUrl)) {
+ builder.setGeneralErrorUrl(new URL(generalErrorUrl));
+ }
+
+ // Parse the general error URL user data
+ String generalErrorUserData =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.APPLICATION),
+ Ts43XmlDoc.Parm.GENERAL_ERROR_USER_DATA);
+ if (!TextUtils.isEmpty(generalErrorUserData)) {
+ builder.setGeneralErrorUserData(generalErrorUserData);
+ }
+
+ // Parse the token for next operation.
+ String token =
+ ts43XmlDoc.get(
+ ImmutableList.of(Ts43XmlDoc.CharacteristicType.TOKEN),
+ Ts43XmlDoc.Parm.TOKEN);
+ if (!TextUtils.isEmpty(token)) {
+ // Some servers issue the new token in operation result for next operation to use.
+ // We need to save it.
+ mAuthToken = token;
+ Log.d(TAG, "processGeneralResult: Token replaced.");
+ }
+ }
+
+ /**
+ * Get the service status from string as described in GSMA Service Entitlement Configuration
+ * section 6.5.4.
+ *
+ * @param serviceStatusString Service status in string format defined in GSMA Service
+ * Entitlement Configuration section 6.5.4.
+ * @return The converted service status. {@link EsimOdsaOperation#SERVICE_STATUS_UNKNOWN} if not
+ * able to convert.
+ */
+ @OdsaServiceStatus
+ private int getServiceStatusFromString(@NonNull String serviceStatusString) {
+ switch (serviceStatusString) {
+ case Ts43XmlDoc.ParmValues.SERVICE_STATUS_ACTIVATED:
+ return EsimOdsaOperation.SERVICE_STATUS_ACTIVATED;
+ case Ts43XmlDoc.ParmValues.SERVICE_STATUS_ACTIVATING:
+ return EsimOdsaOperation.SERVICE_STATUS_ACTIVATING;
+ case Ts43XmlDoc.ParmValues.SERVICE_STATUS_DEACTIVATED:
+ return EsimOdsaOperation.SERVICE_STATUS_DEACTIVATED;
+ case Ts43XmlDoc.ParmValues.SERVICE_STATUS_DEACTIVATED_NO_REUSE:
+ return EsimOdsaOperation.SERVICE_STATUS_DEACTIVATED_NO_REUSE;
+ }
+ return EsimOdsaOperation.SERVICE_STATUS_UNKNOWN;
+ }
+} \ No newline at end of file
diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
index 2847c16..3029200 100644
--- a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
+++ b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
@@ -16,15 +16,19 @@
package com.android.libraries.entitlement.eapaka;
+import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_EAP_AKA_FAILURE;
import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE;
+import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_JSON_COMPOSE_FAILURE;
import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE;
import android.content.Context;
+import android.content.pm.PackageInfo;
import android.net.Uri;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -33,6 +37,7 @@ import com.android.libraries.entitlement.EsimOdsaOperation;
import com.android.libraries.entitlement.ServiceEntitlementException;
import com.android.libraries.entitlement.ServiceEntitlementRequest;
import com.android.libraries.entitlement.http.HttpClient;
+import com.android.libraries.entitlement.http.HttpConstants.ContentType;
import com.android.libraries.entitlement.http.HttpConstants.RequestMethod;
import com.android.libraries.entitlement.http.HttpRequest;
import com.android.libraries.entitlement.http.HttpResponse;
@@ -49,6 +54,8 @@ public class EapAkaApi {
private static final String TAG = "ServiceEntitlement";
public static final String EAP_CHALLENGE_RESPONSE = "eap-relay-packet";
+ private static final String CONTENT_TYPE_EAP_RELAY_JSON =
+ "application/vnd.gsma.eap-relay.v1.0+json";
private static final String VERS = "vers";
private static final String ENTITLEMENT_VERSION = "entitlement_version";
@@ -83,21 +90,35 @@ public class EapAkaApi {
private static final String TERMINAL_EID = "terminal_eid";
private static final String TARGET_TERMINAL_ID = "target_terminal_id";
+ // Non-standard params for Korean carriers
+ private static final String TARGET_TERMINAL_IDS = "target_terminal_imeis";
private static final String TARGET_TERMINAL_ICCID = "target_terminal_iccid";
private static final String TARGET_TERMINAL_EID = "target_terminal_eid";
+ // Non-standard params for Korean carriers
+ private static final String TARGET_TERMINAL_SERIAL_NUMBER = "target_terminal_sn";
+ // Non-standard params for Korean carriers
+ private static final String TARGET_TERMINAL_MODEL = "target_terminal_model";
private static final String OLD_TERMINAL_ID = "old_terminal_id";
private static final String OLD_TERMINAL_ICCID = "old_terminal_iccid";
- private static final String NETWORK_IDENTIFIER = "network_identifier";
+ private static final String BOOST_TYPE = "boost_type";
- // In case of EAP-AKA synchronization failure, we try to recover for at most two times.
- private static final int FOLLOW_SYNC_FAILURE_MAX_COUNT = 2;
+ // In case of EAP-AKA synchronization failure or another challenge, we try to authenticate for
+ // at most three times.
+ private static final int MAX_EAP_AKA_ATTEMPTS = 3;
+
+ // Max TERMINAL_* string length according to GSMA RCC.14 section 2.4
+ private static final int MAX_TERMINAL_VENDOR_LENGTH = 4;
+ private static final int MAX_TERMINAL_MODEL_LENGTH = 10;
+ private static final int MAX_TERMINAL_SOFTWARE_VERSION_LENGTH = 20;
private final Context mContext;
private final int mSimSubscriptionId;
private final HttpClient mHttpClient;
private final String mBypassEapAkaResponse;
+ private final String mAppVersion;
+ private final TelephonyManager mTelephonyManager;
public EapAkaApi(
Context context,
@@ -117,40 +138,89 @@ public class EapAkaApi {
this.mSimSubscriptionId = simSubscriptionId;
this.mHttpClient = httpClient;
this.mBypassEapAkaResponse = bypassEapAkaResponse;
+ this.mAppVersion = getAppVersion(context);
+ this.mTelephonyManager =
+ mContext.getSystemService(TelephonyManager.class)
+ .createForSubscriptionId(mSimSubscriptionId);
}
/**
- * Retrieves raw entitlement configuration doc though EAP-AKA authentication.
+ * Retrieves HTTP response with the entitlement configuration doc though EAP-AKA authentication.
*
* <p>Implementation based on GSMA TS.43-v5.0 2.6.1.
*
* @throws ServiceEntitlementException when getting an unexpected http response.
*/
- @Nullable
- public String queryEntitlementStatus(ImmutableList<String> appIds,
- CarrierConfig carrierConfig, ServiceEntitlementRequest request)
+ @NonNull
+ public HttpResponse queryEntitlementStatus(
+ ImmutableList<String> appIds,
+ CarrierConfig carrierConfig,
+ ServiceEntitlementRequest request)
throws ServiceEntitlementException {
- Uri.Builder urlBuilder = Uri.parse(carrierConfig.serverUrl()).buildUpon();
- appendParametersForServiceEntitlementRequest(urlBuilder, appIds, request);
+ Uri.Builder urlBuilder = null;
+ JSONObject postData = null;
+ if (carrierConfig.useHttpPost()) {
+ postData = new JSONObject();
+ appendParametersForAuthentication(postData, request);
+ appendParametersForServiceEntitlementRequest(postData, appIds, request);
+ } else {
+ urlBuilder = Uri.parse(carrierConfig.serverUrl()).buildUpon();
+ appendParametersForAuthentication(urlBuilder, request);
+ appendParametersForServiceEntitlementRequest(urlBuilder, appIds, request);
+ }
+
if (!TextUtils.isEmpty(request.authenticationToken())) {
// Fast Re-Authentication flow with pre-existing auth token
Log.d(TAG, "Fast Re-Authentication");
- return httpGet(
- urlBuilder.toString(), carrierConfig, request.acceptContentType()).body();
+ return carrierConfig.useHttpPost()
+ ? httpPost(
+ postData,
+ carrierConfig,
+ request.acceptContentType(),
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion())
+ : httpGet(
+ urlBuilder.toString(),
+ carrierConfig,
+ request.acceptContentType(),
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion());
} else {
// Full Authentication flow
Log.d(TAG, "Full Authentication");
HttpResponse challengeResponse =
- httpGet(
- urlBuilder.toString(),
- carrierConfig,
- ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
+ carrierConfig.useHttpPost()
+ ? httpPost(
+ postData,
+ carrierConfig,
+ CONTENT_TYPE_EAP_RELAY_JSON,
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion())
+ : httpGet(
+ urlBuilder.toString(),
+ carrierConfig,
+ CONTENT_TYPE_EAP_RELAY_JSON,
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion());
+ String eapAkaChallenge = getEapAkaChallenge(challengeResponse);
+ if (eapAkaChallenge == null) {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE,
+ "Failed to parse EAP-AKA challenge: " + challengeResponse.body());
+ }
return respondToEapAkaChallenge(
carrierConfig,
- challengeResponse,
- FOLLOW_SYNC_FAILURE_MAX_COUNT,
- request.acceptContentType())
- .body();
+ eapAkaChallenge,
+ challengeResponse.cookies(),
+ MAX_EAP_AKA_ATTEMPTS,
+ request.acceptContentType(),
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion());
}
}
@@ -158,62 +228,105 @@ public class EapAkaApi {
* Sends a follow-up HTTP request to the HTTP {@code response} using the same cookie, and
* returns the follow-up HTTP response.
*
- * <p>The {@code response} should contain a EAP-AKA challenge from server, and the
- * follow-up request could contain:
+ * <p>The {@code eapAkaChallenge} should be the EAP-AKA challenge from server, and the follow-up
+ * request could contain:
*
* <ul>
- * <li>The EAP-AKA response message, and the follow-up response should contain the
- * service entitlement configuration; or,
- * <li>The EAP-AKA synchronization failure message, and the follow-up response should
- * contain the new EAP-AKA challenge. Then this method calls itself to follow-up
- * the new challenge and return a new response, if {@code followSyncFailureCount}
- * is greater than zero. When this method call itself {@code followSyncFailureCount} is
- * reduced by one to prevent infinite loop (unlikely in practice, but just in case).
+ * <li>The EAP-AKA response message, and the follow-up response should contain the service
+ * entitlement configuration, or another EAP-AKA challenge in which case the method calls
+ * if {@code remainingAttempts} is greater than zero (If {@code remainingAttempts} reaches
+ * 0, the method will throw ServiceEntitlementException) ; or
+ * <li>The EAP-AKA synchronization failure message, and the follow-up response should contain
+ * the new EAP-AKA challenge. Then this method calls itself to follow-up the new challenge
+ * and return a new response, as long as {@code remainingAttempts} is greater than zero.
* </ul>
*
- * @param response Challenge response from server which its content type is JSON
+ * @return Challenge response from server whose content type is JSON
*/
+ @NonNull
private HttpResponse respondToEapAkaChallenge(
CarrierConfig carrierConfig,
- HttpResponse response,
- int followSyncFailureCount,
- String contentType)
+ String eapAkaChallenge,
+ ImmutableList<String> cookies,
+ int remainingAttempts,
+ String acceptContentType,
+ String terminalVendor,
+ String terminalModel,
+ String terminalSoftwareVersion)
throws ServiceEntitlementException {
- String eapAkaChallenge;
- try {
- eapAkaChallenge = new JSONObject(response.body()).getString(EAP_CHALLENGE_RESPONSE);
- } catch (JSONException jsonException) {
- throw new ServiceEntitlementException(
- ERROR_MALFORMED_HTTP_RESPONSE, "Failed to parse json object", jsonException);
- }
if (!mBypassEapAkaResponse.isEmpty()) {
return challengeResponse(
- mBypassEapAkaResponse,
- carrierConfig,
- response.cookies(),
- contentType);
+ mBypassEapAkaResponse,
+ carrierConfig,
+ cookies,
+ CONTENT_TYPE_EAP_RELAY_JSON + ", " + acceptContentType,
+ terminalVendor,
+ terminalModel,
+ terminalSoftwareVersion);
}
+
EapAkaChallenge challenge = EapAkaChallenge.parseEapAkaChallenge(eapAkaChallenge);
EapAkaResponse eapAkaResponse =
EapAkaResponse.respondToEapAkaChallenge(mContext, mSimSubscriptionId, challenge);
- // This could be a successful authentication, or synchronization failure.
- if (eapAkaResponse.response() != null) { // successful authentication
- return challengeResponse(
+ // This could be a successful authentication, another challenge, or synchronization failure.
+ if (eapAkaResponse.response() != null) {
+ HttpResponse response =
+ challengeResponse(
eapAkaResponse.response(),
carrierConfig,
- response.cookies(),
- contentType);
+ cookies,
+ CONTENT_TYPE_EAP_RELAY_JSON + ", " + acceptContentType,
+ terminalVendor,
+ terminalModel,
+ terminalSoftwareVersion);
+ String nextEapAkaChallenge = getEapAkaChallenge(response);
+ // successful authentication
+ if (nextEapAkaChallenge == null) {
+ return response;
+ }
+ // another challenge
+ Log.d(TAG, "Received another challenge");
+ if (remainingAttempts > 0) {
+ return respondToEapAkaChallenge(
+ carrierConfig,
+ nextEapAkaChallenge,
+ cookies,
+ remainingAttempts - 1,
+ acceptContentType,
+ terminalVendor,
+ terminalModel,
+ terminalSoftwareVersion);
+ } else {
+ throw new ServiceEntitlementException(
+ ERROR_EAP_AKA_FAILURE, "Unable to EAP-AKA authenticate");
+ }
} else if (eapAkaResponse.synchronizationFailureResponse() != null) {
Log.d(TAG, "synchronization failure");
HttpResponse newChallenge =
challengeResponse(
eapAkaResponse.synchronizationFailureResponse(),
carrierConfig,
- response.cookies(),
- ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
- if (followSyncFailureCount > 0) {
+ cookies,
+ CONTENT_TYPE_EAP_RELAY_JSON,
+ terminalVendor,
+ terminalModel,
+ terminalSoftwareVersion);
+ String nextEapAkaChallenge = getEapAkaChallenge(newChallenge);
+ if (nextEapAkaChallenge == null) {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE,
+ "Failed to parse EAP-AKA challenge: " + newChallenge.body());
+ }
+ if (remainingAttempts > 0) {
return respondToEapAkaChallenge(
- carrierConfig, newChallenge, followSyncFailureCount - 1, contentType);
+ carrierConfig,
+ nextEapAkaChallenge,
+ cookies,
+ remainingAttempts - 1,
+ acceptContentType,
+ terminalVendor,
+ terminalModel,
+ terminalSoftwareVersion);
} else {
throw new ServiceEntitlementException(
ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE,
@@ -224,13 +337,16 @@ public class EapAkaApi {
}
}
+ @NonNull
private HttpResponse challengeResponse(
String eapAkaChallengeResponse,
CarrierConfig carrierConfig,
ImmutableList<String> cookies,
- String contentType)
+ String acceptContentType,
+ String terminalVendor,
+ String terminalModel,
+ String terminalSoftwareVersion)
throws ServiceEntitlementException {
- Log.d(TAG, "challengeResponse");
JSONObject postData = new JSONObject();
try {
postData.put(EAP_CHALLENGE_RESPONSE, eapAkaChallengeResponse);
@@ -238,67 +354,174 @@ public class EapAkaApi {
throw new ServiceEntitlementException(
ERROR_MALFORMED_HTTP_RESPONSE, "Failed to put post data", jsonException);
}
- HttpRequest request =
- HttpRequest.builder()
- .setUrl(carrierConfig.serverUrl())
- .setRequestMethod(RequestMethod.POST)
- .setPostData(postData)
- .addRequestProperty(HttpHeaders.ACCEPT, contentType)
- .addRequestProperty(
- HttpHeaders.CONTENT_TYPE,
- ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON)
- .addRequestProperty(HttpHeaders.COOKIE, cookies)
- .setTimeoutInSec(carrierConfig.timeoutInSec())
- .setNetwork(carrierConfig.network())
- .build();
- return mHttpClient.request(request);
+ return httpPost(
+ postData,
+ carrierConfig,
+ acceptContentType,
+ terminalVendor,
+ terminalModel,
+ terminalSoftwareVersion,
+ CONTENT_TYPE_EAP_RELAY_JSON,
+ cookies);
}
/**
- * Retrieves raw doc of performing ODSA operations. For operation type, see {@link
+ * Retrieves HTTP response from performing ODSA operations. For operation type, see {@link
* EsimOdsaOperation}.
*
* <p>Implementation based on GSMA TS.43-v5.0 6.1.
*/
- public String performEsimOdsaOperation(String appId, CarrierConfig carrierConfig,
- ServiceEntitlementRequest request, EsimOdsaOperation odsaOperation)
+ @NonNull
+ public HttpResponse performEsimOdsaOperation(
+ String appId,
+ CarrierConfig carrierConfig,
+ ServiceEntitlementRequest request,
+ EsimOdsaOperation odsaOperation)
throws ServiceEntitlementException {
- Uri.Builder urlBuilder = Uri.parse(carrierConfig.serverUrl()).buildUpon();
- appendParametersForServiceEntitlementRequest(urlBuilder, ImmutableList.of(appId), request);
- appendParametersForEsimOdsaOperation(urlBuilder, odsaOperation);
+ Uri.Builder urlBuilder = null;
+ JSONObject postData = null;
+ if (carrierConfig.useHttpPost()) {
+ postData = new JSONObject();
+ appendParametersForAuthentication(postData, request);
+ appendParametersForServiceEntitlementRequest(
+ postData, ImmutableList.of(appId), request);
+ appendParametersForEsimOdsaOperation(postData, odsaOperation);
+ } else {
+ urlBuilder = Uri.parse(carrierConfig.serverUrl()).buildUpon();
+ appendParametersForAuthentication(urlBuilder, request);
+ appendParametersForServiceEntitlementRequest(
+ urlBuilder, ImmutableList.of(appId), request);
+ appendParametersForEsimOdsaOperation(urlBuilder, odsaOperation);
+ }
if (!TextUtils.isEmpty(request.authenticationToken())
|| !TextUtils.isEmpty(request.temporaryToken())) {
// Fast Re-Authentication flow with pre-existing auth token
Log.d(TAG, "Fast Re-Authentication");
- return httpGet(
- urlBuilder.toString(), carrierConfig, request.acceptContentType()).body();
+ return carrierConfig.useHttpPost()
+ ? httpPost(
+ postData,
+ carrierConfig,
+ request.acceptContentType(),
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion())
+ : httpGet(
+ urlBuilder.toString(),
+ carrierConfig,
+ request.acceptContentType(),
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion());
} else {
// Full Authentication flow
Log.d(TAG, "Full Authentication");
HttpResponse challengeResponse =
- httpGet(
- urlBuilder.toString(),
- carrierConfig,
- ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
+ carrierConfig.useHttpPost()
+ ? httpPost(
+ postData,
+ carrierConfig,
+ CONTENT_TYPE_EAP_RELAY_JSON,
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion())
+ : httpGet(
+ urlBuilder.toString(),
+ carrierConfig,
+ CONTENT_TYPE_EAP_RELAY_JSON,
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion());
+ String eapAkaChallenge = getEapAkaChallenge(challengeResponse);
+ if (eapAkaChallenge == null) {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE,
+ "Failed to parse EAP-AKA challenge: " + challengeResponse.body());
+ }
return respondToEapAkaChallenge(
carrierConfig,
- challengeResponse,
- FOLLOW_SYNC_FAILURE_MAX_COUNT,
- request.acceptContentType())
- .body();
+ eapAkaChallenge,
+ challengeResponse.cookies(),
+ MAX_EAP_AKA_ATTEMPTS,
+ request.acceptContentType(),
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion());
}
}
- private void appendParametersForServiceEntitlementRequest(
- Uri.Builder urlBuilder, ImmutableList<String> appIds,
- ServiceEntitlementRequest request) {
- TelephonyManager telephonyManager = mContext.getSystemService(
- TelephonyManager.class).createForSubscriptionId(mSimSubscriptionId);
+ /**
+ * Retrieves the endpoint for OpenID Connect(OIDC) authentication.
+ *
+ * <p>Implementation based on section 2.8.2 of TS.43
+ *
+ * <p>The user should call {@link #queryEntitlementStatusFromOidc(String, CarrierConfig,
+ * String)} with the authentication result to retrieve the service entitlement configuration.
+ */
+ @NonNull
+ public String acquireOidcAuthenticationEndpoint(
+ String appId, CarrierConfig carrierConfig, ServiceEntitlementRequest request)
+ throws ServiceEntitlementException {
+ Uri.Builder urlBuilder = null;
+ JSONObject postData = null;
+ if (carrierConfig.useHttpPost()) {
+ postData = new JSONObject();
+ appendParametersForServiceEntitlementRequest(
+ postData, ImmutableList.of(appId), request);
+ } else {
+ urlBuilder = Uri.parse(carrierConfig.serverUrl()).buildUpon();
+ appendParametersForServiceEntitlementRequest(
+ urlBuilder, ImmutableList.of(appId), request);
+ }
+
+ HttpResponse response =
+ carrierConfig.useHttpPost()
+ ? httpPost(
+ postData,
+ carrierConfig,
+ request.acceptContentType(),
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion())
+ : httpGet(
+ urlBuilder.toString(),
+ carrierConfig,
+ request.acceptContentType(),
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion());
+ return response.location();
+ }
+
+ /**
+ * Retrieves the HTTP response with the service entitlement configuration from OIDC
+ * authentication result.
+ *
+ * <p>Implementation based on section 2.8.2 of TS.43.
+ *
+ * <p>{@link #acquireOidcAuthenticationEndpoint} must be called before calling this method.
+ */
+ @NonNull
+ public HttpResponse queryEntitlementStatusFromOidc(
+ String url, CarrierConfig carrierConfig, ServiceEntitlementRequest request)
+ throws ServiceEntitlementException {
+ Uri.Builder urlBuilder = Uri.parse(url).buildUpon();
+ return httpGet(
+ urlBuilder.toString(),
+ carrierConfig,
+ request.acceptContentType(),
+ request.terminalVendor(),
+ request.terminalModel(),
+ request.terminalSoftwareVersion());
+ }
+
+ @SuppressWarnings("HardwareIds")
+ private void appendParametersForAuthentication(
+ Uri.Builder urlBuilder, ServiceEntitlementRequest request) {
if (!TextUtils.isEmpty(request.authenticationToken())) {
// IMSI and token required for fast AuthN.
urlBuilder
- .appendQueryParameter(IMSI, telephonyManager.getSubscriberId())
+ .appendQueryParameter(IMSI, mTelephonyManager.getSubscriberId())
.appendQueryParameter(TOKEN, request.authenticationToken());
} else if (!TextUtils.isEmpty(request.temporaryToken())) {
// temporary_token required for fast AuthN.
@@ -307,20 +530,53 @@ public class EapAkaApi {
// EAP_ID required for initial AuthN
urlBuilder.appendQueryParameter(
EAP_ID,
- getImsiEap(telephonyManager.getSimOperator(),
- telephonyManager.getSubscriberId()));
+ getImsiEap(
+ mTelephonyManager.getSimOperator(),
+ mTelephonyManager.getSubscriberId()));
}
+ }
+ @SuppressWarnings("HardwareIds")
+ private void appendParametersForAuthentication(
+ JSONObject postData, ServiceEntitlementRequest request)
+ throws ServiceEntitlementException {
+ try {
+ if (!TextUtils.isEmpty(request.authenticationToken())) {
+ // IMSI and token required for fast AuthN.
+ postData.put(IMSI, mTelephonyManager.getSubscriberId());
+ postData.put(TOKEN, request.authenticationToken());
+ } else if (!TextUtils.isEmpty(request.temporaryToken())) {
+ // temporary_token required for fast AuthN.
+ postData.put(TEMPORARY_TOKEN, request.temporaryToken());
+ } else {
+ // EAP_ID required for initial AuthN
+ postData.put(
+ EAP_ID,
+ getImsiEap(
+ mTelephonyManager.getSimOperator(),
+ mTelephonyManager.getSubscriberId()));
+ }
+ } catch (JSONException jsonException) {
+ // Should never happen
+ throw new ServiceEntitlementException(
+ ERROR_JSON_COMPOSE_FAILURE, "Failed to compose JSON", jsonException);
+ }
+ }
+
+ private void appendParametersForServiceEntitlementRequest(
+ Uri.Builder urlBuilder,
+ ImmutableList<String> appIds,
+ ServiceEntitlementRequest request) {
if (!TextUtils.isEmpty(request.notificationToken())) {
urlBuilder
- .appendQueryParameter(NOTIF_ACTION,
- Integer.toString(request.notificationAction()))
+ .appendQueryParameter(
+ NOTIF_ACTION, Integer.toString(request.notificationAction()))
.appendQueryParameter(NOTIF_TOKEN, request.notificationToken());
}
// Assign terminal ID with device IMEI if not set.
if (TextUtils.isEmpty(request.terminalId())) {
- urlBuilder.appendQueryParameter(TERMINAL_ID, telephonyManager.getImei());
+ urlBuilder.appendQueryParameter(TERMINAL_ID, mTelephonyManager.getImei());
} else {
urlBuilder.appendQueryParameter(TERMINAL_ID, request.terminalId());
}
@@ -328,7 +584,7 @@ public class EapAkaApi {
// Optional query parameters, append them if not empty
appendOptionalQueryParameter(urlBuilder, APP_VERSION, request.appVersion());
appendOptionalQueryParameter(urlBuilder, APP_NAME, request.appName());
- appendOptionalQueryParameter(urlBuilder, NETWORK_IDENTIFIER, request.networkIdentifier());
+ appendOptionalQueryParameter(urlBuilder, BOOST_TYPE, request.boostType());
for (String appId : appIds) {
urlBuilder.appendQueryParameter(APP, appId);
@@ -336,73 +592,354 @@ public class EapAkaApi {
urlBuilder
// Identity and Authentication parameters
- .appendQueryParameter(TERMINAL_VENDOR, request.terminalVendor())
- .appendQueryParameter(TERMINAL_MODEL, request.terminalModel())
- .appendQueryParameter(TERMIAL_SW_VERSION, request.terminalSoftwareVersion())
+ .appendQueryParameter(
+ TERMINAL_VENDOR,
+ trimString(request.terminalVendor(), MAX_TERMINAL_VENDOR_LENGTH))
+ .appendQueryParameter(
+ TERMINAL_MODEL,
+ trimString(request.terminalModel(), MAX_TERMINAL_MODEL_LENGTH))
+ .appendQueryParameter(
+ TERMIAL_SW_VERSION,
+ trimString(
+ request.terminalSoftwareVersion(),
+ MAX_TERMINAL_SOFTWARE_VERSION_LENGTH))
// General Service parameters
.appendQueryParameter(VERS, Integer.toString(request.configurationVersion()))
.appendQueryParameter(ENTITLEMENT_VERSION, request.entitlementVersion());
}
+ private void appendParametersForServiceEntitlementRequest(
+ JSONObject postData, ImmutableList<String> appIds, ServiceEntitlementRequest request)
+ throws ServiceEntitlementException {
+ try {
+ if (!TextUtils.isEmpty(request.notificationToken())) {
+ postData.put(NOTIF_ACTION, Integer.toString(request.notificationAction()));
+ postData.put(NOTIF_TOKEN, request.notificationToken());
+ }
+
+ // Assign terminal ID with device IMEI if not set.
+ if (TextUtils.isEmpty(request.terminalId())) {
+ postData.put(TERMINAL_ID, mTelephonyManager.getImei());
+ } else {
+ postData.put(TERMINAL_ID, request.terminalId());
+ }
+
+ // Optional query parameters, append them if not empty
+ appendOptionalQueryParameter(postData, APP_VERSION, request.appVersion());
+ appendOptionalQueryParameter(postData, APP_NAME, request.appName());
+ appendOptionalQueryParameter(postData, BOOST_TYPE, request.boostType());
+
+ if (appIds.size() == 1) {
+ appendOptionalQueryParameter(postData, APP, appIds.get(0));
+ } else {
+ appendOptionalQueryParameter(
+ postData, APP, "[" + TextUtils.join(",", appIds) + "]");
+ }
+
+ appendOptionalQueryParameter(
+ postData,
+ TERMINAL_VENDOR,
+ trimString(request.terminalVendor(), MAX_TERMINAL_VENDOR_LENGTH));
+ appendOptionalQueryParameter(
+ postData,
+ TERMINAL_MODEL,
+ trimString(request.terminalModel(), MAX_TERMINAL_MODEL_LENGTH));
+ appendOptionalQueryParameter(
+ postData,
+ TERMIAL_SW_VERSION,
+ trimString(
+ request.terminalSoftwareVersion(),
+ MAX_TERMINAL_SOFTWARE_VERSION_LENGTH));
+ appendOptionalQueryParameter(
+ postData, VERS, Integer.toString(request.configurationVersion()));
+ appendOptionalQueryParameter(
+ postData, ENTITLEMENT_VERSION, request.entitlementVersion());
+ } catch (JSONException jsonException) {
+ // Should never happen
+ throw new ServiceEntitlementException(
+ ERROR_JSON_COMPOSE_FAILURE, "Failed to compose JSON", jsonException);
+ }
+ }
+
private void appendParametersForEsimOdsaOperation(
Uri.Builder urlBuilder, EsimOdsaOperation odsaOperation) {
urlBuilder.appendQueryParameter(OPERATION, odsaOperation.operation());
if (odsaOperation.operationType() != EsimOdsaOperation.OPERATION_TYPE_NOT_SET) {
- urlBuilder.appendQueryParameter(OPERATION_TYPE,
- Integer.toString(odsaOperation.operationType()));
+ urlBuilder.appendQueryParameter(
+ OPERATION_TYPE, Integer.toString(odsaOperation.operationType()));
}
appendOptionalQueryParameter(
urlBuilder,
OPERATION_TARGETS,
TextUtils.join(",", odsaOperation.operationTargets()));
- appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_ID,
- odsaOperation.companionTerminalId());
- appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_VENDOR,
- odsaOperation.companionTerminalVendor());
- appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_MODEL,
- odsaOperation.companionTerminalModel());
- appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_SW_VERSION,
+ appendOptionalQueryParameter(
+ urlBuilder, COMPANION_TERMINAL_ID, odsaOperation.companionTerminalId());
+ appendOptionalQueryParameter(
+ urlBuilder, COMPANION_TERMINAL_VENDOR, odsaOperation.companionTerminalVendor());
+ appendOptionalQueryParameter(
+ urlBuilder, COMPANION_TERMINAL_MODEL, odsaOperation.companionTerminalModel());
+ appendOptionalQueryParameter(
+ urlBuilder,
+ COMPANION_TERMINAL_SW_VERSION,
odsaOperation.companionTerminalSoftwareVersion());
- appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_FRIENDLY_NAME,
+ appendOptionalQueryParameter(
+ urlBuilder,
+ COMPANION_TERMINAL_FRIENDLY_NAME,
odsaOperation.companionTerminalFriendlyName());
- appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_SERVICE,
- odsaOperation.companionTerminalService());
- appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_ICCID,
- odsaOperation.companionTerminalIccid());
- appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_EID,
- odsaOperation.companionTerminalEid());
- appendOptionalQueryParameter(urlBuilder, TERMINAL_ICCID,
- odsaOperation.terminalIccid());
+ appendOptionalQueryParameter(
+ urlBuilder, COMPANION_TERMINAL_SERVICE, odsaOperation.companionTerminalService());
+ appendOptionalQueryParameter(
+ urlBuilder, COMPANION_TERMINAL_ICCID, odsaOperation.companionTerminalIccid());
+ appendOptionalQueryParameter(
+ urlBuilder, COMPANION_TERMINAL_EID, odsaOperation.companionTerminalEid());
+ appendOptionalQueryParameter(urlBuilder, TERMINAL_ICCID, odsaOperation.terminalIccid());
appendOptionalQueryParameter(urlBuilder, TERMINAL_EID, odsaOperation.terminalEid());
- appendOptionalQueryParameter(urlBuilder, TARGET_TERMINAL_ID,
- odsaOperation.targetTerminalId());
- appendOptionalQueryParameter(urlBuilder, TARGET_TERMINAL_ICCID,
- odsaOperation.targetTerminalIccid());
- appendOptionalQueryParameter(urlBuilder, TARGET_TERMINAL_EID,
- odsaOperation.targetTerminalEid());
- appendOptionalQueryParameter(urlBuilder, OLD_TERMINAL_ICCID,
- odsaOperation.oldTerminalIccid());
- appendOptionalQueryParameter(urlBuilder, OLD_TERMINAL_ID,
- odsaOperation.oldTerminalId());
+ appendOptionalQueryParameter(
+ urlBuilder, TARGET_TERMINAL_ID, odsaOperation.targetTerminalId());
+ appendOptionalQueryParameter(
+ urlBuilder, TARGET_TERMINAL_IDS, odsaOperation.targetTerminalIds());
+ appendOptionalQueryParameter(
+ urlBuilder, TARGET_TERMINAL_ICCID, odsaOperation.targetTerminalIccid());
+ appendOptionalQueryParameter(
+ urlBuilder, TARGET_TERMINAL_EID, odsaOperation.targetTerminalEid());
+ appendOptionalQueryParameter(
+ urlBuilder,
+ TARGET_TERMINAL_SERIAL_NUMBER,
+ odsaOperation.targetTerminalSerialNumber());
+ appendOptionalQueryParameter(
+ urlBuilder, TARGET_TERMINAL_MODEL, odsaOperation.targetTerminalModel());
+ appendOptionalQueryParameter(
+ urlBuilder, OLD_TERMINAL_ICCID, odsaOperation.oldTerminalIccid());
+ appendOptionalQueryParameter(urlBuilder, OLD_TERMINAL_ID, odsaOperation.oldTerminalId());
+ }
+
+ private void appendParametersForEsimOdsaOperation(
+ JSONObject postData, EsimOdsaOperation odsaOperation)
+ throws ServiceEntitlementException {
+ try {
+ postData.put(OPERATION, odsaOperation.operation());
+ if (odsaOperation.operationType() != EsimOdsaOperation.OPERATION_TYPE_NOT_SET) {
+ postData.put(OPERATION_TYPE, Integer.toString(odsaOperation.operationType()));
+ }
+ appendOptionalQueryParameter(
+ postData,
+ OPERATION_TARGETS,
+ TextUtils.join(",", odsaOperation.operationTargets()));
+ appendOptionalQueryParameter(
+ postData, COMPANION_TERMINAL_ID, odsaOperation.companionTerminalId());
+ appendOptionalQueryParameter(
+ postData, COMPANION_TERMINAL_VENDOR, odsaOperation.companionTerminalVendor());
+ appendOptionalQueryParameter(
+ postData, COMPANION_TERMINAL_MODEL, odsaOperation.companionTerminalModel());
+ appendOptionalQueryParameter(
+ postData,
+ COMPANION_TERMINAL_SW_VERSION,
+ odsaOperation.companionTerminalSoftwareVersion());
+ appendOptionalQueryParameter(
+ postData,
+ COMPANION_TERMINAL_FRIENDLY_NAME,
+ odsaOperation.companionTerminalFriendlyName());
+ appendOptionalQueryParameter(
+ postData, COMPANION_TERMINAL_SERVICE, odsaOperation.companionTerminalService());
+ appendOptionalQueryParameter(
+ postData, COMPANION_TERMINAL_ICCID, odsaOperation.companionTerminalIccid());
+ appendOptionalQueryParameter(
+ postData, COMPANION_TERMINAL_EID, odsaOperation.companionTerminalEid());
+ appendOptionalQueryParameter(postData, TERMINAL_ICCID, odsaOperation.terminalIccid());
+ appendOptionalQueryParameter(postData, TERMINAL_EID, odsaOperation.terminalEid());
+ appendOptionalQueryParameter(
+ postData, TARGET_TERMINAL_ID, odsaOperation.targetTerminalId());
+ appendOptionalQueryParameter(
+ postData, TARGET_TERMINAL_IDS, odsaOperation.targetTerminalIds());
+ appendOptionalQueryParameter(
+ postData, TARGET_TERMINAL_ICCID, odsaOperation.targetTerminalIccid());
+ appendOptionalQueryParameter(
+ postData, TARGET_TERMINAL_EID, odsaOperation.targetTerminalEid());
+ appendOptionalQueryParameter(
+ postData,
+ TARGET_TERMINAL_SERIAL_NUMBER,
+ odsaOperation.targetTerminalSerialNumber());
+ appendOptionalQueryParameter(
+ postData, TARGET_TERMINAL_MODEL, odsaOperation.targetTerminalModel());
+ appendOptionalQueryParameter(
+ postData, OLD_TERMINAL_ICCID, odsaOperation.oldTerminalIccid());
+ appendOptionalQueryParameter(postData, OLD_TERMINAL_ID, odsaOperation.oldTerminalId());
+ } catch (JSONException jsonException) {
+ // Should never happen
+ throw new ServiceEntitlementException(
+ ERROR_JSON_COMPOSE_FAILURE, "Failed to compose JSON", jsonException);
+ }
+ }
+
+ private void appendOptionalQueryParameter(Uri.Builder urlBuilder, String key, String value) {
+ if (!TextUtils.isEmpty(value)) {
+ urlBuilder.appendQueryParameter(key, value);
+ }
+ }
+
+ private void appendOptionalQueryParameter(JSONObject postData, String key, String value)
+ throws JSONException {
+ if (!TextUtils.isEmpty(value)) {
+ postData.put(key, value);
+ }
+ }
+
+ private void appendOptionalQueryParameter(
+ Uri.Builder urlBuilder, String key, ImmutableList<String> values) {
+ for (String value : values) {
+ if (!TextUtils.isEmpty(value)) {
+ urlBuilder.appendQueryParameter(key, value);
+ }
+ }
+ }
+
+ private void appendOptionalQueryParameter(
+ JSONObject postData, String key, ImmutableList<String> values) throws JSONException {
+ for (String value : values) {
+ if (!TextUtils.isEmpty(value)) {
+ postData.put(key, value);
+ }
+ }
}
- private HttpResponse httpGet(String url, CarrierConfig carrierConfig, String contentType)
+ @NonNull
+ private HttpResponse httpGet(
+ String url,
+ CarrierConfig carrierConfig,
+ String acceptContentType,
+ String terminalVendor,
+ String terminalModel,
+ String terminalSoftwareVersion)
throws ServiceEntitlementException {
- HttpRequest httpRequest =
+ HttpRequest.Builder builder =
HttpRequest.builder()
.setUrl(url)
.setRequestMethod(RequestMethod.GET)
- .addRequestProperty(HttpHeaders.ACCEPT, contentType)
+ .addRequestProperty(HttpHeaders.ACCEPT, acceptContentType)
.setTimeoutInSec(carrierConfig.timeoutInSec())
- .setNetwork(carrierConfig.network())
- .build();
- return mHttpClient.request(httpRequest);
+ .setNetwork(carrierConfig.network());
+ String userAgent =
+ getUserAgent(
+ carrierConfig.clientTs43(),
+ terminalVendor,
+ terminalModel,
+ terminalSoftwareVersion);
+ if (!TextUtils.isEmpty(userAgent)) {
+ builder.addRequestProperty(HttpHeaders.USER_AGENT, userAgent);
+ }
+ return mHttpClient.request(builder.build());
}
- private void appendOptionalQueryParameter(Uri.Builder urlBuilder, String key, String value) {
- if (!TextUtils.isEmpty(value)) {
- urlBuilder.appendQueryParameter(key, value);
+ @NonNull
+ private HttpResponse httpPost(
+ JSONObject postData,
+ CarrierConfig carrierConfig,
+ String acceptContentType,
+ String terminalVendor,
+ String terminalModel,
+ String terminalSoftwareVersion)
+ throws ServiceEntitlementException {
+ return httpPost(
+ postData,
+ carrierConfig,
+ acceptContentType,
+ terminalVendor,
+ terminalModel,
+ terminalSoftwareVersion,
+ ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON,
+ ImmutableList.of());
+ }
+
+ @NonNull
+ private HttpResponse httpPost(
+ JSONObject postData,
+ CarrierConfig carrierConfig,
+ String acceptContentType,
+ String terminalVendor,
+ String terminalModel,
+ String terminalSoftwareVersion,
+ String contentType,
+ ImmutableList<String> cookies)
+ throws ServiceEntitlementException {
+ HttpRequest.Builder builder =
+ HttpRequest.builder()
+ .setUrl(carrierConfig.serverUrl())
+ .setRequestMethod(RequestMethod.POST)
+ .setPostData(postData)
+ .addRequestProperty(HttpHeaders.ACCEPT, acceptContentType)
+ .addRequestProperty(HttpHeaders.CONTENT_TYPE, contentType)
+ .addRequestProperty(HttpHeaders.COOKIE, cookies)
+ .setTimeoutInSec(carrierConfig.timeoutInSec())
+ .setNetwork(carrierConfig.network());
+ String userAgent =
+ getUserAgent(
+ carrierConfig.clientTs43(),
+ terminalVendor,
+ terminalModel,
+ terminalSoftwareVersion);
+ if (!TextUtils.isEmpty(userAgent)) {
+ builder.addRequestProperty(HttpHeaders.USER_AGENT, userAgent);
}
+ return mHttpClient.request(builder.build());
+ }
+
+ @Nullable
+ private String getEapAkaChallenge(HttpResponse response) throws ServiceEntitlementException {
+ String eapAkaChallenge = null;
+ String responseBody = response.body();
+ if (response.contentType() == ContentType.JSON) {
+ try {
+ eapAkaChallenge =
+ new JSONObject(responseBody).optString(EAP_CHALLENGE_RESPONSE, null);
+ } catch (JSONException jsonException) {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE,
+ "Failed to parse json object",
+ jsonException);
+ }
+ } else if (response.contentType() == ContentType.XML) {
+ // EAP-AKA challenge is always in JSON format.
+ return null;
+ } else {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE, "Unknown HTTP content type");
+ }
+ return eapAkaChallenge;
+ }
+
+ private String getAppVersion(Context context) {
+ try {
+ PackageInfo packageInfo =
+ context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
+ return packageInfo.versionName;
+ } catch (Exception e) {
+ // should be impossible
+ }
+ return "";
+ }
+
+ private String getUserAgent(
+ String clientTs43,
+ String terminalVendor,
+ String terminalModel,
+ String terminalSoftwareVersion) {
+ if (!TextUtils.isEmpty(clientTs43)
+ && !TextUtils.isEmpty(terminalVendor)
+ && !TextUtils.isEmpty(terminalModel)
+ && !TextUtils.isEmpty(terminalSoftwareVersion)) {
+ return String.format(
+ "PRD-TS43 term-%s/%s %s/%s OS-Android/%s",
+ trimString(terminalVendor, MAX_TERMINAL_VENDOR_LENGTH),
+ trimString(terminalModel, MAX_TERMINAL_MODEL_LENGTH),
+ clientTs43,
+ mAppVersion,
+ trimString(terminalSoftwareVersion, MAX_TERMINAL_SOFTWARE_VERSION_LENGTH));
+ }
+ return "";
+ }
+
+ private String trimString(String s, int maxLength) {
+ return s.substring(0, Math.min(s.length(), maxLength));
}
/**
@@ -425,16 +962,13 @@ public class EapAkaApi {
return "0" + imsi + "@nai.epc.mnc" + mnc + ".mcc" + mcc + ".3gppnetwork.org";
}
- /**
- * Retrieves the history of past HTTP request and responses.
- */
+ /** Retrieves the history of past HTTP request and responses. */
+ @NonNull
public List<String> getHistory() {
return mHttpClient.getHistory();
}
- /**
- * Clears the history of past HTTP request and responses.
- */
+ /** Clears the history of past HTTP request and responses. */
public void clearHistory() {
mHttpClient.clearHistory();
}
diff --git a/java/com/android/libraries/entitlement/http/HttpClient.java b/java/com/android/libraries/entitlement/http/HttpClient.java
index f2b394d..88ac5d5 100644
--- a/java/com/android/libraries/entitlement/http/HttpClient.java
+++ b/java/com/android/libraries/entitlement/http/HttpClient.java
@@ -125,7 +125,7 @@ public class HttpClient {
} else {
mConnection = (HttpURLConnection) network.openConnection(url);
}
-
+ mConnection.setInstanceFollowRedirects(false);
// add HTTP headers
for (Map.Entry<String, String> entry : request.requestProperties().entries()) {
mConnection.addRequestProperty(entry.getKey(), entry.getValue());
@@ -158,13 +158,16 @@ public class HttpClient {
try {
int responseCode = connection.getResponseCode();
logPii("HttpClient.response headers: " + connection.getHeaderFields());
- if (responseCode != HttpURLConnection.HTTP_OK) {
+ if (responseCode != HttpURLConnection.HTTP_OK
+ && responseCode != HttpURLConnection.HTTP_MOVED_TEMP) {
throw new ServiceEntitlementException(ERROR_HTTP_STATUS_NOT_SUCCESS, responseCode,
connection.getHeaderField(HttpHeaders.RETRY_AFTER),
- "Invalid connection response");
+ "Invalid connection response: " + responseCode);
}
responseBuilder.setResponseCode(responseCode);
responseBuilder.setResponseMessage(nullToEmpty(connection.getResponseMessage()));
+ responseBuilder.setLocation(
+ nullToEmpty(connection.getHeaderField(HttpHeaders.LOCATION)));
} catch (IOException e) {
throw new ServiceEntitlementException(
ERROR_HTTP_STATUS_NOT_SUCCESS, "Read response code failed!", e);
diff --git a/java/com/android/libraries/entitlement/http/HttpConstants.java b/java/com/android/libraries/entitlement/http/HttpConstants.java
index 58f8c48..2c8fe83 100644
--- a/java/com/android/libraries/entitlement/http/HttpConstants.java
+++ b/java/com/android/libraries/entitlement/http/HttpConstants.java
@@ -41,9 +41,12 @@ public final class HttpConstants {
private ContentType() {
}
- public static final int UNKNOWN = -1;
- public static final int JSON = 0;
- public static final int XML = 1;
+ public static final int UNKNOWN =
+ com.android.libraries.entitlement.utils.HttpConstants.UNKNOWN;
+ public static final int JSON =
+ com.android.libraries.entitlement.utils.HttpConstants.JSON;
+ public static final int XML =
+ com.android.libraries.entitlement.utils.HttpConstants.XML;
public static final String NAME = "Content-Type";
}
diff --git a/java/com/android/libraries/entitlement/http/HttpRequest.java b/java/com/android/libraries/entitlement/http/HttpRequest.java
index b6cd771..ec5ca7a 100644
--- a/java/com/android/libraries/entitlement/http/HttpRequest.java
+++ b/java/com/android/libraries/entitlement/http/HttpRequest.java
@@ -16,6 +16,7 @@
package com.android.libraries.entitlement.http;
+import android.content.res.Resources;
import android.net.Network;
import androidx.annotation.Nullable;
@@ -24,6 +25,8 @@ import com.android.libraries.entitlement.CarrierConfig;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.net.HttpHeaders;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.json.JSONObject;
@@ -72,6 +75,7 @@ public abstract class HttpRequest {
abstract ImmutableListMultimap.Builder<String, String> requestPropertiesBuilder();
/** Adds an HTTP header field. */
+ @CanIgnoreReturnValue
public Builder addRequestProperty(String key, String value) {
requestPropertiesBuilder().put(key, value);
return this;
@@ -82,6 +86,7 @@ public abstract class HttpRequest {
* {@link #addRequestProperty(String, String)} multiple times with the same key and
* one value at a time.
*/
+ @CanIgnoreReturnValue
public Builder addRequestProperty(String key, List<String> value) {
requestPropertiesBuilder().putAll(key, value);
return this;
@@ -108,6 +113,13 @@ public abstract class HttpRequest {
.setUrl("")
.setRequestMethod("")
.setPostData(new JSONObject())
- .setTimeoutInSec(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ .setTimeoutInSec(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC)
+ .addRequestProperty(
+ HttpHeaders.ACCEPT_LANGUAGE,
+ Resources.getSystem()
+ .getConfiguration()
+ .getLocales()
+ .get(0)
+ .toLanguageTag());
}
}
diff --git a/java/com/android/libraries/entitlement/http/HttpResponse.java b/java/com/android/libraries/entitlement/http/HttpResponse.java
index 142639e..c2d1d2a 100644
--- a/java/com/android/libraries/entitlement/http/HttpResponse.java
+++ b/java/com/android/libraries/entitlement/http/HttpResponse.java
@@ -45,6 +45,11 @@ public abstract class HttpResponse {
public abstract ImmutableList<String> cookies();
/**
+ * Content of the "Location" response header.
+ */
+ public abstract String location();
+
+ /**
* Builder of {@link HttpResponse}.
*/
@AutoValue.Builder
@@ -63,6 +68,11 @@ public abstract class HttpResponse {
* Sets the content of the "Set-Cookie" response headers.
*/
public abstract Builder setCookies(List<String> cookies);
+
+ /**
+ * Sets the content of the "Location" response header.
+ */
+ public abstract Builder setLocation(String location);
}
public static Builder builder() {
@@ -71,7 +81,8 @@ public abstract class HttpResponse {
.setBody("")
.setResponseCode(0)
.setResponseMessage("")
- .setCookies(ImmutableList.of());
+ .setCookies(ImmutableList.of())
+ .setLocation("");
}
/**
@@ -91,7 +102,9 @@ public abstract class HttpResponse {
.append(responseMessage())
.append(" cookies=[")
.append(cookies().size())
- .append(" cookies]}")
+ .append(" cookies]")
+ .append(" location=")
+ .append(location())
.toString();
}
}
diff --git a/java/com/android/libraries/entitlement/odsa/AcquireConfigurationOperation.java b/java/com/android/libraries/entitlement/odsa/AcquireConfigurationOperation.java
new file mode 100644
index 0000000..08a86b8
--- /dev/null
+++ b/java/com/android/libraries/entitlement/odsa/AcquireConfigurationOperation.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.odsa;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.libraries.entitlement.EsimOdsaOperation;
+import com.android.libraries.entitlement.EsimOdsaOperation.CompanionService;
+import com.android.libraries.entitlement.EsimOdsaOperation.OdsaServiceStatus;
+import com.android.libraries.entitlement.utils.Ts43Constants;
+import com.android.libraries.entitlement.utils.Ts43Constants.AppId;
+import com.android.libraries.entitlement.utils.Ts43Constants.NotificationAction;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Acquire configuration operation described in GSMA Service Entitlement Configuration section 6.
+ */
+public final class AcquireConfigurationOperation {
+ /** Indicating polling interval not available. */
+ public static final int POLLING_INTERVAL_NOT_AVAILABLE = -1;
+
+ /**
+ * HTTP request parameters specific to on device service activation (ODSA) acquire configuration
+ * operation. See GSMA spec TS.43 section 6.2.
+ */
+ @AutoValue
+ public abstract static class AcquireConfigurationRequest {
+ /**
+ * Returns the application id. Can only be {@link Ts43Constants#APP_ODSA_COMPANION},
+ * {@link Ts43Constants#APP_ODSA_PRIMARY}, or
+ * {@link Ts43Constants#APP_ODSA_SERVER_INITIATED_REQUESTS}.
+ */
+ @AppId
+ public abstract String appId();
+
+ /**
+ * Returns the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * {@code companion_terminal_id}.
+ */
+ @NonNull
+ public abstract String companionTerminalId();
+
+ /**
+ * Returns the ICCID of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_iccid}.
+ */
+ @NonNull
+ public abstract String companionTerminalIccid();
+
+ /**
+ * Returns the EID of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_eid}.
+ */
+ @NonNull
+ public abstract String companionTerminalEid();
+
+ /**
+ * Returns the ICCID of the primary device eSIM. Used by HTTP parameter
+ * {@code terminal_iccid}.
+ */
+ @NonNull
+ public abstract String terminalIccid();
+
+ /**
+ * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
+ * {@code terminal_eid}.
+ */
+ @NonNull
+ public abstract String terminalEid();
+
+ /**
+ * Returns the unique identifier of the primary device eSIM, like the IMEI associated with
+ * the eSIM. Used by HTTP parameter {@code target_terminal_id}.
+ */
+ @NonNull
+ public abstract String targetTerminalId();
+
+ /**
+ * Returns the ICCID primary device eSIM. Used by HTTP parameter
+ * {@code target_terminal_iccid}.
+ */
+ @NonNull
+ public abstract String targetTerminalIccid();
+
+ /**
+ * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
+ * {@code target_terminal_eid}.
+ */
+ @NonNull
+ public abstract String targetTerminalEid();
+
+ /**
+ * Returns the notification token used to register for entitlement configuration request
+ * from network. Used by HTTP parameter {@code notif_token}.
+ */
+ @NonNull
+ public abstract String notificationToken();
+
+ /**
+ * Returns the action associated with the notification token. Used by HTTP parameter
+ * {@code notif_action}.
+ */
+ @NotificationAction
+ public abstract int notificationAction();
+
+ /** Returns a new {@link Builder} object. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_AcquireConfigurationOperation_AcquireConfigurationRequest
+ .Builder()
+ .setCompanionTerminalId("")
+ .setCompanionTerminalIccid("")
+ .setCompanionTerminalEid("")
+ .setTerminalIccid("")
+ .setTerminalEid("")
+ .setTargetTerminalId("")
+ .setTargetTerminalIccid("")
+ .setTargetTerminalEid("")
+ .setNotificationToken("")
+ .setNotificationAction(Ts43Constants.NOTIFICATION_ACTION_ENABLE_FCM);
+ }
+
+ /** Builder */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the application id.
+ *
+ * @param appId The application id. Can only be
+ * {@link Ts43Constants#APP_ODSA_COMPANION},
+ * {@link Ts43Constants#APP_ODSA_PRIMARY}, or
+ * {@link Ts43Constants#APP_ODSA_SERVER_INITIATED_REQUESTS}.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setAppId(@NonNull @AppId String appId);
+
+ /**
+ * Sets the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * {@code companion_terminal_id} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalId The unique identifier of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalId(@NonNull String companionTerminalId);
+
+ /**
+ * Sets the ICCID of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_iccid} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalIccid The ICCID of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalIccid(
+ @NonNull String companionTerminalIccid);
+
+ /**
+ * Sets the eUICC identifier (EID) of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_eid} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalEid The eUICC identifier (EID) of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalEid(@NonNull String companionTerminalEid);
+
+ /**
+ * Sets the ICCID of the primary device eSIM in case of primary SIM not present. Used by
+ * HTTP parameter {@code terminal_eid} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param terminalIccid The ICCID of the primary device eSIM in case of primary SIM not
+ * present.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTerminalIccid(@NonNull String terminalIccid);
+
+ /**
+ * Sets the eUICC identifier (EID) of the primary device eSIM in case of primary SIM not
+ * present. Used by HTTP parameter {@code terminal_eid} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param terminalEid The eUICC identifier (EID) of the primary device eSIM in case of
+ * primary SIM not present.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTerminalEid(@NonNull String terminalEid);
+
+ /**
+ * Sets the unique identifier of the primary device eSIM in case of multiple SIM, like
+ * the IMEI associated with the eSIM. Used by HTTP parameter {@code target_terminal_id}
+ * if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param targetTerminalId The unique identifier of the primary device eSIM in case of
+ * multiple SIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalId(@NonNull String targetTerminalId);
+
+ /**
+ * Sets the ICCID primary device eSIM in case of multiple SIM. Used by HTTP parameter
+ * {@code target_terminal_iccid} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param targetTerminalIccid The ICCID primary device eSIM in case of multiple SIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalIccid(@NonNull String targetTerminalIccid);
+
+ /**
+ * Sets the eUICC identifier (EID) of the primary device eSIM in case of multiple SIM.
+ * Used by HTTP parameter {@code target_terminal_eid} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param targetTerminalEid The eUICC identifier (EID) of the primary device eSIM in
+ * case of multiple SIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalEid(@NonNull String targetTerminalEid);
+
+ /**
+ * Sets the notification token used to register for entitlement configuration request
+ * from network. Used by HTTP parameter {@code notif_token} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param notificationToken The notification token used to register for entitlement
+ * configuration request from network.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setNotificationToken(@NonNull String notificationToken);
+
+ /**
+ * Sets the action associated with the notification token. Used by HTTP parameter
+ * {@code notif_action} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param notificationAction The action associated with the notification token.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setNotificationAction(
+ @NotificationAction int notificationAction);
+
+ /** Returns build the {@link AcquireConfigurationRequest} object. */
+ @NonNull
+ public abstract AcquireConfigurationRequest build();
+ }
+ }
+
+ /**
+ * Acquire configuration response described in GSMA Service Entitlement Configuration section
+ * section 6.5.5 table 40.
+ */
+ @AutoValue
+ public abstract static class AcquireConfigurationResponse extends OdsaResponse {
+ /** Configuration */
+ @AutoValue
+ public abstract static class Configuration {
+ /** The configuration type is unknown. */
+ public static final int CONFIGURATION_TYPE_UNKNOWN = -1;
+
+ /** The configuration is for ODSA primary device. */
+ public static final int CONFIGURATION_TYPE_PRIMARY = 1;
+
+ /** The configuration is for companion device. */
+ public static final int CONFIGURATION_TYPE_COMPANION = 2;
+
+ /** The configuration is for server-initiated ODSA. */
+ public static final int CONFIGURATION_TYPE_ENTERPRISE = 3;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ CONFIGURATION_TYPE_UNKNOWN,
+ CONFIGURATION_TYPE_PRIMARY,
+ CONFIGURATION_TYPE_COMPANION,
+ CONFIGURATION_TYPE_ENTERPRISE
+ })
+ public @interface ConfigurationType {
+ }
+
+ /** Indicates the configuration type. */
+ @ConfigurationType
+ public abstract int type();
+
+ /**
+ * Integrated Circuit Card Identification - Identifier of the eSIM profile on the
+ * device’s eSIM. {@code null} if an eSIM profile does not exist for the device.
+ */
+ @Nullable
+ public abstract String iccid();
+
+ /**
+ * Indicates the applicable companion device service. {@code null} if not for companion
+ * configuration.
+ */
+ @Nullable
+ @CompanionService
+ public abstract String companionDeviceService();
+
+ /**
+ * Service status.
+ *
+ * @see EsimOdsaOperation#SERVICE_STATUS_UNKNOWN
+ * @see EsimOdsaOperation#SERVICE_STATUS_ACTIVATED
+ * @see EsimOdsaOperation#SERVICE_STATUS_ACTIVATING
+ * @see EsimOdsaOperation#SERVICE_STATUS_DEACTIVATED
+ * @see EsimOdsaOperation#SERVICE_STATUS_DEACTIVATED_NO_REUSE
+ */
+ @OdsaServiceStatus
+ public abstract int serviceStatus();
+
+ /**
+ * Specifies the minimum interval (in minutes) with which the device application may
+ * poll the ECS to refresh the current {@link #serviceStatus()} using {@link
+ * AcquireConfigurationRequest}. This parameter will be present only when {@link
+ * #serviceStatus()} is {@link EsimOdsaOperation#SERVICE_STATUS_ACTIVATING}. If
+ * parameter is not present or value is 0, this polling procedure is not triggered and
+ * ODSA app will keep waiting for any external action to continue the flow.
+ *
+ * <p>The maximum number of {@link AcquireConfigurationRequest} before sending a {@link
+ * #serviceStatus()} with {@link EsimOdsaOperation#SERVICE_STATUS_DEACTIVATED_NO_REUSE}
+ * will be defined as an ECS configuration variable (MaxRefreshRequest).
+ *
+ * <p>{@link #POLLING_INTERVAL_NOT_AVAILABLE} when polling interval is not available.
+ */
+ public abstract int pollingInterval();
+
+ /**
+ * Specifies how and where to download the eSIM profile associated with the device.
+ * Present in case the profile is to be downloaded at this stage.
+ */
+ @Nullable
+ public abstract DownloadInfo downloadInfo();
+
+ /** Includes all information collected by the ES of the companion device. */
+ @Nullable
+ public abstract CompanionDeviceInfo companionDeviceInfo();
+
+ /** Returns the builder. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_AcquireConfigurationOperation_AcquireConfigurationResponse_Configuration
+ .Builder()
+ .setType(CONFIGURATION_TYPE_UNKNOWN)
+ .setIccid("")
+ .setServiceStatus(EsimOdsaOperation.SERVICE_STATUS_UNKNOWN)
+ .setPollingInterval(POLLING_INTERVAL_NOT_AVAILABLE);
+ }
+
+ /** The builder of {@link Configuration} */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Set the configuration type.
+ *
+ * @param configType The configuration type.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setType(@ConfigurationType int configType);
+
+ /**
+ * Set the iccid.
+ *
+ * @param iccid Integrated Circuit Card Identification - Identifier of the eSIM
+ * profile on the device’s eSIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setIccid(@NonNull String iccid);
+
+ /**
+ * Set the applicable companion device service.
+ *
+ * @param companionDeviceService Indicates the applicable companion device service.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionDeviceService(
+ @NonNull @CompanionService String companionDeviceService);
+
+ /**
+ * Set the service status.
+ *
+ * @param serviceStatus Service status.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setServiceStatus(@OdsaServiceStatus int serviceStatus);
+
+ /**
+ * Set the polling interval.
+ *
+ * @param pollingInterval The minimum interval (in minutes) with which the device
+ * application may poll the ECS to refresh the current
+ * {@link #serviceStatus()} using
+ * {@link AcquireConfigurationRequest}.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setPollingInterval(int pollingInterval);
+
+ /**
+ * Set the download information.
+ *
+ * @param downloadInfo Specifies how and where to download the eSIM profile
+ * associated with the device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setDownloadInfo(@NonNull DownloadInfo downloadInfo);
+
+ /**
+ * Set the companion device info.
+ *
+ * @param companionDeviceInfo Includes all information collected by the ES of the
+ * companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionDeviceInfo(
+ @NonNull CompanionDeviceInfo companionDeviceInfo);
+
+ /** Returns build the {@link Configuration} object. */
+ @NonNull
+ public abstract Configuration build();
+ }
+ }
+
+ /**
+ * Configurations defined in GSMA Service Entitlement Configuration section 6.5.5. Could be
+ * more than one if multiple companion device(s) associated with the requesting device that
+ * carry a configuration for ODSA.
+ */
+ @NonNull
+ public abstract ImmutableList<Configuration> configurations();
+
+ /** Returns the builder. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_AcquireConfigurationOperation_AcquireConfigurationResponse
+ .Builder()
+ .setConfigurations(ImmutableList.of());
+ }
+
+ /** The builder of {@link AcquireConfigurationResponse} */
+ @AutoValue.Builder
+ public abstract static class Builder extends OdsaResponse.Builder {
+ /**
+ * Set the configurations
+ *
+ * @param configs Configurations defined in GSMA Service Entitlement Configuration
+ * section 6.5.5. Could be more than one if multiple companion device(s)
+ * associated with the requesting device that carry a configuration for
+ * ODSA.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setConfigurations(
+ @NonNull ImmutableList<Configuration> configs);
+
+ /** Returns build the {@link AcquireConfigurationResponse} object. */
+ @NonNull
+ public abstract AcquireConfigurationResponse build();
+ }
+ }
+
+ private AcquireConfigurationOperation() {
+ }
+}
diff --git a/java/com/android/libraries/entitlement/odsa/AcquireTemporaryTokenOperation.java b/java/com/android/libraries/entitlement/odsa/AcquireTemporaryTokenOperation.java
new file mode 100644
index 0000000..4f814b9
--- /dev/null
+++ b/java/com/android/libraries/entitlement/odsa/AcquireTemporaryTokenOperation.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.odsa;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.libraries.entitlement.EsimOdsaOperation.OdsaOperation;
+import com.android.libraries.entitlement.utils.Ts43Constants;
+import com.android.libraries.entitlement.utils.Ts43Constants.AppId;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+import java.time.Instant;
+
+/**
+ * Acquire temporary token operation described in GSMA Service Entitlement Configuration section 6.
+ */
+public final class AcquireTemporaryTokenOperation {
+ /**
+ * Acquire temporary token request described in GSMA Service Entitlement Configuration section
+ * 6.2.
+ */
+ @AutoValue
+ public abstract static class AcquireTemporaryTokenRequest {
+ /**
+ * Returns the application id. Can only be {@link Ts43Constants#APP_ODSA_COMPANION}, {@link
+ * Ts43Constants#APP_ODSA_PRIMARY}, or
+ * {@link Ts43Constants#APP_ODSA_SERVER_INITIATED_REQUESTS}.
+ */
+ @NonNull
+ @AppId
+ public abstract String appId();
+
+ /**
+ * Returns the comma separated list of operation targets used with temporary token from
+ * {@code AcquireTemporaryToken} operation. Used by HTTP parameter
+ * {@code operation_targets}.
+ */
+ @NonNull
+ @OdsaOperation
+ public abstract ImmutableList<String> operationTargets();
+
+ /**
+ * Returns the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * {@code companion_terminal_id}.
+ */
+ @NonNull
+ public abstract String companionTerminalId();
+
+ /** Returns a new {@link Builder} object. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_AcquireTemporaryTokenOperation_AcquireTemporaryTokenRequest
+ .Builder()
+ .setAppId(Ts43Constants.APP_UNKNOWN)
+ .setOperationTargets(ImmutableList.of())
+ .setCompanionTerminalId("");
+ }
+
+ /** Builder. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the application id.
+ *
+ * @param appId The application id. Can only be
+ * {@link Ts43Constants#APP_ODSA_COMPANION},
+ * {@link Ts43Constants#APP_ODSA_PRIMARY}, or
+ * {@link Ts43Constants#APP_ODSA_SERVER_INITIATED_REQUESTS}.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setAppId(@NonNull @AppId String appId);
+
+ /**
+ * Sets the operation targets to be used with temporary token from {@code
+ * AcquireTemporaryToken} operation. Used by HTTP parameter {@code operation_targets} if
+ * set.
+ *
+ * @param operationTargets The operation targets to be used with temporary token from
+ * {@code AcquireTemporaryToken} operation.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setOperationTargets(
+ @NonNull @OdsaOperation ImmutableList<String> operationTargets);
+
+ /**
+ * Sets the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * {@code companion_terminal_id} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalId The unique identifier of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalId(@NonNull String companionTerminalId);
+
+ /** Returns the {@link AcquireTemporaryTokenRequest} object. */
+ @NonNull
+ public abstract AcquireTemporaryTokenRequest build();
+ }
+ }
+
+ /**
+ * Acquire temporary token response described in GSMA Service Entitlement Configuration section
+ * 6.5.7.
+ */
+ @AutoValue
+ @AutoValue.CopyAnnotations
+ @SuppressWarnings("AndroidJdkLibsChecker") // java.time.Instant
+ public abstract static class AcquireTemporaryTokenResponse extends OdsaResponse {
+ /** The temporary token used to establish trust between ECS and the client. */
+ @NonNull
+ public abstract String temporaryToken();
+
+ /** The expiration time (UTC time) of the token. {@code null} if not available. */
+ @AutoValue.CopyAnnotations
+ @SuppressWarnings("AndroidJdkLibsChecker") // java.time.Instant
+
+ @Nullable
+ public abstract Instant temporaryTokenExpiry();
+
+ /** The allowed ODSA operations requested using {@link #temporaryToken()}. */
+ @NonNull
+ @OdsaOperation
+ public abstract ImmutableList<String> operationTargets();
+
+ /** Returns a new {@link AcquireTemporaryTokenRequest.Builder} object. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_AcquireTemporaryTokenOperation_AcquireTemporaryTokenResponse
+ .Builder()
+ .setTemporaryToken("")
+ .setTemporaryTokenExpiry(null)
+ .setOperationTargets(ImmutableList.of());
+ }
+
+ /** Builder. */
+ @AutoValue.Builder
+ @AutoValue.CopyAnnotations
+ @SuppressWarnings("AndroidJdkLibsChecker") // java.time.Instant
+ public abstract static class Builder extends OdsaResponse.Builder {
+ /**
+ * Sets the temporary token.
+ *
+ * @param token The temporary token used to establish trust between ECS and the client.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTemporaryToken(@NonNull String token);
+
+ /**
+ * Sets the expiration time of the token.
+ *
+ * @param expiry The expiration time (UTC time) of the token.
+ * @return The builder.
+ */
+ @AutoValue.CopyAnnotations
+ @SuppressWarnings("AndroidJdkLibsChecker") // java.time.Instant
+ @NonNull
+ public abstract Builder setTemporaryTokenExpiry(@NonNull Instant expiry);
+
+ /**
+ * Sets the allowed ODSA operations requested using {@link #temporaryToken()}.
+ *
+ * @param operationTargets The allowed ODSA operations requested using {@link
+ * #temporaryToken()}.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setOperationTargets(
+ @NonNull @OdsaOperation ImmutableList<String> operationTargets);
+
+ /** Returns the {@link AcquireTemporaryTokenResponse} object. */
+ @NonNull
+ public abstract AcquireTemporaryTokenResponse build();
+ }
+ }
+
+ private AcquireTemporaryTokenOperation() {
+ }
+}
diff --git a/java/com/android/libraries/entitlement/odsa/CheckEligibilityOperation.java b/java/com/android/libraries/entitlement/odsa/CheckEligibilityOperation.java
new file mode 100644
index 0000000..8dc8d97
--- /dev/null
+++ b/java/com/android/libraries/entitlement/odsa/CheckEligibilityOperation.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.odsa;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.libraries.entitlement.EsimOdsaOperation.CompanionService;
+import com.android.libraries.entitlement.utils.HttpConstants;
+import com.android.libraries.entitlement.utils.HttpConstants.ContentType;
+import com.android.libraries.entitlement.utils.Ts43Constants;
+import com.android.libraries.entitlement.utils.Ts43Constants.AppId;
+import com.android.libraries.entitlement.utils.Ts43Constants.NotificationAction;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.URL;
+
+/** Check eligibility operation described in GSMA Service Entitlement Configuration section 6. */
+public final class CheckEligibilityOperation {
+ /** ODSA app check eligibility result unknown. */
+ public static final int ELIGIBILITY_RESULT_UNKNOWN = -1;
+
+ /** ODSA app cannot be offered and invoked by the end-user. */
+ public static final int ELIGIBILITY_RESULT_DISABLED = 0;
+
+ /** ODSA app can be invoked by end-user or to activate a new subscription. */
+ public static final int ELIGIBILITY_RESULT_ENABLED = 1;
+
+ /** ODSA app is not compatible with the device or server. */
+ public static final int ELIGIBILITY_RESULT_INCOMPATIBLE = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ ELIGIBILITY_RESULT_UNKNOWN,
+ ELIGIBILITY_RESULT_DISABLED,
+ ELIGIBILITY_RESULT_ENABLED,
+ ELIGIBILITY_RESULT_INCOMPATIBLE
+ })
+ public @interface EligibilityResult {
+ }
+
+ /**
+ * HTTP request parameters specific to on device service activation (ODSA).
+ * See GSMA spec TS.43 section 6.2.
+ */
+ @AutoValue
+ public abstract static class CheckEligibilityRequest {
+ /**
+ * Returns the application id. Can only be {@link Ts43Constants#APP_ODSA_COMPANION},
+ * {@link Ts43Constants#APP_ODSA_PRIMARY}, or
+ * {@link Ts43Constants#APP_ODSA_SERVER_INITIATED_REQUESTS}.
+ */
+ @AppId
+ public abstract String appId();
+
+ /**
+ * Returns the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * {@code companion_terminal_id}.
+ */
+ @NonNull
+ public abstract String companionTerminalId();
+
+ /**
+ * Returns the OEM of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_vendor}.
+ */
+ @NonNull
+ public abstract String companionTerminalVendor();
+
+ /**
+ * Returns the model of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_model}.
+ */
+ @NonNull
+ public abstract String companionTerminalModel();
+
+ /**
+ * Returns the software version of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_sw_version}.
+ */
+ @NonNull
+ public abstract String companionTerminalSoftwareVersion();
+
+ /**
+ * Returns the user-friendly version of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_friendly_name}.
+ */
+ @NonNull
+ public abstract String companionTerminalFriendlyName();
+
+ /**
+ * Returns the notification token used to register for entitlement configuration request
+ * from network. Used by HTTP parameter {@code notif_token}.
+ */
+ @NonNull
+ public abstract String notificationToken();
+
+ /**
+ * Returns the action associated with the notification token. Used by HTTP parameter
+ * {@code notif_action}.
+ */
+ @NotificationAction
+ public abstract int notificationAction();
+
+ /** Returns a new {@link Builder} object. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_CheckEligibilityOperation_CheckEligibilityRequest.Builder()
+ .setAppId(Ts43Constants.APP_UNKNOWN)
+ .setCompanionTerminalId("")
+ .setCompanionTerminalVendor("")
+ .setCompanionTerminalModel("")
+ .setCompanionTerminalSoftwareVersion("")
+ .setCompanionTerminalFriendlyName("")
+ .setNotificationToken("")
+ .setNotificationAction(Ts43Constants.NOTIFICATION_ACTION_ENABLE_FCM);
+ }
+
+ /** Builder */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the application id.
+ *
+ * @param appId The application id. Can only be
+ * {@link Ts43Constants#APP_ODSA_COMPANION},
+ * {@link Ts43Constants#APP_ODSA_PRIMARY}, or {@link
+ * Ts43Constants#APP_ODSA_SERVER_INITIATED_REQUESTS}.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setAppId(@NonNull @AppId String appId);
+
+ /**
+ * Sets the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * {@code companion_terminal_id} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalId The unique identifier of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalId(@NonNull String companionTerminalId);
+
+ /**
+ * Sets the OEM of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_vendor} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalVendor The OEM of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalVendor(
+ @NonNull String companionTerminalVendor);
+
+ /**
+ * Sets the model of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_model} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalModel The model of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalModel(
+ @NonNull String companionTerminalModel);
+
+ /**
+ * Sets the software version of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_sw_version} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalSoftwareVersion The software version of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalSoftwareVersion(
+ @NonNull String companionTerminalSoftwareVersion);
+
+ /**
+ * Sets the user-friendly version of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_friendly_name} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalFriendlyName The user-friendly version of the companion
+ * device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalFriendlyName(
+ @NonNull String companionTerminalFriendlyName);
+
+ /**
+ * Sets the notification token used to register for entitlement configuration request
+ * from network. Used by HTTP parameter {@code notif_token} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param notificationToken The notification token used to register for entitlement
+ * configuration request from network.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setNotificationToken(@NonNull String notificationToken);
+
+ /**
+ * Sets the action associated with the notification token. Used by HTTP parameter
+ * {@code notif_action} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param notificationAction The action associated with the notification token.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setNotificationAction(
+ @NotificationAction int notificationAction);
+
+ /** Returns the {@link CheckEligibilityRequest} object. */
+ @NonNull
+ public abstract CheckEligibilityRequest build();
+ }
+ }
+
+ /**
+ * Check eligibility response described in GSMA Service Entitlement Configuration section 6.5.2.
+ */
+ @AutoValue
+ public abstract static class CheckEligibilityResponse extends OdsaResponse {
+ /** Returns the result of check eligibility request. */
+ @EligibilityResult
+ public abstract int appEligibility();
+
+ /** Indicates the applicable companion device services. */
+ @NonNull
+ @CompanionService
+ public abstract ImmutableList<String> companionDeviceServices();
+
+ /**
+ * The provided URL shall present a web view to user on the reason(s) why the ODSA app
+ * cannot be used/invoked.
+ */
+ @Nullable
+ public abstract URL notEnabledUrl();
+
+ /**
+ * User data sent to the Service Provider when requesting the {@link #notEnabledUrl()} web
+ * view. It should contain user-specific attributes to improve user experience. The format
+ * must follow the {@link #notEnabledContentsType()} parameter. For content types of
+ * {@code JSON} and {@code XML}, it is possible to provide the base64 encoding of the value
+ * by preceding it with {@code encodedValue=}.
+ */
+ @NonNull
+ public abstract String notEnabledUserData();
+
+ /**
+ * Specifies content and HTTP method to use when reaching out to the web server specified in
+ * {@link #notEnabledUrl()}.
+ */
+ @ContentType
+ public abstract int notEnabledContentsType();
+
+ /** Returns the builder. */
+ public static Builder builder() {
+ return new AutoValue_CheckEligibilityOperation_CheckEligibilityResponse.Builder()
+ .setAppEligibility(ELIGIBILITY_RESULT_UNKNOWN)
+ .setCompanionDeviceServices(ImmutableList.of())
+ .setNotEnabledUserData("")
+ .setNotEnabledContentsType(HttpConstants.UNKNOWN);
+ }
+
+ /** The builder. */
+ @AutoValue.Builder
+ public abstract static class Builder extends OdsaResponse.Builder {
+ /**
+ * Set the eligibility.
+ *
+ * @param eligibility The result of check eligibility request.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setAppEligibility(@EligibilityResult int eligibility);
+
+ /**
+ * Set the companion device services.
+ *
+ * @param companionDeviceServices The applicable companion device services.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionDeviceServices(
+ @NonNull @CompanionService ImmutableList<String> companionDeviceServices);
+
+ /**
+ * Set the URL presenting a web view to user on the reason(s) why the ODSA app cannot be
+ * used/invoked.
+ *
+ * @param url The provided URL shall present a web view to user on the reason(s) why the
+ * ODSA app cannot be used/invoked.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setNotEnabledUrl(@NonNull URL url);
+
+ /**
+ * Set the user data sent to the Service Provider when requesting the
+ * {@link #notEnabledUrl()} web view.
+ *
+ * @param notEnabledUserData User data sent to the Service Provider when requesting the
+ * {@link #notEnabledUrl()} web view. It should contain
+ * user-specific attributes to improve user experience. The
+ * format must follow the {@link #notEnabledContentsType()}
+ * parameter. For content types of {@link HttpConstants#JSON}
+ * and {@link HttpConstants#XML}, it is possible to provide
+ * the base64 encoding of the value by preceding it with
+ * {@code encodedValue=}.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setNotEnabledUserData(@NonNull String notEnabledUserData);
+
+ /**
+ * Set the content and HTTP method to use when reaching out to the web server specified
+ * in {@link #notEnabledUrl()}.
+ *
+ * @param notEnabledContentsType Specifies content and HTTP method to use when reaching
+ * out to the web server specified in
+ * {@link #notEnabledUrl()}.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setNotEnabledContentsType(
+ @ContentType int notEnabledContentsType);
+
+ /** Build the {@link CheckEligibilityResponse} object. */
+ public abstract CheckEligibilityResponse build();
+ }
+ }
+
+ private CheckEligibilityOperation() {
+ }
+}
diff --git a/java/com/android/libraries/entitlement/odsa/CompanionDeviceInfo.java b/java/com/android/libraries/entitlement/odsa/CompanionDeviceInfo.java
new file mode 100644
index 0000000..6762e79
--- /dev/null
+++ b/java/com/android/libraries/entitlement/odsa/CompanionDeviceInfo.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.odsa;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Companion device info described in GSMA Service Entitlement Configuration section 6.5.5 table 41.
+ */
+@AutoValue
+public abstract class CompanionDeviceInfo {
+ /**
+ * User friendly identification for the companion device which can be used by the Service
+ * Provider in Web Views.
+ */
+ @NonNull
+ public abstract String companionTerminalFriendlyName();
+
+ /** Manufacturer of the companion device. */
+ @NonNull
+ public abstract String companionTerminalVendor();
+
+ /** Model of the companion device. */
+ @Nullable
+ public abstract String companionTerminalModel();
+
+ /** eUICC identifier (EID) of the companion device being managed. */
+ @Nullable
+ public abstract String companionTerminalEid();
+
+ /** Returns the builder of {@link CompanionDeviceInfo}. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_CompanionDeviceInfo.Builder();
+ }
+
+ /** The builder. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Set user friendly identification for the companion device.
+ *
+ * @param companionTerminalFriendlyName User friendly identification for the companion
+ * device which can be used by the Service Provider in
+ * Web Views.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalFriendlyName(
+ @NonNull String companionTerminalFriendlyName);
+
+ /**
+ * Set manufacturer of the companion device.
+ *
+ * @param companionTerminalVendor manufacturer of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalVendor(@NonNull String companionTerminalVendor);
+
+ /**
+ * Set model of the companion device.
+ *
+ * @param companionTerminalModel Model of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalModel(@NonNull String companionTerminalModel);
+
+ /**
+ * Set EID of the companion device.
+ *
+ * @param companionTerminalEid eUICC identifier (EID) of the companion device being managed.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalEid(@NonNull String companionTerminalEid);
+
+ /** Build the CompanionDeviceInfo object. */
+ @NonNull
+ public abstract CompanionDeviceInfo build();
+ }
+}
diff --git a/java/com/android/libraries/entitlement/odsa/DownloadInfo.java b/java/com/android/libraries/entitlement/odsa/DownloadInfo.java
new file mode 100644
index 0000000..d087ec9
--- /dev/null
+++ b/java/com/android/libraries/entitlement/odsa/DownloadInfo.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.odsa;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+/**
+ * Download information described in GSMA Service Entitlement Configuration section 6.5.3 table 38.
+ */
+@AutoValue
+public abstract class DownloadInfo {
+ /**
+ * The ICCID of the eSIM profile to download from SM-DP+. This is not an empty string when
+ * {@link #profileSmdpAddresses()} is used to trigger the profile download.
+ */
+ @NonNull
+ public abstract String profileIccid();
+
+ /**
+ * Address(es) of SM-DP+ to obtain eSIM profile. It is an empty list if {@link
+ * #profileActivationCode()} is not empty.
+ */
+ @NonNull
+ public abstract ImmutableList<String> profileSmdpAddresses();
+
+ /**
+ * Activation code as defined in SGP.22 to permit the download of an eSIM profile from an
+ * SM-DP+. It is an empty string if {@link #profileSmdpAddresses()} is not empty.
+ */
+ @NonNull
+ public abstract String profileActivationCode();
+
+ /** Returns builder of {@link DownloadInfo}. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_DownloadInfo.Builder()
+ .setProfileActivationCode("")
+ .setProfileSmdpAddresses(ImmutableList.of())
+ .setProfileIccid("");
+ }
+
+ /** Builder of DownloadInfo. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Set the ICCID of the download profile.
+ *
+ * @param iccid The ICCID of the eSIM profile to download from SM-DP+.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setProfileIccid(@NonNull String iccid);
+
+ /**
+ * Set the activation code.
+ *
+ * @param activationCode Activation code as defined in SGP.22 to permit the download of an
+ * eSIM profile from an SM-DP+.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setProfileActivationCode(@NonNull String activationCode);
+
+ /**
+ * Set address(es) of SM-DP+ to obtain eSIM profile.
+ *
+ * @param smdpAddress Address(es) of SM-DP+ to obtain eSIM profile.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setProfileSmdpAddresses(@NonNull ImmutableList<String> smdpAddress);
+
+ /** Build the DownloadInfo object. */
+ @NonNull
+ public abstract DownloadInfo build();
+ }
+}
diff --git a/java/com/android/libraries/entitlement/odsa/GetPhoneNumberOperation.java b/java/com/android/libraries/entitlement/odsa/GetPhoneNumberOperation.java
new file mode 100644
index 0000000..3a30f0a
--- /dev/null
+++ b/java/com/android/libraries/entitlement/odsa/GetPhoneNumberOperation.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.odsa;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Get phone number operation described in GSMA Service Entitlement Configuration section 6.
+ */
+public final class GetPhoneNumberOperation {
+ /**
+ * Get phone number request described in GSMA Service Entitlement Configuration section 6.4.8.
+ */
+ @AutoValue
+ public abstract static class GetPhoneNumberRequest {
+ /**
+ * Returns the terminal id.
+ */
+ @NonNull
+ public abstract String terminalId();
+
+ /** Returns a new {@link GetPhoneNumberRequest.Builder} object. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_GetPhoneNumberOperation_GetPhoneNumberRequest
+ .Builder()
+ .setTerminalId("");
+ }
+
+ /** Builder. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the terminal id.
+ *
+ * @param terminalId The terminal id.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTerminalId(@NonNull String terminalId);
+
+ /** Returns the {@link GetPhoneNumberRequest} object. */
+ @NonNull
+ public abstract GetPhoneNumberRequest build();
+ }
+ }
+
+ /**
+ * Get phone number response described in GSMA Service Entitlement Configuration section
+ * 6.5.8.
+ */
+ @AutoValue
+ public abstract static class GetPhoneNumberResponse extends OdsaResponse {
+
+ /** The phone number of the subscriber in E.164 format. */
+ public abstract String msisdn();
+
+ /** Returns a new {@link GetPhoneNumberResponse.Builder} object. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_GetPhoneNumberOperation_GetPhoneNumberResponse
+ .Builder()
+ .setMsisdn("");
+ }
+
+ /** Builder. */
+ @AutoValue.Builder
+ public abstract static class Builder extends OdsaResponse.Builder {
+ /**
+ * Sets the phone number of the subscriber.
+ *
+ * @param msisdn The phone number of the subscriber in E.164 format.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setMsisdn(@NonNull String msisdn);
+
+ /** Returns the {@link GetPhoneNumberResponse} object. */
+ @NonNull
+ public abstract GetPhoneNumberResponse build();
+ }
+ }
+
+ private GetPhoneNumberOperation() {
+ }
+}
diff --git a/java/com/android/libraries/entitlement/odsa/ManageServiceOperation.java b/java/com/android/libraries/entitlement/odsa/ManageServiceOperation.java
new file mode 100644
index 0000000..34868a4
--- /dev/null
+++ b/java/com/android/libraries/entitlement/odsa/ManageServiceOperation.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.odsa;
+
+import androidx.annotation.NonNull;
+
+import com.android.libraries.entitlement.EsimOdsaOperation;
+import com.android.libraries.entitlement.EsimOdsaOperation.CompanionService;
+import com.android.libraries.entitlement.EsimOdsaOperation.OdsaOperationType;
+import com.android.libraries.entitlement.EsimOdsaOperation.OdsaServiceStatus;
+import com.android.libraries.entitlement.utils.Ts43Constants;
+import com.android.libraries.entitlement.utils.Ts43Constants.AppId;
+
+import com.google.auto.value.AutoValue;
+
+/** Manage service operation described in GSMA Service Entitlement Configuration section 6. */
+public final class ManageServiceOperation {
+ /**
+ * HTTP request parameters specific to on device service activation (ODSA) manage service
+ * request. See GSMA spec TS.43 section 6.2.
+ */
+ @AutoValue
+ public abstract static class ManageServiceRequest {
+ /**
+ * Returns the application id. Can only be {@link Ts43Constants#APP_ODSA_COMPANION}, {@link
+ * Ts43Constants#APP_ODSA_PRIMARY}, or
+ * {@link Ts43Constants#APP_ODSA_SERVER_INITIATED_REQUESTS}.
+ */
+ @AppId
+ public abstract String appId();
+
+ /**
+ * Returns the detailed type of the eSIM ODSA operation. Used by HTTP parameter {@code
+ * operation_type}.
+ */
+ @OdsaOperationType
+ public abstract int operationType();
+
+ /**
+ * Returns the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * {@code companion_terminal_id}.
+ */
+ @NonNull
+ public abstract String companionTerminalId();
+
+ /**
+ * Returns the OEM of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_vendor}.
+ */
+ @NonNull
+ public abstract String companionTerminalVendor();
+
+ /**
+ * Returns the model of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_model}.
+ */
+ @NonNull
+ public abstract String companionTerminalModel();
+
+ /**
+ * Returns the software version of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_sw_version}.
+ */
+ @NonNull
+ public abstract String companionTerminalSoftwareVersion();
+
+ /**
+ * Returns the user-friendly version of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_friendly_name}.
+ */
+ @NonNull
+ public abstract String companionTerminalFriendlyName();
+
+ /**
+ * Returns the service type of the companion device, e.g. if the MSISDN is same as the
+ * primary device. Used by HTTP parameter {@code companion_terminal_service}.
+ */
+ @NonNull
+ @CompanionService
+ public abstract String companionTerminalService();
+
+ /**
+ * Returns the ICCID of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_iccid}.
+ */
+ @NonNull
+ public abstract String companionTerminalIccid();
+
+ /** Returns a new {@link Builder} object. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_ManageServiceOperation_ManageServiceRequest.Builder()
+ .setAppId(Ts43Constants.APP_UNKNOWN)
+ .setOperationType(EsimOdsaOperation.OPERATION_TYPE_NOT_SET)
+ .setCompanionTerminalId("")
+ .setCompanionTerminalVendor("")
+ .setCompanionTerminalModel("")
+ .setCompanionTerminalSoftwareVersion("")
+ .setCompanionTerminalFriendlyName("")
+ .setCompanionTerminalService(EsimOdsaOperation.COMPANION_SERVICE_UNKNOWN)
+ .setCompanionTerminalIccid("");
+ }
+
+ /** Builder */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the application id.
+ *
+ * @param appId The application id. Can only be
+ * {@link Ts43Constants#APP_ODSA_COMPANION},
+ * {@link Ts43Constants#APP_ODSA_PRIMARY}, or {@link
+ * Ts43Constants#APP_ODSA_SERVER_INITIATED_REQUESTS}.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setAppId(@NonNull @AppId String appId);
+
+ /**
+ * Sets the detailed type of the eSIM ODSA operation.
+ *
+ * @param operationType Operation type. Only {@link
+ * EsimOdsaOperation#OPERATION_TYPE_ACTIVATE_SERVICE} and {@link
+ * EsimOdsaOperation#OPERATION_TYPE_DEACTIVATE_SERVICE} are
+ * allowed.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setOperationType(@OdsaOperationType int operationType);
+
+ /**
+ * Sets the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * {@code companion_terminal_id} if set.
+ *
+ * @param companionTerminalId The unique identifier of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalId(String companionTerminalId);
+
+ /**
+ * Sets the OEM of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_vendor} if set.
+ *
+ * @param companionTerminalVendor The OEM of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalVendor(String companionTerminalVendor);
+
+ /**
+ * Sets the model of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_model} if set.
+ *
+ * @param companionTerminalModel The model of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalModel(
+ @NonNull String companionTerminalModel);
+
+ /**
+ * Sets the software version of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_sw_version} if set.
+ *
+ * @param companionTerminalSoftwareVersion The software version of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalSoftwareVersion(
+ @NonNull String companionTerminalSoftwareVersion);
+
+ /**
+ * Sets the user-friendly version of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_friendly_name} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalFriendlyName The user-friendly version of the companion
+ * device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalFriendlyName(
+ @NonNull String companionTerminalFriendlyName);
+
+ /**
+ * Sets the service type of the companion device, e.g. if the MSISDN is same as the
+ * primary device. Used by HTTP parameter {@code companion_terminal_service} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalService The service type of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalService(
+ @NonNull @CompanionService String companionTerminalService);
+
+ /**
+ * Sets the ICCID of the companion device. Used by HTTP parameter {@code
+ * companion_terminal_iccid} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalIccid The ICCID of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalIccid(
+ @NonNull String companionTerminalIccid);
+
+ /** Build the {@link ManageServiceRequest} object. */
+ @NonNull
+ public abstract ManageServiceRequest build();
+ }
+ }
+
+ /**
+ * Manage service response described in GSMA Service Entitlement Configuration section 6.5.4
+ * table 39.
+ */
+ @AutoValue
+ public abstract static class ManageServiceResponse extends OdsaResponse {
+ /** Service status. */
+ @OdsaServiceStatus
+ public abstract int serviceStatus();
+
+ /** Returns a new {@link ManageServiceResponse.Builder} object. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_ManageServiceOperation_ManageServiceResponse.Builder()
+ .setServiceStatus(EsimOdsaOperation.SERVICE_STATUS_UNKNOWN);
+ }
+
+ /** Builder */
+ @AutoValue.Builder
+ public abstract static class Builder extends OdsaResponse.Builder {
+ /**
+ * Set the service status.
+ *
+ * @param serviceStatus Service status
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setServiceStatus(@OdsaServiceStatus int serviceStatus);
+
+ /** Build the {@link ManageServiceResponse} object. */
+ @NonNull
+ public abstract ManageServiceResponse build();
+ }
+ }
+
+ private ManageServiceOperation() {
+ }
+}
diff --git a/java/com/android/libraries/entitlement/odsa/ManageSubscriptionOperation.java b/java/com/android/libraries/entitlement/odsa/ManageSubscriptionOperation.java
new file mode 100644
index 0000000..8c06b5f
--- /dev/null
+++ b/java/com/android/libraries/entitlement/odsa/ManageSubscriptionOperation.java
@@ -0,0 +1,732 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.odsa;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.libraries.entitlement.EsimOdsaOperation;
+import com.android.libraries.entitlement.EsimOdsaOperation.CompanionService;
+import com.android.libraries.entitlement.EsimOdsaOperation.OdsaOperationType;
+import com.android.libraries.entitlement.utils.HttpConstants;
+import com.android.libraries.entitlement.utils.HttpConstants.ContentType;
+import com.android.libraries.entitlement.utils.Ts43Constants;
+import com.android.libraries.entitlement.utils.Ts43Constants.AppId;
+import com.android.libraries.entitlement.utils.Ts43Constants.NotificationAction;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.net.URL;
+
+/**
+ * Manage subscription operation described in GSMA Service Entitlement Configuration section 6.5.3.
+ */
+public final class ManageSubscriptionOperation {
+ /**
+ * HTTP request parameters specific to on device service activation (ODSA) manage subscription
+ * request. See GSMA spec TS.43 section 6.2.
+ */
+ @AutoValue
+ public abstract static class ManageSubscriptionRequest {
+ /**
+ * Returns the application id. Can only be {@link Ts43Constants#APP_ODSA_COMPANION},
+ * {@link Ts43Constants#APP_ODSA_PRIMARY}, or
+ * {@link Ts43Constants#APP_ODSA_SERVER_INITIATED_REQUESTS}.
+ */
+ @NonNull
+ @AppId
+ public abstract String appId();
+
+ /**
+ * Returns the detailed type of the eSIM ODSA operation. Used by HTTP parameter
+ * {@code operation_type}.
+ */
+ @OdsaOperationType
+ public abstract int operationType();
+
+ /**
+ * Returns the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * {@code companion_terminal_id}.
+ */
+ @NonNull
+ public abstract String companionTerminalId();
+
+ /**
+ * Returns the OEM of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_vendor}.
+ */
+ @NonNull
+ public abstract String companionTerminalVendor();
+
+ /**
+ * Returns the model of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_model}.
+ */
+ @NonNull
+ public abstract String companionTerminalModel();
+
+ /**
+ * Returns the software version of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_sw_version}.
+ */
+ @NonNull
+ public abstract String companionTerminalSoftwareVersion();
+
+ /**
+ * Returns the user-friendly version of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_friendly_name}.
+ */
+ @NonNull
+ public abstract String companionTerminalFriendlyName();
+
+ /**
+ * Returns the service type of the companion device, e.g. if the MSISDN is same as the
+ * primary device. Used by HTTP parameter {@code companion_terminal_service}.
+ */
+ @NonNull
+ @CompanionService
+ public abstract String companionTerminalService();
+
+ /**
+ * Returns the ICCID of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_iccid}.
+ */
+ @NonNull
+ public abstract String companionTerminalIccid();
+
+ /**
+ * Returns the EID of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_eid}.
+ */
+ @NonNull
+ public abstract String companionTerminalEid();
+
+ /**
+ * Returns the ICCID of the primary device eSIM. Used by HTTP parameter
+ * {@code terminal_iccid}.
+ */
+ @NonNull
+ public abstract String terminalIccid();
+
+ /**
+ * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
+ * {@code terminal_eid}.
+ */
+ @NonNull
+ public abstract String terminalEid();
+
+ /**
+ * Returns the unique identifier of the primary device eSIM, like the IMEI associated with
+ * the eSIM. Used by HTTP parameter {@code target_terminal_id}.
+ */
+ @NonNull
+ public abstract String targetTerminalId();
+
+ /**
+ * Returns the unique identifiers of the primary device eSIM if more than one, like the
+ * IMEIs on dual-SIM devices. Used by HTTP parameter {@code target_terminal_imeis}.
+ *
+ * <p>This is a non-standard params required by some carriers.
+ */
+ @NonNull
+ public abstract ImmutableList<String> targetTerminalIds();
+
+ /**
+ * Returns the ICCID primary device eSIM. Used by HTTP parameter
+ * {@code target_terminal_iccid}.
+ */
+ @NonNull
+ public abstract String targetTerminalIccid();
+
+ /**
+ * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
+ * {@code target_terminal_eid}.
+ */
+ @NonNull
+ public abstract String targetTerminalEid();
+
+ /**
+ * Returns the serial number of primary device. Used by HTTP parameter
+ * {@code target_terminal_sn}.
+ *
+ * <p>This is a non-standard params required by some carriers.
+ */
+ @NonNull
+ public abstract String targetTerminalSerialNumber();
+
+ /**
+ * Returns the model of primary device. Used by HTTP parameter
+ * {@code target_terminal_model}.
+ *
+ * <p>This is a non-standard params required by some carriers.
+ */
+ @NonNull
+ public abstract String targetTerminalModel();
+
+ /**
+ * Returns the unique identifier of the old device eSIM, like the IMEI associated with the
+ * eSIM. Used by HTTP parameter {@code old_terminal_id}.
+ */
+ @NonNull
+ public abstract String oldTerminalId();
+
+ /**
+ * Returns the ICCID of old device eSIM. Used by HTTP parameter {@code old_terminal_iccid}.
+ */
+ @NonNull
+ public abstract String oldTerminalIccid();
+
+ /**
+ * Returns the identifier of the specific plan offered by an MNO. Used by HTTP parameter
+ * {@code plan_id}.
+ */
+ @NonNull
+ public abstract String planId();
+
+ /**
+ * Returns the notification token used to register for entitlement configuration request
+ * from network. Used by HTTP parameter {@code notif_token}.
+ */
+ @NonNull
+ public abstract String notificationToken();
+
+ /**
+ * Returns the action associated with the notification token. Used by HTTP parameter
+ * {@code notif_action}.
+ */
+ @NotificationAction
+ public abstract int notificationAction();
+
+ /** Returns a new {@link Builder} object. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_ManageSubscriptionOperation_ManageSubscriptionRequest.Builder()
+ .setAppId(Ts43Constants.APP_UNKNOWN)
+ .setOperationType(EsimOdsaOperation.OPERATION_TYPE_NOT_SET)
+ .setCompanionTerminalId("")
+ .setCompanionTerminalVendor("")
+ .setCompanionTerminalModel("")
+ .setCompanionTerminalSoftwareVersion("")
+ .setCompanionTerminalFriendlyName("")
+ .setCompanionTerminalService(EsimOdsaOperation.COMPANION_SERVICE_UNKNOWN)
+ .setCompanionTerminalIccid("")
+ .setCompanionTerminalEid("")
+ .setTerminalIccid("")
+ .setTerminalEid("")
+ .setTargetTerminalId("")
+ .setTargetTerminalIds(ImmutableList.of())
+ .setTargetTerminalIccid("")
+ .setTargetTerminalEid("")
+ .setTargetTerminalSerialNumber("")
+ .setTargetTerminalModel("")
+ .setOldTerminalId("")
+ .setOldTerminalIccid("")
+ .setPlanId("")
+ .setNotificationToken("")
+ .setNotificationAction(Ts43Constants.NOTIFICATION_ACTION_ENABLE_FCM);
+ }
+
+ /** Builder */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the application id.
+ *
+ * @param appId The application id. Can only be
+ * {@link Ts43Constants#APP_ODSA_COMPANION},
+ * {@link Ts43Constants#APP_ODSA_PRIMARY}, or {@link
+ * Ts43Constants#APP_ODSA_SERVER_INITIATED_REQUESTS}.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setAppId(@NonNull @AppId String appId);
+
+ /**
+ * Sets the detailed type of the eSIM ODSA operation. Used by HTTP parameter
+ * {@code operation_type} if set.
+ *
+ * @param operationType The detailed type of the eSIM ODSA operation.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setOperationType(@OdsaOperationType int operationType);
+
+ /**
+ * Sets the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * {@code companion_terminal_id} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalId The unique identifier of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalId(String companionTerminalId);
+
+ /**
+ * Sets the OEM of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_vendor} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalVendor The OEM of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalVendor(
+ @NonNull String companionTerminalVendor);
+
+ /**
+ * Sets the model of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_model} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalModel The model of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalModel(
+ @NonNull String companionTerminalModel);
+
+ /**
+ * Sets the software version of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_sw_version} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalSoftwareVersion The software version of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalSoftwareVersion(
+ @NonNull String companionTerminalSoftwareVersion);
+
+ /**
+ * Sets the user-friendly version of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_friendly_name} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalFriendlyName The user-friendly version of the companion
+ * device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalFriendlyName(
+ @NonNull String companionTerminalFriendlyName);
+
+ /**
+ * Sets the service type of the companion device, e.g. if the MSISDN is same as the
+ * primary device. Used by HTTP parameter {@code companion_terminal_service} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalService The service type of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalService(
+ @NonNull @CompanionService String companionTerminalService);
+
+ /**
+ * Sets the ICCID of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_iccid} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalIccid The ICCID of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalIccid(
+ @NonNull String companionTerminalIccid);
+
+ /**
+ * Sets the eUICC identifier (EID) of the companion device. Used by HTTP parameter
+ * {@code companion_terminal_eid} if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @param companionTerminalEid The eUICC identifier (EID) of the companion device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setCompanionTerminalEid(@NonNull String companionTerminalEid);
+
+ /**
+ * Sets the ICCID of the primary device eSIM in case of primary SIM not present. Used by
+ * HTTP parameter {@code terminal_eid} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param terminalIccid The ICCID of the primary device eSIM in case of primary SIM not
+ * present.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTerminalIccid(@NonNull String terminalIccid);
+
+ /**
+ * Sets the eUICC identifier (EID) of the primary device eSIM in case of primary SIM not
+ * present. Used by HTTP parameter {@code terminal_eid} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param terminalEid The eUICC identifier (EID) of the primary device eSIM in case of
+ * primary SIM not present.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTerminalEid(@NonNull String terminalEid);
+
+ /**
+ * Sets the unique identifiers of the primary device eSIM if more than one, like the
+ * IMEIs on dual-SIM devices. Used by HTTP parameter {@code target_terminal_imeis}
+ * if set.
+ *
+ * <p>This is a non-standard params required by some carriers.
+ *
+ * @param targetTerminalIds The unique identifiers of the primary device eSIM if more
+ * than one.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalIds(
+ @NonNull ImmutableList<String> targetTerminalIds);
+
+ /**
+ * Sets the unique identifier of the primary device eSIM in case of multiple SIM, like
+ * the IMEI associated with the eSIM. Used by HTTP parameter {@code target_terminal_id}
+ * if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param targetTerminalId The unique identifier of the primary device eSIM in case of
+ * multiple SIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalId(@NonNull String targetTerminalId);
+
+ /**
+ * Sets the ICCID primary device eSIM in case of multiple SIM. Used by HTTP parameter
+ * {@code target_terminal_iccid} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param targetTerminalIccid The ICCID primary device eSIM in case of multiple SIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalIccid(@NonNull String targetTerminalIccid);
+
+ /**
+ * Sets the eUICC identifier (EID) of the primary device eSIM in case of multiple SIM.
+ * Used by HTTP parameter {@code target_terminal_eid} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param targetTerminalEid The eUICC identifier (EID) of the primary device eSIM in
+ * case of multiple SIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalEid(@NonNull String targetTerminalEid);
+
+ /**
+ * Sets the serial number of primary device. Used by HTTP parameter
+ * {@code target_terminal_sn} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param targetTerminalSerialNumber The serial number of primary device. This is a
+ * non-standard params required by some carriers.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalSerialNumber(
+ @NonNull String targetTerminalSerialNumber);
+
+ /**
+ * Sets the model of primary device. Used by HTTP parameter
+ * {@code target_terminal_model} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param targetTerminalModel The model of primary device. This is a non-standard params
+ * required by some carriers.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setTargetTerminalModel(@NonNull String targetTerminalModel);
+
+ /**
+ * Sets the unique identifier of the old device eSIM, like the IMEI associated with the
+ * eSIM.Used by HTTP parameter {@code old_terminal_id} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param oldTerminalId The unique identifier of the old device eSIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setOldTerminalId(@NonNull String oldTerminalId);
+
+ /**
+ * Sets the ICCID old device eSIM. Used by HTTP parameter {@code old_terminal_iccid}
+ * if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param oldTerminalIccid The ICCID old device eSIM.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setOldTerminalIccid(@NonNull String oldTerminalIccid);
+
+ /**
+ * Sets the identifier of the specific plan offered by an MNO. Used by HTTP parameter
+ * {@code plan_id} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param planId The identifier of the specific plan offered by an MNO.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setPlanId(@NonNull String planId);
+
+ /**
+ * Sets the notification token used to register for entitlement configuration request
+ * from network. Used by HTTP parameter {@code notif_token} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param notificationToken The notification token used to register for entitlement
+ * configuration request from network.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setNotificationToken(@NonNull String notificationToken);
+
+ /**
+ * Sets the action associated with the notification token. Used by HTTP parameter
+ * {@code notif_action} if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ *
+ * @param notificationAction The action associated with the notification token.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setNotificationAction(
+ @NotificationAction int notificationAction);
+
+ /** Returns the {@link ManageSubscriptionRequest} object. */
+ @NonNull
+ public abstract ManageSubscriptionRequest build();
+ }
+ }
+
+ /**
+ * Manage subscription response described in GSMA Service Entitlement Configuration section
+ * 6.5.3 table 37.
+ */
+ @AutoValue
+ public abstract static class ManageSubscriptionResponse extends OdsaResponse {
+ /** Subscription result unknown. */
+ public static final int SUBSCRIPTION_RESULT_UNKNOWN = -1;
+
+ /**
+ * Indicates that end-user must go through the subscription web view procedure, using
+ * information included below.
+ */
+ public static final int SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET = 1;
+
+ /**
+ * Indicates that a eSIM profile must be downloaded by the device, with further information
+ * included in response.
+ */
+ public static final int SUBSCRIPTION_RESULT_DOWNLOAD_PROFILE = 2;
+
+ /**
+ * Indicates that subscription flow has ended and the end-user has already downloaded the
+ * eSIM profile so there is no need to perform any other action.
+ */
+ public static final int SUBSCRIPTION_RESULT_DONE = 3;
+
+ /**
+ * Indicates that an eSIM profile is not ready to be downloaded when a user requests to
+ * transfer subscription or to add the new subscription through native UX on the eSIM
+ * device.
+ */
+ public static final int SUBSCRIPTION_RESULT_DELAYED_DOWNLOAD = 4;
+
+ /**
+ * Indicates that subscription flow has ended without completing the ODSA procedure. An eSIM
+ * profile is not available.
+ */
+ public static final int SUBSCRIPTION_RESULT_DISMISS = 5;
+
+ /**
+ * Indicates that the profile in use needs to be deleted to complete the subscription
+ * transfer.
+ */
+ public static final int SUBSCRIPTION_RESULT_DELETE_PROFILE_IN_USE = 6;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ SUBSCRIPTION_RESULT_UNKNOWN,
+ SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET,
+ SUBSCRIPTION_RESULT_DOWNLOAD_PROFILE,
+ SUBSCRIPTION_RESULT_DONE,
+ SUBSCRIPTION_RESULT_DELAYED_DOWNLOAD,
+ SUBSCRIPTION_RESULT_DISMISS,
+ SUBSCRIPTION_RESULT_DELETE_PROFILE_IN_USE
+ })
+ public @interface SubscriptionResult {
+ }
+
+ /** The subscription result. */
+ @SubscriptionResult
+ public abstract int subscriptionResult();
+
+ /**
+ * URL refers to web views responsible for a certain action on the eSIM device subscription.
+ * The
+ * Service Provider can provide different URL based on the operation_type input parameter
+ * ({@link EsimOdsaOperation#OPERATION_TYPE_SUBSCRIBE}, {@link
+ * EsimOdsaOperation#OPERATION_TYPE_UNSUBSCRIBE}, {@link
+ * EsimOdsaOperation#OPERATION_TYPE_CHANGE_SUBSCRIPTION}).
+ *
+ * <p>{@code null} if {@link #subscriptionResult()} is not {@link
+ * #SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET}.
+ */
+ @Nullable
+ public abstract URL subscriptionServiceUrl();
+
+ /**
+ * User data sent to the Service Provider when requesting the
+ * {@link #subscriptionServiceUrl()}
+ * web view. It should contain user-specific attributes to improve user experience.
+ *
+ * <p>{@code null} if {@link #subscriptionResult()} is not {@link
+ * #SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET}.
+ */
+ @Nullable
+ public abstract String subscriptionServiceUserData();
+
+ /**
+ * Specifies content and HTTP method to use when reaching out to the web server specified by
+ * {@link #subscriptionServiceUrl()}.
+ */
+ @ContentType
+ public abstract int subscriptionServiceContentsType();
+
+ /**
+ * Specifies how and where to download the eSIM profile associated with the companion or
+ * primary device.
+ *
+ * <p>{@code null} if {@link #subscriptionResult()} is not {@link
+ * #SUBSCRIPTION_RESULT_DOWNLOAD_PROFILE}.
+ */
+ @Nullable
+ public abstract DownloadInfo downloadInfo();
+
+ /** Returns the builder. */
+ @NonNull
+ public static Builder builder() {
+ return new AutoValue_ManageSubscriptionOperation_ManageSubscriptionResponse.Builder()
+ .setSubscriptionResult(SUBSCRIPTION_RESULT_UNKNOWN)
+ .setSubscriptionServiceContentsType(HttpConstants.UNKNOWN);
+ }
+
+ /** Builder */
+ @AutoValue.Builder
+ public abstract static class Builder extends OdsaResponse.Builder {
+ /**
+ * Set subscription result.
+ *
+ * @param subscriptionResult The subscription result.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setSubscriptionResult(
+ @NonNull @SubscriptionResult int subscriptionResult);
+
+ /**
+ * Set the URL refers to web views responsible for a certain action on the eSIM device
+ * subscription.
+ *
+ * @param url URL refers to web views responsible for a certain action on the eSIM
+ * device subscription. The Service Provider can provide different URL based
+ * on the operation_type input parameter (
+ * {@link EsimOdsaOperation#OPERATION_TYPE_SUBSCRIBE}, {@link
+ * EsimOdsaOperation#OPERATION_TYPE_UNSUBSCRIBE}, {@link
+ * EsimOdsaOperation#OPERATION_TYPE_CHANGE_SUBSCRIPTION}).
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setSubscriptionServiceUrl(@NonNull URL url);
+
+ /**
+ * Set user data sent to the Service Provider.
+ *
+ * @param userData User data sent to the Service Provider when requesting the {@link
+ * #subscriptionServiceUrl()} web view. It should contain user-specific
+ * attributes to improve user experience.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setSubscriptionServiceUserData(@NonNull String userData);
+
+ /**
+ * Set the content type.
+ *
+ * @param contentType Specifies content and HTTP method to use when reaching out to the
+ * web server specified by {@link #subscriptionServiceUrl()}.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setSubscriptionServiceContentsType(
+ @ContentType int contentType);
+
+ /**
+ * Set download information of eSIM profile associated with the companion or primary
+ * device.
+ *
+ * @param downloadInfo Specifies how and where to download the eSIM profile associated
+ * with the companion or primary device.
+ * @return The builder.
+ */
+ @NonNull
+ public abstract Builder setDownloadInfo(@NonNull DownloadInfo downloadInfo);
+
+ /** Returns the {@link ManageSubscriptionResponse} object. */
+ @NonNull
+ public abstract ManageSubscriptionResponse build();
+ }
+ }
+
+ private ManageSubscriptionOperation() {
+ }
+}
diff --git a/java/com/android/libraries/entitlement/odsa/OdsaResponse.java b/java/com/android/libraries/entitlement/odsa/OdsaResponse.java
new file mode 100644
index 0000000..f81a64b
--- /dev/null
+++ b/java/com/android/libraries/entitlement/odsa/OdsaResponse.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.odsa;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.libraries.entitlement.EsimOdsaOperation.OdsaOperationResult;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
+import java.net.URL;
+
+/** ODSA general response described in GSMA Service Entitlement Configuration section 6.5.1. */
+public abstract class OdsaResponse {
+ /** Operation result. */
+ @OdsaOperationResult
+ public abstract int operationResult();
+
+ /**
+ * The provided URL shall present a web view to user on the reason(s) why the authentication
+ * failed.
+ */
+ @Nullable
+ public abstract URL generalErrorUrl();
+
+ /**
+ * User data sent to the Service Provider when requesting the {@link #generalErrorUrl()} web
+ * view. It should contain user-specific attributes to improve user experience.
+ */
+ @Nullable
+ public abstract String generalErrorUserData();
+
+ /** Builder */
+ public abstract static class Builder {
+ /**
+ * Set the operation result.
+ *
+ * @param operationResult The operation result.
+ * @return The builder.
+ */
+ @NonNull
+ @CanIgnoreReturnValue
+ public abstract Builder setOperationResult(@OdsaOperationResult int operationResult);
+
+ /**
+ * Set the URL to the web view to user on the reason(s) why the authentication failed.
+ *
+ * @param url The provided URL shall present a web view to user on the reason(s) why the
+ * authentication failed.
+ * @return The builder.
+ */
+ @NonNull
+ @CanIgnoreReturnValue
+ public abstract Builder setGeneralErrorUrl(@NonNull URL url);
+
+ /**
+ * Set the user data of {@link #generalErrorUrl()}.
+ *
+ * @param userData User data sent to the Service Provider when requesting the {@link
+ * #generalErrorUrl()} web view. It should contain user-specific attributes
+ * to improve user
+ * experience.
+ * @return The builder.
+ */
+ @NonNull
+ @CanIgnoreReturnValue
+ public abstract Builder setGeneralErrorUserData(@NonNull String userData);
+ }
+}
diff --git a/java/com/android/libraries/entitlement/odsa/PlanOffer.java b/java/com/android/libraries/entitlement/odsa/PlanOffer.java
new file mode 100644
index 0000000..123e5ec
--- /dev/null
+++ b/java/com/android/libraries/entitlement/odsa/PlanOffer.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.odsa;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+
+/** Mobile plan described in GSMA Service Entitlement Configuration section 6.5.6 table 43. */
+@AutoValue
+public abstract class PlanOffer {
+ /** ID for the plan offered by the MNO. */
+ @NonNull
+ public abstract String planId();
+
+ /**
+ * Name of the plan offered by the MNO. It is considered as an optional parameter due to it is
+ * not required in any request, but it is recommended to make easier the plan identification.
+ */
+ @Nullable
+ public abstract String planName();
+
+ /**
+ * Description of the plan offered by the MNO. It is considered as an optional parameter due to
+ * it is not required in any request, but it is recommended to make easier the plan
+ * identification.
+ */
+ @Nullable
+ public abstract String planDescription();
+
+ /** Returns the builder of {@link PlanOffer}. */
+ public static Builder builder() {
+ return new AutoValue_PlanOffer.Builder();
+ }
+
+ /** Builder of PlanOffer */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /** Sets ID for the plan offered by the MNO. */
+ @NonNull
+ public abstract Builder setPlanId(@NonNull String planId);
+
+ /**
+ * Sets name of the plan offered by the MNO. It is considered as an optional parameter due
+ * to it is not required in any request, but it is recommended to make easier the plan
+ * identification.
+ */
+ @NonNull
+ public abstract Builder setPlanName(@NonNull String planName);
+
+ /**
+ * Sets description of the plan offered by the MNO. It is considered as an optional
+ * parameter due to it is not required in any request, but it is recommended to make easier
+ * the plan identification.
+ */
+ @NonNull
+ public abstract Builder setPlanDescription(@NonNull String planDescription);
+
+ /** Build the {@link PlanOffer} object. */
+ @NonNull
+ public abstract PlanOffer build();
+ }
+}
diff --git a/java/com/android/libraries/entitlement/utils/DebugUtils.java b/java/com/android/libraries/entitlement/utils/DebugUtils.java
index 062c9b4..d89c572 100644
--- a/java/com/android/libraries/entitlement/utils/DebugUtils.java
+++ b/java/com/android/libraries/entitlement/utils/DebugUtils.java
@@ -18,14 +18,19 @@ package com.android.libraries.entitlement.utils;
import android.os.Build;
import android.os.SystemProperties;
+import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.NonNull;
+
/** Provides API for debugging and not allow to debug on user build. */
public final class DebugUtils {
private static final String TAG = "ServiceEntitlement";
private static final String PROP_PII_LOGGABLE = "dbg.se.pii_loggable";
private static final String BUILD_TYPE_USER = "user";
+ private static final String PROP_FAKE_EAP_AKA_RESPONSE =
+ "persist.entitlement.fake_eap_aka_response";
private DebugUtils() {}
@@ -36,6 +41,25 @@ public final class DebugUtils {
}
}
+ /**
+ * Get the bypass EAP-AKA response. This is only available on debug builds and can be set by
+ * running the following commands, where {@code response} should be the expected response from
+ * an EAP-AKA request:
+ * adb root
+ * adb shell setprop persist.entitlement.fake_eap_aka_response response
+ *
+ * @return The bypass EAP-AKA response, or an empty string if it is either not set or the device
+ * is not on a debug build.
+ */
+ @NonNull
+ public static String getBypassEapAkaResponse() {
+ String bypassResponse = SystemProperties.get(PROP_FAKE_EAP_AKA_RESPONSE);
+ if (TextUtils.isEmpty(bypassResponse) || !isDebugBuild()) {
+ return "";
+ }
+ return bypassResponse;
+ }
+
private static boolean isDebugBuild() {
return !BUILD_TYPE_USER.equals(Build.TYPE);
}
diff --git a/java/com/android/libraries/entitlement/utils/HttpConstants.java b/java/com/android/libraries/entitlement/utils/HttpConstants.java
new file mode 100644
index 0000000..1cd194b
--- /dev/null
+++ b/java/com/android/libraries/entitlement/utils/HttpConstants.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.utils;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Http constants using for entitlement flow of TS.43. */
+public final class HttpConstants {
+ /** HTTP content is unknown. */
+ public static final int UNKNOWN = -1;
+
+ /** HTTP content is JSON. */
+ public static final int JSON = 0;
+
+ /** HTTP content is XML. */
+ public static final int XML = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({UNKNOWN, JSON, XML})
+ public @interface ContentType {}
+
+ private HttpConstants() {}
+} \ No newline at end of file
diff --git a/java/com/android/libraries/entitlement/utils/Ts43Constants.java b/java/com/android/libraries/entitlement/utils/Ts43Constants.java
new file mode 100644
index 0000000..ea7bca7
--- /dev/null
+++ b/java/com/android/libraries/entitlement/utils/Ts43Constants.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement.utils;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** Defines the constants used for TS43 operations. */
+public final class Ts43Constants {
+ /** App ID unknown. For initialization only. */
+ public static final String APP_UNKNOWN = "";
+
+ /** App ID for Voice-Over-LTE entitlement. */
+ public static final String APP_VOLTE = "ap2003";
+
+ /** App ID for Voice-Over-WiFi entitlement. */
+ public static final String APP_VOWIFI = "ap2004";
+
+ /** App ID for SMS-Over-IP entitlement. */
+ public static final String APP_SMSOIP = "ap2005";
+
+ /** App ID for on device service activation (ODSA) for companion device. */
+ public static final String APP_ODSA_COMPANION = "ap2006";
+
+ /** App ID for on device service activation (ODSA) for primary device. */
+ public static final String APP_ODSA_PRIMARY = "ap2009";
+
+ /** App ID for data plan information entitlement. */
+ public static final String APP_DATA_PLAN_BOOST = "ap2010";
+
+ /** App ID for server initiated requests, entitlement and activation. */
+ public static final String APP_ODSA_SERVER_INITIATED_REQUESTS = "ap2011";
+
+ /** App ID for direct carrier billing. */
+ public static final String APP_DIRECT_CARRIER_BILLING = "ap2012";
+
+ /** App ID for private user identity. */
+ public static final String APP_PRIVATE_USER_IDENTITY = "ap2013";
+
+ /** App ID for phone number information. */
+ public static final String APP_PHONE_NUMBER_INFORMATION = "ap2014";
+
+ /** App ID for satellite entitlement. */
+ public static final String APP_SATELLITE_ENTITLEMENT = "ap2016";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef({
+ APP_UNKNOWN,
+ APP_VOLTE,
+ APP_VOWIFI,
+ APP_SMSOIP,
+ APP_ODSA_COMPANION,
+ APP_ODSA_PRIMARY,
+ APP_DATA_PLAN_BOOST,
+ APP_ODSA_SERVER_INITIATED_REQUESTS,
+ APP_DIRECT_CARRIER_BILLING,
+ APP_PRIVATE_USER_IDENTITY,
+ APP_PHONE_NUMBER_INFORMATION,
+ APP_SATELLITE_ENTITLEMENT
+ })
+ public @interface AppId {
+ }
+
+ /**
+ * Check if the application id is valid.
+ *
+ * @param appId The application id.
+ * @return {@code true} if valid, otherwise {@code false}.
+ */
+ public static boolean isValidAppId(@NonNull @AppId String appId) {
+ switch (appId) {
+ case APP_VOLTE:
+ case APP_VOWIFI:
+ case APP_SMSOIP:
+ case APP_ODSA_COMPANION:
+ case APP_ODSA_PRIMARY:
+ case APP_DATA_PLAN_BOOST:
+ case APP_ODSA_SERVER_INITIATED_REQUESTS:
+ case APP_DIRECT_CARRIER_BILLING:
+ case APP_PRIVATE_USER_IDENTITY:
+ case APP_PHONE_NUMBER_INFORMATION:
+ case APP_SATELLITE_ENTITLEMENT:
+ return true;
+ default: // fall through
+ }
+ return false;
+ }
+
+ /**
+ * Action to disable notification token.
+ */
+ public static final int NOTIFICATION_ACTION_DISABLE = 0;
+
+ /**
+ * Action to enable GCM notification token.
+ */
+ public static final int NOTIFICATION_ACTION_ENABLE_GCM = 1;
+
+ /**
+ * Action to enable FCM notification token.
+ */
+ public static final int NOTIFICATION_ACTION_ENABLE_FCM = 2;
+
+ /**
+ * Action to enable WNS push notification token.
+ */
+ public static final int NOTIFICATION_ACTION_ENABLE_WNS = 3;
+
+ /**
+ * Action to enable APNS notification token.
+ */
+ public static final int NOTIFICATION_ACTION_ENABLE_APNS = 4;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ NOTIFICATION_ACTION_DISABLE,
+ NOTIFICATION_ACTION_ENABLE_GCM,
+ NOTIFICATION_ACTION_ENABLE_FCM,
+ NOTIFICATION_ACTION_ENABLE_WNS,
+ NOTIFICATION_ACTION_ENABLE_APNS,
+ })
+ public @interface NotificationAction {}
+
+ /**
+ * Check if the notification action is valid.
+ *
+ * @param notificationAction The notification action.
+ * @return {@code true} if valid, otherwise {@code false}.
+ */
+ public static boolean isValidNotificationAction(@NotificationAction int notificationAction) {
+ switch (notificationAction) {
+ case NOTIFICATION_ACTION_DISABLE:
+ case NOTIFICATION_ACTION_ENABLE_GCM:
+ case NOTIFICATION_ACTION_ENABLE_FCM:
+ case NOTIFICATION_ACTION_ENABLE_WNS:
+ case NOTIFICATION_ACTION_ENABLE_APNS:
+ return true;
+ default: // fall through
+ }
+ return false;
+ }
+
+ /** Default entitlement version. */
+ public static final String DEFAULT_ENTITLEMENT_VERSION = "2.0";
+
+ private Ts43Constants() {
+ }
+} \ No newline at end of file
diff --git a/java/com/android/libraries/entitlement/utils/Ts43XmlDoc.java b/java/com/android/libraries/entitlement/utils/Ts43XmlDoc.java
new file mode 100644
index 0000000..ec6910c
--- /dev/null
+++ b/java/com/android/libraries/entitlement/utils/Ts43XmlDoc.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.libraries.entitlement.utils;
+
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.common.collect.ImmutableList;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+/** Wraps the TS.43 XML raw string and parses it into nodes. */
+public final class Ts43XmlDoc {
+ private static final String TAG = "Ts43XmlDoc";
+ private static final String NODE_CHARACTERISTIC = "characteristic";
+ private static final String NODE_PARM = "parm";
+ private static final String PARM_NAME = "name";
+ private static final String PARM_VALUE = "value";
+
+ /** Type names of characteristics. */
+ public static final class CharacteristicType {
+ private CharacteristicType() {
+ }
+
+ public static final String APPLICATION = "APPLICATION";
+ public static final String PRIMARY_CONFIGURATION = "PrimaryConfiguration";
+ public static final String COMPANION_CONFIGURATIONS = "CompanionConfigurations";
+ public static final String COMPANION_CONFIGURATION = "CompanionConfiguration";
+ public static final String ENTERPRISE_CONFIGURATION = "EnterpriseConfiguration";
+ public static final String USER = "USER";
+ public static final String TOKEN = "TOKEN";
+ public static final String DOWNLOAD_INFO = "DownloadInfo";
+ }
+
+ /** Names of parameters. */
+ public static final class Parm {
+ private Parm() {
+ }
+
+ public static final String TOKEN = "token";
+ public static final String APP_ID = "AppID";
+ public static final String VERSION = "version";
+ public static final String VALIDITY = "validity";
+ public static final String OPERATION_RESULT = "OperationResult";
+ public static final String GENERAL_ERROR_URL = "GeneralErrorURL";
+ public static final String GENERAL_ERROR_USER_DATA = "GeneralErrorUserData";
+ public static final String PRIMARY_APP_ELIGIBILITY = "PrimaryAppEligibility";
+ public static final String COMPANION_APP_ELIGIBILITY = "CompanionAppEligibility";
+ public static final String ENTERPRISE_APP_ELIGIBILITY = "EnterpriseAppEligibility";
+ public static final String NOT_ENABLED_URL = "NotEnabledURL";
+ public static final String NOT_ENABLED_USER_DATA = "NotEnabledUserData";
+ public static final String NOT_ENABLED_CONTENTS_TYPE = "NotEnabledContentsType";
+ public static final String COMPANION_DEVICE_SERVICES = "CompanionDeviceServices";
+ public static final String TEMPORARY_TOKEN = "TemporaryToken";
+ public static final String TEMPORARY_TOKEN_EXPIRY = "TemporaryTokenExpiry";
+ public static final String MSISDN = "msisdn";
+ public static final String ICCID = "ICCID";
+ public static final String SERVICE_STATUS = "ServiceStatus";
+ public static final String POLLING_INTERVAL = "PollingInterval";
+ public static final String SUBSCRIPTION_RESULT = "SubscriptionResult";
+ public static final String SUBSCRIPTION_SERVICE_URL = "SubscriptionServiceURL";
+ public static final String SUBSCRIPTION_SERVICE_USER_DATA = "SubscriptionServiceUserData";
+ public static final String SUBSCRIPTION_SERVICE_CONTENTS_TYPE =
+ "SubscriptionServiceContentsType";
+ public static final String PROFILE_ACTIVATION_CODE = "ProfileActivationCode";
+ public static final String PROFILE_ICCID = "ProfileIccid";
+ public static final String PROFILE_SMDP_ADDRESS = "ProfileSmdpAddress";
+ public static final String OPERATION_TARGETS = "OperationTargets";
+ }
+
+ /** Parameter values of XML response content. */
+ public static final class ParmValues {
+ private ParmValues() {
+ }
+
+ public static final String OPERATION_RESULT_SUCCESS = "1";
+ public static final String OPERATION_RESULT_ERROR_GENERAL = "100";
+ public static final String OPERATION_RESULT_ERROR_INVALID_OPERATION = "101";
+ public static final String OPERATION_RESULT_ERROR_INVALID_PARAMETER = "102";
+ public static final String OPERATION_RESULT_WARNING_NOT_SUPPORTED_OPERATION = "103";
+ public static final String PRIMARY_APP_ELIGIBILITY_ENABLED = "1";
+ public static final String SERVICE_STATUS_ACTIVATED = "1";
+ public static final String SERVICE_STATUS_ACTIVATING = "2";
+ public static final String SERVICE_STATUS_DEACTIVATED = "3";
+ public static final String SERVICE_STATUS_DEACTIVATED_NO_REUSE = "4";
+ public static final String SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET = "1";
+ public static final String SUBSCRIPTION_RESULT_DOWNLOAD_PROFILE = "2";
+ public static final String SUBSCRIPTION_RESULT_DONE = "3";
+ public static final String SUBSCRIPTION_RESULT_DELAYED_DOWNLOAD = "4";
+ public static final String SUBSCRIPTION_RESULT_DISMISS = "5";
+ public static final String SUBSCRIPTION_RESULT_DELETE_PROFILE_IN_USE = "6";
+ public static final String CONTENTS_TYPE_XML = "xml";
+ public static final String CONTENTS_TYPE_JSON = "json";
+ public static final String DISABLED = "0";
+ public static final String ENABLED = "1";
+ public static final String INCOMPATIBLE = "2";
+ }
+
+ /**
+ * Maps characteristics to a map of parameters. Key is the characteristic type. Value is
+ * parameter
+ * name and value. Example: {"APPLICATION" -> {"AppId" -> "ap2009", "OperationResult" -> "1"},
+ * "APPLICATION|PrimaryConfiguration" -> {"ICCID" -> "123", "ServiceStatus" -> "2",
+ * "PollingInterval" -> "1"} }
+ */
+ private final Map<String, Map<String, String>> mCharacteristicsMap = new ArrayMap<>();
+
+ public Ts43XmlDoc(String responseBody) {
+ parseXmlResponse(responseBody);
+ }
+
+ /** Returns {@code true} if a node structure exists for a given characteristicTypes. */
+ public boolean contains(ImmutableList<String> characteristicTypes) {
+ return mCharacteristicsMap.containsKey(TextUtils.join("|", characteristicTypes));
+ }
+
+ /**
+ * Returns param value for given characteristicType and parameterName, or {@code null} if not
+ * found.
+ */
+ @Nullable
+ public String get(ImmutableList<String> characteristicTypes, String parameterName) {
+ Map<String, String> parmMap = mCharacteristicsMap.get(
+ TextUtils.join("|", characteristicTypes));
+ return parmMap == null ? null : parmMap.get(parameterName.toLowerCase(Locale.ROOT));
+ }
+
+ /**
+ * Parses the response body as per format defined in TS.43 New Characteristics for XML-Based
+ * Document.
+ */
+ private void parseXmlResponse(String responseBody) {
+ if (responseBody == null) {
+ return;
+ }
+ // Workaround: some server doesn't escape "&" in XML response and that will cause XML parser
+ // failure later.
+ // This is a quick impl of escaping w/o introducing a ton of new dependencies.
+ responseBody = responseBody.replace("&", "&amp;").replace("&amp;amp;", "&amp;");
+ try {
+ InputSource inputSource = new InputSource(new StringReader(responseBody));
+ DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder docBuilder = builderFactory.newDocumentBuilder();
+ Document doc = docBuilder.parse(inputSource);
+ doc.getDocumentElement().normalize();
+ NodeList nodeList = doc.getDocumentElement().getChildNodes();
+ for (int i = 0; i < nodeList.getLength(); i++) {
+ parseNode(new ArrayList<>(), Objects.requireNonNull(nodeList.item(i)));
+ }
+ } catch (ParserConfigurationException | IOException | SAXException e) {
+ // Nodes that failed to parse won't be stored in nodesMap
+ Log.w(TAG, "e=" + e);
+ }
+ }
+
+ @SuppressWarnings("AndroidJdkLibsChecker") // java.util.Map#getOrDefault
+ /* Parses characteristics and parm values into characteristicsMap. */
+ private void parseNode(List<String> characteristics, Node node) {
+ String nodeName = node.getNodeName();
+ NamedNodeMap attributes = node.getAttributes();
+ if (attributes == null) {
+ return;
+ }
+ if (nodeName.equals(NODE_CHARACTERISTIC)) {
+ Node typeNode = attributes.getNamedItem("type");
+ if (typeNode == null) {
+ return;
+ }
+ characteristics.add(Objects.requireNonNull(typeNode.getNodeValue()));
+ NodeList children = node.getChildNodes();
+ for (int i = 0; i < children.getLength(); i++) {
+ parseNode(characteristics, Objects.requireNonNull(children.item(i)));
+ }
+ characteristics.remove(characteristics.size() - 1);
+ } else if (nodeName.equals(NODE_PARM)) {
+ Node parmNameNode = attributes.getNamedItem(PARM_NAME);
+ Node parmValueNode = attributes.getNamedItem(PARM_VALUE);
+ if (parmNameNode == null || parmValueNode == null) {
+ return;
+ }
+ String characteristicKey = TextUtils.join("|", characteristics);
+ Map<String, String> parmMap =
+ mCharacteristicsMap.getOrDefault(characteristicKey, new ArrayMap<>());
+ parmMap.put(
+ Objects.requireNonNull(parmNameNode.getNodeValue().toLowerCase(Locale.ROOT)),
+ Objects.requireNonNull(parmValueNode.getNodeValue()));
+ mCharacteristicsMap.put(characteristicKey, parmMap);
+ }
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "Ts43XmlDoc: " + mCharacteristicsMap;
+ }
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 4bafe3a..914b3ab 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -48,7 +48,7 @@ android_test {
"service-entitlement-testing-utils",
"testables",
"testng",
- "truth-prebuilt",
+ "truth",
],
certificate: "platform",
test_suites: ["device-tests"],
diff --git a/tests/src/com/android/libraries/entitlement/ServiceEntitlementTest.java b/tests/src/com/android/libraries/entitlement/ServiceEntitlementTest.java
index 7e2b0f1..25812a9 100644
--- a/tests/src/com/android/libraries/entitlement/ServiceEntitlementTest.java
+++ b/tests/src/com/android/libraries/entitlement/ServiceEntitlementTest.java
@@ -29,6 +29,7 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.runner.AndroidJUnit4;
import com.android.libraries.entitlement.eapaka.EapAkaApi;
+import com.android.libraries.entitlement.http.HttpResponse;
import com.google.common.collect.ImmutableList;
@@ -46,6 +47,9 @@ public class ServiceEntitlementTest {
private static final String QUERY_APP_VOWIFI_RESULT = "QUERY_APP_VOWIFI_RESULT";
private static final String QUERY_APP_ODSA_COMPANION_RESULT = "QUERY_APP_ODSA_COMPANION_RESULT";
private static final String QUERY_APP_ODSA_PRIMARY_RESULT = "QUERY_APP_ODSA_PRIMARY_RESULT";
+ private static final String QUERY_OIDC_RESULT = "QUERY_OIDC_RESULT";
+ private static final String QUERY_ENTITLEMENT_STATUS_FROM_OIDC =
+ "QUERY_ENTITLEMENT_STATUS_FROM_OIDC";
private static final String TEST_URL = "https://test.url";
private static final String IMSI = "234107813240779";
@@ -54,6 +58,7 @@ public class ServiceEntitlementTest {
@Rule public final MockitoRule rule = MockitoJUnit.rule();
@Mock EapAkaApi mMockEapAkaApi;
+ @Mock HttpResponse mMockHttpResponse;
@Mock private TelephonyManager mMockTelephonyManager;
@Mock private TelephonyManager mMockTelephonyManagerForSubId;
@@ -66,6 +71,11 @@ public class ServiceEntitlementTest {
mCarrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
mServiceEntitlement = new ServiceEntitlement(mCarrierConfig, mMockEapAkaApi);
mContext = spy(ApplicationProvider.getApplicationContext());
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mMockTelephonyManager);
+ when(mMockTelephonyManager.createForSubscriptionId(SUB_ID))
+ .thenReturn(mMockTelephonyManagerForSubId);
+ when(mMockTelephonyManagerForSubId.getSubscriberId()).thenReturn(IMSI);
+ when(mMockTelephonyManagerForSubId.getSimOperator()).thenReturn(MCCMNC);
}
@Test
@@ -73,20 +83,16 @@ public class ServiceEntitlementTest {
CarrierConfig config = CarrierConfig.builder().build();
ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
ServiceEntitlement serviceEntitlement = new ServiceEntitlement(mContext, config, SUB_ID);
- when(mContext.getSystemService(TelephonyManager.class))
- .thenReturn(mMockTelephonyManager);
- when(mMockTelephonyManager.createForSubscriptionId(SUB_ID))
- .thenReturn(mMockTelephonyManagerForSubId);
- when(mMockTelephonyManagerForSubId.getSubscriberId()).thenReturn(IMSI);
- when(mMockTelephonyManagerForSubId.getSimOperator()).thenReturn(MCCMNC);
- ServiceEntitlementException exception = expectThrows(
- ServiceEntitlementException.class,
- () -> serviceEntitlement.queryEntitlementStatus(
- ImmutableList.of(ServiceEntitlement.APP_VOWIFI), request));
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ serviceEntitlement.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), request));
- assertThat(exception.getErrorCode()).isEqualTo(
- ServiceEntitlementException.ERROR_SERVER_NOT_CONNECTABLE);
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_SERVER_NOT_CONNECTABLE);
assertThat(exception.getMessage()).isEqualTo("Configure connection failed!");
assertThat(exception.getHttpStatus()).isEqualTo(0);
assertThat(exception.getRetryAfter()).isEmpty();
@@ -96,11 +102,16 @@ public class ServiceEntitlementTest {
public void queryEntitlementStatus_appVolte_returnResult() throws Exception {
ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
when(mMockEapAkaApi.queryEntitlementStatus(
- ImmutableList.of(ServiceEntitlement.APP_VOLTE), mCarrierConfig, request))
- .thenReturn(QUERY_APP_VOLTE_RESULT);
+ ImmutableList.of(ServiceEntitlement.APP_VOLTE), mCarrierConfig, request))
+ .thenAnswer(
+ invocation -> {
+ when(mMockHttpResponse.body()).thenReturn(QUERY_APP_VOLTE_RESULT);
+ return mMockHttpResponse;
+ });
assertThat(
- mServiceEntitlement.queryEntitlementStatus(ServiceEntitlement.APP_VOLTE, request))
+ mServiceEntitlement.queryEntitlementStatus(
+ ServiceEntitlement.APP_VOLTE, request))
.isEqualTo(QUERY_APP_VOLTE_RESULT);
}
@@ -108,13 +119,16 @@ public class ServiceEntitlementTest {
public void queryEntitlementStatus_appVowifi_returnResult() throws Exception {
ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
when(mMockEapAkaApi.queryEntitlementStatus(
- ImmutableList.of(ServiceEntitlement.APP_VOWIFI), mCarrierConfig, request))
- .thenReturn(QUERY_APP_VOWIFI_RESULT);
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), mCarrierConfig, request))
+ .thenAnswer(
+ invocation -> {
+ when(mMockHttpResponse.body()).thenReturn(QUERY_APP_VOWIFI_RESULT);
+ return mMockHttpResponse;
+ });
assertThat(
- mServiceEntitlement.queryEntitlementStatus(
- ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
- request))
+ mServiceEntitlement.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), request))
.isEqualTo(QUERY_APP_VOWIFI_RESULT);
}
@@ -123,12 +137,20 @@ public class ServiceEntitlementTest {
ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
EsimOdsaOperation odsaOperation = EsimOdsaOperation.builder().build();
when(mMockEapAkaApi.performEsimOdsaOperation(
- ServiceEntitlement.APP_ODSA_COMPANION, mCarrierConfig, request, odsaOperation))
- .thenReturn(QUERY_APP_ODSA_COMPANION_RESULT);
+ ServiceEntitlement.APP_ODSA_COMPANION,
+ mCarrierConfig,
+ request,
+ odsaOperation))
+ .thenAnswer(
+ invocation -> {
+ when(mMockHttpResponse.body())
+ .thenReturn(QUERY_APP_ODSA_COMPANION_RESULT);
+ return mMockHttpResponse;
+ });
assertThat(
- mServiceEntitlement.performEsimOdsa(
- ServiceEntitlement.APP_ODSA_COMPANION, request, odsaOperation))
+ mServiceEntitlement.performEsimOdsa(
+ ServiceEntitlement.APP_ODSA_COMPANION, request, odsaOperation))
.isEqualTo(QUERY_APP_ODSA_COMPANION_RESULT);
}
@@ -137,12 +159,50 @@ public class ServiceEntitlementTest {
ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
EsimOdsaOperation odsaOperation = EsimOdsaOperation.builder().build();
when(mMockEapAkaApi.performEsimOdsaOperation(
- ServiceEntitlement.APP_ODSA_PRIMARY, mCarrierConfig, request, odsaOperation))
- .thenReturn(QUERY_APP_ODSA_PRIMARY_RESULT);
+ ServiceEntitlement.APP_ODSA_PRIMARY,
+ mCarrierConfig,
+ request,
+ odsaOperation))
+ .thenAnswer(
+ invocation -> {
+ when(mMockHttpResponse.body())
+ .thenReturn(QUERY_APP_ODSA_PRIMARY_RESULT);
+ return mMockHttpResponse;
+ });
assertThat(
- mServiceEntitlement.performEsimOdsa(
- ServiceEntitlement.APP_ODSA_PRIMARY, request, odsaOperation))
+ mServiceEntitlement.performEsimOdsa(
+ ServiceEntitlement.APP_ODSA_PRIMARY, request, odsaOperation))
.isEqualTo(QUERY_APP_ODSA_PRIMARY_RESULT);
}
+
+ @Test
+ public void acquireOidcAuthenticationEndpoint_returnResult() throws Exception {
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+ when(mMockEapAkaApi.acquireOidcAuthenticationEndpoint(
+ ServiceEntitlement.APP_ODSA_COMPANION, mCarrierConfig, request))
+ .thenReturn(QUERY_OIDC_RESULT);
+
+ assertThat(
+ mServiceEntitlement.acquireOidcAuthenticationEndpoint(
+ ServiceEntitlement.APP_ODSA_COMPANION, request))
+ .isEqualTo(QUERY_OIDC_RESULT);
+ }
+
+ @Test
+ public void queryEntitlementStatusFromOidc_returnResult() throws Exception {
+ when(mMockEapAkaApi.queryEntitlementStatusFromOidc(
+ ServiceEntitlement.APP_ODSA_PRIMARY, mCarrierConfig, null))
+ .thenAnswer(
+ invocation -> {
+ when(mMockHttpResponse.body())
+ .thenReturn(QUERY_ENTITLEMENT_STATUS_FROM_OIDC);
+ return mMockHttpResponse;
+ });
+
+ assertThat(
+ mServiceEntitlement.queryEntitlementStatusFromOidc(
+ ServiceEntitlement.APP_ODSA_PRIMARY))
+ .isEqualTo(QUERY_ENTITLEMENT_STATUS_FROM_OIDC);
+ }
}
diff --git a/tests/src/com/android/libraries/entitlement/Ts43AuthenticationTest.java b/tests/src/com/android/libraries/entitlement/Ts43AuthenticationTest.java
new file mode 100644
index 0000000..f30e171
--- /dev/null
+++ b/tests/src/com/android/libraries/entitlement/Ts43AuthenticationTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+
+import com.android.libraries.entitlement.Ts43Authentication.Ts43AuthToken;
+import com.android.libraries.entitlement.eapaka.EapAkaApi;
+import com.android.libraries.entitlement.http.HttpResponse;
+import com.android.libraries.entitlement.utils.Ts43Constants;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+import java.net.URL;
+
+@RunWith(AndroidTestingRunner.class)
+public class Ts43AuthenticationTest {
+ private static final String TEST_URL = "https://test.url";
+ private static final String ENTITLEMENT_VERSION = "9.0";
+ private static final String APP_NAME = "com.fake.app";
+ private static final String APP_VERSION = "1.0";
+ private static final String TOKEN = "ASH127AHHA88SF";
+ private static final long VALIDITY = 86400;
+ private static final String IMEI = "861536030196001";
+ private static final ImmutableList<String> COOKIES =
+ ImmutableList.of("key1=value1", "key2=value2");
+
+ private static final String HTTP_RESPONSE_WITH_TOKEN =
+ "<?xml version=\"1.0\"?>"
+ + "<wap-provisioningdoc version=\"1.1\">"
+ + " <characteristic type=\"VERS\">"
+ + " <parm name=\"version\" value=\"1\"/>"
+ + " <parm name=\"validity\" value=\" + " + VALIDITY + "\"/>"
+ + " </characteristic>"
+ + " <characteristic type=\"TOKEN\">"
+ + " <parm name=\"token\" value=\"" + TOKEN + "\"/>"
+ + " <parm name=\"validity\" value=\"" + VALIDITY + "\"/>"
+ + " </characteristic>"
+ + "</wap-provisioningdoc>";
+ private static final String HTTP_RESPONSE_WITHOUT_TOKEN =
+ "<?xml version=\"1.0\"?>"
+ + "<wap-provisioningdoc version=\"1.1\">"
+ + " <characteristic type=\"VERS\">"
+ + " <parm name=\"version\" value=\"1\"/>"
+ + " <parm name=\"validity\" value=\" + " + VALIDITY + "\"/>"
+ + " </characteristic>"
+ + "</wap-provisioningdoc>";
+
+ private static final String HTTP_RESPONSE_WITHOUT_VALIDITY =
+ "<?xml version=\"1.0\"?>"
+ + "<wap-provisioningdoc version=\"1.1\">"
+ + " <characteristic type=\"VERS\">"
+ + " <parm name=\"version\" value=\"1\"/>"
+ + " <parm name=\"validity\" value=\" + " + VALIDITY + "\"/>"
+ + " </characteristic>"
+ + " <characteristic type=\"TOKEN\">"
+ + " <parm name=\"token\" value=\"" + TOKEN + "\"/>"
+ + " </characteristic>"
+ + "</wap-provisioningdoc>";
+
+ private Ts43Authentication mTs43Authentication;
+
+ @Mock
+ private EapAkaApi mMockEapAkaApi;
+
+ @Mock
+ private HttpResponse mMockHttpResponse;
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private TelephonyManager mTelephonyManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlement serviceEntitlement = new ServiceEntitlement(carrierConfig,
+ mMockEapAkaApi);
+ mTs43Authentication = new Ts43Authentication(mContext, new URL(TEST_URL),
+ ENTITLEMENT_VERSION);
+
+ Field field = Ts43Authentication.class.getDeclaredField("mServiceEntitlement");
+ field.setAccessible(true);
+ field.set(mTs43Authentication, serviceEntitlement);
+
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(IMEI).when(mTelephonyManager).getImei(0);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ doReturn(Context.TELEPHONY_SERVICE).when(mContext)
+ .getSystemServiceName(TelephonyManager.class);
+ doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
+ doReturn(mMockHttpResponse).when(mMockEapAkaApi)
+ .queryEntitlementStatus(any(), any(), any());
+ doReturn(COOKIES).when(mMockHttpResponse).cookies();
+ }
+
+ @Test
+ public void testGetAuthToken_receivedValidToken() throws Exception {
+ doReturn(HTTP_RESPONSE_WITH_TOKEN).when(mMockHttpResponse).body();
+ Ts43AuthToken mToken = mTs43Authentication.getAuthToken(
+ 0, Ts43Constants.APP_ODSA_PRIMARY, APP_NAME, APP_VERSION);
+ assertThat(mToken.token()).isEqualTo(TOKEN);
+ assertThat(mToken.validity()).isEqualTo(VALIDITY);
+ }
+
+ @Test
+ public void testGetAuthToken_invalidParams_throwException() {
+ assertThrows(NullPointerException.class, () -> new Ts43Authentication(
+ null, new URL(TEST_URL), ENTITLEMENT_VERSION));
+
+ assertThrows(NullPointerException.class, () -> new Ts43Authentication(
+ mContext, null, ENTITLEMENT_VERSION));
+ }
+
+ @Test
+ public void testGetAuthToken_invalidAppId_throwException() {
+ assertThrows(NullPointerException.class, () -> mTs43Authentication.getAuthToken(
+ 0, null, APP_NAME, APP_VERSION));
+ assertThrows(IllegalArgumentException.class, () -> mTs43Authentication.getAuthToken(
+ 0, "invalid_app_id", APP_NAME, APP_VERSION));
+ }
+
+ @Test
+ public void testGetAuthToken_invalidSlotIndex_throwException() {
+ assertThrows(IllegalArgumentException.class, () -> mTs43Authentication.getAuthToken(
+ 5, Ts43Constants.APP_ODSA_PRIMARY, APP_NAME, APP_VERSION));
+ }
+
+ @Test
+ public void testGetAuthToken_tokenNotAvailable_throwException() {
+ doReturn(HTTP_RESPONSE_WITHOUT_TOKEN).when(mMockHttpResponse).body();
+
+ try {
+ mTs43Authentication.getAuthToken(
+ 0, Ts43Constants.APP_ODSA_PRIMARY, APP_NAME, APP_VERSION);
+ fail("Expected to get exception.");
+ } catch (ServiceEntitlementException e) {
+ assertThat(e.getErrorCode()).isEqualTo(
+ ServiceEntitlementException.ERROR_TOKEN_NOT_AVAILABLE);
+ }
+ }
+
+ @Test
+ public void testGetAuthToken_validityNotAvailable() throws Exception {
+ doReturn(HTTP_RESPONSE_WITHOUT_VALIDITY).when(mMockHttpResponse).body();
+ Ts43AuthToken mToken = mTs43Authentication.getAuthToken(
+ 0, Ts43Constants.APP_ODSA_PRIMARY, APP_NAME, APP_VERSION);
+ assertThat(mToken.token()).isEqualTo(TOKEN);
+ assertThat(mToken.validity()).isEqualTo(Ts43AuthToken.VALIDITY_NOT_AVAILABLE);
+ }
+
+ @Test
+ public void testGetAuthToken_httpResponseError() throws Exception {
+ doThrow(new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_HTTP_STATUS_NOT_SUCCESS, 1234, "http error"))
+ .when(mMockEapAkaApi).queryEntitlementStatus(any(), any(), any());
+ try {
+ mTs43Authentication.getAuthToken(
+ 0, Ts43Constants.APP_ODSA_PRIMARY, APP_NAME, APP_VERSION);
+ fail("Expected to get exception.");
+ } catch (ServiceEntitlementException e) {
+ assertThat(e.getErrorCode()).isEqualTo(
+ ServiceEntitlementException.ERROR_HTTP_STATUS_NOT_SUCCESS);
+ assertThat(e.getHttpStatus()).isEqualTo(1234);
+ }
+ }
+}
diff --git a/tests/src/com/android/libraries/entitlement/Ts43OperationTest.java b/tests/src/com/android/libraries/entitlement/Ts43OperationTest.java
new file mode 100644
index 0000000..97e2193
--- /dev/null
+++ b/tests/src/com/android/libraries/entitlement/Ts43OperationTest.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.entitlement;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+import android.testing.AndroidTestingRunner;
+
+import com.android.libraries.entitlement.eapaka.EapAkaApi;
+import com.android.libraries.entitlement.http.HttpResponse;
+import com.android.libraries.entitlement.odsa.AcquireConfigurationOperation.AcquireConfigurationRequest;
+import com.android.libraries.entitlement.odsa.AcquireConfigurationOperation.AcquireConfigurationResponse;
+import com.android.libraries.entitlement.odsa.AcquireTemporaryTokenOperation.AcquireTemporaryTokenRequest;
+import com.android.libraries.entitlement.odsa.AcquireTemporaryTokenOperation.AcquireTemporaryTokenResponse;
+import com.android.libraries.entitlement.odsa.CheckEligibilityOperation;
+import com.android.libraries.entitlement.odsa.CheckEligibilityOperation.CheckEligibilityRequest;
+import com.android.libraries.entitlement.odsa.CheckEligibilityOperation.CheckEligibilityResponse;
+import com.android.libraries.entitlement.odsa.GetPhoneNumberOperation.GetPhoneNumberRequest;
+import com.android.libraries.entitlement.odsa.GetPhoneNumberOperation.GetPhoneNumberResponse;
+import com.android.libraries.entitlement.odsa.ManageServiceOperation.ManageServiceRequest;
+import com.android.libraries.entitlement.odsa.ManageServiceOperation.ManageServiceResponse;
+import com.android.libraries.entitlement.odsa.ManageSubscriptionOperation.ManageSubscriptionRequest;
+import com.android.libraries.entitlement.odsa.ManageSubscriptionOperation.ManageSubscriptionResponse;
+import com.android.libraries.entitlement.utils.Ts43Constants;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.lang.reflect.Field;
+import java.net.URL;
+
+@RunWith(AndroidTestingRunner.class)
+public class Ts43OperationTest {
+ private static final String TEST_URL = "https://test.url";
+ private static final String ENTITLEMENT_VERSION = "9.0";
+ private static final String TOKEN = "ASH127AHHA88SF";
+ private static final String NEW_TOKEN = "ES7WLERXJH";
+ private static final String SUBSCRIPTION_SERVICE_URL = "http://www.MNO.org/CDSubs";
+ private static final String SUBSCRIPTION_SERVICE_USER_DATA = "imsi=XX";
+ private static final String IMEI = "861536030196001";
+ private static final String TERMINAL_ID = "861536030196005";
+ private static final String COMPANION_TERMINAL_ID = "98112687006099944";
+ private static final String COMPANION_TERMINAL_EID = "JHSDHljhsdfy763hh";
+ private static final String ICCID = "123456789";
+ private static final String PROFILE_SMDP_ADDRESS = "SMDP+ ADDR";
+
+ private static final String TEMPORARY_TOKEN = "A8daAd8ads7fau34789947kjhsfad;kjfh";
+
+ private static final String TEMPORARY_TOKEN_EXPIRY = "2019-01-29T13:15:31Z";
+
+ private static final String NOT_ENABLED_URL = "http://www.MNO.org/AppNotAllowed";
+
+ private static final String NOT_ENABLED_USER_DATA = "msisdn=XX";
+
+ private static final String MSISDN = "+16502530000";
+
+ private static final String MANAGE_SUBSCRIPTION_RESPONSE_CONTINUE_TO_WEBSHEET =
+ "<?xml version=\"1.0\"?>"
+ + "<wap-provisioningdoc version=\"1.1\">"
+ + "<characteristic type=\"VERS\">"
+ + " <parm name=\"version\" value=\"1\"/>"
+ + " <parm name=\"validity\" value=\"172800\"/>"
+ + "</characteristic>"
+ + "<characteristic type=\"TOKEN\">"
+ + " <parm name=\"token\" value=\"" + NEW_TOKEN + "\"/>"
+ + "</characteristic>"
+ + "<characteristic type=\"APPLICATION\">"
+ + " <parm name=\"AppID\" value=\"ap2006\"/>"
+ + " <parm name=\"SubscriptionServiceURL\""
+ + " value=\"" + SUBSCRIPTION_SERVICE_URL + "\"/>"
+ + " <parm name=\"SubscriptionServiceUserData\""
+ + " value=\"" + SUBSCRIPTION_SERVICE_USER_DATA + "\"/>"
+ + " <parm name=\"SubscriptionResult\" value=\"1\"/>"
+ + " <parm name=\"OperationResult\" value=\"1\"/>"
+ + "</characteristic>"
+ + "</wap-provisioningdoc>";
+
+ private static final String MANAGE_SUBSCRIPTION_RESPONSE_DOWNLOAD_PROFILE =
+ "<?xml version=\"1.0\"?>\n"
+ + "<wap-provisioningdoc version=\"1.1\">\n"
+ + " <characteristic type=\"VERS\">\n"
+ + " <parm name=\"version\" value=\"1\"/>\n"
+ + " <parm name=\"validity\" value=\"172800\"/>\n"
+ + " </characteristic>\n"
+ + " <characteristic type=\"TOKEN\">\n"
+ + " <parm name=\"token\" value=\"ASH127AHHA88SF\"/>\n"
+ + " </characteristic>\n"
+ + " <characteristic type=\"APPLICATION\">\n"
+ + " <parm name=\"AppID\" value=\"ap2006\"/>\n"
+ + " <characteristic type=\"DownloadInfo\">\n"
+ + " <parm name=\"ProfileIccid\" value=\"" + ICCID + "\"/>\n"
+ + " <parm name=\"ProfileSmdpAddress\" value=\""
+ + PROFILE_SMDP_ADDRESS + "\"/>\n"
+ + " </characteristic>\n"
+ + " <parm name=\"SubscriptionResult\" value=\"2\"/>\n"
+ + " <parm name=\"OperationResult\" value=\"1\"/>\n"
+ + " </characteristic>\n"
+ + "</wap-provisioningdoc>";
+
+ private static final String ACQUIRE_TEMPORARY_TOKEN_RESPONSE =
+ "<?xml version=\"1.0\"?>\n"
+ + "<wap-provisioningdoc version=\"1.1\">\n"
+ + "<characteristic type=\"VERS\">\n"
+ + " <parm name=\"version\" value=\"1\"/>\n"
+ + " <parm name=\"validity\" value=\"172800\"/>\n"
+ + "</characteristic>\n"
+ + "<characteristic type=\"TOKEN\">\n"
+ + " <parm name=\"token\" value=\"ASH127AHHA88SF\"/>\n"
+ + "</characteristic>\n"
+ + "<characteristic type=\"APPLICATION\">\n"
+ + " <parm name=\"AppID\" value=\"ap2009\"/>\n"
+ + " <parm name=\"TemporaryToken\" value=\"" + TEMPORARY_TOKEN + "\"/>\n"
+ + " <parm name=\"TemporaryTokenExpiry\" "
+ + " value=\"" + TEMPORARY_TOKEN_EXPIRY + "\"/>\n"
+ + " <parm name=\"OperationTargets\"\n"
+ + " value=\"ManageSubscription,AcquireConfiguration\"/>\n"
+ + " <parm name=\"OperationResult\" value=\"1\"/>\n"
+ + "</characteristic>\n"
+ + "</wap-provisioningdoc>";
+
+ private static final String ACQUIRE_CONFIGURATION_RESPONSE =
+ "<?xml version=\"1.0\"?>\n"
+ + "<wap-provisioningdoc version=\"1.1\">\n"
+ + "<characteristic type=\"VERS\">\n"
+ + " <parm name=\"version\" value=\"1\"/>\n"
+ + " <parm name=\"validity\" value=\"172800\"/>\n"
+ + "</characteristic>\n"
+ + "<characteristic type=\"TOKEN\">\n"
+ + " <parm name=\"token\" value=\"ASH127AHHA88SF\"/>\n"
+ + "</characteristic>\n"
+ + "<characteristic type=\"APPLICATION\">\n"
+ + " <parm name=\"AppID\" value=\"ap2006\"/>\n"
+ + " <characteristic type=\"PrimaryConfiguration\">\n"
+ + " <parm name=\"ICCID\" value=\"" + ICCID + "\"/>\n"
+ + " <characteristic type=\"DownloadInfo\">\n"
+ + " <parm name=\"ProfileIccid\" value=\"" + ICCID + "\"/>\n"
+ + " <parm name=\"ProfileSmdpAddress\" value=\""
+ + PROFILE_SMDP_ADDRESS + "\"/>\n"
+ + " </characteristic>\n"
+ + " <parm name=\"ServiceStatus\" value=\"1\"/>\n"
+ + " </characteristic>\n"
+ + " <parm name=\"OperationResult\" value=\"1\"/>\n"
+ + "</characteristic>\n"
+ + "</wap-provisioningdoc>\n";
+
+ private static final String CHECK_ELIGIBILITY_RESPONSE =
+ "<?xml version=\"1.0\"?>\n"
+ + "<wap-provisioningdoc version=\"1.1\">\n"
+ + "<characteristic type=\"VERS\">\n"
+ + " <parm name=\"version\" value=\"1\"/>\n"
+ + " <parm name=\"validity\" value=\"172800\"/>\n"
+ + "</characteristic>\n"
+ + "<characteristic type=\"TOKEN\">\n"
+ + " <parm name=\"token\" value=\"ASH127AHHA88SF\"/>\n"
+ + "</characteristic>\n"
+ + "<characteristic type=\"APPLICATION\">\n"
+ + " <parm name=\"AppID\" value=\"ap2006\"/>\n"
+ + " <parm name=\"CompanionAppEligibility\" value=\"1\"/>\n"
+ + " <parm name=\"CompanionDeviceServices\" value=\"SharedNumber\"/>\n"
+ + " <parm name=\"NotEnabledURL\" value=\"" + NOT_ENABLED_URL + "\"/>\n"
+ + " <parm name=\"NotEnabledUserData\" value=\"" + NOT_ENABLED_USER_DATA
+ + "\"/>\n"
+ + " <parm name=\"OperationResult\" value=\"1\"/>\n"
+ + "</characteristic>\n"
+ + "</wap-provisioningdoc>";
+
+ public String MANAGE_SERVICE_RESPONSE =
+ "<?xml version=\"1.0\"?>\n"
+ + "<wap-provisioningdoc version=\"1.1\">\n"
+ + "<characteristic type=\"VERS\">\n"
+ + " <parm name=\"version\" value=\"1\"/>\n"
+ + " <parm name=\"validity\" value=\"172800\"/>\n"
+ + "</characteristic>\n"
+ + "<characteristic type=\"TOKEN\">\n"
+ + " <parm name=\"token\" value=\"ASH127AHHA88SF\"/>\n"
+ + "</characteristic>\n"
+ + "<characteristic type=\"APPLICATION\">\n"
+ + " <parm name=\"AppID\" value=\"ap2006\"/>\n"
+ + " <parm name=\"ServiceStatus\" value=\"3\"/>\n"
+ + " <parm name=\"OperationResult\" value=\"1\"/>\n"
+ + "</characteristic>\n"
+ + "</wap-provisioningdoc>";
+
+ public String GET_PHONE_NUMBER_RESPONSE =
+ "<?xml version=\"1.0\"?>\n"
+ + "<wap-provisioningdoc version=\"1.1\">\n"
+ + "<characteristic type=\"VERS\">\n"
+ + " <parm name=\"version\" value=\"1\"/>\n"
+ + " <parm name=\"validity\" value=\"172800\"/>\n"
+ + "</characteristic>\n"
+ + "<characteristic type=\"TOKEN\">\n"
+ + " <parm name=\"token\" value=\"ASH127AHHA88SF\"/>\n"
+ + "</characteristic>\n"
+ + "<characteristic type=\"APPLICATION\">\n"
+ + " <parm name=\"AppID\" value=\"ap2014\"/>\n"
+ + " <parm name=\"OperationResult\" value=\"1\"/>\n"
+ + " <parm name=\"MSISDN\" value=\"" + MSISDN + "\"/>\n"
+ + "</characteristic>\n"
+ + "</wap-provisioningdoc>";
+
+ @Mock
+ private EapAkaApi mMockEapAkaApi;
+
+ @Mock
+ private HttpResponse mMockHttpResponse;
+
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private TelephonyManager mTelephonyManager;
+
+ private Ts43Operation mTs43Operation;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlement serviceEntitlement =
+ new ServiceEntitlement(carrierConfig, mMockEapAkaApi);
+ doReturn(mMockHttpResponse).when(mMockEapAkaApi)
+ .performEsimOdsaOperation(any(), any(), any(), any());
+
+ doReturn(2).when(mTelephonyManager).getActiveModemCount();
+ doReturn(IMEI).when(mTelephonyManager).getImei(0);
+ doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt());
+ doReturn(Context.TELEPHONY_SERVICE).when(mContext)
+ .getSystemServiceName(TelephonyManager.class);
+ doReturn(mTelephonyManager).when(mContext).getSystemService(Context.TELEPHONY_SERVICE);
+
+ mTs43Operation = new Ts43Operation(mContext, 0, new URL(TEST_URL),
+ ENTITLEMENT_VERSION, TOKEN, Ts43Operation.TOKEN_TYPE_NORMAL);
+
+ Field field = Ts43Operation.class.getDeclaredField("mServiceEntitlement");
+ field.setAccessible(true);
+ field.set(mTs43Operation, serviceEntitlement);
+ }
+
+ @Test
+ public void testManageSubscription_continueToWebsheet() throws Exception {
+ doReturn(MANAGE_SUBSCRIPTION_RESPONSE_CONTINUE_TO_WEBSHEET).when(mMockHttpResponse).body();
+
+ ManageSubscriptionRequest request = ManageSubscriptionRequest.builder()
+ .setAppId(Ts43Constants.APP_ODSA_PRIMARY)
+ .setOperationType(EsimOdsaOperation.OPERATION_TYPE_SUBSCRIBE)
+ .setCompanionTerminalId(COMPANION_TERMINAL_ID)
+ .setCompanionTerminalEid(COMPANION_TERMINAL_EID)
+ .build();
+
+ ManageSubscriptionResponse response = mTs43Operation.manageSubscription(request);
+ assertThat(response.operationResult()).isEqualTo(
+ EsimOdsaOperation.OPERATION_RESULT_SUCCESS);
+ assertThat(response.subscriptionResult()).isEqualTo(
+ ManageSubscriptionResponse.SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET);
+ assertThat(response.subscriptionServiceUrl()).isEqualTo(new URL(SUBSCRIPTION_SERVICE_URL));
+ assertThat(response.subscriptionServiceUserData())
+ .isEqualTo(SUBSCRIPTION_SERVICE_USER_DATA);
+ }
+
+ @Test
+ public void testManageSubscription_downloadProfile() throws Exception {
+ doReturn(MANAGE_SUBSCRIPTION_RESPONSE_DOWNLOAD_PROFILE).when(mMockHttpResponse).body();
+
+ ManageSubscriptionRequest request = ManageSubscriptionRequest.builder()
+ .setAppId(Ts43Constants.APP_ODSA_PRIMARY)
+ .setOperationType(EsimOdsaOperation.OPERATION_TYPE_SUBSCRIBE)
+ .setCompanionTerminalId(COMPANION_TERMINAL_ID)
+ .setCompanionTerminalEid(COMPANION_TERMINAL_EID)
+ .build();
+
+ ManageSubscriptionResponse response = mTs43Operation.manageSubscription(request);
+ assertThat(response.operationResult()).isEqualTo(
+ EsimOdsaOperation.OPERATION_RESULT_SUCCESS);
+ assertThat(response.subscriptionResult()).isEqualTo(
+ ManageSubscriptionResponse.SUBSCRIPTION_RESULT_DOWNLOAD_PROFILE);
+ assertThat(response.downloadInfo().profileIccid()).isEqualTo(ICCID);
+ assertThat(response.downloadInfo().profileSmdpAddresses())
+ .isEqualTo(ImmutableList.of(PROFILE_SMDP_ADDRESS));
+ }
+
+ @Test
+ public void testAcquireTemporaryToken() throws Exception {
+ doReturn(ACQUIRE_TEMPORARY_TOKEN_RESPONSE).when(mMockHttpResponse).body();
+
+ AcquireTemporaryTokenRequest request = AcquireTemporaryTokenRequest.builder()
+ .setAppId(Ts43Constants.APP_ODSA_PRIMARY)
+ .setOperationTargets(ImmutableList.of(
+ EsimOdsaOperation.OPERATION_MANAGE_SUBSCRIPTION,
+ EsimOdsaOperation.OPERATION_ACQUIRE_CONFIGURATION))
+ .build();
+ AcquireTemporaryTokenResponse response = mTs43Operation.acquireTemporaryToken(request);
+ assertThat(response.operationResult()).isEqualTo(
+ EsimOdsaOperation.OPERATION_RESULT_SUCCESS);
+ assertThat(response.temporaryToken()).isEqualTo(TEMPORARY_TOKEN);
+ assertThat(response.temporaryTokenExpiry().toString()).isEqualTo(TEMPORARY_TOKEN_EXPIRY);
+ assertThat(response.operationTargets()).isEqualTo(ImmutableList.of(
+ EsimOdsaOperation.OPERATION_MANAGE_SUBSCRIPTION,
+ EsimOdsaOperation.OPERATION_ACQUIRE_CONFIGURATION));
+ }
+
+ @Test
+ public void testAcquireConfiguration() throws Exception {
+ doReturn(ACQUIRE_CONFIGURATION_RESPONSE).when(mMockHttpResponse).body();
+ AcquireConfigurationRequest request = AcquireConfigurationRequest.builder()
+ .setAppId(Ts43Constants.APP_ODSA_PRIMARY)
+ .build();
+
+ AcquireConfigurationResponse response = mTs43Operation.acquireConfiguration(request);
+ assertThat(response.operationResult()).isEqualTo(
+ EsimOdsaOperation.OPERATION_RESULT_SUCCESS);
+ assertThat(response.configurations()).hasSize(1);
+ AcquireConfigurationResponse.Configuration config = response.configurations().get(0);
+ assertThat(config.iccid()).isEqualTo(ICCID);
+ assertThat(config.downloadInfo().profileIccid()).isEqualTo(ICCID);
+ assertThat(config.downloadInfo().profileSmdpAddresses()).isEqualTo(
+ ImmutableList.of(PROFILE_SMDP_ADDRESS));
+ assertThat(config.serviceStatus()).isEqualTo(EsimOdsaOperation.SERVICE_STATUS_ACTIVATED);
+ }
+
+ @Test
+ public void testCheckEligibility() throws Exception {
+ doReturn(CHECK_ELIGIBILITY_RESPONSE).when(mMockHttpResponse).body();
+ CheckEligibilityRequest request = CheckEligibilityRequest.builder()
+ .setAppId(Ts43Constants.APP_ODSA_PRIMARY)
+ .build();
+
+ CheckEligibilityResponse response = mTs43Operation.checkEligibility(request);
+ assertThat(response.operationResult()).isEqualTo(
+ EsimOdsaOperation.OPERATION_RESULT_SUCCESS);
+ assertThat(response.appEligibility()).isEqualTo(
+ CheckEligibilityOperation.ELIGIBILITY_RESULT_ENABLED);
+ assertThat(response.companionDeviceServices()).containsExactly(
+ EsimOdsaOperation.COMPANION_SERVICE_SHARED_NUMBER);
+ assertThat(response.notEnabledUrl()).isEqualTo(new URL(NOT_ENABLED_URL));
+ assertThat(response.notEnabledUserData()).isEqualTo(NOT_ENABLED_USER_DATA);
+ }
+
+ @Test
+ public void testManageService() throws Exception {
+ doReturn(MANAGE_SERVICE_RESPONSE).when(mMockHttpResponse).body();
+ ManageServiceRequest request = ManageServiceRequest.builder()
+ .setAppId(Ts43Constants.APP_ODSA_PRIMARY)
+ .build();
+
+ ManageServiceResponse response = mTs43Operation.manageService(request);
+ assertThat(response.operationResult()).isEqualTo(
+ EsimOdsaOperation.OPERATION_RESULT_SUCCESS);
+ assertThat(response.serviceStatus()).isEqualTo(
+ EsimOdsaOperation.SERVICE_STATUS_DEACTIVATED);
+ }
+
+ @Test
+ public void testGetPhoneNumber() throws Exception {
+ doReturn(GET_PHONE_NUMBER_RESPONSE).when(mMockHttpResponse).body();
+
+ GetPhoneNumberRequest request = GetPhoneNumberRequest.builder()
+ .setTerminalId(TERMINAL_ID)
+ .build();
+
+ GetPhoneNumberResponse response = mTs43Operation.getPhoneNumber(request);
+ assertThat(response.operationResult()).isEqualTo(
+ EsimOdsaOperation.OPERATION_RESULT_SUCCESS);
+ assertThat(response.msisdn()).isEqualTo(MSISDN);
+ }
+}
diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
index 8e331f3..8a4a185 100644
--- a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
+++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
@@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -32,6 +33,8 @@ import static org.mockito.Mockito.when;
import static org.testng.Assert.expectThrows;
import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
import android.net.Network;
import android.telephony.TelephonyManager;
@@ -45,6 +48,7 @@ import com.android.libraries.entitlement.ServiceEntitlementException;
import com.android.libraries.entitlement.ServiceEntitlementRequest;
import com.android.libraries.entitlement.http.HttpClient;
import com.android.libraries.entitlement.http.HttpConstants.ContentType;
+import com.android.libraries.entitlement.http.HttpConstants.RequestMethod;
import com.android.libraries.entitlement.http.HttpRequest;
import com.android.libraries.entitlement.http.HttpResponse;
@@ -67,8 +71,11 @@ public class EapAkaApiTest {
private static final String TEST_URL = "https://test.url/test-path";
private static final String EAP_AKA_CHALLENGE =
"{\"eap-relay-packet\":\"" + EAP_AKA_CHALLENGE_REQUEST + "\"}";
+ private static final String INVALID_EAP_AKA_CHALLENGE =
+ "{\"invalid-eap-relay-packet\":\"" + EAP_AKA_CHALLENGE_REQUEST + "\"}";
// com.google.common.net.HttpHeaders.COOKIE
private static final String HTTP_HEADER_COOKIE = "Cookie";
+ private static final String HTTP_HEADER_LOCATION = "Location";
private static final String COOKIE_VALUE = "COOKIE=abcdefg";
private static final String COOKIE_VALUE_1 = "COOKIE=hijklmn";
private static final String RESPONSE_XML =
@@ -96,9 +103,21 @@ public class EapAkaApiTest {
private static final String ACCEPT_CONTENT_TYPE_JSON_AND_XML =
"application/vnd.gsma.eap-relay.v1.0+json, text/vnd.wap.connectivity-xml";
private static final String BYPASS_EAP_AKA_RESPONSE = "abc";
+ private static final String VENDOR = "VEND";
+ private static final String MODEL = "MODEL";
+ private static final String SW_VERSION = "SW_VERSION";
+ private static final String LONG_VENDOR = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ private static final String LONG_MODEL = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ private static final String LONG_SW_VERSION = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ private static final String LONG_VENDOR_TRIMMED = "aaaa";
+ private static final String LONG_MODEL_TRIMMED = "aaaaaaaaaa";
+ private static final String LONG_SW_VERSION_TRIMMED = "aaaaaaaaaaaaaaaaaaaa";
+ private static final String APP_VERSION = "APP_VERSION";
@Rule public final MockitoRule rule = MockitoJUnit.rule();
+ @Mock private PackageManager mMockPackageManager;
+ @Mock private PackageInfo mMockPackageInfo;
@Mock private HttpClient mMockHttpClient;
@Mock private Network mMockNetwork;
@Mock private TelephonyManager mMockTelephonyManager;
@@ -110,23 +129,28 @@ public class EapAkaApiTest {
private EapAkaApi mEapAkaApiBypassAuthentication;
@Before
- public void setUp() {
+ public void setUp() throws Exception {
mContext = spy(ApplicationProvider.getApplicationContext());
- mEapAkaApi = new EapAkaApi(mContext, SUB_ID, mMockHttpClient, "");
- mEapAkaApiBypassAuthentication =
- new EapAkaApi(mContext, SUB_ID, mMockHttpClient, BYPASS_EAP_AKA_RESPONSE);
- when(mContext.getSystemService(TelephonyManager.class))
- .thenReturn(mMockTelephonyManager);
+ when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
+ mMockPackageInfo.versionName = APP_VERSION;
+ when(mMockPackageManager.getPackageInfo(anyString(), anyInt()))
+ .thenReturn(mMockPackageInfo);
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mMockTelephonyManager);
when(mMockTelephonyManager.createForSubscriptionId(SUB_ID))
.thenReturn(mMockTelephonyManagerForSubId);
when(mMockTelephonyManagerForSubId.getSubscriberId()).thenReturn(IMSI);
when(mMockTelephonyManagerForSubId.getSimOperator()).thenReturn(MCCMNC);
+ mEapAkaApi = new EapAkaApi(mContext, SUB_ID, mMockHttpClient, "");
+ mEapAkaApiBypassAuthentication =
+ new EapAkaApi(mContext, SUB_ID, mMockHttpClient, BYPASS_EAP_AKA_RESPONSE);
}
@Test
public void queryEntitlementStatus_hasAuthenticationToken() throws Exception {
HttpResponse httpResponse =
- HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
.build();
when(mMockHttpClient.request(any())).thenReturn(httpResponse);
CarrierConfig carrierConfig =
@@ -134,62 +158,331 @@ public class EapAkaApiTest {
ServiceEntitlementRequest request =
ServiceEntitlementRequest.builder().setAuthenticationToken(TOKEN).build();
- String response =
+ HttpResponse response =
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ assertThat(response).isEqualTo(httpResponse);
+ verify(mMockHttpClient).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getValue().timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getValue().network()).isEqualTo(mMockNetwork);
+ assertThat(mHttpRequestCaptor.getValue().requestMethod()).isEqualTo(RequestMethod.GET);
+ }
+
+ @Test
+ public void queryEntitlementStatus_hasAuthenticationToken_useHttpPost() throws Exception {
+ HttpResponse httpResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any())).thenReturn(httpResponse);
+ CarrierConfig carrierConfig =
+ CarrierConfig.builder()
+ .setServerUrl(TEST_URL)
+ .setNetwork(mMockNetwork)
+ .setUseHttpPost(true)
+ .build();
+ ServiceEntitlementRequest request =
+ ServiceEntitlementRequest.builder().setAuthenticationToken(TOKEN).build();
+
+ HttpResponse response =
mEapAkaApi.queryEntitlementStatus(
ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
- assertThat(response).isEqualTo(RESPONSE_XML);
+ assertThat(response).isEqualTo(httpResponse);
verify(mMockHttpClient).request(mHttpRequestCaptor.capture());
assertThat(mHttpRequestCaptor.getValue().timeoutInSec())
.isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
assertThat(mHttpRequestCaptor.getValue().network()).isEqualTo(mMockNetwork);
+ assertThat(mHttpRequestCaptor.getValue().requestMethod()).isEqualTo(RequestMethod.POST);
}
@Test
public void queryEntitlementStatus_noAuthenticationToken() throws Exception {
when(mMockTelephonyManagerForSubId.getIccAuthentication(
- TelephonyManager.APPTYPE_USIM,
- TelephonyManager.AUTHTYPE_EAP_AKA,
- EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
.thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
HttpResponse eapChallengeResponse =
- HttpResponse
- .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE)
- .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1)).build();
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
HttpResponse xmlResponse =
- HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
.build();
when(mMockHttpClient.request(any()))
- .thenReturn(eapChallengeResponse).thenReturn(xmlResponse);
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
- String respopnse =
+ HttpResponse response =
mEapAkaApi.queryEntitlementStatus(
ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
- assertThat(respopnse).isEqualTo(RESPONSE_XML);
+ assertThat(response).isEqualTo(xmlResponse);
+ verify(mMockHttpClient, times(2)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).requestMethod())
+ .isEqualTo(RequestMethod.GET);
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestMethod())
+ .isEqualTo(RequestMethod.POST);
// Verify that the 2nd request has cookies set by the 1st response
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
+ .containsAtLeast(
+ HTTP_HEADER_COOKIE, COOKIE_VALUE,
+ HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).network()).isNull();
+ }
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_useHttpPost() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig =
+ CarrierConfig.builder().setServerUrl(TEST_URL).setUseHttpPost(true).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ HttpResponse response =
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ assertThat(response).isEqualTo(xmlResponse);
verify(mMockHttpClient, times(2)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).requestMethod())
+ .isEqualTo(RequestMethod.POST);
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestMethod())
+ .isEqualTo(RequestMethod.POST);
+ // Verify that the 2nd request has cookies set by the 1st response
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
+ .containsAtLeast(
+ HTTP_HEADER_COOKIE, COOKIE_VALUE,
+ HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).network()).isNull();
+ }
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_invalidChallenge() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(INVALID_EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
+ carrierConfig,
+ request));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE);
+ assertThat(exception.getMessage())
+ .isEqualTo("Failed to parse EAP-AKA challenge: " + INVALID_EAP_AKA_CHALLENGE);
+ assertThat(exception.getCause()).isNull();
+ assertThat(exception.getHttpStatus()).isEqualTo(0);
+ assertThat(exception.getRetryAfter()).isEmpty();
+ }
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_secondChallenge() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ HttpResponse response =
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ assertThat(response).isEqualTo(xmlResponse);
+ // Verify that the subsequent requests have cookies set by the 1st response
+ verify(mMockHttpClient, times(3)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
+ .containsAtLeast(
+ HTTP_HEADER_COOKIE, COOKIE_VALUE,
+ HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(2).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(2).network()).isNull();
+ }
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_thirdChallenge() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ HttpResponse response =
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ assertThat(response).isEqualTo(xmlResponse);
+ // Verify that the subsequent requests have cookies set by the 1st response
+ verify(mMockHttpClient, times(4)).request(mHttpRequestCaptor.capture());
assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
- .containsAtLeast(HTTP_HEADER_COOKIE, COOKIE_VALUE,
- HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
+ .containsAtLeast(
+ HTTP_HEADER_COOKIE, COOKIE_VALUE,
+ HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
assertThat(mHttpRequestCaptor.getAllValues().get(0).timeoutInSec())
.isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
assertThat(mHttpRequestCaptor.getAllValues().get(0).network()).isNull();
assertThat(mHttpRequestCaptor.getAllValues().get(1).timeoutInSec())
.isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
assertThat(mHttpRequestCaptor.getAllValues().get(1).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(2).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(2).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(3).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(3).network()).isNull();
+ }
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_fourthChallenge_throwException()
+ throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
+ carrierConfig,
+ request));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_EAP_AKA_FAILURE);
+ assertThat(exception.getMessage()).isEqualTo("Unable to EAP-AKA authenticate");
+ assertThat(exception.getCause()).isNull();
+ assertThat(exception.getHttpStatus()).isEqualTo(0);
+ assertThat(exception.getRetryAfter()).isEmpty();
}
@Test
public void queryEntitlementStatus_hasAuthenticationToken_multipleAppIds() throws Exception {
HttpResponse response =
- HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
.build();
when(mMockHttpClient.request(any())).thenReturn(response);
- ImmutableList<String> appIds = ImmutableList.of(ServiceEntitlement.APP_VOWIFI,
- ServiceEntitlement.APP_VOLTE);
+ ImmutableList<String> appIds =
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI, ServiceEntitlement.APP_VOLTE);
CarrierConfig carrierConfig =
CarrierConfig.builder().setServerUrl(TEST_URL).setTimeoutInSec(70).build();
ServiceEntitlementRequest request =
@@ -210,19 +503,20 @@ public class EapAkaApiTest {
HttpResponse eapChallengeResponse =
HttpResponse.builder().setContentType(ContentType.JSON).build();
when(mMockHttpClient.request(any())).thenReturn(eapChallengeResponse);
- CarrierConfig carrierConfig =
- CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
- ServiceEntitlementException exception = expectThrows(
- ServiceEntitlementException.class,
- () -> mEapAkaApi.queryEntitlementStatus(
- ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
- carrierConfig,
- request));
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
+ carrierConfig,
+ request));
- assertThat(exception.getErrorCode()).isEqualTo(
- ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE);
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE);
assertThat(exception.getMessage()).isEqualTo("Failed to parse json object");
assertThat(exception.getCause()).isInstanceOf(JSONException.class);
assertThat(exception.getHttpStatus()).isEqualTo(0);
@@ -233,17 +527,21 @@ public class EapAkaApiTest {
public void queryEntitlementStatus_noAuthenticationToken_handleEapAkaSyncFailure()
throws Exception {
when(mMockTelephonyManagerForSubId.getIccAuthentication(
- TelephonyManager.APPTYPE_USIM,
- TelephonyManager.AUTHTYPE_EAP_AKA,
- EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
.thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE)
.thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
HttpResponse eapChallengeResponse =
- HttpResponse
- .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE)
- .setCookies(ImmutableList.of(COOKIE_VALUE)).build();
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE))
+ .build();
HttpResponse xmlResponse =
- HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
.build();
when(mMockHttpClient.request(any()))
.thenReturn(eapChallengeResponse)
@@ -252,11 +550,11 @@ public class EapAkaApiTest {
CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
- String response =
+ HttpResponse response =
mEapAkaApi.queryEntitlementStatus(
ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
- assertThat(response).isEqualTo(RESPONSE_XML);
+ assertThat(response).isEqualTo(xmlResponse);
// Verify that the 2nd/3rd request has cookie set by the 1st/2nd response
verify(mMockHttpClient, times(3)).request(mHttpRequestCaptor.capture());
assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
@@ -266,30 +564,123 @@ public class EapAkaApiTest {
}
@Test
+ public void queryEntitlementStatus_noAuthenticationToken_eapAkaSyncFailure_invalidChallenge()
+ throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE))
+ .build();
+ HttpResponse invalidEapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(INVALID_EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE))
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(invalidEapChallengeResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
+ carrierConfig,
+ request));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE);
+ assertThat(exception.getMessage())
+ .isEqualTo("Failed to parse EAP-AKA challenge: " + INVALID_EAP_AKA_CHALLENGE);
+ assertThat(exception.getCause()).isNull();
+ assertThat(exception.getHttpStatus()).isEqualTo(0);
+ assertThat(exception.getRetryAfter()).isEmpty();
+ }
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_fourthEapAkaSyncFailure()
+ throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE)
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE)
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE)
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE))
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
+ carrierConfig,
+ request));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE);
+ assertThat(exception.getMessage())
+ .isEqualTo("Unable to recover from EAP-AKA synchroinization failure");
+ assertThat(exception.getCause()).isNull();
+ assertThat(exception.getHttpStatus()).isEqualTo(0);
+ assertThat(exception.getRetryAfter()).isEmpty();
+ }
+
+ @Test
public void queryEntitlementStatus_hasNoAuthenticationToken_bypassAuthentication()
throws Exception {
HttpResponse eapChallengeResponse =
- HttpResponse
- .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE)
- .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1)).build();
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
HttpResponse xmlResponse =
- HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
.build();
when(mMockHttpClient.request(any()))
- .thenReturn(eapChallengeResponse).thenReturn(xmlResponse);
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
- String respopnse =
+ HttpResponse response =
mEapAkaApiBypassAuthentication.queryEntitlementStatus(
ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
- assertThat(respopnse).isEqualTo(RESPONSE_XML);
+ assertThat(response).isEqualTo(xmlResponse);
// Verify that the 2nd request has cookies set by the 1st response
verify(mMockHttpClient, times(2)).request(mHttpRequestCaptor.capture());
assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
- .containsAtLeast(HTTP_HEADER_COOKIE, COOKIE_VALUE,
- HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
+ .containsAtLeast(
+ HTTP_HEADER_COOKIE, COOKIE_VALUE,
+ HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
assertThat(mHttpRequestCaptor.getAllValues().get(0).timeoutInSec())
.isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
assertThat(mHttpRequestCaptor.getAllValues().get(0).network()).isNull();
@@ -314,8 +705,7 @@ public class EapAkaApiTest {
when(mMockHttpClient.request(any())).thenReturn(response);
CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
ServiceEntitlementRequest request =
- ServiceEntitlementRequest
- .builder()
+ ServiceEntitlementRequest.builder()
.setAuthenticationToken(TOKEN)
.setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_XML)
.build();
@@ -335,10 +725,7 @@ public class EapAkaApiTest {
when(mMockHttpClient.request(any())).thenReturn(response);
CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
ServiceEntitlementRequest request =
- ServiceEntitlementRequest
- .builder()
- .setAuthenticationToken(TOKEN)
- .build();
+ ServiceEntitlementRequest.builder().setAuthenticationToken(TOKEN).build();
mEapAkaApi.queryEntitlementStatus(
ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
@@ -349,53 +736,392 @@ public class EapAkaApiTest {
}
@Test
+ public void queryEntitlementStatus_terminalVendorModelSWVersionTrimmed() throws Exception {
+ CarrierConfig carrierConfig =
+ CarrierConfig.builder()
+ .setServerUrl(TEST_URL)
+ .setClientTs43(CarrierConfig.CLIENT_TS_43_IMS_ENTITLEMENT)
+ .build();
+ ServiceEntitlementRequest request =
+ ServiceEntitlementRequest.builder()
+ .setAuthenticationToken(TOKEN)
+ .setTerminalVendor(LONG_VENDOR)
+ .setTerminalModel(LONG_MODEL)
+ .setTerminalSoftwareVersion(LONG_SW_VERSION)
+ .build();
+
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ verify(mMockHttpClient).request(mHttpRequestCaptor.capture());
+ String urlParams =
+ String.format(
+ "terminal_vendor=%s&terminal_model=%s&terminal_sw_version=%s",
+ LONG_VENDOR_TRIMMED, LONG_MODEL_TRIMMED, LONG_SW_VERSION_TRIMMED);
+ assertThat(mHttpRequestCaptor.getValue().url()).contains(urlParams);
+ }
+
+ @Test
+ public void queryEntitlementStatus_userAgentSet() throws Exception {
+ CarrierConfig carrierConfig =
+ CarrierConfig.builder()
+ .setServerUrl(TEST_URL)
+ .setClientTs43(CarrierConfig.CLIENT_TS_43_IMS_ENTITLEMENT)
+ .build();
+ ServiceEntitlementRequest request =
+ ServiceEntitlementRequest.builder()
+ .setAuthenticationToken(TOKEN)
+ .setTerminalVendor(VENDOR)
+ .setTerminalModel(MODEL)
+ .setTerminalSoftwareVersion(SW_VERSION)
+ .build();
+
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ verify(mMockHttpClient).request(mHttpRequestCaptor.capture());
+ String userAgent =
+ String.format(
+ "PRD-TS43 term-%s/%s %s/%s OS-Android/%s",
+ VENDOR, MODEL, carrierConfig.clientTs43(), APP_VERSION, SW_VERSION);
+ assertThat(
+ mHttpRequestCaptor
+ .getValue()
+ .requestProperties()
+ .get(HttpHeaders.USER_AGENT)
+ .get(0))
+ .isEqualTo(userAgent);
+ }
+
+ @Test
+ public void queryEntitlementStatus_userAgentSet_duringEapAka() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig =
+ CarrierConfig.builder()
+ .setServerUrl(TEST_URL)
+ .setClientTs43(CarrierConfig.CLIENT_TS_43_IMS_ENTITLEMENT)
+ .build();
+ ServiceEntitlementRequest request =
+ ServiceEntitlementRequest.builder()
+ .setTerminalVendor(VENDOR)
+ .setTerminalModel(MODEL)
+ .setTerminalSoftwareVersion(SW_VERSION)
+ .build();
+
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ verify(mMockHttpClient, times(2)).request(mHttpRequestCaptor.capture());
+ String userAgent =
+ String.format(
+ "PRD-TS43 term-%s/%s %s/%s OS-Android/%s",
+ VENDOR, MODEL, carrierConfig.clientTs43(), APP_VERSION, SW_VERSION);
+ assertThat(
+ mHttpRequestCaptor
+ .getAllValues()
+ .get(0)
+ .requestProperties()
+ .get(HttpHeaders.USER_AGENT)
+ .get(0))
+ .isEqualTo(userAgent);
+ assertThat(
+ mHttpRequestCaptor
+ .getAllValues()
+ .get(1)
+ .requestProperties()
+ .get(HttpHeaders.USER_AGENT)
+ .get(0))
+ .isEqualTo(userAgent);
+ }
+
+ @Test
+ public void queryEntitlementStatus_userAgentTrimmed() throws Exception {
+ CarrierConfig carrierConfig =
+ CarrierConfig.builder()
+ .setServerUrl(TEST_URL)
+ .setClientTs43(CarrierConfig.CLIENT_TS_43_IMS_ENTITLEMENT)
+ .build();
+ ServiceEntitlementRequest request =
+ ServiceEntitlementRequest.builder()
+ .setAuthenticationToken(TOKEN)
+ .setTerminalVendor(LONG_VENDOR)
+ .setTerminalModel(LONG_MODEL)
+ .setTerminalSoftwareVersion(LONG_SW_VERSION)
+ .build();
+
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ verify(mMockHttpClient).request(mHttpRequestCaptor.capture());
+ String userAgent =
+ String.format(
+ "PRD-TS43 term-%s/%s %s/%s OS-Android/%s",
+ LONG_VENDOR_TRIMMED,
+ LONG_MODEL_TRIMMED,
+ carrierConfig.clientTs43(),
+ APP_VERSION,
+ LONG_SW_VERSION_TRIMMED);
+ assertThat(
+ mHttpRequestCaptor
+ .getValue()
+ .requestProperties()
+ .get(HttpHeaders.USER_AGENT)
+ .get(0))
+ .isEqualTo(userAgent);
+ }
+
+ @Test
public void performEsimOdsaOperation_noAuthenticationToken_returnsResult() throws Exception {
when(mMockTelephonyManagerForSubId.getIccAuthentication(
- TelephonyManager.APPTYPE_USIM,
- TelephonyManager.AUTHTYPE_EAP_AKA,
- EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
.thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
HttpResponse eapChallengeResponse =
- HttpResponse
- .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE)
- .setCookies(ImmutableList.of(COOKIE_VALUE)).build();
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE))
+ .build();
HttpResponse xmlResponse =
- HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
.build();
when(mMockHttpClient.request(any()))
- .thenReturn(eapChallengeResponse).thenReturn(xmlResponse);
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
EsimOdsaOperation operation = EsimOdsaOperation.builder().build();
- String response =
- mEapAkaApi.performEsimOdsaOperation(ServiceEntitlement.APP_ODSA_COMPANION,
- carrierConfig, request, operation);
+ HttpResponse response =
+ mEapAkaApi.performEsimOdsaOperation(
+ ServiceEntitlement.APP_ODSA_COMPANION, carrierConfig, request, operation);
+
+ assertThat(response).isEqualTo(xmlResponse);
+ verify(mMockHttpClient, times(2)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).requestMethod())
+ .isEqualTo(RequestMethod.GET);
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestMethod())
+ .isEqualTo(RequestMethod.POST);
+ }
- assertThat(response).isEqualTo(RESPONSE_XML);
- verify(mMockHttpClient, times(2)).request(any());
+ @Test
+ public void performEsimOdsaOperation_noAuthenticationToken_useHttpPost_returnsResult()
+ throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig =
+ CarrierConfig.builder().setServerUrl(TEST_URL).setUseHttpPost(true).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+ EsimOdsaOperation operation = EsimOdsaOperation.builder().build();
+
+ HttpResponse response =
+ mEapAkaApi.performEsimOdsaOperation(
+ ServiceEntitlement.APP_ODSA_COMPANION, carrierConfig, request, operation);
+
+ assertThat(response).isEqualTo(xmlResponse);
+ verify(mMockHttpClient, times(2)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).requestMethod())
+ .isEqualTo(RequestMethod.POST);
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestMethod())
+ .isEqualTo(RequestMethod.POST);
}
@Test
public void performEsimOdsaOperation_manageSubscription_returnsResult() throws Exception {
- HttpResponse httpResponse =
- HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
.build();
- when(mMockHttpClient.request(any())).thenReturn(httpResponse);
+ when(mMockHttpClient.request(any())).thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request =
+ ServiceEntitlementRequest.builder().setAuthenticationToken(TOKEN).build();
+ EsimOdsaOperation operation =
+ EsimOdsaOperation.builder()
+ .setOperation(EsimOdsaOperation.OPERATION_MANAGE_SUBSCRIPTION)
+ .setOperationType(EsimOdsaOperation.OPERATION_TYPE_SUBSCRIBE)
+ .build();
+
+ HttpResponse response =
+ mEapAkaApi.performEsimOdsaOperation(
+ ServiceEntitlement.APP_ODSA_COMPANION, carrierConfig, request, operation);
+
+ assertThat(response).isEqualTo(xmlResponse);
+ verify(mMockHttpClient, times(1)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).requestMethod())
+ .isEqualTo(RequestMethod.GET);
+ }
+
+ @Test
+ public void performEsimOdsaOperation_manageSubscription_useHttpPost_returnsResult()
+ throws Exception {
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any())).thenReturn(xmlResponse);
CarrierConfig carrierConfig =
- CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ CarrierConfig.builder().setServerUrl(TEST_URL).setUseHttpPost(true).build();
ServiceEntitlementRequest request =
ServiceEntitlementRequest.builder().setAuthenticationToken(TOKEN).build();
- EsimOdsaOperation operation = EsimOdsaOperation.builder()
- .setOperation(EsimOdsaOperation.OPERATION_MANAGE_SUBSCRIPTION)
- .setOperationType(EsimOdsaOperation.OPERATION_TYPE_SUBSCRIBE)
- .build();
+ EsimOdsaOperation operation =
+ EsimOdsaOperation.builder()
+ .setOperation(EsimOdsaOperation.OPERATION_MANAGE_SUBSCRIPTION)
+ .setOperationType(EsimOdsaOperation.OPERATION_TYPE_SUBSCRIBE)
+ .build();
+
+ HttpResponse response =
+ mEapAkaApi.performEsimOdsaOperation(
+ ServiceEntitlement.APP_ODSA_COMPANION, carrierConfig, request, operation);
+
+ assertThat(response).isEqualTo(xmlResponse);
+ verify(mMockHttpClient, times(1)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).requestMethod())
+ .isEqualTo(RequestMethod.POST);
+ }
+
+ @Test
+ public void performEsimOdsaOperation_noAuthenticationToken_invalidChallenge() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(INVALID_EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+ EsimOdsaOperation operation = EsimOdsaOperation.builder().build();
+
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.performEsimOdsaOperation(
+ ServiceEntitlement.APP_ODSA_COMPANION,
+ carrierConfig,
+ request,
+ operation));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE);
+ assertThat(exception.getMessage())
+ .isEqualTo("Failed to parse EAP-AKA challenge: " + INVALID_EAP_AKA_CHALLENGE);
+ assertThat(exception.getCause()).isNull();
+ assertThat(exception.getHttpStatus()).isEqualTo(0);
+ assertThat(exception.getRetryAfter()).isEmpty();
+ }
- String response =
- mEapAkaApi.performEsimOdsaOperation(ServiceEntitlement.APP_ODSA_COMPANION,
- carrierConfig, request, operation);
+ @Test
+ public void acquireOidcAuthenticationEndpoint() throws Exception {
+ HttpResponse response =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setLocation(HTTP_HEADER_LOCATION)
+ .build();
+ when(mMockHttpClient.request(any())).thenReturn(response);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ String endpoint =
+ mEapAkaApi.acquireOidcAuthenticationEndpoint(
+ ServiceEntitlement.APP_ODSA_COMPANION, carrierConfig, request);
+
+ assertThat(endpoint).isEqualTo(HTTP_HEADER_LOCATION);
+ verify(mMockHttpClient, times(1)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).requestMethod())
+ .isEqualTo(RequestMethod.GET);
+ }
+
+ @Test
+ public void acquireOidcAuthenticationEndpoint_useHttpPost() throws Exception {
+ HttpResponse response =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setLocation(HTTP_HEADER_LOCATION)
+ .build();
+ when(mMockHttpClient.request(any())).thenReturn(response);
+ CarrierConfig carrierConfig =
+ CarrierConfig.builder().setServerUrl(TEST_URL).setUseHttpPost(true).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ String endpoint =
+ mEapAkaApi.acquireOidcAuthenticationEndpoint(
+ ServiceEntitlement.APP_ODSA_COMPANION, carrierConfig, request);
+
+ assertThat(endpoint).isEqualTo(HTTP_HEADER_LOCATION);
+ verify(mMockHttpClient, times(1)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).requestMethod())
+ .isEqualTo(RequestMethod.POST);
+ }
+
+ @Test
+ public void queryEntitlementStatusFromOidc() throws Exception {
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any())).thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ HttpResponse response =
+ mEapAkaApi.queryEntitlementStatusFromOidc(TEST_URL, carrierConfig, request);
- assertThat(response).isEqualTo(RESPONSE_XML);
+ assertThat(response).isEqualTo(xmlResponse);
verify(mMockHttpClient, times(1)).request(any());
}
}