Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(142)

Side by Side Diff: remoting/android/java/src/org/chromium/chromoting/Chromoting.java

Issue 1968283002: Implementing Host List Context Menu and Delete Feature (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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.annotation.SuppressLint; 7 import android.annotation.SuppressLint;
8 import android.app.ProgressDialog; 8 import android.app.ProgressDialog;
9 import android.content.DialogInterface; 9 import android.content.DialogInterface;
10 import android.content.Intent; 10 import android.content.Intent;
11 import android.content.SharedPreferences; 11 import android.content.SharedPreferences;
12 import android.content.pm.PackageManager; 12 import android.content.pm.PackageManager;
13 import android.content.res.Configuration; 13 import android.content.res.Configuration;
14 import android.graphics.drawable.Drawable; 14 import android.graphics.drawable.Drawable;
15 import android.os.Bundle; 15 import android.os.Bundle;
16 import android.provider.Settings; 16 import android.provider.Settings;
17 import android.support.v4.graphics.drawable.DrawableCompat; 17 import android.support.v4.graphics.drawable.DrawableCompat;
18 import android.support.v4.widget.DrawerLayout; 18 import android.support.v4.widget.DrawerLayout;
19 import android.support.v7.app.ActionBarDrawerToggle; 19 import android.support.v7.app.ActionBarDrawerToggle;
20 import android.support.v7.app.AlertDialog; 20 import android.support.v7.app.AlertDialog;
21 import android.support.v7.app.AppCompatActivity; 21 import android.support.v7.app.AppCompatActivity;
22 import android.support.v7.widget.Toolbar; 22 import android.support.v7.widget.Toolbar;
23 import android.view.ContextMenu;
23 import android.view.Gravity; 24 import android.view.Gravity;
24 import android.view.Menu; 25 import android.view.Menu;
25 import android.view.MenuItem; 26 import android.view.MenuItem;
26 import android.view.View; 27 import android.view.View;
27 import android.widget.AdapterView; 28 import android.widget.AdapterView;
28 import android.widget.ArrayAdapter; 29 import android.widget.ArrayAdapter;
29 import android.widget.LinearLayout; 30 import android.widget.LinearLayout;
30 import android.widget.ListView; 31 import android.widget.ListView;
31 import android.widget.Toast; 32 import android.widget.Toast;
32 33
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 */ 110 */
110 boolean mTriedNewAuthToken; 111 boolean mTriedNewAuthToken;
111 112
112 /** 113 /**
113 * Flag to track whether a call to AccountManager.getAuthToken() is currentl y pending. 114 * Flag to track whether a call to AccountManager.getAuthToken() is currentl y pending.
114 * This avoids infinitely-nested calls in case onStart() gets triggered a se cond time 115 * This avoids infinitely-nested calls in case onStart() gets triggered a se cond time
115 * while a token is being fetched. 116 * while a token is being fetched.
116 */ 117 */
117 private boolean mWaitingForAuthToken = false; 118 private boolean mWaitingForAuthToken = false;
118 119
120 /** ID of the host in progress of deletion. **/
121 private String mHostIdBeingDeleted;
122
119 private DrawerLayout mDrawerLayout; 123 private DrawerLayout mDrawerLayout;
120 124
121 private ActionBarDrawerToggle mDrawerToggle; 125 private ActionBarDrawerToggle mDrawerToggle;
122 126
123 private AccountSwitcher mAccountSwitcher; 127 private AccountSwitcher mAccountSwitcher;
124 128
125 /** The currently-connected Client, if any. */ 129 /** The currently-connected Client, if any. */
126 private Client mClient; 130 private Client mClient;
127 131
128 /** Shows a warning explaining that a Google account is required, then close s the activity. */ 132 /** Shows a warning explaining that a Google account is required, then close s the activity. */
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
189 setContentView(R.layout.main); 193 setContentView(R.layout.main);
190 194
191 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); 195 Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
192 setSupportActionBar(toolbar); 196 setSupportActionBar(toolbar);
193 197
194 mTriedNewAuthToken = false; 198 mTriedNewAuthToken = false;
195 mHostListManager = new HostListManager(); 199 mHostListManager = new HostListManager();
196 200
197 // Get ahold of our view widgets. 201 // Get ahold of our view widgets.
198 mHostListView = (ListView) findViewById(R.id.hostList_chooser); 202 mHostListView = (ListView) findViewById(R.id.hostList_chooser);
203 registerForContextMenu(mHostListView);
199 mEmptyView = findViewById(R.id.hostList_empty); 204 mEmptyView = findViewById(R.id.hostList_empty);
200 mHostListView.setOnItemClickListener( 205 mHostListView.setOnItemClickListener(
201 new AdapterView.OnItemClickListener() { 206 new AdapterView.OnItemClickListener() {
202 @Override 207 @Override
203 public void onItemClick(AdapterView<?> parent, View view, in t position, 208 public void onItemClick(AdapterView<?> parent, View view, in t position,
204 long id) { 209 long id) {
205 onHostClicked(position); 210 onHostClicked(position);
206 } 211 }
207 }); 212 });
208 213
(...skipping 155 matching lines...) Expand 10 before | Expand all | Expand 10 after
364 369
365 /** Called when a child Activity exits and sends a result back to this Activ ity. */ 370 /** Called when a child Activity exits and sends a result back to this Activ ity. */
366 @Override 371 @Override
367 public void onActivityResult(int requestCode, int resultCode, Intent data) { 372 public void onActivityResult(int requestCode, int resultCode, Intent data) {
368 mAccountSwitcher.onActivityResult(requestCode, resultCode, data); 373 mAccountSwitcher.onActivityResult(requestCode, resultCode, data);
369 374
370 if (requestCode == OAuthTokenFetcher.REQUEST_CODE_RECOVER_FROM_OAUTH_ERR OR) { 375 if (requestCode == OAuthTokenFetcher.REQUEST_CODE_RECOVER_FROM_OAUTH_ERR OR) {
371 if (resultCode == RESULT_OK) { 376 if (resultCode == RESULT_OK) {
372 // User gave OAuth permission to this app (or recovered from any OAuth failure), 377 // User gave OAuth permission to this app (or recovered from any OAuth failure),
373 // so retry fetching the token. 378 // so retry fetching the token.
374 requestAuthToken(false); 379 refreshHostList(false);
375 } else { 380 } else {
376 // User denied permission or cancelled the dialog, so cancel the request. 381 // User denied permission or cancelled the dialog, so cancel the request.
377 mWaitingForAuthToken = false; 382 mWaitingForAuthToken = false;
378 updateHostListView(); 383 updateHostListView();
379 } 384 }
380 } 385 }
381 } 386 }
382 387
383 /** Called when a permissions request has returned. */ 388 /** Called when a permissions request has returned. */
384 @Override 389 @Override
(...skipping 12 matching lines...) Expand all
397 } 402 }
398 403
399 /** Called when the display is rotated (as registered in the manifest). */ 404 /** Called when the display is rotated (as registered in the manifest). */
400 @Override 405 @Override
401 public void onConfigurationChanged(Configuration newConfig) { 406 public void onConfigurationChanged(Configuration newConfig) {
402 super.onConfigurationChanged(newConfig); 407 super.onConfigurationChanged(newConfig);
403 408
404 mDrawerToggle.onConfigurationChanged(newConfig); 409 mDrawerToggle.onConfigurationChanged(newConfig);
405 } 410 }
406 411
407 /** Called to initialize the action bar. */ 412 private int getHostIndexForMenu(ContextMenu.ContextMenuInfo menuInfo) {
Lambros 2016/05/12 23:42:13 static
Yuwei 2016/05/13 19:09:32 Done.
413 return ((AdapterView.AdapterContextMenuInfo) menuInfo).position;
414 }
415
416 @Override
417 public void onCreateContextMenu(ContextMenu menu, View v,
418 ContextMenu.ContextMenuInfo menuInfo) {
Lambros 2016/05/12 23:42:13 nit: Java line-continuation is +8-space indent.
Yuwei 2016/05/13 19:09:32 Done.
419 super.onCreateContextMenu(menu, v, menuInfo);
420 if (v.getId() == R.id.hostList_chooser) {
421 getMenuInflater().inflate(R.menu.options_actionbar, menu);
422 HostInfo info = mHosts[getHostIndexForMenu(menuInfo)];
423 menu.setHeaderTitle(info.name);
424 }
425 }
426
427 @Override
428 public boolean onContextItemSelected(MenuItem item) {
429 int i = item.getItemId();
Lambros 2016/05/12 23:42:13 nit: 'id' instead of 'i'.
Yuwei 2016/05/13 19:09:32 Done.
430 if (i == R.id.actionbar_connect) {
431 onHostClicked(getHostIndexForMenu(item.getMenuInfo()));
Lambros 2016/05/12 23:42:13 Maybe pull out the index? int hostIndex = getHostI
Yuwei 2016/05/13 19:09:32 Done.
432 } else if (i == R.id.actionbar_delete) {
433 HostInfo hostInfo = mHosts[getHostIndexForMenu(item.getMenuInfo())];
434 AlertDialog.Builder builder = new AlertDialog.Builder(this);
435 String message = getString(R.string.confirm_host_delete_android, hos tInfo.name);
436 final String hostId = hostInfo.id;
437 builder.setMessage(message)
438 .setPositiveButton(android.R.string.yes,
439 new DialogInterface.OnClickListener() {
440 @Override
441 public void onClick(DialogInterface dialog, int which) {
442 deleteHost(false, hostId);
443 dialog.dismiss();
444 }
445 })
446 .setNegativeButton(android.R.string.no, null)
447 .create().show();
448 } else {
449 return false;
450 }
451 return true;
452 }
453
454 /** Called to initialize the action bar. */
Lambros 2016/05/12 23:42:13 nit: indentation
Yuwei 2016/05/13 19:09:32 Done.
408 @Override 455 @Override
409 public boolean onCreateOptionsMenu(Menu menu) { 456 public boolean onCreateOptionsMenu(Menu menu) {
410 getMenuInflater().inflate(R.menu.chromoting_actionbar, menu); 457 getMenuInflater().inflate(R.menu.chromoting_actionbar, menu);
411 mRefreshButton = menu.findItem(R.id.actionbar_directoryrefresh); 458 mRefreshButton = menu.findItem(R.id.actionbar_directoryrefresh);
412 459
413 if (mAccount == null) { 460 if (mAccount == null) {
414 // If there is no account, don't allow the user to refresh the listi ng. 461 // If there is no account, don't allow the user to refresh the listi ng.
415 mRefreshButton.setEnabled(false); 462 mRefreshButton.setEnabled(false);
416 } 463 }
417 464
418 ChromotingUtil.tintMenuIcons(this, menu); 465 ChromotingUtil.tintMenuIcons(this, menu);
419 466
420 return super.onCreateOptionsMenu(menu); 467 return super.onCreateOptionsMenu(menu);
421 } 468 }
422 469
423 /** Called whenever an action bar button is pressed. */ 470 /** Called whenever an action bar button is pressed. */
424 @Override 471 @Override
425 public boolean onOptionsItemSelected(MenuItem item) { 472 public boolean onOptionsItemSelected(MenuItem item) {
426 if (mDrawerToggle.onOptionsItemSelected(item)) { 473 if (mDrawerToggle.onOptionsItemSelected(item)) {
427 return true; 474 return true;
428 } 475 }
429 476
430 int id = item.getItemId(); 477 int id = item.getItemId();
431 if (id == R.id.actionbar_directoryrefresh) { 478 if (id == R.id.actionbar_directoryrefresh) {
432 refreshHostList(); 479 refreshHostList(false);
433 return true; 480 return true;
434 } 481 }
435 return super.onOptionsItemSelected(item); 482 return super.onOptionsItemSelected(item);
436 } 483 }
437 484
438 /** Called when the user touches hyperlinked text. */ 485 /** Called when the user touches hyperlinked text. */
439 @Override 486 @Override
440 public void onClick(View view) { 487 public void onClick(View view) {
441 HelpSingleton.getInstance().launchHelp(this, HelpContext.HOST_SETUP); 488 HelpSingleton.getInstance().launchHelp(this, HelpContext.HOST_SETUP);
442 } 489 }
(...skipping 30 matching lines...) Expand all
473 } 520 }
474 } 521 }
475 }); 522 });
476 523
477 SessionConnector connector = new SessionConnector(mClient, this, this, m HostListManager); 524 SessionConnector connector = new SessionConnector(mClient, this, this, m HostListManager);
478 mAuthenticator = new SessionAuthenticator(this, mClient, host); 525 mAuthenticator = new SessionAuthenticator(this, mClient, host);
479 connector.connectToHost(mAccount, mToken, host, mAuthenticator, 526 connector.connectToHost(mAccount, mToken, host, mAuthenticator,
480 getPreferences(MODE_PRIVATE).getString(PREFERENCE_EXPERIMENTAL_F LAGS, "")); 527 getPreferences(MODE_PRIVATE).getString(PREFERENCE_EXPERIMENTAL_F LAGS, ""));
481 } 528 }
482 529
483 private void refreshHostList() { 530 private void showAuthErrorMessage(OAuthTokenFetcher.Error error) {
531 String explanation = getString(error == OAuthTokenFetcher.Error.NETWORK
532 ? R.string.error_network_error : R.string.error_unexpected);
533 Toast.makeText(Chromoting.this, explanation, Toast.LENGTH_LONG).show();
534 }
535
536 private void refreshHostList(boolean expireCurrentToken) {
484 if (mWaitingForAuthToken) { 537 if (mWaitingForAuthToken) {
485 return; 538 return;
486 } 539 }
487
488 mTriedNewAuthToken = false;
489 showHostListLoadingIndicator(); 540 showHostListLoadingIndicator();
490 541
491 // The refresh button simply makes use of the currently-chosen account. 542 // The refresh button simply makes use of the currently-chosen account.
492 requestAuthToken(false); 543 requestAuthToken(expireCurrentToken, new OAuthTokenFetcher.Callback() {
544 @Override
545 public void onTokenFetched(String token) {
546 mHostListManager.retrieveHostList(mToken, Chromoting.this);
547 }
548
549 @Override
550 public void onError(OAuthTokenFetcher.Error error) {
551 showAuthErrorMessage(error);
552 updateHostListView();
553 }
554 });
493 } 555 }
494 556
495 private void requestAuthToken(boolean expireCurrentToken) { 557 private void deleteHost(boolean expireCurrentToken, final String hostId) {
558 if (mWaitingForAuthToken) {
559 return;
560 }
561 showHostListLoadingIndicator();
562
563 mHostIdBeingDeleted = hostId;
564
565 requestAuthToken(expireCurrentToken, new OAuthTokenFetcher.Callback() {
566 @Override
567 public void onTokenFetched(String token) {
568 mHostListManager.deleteHost(token, hostId, Chromoting.this);
569 }
570
571 @Override
572 public void onError(OAuthTokenFetcher.Error error) {
573 showAuthErrorMessage(error);
574 }
575 });
576 }
577
578 // TODO(yuweih): This implementation have undefined behavior if multiple aut h token requests
579 // start at the same time. In that case we should consider queueing up the r equests.
580 private void requestAuthToken(boolean expireCurrentToken,
581 final OAuthTokenFetcher.Callback callback) {
Lambros 2016/05/12 23:42:13 nit: line-continuation is +8 space.
496 mWaitingForAuthToken = true; 582 mWaitingForAuthToken = true;
497 583
498 OAuthTokenFetcher fetcher = new OAuthTokenFetcher(this, mAccount, TOKEN_ SCOPE, 584 OAuthTokenFetcher fetcher = new OAuthTokenFetcher(this, mAccount, TOKEN_ SCOPE,
499 new OAuthTokenFetcher.Callback() { 585 new OAuthTokenFetcher.Callback() {
500 @Override 586 @Override
501 public void onTokenFetched(String token) { 587 public void onTokenFetched(String token) {
502 mWaitingForAuthToken = false; 588 mWaitingForAuthToken = false;
503 mToken = token; 589 mToken = token;
504 mHostListManager.retrieveHostList(mToken, Chromoting.thi s); 590 callback.onTokenFetched(token);
505 } 591 }
506 592
507 @Override 593 @Override
508 public void onError(OAuthTokenFetcher.Error error) { 594 public void onError(OAuthTokenFetcher.Error error) {
509 mWaitingForAuthToken = false; 595 mWaitingForAuthToken = false;
510 updateHostListView(); 596 callback.onError(error);
511 String explanation = getString(error == OAuthTokenFetche r.Error.NETWORK
512 ? R.string.error_network_error : R.string.error_ unexpected);
513 Toast.makeText(Chromoting.this, explanation, Toast.LENGT H_LONG).show();
514 } 597 }
515 }); 598 });
516 599
517 if (expireCurrentToken) { 600 if (expireCurrentToken) {
518 fetcher.clearAndFetch(mToken); 601 fetcher.clearAndFetch(mToken);
519 mToken = null; 602 mToken = null;
520 } else { 603 } else {
521 fetcher.fetch(); 604 fetcher.fetch();
522 } 605 }
523 } 606 }
524 607
525 @Override 608 @Override
526 public void onAccountSelected(String accountName) { 609 public void onAccountSelected(String accountName) {
527 mAccount = accountName; 610 mAccount = accountName;
528 611
529 // The current host list is no longer valid for the new account, so clea r the list. 612 // The current host list is no longer valid for the new account, so clea r the list.
530 mHosts = new HostInfo[0]; 613 mHosts = new HostInfo[0];
531 updateUi(); 614 updateUi();
532 refreshHostList(); 615 refreshHostList(false);
533 } 616 }
534 617
535 @Override 618 @Override
536 public void onAccountsListEmpty() { 619 public void onAccountsListEmpty() {
537 showNoAccountsDialog(); 620 showNoAccountsDialog();
538 } 621 }
539 622
540 @Override 623 @Override
541 public void onRequestCloseDrawer() { 624 public void onRequestCloseDrawer() {
542 mDrawerLayout.closeDrawers(); 625 mDrawerLayout.closeDrawers();
543 } 626 }
544 627
545 @Override 628 @Override
546 public void onHostListReceived(HostInfo[] hosts) { 629 public void onHostListReceived(HostInfo[] hosts) {
547 // Store a copy of the array, so that it can't be mutated by the HostLis tManager. HostInfo 630 // Store a copy of the array, so that it can't be mutated by the HostLis tManager. HostInfo
548 // is an immutable type, so a shallow copy of the array is sufficient he re. 631 // is an immutable type, so a shallow copy of the array is sufficient he re.
632 mTriedNewAuthToken = false;
Lambros 2016/05/12 23:42:13 I think we can get rid of the retry-if-auth-token-
Yuwei 2016/05/13 19:09:32 Done.
549 mHosts = Arrays.copyOf(hosts, hosts.length); 633 mHosts = Arrays.copyOf(hosts, hosts.length);
550 updateHostListView(); 634 updateHostListView();
551 updateUi(); 635 updateUi();
552 } 636 }
553 637
554 @Override 638 @Override
555 public void onHostUpdated() { 639 public void onHostUpdated() {
556 // Not implemented Yet. 640 // Not implemented Yet.
557 } 641 }
558 642
559 @Override 643 @Override
560 public void onHostDeleted() { 644 public void onHostDeleted() {
561 // Not implemented Yet. 645 mHostIdBeingDeleted = null;
646 mHostListManager.retrieveHostList(mToken, this);
562 } 647 }
563 648
564 @Override 649 @Override
565 public void onError(HostListManager.Error error) { 650 public void onError(HostListManager.RequestType type, HostListManager.Error error) {
566 String explanation = null; 651 String explanation = null;
567 switch (error) { 652 switch (error) {
568 case AUTH_FAILED: 653 case AUTH_FAILED:
569 break; 654 break;
570 case NETWORK_ERROR: 655 case NETWORK_ERROR:
571 explanation = getString(R.string.error_network_error); 656 explanation = getString(R.string.error_network_error);
572 break; 657 break;
573 case UNEXPECTED_RESPONSE: 658 case UNEXPECTED_RESPONSE:
574 case SERVICE_UNAVAILABLE: 659 case SERVICE_UNAVAILABLE:
575 case UNKNOWN: 660 case UNKNOWN:
576 explanation = getString(R.string.error_unexpected); 661 explanation = getString(R.string.error_unexpected);
577 break; 662 break;
578 default: 663 default:
579 // Unreachable. 664 // Unreachable.
580 return; 665 return;
581 } 666 }
582 667
583 if (explanation != null) { 668 if (explanation != null) {
584 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); 669 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show();
585 updateHostListView(); 670 updateHostListView();
586 return; 671 return;
587 } 672 }
588 673
589 // This is the AUTH_FAILED case. 674 // This is the AUTH_FAILED case.
590 675
591 if (!mTriedNewAuthToken) { 676 if (!mTriedNewAuthToken) {
592 // This was our first connection attempt. 677 // This was our first connection attempt.
593 mTriedNewAuthToken = true; 678 mTriedNewAuthToken = true;
594 requestAuthToken(true); 679 switch (type) {
595 680 case RETRIEVE_HOST_LIST:
681 refreshHostList(true);
682 break;
683 case DELETE_HOST:
684 deleteHost(true, mHostIdBeingDeleted);
685 }
596 // We're not in an error state *yet*. 686 // We're not in an error state *yet*.
597 return;
598 } else { 687 } else {
599 // Authentication truly failed. 688 // Authentication truly failed.
689 mTriedNewAuthToken = false;
690 mHostIdBeingDeleted = null;
600 Log.e(TAG, "Fresh auth token was rejected."); 691 Log.e(TAG, "Fresh auth token was rejected.");
601 explanation = getString(R.string.error_authentication_failed); 692 explanation = getString(R.string.error_authentication_failed);
602 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show(); 693 Toast.makeText(this, explanation, Toast.LENGTH_LONG).show();
603 updateHostListView(); 694 updateHostListView();
604 } 695 }
605 } 696 }
606 697
607 /** 698 /**
608 * Updates the infotext and host list display. 699 * Updates the infotext and host list display.
609 */ 700 */
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
650 // Unreachable, but required by Google Java style and findbugs. 741 // Unreachable, but required by Google Java style and findbugs.
651 assert false : "Unreached"; 742 assert false : "Unreached";
652 } 743 }
653 744
654 if (dismissProgress && mProgressIndicator != null) { 745 if (dismissProgress && mProgressIndicator != null) {
655 mProgressIndicator.dismiss(); 746 mProgressIndicator.dismiss();
656 mProgressIndicator = null; 747 mProgressIndicator = null;
657 } 748 }
658 } 749 }
659 } 750 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698