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.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