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