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