Home | History | Annotate | Download | only in chromoting
      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 {
     38         void onHostListReceived(HostInfo[] hosts);
     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 
     52     public HostListLoader() {
     53         // Thread responsible for downloading the host list.
     54 
     55         mMainThread = new Handler(Looper.getMainLooper());
     56     }
     57 
     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       */
     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 
     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. */
    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 
    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