• 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.chromium.chromoting.jni.JniInterface;
13 import org.json.JSONArray;
14 import org.json.JSONException;
15 import org.json.JSONObject;
16 
17 import java.io.IOException;
18 import java.net.HttpURLConnection;
19 import java.net.MalformedURLException;
20 import java.net.URL;
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;
26 
27 /** Helper for fetching the host list. */
28 public class HostListLoader {
29     public enum Error {
30         AUTH_FAILED,
31         NETWORK_ERROR,
32         SERVICE_UNAVAILABLE,
33         UNEXPECTED_RESPONSE,
34         UNKNOWN,
35     }
36 
37     /** Callback for receiving the host list, or getting notified of an error. */
38     public interface Callback {
onHostListReceived(HostInfo[] hosts)39         void onHostListReceived(HostInfo[] hosts);
onError(Error error)40         void onError(Error error);
41     }
42 
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=";
46 
47     /** Callback handler to be used for network operations. */
48     private Handler mNetworkThread;
49 
50     /** Handler for main thread. */
51     private Handler mMainThread;
52 
HostListLoader()53     public HostListLoader() {
54         // Thread responsible for downloading the host list.
55 
56         mMainThread = new Handler(Looper.getMainLooper());
57     }
58 
initNetworkThread()59     private void initNetworkThread() {
60         if (mNetworkThread == null) {
61             HandlerThread thread = new HandlerThread("network");
62             thread.start();
63             mNetworkThread = new Handler(thread.getLooper());
64         }
65     }
66 
67     /**
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.
72       */
retrieveHostList(String authToken, Callback callback)73     public void retrieveHostList(String authToken, Callback callback) {
74         initNetworkThread();
75         final String authTokenFinal = authToken;
76         final Callback callbackFinal = callback;
77         mNetworkThread.post(new Runnable() {
78             @Override
79             public void run() {
80                 doRetrieveHostList(authTokenFinal, callbackFinal);
81             }
82         });
83     }
84 
doRetrieveHostList(String authToken, Callback callback)85     private void doRetrieveHostList(String authToken, Callback callback) {
86         HttpURLConnection link = null;
87         String response = null;
88         try {
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);
94 
95             // Listen for the server to respond.
96             int status = link.getResponseCode();
97             switch (status) {
98                 case HttpURLConnection.HTTP_OK:  // 200
99                     break;
100                 case HttpURLConnection.HTTP_UNAUTHORIZED:  // 401
101                     postError(callback, Error.AUTH_FAILED);
102                     return;
103                 case HttpURLConnection.HTTP_BAD_GATEWAY:  // 502
104                 case HttpURLConnection.HTTP_UNAVAILABLE:  // 503
105                     postError(callback, Error.SERVICE_UNAVAILABLE);
106                     return;
107                 default:
108                     postError(callback, Error.UNKNOWN);
109                     return;
110             }
111 
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());
117             }
118             response = String.valueOf(responseBuilder);
119             incoming.close();
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);
125             return;
126         } finally {
127             if (link != null) {
128                 link.disconnect();
129             }
130         }
131 
132         // Parse directory response.
133         ArrayList<HostInfo> hostList = new ArrayList<HostInfo>();
134         try {
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");
139 
140                 int index = 0;
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);
149                     hostList.add(host);
150                     ++index;
151                 }
152             }
153         } catch (JSONException ex) {
154             Log.e("hostlist", "Error parsing host list response: ", ex);
155             postError(callback, Error.UNEXPECTED_RESPONSE);
156             return;
157         }
158 
159         sortHosts(hostList);
160 
161         final Callback callbackFinal = callback;
162         final HostInfo[] hosts = hostList.toArray(new HostInfo[hostList.size()]);
163         mMainThread.post(new Runnable() {
164             @Override
165             public void run() {
166                 callbackFinal.onHostListReceived(hosts);
167             }
168         });
169     }
170 
171     /** Posts error to callback on main thread. */
postError(Callback callback, Error error)172     private void postError(Callback callback, Error error) {
173         final Callback callbackFinal = callback;
174         final Error errorFinal = error;
175         mMainThread.post(new Runnable() {
176             @Override
177             public void run() {
178                 callbackFinal.onError(errorFinal);
179             }
180         });
181     }
182 
sortHosts(ArrayList<HostInfo> hosts)183     private static void sortHosts(ArrayList<HostInfo> hosts) {
184         Comparator<HostInfo> hostComparator = new Comparator<HostInfo>() {
185             @Override
186             public int compare(HostInfo a, HostInfo b) {
187                 if (a.isOnline != b.isOnline) {
188                     return a.isOnline ? -1 : 1;
189                 }
190                 String aName = a.name.toUpperCase(Locale.getDefault());
191                 String bName = b.name.toUpperCase(Locale.getDefault());
192                 return aName.compareTo(bName);
193             }
194         };
195         Collections.sort(hosts, hostComparator);
196     }
197 }
198