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