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