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
3 // found in the LICENSE file.
5 package org
.chromium
.chromoting
;
7 import android
.os
.Handler
;
8 import android
.os
.HandlerThread
;
9 import android
.os
.Looper
;
10 import android
.util
.Log
;
12 import org
.json
.JSONArray
;
13 import org
.json
.JSONException
;
14 import org
.json
.JSONObject
;
16 import java
.io
.IOException
;
17 import java
.net
.HttpURLConnection
;
18 import java
.net
.MalformedURLException
;
20 import java
.util
.ArrayList
;
21 import java
.util
.Collections
;
22 import java
.util
.Comparator
;
23 import java
.util
.Locale
;
24 import java
.util
.Scanner
;
26 /** Helper for fetching the host list. */
27 public class HostListLoader
{
36 /** Callback for receiving the host list, or getting notified of an error. */
37 public interface Callback
{
38 void onHostListReceived(HostInfo
[] hosts
);
39 void onError(Error error
);
42 /** Path from which to download a user's host list JSON object. */
43 private static final String HOST_LIST_PATH
=
44 "https://www.googleapis.com/chromoting/v1/@me/hosts";
46 /** Callback handler to be used for network operations. */
47 private Handler mNetworkThread
;
49 /** Handler for main thread. */
50 private Handler mMainThread
;
52 public HostListLoader() {
53 // Thread responsible for downloading the host list.
55 mMainThread
= new Handler(Looper
.getMainLooper());
58 private void initNetworkThread() {
59 if (mNetworkThread
== null) {
60 HandlerThread thread
= new HandlerThread("network");
62 mNetworkThread
= new Handler(thread
.getLooper());
67 * Causes the host list to be fetched on a background thread. This should be called on the
68 * main thread, and callbacks will also be invoked on the main thread. On success,
69 * callback.onHostListReceived() will be called, otherwise callback.onError() will be called
70 * with an error-code describing the failure.
72 public void retrieveHostList(String authToken
, Callback callback
) {
74 final String authTokenFinal
= authToken
;
75 final Callback callbackFinal
= callback
;
76 mNetworkThread
.post(new Runnable() {
79 doRetrieveHostList(authTokenFinal
, callbackFinal
);
84 private void doRetrieveHostList(String authToken
, Callback callback
) {
85 HttpURLConnection link
= null;
86 String response
= null;
88 link
= (HttpURLConnection
) new URL(HOST_LIST_PATH
).openConnection();
89 link
.setRequestProperty("Authorization", "OAuth " + authToken
);
91 // Listen for the server to respond.
92 int status
= link
.getResponseCode();
94 case HttpURLConnection
.HTTP_OK
: // 200
96 case HttpURLConnection
.HTTP_UNAUTHORIZED
: // 401
97 postError(callback
, Error
.AUTH_FAILED
);
99 case HttpURLConnection
.HTTP_BAD_GATEWAY
: // 502
100 case HttpURLConnection
.HTTP_UNAVAILABLE
: // 503
101 postError(callback
, Error
.SERVICE_UNAVAILABLE
);
104 postError(callback
, Error
.UNKNOWN
);
108 StringBuilder responseBuilder
= new StringBuilder();
109 Scanner incoming
= new Scanner(link
.getInputStream());
110 Log
.i("auth", "Successfully authenticated to directory server");
111 while (incoming
.hasNext()) {
112 responseBuilder
.append(incoming
.nextLine());
114 response
= String
.valueOf(responseBuilder
);
116 } catch (MalformedURLException ex
) {
117 // This should never happen.
118 throw new RuntimeException("Unexpected error while fetching host list: " + ex
);
119 } catch (IOException ex
) {
120 postError(callback
, Error
.NETWORK_ERROR
);
128 // Parse directory response.
129 ArrayList
<HostInfo
> hostList
= new ArrayList
<HostInfo
>();
131 JSONObject data
= new JSONObject(response
).getJSONObject("data");
132 if (data
.has("items")) {
133 JSONArray hostsJson
= data
.getJSONArray("items");
134 Log
.i("hostlist", "Received host listing from directory server");
137 while (!hostsJson
.isNull(index
)) {
138 JSONObject hostJson
= hostsJson
.getJSONObject(index
);
139 // 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 though a connection
141 // attempt will fail because of the missing keys. The failed attempt will
142 // trigger reloading of the host-list, by which time the keys will hopefully be
143 // present, and the retried connection can succeed.
144 HostInfo host
= HostInfo
.create(hostJson
);
149 } catch (JSONException ex
) {
150 Log
.e("hostlist", "Error parsing host list response: ", ex
);
151 postError(callback
, Error
.UNEXPECTED_RESPONSE
);
157 final Callback callbackFinal
= callback
;
158 final HostInfo
[] hosts
= hostList
.toArray(new HostInfo
[hostList
.size()]);
159 mMainThread
.post(new Runnable() {
162 callbackFinal
.onHostListReceived(hosts
);
167 /** Posts error to callback on main thread. */
168 private void postError(Callback callback
, Error error
) {
169 final Callback callbackFinal
= callback
;
170 final Error errorFinal
= error
;
171 mMainThread
.post(new Runnable() {
174 callbackFinal
.onError(errorFinal
);
179 private static void sortHosts(ArrayList
<HostInfo
> hosts
) {
180 Comparator
<HostInfo
> hostComparator
= new Comparator
<HostInfo
>() {
182 public int compare(HostInfo a
, HostInfo b
) {
183 if (a
.isOnline
!= b
.isOnline
) {
184 return a
.isOnline ?
-1 : 1;
186 String aName
= a
.name
.toUpperCase(Locale
.getDefault());
187 String bName
= b
.name
.toUpperCase(Locale
.getDefault());
188 return aName
.compareTo(bName
);
191 Collections
.sort(hosts
, hostComparator
);