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