1d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian/*
2d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * Copyright (C) 2017 The Android Open Source Project
3d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
4d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * Licensed under the Apache License, Version 2.0 (the "License");
5d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * you may not use this file except in compliance with the License.
6d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * You may obtain a copy of the License at
7d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
8d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *      http://www.apache.org/licenses/LICENSE-2.0
9d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian *
10d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * Unless required by applicable law or agreed to in writing, software
11d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * distributed under the License is distributed on an "AS IS" BASIS,
12d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * See the License for the specific language governing permissions and
14d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian * limitations under the License
15d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian */
16d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
17d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianpackage com.android.incallui.calllocation.impl;
18d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
19d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport static com.android.dialer.util.DialerUtils.closeQuietly;
20d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
21d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.content.Context;
22d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.net.Uri;
23d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.net.Uri.Builder;
24d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.os.SystemClock;
25d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport android.util.Pair;
26d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.dialer.common.LogUtil;
27d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.android.dialer.util.MoreStrings;
28d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport com.google.android.common.http.UrlRules;
29d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.io.ByteArrayOutputStream;
30d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.io.FilterInputStream;
31d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.io.IOException;
32d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.io.InputStream;
33d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.net.HttpURLConnection;
34d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.net.MalformedURLException;
35d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.net.ProtocolException;
36d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.net.URL;
37d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.List;
38d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.Objects;
39d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianimport java.util.Set;
40d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
41d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian/** Utility for making http requests. */
42d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanianpublic class HttpFetcher {
43d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
44d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  // Phone number
45d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static final String PARAM_ID = "id";
46d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  // auth token
47d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static final String PARAM_ACCESS_TOKEN = "access_token";
48d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static final String TAG = HttpFetcher.class.getSimpleName();
49d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
50d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
51d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Send a http request to the given url.
52d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
53d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param urlString The url to request.
54d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return The response body as a byte array. Or {@literal null} if status code is not 2xx.
55d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @throws java.io.IOException when an error occurs.
56d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
57d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static byte[] sendRequestAsByteArray(
58d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      Context context, String urlString, String requestMethod, List<Pair<String, String>> headers)
59d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      throws IOException, AuthException {
60d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    Objects.requireNonNull(urlString);
61d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
62d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    URL url = reWriteUrl(context, urlString);
63d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (url == null) {
64d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return null;
65d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
66d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
67d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    HttpURLConnection conn = null;
68d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    InputStream is = null;
69d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    boolean isError = false;
70d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final long start = SystemClock.uptimeMillis();
71d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    try {
72d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      conn = (HttpURLConnection) url.openConnection();
73d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      setMethodAndHeaders(conn, requestMethod, headers);
74d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      int responseCode = conn.getResponseCode();
75d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      LogUtil.i("HttpFetcher.sendRequestAsByteArray", "response code: " + responseCode);
76d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // All 2xx codes are successful.
77d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (responseCode / 100 == 2) {
78d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        is = conn.getInputStream();
79d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      } else {
80d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        is = conn.getErrorStream();
81d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        isError = true;
82d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
83d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
84d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      final ByteArrayOutputStream baos = new ByteArrayOutputStream();
85d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      final byte[] buffer = new byte[1024];
86d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      int bytesRead;
87d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
88d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      while ((bytesRead = is.read(buffer)) != -1) {
89d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        baos.write(buffer, 0, bytesRead);
90d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
91d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
92d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (isError) {
93d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        handleBadResponse(url.toString(), baos.toByteArray());
94d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        if (responseCode == 401) {
95d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          throw new AuthException("Auth error");
96d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        }
97d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        return null;
98d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
99d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
100d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      byte[] response = baos.toByteArray();
101d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      LogUtil.i("HttpFetcher.sendRequestAsByteArray", "received " + response.length + " bytes");
102d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      long end = SystemClock.uptimeMillis();
103d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      LogUtil.i("HttpFetcher.sendRequestAsByteArray", "fetch took " + (end - start) + " ms");
104d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return response;
105d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } finally {
106d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      closeQuietly(is);
107d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (conn != null) {
108d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        conn.disconnect();
109d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
110d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
111d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
112d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
113d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
114d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Send a http request to the given url.
115d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
116d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return The response body as a InputStream. Or {@literal null} if status code is not 2xx.
117d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @throws java.io.IOException when an error occurs.
118d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
119d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static InputStream sendRequestAsInputStream(
120d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      Context context, String urlString, String requestMethod, List<Pair<String, String>> headers)
121d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      throws IOException, AuthException {
122d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    Objects.requireNonNull(urlString);
123d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
124d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    URL url = reWriteUrl(context, urlString);
125d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (url == null) {
126d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return null;
127d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
128d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
129d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    HttpURLConnection httpUrlConnection = null;
130d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    boolean isSuccess = false;
131d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    try {
132d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      httpUrlConnection = (HttpURLConnection) url.openConnection();
133d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      setMethodAndHeaders(httpUrlConnection, requestMethod, headers);
134d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      int responseCode = httpUrlConnection.getResponseCode();
135d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      LogUtil.i("HttpFetcher.sendRequestAsInputStream", "response code: " + responseCode);
136d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
137d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (responseCode == 401) {
138d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        throw new AuthException("Auth error");
139d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      } else if (responseCode / 100 == 2) { // All 2xx codes are successful.
140d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        InputStream is = httpUrlConnection.getInputStream();
141d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        if (is != null) {
142d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          is = new HttpInputStreamWrapper(httpUrlConnection, is);
143d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          isSuccess = true;
144d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          return is;
145d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        }
146d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
147d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
148d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return null;
149d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } finally {
150d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (httpUrlConnection != null && !isSuccess) {
151d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        httpUrlConnection.disconnect();
152d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
153d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
154d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
155d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
156d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
157d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Set http method and headers.
158d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
159d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param conn The connection to add headers to.
160d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param requestMethod request method
161d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param headers http headers where the first item in the pair is the key and second item is the
162d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *     value.
163d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
164d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static void setMethodAndHeaders(
165d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      HttpURLConnection conn, String requestMethod, List<Pair<String, String>> headers)
166d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      throws ProtocolException {
167d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    conn.setRequestMethod(requestMethod);
168d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (headers != null) {
169d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      for (Pair<String, String> pair : headers) {
170d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        conn.setRequestProperty(pair.first, pair.second);
171d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
172d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
173d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
174d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
175d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static String obfuscateUrl(String urlString) {
176d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final Uri uri = Uri.parse(urlString);
177d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final Builder builder =
178d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        new Builder().scheme(uri.getScheme()).authority(uri.getAuthority()).path(uri.getPath());
179d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final Set<String> names = uri.getQueryParameterNames();
180d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    for (String name : names) {
181d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (PARAM_ACCESS_TOKEN.equals(name)) {
182d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        builder.appendQueryParameter(name, "token");
183d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      } else {
184d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        final String value = uri.getQueryParameter(name);
185d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        if (PARAM_ID.equals(name)) {
186d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          builder.appendQueryParameter(name, MoreStrings.toSafeString(value));
187d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        } else {
188d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian          builder.appendQueryParameter(name, value);
189d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        }
190d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
191d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
192d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return builder.toString();
193d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
194d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
195d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /** Same as {@link #getRequestAsString(Context, String, String, List)} with null headers. */
196d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static String getRequestAsString(Context context, String urlString)
197d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      throws IOException, AuthException {
198d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return getRequestAsString(context, urlString, "GET" /* Default to get. */, null);
199d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
200d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
201d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
202d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Send a http request to the given url.
203d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
204d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param context The android context.
205d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param urlString The url to request.
206d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @param headers Http headers to pass in the request. {@literal null} is allowed.
207d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return The response body as a String. Or {@literal null} if status code is not 2xx.
208d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @throws java.io.IOException when an error occurs.
209d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
210d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  public static String getRequestAsString(
211d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      Context context, String urlString, String requestMethod, List<Pair<String, String>> headers)
212d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      throws IOException, AuthException {
213d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final byte[] byteArr = sendRequestAsByteArray(context, urlString, requestMethod, headers);
214d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (byteArr == null) {
215d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      // Encountered error response... just return.
216d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return null;
217d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
218d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final String response = new String(byteArr);
219d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    LogUtil.i("HttpFetcher.getRequestAsString", "response body: " + response);
220d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return response;
221d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
222d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
223d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /**
224d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * Lookup up url re-write rules from gServices and apply to the given url.
225d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   *
226fc37b02f5d3381a7882770941e461b13b679b6efEric Erfanian   * <p>https://wiki.corp.google.com/twiki/bin/view/Main/AndroidGservices#URL_Rewriting_Rules
227fc37b02f5d3381a7882770941e461b13b679b6efEric Erfanian   *
228d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   * @return The new url.
229d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian   */
230d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static URL reWriteUrl(Context context, String url) {
231d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final UrlRules rules = UrlRules.getRules(context.getContentResolver());
232d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final UrlRules.Rule rule = rules.matchRule(url);
233d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final String newUrl = rule.apply(url);
234d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
235d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (newUrl == null) {
236d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (LogUtil.isDebugEnabled()) {
237d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        // Url is blocked by re-write.
238d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        LogUtil.i(
239d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            "HttpFetcher.reWriteUrl",
240d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            "url " + obfuscateUrl(url) + " is blocked.  Ignoring request.");
241d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
242d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      return null;
243d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
244d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
245d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    if (LogUtil.isDebugEnabled()) {
246d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      LogUtil.i("HttpFetcher.reWriteUrl", "fetching " + obfuscateUrl(newUrl));
247d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (!newUrl.equals(url)) {
248d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        LogUtil.i(
249d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            "HttpFetcher.reWriteUrl",
250d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian            "Original url: " + obfuscateUrl(url) + ", after re-write: " + obfuscateUrl(newUrl));
251d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
252d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
253d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
254d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    URL urlObject = null;
255d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    try {
256d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      urlObject = new URL(newUrl);
257d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    } catch (MalformedURLException e) {
258d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      LogUtil.e("HttpFetcher.reWriteUrl", "failed to parse url: " + url, e);
259d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
260d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    return urlObject;
261d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
262d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
263d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static void handleBadResponse(String url, byte[] response) {
264d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    LogUtil.i("HttpFetcher.handleBadResponse", "Got bad response code from url: " + url);
265d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    LogUtil.i("HttpFetcher.handleBadResponse", new String(response));
266d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
267d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
268d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  /** Disconnect {@link HttpURLConnection} when InputStream is closed */
269d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  private static class HttpInputStreamWrapper extends FilterInputStream {
270d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
271d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final HttpURLConnection mHttpUrlConnection;
272d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    final long mStartMillis = SystemClock.uptimeMillis();
273d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
274d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    public HttpInputStreamWrapper(HttpURLConnection conn, InputStream in) {
275d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      super(in);
276d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      mHttpUrlConnection = conn;
277d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
278d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian
279d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    @Override
280d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    public void close() throws IOException {
281d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      super.close();
282d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      mHttpUrlConnection.disconnect();
283d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      if (LogUtil.isDebugEnabled()) {
284d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        long endMillis = SystemClock.uptimeMillis();
285d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian        LogUtil.i("HttpFetcher.close", "fetch took " + (endMillis - mStartMillis) + " ms");
286d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian      }
287d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian    }
288d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian  }
289d5e47f6da5b08b13ecdfa7f1edc7e12aeb83fab9Eric Erfanian}
290