class ConfigFetchHttpClient {

  @Keep
  FetchResponse fetch(
      HttpURLConnection urlConnection,
      String installationId,
      String installationAuthToken,
      Map<String, String> analyticsUserProperties,
      String lastFetchETag,
      Map<String, String> customHeaders,
      Date currentTime)
      throws FirebaseRemoteConfigException {
    setUpUrlConnection(urlConnection, lastFetchETag, installationAuthToken, customHeaders);

    String fetchResponseETag;
    JSONObject fetchResponse;
    try {
      byte[] requestBody =
          createFetchRequestBody(installationId, installationAuthToken, analyticsUserProperties)
              .toString()
              .getBytes("utf-8");
      setFetchRequestBody(urlConnection, requestBody);

      urlConnection.connect();

      int responseCode = urlConnection.getResponseCode();
      if (responseCode != 200) {
        throw new FirebaseRemoteConfigServerException(
            responseCode, urlConnection.getResponseMessage());
      }
      fetchResponseETag = urlConnection.getHeaderField(ETAG_HEADER);
      fetchResponse = getFetchResponseBody(urlConnection);
    } catch (IOException | JSONException e) {
      throw new FirebaseRemoteConfigClientException(
          "The client had an error while calling the backend!", e);
    } finally {
      urlConnection.disconnect();
      // Explicitly close the input stream due to a bug in the Android okhttp implementation.
      try {
        urlConnection.getInputStream().close();
      } catch (IOException e) {
      }
    }

    if (!backendHasUpdates(fetchResponse)) {
      return FetchResponse.forBackendHasNoUpdates(currentTime);
    }

    ConfigContainer fetchedConfigs = extractConfigs(fetchResponse, currentTime);
    return FetchResponse.forBackendUpdatesFetched(fetchedConfigs, fetchResponseETag);
  }

  public  ConfigFetchHttpClient(
      Context context,
      String appId,
      String apiKey,
      String namespace,
      long connectTimeoutInSeconds,
      long readTimeoutInSeconds);

}