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