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