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.content.Context; | |
15 import android.content.Intent; | 14 import android.content.Intent; |
16 import android.content.SharedPreferences; | 15 import android.content.SharedPreferences; |
17 import android.os.Bundle; | 16 import android.os.Bundle; |
18 import android.os.Handler; | 17 import android.os.Handler; |
19 import android.os.HandlerThread; | 18 import android.os.HandlerThread; |
20 import android.text.Html; | 19 import android.text.Html; |
21 import android.util.Log; | 20 import android.util.Log; |
22 import android.view.Menu; | 21 import android.view.Menu; |
23 import android.view.MenuItem; | 22 import android.view.MenuItem; |
24 import android.view.View; | 23 import android.view.View; |
(...skipping 22 matching lines...) Expand all Loading... | |
47 private static final String ACCOUNT_TYPE = "com.google"; | 46 private static final String ACCOUNT_TYPE = "com.google"; |
48 | 47 |
49 /** Scopes at which the authentication token we request will be valid. */ | 48 /** Scopes at which the authentication token we request will be valid. */ |
50 private static final String TOKEN_SCOPE = "oauth2:https://www.googleapis.com /auth/chromoting " + | 49 private static final String TOKEN_SCOPE = "oauth2:https://www.googleapis.com /auth/chromoting " + |
51 "https://www.googleapis.com/auth/googletalk"; | 50 "https://www.googleapis.com/auth/googletalk"; |
52 | 51 |
53 /** Path from which to download a user's host list JSON object. */ | 52 /** Path from which to download a user's host list JSON object. */ |
54 private static final String HOST_LIST_PATH = | 53 private static final String HOST_LIST_PATH = |
55 "https://www.googleapis.com/chromoting/v1/@me/hosts?key="; | 54 "https://www.googleapis.com/chromoting/v1/@me/hosts?key="; |
56 | 55 |
57 /** Color to use for hosts that are online. */ | 56 /** Lock to protect |mAccount| and |mToken|. */ |
58 private static final String HOST_COLOR_ONLINE = "green"; | 57 // TODO(lambroslambrou): |mHosts| needs to be protected as well. |
59 | 58 private Object mLock = new Object(); |
60 /** Color to use for hosts that are offline. */ | |
61 private static final String HOST_COLOR_OFFLINE = "red"; | |
62 | 59 |
63 /** User's account details. */ | 60 /** User's account details. */ |
64 private Account mAccount; | 61 private Account mAccount; |
65 | 62 |
66 /** Account auth token. */ | 63 /** Account auth token. */ |
67 private String mToken; | 64 private String mToken; |
68 | 65 |
69 /** List of hosts. */ | 66 /** List of hosts. */ |
70 private JSONArray mHosts; | 67 private JSONArray mHosts; |
71 | 68 |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
181 } | 178 } |
182 else { | 179 else { |
183 // The refresh button simply makes use of the currently-chosen accou nt. | 180 // The refresh button simply makes use of the currently-chosen accou nt. |
184 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t his, | 181 AccountManager.get(this).getAuthToken(mAccount, TOKEN_SCOPE, null, t his, |
185 new HostListDirectoryGrabber(this), mNetwork); | 182 new HostListDirectoryGrabber(this), mNetwork); |
186 } | 183 } |
187 | 184 |
188 return true; | 185 return true; |
189 } | 186 } |
190 | 187 |
188 /** Called when the user taps on a host entry. */ | |
189 public void connectToHost(JSONObject host) { | |
190 try { | |
191 synchronized (mLock) { | |
192 JniInterface.connectToHost(mAccount.name, mToken, host.getString ("jabberId"), | |
193 host.getString("hostId"), host.getString("publicKey"), | |
194 new Runnable() { | |
195 @Override | |
196 public void run() { | |
197 startActivity(new Intent(Chromoting.this, De sktop.class)); | |
198 } | |
199 }); | |
200 } | |
201 } catch (JSONException ex) { | |
202 Log.w("host", ex); | |
203 Toast.makeText(this, getString(R.string.error_reading_host), | |
204 Toast.LENGTH_LONG).show(); | |
205 // Close the application. | |
206 finish(); | |
207 } | |
208 } | |
209 | |
191 /** | 210 /** |
192 * Processes the authentication token once the system provides it. Once in p ossession of such a | 211 * Processes the authentication token once the system provides it. Once in p ossession of such a |
193 * token, attempts to request a host list from the directory server. In case of a bad response, | 212 * token, attempts to request a host list from the directory server. In case of a bad response, |
194 * this is retried once in case the system's cached auth token had expired. | 213 * this is retried once in case the system's cached auth token had expired. |
195 */ | 214 */ |
196 private class HostListDirectoryGrabber implements AccountManagerCallback<Bun dle> { | 215 private class HostListDirectoryGrabber implements AccountManagerCallback<Bun dle> { |
197 /** Whether authentication has already been attempted. */ | 216 /** Whether authentication has already been attempted. */ |
198 private boolean mAlreadyTried; | 217 private boolean mAlreadyTried; |
199 | 218 |
200 /** Communication with the screen. */ | 219 /** Communication with the screen. */ |
(...skipping 13 matching lines...) Expand all Loading... | |
214 public void run(AccountManagerFuture<Bundle> future) { | 233 public void run(AccountManagerFuture<Bundle> future) { |
215 Log.i("auth", "User finished with auth dialogs"); | 234 Log.i("auth", "User finished with auth dialogs"); |
216 try { | 235 try { |
217 // Here comes our auth token from the Android system. | 236 // Here comes our auth token from the Android system. |
218 Bundle result = future.getResult(); | 237 Bundle result = future.getResult(); |
219 String accountName = result.getString(AccountManager.KEY_ACCOUNT _NAME); | 238 String accountName = result.getString(AccountManager.KEY_ACCOUNT _NAME); |
220 String accountType = result.getString(AccountManager.KEY_ACCOUNT _TYPE); | 239 String accountType = result.getString(AccountManager.KEY_ACCOUNT _TYPE); |
221 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN ); | 240 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN ); |
222 Log.i("auth", "Received an auth token from system"); | 241 Log.i("auth", "Received an auth token from system"); |
223 | 242 |
224 synchronized (mUi) { | 243 synchronized (mLock) { |
225 mAccount = new Account(accountName, accountType); | 244 mAccount = new Account(accountName, accountType); |
226 mToken = authToken; | 245 mToken = authToken; |
227 getPreferences(MODE_PRIVATE).edit().putString("account_name" , accountName). | 246 getPreferences(MODE_PRIVATE).edit().putString("account_name" , accountName). |
228 putString("account_type", accountType).apply(); | 247 putString("account_type", accountType).apply(); |
229 } | 248 } |
230 | 249 |
231 // Send our HTTP request to the directory server. | 250 // Send our HTTP request to the directory server. |
232 URLConnection link = | 251 URLConnection link = |
233 new URL(HOST_LIST_PATH + JniInterface.nativeGetApiKey()) .openConnection(); | 252 new URL(HOST_LIST_PATH + JniInterface.nativeGetApiKey()) .openConnection(); |
234 link.addRequestProperty("client_id", JniInterface.nativeGetClien tId()); | 253 link.addRequestProperty("client_id", JniInterface.nativeGetClien tId()); |
(...skipping 20 matching lines...) Expand all Loading... | |
255 // Assemble error message to display to the user. | 274 // Assemble error message to display to the user. |
256 String explanation = getString(R.string.error_unknown); | 275 String explanation = getString(R.string.error_unknown); |
257 if (ex instanceof OperationCanceledException) { | 276 if (ex instanceof OperationCanceledException) { |
258 explanation = getString(R.string.error_auth_canceled); | 277 explanation = getString(R.string.error_auth_canceled); |
259 } else if (ex instanceof AuthenticatorException) { | 278 } else if (ex instanceof AuthenticatorException) { |
260 explanation = getString(R.string.error_no_accounts); | 279 explanation = getString(R.string.error_no_accounts); |
261 } else if (ex instanceof IOException) { | 280 } else if (ex instanceof IOException) { |
262 if (!mAlreadyTried) { | 281 if (!mAlreadyTried) { |
263 // This was our first connection attempt. | 282 // This was our first connection attempt. |
264 | 283 |
265 synchronized (mUi) { | 284 synchronized (mLock) { |
266 if (mAccount != null) { | 285 if (mAccount != null) { |
267 // We got an account, but couldn't log into it. We'll retry in case | 286 // We got an account, but couldn't log into it. We'll retry in case |
268 // the system's cached authentication token had already expired. | 287 // the system's cached authentication token had already expired. |
269 AccountManager authenticator = AccountManager.ge t(mUi); | 288 AccountManager authenticator = AccountManager.ge t(mUi); |
270 mAlreadyTried = true; | 289 mAlreadyTried = true; |
271 | 290 |
272 Log.w("auth", "Requesting renewal of rejected au th token"); | 291 Log.w("auth", "Requesting renewal of rejected au th token"); |
273 authenticator.invalidateAuthToken(mAccount.type, mToken); | 292 authenticator.invalidateAuthToken(mAccount.type, mToken); |
274 mToken = null; | 293 mToken = null; |
275 authenticator.getAuthToken( | 294 authenticator.getAuthToken( |
276 mAccount, TOKEN_SCOPE, null, mUi, this, mNetwork); | 295 mAccount, TOKEN_SCOPE, null, mUi, this, mNetwork); |
277 | 296 |
278 // We're not in an error state *yet*. | 297 // We're not in an error state *yet*. |
279 return; | 298 return; |
280 } | 299 } |
281 } | 300 } |
282 | 301 |
283 // We didn't even get an account, so the auth server is likely unreachable. | 302 // We didn't even get an account, so the auth server is likely unreachable. |
284 explanation = getString(R.string.error_bad_connection); | 303 explanation = getString(R.string.error_bad_connection); |
285 } else { | 304 } else { |
286 // Authentication truly failed. | 305 // Authentication truly failed. |
287 Log.e("auth", "Fresh auth token was also rejected"); | 306 Log.e("auth", "Fresh auth token was also rejected"); |
288 explanation = getString(R.string.error_auth_failed); | 307 explanation = getString(R.string.error_auth_failed); |
289 } | 308 } |
290 } else if (ex instanceof JSONException) { | 309 } else if (ex instanceof JSONException) { |
291 explanation = getString(R.string.error_unexpected_response); | 310 explanation = getString(R.string.error_unexpected_response); |
292 runOnUiThread(new HostListDisplayer(mUi)); | |
293 } | 311 } |
294 | 312 |
295 mHosts = null; | 313 mHosts = null; |
296 Log.w("auth", ex); | 314 Log.w("auth", ex); |
297 Toast.makeText(mUi, explanation, Toast.LENGTH_LONG).show(); | 315 Toast.makeText(mUi, explanation, Toast.LENGTH_LONG).show(); |
298 } | 316 } |
299 | 317 |
300 // Share our findings with the user. | 318 // Share our findings with the user. |
301 runOnUiThread(new HostListDisplayer(mUi)); | 319 runOnUiThread(new Runnable() { |
320 @Override | |
321 public void run() { | |
322 updateUi(); | |
323 } | |
324 }); | |
302 } | 325 } |
303 } | 326 } |
304 | 327 |
305 /** Formats the host list and offers it to the user. */ | 328 /** |
306 private class HostListDisplayer implements Runnable { | 329 * Updates the infotext and host list display. |
307 /** Communication with the screen. */ | 330 * This method affects the UI and must be run on the main thread. |
308 private Activity mUi; | 331 */ |
309 | 332 private void updateUi() { |
310 /** Constructor. */ | 333 synchronized (mLock) { |
311 public HostListDisplayer(Activity ui) { | 334 mRefreshButton.setEnabled(mAccount != null); |
312 mUi = ui; | 335 if (mAccount != null) { |
336 mAccountSwitcher.setTitle(mAccount.name); | |
337 } | |
313 } | 338 } |
314 | 339 |
315 /** | 340 if (mHosts == null) { |
316 * Updates the infotext and host list display. | 341 mGreeting.setText(getString(R.string.inst_empty_list)); |
317 * This method affects the UI and must be run on its same thread. | 342 mList.setAdapter(null); |
318 */ | 343 return; |
319 @Override | |
320 public void run() { | |
321 synchronized (mUi) { | |
322 mRefreshButton.setEnabled(mAccount != null); | |
323 if (mAccount != null) { | |
324 mAccountSwitcher.setTitle(mAccount.name); | |
325 } | |
326 } | |
327 | |
328 if (mHosts == null) { | |
329 mGreeting.setText(getString(R.string.inst_empty_list)); | |
330 mList.setAdapter(null); | |
331 return; | |
332 } | |
333 | |
334 mGreeting.setText(getString(R.string.inst_host_list)); | |
335 | |
336 ArrayAdapter<JSONObject> displayer = new HostListAdapter(mUi, R.layo ut.host); | |
337 Log.i("hostlist", "About to populate host list display"); | |
338 try { | |
339 int index = 0; | |
340 while (!mHosts.isNull(index)) { | |
341 displayer.add(mHosts.getJSONObject(index)); | |
342 ++index; | |
343 } | |
344 mList.setAdapter(displayer); | |
345 } | |
346 catch(JSONException ex) { | |
347 Log.w("hostlist", ex); | |
348 Toast.makeText( | |
349 mUi, getString(R.string.error_cataloging_hosts), Toast.L ENGTH_LONG).show(); | |
350 | |
351 // Close the application. | |
352 finish(); | |
353 } | |
354 } | |
355 } | |
356 | |
357 /** Describes the appearance and behavior of each host list entry. */ | |
358 private class HostListAdapter extends ArrayAdapter<JSONObject> { | |
359 /** Constructor. */ | |
360 public HostListAdapter(Context context, int textViewResourceId) { | |
361 super(context, textViewResourceId); | |
362 } | 344 } |
363 | 345 |
364 /** Generates a View corresponding to this particular host. */ | 346 mGreeting.setText(getString(R.string.inst_host_list)); |
365 @Override | |
366 public View getView(int position, View convertView, ViewGroup parent) { | |
367 TextView target = (TextView)super.getView(position, convertView, par ent); | |
368 | 347 |
369 try { | 348 ArrayAdapter<JSONObject> displayer = new HostListAdapter(this, R.layout. host); |
370 final JSONObject host = getItem(position); | 349 Log.i("hostlist", "About to populate host list display"); |
371 target.setText(Html.fromHtml(host.getString("hostName") + " (<fo nt color = \"" + | 350 try { |
372 (host.getString("status").equals("ONLINE") ? HOST_COLOR_ ONLINE : | 351 int index = 0; |
373 HOST_COLOR_OFFLINE) + "\">" + host.getString("status") + "</font>)")); | 352 while (!mHosts.isNull(index)) { |
353 displayer.add(mHosts.getJSONObject(index)); | |
354 ++index; | |
355 } | |
356 mList.setAdapter(displayer); | |
357 } catch (JSONException ex) { | |
358 Log.w("hostlist", ex); | |
359 Toast.makeText(this, getString(R.string.error_cataloging_hosts), | |
360 Toast.LENGTH_LONG).show(); | |
374 | 361 |
375 if (host.getString("status").equals("ONLINE")) { // Host is onl ine. | 362 // Close the application. |
376 target.setOnClickListener(new View.OnClickListener() { | 363 finish(); |
377 @Override | |
378 public void onClick(View v) { | |
379 try { | |
380 synchronized (getContext()) { | |
381 JniInterface.connectToHost(mAccount.name , mToken, | |
382 host.getString("jabberId"), | |
383 host.getString("hostId"), | |
384 host.getString("publicKey"), | |
385 new Runnable() { | |
386 @Override | |
387 public void run() { | |
388 startActivity( | |
389 new Intent(getContext(), Desktop.class)); | |
390 } | |
391 }); | |
392 } | |
393 } | |
394 catch(JSONException ex) { | |
395 Log.w("host", ex); | |
396 Toast.makeText(getContext(), | |
397 getString(R.string.error_reading_hos t), | |
398 Toast.LENGTH_LONG).show(); | |
399 | |
400 // Close the application. | |
401 finish(); | |
402 } | |
403 } | |
404 }); | |
405 } else { // Host is offline. | |
406 // Disallow interaction with this entry. | |
407 target.setEnabled(false); | |
408 } | |
409 } | |
410 catch(JSONException ex) { | |
411 Log.w("hostlist", ex); | |
412 Toast.makeText(getContext(), | |
413 getString(R.string.error_displaying_host), | |
414 Toast.LENGTH_LONG).show(); | |
415 | |
416 // Close the application. | |
417 finish(); | |
418 } | |
419 | |
420 return target; | |
421 } | 364 } |
422 } | 365 } |
423 } | 366 } |
367 | |
368 /** Describes the appearance and behavior of each host list entry. */ | |
369 class HostListAdapter extends ArrayAdapter<JSONObject> { | |
Sergey Ulanov
2013/12/21 02:15:28
Does this compile? I think you need to put it to a
Lambros
2013/12/26 19:29:46
It's legal according to: http://docs.oracle.com/ja
Sergey Ulanov
2013/12/26 20:03:48
This isn't really a Pimple (pimple = Private IMPLE
Lambros
2013/12/27 01:56:54
OK, done!
| |
370 /** Color to use for hosts that are online. */ | |
371 private static final String HOST_COLOR_ONLINE = "green"; | |
372 | |
373 /** Color to use for hosts that are offline. */ | |
374 private static final String HOST_COLOR_OFFLINE = "red"; | |
375 | |
376 private Chromoting mChromoting; | |
377 | |
378 /** Constructor. */ | |
379 public HostListAdapter(Chromoting chromoting, int textViewResourceId) { | |
380 super(chromoting, textViewResourceId); | |
381 mChromoting = chromoting; | |
382 } | |
383 | |
384 /** Generates a View corresponding to this particular host. */ | |
385 @Override | |
386 public View getView(int position, View convertView, ViewGroup parent) { | |
387 TextView target = (TextView)super.getView(position, convertView, parent) ; | |
388 | |
389 try { | |
390 final JSONObject host = getItem(position); | |
391 String status = host.getString("status"); | |
392 boolean online = status.equals("ONLINE"); | |
393 target.setText(Html.fromHtml(host.getString("hostName") + " (<font c olor = \"" + | |
394 (online ? HOST_COLOR_ONLINE : HOST_COLOR_OFFLINE) + "\">" + status + | |
395 "</font>)")); | |
396 | |
397 if (online) { | |
398 target.setOnClickListener(new View.OnClickListener() { | |
399 @Override | |
400 public void onClick(View v) { | |
401 mChromoting.connectToHost(host); | |
402 } | |
403 }); | |
404 } else { | |
405 // Disallow interaction with this entry. | |
406 target.setEnabled(false); | |
407 } | |
408 } catch (JSONException ex) { | |
409 Log.w("hostlist", ex); | |
410 Toast.makeText(mChromoting, mChromoting.getString(R.string.error_dis playing_host), | |
411 Toast.LENGTH_LONG).show(); | |
412 | |
413 // Close the application. | |
414 mChromoting.finish(); | |
415 } | |
416 | |
417 return target; | |
418 } | |
419 } | |
OLD | NEW |