Merge "Support specifying accept configuration document type XML or JSON" into sc-dev
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java b/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
index 9db5246..c7b0ad3 100644
--- a/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
+++ b/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
@@ -26,14 +26,23 @@
  */
 @AutoValue
 public abstract class ServiceEntitlementRequest {
-    /**
-     * Disables notification token.
-     */
+    /** Disables notification token. */
     public static final int NOTICATION_ACTION_DISABLE = 0;
-    /**
-     * Enables FCM notification token.
-     */
+    /** 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. */
+    public static final String ACCEPT_CONTENT_TYPE_JSON_AND_XML =
+            "application/vnd.gsma.eap-relay.v1.0+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
@@ -100,12 +109,21 @@
     public abstract int notificationAction();
 
     /**
+     * Returns the accepted content type of http response.
+     *
+     * @see #ACCEPT_CONTENT_TYPE_XML
+     * @see #ACCEPT_CONTENT_TYPE_JSON
+     * @see #ACCEPT_CONTENT_TYPE_JSON_AND_XML
+     */
+    public abstract String acceptContentType();
+
+    /**
      * Returns a new {@link Builder} object.
      */
     public static Builder builder() {
         return new AutoValue_ServiceEntitlementRequest.Builder()
-                .setConfigurationVersion(0)
-                .setEntitlementVersion("2.0")
+                .setConfigurationVersion(DEFAULT_CONFIGURATION_VERSION)
+                .setEntitlementVersion(DEFAULT_ENTITLEMENT_VERSION)
                 .setAuthenticationToken("")
                 .setTerminalId("")
                 .setTerminalVendor(Build.MANUFACTURER)
@@ -114,7 +132,8 @@
                 .setAppName("")
                 .setAppVersion("")
                 .setNotificationToken("")
-                .setNotificationAction(NOTICATION_ACTION_ENABLE_FCM);
+                .setNotificationAction(NOTICATION_ACTION_ENABLE_FCM)
+                .setAcceptContentType(ACCEPT_CONTENT_TYPE_JSON_AND_XML);
     }
 
     /**
@@ -126,7 +145,8 @@
          * Sets the version of configuration currently stored on the client. Used by HTTP parameter
          * "vers".
          *
-         * <p>If not set, default to 0 indicating no existing configuration.
+         * <p>If not set, default to {@link #DEFAULT_CONFIGURATION_VERSION} indicating no existing
+         * configuration.
          */
         public abstract Builder setConfigurationVersion(int value);
 
@@ -134,7 +154,7 @@
          * Sets the current version of the entitlement specification. Used by HTTP parameter
          * "entitlement_version".
          *
-         * <p>If not set, default to "2.0" base on TS.43-v5.0.
+         * <p>If not set, default to {@link #DEFAULT_ENTITLEMENT_VERSION} base on TS.43-v5.0.
          */
         public abstract Builder setEntitlementVersion(String value);
 
@@ -211,6 +231,18 @@
          */
         public abstract Builder setNotificationAction(int value);
 
+        /**
+         * Sets the configuration document format the caller accepts, e.g. XML or JSON. Used by HTTP
+         * request header "Accept".
+         *
+         * <p>If not set, will use {@link #ACCEPT_CONTENT_TYPE_JSON_AND_XML}.
+         *
+         * @see #ACCEPT_CONTENT_TYPE_XML
+         * @see #ACCEPT_CONTENT_TYPE_JSON
+         * @see #ACCEPT_CONTENT_TYPE_JSON_AND_XML
+         */
+        public abstract Builder setAcceptContentType(String contentType);
+
         public abstract ServiceEntitlementRequest build();
     }
 }
diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
index bf53d87..2e337e4 100644
--- a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
+++ b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
@@ -33,7 +33,6 @@
 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;
@@ -64,11 +63,6 @@
     private static final String APP_VERSION = "app_version";
     private static final String APP_NAME = "app_name";
 
-    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 REQUEST_CONTENT_TYPE_JSON =
-            "application/vnd.gsma.eap-relay.v1.0+json";
-
     private static final String OPERATION = "operation";
     private static final String OPERATION_TYPE = "operation_type";
     private static final String COMPANION_TERMINAL_ID = "companion_terminal_id";
@@ -119,16 +113,26 @@
             throws ServiceEntitlementException {
         Uri.Builder urlBuilder = Uri.parse(carrierConfig.serverUrl()).buildUpon();
         appendParametersForServiceEntitlementRequest(urlBuilder, appIds, request);
-        HttpResponse response = httpGet(urlBuilder.toString(), carrierConfig);
-        if (!request.authenticationToken().isEmpty()) {
+        if (!TextUtils.isEmpty(request.authenticationToken())) {
             // Fast Re-Authentication flow with pre-existing auth token
             Log.d(TAG, "Fast Re-Authentication");
-            return response.body();
+            return httpGet(
+                    urlBuilder.toString(), carrierConfig, request.acceptContentType()).body();
+        } else {
+            // Full Authentication flow
+            Log.d(TAG, "Full Authentication");
+            HttpResponse challengeResponse =
+                    httpGet(
+                        urlBuilder.toString(),
+                        carrierConfig,
+                        ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
+            return respondToEapAkaChallenge(
+                    carrierConfig,
+                    challengeResponse,
+                    FOLLOW_SYNC_FAILURE_MAX_COUNT,
+                    request.acceptContentType())
+                    .body();
         }
-        // Full Authentication flow
-        Log.d(TAG, "Full Authentication");
-        return respondToEapAkaChallenge(carrierConfig, response, FOLLOW_SYNC_FAILURE_MAX_COUNT)
-                .body();
     }
 
     /**
@@ -147,14 +151,15 @@
      *       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).
      * </ul>
+     *
+     * @param response Challenge response from server which its content type is JSON
      */
     private HttpResponse respondToEapAkaChallenge(
-            CarrierConfig carrierConfig, HttpResponse response, int followSyncFailureCount)
+            CarrierConfig carrierConfig,
+            HttpResponse response,
+            int followSyncFailureCount,
+            String contentType)
             throws ServiceEntitlementException {
-        if (response.contentType() != ContentType.JSON) {
-            throw new ServiceEntitlementException(
-                    ERROR_MALFORMED_HTTP_RESPONSE, "Unexpected http ContentType");
-        }
         String eapAkaChallenge;
         try {
             eapAkaChallenge = new JSONObject(response.body()).getString(EAP_CHALLENGE_RESPONSE);
@@ -167,17 +172,22 @@
                 EapAkaResponse.respondToEapAkaChallenge(mContext, mSimSubscriptionId, challenge);
         // This could be a successful authentication, or synchronization failure.
         if (eapAkaResponse.response() != null) { // successful authentication
-            return challengeResponse(eapAkaResponse.response(), carrierConfig, response.cookie());
+            return challengeResponse(
+                            eapAkaResponse.response(),
+                            carrierConfig,
+                            response.cookie(),
+                            contentType);
         } else if (eapAkaResponse.synchronizationFailureResponse() != null) {
             Log.d(TAG, "synchronization failure");
             HttpResponse newChallenge =
                     challengeResponse(
                             eapAkaResponse.synchronizationFailureResponse(),
                             carrierConfig,
-                            response.cookie());
+                            response.cookie(),
+                            ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
             if (followSyncFailureCount > 0) {
                 return respondToEapAkaChallenge(
-                        carrierConfig, newChallenge, followSyncFailureCount - 1);
+                        carrierConfig, newChallenge, followSyncFailureCount - 1, contentType);
             } else {
                 throw new ServiceEntitlementException(
                         ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE,
@@ -189,7 +199,10 @@
     }
 
     private HttpResponse challengeResponse(
-            String eapAkaChallengeResponse, CarrierConfig carrierConfig, String cookie)
+            String eapAkaChallengeResponse,
+            CarrierConfig carrierConfig,
+            String cookie,
+            String contentType)
             throws ServiceEntitlementException {
         Log.d(TAG, "challengeResponse");
         JSONObject postData = new JSONObject();
@@ -204,8 +217,10 @@
                         .setUrl(carrierConfig.serverUrl())
                         .setRequestMethod(RequestMethod.POST)
                         .setPostData(postData)
-                        .addRequestProperty(HttpHeaders.ACCEPT, ACCEPT_CONTENT_TYPE_JSON_AND_XML)
-                        .addRequestProperty(HttpHeaders.CONTENT_TYPE, REQUEST_CONTENT_TYPE_JSON)
+                        .addRequestProperty(HttpHeaders.ACCEPT, contentType)
+                        .addRequestProperty(
+                                HttpHeaders.CONTENT_TYPE,
+                                ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON)
                         .addRequestProperty(HttpHeaders.COOKIE, cookie)
                         .setTimeoutInSec(carrierConfig.timeoutInSec())
                         .setNetwork(carrierConfig.network())
@@ -226,16 +241,26 @@
         appendParametersForServiceEntitlementRequest(urlBuilder, ImmutableList.of(appId), request);
         appendParametersForEsimOdsaOperation(urlBuilder, odsaOperation);
 
-        HttpResponse response = httpGet(urlBuilder.toString(), carrierConfig);
-        if (!request.authenticationToken().isEmpty()) {
+        if (!TextUtils.isEmpty(request.authenticationToken())) {
             // Fast Re-Authentication flow with pre-existing auth token
             Log.d(TAG, "Fast Re-Authentication");
-            return response.body();
+            return httpGet(
+                    urlBuilder.toString(), carrierConfig, request.acceptContentType()).body();
+        } else {
+            // Full Authentication flow
+            Log.d(TAG, "Full Authentication");
+            HttpResponse challengeResponse =
+                    httpGet(
+                            urlBuilder.toString(),
+                            carrierConfig,
+                            ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
+            return respondToEapAkaChallenge(
+                    carrierConfig,
+                    challengeResponse,
+                    FOLLOW_SYNC_FAILURE_MAX_COUNT,
+                    request.acceptContentType())
+                    .body();
         }
-        // Full Authentication flow
-        Log.d(TAG, "Full Authentication");
-        return respondToEapAkaChallenge(carrierConfig, response, FOLLOW_SYNC_FAILURE_MAX_COUNT)
-                .body();
     }
 
     private void appendParametersForServiceEntitlementRequest(
@@ -322,13 +347,13 @@
                 odsaOperation.targetTerminalEid());
     }
 
-    private HttpResponse httpGet(String url, CarrierConfig carrierConfig)
+    private HttpResponse httpGet(String url, CarrierConfig carrierConfig, String contentType)
             throws ServiceEntitlementException {
         HttpRequest httpRequest =
                 HttpRequest.builder()
                         .setUrl(url)
                         .setRequestMethod(RequestMethod.GET)
-                        .addRequestProperty(HttpHeaders.ACCEPT, ACCEPT_CONTENT_TYPE_JSON_AND_XML)
+                        .addRequestProperty(HttpHeaders.ACCEPT, contentType)
                         .setTimeoutInSec(carrierConfig.timeoutInSec())
                         .setNetwork(carrierConfig.network())
                         .build();
diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
index 99e8cfa..f289c04 100644
--- a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
+++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
@@ -48,6 +48,7 @@
 import com.android.libraries.entitlement.http.HttpResponse;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.net.HttpHeaders;
 
 import org.json.JSONException;
 import org.junit.Before;
@@ -93,19 +94,13 @@
     private static final String ACCEPT_CONTENT_TYPE_JSON_AND_XML =
             "application/vnd.gsma.eap-relay.v1.0+json, text/vnd.wap.connectivity-xml";
 
-    @Rule
-    public final MockitoRule rule = MockitoJUnit.rule();
+    @Rule public final MockitoRule rule = MockitoJUnit.rule();
 
-    @Mock
-    private HttpClient mMockHttpClient;
-    @Mock
-    private Network mMockNetwork;
-    @Mock
-    private TelephonyManager mMockTelephonyManager;
-    @Mock
-    private TelephonyManager mMockTelephonyManagerForSubId;
-    @Captor
-    private ArgumentCaptor<HttpRequest> mHttpRequestCaptor;
+    @Mock private HttpClient mMockHttpClient;
+    @Mock private Network mMockNetwork;
+    @Mock private TelephonyManager mMockTelephonyManager;
+    @Mock private TelephonyManager mMockTelephonyManagerForSubId;
+    @Captor private ArgumentCaptor<HttpRequest> mHttpRequestCaptor;
 
     private Context mContext;
     private EapAkaApi mEapAkaApi;
@@ -203,29 +198,6 @@
     }
 
     @Test
-    public void queryEntitlementStatus_noAuthenticationToken_contentTypeNotJson_throwException()
-            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();
-
-        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("Unexpected http ContentType");
-    }
-
-    @Test
     public void queryEntitlementStatus_noAuthenticationToken_emptyResponseBody_throwException()
             throws Exception {
         HttpResponse eapChallengeResponse =
@@ -285,6 +257,47 @@
     }
 
     @Test
+    public void queryEntitlementStatus_acceptContentTypeSpecified_verfityAcceptContentType()
+            throws Exception {
+        HttpResponse response = HttpResponse.builder().setBody(RESPONSE_XML).build();
+        when(mMockHttpClient.request(any())).thenReturn(response);
+        CarrierConfig carrierConfig = CarrierConfig.builder().build();
+        ServiceEntitlementRequest request =
+                ServiceEntitlementRequest
+                        .builder()
+                        .setAuthenticationToken(TOKEN)
+                        .setAcceptContentType(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_XML)
+                        .build();
+
+        mEapAkaApi.queryEntitlementStatus(
+                ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+        verify(mMockHttpClient).request(mHttpRequestCaptor.capture());
+        assertThat(mHttpRequestCaptor.getValue().requestProperties().get(HttpHeaders.ACCEPT))
+                .isEqualTo(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_XML);
+    }
+
+    @Test
+    public void queryEntitlementStatus_acceptContentTypeNotSpecified_defaultAcceptContentType()
+            throws Exception {
+        HttpResponse response = HttpResponse.builder().setBody(RESPONSE_XML).build();
+        when(mMockHttpClient.request(any())).thenReturn(response);
+        CarrierConfig carrierConfig = CarrierConfig.builder().build();
+        ServiceEntitlementRequest request =
+                ServiceEntitlementRequest
+                        .builder()
+                        .setAuthenticationToken(TOKEN)
+                        .build();
+
+        mEapAkaApi.queryEntitlementStatus(
+                ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+        verify(mMockHttpClient).request(mHttpRequestCaptor.capture());
+        assertThat(mHttpRequestCaptor.getValue().requestProperties().get(HttpHeaders.ACCEPT))
+                .isEqualTo(ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON_AND_XML);
+    }
+
+    @Test
     public void performEsimOdsaOperation_noAuthenticationToken_returnsResult() throws Exception {
         when(mMockTelephonyManagerForSubId.getIccAuthentication(
                 TelephonyManager.APPTYPE_USIM,