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