Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chromoting; | 5 package org.chromium.chromoting; |
| 6 | 6 |
| 7 import android.os.Handler; | 7 import android.os.Handler; |
| 8 import android.os.HandlerThread; | 8 import android.os.HandlerThread; |
| 9 import android.os.Looper; | 9 import android.os.Looper; |
| 10 | 10 |
| 11 import org.chromium.base.Log; | 11 import org.chromium.base.Log; |
| 12 import org.json.JSONArray; | 12 import org.json.JSONArray; |
| 13 import org.json.JSONException; | 13 import org.json.JSONException; |
| 14 import org.json.JSONObject; | 14 import org.json.JSONObject; |
| 15 | 15 |
| 16 import java.io.IOException; | 16 import java.io.IOException; |
| 17 import java.io.OutputStream; | |
| 17 import java.net.HttpURLConnection; | 18 import java.net.HttpURLConnection; |
| 18 import java.net.MalformedURLException; | 19 import java.net.MalformedURLException; |
| 19 import java.net.URL; | 20 import java.net.URL; |
| 20 import java.util.ArrayList; | 21 import java.util.ArrayList; |
| 21 import java.util.Collections; | 22 import java.util.Collections; |
| 22 import java.util.Comparator; | 23 import java.util.Comparator; |
| 23 import java.util.Locale; | 24 import java.util.Locale; |
| 24 import java.util.Scanner; | 25 import java.util.Scanner; |
| 25 | 26 |
| 26 /** Helper for fetching the host list. */ | 27 /** Helper for fetching and modifying the host list. */ |
| 27 public class HostListLoader { | 28 public class HostListManager { |
| 28 public enum Error { | 29 public enum Error { |
| 29 AUTH_FAILED, | 30 AUTH_FAILED, |
| 30 NETWORK_ERROR, | 31 NETWORK_ERROR, |
| 31 SERVICE_UNAVAILABLE, | 32 SERVICE_UNAVAILABLE, |
| 32 UNEXPECTED_RESPONSE, | 33 UNEXPECTED_RESPONSE, |
| 33 UNKNOWN, | 34 UNKNOWN, |
| 34 } | 35 } |
| 35 | 36 |
| 36 /** Callback for receiving the host list, or getting notified of an error. * / | 37 /** Callback for receiving the host list, or getting notified of an error. * / |
| 37 public interface Callback { | 38 public interface Callback { |
| 38 void onHostListReceived(HostInfo[] hosts); | 39 void onHostListReceived(HostInfo[] response); |
| 40 void onHostUpdated(); | |
| 41 void onHostDeleted(); | |
| 39 void onError(Error error); | 42 void onError(Error error); |
| 40 } | 43 } |
| 41 | 44 |
| 45 /** | |
| 46 * Represents a response from the directory server. | |
| 47 * |error| and |body| can be null and they can't be both non-null. | |
|
Lambros
2016/05/05 00:00:14
The way this is phrased, it sounds like |error| an
Yuwei
2016/05/05 00:40:59
Basically error==/!=null tells you whether the req
Yuwei
2016/05/06 00:31:58
I guess it looks clearer now...
| |
| 48 **/ | |
| 49 private static class Response { | |
| 50 public final Error error; | |
| 51 public final String body; | |
| 52 public Response(Error error, String body) { | |
| 53 this.error = error; | |
| 54 this.body = body; | |
| 55 } | |
| 56 } | |
| 57 | |
| 42 private static final String TAG = "Chromoting"; | 58 private static final String TAG = "Chromoting"; |
| 43 | 59 |
| 44 /** Path from which to download a user's host list JSON object. */ | 60 /** Path from which to download a user's host list JSON object. */ |
| 45 private static final String HOST_LIST_PATH = | 61 private static final String HOST_LIST_PATH = |
| 46 "https://www.googleapis.com/chromoting/v1/@me/hosts"; | 62 "https://www.googleapis.com/chromoting/v1/@me/hosts"; |
| 47 | 63 |
| 48 /** Callback handler to be used for network operations. */ | 64 /** Callback handler to be used for network operations. */ |
| 49 private Handler mNetworkThread; | 65 private Handler mNetworkThread; |
| 50 | 66 |
| 51 /** Handler for main thread. */ | 67 /** Handler for main thread. */ |
| 52 private Handler mMainThread; | 68 private Handler mMainThread; |
| 53 | 69 |
| 54 public HostListLoader() { | 70 public HostListManager() { |
| 55 // Thread responsible for downloading the host list. | 71 // Thread responsible for downloading the host list. |
| 56 | 72 |
| 57 mMainThread = new Handler(Looper.getMainLooper()); | 73 mMainThread = new Handler(Looper.getMainLooper()); |
| 58 } | 74 } |
| 59 | 75 |
| 60 private void initNetworkThread() { | 76 private void runOnNetworkThread(Runnable runnable) { |
| 61 if (mNetworkThread == null) { | 77 if (mNetworkThread == null) { |
| 62 HandlerThread thread = new HandlerThread("network"); | 78 HandlerThread thread = new HandlerThread("network"); |
| 63 thread.start(); | 79 thread.start(); |
| 64 mNetworkThread = new Handler(thread.getLooper()); | 80 mNetworkThread = new Handler(thread.getLooper()); |
| 65 } | 81 } |
| 82 mNetworkThread.post(runnable); | |
| 66 } | 83 } |
| 67 | 84 |
| 68 /** | 85 /** |
| 69 * Causes the host list to be fetched on a background thread. This should b e called on the | 86 * Causes the host list to be fetched on a background thread. This should b e called on the |
| 70 * main thread, and callbacks will also be invoked on the main thread. On s uccess, | 87 * main thread, and callbacks will also be invoked on the main thread. On s uccess, |
| 71 * callback.onHostListReceived() will be called, otherwise callback.onError () will be called | 88 * callback.onHostListReceived() will be called, otherwise callback.onError () will be called |
| 72 * with an error-code describing the failure. | 89 * with an error-code describing the failure. |
| 73 */ | 90 */ |
| 74 public void retrieveHostList(String authToken, Callback callback) { | 91 public void retrieveHostList(final String authToken, final Callback callback ) { |
| 75 initNetworkThread(); | 92 runOnNetworkThread(new Runnable() { |
| 76 final String authTokenFinal = authToken; | |
| 77 final Callback callbackFinal = callback; | |
| 78 mNetworkThread.post(new Runnable() { | |
| 79 @Override | 93 @Override |
| 80 public void run() { | 94 public void run() { |
| 81 doRetrieveHostList(authTokenFinal, callbackFinal); | 95 doRetrieveHostList(authToken, callback); |
| 82 } | 96 } |
| 83 }); | 97 }); |
| 84 } | 98 } |
| 85 | 99 |
| 86 private void doRetrieveHostList(String authToken, Callback callback) { | 100 private void doRetrieveHostList(String authToken, Callback callback) { |
| 87 HttpURLConnection link = null; | 101 Response response = sendRequest(authToken, HOST_LIST_PATH, "GET", null, null); |
| 88 String response = null; | 102 if (response.error != null) { |
| 89 try { | 103 postError(callback, response.error); |
| 90 link = (HttpURLConnection) new URL(HOST_LIST_PATH).openConnection(); | |
| 91 link.setRequestProperty("Authorization", "OAuth " + authToken); | |
| 92 | |
| 93 // Listen for the server to respond. | |
| 94 int status = link.getResponseCode(); | |
| 95 switch (status) { | |
| 96 case HttpURLConnection.HTTP_OK: // 200 | |
| 97 break; | |
| 98 case HttpURLConnection.HTTP_UNAUTHORIZED: // 401 | |
| 99 postError(callback, Error.AUTH_FAILED); | |
| 100 return; | |
| 101 case HttpURLConnection.HTTP_BAD_GATEWAY: // 502 | |
| 102 case HttpURLConnection.HTTP_UNAVAILABLE: // 503 | |
| 103 postError(callback, Error.SERVICE_UNAVAILABLE); | |
| 104 return; | |
| 105 default: | |
| 106 postError(callback, Error.UNKNOWN); | |
| 107 return; | |
| 108 } | |
| 109 | |
| 110 StringBuilder responseBuilder = new StringBuilder(); | |
| 111 Scanner incoming = new Scanner(link.getInputStream()); | |
| 112 while (incoming.hasNext()) { | |
| 113 responseBuilder.append(incoming.nextLine()); | |
| 114 } | |
| 115 response = String.valueOf(responseBuilder); | |
| 116 incoming.close(); | |
| 117 } catch (MalformedURLException ex) { | |
| 118 // This should never happen. | |
| 119 throw new RuntimeException("Unexpected error while fetching host lis t: ", ex); | |
| 120 } catch (IOException ex) { | |
| 121 postError(callback, Error.NETWORK_ERROR); | |
| 122 return; | 104 return; |
| 123 } finally { | |
| 124 if (link != null) { | |
| 125 link.disconnect(); | |
| 126 } | |
| 127 } | 105 } |
| 128 | 106 |
| 129 // Parse directory response. | 107 // Parse directory response. |
| 130 ArrayList<HostInfo> hostList = new ArrayList<HostInfo>(); | 108 ArrayList<HostInfo> hostList = new ArrayList<HostInfo>(); |
| 131 try { | 109 try { |
| 132 JSONObject data = new JSONObject(response).getJSONObject("data"); | 110 JSONObject data = new JSONObject(response.body).getJSONObject("data" ); |
| 133 if (data.has("items")) { | 111 if (data.has("items")) { |
| 134 JSONArray hostsJson = data.getJSONArray("items"); | 112 JSONArray hostsJson = data.getJSONArray("items"); |
| 135 | 113 |
| 136 int index = 0; | 114 int index = 0; |
| 137 while (!hostsJson.isNull(index)) { | 115 while (!hostsJson.isNull(index)) { |
| 138 JSONObject hostJson = hostsJson.getJSONObject(index); | 116 JSONObject hostJson = hostsJson.getJSONObject(index); |
| 139 // If a host is only recently registered, it may be missing some of the keys | 117 // If a host is only recently registered, it may be missing some of the keys |
| 140 // below. It should still be visible in the list, even thoug h a connection | 118 // below. It should still be visible in the list, even thoug h a connection |
| 141 // attempt will fail because of the missing keys. The failed attempt will | 119 // attempt will fail because of the missing keys. The failed attempt will |
| 142 // trigger reloading of the host-list, by which time the key s will hopefully be | 120 // trigger reloading of the host-list, by which time the key s will hopefully be |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 158 final Callback callbackFinal = callback; | 136 final Callback callbackFinal = callback; |
| 159 final HostInfo[] hosts = hostList.toArray(new HostInfo[hostList.size()]) ; | 137 final HostInfo[] hosts = hostList.toArray(new HostInfo[hostList.size()]) ; |
| 160 mMainThread.post(new Runnable() { | 138 mMainThread.post(new Runnable() { |
| 161 @Override | 139 @Override |
| 162 public void run() { | 140 public void run() { |
| 163 callbackFinal.onHostListReceived(hosts); | 141 callbackFinal.onHostListReceived(hosts); |
| 164 } | 142 } |
| 165 }); | 143 }); |
| 166 } | 144 } |
| 167 | 145 |
| 146 /** | |
| 147 * Updates a host on the background thread. On success, callback.onHostUpdat ed() will be called, | |
| 148 * otherwise callback.onError() will be called with an error-code describing the failure. | |
| 149 */ | |
| 150 public void putHost(final String authToken, final String hostId, final Strin g hostName, | |
| 151 final String publicKey, final Callback callback) { | |
| 152 runOnNetworkThread(new Runnable() { | |
| 153 @Override | |
| 154 public void run() { | |
| 155 doPutHost(authToken, hostId, hostName, publicKey, callback); | |
| 156 } | |
| 157 }); | |
| 158 } | |
| 159 | |
| 160 private void doPutHost(String authToken, String hostId, String hostName, Str ing publicKey, | |
| 161 final Callback callback) { | |
| 162 String requestJson; | |
| 163 try { | |
| 164 JSONObject data = new JSONObject(); | |
| 165 data.put("hostId", hostId); | |
| 166 data.put("hostName", hostName); | |
| 167 data.put("publicKey", publicKey); | |
| 168 JSONObject request = new JSONObject(); | |
| 169 request.put("data", data); | |
| 170 requestJson = request.toString(); | |
| 171 } catch (JSONException ex) { | |
| 172 Log.e(TAG, "Error creating put host JSON string: %s", ex.getMessage( )); | |
| 173 postError(callback, Error.UNKNOWN); | |
| 174 return; | |
| 175 } | |
| 176 Response response = sendRequest(authToken, HOST_LIST_PATH + '/' + hostId , "PUT", | |
| 177 "application/json", requestJson); | |
| 178 if (response.error != null) { | |
| 179 postError(callback, response.error); | |
| 180 } else { | |
| 181 mMainThread.post(new Runnable() { | |
| 182 @Override | |
| 183 public void run() { | |
| 184 callback.onHostUpdated(); | |
| 185 } | |
| 186 }); | |
| 187 } | |
| 188 } | |
| 189 | |
| 190 /** | |
| 191 * Deletes a host on the background thread. On success, callback.onHostUpdat ed() will be called, | |
| 192 * otherwise callback.onError() will be called with an error-code describing the failure. | |
| 193 */ | |
| 194 public void deleteHost(final String authToken, final String hostId, | |
| 195 final Callback callback) { | |
| 196 runOnNetworkThread(new Runnable() { | |
| 197 @Override | |
| 198 public void run() { | |
| 199 doDeleteHost(authToken, hostId, callback); | |
| 200 } | |
| 201 }); | |
| 202 } | |
| 203 | |
| 204 private void doDeleteHost(String authToken, String hostId, final Callback ca llback) { | |
| 205 Response response = sendRequest(authToken, HOST_LIST_PATH + '/' + hostId , "DELETE", | |
| 206 null, null); | |
| 207 if (response.error != null) { | |
| 208 postError(callback, response.error); | |
| 209 } else { | |
| 210 mMainThread.post(new Runnable() { | |
| 211 @Override | |
| 212 public void run() { | |
| 213 callback.onHostDeleted(); | |
| 214 } | |
| 215 }); | |
| 216 } | |
| 217 } | |
| 218 | |
| 168 /** Posts error to callback on main thread. */ | 219 /** Posts error to callback on main thread. */ |
| 169 private void postError(Callback callback, Error error) { | 220 private void postError(Callback callback, Error error) { |
| 170 final Callback callbackFinal = callback; | 221 final Callback callbackFinal = callback; |
| 171 final Error errorFinal = error; | 222 final Error errorFinal = error; |
| 172 mMainThread.post(new Runnable() { | 223 mMainThread.post(new Runnable() { |
| 173 @Override | 224 @Override |
| 174 public void run() { | 225 public void run() { |
| 175 callbackFinal.onError(errorFinal); | 226 callbackFinal.onError(errorFinal); |
| 176 } | 227 } |
| 177 }); | 228 }); |
| 178 } | 229 } |
| 179 | 230 |
| 180 private static void sortHosts(ArrayList<HostInfo> hosts) { | 231 private static void sortHosts(ArrayList<HostInfo> hosts) { |
| 181 Comparator<HostInfo> hostComparator = new Comparator<HostInfo>() { | 232 Comparator<HostInfo> hostComparator = new Comparator<HostInfo>() { |
| 182 @Override | 233 @Override |
| 183 public int compare(HostInfo a, HostInfo b) { | 234 public int compare(HostInfo a, HostInfo b) { |
| 184 if (a.isOnline != b.isOnline) { | 235 if (a.isOnline != b.isOnline) { |
| 185 return a.isOnline ? -1 : 1; | 236 return a.isOnline ? -1 : 1; |
| 186 } | 237 } |
| 187 String aName = a.name.toUpperCase(Locale.getDefault()); | 238 String aName = a.name.toUpperCase(Locale.getDefault()); |
| 188 String bName = b.name.toUpperCase(Locale.getDefault()); | 239 String bName = b.name.toUpperCase(Locale.getDefault()); |
| 189 return aName.compareTo(bName); | 240 return aName.compareTo(bName); |
| 190 } | 241 } |
| 191 }; | 242 }; |
| 192 Collections.sort(hosts, hostComparator); | 243 Collections.sort(hosts, hostComparator); |
| 193 } | 244 } |
| 245 | |
| 246 /** | |
| 247 * Sends request to the url and returns the response. | |
| 248 * @param authToken auth token | |
| 249 * @param url the URL to send the request | |
| 250 * @param method /GET/POST/PUT/DELETE/etc. | |
| 251 * @param requestContentType The content type of the request body. This can be null. | |
| 252 * @param requestBody This can be null. | |
| 253 * @return The response. | |
| 254 */ | |
| 255 private static Response sendRequest(String authToken, String url, String met hod, | |
| 256 String requestContentType, String reques tBody) { | |
| 257 HttpURLConnection link = null; | |
| 258 Error error = null; | |
| 259 try { | |
| 260 link = (HttpURLConnection) new URL(url).openConnection(); | |
| 261 link.setRequestMethod(method); | |
| 262 link.setRequestProperty("Authorization", "OAuth " + authToken); | |
| 263 if (requestContentType != null) { | |
| 264 link.setRequestProperty("Content-Type", requestContentType); | |
| 265 } | |
| 266 if (requestBody != null) { | |
| 267 byte[] requestBytes = requestBody.getBytes("UTF-8"); | |
| 268 OutputStream outStream = link.getOutputStream(); | |
| 269 outStream.write(requestBytes); | |
| 270 outStream.close(); | |
| 271 } | |
| 272 | |
| 273 // Listen for the server to respond. | |
| 274 int status = link.getResponseCode(); | |
| 275 switch (status) { | |
| 276 case HttpURLConnection.HTTP_OK: // 200 | |
| 277 break; | |
| 278 case HttpURLConnection.HTTP_UNAUTHORIZED: // 401 | |
| 279 error = Error.AUTH_FAILED; | |
| 280 break; | |
| 281 case HttpURLConnection.HTTP_BAD_GATEWAY: // 502 | |
| 282 case HttpURLConnection.HTTP_UNAVAILABLE: // 503 | |
| 283 error = Error.SERVICE_UNAVAILABLE; | |
| 284 break; | |
| 285 default: | |
| 286 error = Error.UNKNOWN; | |
| 287 } | |
| 288 | |
| 289 if (error != null) { | |
| 290 return new Response(error, null); | |
| 291 } | |
| 292 | |
| 293 StringBuilder responseBuilder = new StringBuilder(); | |
| 294 Scanner incoming = new Scanner(link.getInputStream()); | |
| 295 while (incoming.hasNext()) { | |
| 296 responseBuilder.append(incoming.nextLine()); | |
| 297 } | |
| 298 String response = String.valueOf(responseBuilder); | |
|
Lambros
2016/05/05 00:00:14
Just my own preference, but I would write this as
Yuwei
2016/05/05 00:40:58
That's right... BTW the code comes from HostListLo
Yuwei
2016/05/06 00:31:58
Done.
| |
| 299 incoming.close(); | |
| 300 return new Response(null, response); | |
| 301 } catch (MalformedURLException ex) { | |
| 302 // This should never happen. | |
| 303 throw new RuntimeException("Unexpected error while fetching host lis t: ", ex); | |
| 304 } catch (IOException ex) { | |
| 305 return new Response(Error.NETWORK_ERROR, null); | |
| 306 } finally { | |
| 307 if (link != null) { | |
| 308 link.disconnect(); | |
| 309 } | |
| 310 } | |
| 311 } | |
| 194 } | 312 } |
| OLD | NEW |