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