AccountSettings.java revision 03cd72805dab0379ed255d151f1c17cc60655fc3
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 android.app.ActionBar;
20import android.app.Activity;
21import android.app.AlertDialog;
22import android.app.Dialog;
23import android.app.DialogFragment;
24import android.app.Fragment;
25import android.content.Context;
26import android.content.DialogInterface;
27import android.content.Intent;
28import android.content.res.Resources;
29import android.database.ContentObserver;
30import android.database.Cursor;
31import android.net.Uri;
32import android.os.AsyncTask;
33import android.os.Bundle;
34import android.preference.PreferenceActivity;
35import android.util.Log;
36import android.view.KeyEvent;
37import android.view.Menu;
38import android.view.MenuItem;
39
40import com.android.email.Controller;
41import com.android.email.R;
42import com.android.email.activity.ActivityHelper;
43import com.android.email.mail.Sender;
44import com.android.email.mail.Store;
45import com.android.emailcommon.Logging;
46import com.android.emailcommon.provider.Account;
47import com.android.emailcommon.provider.EmailContent.AccountColumns;
48import com.android.emailcommon.utility.IntentUtilities;
49import com.android.emailcommon.utility.Utility;
50
51import 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 */
68public 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