Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / remoting / android / java / src / org / chromium / chromoting / HostListLoader.java
blob05a631e0c019311f19f00451ee711efe419d18b8
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;
19 import java.net.URL;
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 {
28 public enum Error {
29 AUTH_FAILED,
30 NETWORK_ERROR,
31 SERVICE_UNAVAILABLE,
32 UNEXPECTED_RESPONSE,
33 UNKNOWN,
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");
61 thread.start();
62 mNetworkThread = new Handler(thread.getLooper());
66 /**
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) {
73 initNetworkThread();
74 final String authTokenFinal = authToken;
75 final Callback callbackFinal = callback;
76 mNetworkThread.post(new Runnable() {
77 @Override
78 public void run() {
79 doRetrieveHostList(authTokenFinal, callbackFinal);
81 });
84 private void doRetrieveHostList(String authToken, Callback callback) {
85 HttpURLConnection link = null;
86 String response = null;
87 try {
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();
93 switch (status) {
94 case HttpURLConnection.HTTP_OK: // 200
95 break;
96 case HttpURLConnection.HTTP_UNAUTHORIZED: // 401
97 postError(callback, Error.AUTH_FAILED);
98 return;
99 case HttpURLConnection.HTTP_BAD_GATEWAY: // 502
100 case HttpURLConnection.HTTP_UNAVAILABLE: // 503
101 postError(callback, Error.SERVICE_UNAVAILABLE);
102 return;
103 default:
104 postError(callback, Error.UNKNOWN);
105 return;
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);
115 incoming.close();
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);
121 return;
122 } finally {
123 if (link != null) {
124 link.disconnect();
128 // Parse directory response.
129 ArrayList<HostInfo> hostList = new ArrayList<HostInfo>();
130 try {
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");
136 int index = 0;
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);
145 hostList.add(host);
146 ++index;
149 } catch (JSONException ex) {
150 Log.e("hostlist", "Error parsing host list response: ", ex);
151 postError(callback, Error.UNEXPECTED_RESPONSE);
152 return;
155 sortHosts(hostList);
157 final Callback callbackFinal = callback;
158 final HostInfo[] hosts = hostList.toArray(new HostInfo[hostList.size()]);
159 mMainThread.post(new Runnable() {
160 @Override
161 public void run() {
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() {
172 @Override
173 public void run() {
174 callbackFinal.onError(errorFinal);
179 private static void sortHosts(ArrayList<HostInfo> hosts) {
180 Comparator<HostInfo> hostComparator = new Comparator<HostInfo>() {
181 @Override
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);