AccountSettings.java revision 5a3888f35b669ffb3cc785d7dfe4862879a3896c
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.activity.setup; 18 19import com.android.email.Controller; 20import com.android.email.R; 21import com.android.email.activity.ActivityHelper; 22import com.android.email.activity.IntentUtilities; 23import com.android.email.mail.Sender; 24import com.android.email.mail.Store; 25import com.android.emailcommon.Logging; 26import com.android.emailcommon.provider.Account; 27import com.android.emailcommon.provider.EmailContent.AccountColumns; 28import com.android.emailcommon.utility.Utility; 29 30import android.app.ActionBar; 31import android.app.Activity; 32import android.app.AlertDialog; 33import android.app.Dialog; 34import android.app.DialogFragment; 35import android.app.Fragment; 36import android.content.Context; 37import android.content.DialogInterface; 38import android.content.Intent; 39import android.content.res.Resources; 40import android.database.Cursor; 41import android.net.Uri; 42import android.os.AsyncTask; 43import android.os.Bundle; 44import android.preference.PreferenceActivity; 45import android.util.Log; 46import android.view.KeyEvent; 47import android.view.Menu; 48import android.view.MenuItem; 49 50import java.util.List; 51 52/** 53 * Handles account preferences, using multi-pane arrangement when possible. 54 * 55 * This activity uses the following fragments: 56 * AccountSettingsFragment 57 * Account{Incoming/Outgoing/Exchange}Fragment 58 * AccountCheckSettingsFragment 59 * GeneralPreferences 60 * DebugFragment 61 * 62 * TODO: Delete account - on single-pane view (phone UX) the account list doesn't update properly 63 * TODO: Handle dynamic changes to the account list (exit if necessary). It probably makes 64 * sense to use a loader for the accounts list, because it would provide better support for 65 * dealing with accounts being added/deleted and triggering the header reload. 66 */ 67public class AccountSettings extends PreferenceActivity 68 implements AccountSettingsEditQuickResponsesFragment.Callback { 69 /* 70 * Intent to open account settings for account=1 71 adb shell am start -a android.intent.action.EDIT \ 72 -d '"content://ui.email.android.com/settings?ACCOUNT_ID=1"' 73 */ 74 75 // Intent extras for our internal activity launch 76 private static final String EXTRA_ENABLE_DEBUG = "AccountSettings.enable_debug"; 77 private static final String EXTRA_LOGIN_WARNING_FOR_ACCOUNT = "AccountSettings.for_account"; 78 private static final String EXTRA_TITLE = "AccountSettings.title"; 79 80 // Intent extras for launch directly from system account manager 81 // NOTE: This string must match the one in res/xml/account_preferences.xml 82 private static final String ACTION_ACCOUNT_MANAGER_ENTRY = 83 "com.android.email.activity.setup.ACCOUNT_MANAGER_ENTRY"; 84 // NOTE: This constant should eventually be defined in android.accounts.Constants 85 private static final String EXTRA_ACCOUNT_MANAGER_ACCOUNT = "account"; 86 87 // Key for arguments bundle for QuickResponse editing 88 private static final String QUICK_RESPONSE_ACCOUNT_KEY = "account"; 89 90 // Key codes used to open a debug settings fragment. 91 private static final int[] SECRET_KEY_CODES = { 92 KeyEvent.KEYCODE_D, KeyEvent.KEYCODE_E, KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_U, 93 KeyEvent.KEYCODE_G 94 }; 95 private int mSecretKeyCodeIndex = 0; 96 97 // Support for account-by-name lookup 98 private static final String SELECTION_ACCOUNT_EMAIL_ADDRESS = 99 AccountColumns.EMAIL_ADDRESS + "=?"; 100 101 // When the user taps "Email Preferences" 10 times in a row, we'll enable the debug settings. 102 private int mNumGeneralHeaderClicked = 0; 103 104 private long mRequestedAccountId; 105 private Header mRequestedAccountHeader; 106 private Header[] mAccountListHeaders; 107 private Header mAppPreferencesHeader; 108 /* package */ Fragment mCurrentFragment; 109 private long mDeletingAccountId = -1; 110 private boolean mShowDebugMenu; 111 private List<Header> mGeneratedHeaders; 112 private boolean mResumed; 113 114 // Async Tasks 115 private LoadAccountListTask mLoadAccountListTask; 116 private GetAccountIdFromAccountTask mGetAccountIdFromAccountTask; 117 118 // Specific callbacks used by settings fragments 119 private final AccountSettingsFragmentCallback mAccountSettingsFragmentCallback 120 = new AccountSettingsFragmentCallback(); 121 private final AccountServerSettingsFragmentCallback mAccountServerSettingsFragmentCallback 122 = new AccountServerSettingsFragmentCallback(); 123 124 /** 125 * Display (and edit) settings for a specific account, or -1 for any/all accounts 126 */ 127 public static void actionSettings(Activity fromActivity, long accountId) { 128 fromActivity.startActivity(createAccountSettingsIntent(fromActivity, accountId, null)); 129 } 130 131 /** 132 * Create and return an intent to display (and edit) settings for a specific account, or -1 133 * for any/all accounts. If an account name string is provided, a warning dialog will be 134 * displayed as well. 135 */ 136 public static Intent createAccountSettingsIntent(Context context, long accountId, 137 String loginWarningAccountName) { 138 final Uri.Builder b = IntentUtilities.createActivityIntentUrlBuilder("settings"); 139 IntentUtilities.setAccountId(b, accountId); 140 Intent i = new Intent(Intent.ACTION_EDIT, b.build()); 141 if (loginWarningAccountName != null) { 142 i.putExtra(EXTRA_LOGIN_WARNING_FOR_ACCOUNT, loginWarningAccountName); 143 } 144 return i; 145 } 146 147 /** 148 * Launch generic settings and pre-enable the debug preferences 149 */ 150 public static void actionSettingsWithDebug(Context fromContext) { 151 Intent i = new Intent(fromContext, AccountSettings.class); 152 i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 153 i.putExtra(EXTRA_ENABLE_DEBUG, true); 154 fromContext.startActivity(i); 155 } 156 157 @Override 158 public void onCreate(Bundle savedInstanceState) { 159 super.onCreate(savedInstanceState); 160 ActivityHelper.debugSetWindowFlags(this); 161 162 Intent i = getIntent(); 163 if (savedInstanceState == null) { 164 // If we are not restarting from a previous instance, we need to 165 // figure out the initial prefs to show. (Otherwise, we want to 166 // continue showing whatever the user last selected.) 167 if (ACTION_ACCOUNT_MANAGER_ENTRY.equals(i.getAction())) { 168 // This case occurs if we're changing account settings from Settings -> Accounts 169 mGetAccountIdFromAccountTask = 170 (GetAccountIdFromAccountTask) new GetAccountIdFromAccountTask().execute(i); 171 } else { 172 // Otherwise, we're called from within the Email app and look for our extras 173 mRequestedAccountId = IntentUtilities.getAccountIdFromIntent(i); 174 String loginWarningAccount = i.getStringExtra(EXTRA_LOGIN_WARNING_FOR_ACCOUNT); 175 if (loginWarningAccount != null) { 176 // Show dialog (first time only - don't re-show on a rotation) 177 LoginWarningDialog dialog = LoginWarningDialog.newInstance(loginWarningAccount); 178 dialog.show(getFragmentManager(), "loginwarning"); 179 } 180 } 181 } 182 mShowDebugMenu = i.getBooleanExtra(EXTRA_ENABLE_DEBUG, false); 183 184 String title = i.getStringExtra(EXTRA_TITLE); 185 if (title != null) { 186 setTitle(title); 187 } 188 189 getActionBar().setDisplayOptions( 190 ActionBar.DISPLAY_HOME_AS_UP, ActionBar.DISPLAY_HOME_AS_UP); 191 } 192 193 @Override 194 public void onResume() { 195 super.onResume(); 196 updateAccounts(); 197 mResumed = true; 198 199 // When we're resuming, enable/disable the add account button 200 if (hasHeaders()) { 201 invalidateOptionsMenu(); 202 } 203 } 204 205 @Override 206 public void onPause() { 207 super.onPause(); 208 mResumed = false; 209 } 210 211 @Override 212 protected void onDestroy() { 213 super.onDestroy(); 214 Utility.cancelTaskInterrupt(mLoadAccountListTask); 215 mLoadAccountListTask = null; 216 Utility.cancelTaskInterrupt(mGetAccountIdFromAccountTask); 217 mGetAccountIdFromAccountTask = null; 218 } 219 220 /** 221 * Listen for secret sequence and, if heard, enable debug menu 222 */ 223 @Override 224 public boolean onKeyDown(int keyCode, KeyEvent event) { 225 if (event.getKeyCode() == SECRET_KEY_CODES[mSecretKeyCodeIndex]) { 226 mSecretKeyCodeIndex++; 227 if (mSecretKeyCodeIndex == SECRET_KEY_CODES.length) { 228 mSecretKeyCodeIndex = 0; 229 enableDebugMenu(); 230 } 231 } else { 232 mSecretKeyCodeIndex = 0; 233 } 234 return super.onKeyDown(keyCode, event); 235 } 236 237 @Override 238 public boolean onCreateOptionsMenu(Menu menu) { 239 super.onCreateOptionsMenu(menu); 240 getMenuInflater().inflate(R.menu.account_settings_add_account_option, menu); 241 return true; 242 } 243 244 @Override 245 public boolean onPrepareOptionsMenu(Menu menu) { 246 return shouldShowNewAccount(); 247 } 248 249 @Override 250 public boolean onOptionsItemSelected(MenuItem item) { 251 switch (item.getItemId()) { 252 case android.R.id.home: 253 // The app icon on the action bar is pressed. Just emulate a back press. 254 // TODO: this should navigate to the main screen, even if a sub-setting is open. 255 // But we shouldn't just finish(), as we want to show "discard changes?" dialog 256 // when necessary. 257 onBackPressed(); 258 break; 259 case R.id.add_new_account: 260 onAddNewAccount(); 261 break; 262 default: 263 return super.onOptionsItemSelected(item); 264 } 265 return true; 266 } 267 268 @Override 269 public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args, 270 int titleRes, int shortTitleRes) { 271 Intent result = super.onBuildStartFragmentIntent( 272 fragmentName, args, titleRes, shortTitleRes); 273 274 // When opening a sub-settings page (e.g. account specific page), see if we want to modify 275 // the activity title. 276 String title = AccountSettingsFragment.getTitleFromArgs(args); 277 if ((titleRes == 0) && (title != null)) { 278 result.putExtra(EXTRA_TITLE, title); 279 } 280 return result; 281 } 282 283 /** 284 * Any time we exit via this pathway, and we are showing a server settings fragment, 285 * we put up the exit-save-changes dialog. This will work for the following cases: 286 * Cancel button 287 * Back button 288 * Up arrow in application icon 289 * It will *not* apply in the following cases: 290 * Click the parent breadcrumb - need to find a hook for this 291 * Click in the header list (e.g. another account) - handled elsewhere 292 */ 293 @Override 294 public void onBackPressed() { 295 if (mCurrentFragment instanceof AccountServerBaseFragment) { 296 boolean changed = ((AccountServerBaseFragment) mCurrentFragment).haveSettingsChanged(); 297 if (changed) { 298 UnsavedChangesDialogFragment dialogFragment = 299 UnsavedChangesDialogFragment.newInstanceForBack(); 300 dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG); 301 return; // Prevent "back" from being handled 302 } 303 } 304 super.onBackPressed(); 305 } 306 307 /** 308 * If the caller requested a specific account to be edited, switch to it. This is a one-shot, 309 * so the user is free to edit another account as well. 310 */ 311 @Override 312 public Header onGetNewHeader() { 313 Header result = mRequestedAccountHeader; 314 mRequestedAccountHeader = null; 315 return result; 316 } 317 318 private void enableDebugMenu() { 319 mShowDebugMenu = true; 320 invalidateHeaders(); 321 } 322 323 /** 324 * Decide if "add account" should be shown 325 */ 326 private boolean shouldShowNewAccount() { 327 // If in single pane mode, only add accounts at top level 328 if (!isMultiPane()) { 329 if (!hasHeaders()) return false; 330 } else { 331 // If in multi pane mode, only add accounts when showing a top level fragment 332 // Note: null is OK; This is the case when we first launch the activity 333 if ((mCurrentFragment != null) 334 && !(mCurrentFragment instanceof GeneralPreferences) 335 && !(mCurrentFragment instanceof DebugFragment) 336 && !(mCurrentFragment instanceof AccountSettingsFragment)) return false; 337 } 338 return true; 339 } 340 341 private void onAddNewAccount() { 342 AccountSetupBasics.actionNewAccount(this); 343 } 344 345 /** 346 * Start the async reload of the accounts list (if the headers are being displayed) 347 */ 348 private void updateAccounts() { 349 if (hasHeaders()) { 350 Utility.cancelTaskInterrupt(mLoadAccountListTask); 351 mLoadAccountListTask = (LoadAccountListTask) 352 new LoadAccountListTask().execute(mDeletingAccountId); 353 } 354 } 355 356 /** 357 * Write the current header (accounts) array into the one provided by the PreferenceActivity. 358 * Skip any headers that match mDeletingAccountId (this is a quick-hide algorithm while a 359 * background thread works on deleting the account). Also sets mRequestedAccountHeader if 360 * we find the requested account (by id). 361 */ 362 @Override 363 public void onBuildHeaders(List<Header> target) { 364 // Assume the account is unspecified 365 mRequestedAccountHeader = null; 366 367 // Always add app preferences as first header 368 target.clear(); 369 target.add(getAppPreferencesHeader()); 370 371 // Then add zero or more account headers as necessary 372 if (mAccountListHeaders != null) { 373 int headerCount = mAccountListHeaders.length; 374 for (int index = 0; index < headerCount; index++) { 375 Header header = mAccountListHeaders[index]; 376 if (header != null && header.id != HEADER_ID_UNDEFINED) { 377 if (header.id != mDeletingAccountId) { 378 target.add(header); 379 if (header.id == mRequestedAccountId) { 380 mRequestedAccountHeader = header; 381 mRequestedAccountId = -1; 382 } 383 } 384 } 385 } 386 } 387 388 // finally, if debug header is enabled, show it 389 if (mShowDebugMenu) { 390 // setup lightweight header for debugging 391 Header debugHeader = new Header(); 392 debugHeader.title = getText(R.string.debug_title); 393 debugHeader.summary = null; 394 debugHeader.iconRes = 0; 395 debugHeader.fragment = DebugFragment.class.getCanonicalName(); 396 debugHeader.fragmentArguments = null; 397 target.add(debugHeader); 398 } 399 400 // Save for later use (see forceSwitch) 401 mGeneratedHeaders = target; 402 } 403 404 /** 405 * Generate and return the first header, for app preferences 406 */ 407 private Header getAppPreferencesHeader() { 408 // Set up fixed header for general settings 409 if (mAppPreferencesHeader == null) { 410 mAppPreferencesHeader = new Header(); 411 mAppPreferencesHeader.title = getText(R.string.header_label_general_preferences); 412 mAppPreferencesHeader.summary = null; 413 mAppPreferencesHeader.iconRes = 0; 414 mAppPreferencesHeader.fragment = GeneralPreferences.class.getCanonicalName(); 415 mAppPreferencesHeader.fragmentArguments = null; 416 } 417 return mAppPreferencesHeader; 418 } 419 420 /** 421 * This AsyncTask reads the accounts list and generates the headers. When the headers are 422 * ready, we'll trigger PreferenceActivity to refresh the account list with them. 423 * 424 * The array generated and stored in mAccountListHeaders may be sparse so any readers should 425 * check for and skip over null entries, and should not assume array length is # of accounts. 426 * 427 * TODO: Smaller projection 428 * TODO: Convert to Loader 429 * TODO: Write a test, including operation of deletingAccountId param 430 */ 431 private class LoadAccountListTask extends AsyncTask<Long, Void, Object[]> { 432 433 @Override 434 protected Object[] doInBackground(Long... params) { 435 Header[] result = null; 436 Boolean deletingAccountFound = false; 437 long deletingAccountId = params[0]; 438 439 Cursor c = getContentResolver().query( 440 Account.CONTENT_URI, 441 Account.CONTENT_PROJECTION, null, null, null); 442 try { 443 int index = 0; 444 int headerCount = c.getCount(); 445 result = new Header[headerCount]; 446 447 while (c.moveToNext()) { 448 long accountId = c.getLong(Account.CONTENT_ID_COLUMN); 449 if (accountId == deletingAccountId) { 450 deletingAccountFound = true; 451 continue; 452 } 453 String name = c.getString(Account.CONTENT_DISPLAY_NAME_COLUMN); 454 String email = c.getString(Account.CONTENT_EMAIL_ADDRESS_COLUMN); 455 Header newHeader = new Header(); 456 newHeader.id = accountId; 457 newHeader.title = name; 458 newHeader.summary = email; 459 newHeader.fragment = AccountSettingsFragment.class.getCanonicalName(); 460 newHeader.fragmentArguments = 461 AccountSettingsFragment.buildArguments(accountId, email); 462 463 result[index++] = newHeader; 464 } 465 } finally { 466 if (c != null) { 467 c.close(); 468 } 469 } 470 return new Object[] { result, deletingAccountFound }; 471 } 472 473 @Override 474 protected void onPostExecute(Object[] result) { 475 if (isCancelled() || result == null) return; 476 // Extract the results 477 Header[] headers = (Header[]) result[0]; 478 boolean deletingAccountFound = (Boolean) result[1]; 479 // report the settings 480 mAccountListHeaders = headers; 481 invalidateHeaders(); 482 if (!deletingAccountFound) { 483 mDeletingAccountId = -1; 484 } 485 } 486 } 487 488 /** 489 * Called when the user selects an item in the header list. Handles save-data cases as needed 490 * 491 * @param header The header that was selected. 492 * @param position The header's position in the list. 493 */ 494 @Override 495 public void onHeaderClick(Header header, int position) { 496 // special case when exiting the server settings fragments 497 if (mCurrentFragment instanceof AccountServerBaseFragment) { 498 boolean changed = ((AccountServerBaseFragment)mCurrentFragment).haveSettingsChanged(); 499 if (changed) { 500 UnsavedChangesDialogFragment dialogFragment = 501 UnsavedChangesDialogFragment.newInstanceForHeader(position); 502 dialogFragment.show(getFragmentManager(), UnsavedChangesDialogFragment.TAG); 503 return; 504 } 505 } 506 507 // Secret keys: Click 10x to enable debug settings 508 if (position == 0) { 509 mNumGeneralHeaderClicked++; 510 if (mNumGeneralHeaderClicked == 10) { 511 enableDebugMenu(); 512 } 513 } else { 514 mNumGeneralHeaderClicked = 0; 515 } 516 517 // Process header click normally 518 super.onHeaderClick(header, position); 519 } 520 521 /** 522 * Switch to a specific header without checking for server settings fragments as done 523 * in {@link #onHeaderClick(Header, int)}. Called after we interrupted a header switch 524 * with a dialog, and the user OK'd it. 525 */ 526 private void forceSwitchHeader(int position) { 527 // Clear the current fragment; we're navigating away 528 mCurrentFragment = null; 529 // Ensure the UI visually shows the correct header selected 530 setSelection(position); 531 Header header = mGeneratedHeaders.get(position); 532 switchToHeader(header); 533 } 534 535 /** 536 * Forcefully go backward in the stack. This may potentially discard unsaved settings. 537 */ 538 private void forceBack() { 539 // Clear the current fragment; we're navigating away 540 mCurrentFragment = null; 541 onBackPressed(); 542 } 543 544 @Override 545 public void onAttachFragment(Fragment f) { 546 super.onAttachFragment(f); 547 548 if (f instanceof AccountSettingsFragment) { 549 AccountSettingsFragment asf = (AccountSettingsFragment) f; 550 asf.setCallback(mAccountSettingsFragmentCallback); 551 } else if (f instanceof AccountServerBaseFragment) { 552 AccountServerBaseFragment asbf = (AccountServerBaseFragment) f; 553 asbf.setCallback(mAccountServerSettingsFragmentCallback); 554 } else { 555 // Possibly uninteresting fragment, such as a dialog. 556 return; 557 } 558 mCurrentFragment = f; 559 560 // When we're changing fragments, enable/disable the add account button 561 if (mResumed && hasHeaders()) { 562 invalidateOptionsMenu(); 563 } 564 } 565 566 /** 567 * Callbacks for AccountSettingsFragment 568 */ 569 private class AccountSettingsFragmentCallback implements AccountSettingsFragment.Callback { 570 @Override 571 public void onSettingsChanged(Account account, String preference, Object value) { 572 AccountSettings.this.onSettingsChanged(account, preference, value); 573 } 574 @Override 575 public void onEditQuickResponses(Account account) { 576 AccountSettings.this.onEditQuickResponses(account); 577 } 578 @Override 579 public void onIncomingSettings(Account account) { 580 AccountSettings.this.onIncomingSettings(account); 581 } 582 @Override 583 public void onOutgoingSettings(Account account) { 584 AccountSettings.this.onOutgoingSettings(account); 585 } 586 @Override 587 public void abandonEdit() { 588 finish(); 589 } 590 @Override 591 public void deleteAccount(Account account) { 592 AccountSettings.this.deleteAccount(account); 593 } 594 } 595 596 /** 597 * Callbacks for AccountServerSettingsFragmentCallback 598 */ 599 private class AccountServerSettingsFragmentCallback 600 implements AccountServerBaseFragment.Callback { 601 @Override 602 public void onEnableProceedButtons(boolean enable) { 603 // This is not used - it's a callback for the legacy activities 604 } 605 606 @Override 607 public void onProceedNext(int checkMode, AccountServerBaseFragment target) { 608 AccountCheckSettingsFragment checkerFragment = 609 AccountCheckSettingsFragment.newInstance(checkMode, target); 610 startPreferenceFragment(checkerFragment, true); 611 } 612 613 /** 614 * After verifying a new server configuration as OK, we return here and continue. This 615 * simply does a "back" to exit the settings screen. 616 */ 617 @Override 618 public void onCheckSettingsComplete(int result, int setupMode) { 619 if (result == AccountCheckSettingsFragment.CHECK_SETTINGS_OK) { 620 // Settings checked & saved; clear current fragment 621 mCurrentFragment = null; 622 onBackPressed(); 623 } 624 } 625 } 626 627 /** 628 * Some of the settings have changed. Update internal state as necessary. 629 */ 630 public void onSettingsChanged(Account account, String preference, Object value) { 631 if (AccountSettingsFragment.PREFERENCE_DESCRIPTION.equals(preference)) { 632 for (Header header : mAccountListHeaders) { 633 if (header.id == account.mId) { 634 // Manually tweak the header title. We cannot rebuild the header list from 635 // an account cursor as the account database has not been saved yet. 636 header.title = value.toString(); 637 invalidateHeaders(); 638 break; 639 } 640 } 641 } 642 } 643 644 /** 645 * Dispatch to edit quick responses. 646 */ 647 public void onEditQuickResponses(Account account) { 648 try { 649 Bundle args = new Bundle(); 650 args.putParcelable(QUICK_RESPONSE_ACCOUNT_KEY, account); 651 startPreferencePanel(AccountSettingsEditQuickResponsesFragment.class.getName(), args, 652 R.string.account_settings_edit_quick_responses_label, null, null, 0); 653 } catch (Exception e) { 654 Log.d(Logging.LOG_TAG, "Error while trying to invoke edit quick responses.", e); 655 } 656 } 657 658 /** 659 * Implements AccountSettingsEditQuickResponsesFragment.Callback 660 */ 661 @Override 662 public void onEditQuickResponsesDone() { 663 getFragmentManager().popBackStack(); 664 } 665 666 /** 667 * Dispatch to edit incoming settings. 668 * 669 * TODO: Make things less hardwired 670 */ 671 public void onIncomingSettings(Account account) { 672 try { 673 Store store = Store.getInstance(account, getApplication(), null); 674 if (store != null) { 675 Class<? extends android.app.Activity> setting = store.getSettingActivityClass(); 676 if (setting != null) { 677 SetupData.init(SetupData.FLOW_MODE_EDIT, account); 678 if (setting.equals(AccountSetupIncoming.class)) { 679 startPreferencePanel(AccountSetupIncomingFragment.class.getName(), 680 AccountSetupIncomingFragment.getSettingsModeArgs(), 681 R.string.account_settings_incoming_label, null, null, 0); 682 } else if (setting.equals(AccountSetupExchange.class)) { 683 startPreferencePanel(AccountSetupExchangeFragment.class.getName(), 684 AccountSetupExchangeFragment.getSettingsModeArgs(), 685 R.string.account_settings_incoming_label, null, null, 0); 686 } 687 } 688 } 689 } catch (Exception e) { 690 Log.d(Logging.LOG_TAG, "Error while trying to invoke store settings.", e); 691 } 692 } 693 694 /** 695 * Dispatch to edit outgoing settings. 696 * 697 * TODO: Make things less hardwired 698 */ 699 public void onOutgoingSettings(Account account) { 700 try { 701 Sender sender = Sender.getInstance(getApplication(), account); 702 if (sender != null) { 703 Class<? extends android.app.Activity> setting = sender.getSettingActivityClass(); 704 if (setting != null) { 705 SetupData.init(SetupData.FLOW_MODE_EDIT, account); 706 if (setting.equals(AccountSetupOutgoing.class)) { 707 startPreferencePanel(AccountSetupOutgoingFragment.class.getName(), 708 AccountSetupOutgoingFragment.getSettingsModeArgs(), 709 R.string.account_settings_outgoing_label, null, null, 0); 710 } 711 } 712 } 713 } catch (Exception e) { 714 Log.d(Logging.LOG_TAG, "Error while trying to invoke sender settings.", e); 715 } 716 } 717 718 /** 719 * Delete the selected account 720 */ 721 public void deleteAccount(Account account) { 722 // Kick off the work to actually delete the account 723 // Delete the account (note, this is async. Would be nice to get a callback. 724 Controller.getInstance(this).deleteAccount(account.mId); 725 726 // Then update the UI as appropriate: 727 // If single pane, return to the header list. If multi, rebuild header list 728 if (isMultiPane()) { 729 Header prefsHeader = getAppPreferencesHeader(); 730 this.switchToHeader(prefsHeader.fragment, prefsHeader.fragmentArguments); 731 mDeletingAccountId = account.mId; 732 updateAccounts(); 733 } else { 734 // We should only be calling this while showing AccountSettingsFragment, 735 // so a finish() should bring us back to headers. No point hiding the deleted account. 736 finish(); 737 } 738 } 739 740 /** 741 * This AsyncTask looks up an account based on its email address (which is what we get from 742 * the Account Manager). When the account id is determined, we refresh the header list, 743 * which will select the preferences for that account. 744 */ 745 private class GetAccountIdFromAccountTask extends AsyncTask<Intent, Void, Long> { 746 747 @Override 748 protected Long doInBackground(Intent... params) { 749 Intent intent = params[0]; 750 android.accounts.Account acct = 751 (android.accounts.Account) intent.getParcelableExtra(EXTRA_ACCOUNT_MANAGER_ACCOUNT); 752 return Utility.getFirstRowLong(AccountSettings.this, Account.CONTENT_URI, 753 Account.ID_PROJECTION, SELECTION_ACCOUNT_EMAIL_ADDRESS, 754 new String[] {acct.name}, null, Account.ID_PROJECTION_COLUMN, -1L); 755 } 756 757 @Override 758 protected void onPostExecute(Long accountId) { 759 if (accountId != -1 && !isCancelled()) { 760 mRequestedAccountId = accountId; 761 invalidateHeaders(); 762 } 763 } 764 } 765 766 /** 767 * Dialog fragment to show "exit with unsaved changes?" dialog 768 */ 769 /* package */ static class UnsavedChangesDialogFragment extends DialogFragment { 770 private final static String TAG = "UnsavedChangesDialogFragment"; 771 772 // Argument bundle keys 773 private final static String BUNDLE_KEY_HEADER = "UnsavedChangesDialogFragment.Header"; 774 private final static String BUNDLE_KEY_BACK = "UnsavedChangesDialogFragment.Back"; 775 776 /** 777 * Creates a save changes dialog when the user selects a new header 778 * @param position The new header index to make active if the user accepts the dialog. This 779 * must be a valid header index although there is no error checking. 780 */ 781 public static UnsavedChangesDialogFragment newInstanceForHeader(int position) { 782 UnsavedChangesDialogFragment f = new UnsavedChangesDialogFragment(); 783 Bundle b = new Bundle(); 784 b.putInt(BUNDLE_KEY_HEADER, position); 785 f.setArguments(b); 786 return f; 787 } 788 789 /** 790 * Creates a save changes dialog when the user navigates "back". 791 * {@link #onBackPressed()} defines in which case this may be triggered. 792 */ 793 public static UnsavedChangesDialogFragment newInstanceForBack() { 794 UnsavedChangesDialogFragment f = new UnsavedChangesDialogFragment(); 795 Bundle b = new Bundle(); 796 b.putBoolean(BUNDLE_KEY_BACK, true); 797 f.setArguments(b); 798 return f; 799 } 800 801 // Force usage of newInstance() 802 private UnsavedChangesDialogFragment() { 803 } 804 805 @Override 806 public Dialog onCreateDialog(Bundle savedInstanceState) { 807 final AccountSettings activity = (AccountSettings) getActivity(); 808 final int position = getArguments().getInt(BUNDLE_KEY_HEADER); 809 final boolean isBack = getArguments().getBoolean(BUNDLE_KEY_BACK); 810 811 return new AlertDialog.Builder(activity) 812 .setIconAttribute(android.R.attr.alertDialogIcon) 813 .setTitle(android.R.string.dialog_alert_title) 814 .setMessage(R.string.account_settings_exit_server_settings) 815 .setPositiveButton( 816 R.string.okay_action, 817 new DialogInterface.OnClickListener() { 818 public void onClick(DialogInterface dialog, int which) { 819 if (isBack) { 820 activity.forceBack(); 821 } else { 822 activity.forceSwitchHeader(position); 823 } 824 dismiss(); 825 } 826 }) 827 .setNegativeButton( 828 activity.getString(R.string.cancel_action), null) 829 .create(); 830 } 831 } 832 833 /** 834 * Dialog briefly shown in some cases, to indicate the user that login failed. If the user 835 * clicks OK, we simply dismiss the dialog, leaving the user in the account settings for 836 * that account; If the user clicks "cancel", we exit account settings. 837 */ 838 public static class LoginWarningDialog extends DialogFragment 839 implements DialogInterface.OnClickListener { 840 private static final String BUNDLE_KEY_ACCOUNT_NAME = "account_name"; 841 842 /** 843 * Create a new dialog. 844 */ 845 public static LoginWarningDialog newInstance(String accountName) { 846 final LoginWarningDialog dialog = new LoginWarningDialog(); 847 Bundle b = new Bundle(); 848 b.putString(BUNDLE_KEY_ACCOUNT_NAME, accountName); 849 dialog.setArguments(b); 850 return dialog; 851 } 852 853 @Override 854 public Dialog onCreateDialog(Bundle savedInstanceState) { 855 final String accountName = getArguments().getString(BUNDLE_KEY_ACCOUNT_NAME); 856 857 final Context context = getActivity(); 858 final Resources res = context.getResources(); 859 final AlertDialog.Builder b = new AlertDialog.Builder(context); 860 b.setTitle(R.string.account_settings_login_dialog_title); 861 b.setIconAttribute(android.R.attr.alertDialogIcon); 862 b.setMessage(res.getString(R.string.account_settings_login_dialog_content_fmt, 863 accountName)); 864 b.setPositiveButton(R.string.okay_action, this); 865 b.setNegativeButton(R.string.cancel_action, this); 866 return b.create(); 867 } 868 869 @Override 870 public void onClick(DialogInterface dialog, int which) { 871 dismiss(); 872 if (which == DialogInterface.BUTTON_NEGATIVE) { 873 getActivity().finish(); 874 } 875 } 876 } 877} 878