AccountSettingsFragment.java revision a7bc0319a75184ad706bb35c049af107ac3688e6
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.Email;
20import com.android.email.R;
21import com.android.email.Utility;
22import com.android.email.mail.Sender;
23import com.android.email.mail.Store;
24import com.android.emailcommon.mail.MessagingException;
25import com.android.emailcommon.provider.EmailContent;
26import com.android.emailcommon.provider.EmailContent.Account;
27import com.android.emailcommon.provider.EmailContent.HostAuth;
28
29import android.app.Activity;
30import android.app.AlertDialog;
31import android.app.Dialog;
32import android.app.DialogFragment;
33import android.app.Fragment;
34import android.app.FragmentTransaction;
35import android.content.ContentResolver;
36import android.content.ContentValues;
37import android.content.Context;
38import android.content.DialogInterface;
39import android.content.SharedPreferences;
40import android.os.AsyncTask;
41import android.os.Bundle;
42import android.os.Vibrator;
43import android.preference.CheckBoxPreference;
44import android.preference.EditTextPreference;
45import android.preference.ListPreference;
46import android.preference.Preference;
47import android.preference.PreferenceCategory;
48import android.preference.PreferenceFragment;
49import android.preference.RingtonePreference;
50import android.provider.Calendar;
51import android.provider.ContactsContract;
52import android.text.TextUtils;
53import android.util.Log;
54
55/**
56 * Fragment containing the main logic for account settings.  This also calls out to other
57 * fragments for server settings.
58 *
59 * TODO: Remove or make async the mAccountDirty reload logic.  Probably no longer needed.
60 * TODO: Can we defer calling addPreferencesFromResource() until after we load the account?  This
61 *       could reduce flicker.
62 */
63public class AccountSettingsFragment extends PreferenceFragment {
64
65    // Keys used for arguments bundle
66    private static final String BUNDLE_KEY_ACCOUNT_ID = "AccountSettingsFragment.AccountId";
67
68    private static final String PREFERENCE_CATEGORY_TOP = "account_settings";
69    private static final String PREFERENCE_DESCRIPTION = "account_description";
70    private static final String PREFERENCE_NAME = "account_name";
71    private static final String PREFERENCE_SIGNATURE = "account_signature";
72    private static final String PREFERENCE_FREQUENCY = "account_check_frequency";
73    private static final String PREFERENCE_BACKGROUND_ATTACHMENTS =
74            "account_background_attachments";
75    private static final String PREFERENCE_DEFAULT = "account_default";
76    private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "account_notifications";
77    private static final String PREFERENCE_NOTIFY = "account_notify";
78    private static final String PREFERENCE_VIBRATE_WHEN = "account_settings_vibrate_when";
79    private static final String PREFERENCE_RINGTONE = "account_ringtone";
80    private static final String PREFERENCE_CATEGORY_SERVER = "account_servers";
81    private static final String PREFERENCE_INCOMING = "incoming";
82    private static final String PREFERENCE_OUTGOING = "outgoing";
83    private static final String PREFERENCE_SYNC_CONTACTS = "account_sync_contacts";
84    private static final String PREFERENCE_SYNC_CALENDAR = "account_sync_calendar";
85    private static final String PREFERENCE_SYNC_EMAIL = "account_sync_email";
86    private static final String PREFERENCE_DELETE_ACCOUNT = "delete_account";
87
88    // These strings must match account_settings_vibrate_when_* strings in strings.xml
89    private static final String PREFERENCE_VALUE_VIBRATE_WHEN_ALWAYS = "always";
90    private static final String PREFERENCE_VALUE_VIBRATE_WHEN_SILENT = "silent";
91    private static final String PREFERENCE_VALUE_VIBRATE_WHEN_NEVER = "never";
92
93    private EditTextPreference mAccountDescription;
94    private EditTextPreference mAccountName;
95    private EditTextPreference mAccountSignature;
96    private ListPreference mCheckFrequency;
97    private ListPreference mSyncWindow;
98    private CheckBoxPreference mAccountBackgroundAttachments;
99    private CheckBoxPreference mAccountDefault;
100    private CheckBoxPreference mAccountNotify;
101    private ListPreference mAccountVibrateWhen;
102    private RingtonePreference mAccountRingtone;
103    private CheckBoxPreference mSyncContacts;
104    private CheckBoxPreference mSyncCalendar;
105    private CheckBoxPreference mSyncEmail;
106
107    private Context mContext;
108    private Account mAccount;
109    private boolean mAccountDirty;
110    private long mDefaultAccountId;
111    private Callback mCallback = EmptyCallback.INSTANCE;
112    private boolean mStarted;
113    private boolean mLoaded;
114    private boolean mSaveOnExit;
115
116    // Async Tasks
117    private AsyncTask<?,?,?> mLoadAccountTask;
118
119    /**
120     * Callback interface that owning activities must provide
121     */
122    public interface Callback {
123        public void onIncomingSettings(Account account);
124        public void onOutgoingSettings(Account account);
125        public void abandonEdit();
126        public void deleteAccount(Account account);
127    }
128
129    private static class EmptyCallback implements Callback {
130        public static final Callback INSTANCE = new EmptyCallback();
131        @Override public void onIncomingSettings(Account account) { }
132        @Override public void onOutgoingSettings(Account account) { }
133        @Override public void abandonEdit() { }
134        @Override public void deleteAccount(Account account) { }
135    }
136
137    /**
138     * If launching with an arguments bundle, use this method to build the arguments.
139     * @param accountId The account being modified
140     */
141    public static Bundle buildArguments(long accountId) {
142        Bundle b = new Bundle();
143        b.putLong(BUNDLE_KEY_ACCOUNT_ID, accountId);
144        return b;
145    }
146
147    /**
148     * Called when a fragment is first attached to its activity.
149     * {@link #onCreate(Bundle)} will be called after this.
150     */
151    @Override
152    public void onAttach(Activity activity) {
153        super.onAttach(activity);
154
155        mContext = activity;
156
157        // Notify the activity that we're here.
158        if (activity instanceof AccountSettingsXL) {
159            ((AccountSettingsXL)activity).onAttach(this);
160        }
161    }
162
163    /**
164     * Called to do initial creation of a fragment.  This is called after
165     * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
166     */
167    @Override
168    public void onCreate(Bundle savedInstanceState) {
169        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
170            Log.d(Email.LOG_TAG, "AccountSettingsFragment onCreate");
171        }
172        super.onCreate(savedInstanceState);
173
174        // Load the preferences from an XML resource
175        addPreferencesFromResource(R.xml.account_settings_preferences);
176
177        // Start loading the account data, if provided in the arguments
178        // If not, activity must call startLoadingAccount() directly
179        Bundle b = getArguments();
180        if (b != null) {
181            long accountId = b.getLong(BUNDLE_KEY_ACCOUNT_ID, -1);
182            if (accountId >= 0 && !mLoaded) {
183                startLoadingAccount(accountId);
184            }
185        }
186
187        mAccountDirty = false;
188    }
189
190    @Override
191    public void onActivityCreated(Bundle savedInstanceState) {
192        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
193            Log.d(Email.LOG_TAG, "AccountSettingsFragment onActivityCreated");
194        }
195        super.onActivityCreated(savedInstanceState);
196    }
197
198    /**
199     * Called when the Fragment is visible to the user.
200     */
201    @Override
202    public void onStart() {
203        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
204            Log.d(Email.LOG_TAG, "AccountSettingsFragment onStart");
205        }
206        super.onStart();
207        mStarted = true;
208
209        // If the loaded account is ready now, load the UI
210        if (mAccount != null && !mLoaded) {
211            loadSettings();
212        }
213    }
214
215    /**
216     * Called when the fragment is visible to the user and actively running.
217     * TODO: Don't read account data on UI thread.  This should be fixed by removing the need
218     * to do this, not by spinning up yet another thread.
219     */
220    @Override
221    public void onResume() {
222        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
223            Log.d(Email.LOG_TAG, "AccountSettingsFragment onResume");
224        }
225        super.onResume();
226
227        if (mAccountDirty) {
228            // if we are coming back from editing incoming or outgoing settings,
229            // we need to refresh them here so we don't accidentally overwrite the
230            // old values we're still holding here
231            mAccount.mHostAuthRecv =
232                HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
233            mAccount.mHostAuthSend =
234                HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeySend);
235            // Because "delete policy" UI is on edit incoming settings, we have
236            // to refresh that as well.
237            Account refreshedAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
238            if (refreshedAccount == null || mAccount.mHostAuthRecv == null
239                    || mAccount.mHostAuthSend == null) {
240                mSaveOnExit = false;
241                mCallback.abandonEdit();
242                return;
243            }
244            mAccount.setDeletePolicy(refreshedAccount.getDeletePolicy());
245            mAccountDirty = false;
246        }
247    }
248
249    @Override
250    public void onPause() {
251        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
252            Log.d(Email.LOG_TAG, "AccountSettingsFragment onPause");
253        }
254        super.onPause();
255        if (mSaveOnExit) {
256            saveSettings();
257        }
258    }
259
260    /**
261     * Called when the Fragment is no longer started.
262     */
263    @Override
264    public void onStop() {
265        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
266            Log.d(Email.LOG_TAG, "AccountSettingsFragment onStop");
267        }
268        super.onStop();
269        mStarted = false;
270    }
271
272    /**
273     * Called when the fragment is no longer in use.
274     */
275    @Override
276    public void onDestroy() {
277        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
278            Log.d(Email.LOG_TAG, "AccountSettingsFragment onDestroy");
279        }
280        super.onDestroy();
281
282        Utility.cancelTaskInterrupt(mLoadAccountTask);
283        mLoadAccountTask = null;
284    }
285
286    @Override
287    public void onSaveInstanceState(Bundle outState) {
288        if (Email.DEBUG_LIFECYCLE && Email.DEBUG) {
289            Log.d(Email.LOG_TAG, "AccountSettingsFragment onSaveInstanceState");
290        }
291        super.onSaveInstanceState(outState);
292    }
293
294    /**
295     * Activity provides callbacks here
296     */
297    public void setCallback(Callback callback) {
298        mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
299    }
300
301    /**
302     * Start loading a single account in preparation for editing it
303     */
304    public void startLoadingAccount(long accountId) {
305        Utility.cancelTaskInterrupt(mLoadAccountTask);
306        mLoadAccountTask = new LoadAccountTask().execute(accountId);
307    }
308
309    /**
310     * Async task to load account in order to view/edit it
311     */
312    private class LoadAccountTask extends AsyncTask<Long, Void, Object[]> {
313        @Override
314        protected Object[] doInBackground(Long... params) {
315            long accountId = params[0];
316            Account account = Account.restoreAccountWithId(mContext, accountId);
317            if (account != null) {
318                account.mHostAuthRecv =
319                    HostAuth.restoreHostAuthWithId(mContext, account.mHostAuthKeyRecv);
320                account.mHostAuthSend =
321                    HostAuth.restoreHostAuthWithId(mContext, account.mHostAuthKeySend);
322                if (account.mHostAuthRecv == null || account.mHostAuthSend == null) {
323                    account = null;
324                }
325            }
326            long defaultAccountId = Account.getDefaultAccountId(mContext);
327            return new Object[] { account, Long.valueOf(defaultAccountId) };
328        }
329
330        @Override
331        protected void onPostExecute(Object[] results) {
332            if (results != null && !isCancelled()) {
333                Account account = (Account) results[0];
334                if (account == null) {
335                    mSaveOnExit = false;
336                    mCallback.abandonEdit();
337                } else {
338                    mAccount = account;
339                    mDefaultAccountId = (Long) results[1];
340                    if (mStarted && !mLoaded) {
341                        loadSettings();
342                    }
343                }
344            }
345        }
346    }
347
348    /**
349     * Load account data into preference UI
350     */
351    private void loadSettings() {
352        // We can only do this once, so prevent repeat
353        mLoaded = true;
354        // Once loaded the data is ready to be saved, as well
355        mSaveOnExit = false;
356
357        PreferenceCategory topCategory =
358            (PreferenceCategory) findPreference(PREFERENCE_CATEGORY_TOP);
359        topCategory.setTitle(mContext.getString(R.string.account_settings_title_fmt));
360
361        mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION);
362        mAccountDescription.setSummary(mAccount.getDisplayName());
363        mAccountDescription.setText(mAccount.getDisplayName());
364        mAccountDescription.setOnPreferenceChangeListener(
365            new Preference.OnPreferenceChangeListener() {
366                public boolean onPreferenceChange(Preference preference, Object newValue) {
367                    String summary = newValue.toString().trim();
368                    if (TextUtils.isEmpty(summary)) {
369                        summary = mAccount.mEmailAddress;
370                    }
371                    mAccountDescription.setSummary(summary);
372                    mAccountDescription.setText(summary);
373                    onPreferenceChanged();
374                    return false;
375                }
376            }
377        );
378
379        mAccountName = (EditTextPreference) findPreference(PREFERENCE_NAME);
380        mAccountName.setSummary(mAccount.getSenderName());
381        mAccountName.setText(mAccount.getSenderName());
382        mAccountName.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
383            public boolean onPreferenceChange(Preference preference, Object newValue) {
384                final String summary = newValue.toString().trim();
385                if (!TextUtils.isEmpty(summary)) {
386                    mAccountName.setSummary(summary);
387                    mAccountName.setText(summary);
388                    onPreferenceChanged();
389                }
390                return false;
391            }
392        });
393
394        mAccountSignature = (EditTextPreference) findPreference(PREFERENCE_SIGNATURE);
395        String signature = mAccount.getSignature();
396        mAccountSignature.setText(mAccount.getSignature());
397        mAccountSignature.setOnPreferenceChangeListener(
398            new Preference.OnPreferenceChangeListener() {
399                public boolean onPreferenceChange(Preference preference, Object newValue) {
400                    // Clean up signature if it's only whitespace (which is easy to do on a
401                    // soft keyboard) but leave whitespace in place otherwise, to give the user
402                    // maximum flexibility, e.g. the ability to indent
403                    String signature = newValue.toString();
404                    if (signature.trim().isEmpty()) {
405                        signature = "";
406                    }
407                    mAccountSignature.setText(signature);
408                    onPreferenceChanged();
409                    return false;
410                }
411            });
412
413        mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
414
415        // Before setting value, we may need to adjust the lists
416        Store.StoreInfo info = Store.StoreInfo.getStoreInfo(mAccount.getStoreUri(mContext),
417                mContext);
418        if (info.mPushSupported) {
419            mCheckFrequency.setEntries(R.array.account_settings_check_frequency_entries_push);
420            mCheckFrequency.setEntryValues(R.array.account_settings_check_frequency_values_push);
421        }
422
423        mCheckFrequency.setValue(String.valueOf(mAccount.getSyncInterval()));
424        mCheckFrequency.setSummary(mCheckFrequency.getEntry());
425        mCheckFrequency.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
426            public boolean onPreferenceChange(Preference preference, Object newValue) {
427                final String summary = newValue.toString();
428                int index = mCheckFrequency.findIndexOfValue(summary);
429                mCheckFrequency.setSummary(mCheckFrequency.getEntries()[index]);
430                mCheckFrequency.setValue(summary);
431                onPreferenceChanged();
432                return false;
433            }
434        });
435
436        // Add check window preference
437        mSyncWindow = null;
438        if (info.mVisibleLimitDefault == -1) {
439            mSyncWindow = new ListPreference(mContext);
440            mSyncWindow.setTitle(R.string.account_setup_options_mail_window_label);
441            mSyncWindow.setEntries(R.array.account_settings_mail_window_entries);
442            mSyncWindow.setEntryValues(R.array.account_settings_mail_window_values);
443            mSyncWindow.setValue(String.valueOf(mAccount.getSyncLookback()));
444            mSyncWindow.setSummary(mSyncWindow.getEntry());
445            mSyncWindow.setOrder(4);
446            mSyncWindow.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
447                public boolean onPreferenceChange(Preference preference, Object newValue) {
448                    final String summary = newValue.toString();
449                    int index = mSyncWindow.findIndexOfValue(summary);
450                    mSyncWindow.setSummary(mSyncWindow.getEntries()[index]);
451                    mSyncWindow.setValue(summary);
452                    onPreferenceChanged();
453                    return false;
454                }
455            });
456            topCategory.addPreference(mSyncWindow);
457        }
458
459        // Show "background attachments" for IMAP & EAS - hide it for POP3.
460        mAccountBackgroundAttachments = (CheckBoxPreference)
461                findPreference(PREFERENCE_BACKGROUND_ATTACHMENTS);
462        if ("pop3".equals(mAccount.mHostAuthRecv.mProtocol)) {
463            topCategory.removePreference(mAccountBackgroundAttachments);
464        } else {
465            mAccountBackgroundAttachments.setChecked(
466                    0 != (mAccount.getFlags() & Account.FLAGS_BACKGROUND_ATTACHMENTS));
467            mAccountBackgroundAttachments.setOnPreferenceChangeListener(mPreferenceChangeListener);
468        }
469
470        mAccountDefault = (CheckBoxPreference) findPreference(PREFERENCE_DEFAULT);
471        mAccountDefault.setChecked(mAccount.mId == mDefaultAccountId);
472        mAccountDefault.setOnPreferenceChangeListener(mPreferenceChangeListener);
473
474        mAccountNotify = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY);
475        mAccountNotify.setChecked(0 != (mAccount.getFlags() & Account.FLAGS_NOTIFY_NEW_MAIL));
476        mAccountNotify.setOnPreferenceChangeListener(mPreferenceChangeListener);
477
478        mAccountRingtone = (RingtonePreference) findPreference(PREFERENCE_RINGTONE);
479        mAccountRingtone.setOnPreferenceChangeListener(mPreferenceChangeListener);
480
481        // The following two lines act as a workaround for the RingtonePreference
482        // which does not let us set/get the value programmatically
483        SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences();
484        prefs.edit().putString(PREFERENCE_RINGTONE, mAccount.getRingtone()).apply();
485
486        // Set the vibrator value, or hide it on devices w/o a vibrator
487        mAccountVibrateWhen = (ListPreference) findPreference(PREFERENCE_VIBRATE_WHEN);
488        Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
489        if (vibrator.hasVibrator()) {
490            boolean flagsVibrate = 0 != (mAccount.getFlags() & Account.FLAGS_VIBRATE_ALWAYS);
491            boolean flagsVibrateSilent =
492                    0 != (mAccount.getFlags() & Account.FLAGS_VIBRATE_WHEN_SILENT);
493            mAccountVibrateWhen.setValue(
494                    flagsVibrate ? PREFERENCE_VALUE_VIBRATE_WHEN_ALWAYS :
495                    flagsVibrateSilent ? PREFERENCE_VALUE_VIBRATE_WHEN_SILENT :
496                        PREFERENCE_VALUE_VIBRATE_WHEN_NEVER);
497            mAccountVibrateWhen.setOnPreferenceChangeListener(mPreferenceChangeListener);
498        } else {
499            PreferenceCategory notificationsCategory = (PreferenceCategory)
500                    findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS);
501            notificationsCategory.removePreference(mAccountVibrateWhen);
502        }
503
504        findPreference(PREFERENCE_INCOMING).setOnPreferenceClickListener(
505                new Preference.OnPreferenceClickListener() {
506                    public boolean onPreferenceClick(Preference preference) {
507                        mAccountDirty = true;
508                        mCallback.onIncomingSettings(mAccount);
509                        return true;
510                    }
511                });
512
513        // Hide the outgoing account setup link if it's not activated
514        Preference prefOutgoing = findPreference(PREFERENCE_OUTGOING);
515        boolean showOutgoing = true;
516        try {
517            Sender sender = Sender.getInstance(mContext, mAccount.getSenderUri(mContext));
518            if (sender != null) {
519                Class<? extends android.app.Activity> setting = sender.getSettingActivityClass();
520                showOutgoing = (setting != null);
521            }
522        } catch (MessagingException me) {
523            // just leave showOutgoing as true - bias towards showing it, so user can fix it
524        }
525        if (showOutgoing) {
526            prefOutgoing.setOnPreferenceClickListener(
527                    new Preference.OnPreferenceClickListener() {
528                        public boolean onPreferenceClick(Preference preference) {
529                            mAccountDirty = true;
530                            mCallback.onOutgoingSettings(mAccount);
531                            return true;
532                        }
533                    });
534        } else {
535            PreferenceCategory serverCategory = (PreferenceCategory) findPreference(
536                    PREFERENCE_CATEGORY_SERVER);
537            serverCategory.removePreference(prefOutgoing);
538        }
539
540        mSyncContacts = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CONTACTS);
541        mSyncCalendar = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CALENDAR);
542        mSyncEmail = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_EMAIL);
543        if (mAccount.mHostAuthRecv.mProtocol.equals("eas")) {
544            android.accounts.Account acct = new android.accounts.Account(mAccount.mEmailAddress,
545                    Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
546            mSyncContacts.setChecked(ContentResolver
547                    .getSyncAutomatically(acct, ContactsContract.AUTHORITY));
548            mSyncContacts.setOnPreferenceChangeListener(mPreferenceChangeListener);
549            mSyncCalendar.setChecked(ContentResolver
550                    .getSyncAutomatically(acct, Calendar.AUTHORITY));
551            mSyncCalendar.setOnPreferenceChangeListener(mPreferenceChangeListener);
552            mSyncEmail.setChecked(ContentResolver
553                    .getSyncAutomatically(acct, EmailContent.AUTHORITY));
554            mSyncEmail.setOnPreferenceChangeListener(mPreferenceChangeListener);
555        } else {
556            PreferenceCategory serverCategory = (PreferenceCategory) findPreference(
557                    PREFERENCE_CATEGORY_SERVER);
558            serverCategory.removePreference(mSyncContacts);
559            serverCategory.removePreference(mSyncCalendar);
560            serverCategory.removePreference(mSyncEmail);
561        }
562
563        // Temporary home for delete account
564        Preference prefDeleteAccount = findPreference(PREFERENCE_DELETE_ACCOUNT);
565        prefDeleteAccount.setOnPreferenceClickListener(
566                new Preference.OnPreferenceClickListener() {
567                    public boolean onPreferenceClick(Preference preference) {
568                        DeleteAccountFragment dialogFragment = DeleteAccountFragment.newInstance(
569                                mAccount, AccountSettingsFragment.this);
570                        FragmentTransaction ft = getFragmentManager().beginTransaction();
571                        ft.addToBackStack(null);
572                        dialogFragment.show(ft, DeleteAccountFragment.TAG);
573                        return true;
574                    }
575                });
576    }
577
578    /**
579     * Generic onPreferenceChanged listener for the preferences (above) that just need
580     * to be written, without extra tweaks
581     */
582    private Preference.OnPreferenceChangeListener mPreferenceChangeListener =
583        new Preference.OnPreferenceChangeListener() {
584            public boolean onPreferenceChange(Preference preference, Object newValue) {
585                onPreferenceChanged();
586                return true;
587            }
588    };
589
590    /**
591     * Called any time a preference is changed.
592     */
593    private void onPreferenceChanged() {
594        mSaveOnExit = true;
595    }
596
597    /*
598     * Note: This writes the settings on the UI thread.  This has to be done so the settings are
599     * committed before we might be killed.
600     */
601    private void saveSettings() {
602        // Turn off all controlled flags - will turn them back on while checking UI elements
603        int newFlags = mAccount.getFlags() &
604                ~(Account.FLAGS_NOTIFY_NEW_MAIL |
605                        Account.FLAGS_VIBRATE_ALWAYS | Account.FLAGS_VIBRATE_WHEN_SILENT |
606                        Account.FLAGS_BACKGROUND_ATTACHMENTS);
607
608        newFlags |= mAccountBackgroundAttachments.isChecked() ?
609                Account.FLAGS_BACKGROUND_ATTACHMENTS : 0;
610        mAccount.setDefaultAccount(mAccountDefault.isChecked());
611        // If the display name has been cleared, we'll reset it to the default value (email addr)
612        mAccount.setDisplayName(mAccountDescription.getText().trim());
613        // The sender name must never be empty (this is enforced by the preference editor)
614        mAccount.setSenderName(mAccountName.getText().trim());
615        mAccount.setSignature(mAccountSignature.getText());
616        newFlags |= mAccountNotify.isChecked() ? Account.FLAGS_NOTIFY_NEW_MAIL : 0;
617        mAccount.setSyncInterval(Integer.parseInt(mCheckFrequency.getValue()));
618        if (mSyncWindow != null) {
619            mAccount.setSyncLookback(Integer.parseInt(mSyncWindow.getValue()));
620        }
621        if (mAccountVibrateWhen.getValue().equals(PREFERENCE_VALUE_VIBRATE_WHEN_ALWAYS)) {
622            newFlags |= Account.FLAGS_VIBRATE_ALWAYS;
623        } else if (mAccountVibrateWhen.getValue().equals(PREFERENCE_VALUE_VIBRATE_WHEN_SILENT)) {
624            newFlags |= Account.FLAGS_VIBRATE_WHEN_SILENT;
625        }
626        SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences();
627        mAccount.setRingtone(prefs.getString(PREFERENCE_RINGTONE, null));
628        mAccount.setFlags(newFlags);
629
630        if (mAccount.mHostAuthRecv.mProtocol.equals("eas")) {
631            android.accounts.Account acct = new android.accounts.Account(mAccount.mEmailAddress,
632                    Email.EXCHANGE_ACCOUNT_MANAGER_TYPE);
633            ContentResolver.setSyncAutomatically(acct, ContactsContract.AUTHORITY,
634                    mSyncContacts.isChecked());
635            ContentResolver.setSyncAutomatically(acct, Calendar.AUTHORITY,
636                    mSyncCalendar.isChecked());
637            ContentResolver.setSyncAutomatically(acct, EmailContent.AUTHORITY,
638                    mSyncEmail.isChecked());
639        }
640
641        // Commit the changes
642        // Note, this is done in the UI thread because at this point, we must commit
643        // all changes - any time after onPause completes, we could be killed.  This is analogous
644        // to the way that SharedPreferences tries to work off-thread in apply(), but will pause
645        // until completion in onPause().
646        ContentValues cv = AccountSettingsUtils.getAccountContentValues(mAccount);
647        mAccount.update(mContext, cv);
648
649        // Run the remaining changes off-thread
650        Email.setServicesEnabledAsync(mContext);
651    }
652
653    /**
654     * Dialog fragment to show "remove account?" dialog
655     */
656    public static class DeleteAccountFragment extends DialogFragment {
657        private final static String TAG = "DeleteAccountFragment";
658
659        // Argument bundle keys
660        private final static String BUNDLE_KEY_ACCOUNT_NAME = "DeleteAccountFragment.Name";
661
662        /**
663         * Create the dialog with parameters
664         */
665        public static DeleteAccountFragment newInstance(Account account, Fragment parentFragment) {
666            DeleteAccountFragment f = new DeleteAccountFragment();
667            Bundle b = new Bundle();
668            b.putString(BUNDLE_KEY_ACCOUNT_NAME, account.getDisplayName());
669            f.setArguments(b);
670            f.setTargetFragment(parentFragment, 0);
671            return f;
672        }
673
674        @Override
675        public Dialog onCreateDialog(Bundle savedInstanceState) {
676            Context context = getActivity();
677            final String name = getArguments().getString(BUNDLE_KEY_ACCOUNT_NAME);
678
679            return new AlertDialog.Builder(context)
680                .setIconAttribute(android.R.attr.alertDialogIcon)
681                .setTitle(R.string.account_delete_dlg_title)
682                .setMessage(context.getString(R.string.account_delete_dlg_instructions_fmt, name))
683                .setPositiveButton(
684                        R.string.okay_action,
685                        new DialogInterface.OnClickListener() {
686                            public void onClick(DialogInterface dialog, int whichButton) {
687                                Fragment f = getTargetFragment();
688                                if (f instanceof AccountSettingsFragment) {
689                                    ((AccountSettingsFragment)f).finishDeleteAccount();
690                                }
691                                dismiss();
692                            }
693                        })
694                .setNegativeButton(
695                        R.string.cancel_action,
696                        new DialogInterface.OnClickListener() {
697                            public void onClick(DialogInterface dialog, int whichButton) {
698                                dismiss();
699                            }
700                        })
701                .create();
702        }
703    }
704
705    /**
706     * Callback from delete account dialog - passes the delete command up to the activity
707     */
708    private void finishDeleteAccount() {
709        mSaveOnExit = false;
710        mCallback.deleteAccount(mAccount);
711    }
712}
713