• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 package org.chromium.chromoting;
6 
7 import android.os.Handler;
8 import android.os.HandlerThread;
9 import android.os.Looper;
10 import android.util.Log;
11 
12 import org.json.JSONArray;
13 import org.json.JSONException;
14 import org.json.JSONObject;
15 
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;
25 
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,
34     }
35 
36     /** Callback for receiving the host list, or getting notified of an error. */
37     public interface Callback {
onHostListReceived(HostInfo[] hosts)38         void onHostListReceived(HostInfo[] hosts);
onError(Error error)39         void onError(Error error);
40     }
41 
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";
45 
46     /** Callback handler to be used for network operations. */
47     private Handler mNetworkThread;
48 
49     /** Handler for main thread. */
50     private Handler mMainThread;
51 
HostListLoader()52     public HostListLoader() {
53         // Thread responsible for downloading the host list.
54 
55         mMainThread = new Handler(Looper.getMainLooper());
56     }
57 
initNetworkThread()58     private void initNetworkThread() {
59         if (mNetworkThread == null) {
60             HandlerThread thread = new HandlerThread("network");
61             thread.start();
62             mNetworkThread = new Handler(thread.getLooper());
63         }
64     }
65 
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.
71       */
retrieveHostList(String authToken, Callback callback)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);
80             }
81         });
82     }
83 
doRetrieveHostList(String authToken, Callback callback)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);
90 
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;
106             }
107 
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());
113             }
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();
125             }
126         }
127 
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");
135 
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;
147                 }
148             }
149         } catch (JSONException ex) {
150             Log.e("hostlist", "Error parsing host list response: ", ex);
151             postError(callback, Error.UNEXPECTED_RESPONSE);
152             return;
153         }
154 
155         sortHosts(hostList);
156 
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);
163             }
164         });
165     }
166 
167     /** Posts error to callback on main thread. */
postError(Callback callback, Error error)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);
175             }
176         });
177     }
178 
sortHosts(ArrayList<HostInfo> hosts)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;
185                 }
186                 String aName = a.name.toUpperCase(Locale.getDefault());
187                 String bName = b.name.toUpperCase(Locale.getDefault());
188                 return aName.compareTo(bName);
189             }
190         };
191         Collections.sort(hosts, hostComparator);
192     }
193 }
194