1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
|
// 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 org.chromium.base.Log;
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.Locale;
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);
}
private static final String TAG = "Chromoting";
/** 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";
/** 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).openConnection();
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());
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");
if (data.has("items")) {
JSONArray hostsJson = data.getJSONArray("items");
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, by which time the keys will hopefully be
// present, and the retried connection can succeed.
HostInfo host = HostInfo.create(hostJson);
hostList.add(host);
++index;
}
}
} catch (JSONException ex) {
// Logging the exception stack trace may be too spammy.
Log.e(TAG, "Error parsing host list response: %s", ex.getMessage());
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>() {
@Override
public int compare(HostInfo a, HostInfo b) {
if (a.isOnline != b.isOnline) {
return a.isOnline ? -1 : 1;
}
String aName = a.name.toUpperCase(Locale.getDefault());
String bName = b.name.toUpperCase(Locale.getDefault());
return aName.compareTo(bName);
}
};
Collections.sort(hosts, hostComparator);
}
}
|