| OLD | NEW |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chromoting; | 5 package org.chromium.chromoting; |
| 6 | 6 |
| 7 import android.accounts.Account; | 7 import android.accounts.Account; |
| 8 import android.accounts.AccountManager; | 8 import android.accounts.AccountManager; |
| 9 import android.accounts.AccountManagerCallback; | 9 import android.accounts.AccountManagerCallback; |
| 10 import android.accounts.AccountManagerFuture; | 10 import android.accounts.AccountManagerFuture; |
| 11 import android.accounts.AuthenticatorException; | 11 import android.accounts.AuthenticatorException; |
| 12 import android.accounts.OperationCanceledException; | 12 import android.accounts.OperationCanceledException; |
| 13 import android.app.Activity; | 13 import android.app.Activity; |
| 14 import android.app.ProgressDialog; | 14 import android.app.ProgressDialog; |
| 15 import android.content.DialogInterface; | 15 import android.content.DialogInterface; |
| 16 import android.content.Intent; | 16 import android.content.Intent; |
| 17 import android.content.SharedPreferences; | 17 import android.content.SharedPreferences; |
| 18 import android.os.Bundle; | 18 import android.os.Bundle; |
| 19 import android.os.Handler; | |
| 20 import android.os.HandlerThread; | |
| 21 import android.util.Log; | 19 import android.util.Log; |
| 22 import android.view.Menu; | 20 import android.view.Menu; |
| 23 import android.view.MenuItem; | 21 import android.view.MenuItem; |
| 24 import android.widget.ArrayAdapter; | 22 import android.widget.ArrayAdapter; |
| 25 import android.widget.ListView; | 23 import android.widget.ListView; |
| 26 import android.widget.TextView; | 24 import android.widget.TextView; |
| 27 import android.widget.Toast; | 25 import android.widget.Toast; |
| 28 | 26 |
| 29 import org.chromium.chromoting.jni.JniInterface; | 27 import org.chromium.chromoting.jni.JniInterface; |
| 30 import org.json.JSONArray; | |
| 31 import org.json.JSONException; | |
| 32 import org.json.JSONObject; | |
| 33 | 28 |
| 34 import java.io.IOException; | 29 import java.io.IOException; |
| 35 import java.net.URL; | 30 import java.util.Arrays; |
| 36 import java.net.URLConnection; | |
| 37 import java.util.ArrayList; | |
| 38 import java.util.Collections; | |
| 39 import java.util.Comparator; | |
| 40 import java.util.List; | |
| 41 import java.util.Scanner; | |
| 42 | 31 |
| 43 /** | 32 /** |
| 44 * The user interface for querying and displaying a user's host list from the di
rectory server. It | 33 * The user interface for querying and displaying a user's host list from the di
rectory server. It |
| 45 * also requests and renews authentication tokens using the system account manag
er. | 34 * also requests and renews authentication tokens using the system account manag
er. |
| 46 */ | 35 */ |
| 47 public class Chromoting extends Activity implements JniInterface.ConnectionListe
ner { | 36 public class Chromoting extends Activity implements JniInterface.ConnectionListe
ner, |
| 37 AccountManagerCallback<Bundle>, HostListLoader.Callback { |
| 48 /** Only accounts of this type will be selectable for authentication. */ | 38 /** Only accounts of this type will be selectable for authentication. */ |
| 49 private static final String ACCOUNT_TYPE = "com.google"; | 39 private static final String ACCOUNT_TYPE = "com.google"; |
| 50 | 40 |
| 51 /** Scopes at which the authentication token we request will be valid. */ | 41 /** Scopes at which the authentication token we request will be valid. */ |
| 52 private static final String TOKEN_SCOPE = "oauth2:https://www.googleapis.com
/auth/chromoting " + | 42 private static final String TOKEN_SCOPE = "oauth2:https://www.googleapis.com
/auth/chromoting " + |
| 53 "https://www.googleapis.com/auth/googletalk"; | 43 "https://www.googleapis.com/auth/googletalk"; |
| 54 | 44 |
| 55 /** Path from which to download a user's host list JSON object. */ | |
| 56 private static final String HOST_LIST_PATH = | |
| 57 "https://www.googleapis.com/chromoting/v1/@me/hosts?key="; | |
| 58 | |
| 59 /** Lock to protect |mAccount| and |mToken|. */ | |
| 60 // TODO(lambroslambrou): |mHosts| needs to be protected as well. | |
| 61 private Object mLock = new Object(); | |
| 62 | |
| 63 /** User's account details. */ | 45 /** User's account details. */ |
| 64 private Account mAccount; | 46 private Account mAccount; |
| 65 | 47 |
| 66 /** Account auth token. */ | 48 /** Account auth token. */ |
| 67 private String mToken; | 49 private String mToken; |
| 68 | 50 |
| 51 /** Helper for fetching the host list. */ |
| 52 private HostListLoader mHostListLoader; |
| 53 |
| 69 /** List of hosts. */ | 54 /** List of hosts. */ |
| 70 private JSONArray mHosts; | 55 private HostInfo[] mHosts; |
| 71 | 56 |
| 72 /** Refresh button. */ | 57 /** Refresh button. */ |
| 73 private MenuItem mRefreshButton; | 58 private MenuItem mRefreshButton; |
| 74 | 59 |
| 75 /** Account switcher. */ | 60 /** Account switcher. */ |
| 76 private MenuItem mAccountSwitcher; | 61 private MenuItem mAccountSwitcher; |
| 77 | 62 |
| 78 /** Greeting at the top of the displayed list. */ | 63 /** Greeting at the top of the displayed list. */ |
| 79 private TextView mGreeting; | 64 private TextView mGreeting; |
| 80 | 65 |
| 81 /** Host list as it appears to the user. */ | 66 /** Host list as it appears to the user. */ |
| 82 private ListView mList; | 67 private ListView mList; |
| 83 | 68 |
| 84 /** Callback handler to be used for network operations. */ | |
| 85 private Handler mNetwork; | |
| 86 | |
| 87 /** Dialog for reporting connection progress. */ | 69 /** Dialog for reporting connection progress. */ |
| 88 private ProgressDialog mProgressIndicator; | 70 private ProgressDialog mProgressIndicator; |
| 89 | 71 |
| 90 /** | 72 /** |
| 73 * This is set when receiving an authentication error from the HostListLoade
r. If that occurs, |
| 74 * this flag is set and a fresh authentication token is fetched from the Acc
ountsService, and |
| 75 * used to request the host list a second time. |
| 76 */ |
| 77 boolean mAlreadyTried; |
| 78 |
| 79 /** |
| 91 * Called when the activity is first created. Loads the native library and r
equests an | 80 * Called when the activity is first created. Loads the native library and r
equests an |
| 92 * authentication token from the system. | 81 * authentication token from the system. |
| 93 */ | 82 */ |
| 94 @Override | 83 @Override |
| 95 public void onCreate(Bundle savedInstanceState) { | 84 public void onCreate(Bundle savedInstanceState) { |
| 96 super.onCreate(savedInstanceState); | 85 super.onCreate(savedInstanceState); |
| 97 setContentView(R.layout.main); | 86 setContentView(R.layout.main); |
| 98 | 87 |
| 88 mAlreadyTried = false; |
| 89 mHostListLoader = new HostListLoader(); |
| 90 |
| 99 // Get ahold of our view widgets. | 91 // Get ahold of our view widgets. |
| 100 mGreeting = (TextView)findViewById(R.id.hostList_greeting); | 92 mGreeting = (TextView)findViewById(R.id.hostList_greeting); |
| 101 mList = (ListView)findViewById(R.id.hostList_chooser); | 93 mList = (ListView)findViewById(R.id.hostList_chooser); |
| 102 | 94 |
| 103 // Bring native components online. | 95 // Bring native components online. |
| 104 JniInterface.loadLibrary(this); | 96 JniInterface.loadLibrary(this); |
| 105 | 97 |
| 106 // Thread responsible for downloading/displaying host list. | |
| 107 HandlerThread thread = new HandlerThread("auth_callback"); | |
| 108 thread.start(); | |
| 109 mNetwork = new Handler(thread.getLooper()); | |
| 110 | |
| 111 SharedPreferences prefs = getPreferences(MODE_PRIVATE); | 98 SharedPreferences prefs = getPreferences(MODE_PRIVATE); |
| 112 if (prefs.contains("account_name") && prefs.contains("account_type")) { | 99 if (prefs.contains("account_name") && prefs.contains("account_type")) { |
| 113 // Perform authentication using saved account selection. | 100 // Perform authentication using saved account selection. |
| 114 mAccount = new Account(prefs.getString("account_name", null), | 101 mAccount = new Account(prefs.getString("account_name", null), |
| 115 prefs.getString("account_type", null)); | 102 prefs.getString("account_type", null)); |
| 116 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t
his, | 103 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t
his, this, null); |
| 117 new HostListDirectoryGrabber(this), mNetwork); | |
| 118 if (mAccountSwitcher != null) { | 104 if (mAccountSwitcher != null) { |
| 119 mAccountSwitcher.setTitle(mAccount.name); | 105 mAccountSwitcher.setTitle(mAccount.name); |
| 120 } | 106 } |
| 121 } else { | 107 } else { |
| 122 // Request auth callback once user has chosen an account. | 108 // Request auth callback once user has chosen an account. |
| 123 Log.i("auth", "Requesting auth token from system"); | 109 Log.i("auth", "Requesting auth token from system"); |
| 124 AccountManager.get(this).getAuthTokenByFeatures( | 110 AccountManager.get(this).getAuthTokenByFeatures(ACCOUNT_TYPE, TOKEN_
SCOPE, null, this, |
| 125 ACCOUNT_TYPE, | 111 null, null, this, null); |
| 126 TOKEN_SCOPE, | |
| 127 null, | |
| 128 this, | |
| 129 null, | |
| 130 null, | |
| 131 new HostListDirectoryGrabber(this), | |
| 132 mNetwork | |
| 133 ); | |
| 134 } | 112 } |
| 135 } | 113 } |
| 136 | 114 |
| 137 /** Called when the activity is finally finished. */ | 115 /** Called when the activity is finally finished. */ |
| 138 @Override | 116 @Override |
| 139 public void onDestroy() { | 117 public void onDestroy() { |
| 140 super.onDestroy(); | 118 super.onDestroy(); |
| 141 JniInterface.disconnectFromHost(); | 119 JniInterface.disconnectFromHost(); |
| 142 } | 120 } |
| 143 | 121 |
| (...skipping 18 matching lines...) Expand all Loading... |
| 162 // If the user has picked an account, show its name directly on the
account switcher. | 140 // If the user has picked an account, show its name directly on the
account switcher. |
| 163 mAccountSwitcher.setTitle(mAccount.name); | 141 mAccountSwitcher.setTitle(mAccount.name); |
| 164 } | 142 } |
| 165 | 143 |
| 166 return super.onCreateOptionsMenu(menu); | 144 return super.onCreateOptionsMenu(menu); |
| 167 } | 145 } |
| 168 | 146 |
| 169 /** Called whenever an action bar button is pressed. */ | 147 /** Called whenever an action bar button is pressed. */ |
| 170 @Override | 148 @Override |
| 171 public boolean onOptionsItemSelected(MenuItem item) { | 149 public boolean onOptionsItemSelected(MenuItem item) { |
| 150 mAlreadyTried = false; |
| 172 if (item == mAccountSwitcher) { | 151 if (item == mAccountSwitcher) { |
| 173 // The account switcher triggers a listing of all available accounts
. | 152 // The account switcher triggers a listing of all available accounts
. |
| 174 AccountManager.get(this).getAuthTokenByFeatures( | 153 AccountManager.get(this).getAuthTokenByFeatures(ACCOUNT_TYPE, TOKEN_
SCOPE, null, this, |
| 175 ACCOUNT_TYPE, | 154 null, null, this, null); |
| 176 TOKEN_SCOPE, | 155 } else { |
| 177 null, | |
| 178 this, | |
| 179 null, | |
| 180 null, | |
| 181 new HostListDirectoryGrabber(this), | |
| 182 mNetwork | |
| 183 ); | |
| 184 } | |
| 185 else { | |
| 186 // The refresh button simply makes use of the currently-chosen accou
nt. | 156 // The refresh button simply makes use of the currently-chosen accou
nt. |
| 187 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t
his, | 157 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t
his, this, null); |
| 188 new HostListDirectoryGrabber(this), mNetwork); | |
| 189 } | 158 } |
| 190 | 159 |
| 191 return true; | 160 return true; |
| 192 } | 161 } |
| 193 | 162 |
| 194 /** Called when the user taps on a host entry. */ | 163 /** Called when the user taps on a host entry. */ |
| 195 public void connectToHost(JSONObject host) { | 164 public void connectToHost(HostInfo host) { |
| 196 try { | 165 if (host.jabberId.isEmpty() || host.publicKey.isEmpty()) { |
| 197 synchronized (mLock) { | 166 // TODO(lambroslambrou): If these keys are not present, treat this a
s a connection |
| 198 JniInterface.connectToHost(mAccount.name, mToken, host.getString
("jabberId"), | 167 // failure and reload the host list (see crbug.com/304719). |
| 199 host.getString("hostId"), host.getString("publicKey"), t
his); | |
| 200 } | |
| 201 } catch (JSONException ex) { | |
| 202 Log.w("host", ex); | |
| 203 Toast.makeText(this, getString(R.string.error_reading_host), | 168 Toast.makeText(this, getString(R.string.error_reading_host), |
| 204 Toast.LENGTH_LONG).show(); | 169 Toast.LENGTH_LONG).show(); |
| 205 // Close the application. | 170 return; |
| 206 finish(); | |
| 207 } | 171 } |
| 172 |
| 173 JniInterface.connectToHost(mAccount.name, mToken, host.jabberId, host.id
, host.publicKey, |
| 174 this); |
| 208 } | 175 } |
| 209 | 176 |
| 210 /** | 177 @Override |
| 211 * Processes the authentication token once the system provides it. Once in p
ossession of such a | 178 public void run(AccountManagerFuture<Bundle> future) { |
| 212 * token, attempts to request a host list from the directory server. In case
of a bad response, | 179 Log.i("auth", "User finished with auth dialogs"); |
| 213 * this is retried once in case the system's cached auth token had expired. | 180 Bundle result = null; |
| 214 */ | 181 String explanation = null; |
| 215 private class HostListDirectoryGrabber implements AccountManagerCallback<Bun
dle> { | 182 try { |
| 216 // TODO(lambroslambrou): Refactor this class to provide async interface
usable on the UI | 183 // Here comes our auth token from the Android system. |
| 217 // thread. | 184 result = future.getResult(); |
| 218 | 185 } catch (OperationCanceledException ex) { |
| 219 /** Whether authentication has already been attempted. */ | 186 explanation = getString(R.string.error_auth_canceled); |
| 220 private boolean mAlreadyTried; | 187 } catch (AuthenticatorException ex) { |
| 221 | 188 explanation = getString(R.string.error_no_accounts); |
| 222 /** Communication with the screen. */ | 189 } catch (IOException ex) { |
| 223 private Activity mUi; | 190 explanation = getString(R.string.error_bad_connection); |
| 224 | |
| 225 /** Constructor. */ | |
| 226 public HostListDirectoryGrabber(Activity ui) { | |
| 227 mAlreadyTried = false; | |
| 228 mUi = ui; | |
| 229 } | 191 } |
| 230 | 192 |
| 231 /** | 193 if (result == null) { |
| 232 * Retrieves the host list from the directory server. This method perfor
ms | 194 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); |
| 233 * network operations and must be run an a non-UI thread. | 195 return; |
| 234 */ | |
| 235 @Override | |
| 236 public void run(AccountManagerFuture<Bundle> future) { | |
| 237 Log.i("auth", "User finished with auth dialogs"); | |
| 238 try { | |
| 239 // Here comes our auth token from the Android system. | |
| 240 Bundle result = future.getResult(); | |
| 241 String accountName = result.getString(AccountManager.KEY_ACCOUNT
_NAME); | |
| 242 String accountType = result.getString(AccountManager.KEY_ACCOUNT
_TYPE); | |
| 243 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN
); | |
| 244 Log.i("auth", "Received an auth token from system"); | |
| 245 | |
| 246 synchronized (mLock) { | |
| 247 mAccount = new Account(accountName, accountType); | |
| 248 mToken = authToken; | |
| 249 getPreferences(MODE_PRIVATE).edit().putString("account_name"
, accountName). | |
| 250 putString("account_type", accountType).apply(); | |
| 251 } | |
| 252 | |
| 253 // Send our HTTP request to the directory server. | |
| 254 URLConnection link = | |
| 255 new URL(HOST_LIST_PATH + JniInterface.nativeGetApiKey())
.openConnection(); | |
| 256 link.addRequestProperty("client_id", JniInterface.nativeGetClien
tId()); | |
| 257 link.addRequestProperty("client_secret", JniInterface.nativeGetC
lientSecret()); | |
| 258 link.setRequestProperty("Authorization", "OAuth " + authToken); | |
| 259 | |
| 260 // Listen for the server to respond. | |
| 261 StringBuilder response = new StringBuilder(); | |
| 262 Scanner incoming = new Scanner(link.getInputStream()); | |
| 263 Log.i("auth", "Successfully authenticated to directory server"); | |
| 264 while (incoming.hasNext()) { | |
| 265 response.append(incoming.nextLine()); | |
| 266 } | |
| 267 incoming.close(); | |
| 268 | |
| 269 // Interpret what the directory server told us. | |
| 270 JSONObject data = new JSONObject(String.valueOf(response)).getJS
ONObject("data"); | |
| 271 mHosts = sortHosts(data.getJSONArray("items")); | |
| 272 Log.i("hostlist", "Received host listing from directory server")
; | |
| 273 } catch (RuntimeException ex) { | |
| 274 // Make sure any other failure is reported to the user (as an un
known error). | |
| 275 throw ex; | |
| 276 } catch (Exception ex) { | |
| 277 // Assemble error message to display to the user. | |
| 278 String explanation = getString(R.string.error_unknown); | |
| 279 if (ex instanceof OperationCanceledException) { | |
| 280 explanation = getString(R.string.error_auth_canceled); | |
| 281 } else if (ex instanceof AuthenticatorException) { | |
| 282 explanation = getString(R.string.error_no_accounts); | |
| 283 } else if (ex instanceof IOException) { | |
| 284 if (!mAlreadyTried) { | |
| 285 // This was our first connection attempt. | |
| 286 | |
| 287 synchronized (mLock) { | |
| 288 if (mAccount != null) { | |
| 289 // We got an account, but couldn't log into it.
We'll retry in case | |
| 290 // the system's cached authentication token had
already expired. | |
| 291 AccountManager authenticator = AccountManager.ge
t(mUi); | |
| 292 mAlreadyTried = true; | |
| 293 | |
| 294 Log.w("auth", "Requesting renewal of rejected au
th token"); | |
| 295 authenticator.invalidateAuthToken(mAccount.type,
mToken); | |
| 296 mToken = null; | |
| 297 authenticator.getAuthToken( | |
| 298 mAccount, TOKEN_SCOPE, null, mUi, this,
mNetwork); | |
| 299 | |
| 300 // We're not in an error state *yet*. | |
| 301 return; | |
| 302 } | |
| 303 } | |
| 304 | |
| 305 // We didn't even get an account, so the auth server is
likely unreachable. | |
| 306 explanation = getString(R.string.error_bad_connection); | |
| 307 } else { | |
| 308 // Authentication truly failed. | |
| 309 Log.e("auth", "Fresh auth token was also rejected"); | |
| 310 explanation = getString(R.string.error_auth_failed); | |
| 311 } | |
| 312 } else if (ex instanceof JSONException) { | |
| 313 explanation = getString(R.string.error_unexpected_response); | |
| 314 } | |
| 315 | |
| 316 mHosts = null; | |
| 317 Log.w("auth", ex); | |
| 318 Toast.makeText(mUi, explanation, Toast.LENGTH_LONG).show(); | |
| 319 } | |
| 320 | |
| 321 // Share our findings with the user. | |
| 322 runOnUiThread(new Runnable() { | |
| 323 @Override | |
| 324 public void run() { | |
| 325 updateUi(); | |
| 326 } | |
| 327 }); | |
| 328 } | 196 } |
| 329 | 197 |
| 330 private JSONArray sortHosts(JSONArray hosts) { | 198 String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME); |
| 331 List<JSONObject> hostList = new ArrayList<JSONObject>(); | 199 String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE); |
| 332 for (int i = 0; i < hosts.length(); i++) { | 200 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); |
| 333 try { | 201 Log.i("auth", "Received an auth token from system"); |
| 334 hostList.add(hosts.getJSONObject(i)); | |
| 335 } catch (JSONException ex) { | |
| 336 // Ignore non-object entries. | |
| 337 } | |
| 338 } | |
| 339 | 202 |
| 340 Comparator<JSONObject> compareHosts = new Comparator<JSONObject>() { | 203 mAccount = new Account(accountName, accountType); |
| 341 public int compare(JSONObject a, JSONObject b) { | 204 mToken = authToken; |
| 342 try { | 205 getPreferences(MODE_PRIVATE).edit().putString("account_name", accountNam
e). |
| 343 boolean aOnline = a.getString("status").equals("ONLINE")
; | 206 putString("account_type", accountType).apply(); |
| 344 boolean bOnline = b.getString("status").equals("ONLINE")
; | |
| 345 if (aOnline && !bOnline) { | |
| 346 return -1; | |
| 347 } | |
| 348 if (bOnline && !aOnline) { | |
| 349 return 1; | |
| 350 } | |
| 351 String aName = a.getString("hostName").toUpperCase(); | |
| 352 String bName = b.getString("hostName").toUpperCase(); | |
| 353 return aName.compareTo(bName); | |
| 354 } catch (JSONException ex) { | |
| 355 return 0; | |
| 356 } | |
| 357 } | |
| 358 }; | |
| 359 Collections.sort(hostList, compareHosts); | |
| 360 | 207 |
| 361 JSONArray result = new JSONArray(hostList); | 208 mHostListLoader.retrieveHostList(authToken, this); |
| 362 return result; | 209 } |
| 210 |
| 211 @Override |
| 212 public void onHostListReceived(HostInfo[] hosts) { |
| 213 // Store a copy of the array, so that it can't be mutated by the HostLis
tLoader. HostInfo |
| 214 // is an immutable type, so a shallow copy of the array is sufficient he
re. |
| 215 mHosts = Arrays.copyOf(hosts, hosts.length); |
| 216 updateUi(); |
| 217 } |
| 218 |
| 219 @Override |
| 220 public void onError(HostListLoader.Error error) { |
| 221 String explanation = null; |
| 222 switch (error) { |
| 223 case AUTH_FAILED: |
| 224 break; |
| 225 case NETWORK_ERROR: |
| 226 explanation = getString(R.string.error_bad_connection); |
| 227 break; |
| 228 case SERVICE_UNAVAILABLE: |
| 229 case UNEXPECTED_RESPONSE: |
| 230 explanation = getString(R.string.error_unexpected_response); |
| 231 break; |
| 232 case UNKNOWN: |
| 233 explanation = getString(R.string.error_unknown); |
| 234 break; |
| 235 default: |
| 236 // Unreachable. |
| 237 return; |
| 238 } |
| 239 |
| 240 if (explanation != null) { |
| 241 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); |
| 242 return; |
| 243 } |
| 244 |
| 245 // This is the AUTH_FAILED case. |
| 246 |
| 247 if (!mAlreadyTried) { |
| 248 // This was our first connection attempt. |
| 249 |
| 250 AccountManager authenticator = AccountManager.get(this); |
| 251 mAlreadyTried = true; |
| 252 |
| 253 Log.w("auth", "Requesting renewal of rejected auth token"); |
| 254 authenticator.invalidateAuthToken(mAccount.type, mToken); |
| 255 mToken = null; |
| 256 authenticator.getAuthToken(mAccount, TOKEN_SCOPE, null, this, this,
null); |
| 257 |
| 258 // We're not in an error state *yet*. |
| 259 return; |
| 260 } else { |
| 261 // Authentication truly failed. |
| 262 Log.e("auth", "Fresh auth token was also rejected"); |
| 263 explanation = getString(R.string.error_auth_failed); |
| 264 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); |
| 363 } | 265 } |
| 364 } | 266 } |
| 365 | 267 |
| 366 /** | 268 /** |
| 367 * Updates the infotext and host list display. | 269 * Updates the infotext and host list display. |
| 368 * This method affects the UI and must be run on the main thread. | |
| 369 */ | 270 */ |
| 370 private void updateUi() { | 271 private void updateUi() { |
| 371 synchronized (mLock) { | 272 mRefreshButton.setEnabled(mAccount != null); |
| 372 mRefreshButton.setEnabled(mAccount != null); | 273 if (mAccount != null) { |
| 373 if (mAccount != null) { | 274 mAccountSwitcher.setTitle(mAccount.name); |
| 374 mAccountSwitcher.setTitle(mAccount.name); | |
| 375 } | |
| 376 } | 275 } |
| 377 | 276 |
| 378 if (mHosts == null) { | 277 if (mHosts == null) { |
| 379 mGreeting.setText(getString(R.string.inst_empty_list)); | 278 mGreeting.setText(getString(R.string.inst_empty_list)); |
| 380 mList.setAdapter(null); | 279 mList.setAdapter(null); |
| 381 return; | 280 return; |
| 382 } | 281 } |
| 383 | 282 |
| 384 mGreeting.setText(getString(R.string.inst_host_list)); | 283 mGreeting.setText(getString(R.string.inst_host_list)); |
| 385 | 284 |
| 386 ArrayAdapter<JSONObject> displayer = new HostListAdapter(this, R.layout.
host); | 285 ArrayAdapter<HostInfo> displayer = new HostListAdapter(this, R.layout.ho
st, mHosts); |
| 387 Log.i("hostlist", "About to populate host list display"); | 286 Log.i("hostlist", "About to populate host list display"); |
| 388 try { | 287 mList.setAdapter(displayer); |
| 389 int index = 0; | |
| 390 while (!mHosts.isNull(index)) { | |
| 391 displayer.add(mHosts.getJSONObject(index)); | |
| 392 ++index; | |
| 393 } | |
| 394 mList.setAdapter(displayer); | |
| 395 } catch (JSONException ex) { | |
| 396 Log.w("hostlist", ex); | |
| 397 Toast.makeText(this, getString(R.string.error_cataloging_hosts), | |
| 398 Toast.LENGTH_LONG).show(); | |
| 399 | |
| 400 // Close the application. | |
| 401 finish(); | |
| 402 } | |
| 403 } | 288 } |
| 404 | 289 |
| 405 @Override | 290 @Override |
| 406 public void onConnectionState(JniInterface.ConnectionListener.State state, | 291 public void onConnectionState(JniInterface.ConnectionListener.State state, |
| 407 JniInterface.ConnectionListener.Error error) { | 292 JniInterface.ConnectionListener.Error error) { |
| 408 String stateText = getResources().getStringArray(R.array.protoc_states)[
state.value()]; | 293 String stateText = getResources().getStringArray(R.array.protoc_states)[
state.value()]; |
| 409 boolean dismissProgress = false; | 294 boolean dismissProgress = false; |
| 410 switch (state) { | 295 switch (state) { |
| 411 case INITIALIZING: | 296 case INITIALIZING: |
| 412 case CONNECTING: | 297 case CONNECTING: |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 456 // Unreachable, but required by Google Java style and findbugs. | 341 // Unreachable, but required by Google Java style and findbugs. |
| 457 assert false : "Unreached"; | 342 assert false : "Unreached"; |
| 458 } | 343 } |
| 459 | 344 |
| 460 if (dismissProgress && mProgressIndicator != null) { | 345 if (dismissProgress && mProgressIndicator != null) { |
| 461 mProgressIndicator.dismiss(); | 346 mProgressIndicator.dismiss(); |
| 462 mProgressIndicator = null; | 347 mProgressIndicator = null; |
| 463 } | 348 } |
| 464 } | 349 } |
| 465 } | 350 } |
| OLD | NEW |