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
.chromium
.chromoting
.jni
.JniInterface
;
13 import org
.json
.JSONArray
;
14 import org
.json
.JSONException
;
15 import org
.json
.JSONObject
;
17 import java
.io
.IOException
;
18 import java
.net
.HttpURLConnection
;
19 import java
.net
.MalformedURLException
;
21 import java
.util
.ArrayList
;
22 import java
.util
.Collections
;
23 import java
.util
.Comparator
;
24 import java
.util
.Locale
;
25 import java
.util
.Scanner
;
27 /** Helper for fetching the host list. */
28 public class HostListLoader
{
37 /** Callback for receiving the host list, or getting notified of an error. */
38 public interface Callback
{
39 void onHostListReceived(HostInfo
[] hosts
);
40 void onError(Error error
);
43 /** Path from which to download a user's host list JSON object. */
44 private static final String HOST_LIST_PATH
=
45 "https://www.googleapis.com/chromoting/v1/@me/hosts?key=";
47 /** Callback handler to be used for network operations. */
48 private Handler mNetworkThread
;
50 /** Handler for main thread. */
51 private Handler mMainThread
;
53 public HostListLoader() {
54 // Thread responsible for downloading the host list.
56 mMainThread
= new Handler(Looper
.getMainLooper());
59 private void initNetworkThread() {
60 if (mNetworkThread
== null) {
61 HandlerThread thread
= new HandlerThread("network");
63 mNetworkThread
= new Handler(thread
.getLooper());
68 * Causes the host list to be fetched on a background thread. This should be called on the
69 * main thread, and callbacks will also be invoked on the main thread. On success,
70 * callback.onHostListReceived() will be called, otherwise callback.onError() will be called
71 * with an error-code describing the failure.
73 public void retrieveHostList(String authToken
, Callback callback
) {
75 final String authTokenFinal
= authToken
;
76 final Callback callbackFinal
= callback
;
77 mNetworkThread
.post(new Runnable() {
80 doRetrieveHostList(authTokenFinal
, callbackFinal
);
85 private void doRetrieveHostList(String authToken
, Callback callback
) {
86 HttpURLConnection link
= null;
87 String response
= null;
89 link
= (HttpURLConnection
)
90 new URL(HOST_LIST_PATH
+ JniInterface
.nativeGetApiKey()).openConnection();
91 link
.addRequestProperty("client_id", JniInterface
.nativeGetClientId());
92 link
.addRequestProperty("client_secret", JniInterface
.nativeGetClientSecret());
93 link
.setRequestProperty("Authorization", "OAuth " + authToken
);
95 // Listen for the server to respond.
96 int status
= link
.getResponseCode();
98 case HttpURLConnection
.HTTP_OK
: // 200
100 case HttpURLConnection
.HTTP_UNAUTHORIZED
: // 401
101 postError(callback
, Error
.AUTH_FAILED
);
103 case HttpURLConnection
.HTTP_BAD_GATEWAY
: // 502
104 case HttpURLConnection
.HTTP_UNAVAILABLE
: // 503
105 postError(callback
, Error
.SERVICE_UNAVAILABLE
);
108 postError(callback
, Error
.UNKNOWN
);
112 StringBuilder responseBuilder
= new StringBuilder();
113 Scanner incoming
= new Scanner(link
.getInputStream());
114 Log
.i("auth", "Successfully authenticated to directory server");
115 while (incoming
.hasNext()) {
116 responseBuilder
.append(incoming
.nextLine());
118 response
= String
.valueOf(responseBuilder
);
120 } catch (MalformedURLException ex
) {
121 // This should never happen.
122 throw new RuntimeException("Unexpected error while fetching host list: " + ex
);
123 } catch (IOException ex
) {
124 postError(callback
, Error
.NETWORK_ERROR
);
132 // Parse directory response.
133 ArrayList
<HostInfo
> hostList
= new ArrayList
<HostInfo
>();
135 JSONObject data
= new JSONObject(response
).getJSONObject("data");
136 if (data
.has("items")) {
137 JSONArray hostsJson
= data
.getJSONArray("items");
138 Log
.i("hostlist", "Received host listing from directory server");
141 while (!hostsJson
.isNull(index
)) {
142 JSONObject hostJson
= hostsJson
.getJSONObject(index
);
143 // If a host is only recently registered, it may be missing some of the keys
144 // below. It should still be visible in the list, even though a connection
145 // attempt will fail because of the missing keys. The failed attempt will
146 // trigger reloading of the host-list, by which time the keys will hopefully be
147 // present, and the retried connection can succeed.
148 HostInfo host
= HostInfo
.create(hostJson
);
153 } catch (JSONException ex
) {
154 Log
.e("hostlist", "Error parsing host list response: ", ex
);
155 postError(callback
, Error
.UNEXPECTED_RESPONSE
);
161 final Callback callbackFinal
= callback
;
162 final HostInfo
[] hosts
= hostList
.toArray(new HostInfo
[hostList
.size()]);
163 mMainThread
.post(new Runnable() {
166 callbackFinal
.onHostListReceived(hosts
);
171 /** Posts error to callback on main thread. */
172 private void postError(Callback callback
, Error error
) {
173 final Callback callbackFinal
= callback
;
174 final Error errorFinal
= error
;
175 mMainThread
.post(new Runnable() {
178 callbackFinal
.onError(errorFinal
);
183 private static void sortHosts(ArrayList
<HostInfo
> hosts
) {
184 Comparator
<HostInfo
> hostComparator
= new Comparator
<HostInfo
>() {
186 public int compare(HostInfo a
, HostInfo b
) {
187 if (a
.isOnline
!= b
.isOnline
) {
188 return a
.isOnline ?
-1 : 1;
190 String aName
= a
.name
.toUpperCase(Locale
.getDefault());
191 String bName
= b
.name
.toUpperCase(Locale
.getDefault());
192 return aName
.compareTo(bName
);
195 Collections
.sort(hosts
, hostComparator
);