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