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