Home | History | Annotate | Download | only in setup
      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      */
    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      */
    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      */
    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
    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
    180     public void onResume() {
    181         super.onResume();
    182         getContentResolver().registerContentObserver(Account.NOTIFIER_URI, true, mAccountObserver);
    183         updateAccounts();
    184     }
    185 
    186     @Override
    187     public void onPause() {
    188         super.onPause();
    189         getContentResolver().unregisterContentObserver(mAccountObserver);
    190     }
    191 
    192     @Override
    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
    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
    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
    224     public boolean onPrepareOptionsMenu(Menu menu) {
    225         return shouldShowNewAccount();
    226     }
    227 
    228     @Override
    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
    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
    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
    291     public Header onGetNewHeader() {
    292         Header result = mRequestedAccountHeader;
    293         mRequestedAccountHeader = null;
    294         return result;
    295     }
    296 
    297     private void enableDebugMenu() {
    298         mShowDebugMenu = true;
    299         invalidateHeaders();
    300     }
    301 
    302     /**
    303      * Decide if "add account" should be shown
    304      */
    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 
    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      */
    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
    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      */
    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
    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
    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
    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      */
    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      */
    518     private void forceBack() {
    519         // Clear the current fragment; we're navigating away
    520         mCurrentFragment = null;
    521         onBackPressed();
    522     }
    523 
    524     @Override
    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
    549         public void onSettingsChanged(Account account, String preference, Object value) {
    550             AccountSettings.this.onSettingsChanged(account, preference, value);
    551         }
    552         @Override
    553         public void onEditQuickResponses(Account account) {
    554             AccountSettings.this.onEditQuickResponses(account);
    555         }
    556         @Override
    557         public void onIncomingSettings(Account account) {
    558             AccountSettings.this.onIncomingSettings(account);
    559         }
    560         @Override
    561         public void onOutgoingSettings(Account account) {
    562             AccountSettings.this.onOutgoingSettings(account);
    563         }
    564         @Override
    565         public void abandonEdit() {
    566             finish();
    567         }
    568         @Override
    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
    580         public void onEnableProceedButtons(boolean enable) {
    581             // This is not used - it's a callback for the legacy activities
    582         }
    583 
    584         @Override
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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          */
    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          */
    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()
    746         private UnsavedChangesDialogFragment() {
    747         }
    748 
    749         @Override
    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