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