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