Index: remoting/android/java/src/org/chromium/chromoting/HostListLoader.java |
diff --git a/remoting/android/java/src/org/chromium/chromoting/HostListLoader.java b/remoting/android/java/src/org/chromium/chromoting/HostListLoader.java |
new file mode 100644 |
index 0000000000000000000000000000000000000000..b9d1c54a1640e693a50f297300b0ab5393166920 |
--- /dev/null |
+++ b/remoting/android/java/src/org/chromium/chromoting/HostListLoader.java |
@@ -0,0 +1,197 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+package org.chromium.chromoting; |
+ |
+import android.os.Handler; |
+import android.os.HandlerThread; |
+import android.os.Looper; |
+import android.util.Log; |
+ |
+import org.chromium.chromoting.jni.JniInterface; |
+import org.json.JSONArray; |
+import org.json.JSONException; |
+import org.json.JSONObject; |
+ |
+import java.io.IOException; |
+import java.net.HttpURLConnection; |
+import java.net.MalformedURLException; |
+import java.net.URL; |
+import java.util.ArrayList; |
+import java.util.Collections; |
+import java.util.Comparator; |
+import java.util.Scanner; |
+ |
+/** Helper for fetching the host list. */ |
+public class HostListLoader { |
+ public enum Error { |
+ AUTH_FAILED, |
+ NETWORK_ERROR, |
+ SERVICE_UNAVAILABLE, |
+ UNEXPECTED_RESPONSE, |
+ UNKNOWN, |
+ } |
+ |
+ /** Callback for receiving the host list, or getting notified of an error. */ |
+ public interface Callback { |
+ void onHostListReceived(HostInfo[] hosts); |
+ void onError(Error error); |
+ } |
+ |
+ /** Path from which to download a user's host list JSON object. */ |
+ private static final String HOST_LIST_PATH = |
+ "https://www.googleapis.com/chromoting/v1/@me/hosts?key="; |
+ |
+ /** Callback handler to be used for network operations. */ |
+ private Handler mNetworkThread; |
+ |
+ /** Handler for main thread. */ |
+ private Handler mMainThread; |
+ |
+ public HostListLoader() { |
+ // Thread responsible for downloading the host list. |
+ |
+ mMainThread = new Handler(Looper.getMainLooper()); |
+ } |
+ |
+ private void initNetworkThread() { |
+ if (mNetworkThread == null) { |
+ HandlerThread thread = new HandlerThread("network"); |
+ thread.start(); |
+ mNetworkThread = new Handler(thread.getLooper()); |
+ } |
+ } |
+ |
+ /** |
+ * Causes the host list to be fetched on a background thread. This should be called on the |
+ * main thread, and callbacks will also be invoked on the main thread. On success, |
+ * callback.onHostListReceived() will be called, otherwise callback.onError() will be called |
+ * with an error-code describing the failure. |
+ */ |
+ public void retrieveHostList(String authToken, Callback callback) { |
+ initNetworkThread(); |
+ final String authTokenFinal = authToken; |
+ final Callback callbackFinal = callback; |
+ mNetworkThread.post(new Runnable() { |
+ @Override |
+ public void run() { |
+ doRetrieveHostList(authTokenFinal, callbackFinal); |
+ } |
+ }); |
+ } |
+ |
+ private void doRetrieveHostList(String authToken, Callback callback) { |
+ HttpURLConnection link = null; |
+ String response = null; |
+ try { |
+ link = (HttpURLConnection) |
+ new URL(HOST_LIST_PATH + JniInterface.nativeGetApiKey()).openConnection(); |
+ link.addRequestProperty("client_id", JniInterface.nativeGetClientId()); |
+ link.addRequestProperty("client_secret", JniInterface.nativeGetClientSecret()); |
+ link.setRequestProperty("Authorization", "OAuth " + authToken); |
+ |
+ // Listen for the server to respond. |
+ int status = link.getResponseCode(); |
+ switch (status) { |
+ case HttpURLConnection.HTTP_OK: // 200 |
+ break; |
+ case HttpURLConnection.HTTP_UNAUTHORIZED: // 401 |
+ postError(callback, Error.AUTH_FAILED); |
+ return; |
+ case HttpURLConnection.HTTP_BAD_GATEWAY: // 502 |
+ case HttpURLConnection.HTTP_UNAVAILABLE: // 503 |
+ postError(callback, Error.SERVICE_UNAVAILABLE); |
+ return; |
+ default: |
+ postError(callback, Error.UNKNOWN); |
+ return; |
+ } |
+ |
+ StringBuilder responseBuilder = new StringBuilder(); |
+ Scanner incoming = new Scanner(link.getInputStream()); |
+ Log.i("auth", "Successfully authenticated to directory server"); |
+ while (incoming.hasNext()) { |
+ responseBuilder.append(incoming.nextLine()); |
+ } |
+ response = String.valueOf(responseBuilder); |
+ incoming.close(); |
+ } catch (MalformedURLException ex) { |
+ // This should never happen. |
+ throw new RuntimeException("Unexpected error while fetching host list: " + ex); |
+ } catch (IOException ex) { |
+ postError(callback, Error.NETWORK_ERROR); |
+ return; |
+ } finally { |
+ if (link != null) { |
+ link.disconnect(); |
+ } |
+ } |
+ |
+ // Parse directory response. |
+ ArrayList<HostInfo> hostList = new ArrayList<HostInfo>(); |
+ try { |
+ JSONObject data = new JSONObject(response).getJSONObject("data"); |
+ JSONArray hostsJson = data.getJSONArray("items"); |
+ Log.i("hostlist", "Received host listing from directory server"); |
+ |
+ int index = 0; |
+ while (!hostsJson.isNull(index)) { |
+ JSONObject hostJson = hostsJson.getJSONObject(index); |
+ // If a host is only recently registered, it may be missing some of the keys below. |
+ // It should still be visible in the list, even though a connection attempt will |
+ // fail because of the missing keys. The failed attempt will trigger reloading of |
+ // the host-list (once crbug.com/304719 is fixed), by which time the keys will |
+ // hopefully be present, and the retried connection can succeed. |
+ HostInfo host = new HostInfo( |
+ hostJson.getString("hostName"), |
+ hostJson.getString("hostId"), |
+ hostJson.optString("jabberId"), |
+ hostJson.optString("publicKey"), |
+ hostJson.optString("status").equals("ONLINE")); |
+ hostList.add(host); |
+ ++index; |
+ } |
+ } catch (JSONException ex) { |
+ postError(callback, Error.UNEXPECTED_RESPONSE); |
+ return; |
+ } |
+ |
+ sortHosts(hostList); |
+ |
+ final Callback callbackFinal = callback; |
+ final HostInfo[] hosts = hostList.toArray(new HostInfo[hostList.size()]); |
+ mMainThread.post(new Runnable() { |
+ @Override |
+ public void run() { |
+ callbackFinal.onHostListReceived(hosts); |
+ } |
+ }); |
+ } |
+ |
+ /** Posts error to callback on main thread. */ |
+ private void postError(Callback callback, Error error) { |
+ final Callback callbackFinal = callback; |
+ final Error errorFinal = error; |
+ mMainThread.post(new Runnable() { |
+ @Override |
+ public void run() { |
+ callbackFinal.onError(errorFinal); |
+ } |
+ }); |
+ } |
+ |
+ private static void sortHosts(ArrayList<HostInfo> hosts) { |
+ Comparator<HostInfo> hostComparator = new Comparator<HostInfo>() { |
+ public int compare(HostInfo a, HostInfo b) { |
+ if (a.isOnline != b.isOnline) { |
+ return a.isOnline ? -1 : 1; |
+ } |
+ String aName = a.name.toUpperCase(); |
+ String bName = b.name.toUpperCase(); |
+ return aName.compareTo(bName); |
+ } |
+ }; |
+ Collections.sort(hosts, hostComparator); |
+ } |
+} |