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