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