AccountSettingsFragment.java revision 9c455277797a3564fea2a85d0765509f1dd1a45c
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.content.ContentResolver;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.Intent;
24import android.content.res.Resources;
25import android.database.Cursor;
26import android.media.Ringtone;
27import android.media.RingtoneManager;
28import android.net.Uri;
29import android.os.AsyncTask;
30import android.os.Bundle;
31import android.os.Vibrator;
32import android.preference.CheckBoxPreference;
33import android.preference.EditTextPreference;
34import android.preference.ListPreference;
35import android.preference.Preference;
36import android.preference.PreferenceCategory;
37import android.preference.Preference.OnPreferenceClickListener;
38import android.provider.CalendarContract;
39import android.provider.ContactsContract;
40import android.provider.Settings;
41import android.text.TextUtils;
42import android.view.Menu;
43import android.view.MenuInflater;
44
45import com.android.email.R;
46import com.android.email.SecurityPolicy;
47import com.android.email.provider.EmailProvider;
48import com.android.email.provider.FolderPickerActivity;
49import com.android.email.service.EmailServiceUtils;
50import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
51import com.android.email2.ui.MailActivityEmail;
52import com.android.emailcommon.Logging;
53import com.android.emailcommon.provider.Account;
54import com.android.emailcommon.provider.EmailContent;
55import com.android.emailcommon.provider.HostAuth;
56import com.android.emailcommon.provider.Mailbox;
57import com.android.emailcommon.provider.Policy;
58import com.android.emailcommon.utility.Utility;
59import com.android.mail.preferences.AccountPreferences;
60import com.android.mail.preferences.FolderPreferences;
61import com.android.mail.providers.Folder;
62import com.android.mail.providers.UIProvider;
63import com.android.mail.ui.settings.SettingsUtils;
64import com.android.mail.utils.LogUtils;
65import com.android.mail.utils.NotificationUtils;
66
67import java.util.ArrayList;
68
69/**
70 * Fragment containing the main logic for account settings.  This also calls out to other
71 * fragments for server settings.
72 *
73 * TODO: Remove or make async the mAccountDirty reload logic.  Probably no longer needed.
74 * TODO: Can we defer calling addPreferencesFromResource() until after we load the account?  This
75 *       could reduce flicker.
76 */
77public class AccountSettingsFragment extends EmailPreferenceFragment
78        implements Preference.OnPreferenceChangeListener {
79
80    // Keys used for arguments bundle
81    private static final String BUNDLE_KEY_ACCOUNT_ID = "AccountSettingsFragment.AccountId";
82    private static final String BUNDLE_KEY_ACCOUNT_EMAIL = "AccountSettingsFragment.Email";
83
84    public static final String PREFERENCE_DESCRIPTION = "account_description";
85    private static final String PREFERENCE_NAME = "account_name";
86    private static final String PREFERENCE_SIGNATURE = "account_signature";
87    private static final String PREFERENCE_QUICK_RESPONSES = "account_quick_responses";
88    private static final String PREFERENCE_FREQUENCY = "account_check_frequency";
89    private static final String PREFERENCE_BACKGROUND_ATTACHMENTS =
90            "account_background_attachments";
91    private static final String PREFERENCE_CATEGORY_DATA_USAGE = "data_usage";
92    private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "account_notifications";
93    private static final String PREFERENCE_CATEGORY_SERVER = "account_servers";
94    private static final String PREFERENCE_CATEGORY_POLICIES = "account_policies";
95    private static final String PREFERENCE_POLICIES_ENFORCED = "policies_enforced";
96    private static final String PREFERENCE_POLICIES_UNSUPPORTED = "policies_unsupported";
97    private static final String PREFERENCE_POLICIES_RETRY_ACCOUNT = "policies_retry_account";
98    private static final String PREFERENCE_INCOMING = "incoming";
99    private static final String PREFERENCE_OUTGOING = "outgoing";
100    private static final String PREFERENCE_SYNC_CONTACTS = "account_sync_contacts";
101    private static final String PREFERENCE_SYNC_CALENDAR = "account_sync_calendar";
102    private static final String PREFERENCE_SYNC_EMAIL = "account_sync_email";
103
104    private static final String PREFERENCE_SYSTEM_FOLDERS = "system_folders";
105    private static final String PREFERENCE_SYSTEM_FOLDERS_TRASH = "system_folders_trash";
106    private static final String PREFERENCE_SYSTEM_FOLDERS_SENT = "system_folders_sent";
107
108    // Request code to start different activities.
109    private static final int RINGTONE_REQUEST_CODE = 0;
110
111    private EditTextPreference mAccountDescription;
112    private EditTextPreference mAccountName;
113    private EditTextPreference mAccountSignature;
114    private ListPreference mCheckFrequency;
115    private ListPreference mSyncWindow;
116    private CheckBoxPreference mAccountBackgroundAttachments;
117    private CheckBoxPreference mInboxNotify;
118    private CheckBoxPreference mInboxVibrate;
119    private Preference mInboxRingtone;
120    private PreferenceCategory mNotificationsCategory;
121    private CheckBoxPreference mSyncContacts;
122    private CheckBoxPreference mSyncCalendar;
123    private CheckBoxPreference mSyncEmail;
124
125    private Context mContext;
126    private Account mAccount;
127    private boolean mAccountDirty;
128    private Callback mCallback = EmptyCallback.INSTANCE;
129    private boolean mStarted;
130    private boolean mLoaded;
131    private boolean mSaveOnExit;
132
133    private Ringtone mRingtone;
134
135    private AccountPreferences mAccountPreferences;
136    private FolderPreferences mInboxFolderPreferences;
137
138    /** The e-mail of the account being edited. */
139    private String mAccountEmail;
140
141    // Async Tasks
142    private AsyncTask<?,?,?> mLoadAccountTask;
143
144    /**
145     * Callback interface that owning activities must provide
146     */
147    public interface Callback {
148        public void onSettingsChanged(Account account, String preference, Object value);
149        public void onEditQuickResponses(Account account);
150        public void onIncomingSettings(Account account);
151        public void onOutgoingSettings(Account account);
152        public void abandonEdit();
153    }
154
155    private static class EmptyCallback implements Callback {
156        public static final Callback INSTANCE = new EmptyCallback();
157        @Override public void onSettingsChanged(Account account, String preference, Object value) {}
158        @Override public void onEditQuickResponses(Account account) {}
159        @Override public void onIncomingSettings(Account account) {}
160        @Override public void onOutgoingSettings(Account account) {}
161        @Override public void abandonEdit() {}
162    }
163
164    /**
165     * If launching with an arguments bundle, use this method to build the arguments.
166     */
167    public static Bundle buildArguments(long accountId, String email) {
168        Bundle b = new Bundle();
169        b.putLong(BUNDLE_KEY_ACCOUNT_ID, accountId);
170        b.putString(BUNDLE_KEY_ACCOUNT_EMAIL, email);
171        return b;
172    }
173
174    public static String getTitleFromArgs(Bundle args) {
175        return (args == null) ? null : args.getString(BUNDLE_KEY_ACCOUNT_EMAIL);
176    }
177
178    @Override
179    public void onAttach(Activity activity) {
180        super.onAttach(activity);
181        mContext = activity;
182    }
183
184    /**
185     * Called to do initial creation of a fragment.  This is called after
186     * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
187     */
188    @Override
189    public void onCreate(Bundle savedInstanceState) {
190        if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) {
191            LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onCreate");
192        }
193        super.onCreate(savedInstanceState);
194
195        // Load the preferences from an XML resource
196        addPreferencesFromResource(R.xml.account_settings_preferences);
197
198        // Start loading the account data, if provided in the arguments
199        // If not, activity must call startLoadingAccount() directly
200        Bundle b = getArguments();
201        if (b != null) {
202            long accountId = b.getLong(BUNDLE_KEY_ACCOUNT_ID, -1);
203            mAccountEmail = b.getString(BUNDLE_KEY_ACCOUNT_EMAIL);
204            if (accountId >= 0 && !mLoaded) {
205                startLoadingAccount(accountId);
206            }
207        }
208
209        mAccountDirty = false;
210    }
211
212    @Override
213    public void onActivityCreated(Bundle savedInstanceState) {
214        if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) {
215            LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onActivityCreated");
216        }
217        super.onActivityCreated(savedInstanceState);
218    }
219
220    /**
221     * Called when the Fragment is visible to the user.
222     */
223    @Override
224    public void onStart() {
225        if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) {
226            LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onStart");
227        }
228        super.onStart();
229        mStarted = true;
230
231        // If the loaded account is ready now, load the UI
232        if (mAccount != null && !mLoaded) {
233            loadSettings();
234        }
235    }
236
237    /**
238     * Called when the fragment is visible to the user and actively running.
239     * TODO: Don't read account data on UI thread.  This should be fixed by removing the need
240     * to do this, not by spinning up yet another thread.
241     */
242    @Override
243    public void onResume() {
244        if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) {
245            LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onResume");
246        }
247        super.onResume();
248
249        if (mAccountDirty) {
250            // if we are coming back from editing incoming or outgoing settings,
251            // we need to refresh them here so we don't accidentally overwrite the
252            // old values we're still holding here
253            mAccount.mHostAuthRecv =
254                HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
255            mAccount.mHostAuthSend =
256                HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeySend);
257            // Because "delete policy" UI is on edit incoming settings, we have
258            // to refresh that as well.
259            Account refreshedAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
260            if (refreshedAccount == null || mAccount.mHostAuthRecv == null) {
261                mSaveOnExit = false;
262                mCallback.abandonEdit();
263                return;
264            }
265            mAccount.setDeletePolicy(refreshedAccount.getDeletePolicy());
266            mAccountDirty = false;
267        }
268    }
269
270    @Override
271    public void onPause() {
272        if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) {
273            LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onPause");
274        }
275        super.onPause();
276        if (mSaveOnExit) {
277            saveSettings();
278        }
279    }
280
281    /**
282     * Called when the Fragment is no longer started.
283     */
284    @Override
285    public void onStop() {
286        if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) {
287            LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onStop");
288        }
289        super.onStop();
290        mStarted = false;
291    }
292
293    @Override
294    public void onActivityResult(int requestCode, int resultCode, Intent data) {
295        switch (requestCode) {
296            case RINGTONE_REQUEST_CODE:
297                if (resultCode == Activity.RESULT_OK && data != null) {
298                    Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
299                    setRingtone(uri);
300                }
301                break;
302        }
303    }
304
305    /**
306     * Sets the current ringtone.
307     */
308    private void setRingtone(Uri ringtone) {
309        if (ringtone != null) {
310            mInboxFolderPreferences.setNotificationRingtoneUri(ringtone.toString());
311            mRingtone = RingtoneManager.getRingtone(getActivity(), ringtone);
312        } else {
313            // Null means silent was selected.
314            mInboxFolderPreferences.setNotificationRingtoneUri("");
315            mRingtone = null;
316        }
317
318        setRingtoneSummary();
319    }
320
321    private void setRingtoneSummary() {
322        final String summary = mRingtone != null ? mRingtone.getTitle(mContext)
323                : mContext.getString(R.string.silent_ringtone);
324
325        mInboxRingtone.setSummary(summary);
326    }
327
328    /**
329     * Listen to all preference changes in this class.
330     * @param preference
331     * @param newValue
332     * @return
333     */
334    @Override
335    public boolean onPreferenceChange(Preference preference, Object newValue){
336        // Can't use a switch here. Falling back to a giant conditional.
337        final String key = preference.getKey();
338        if (key.equals(PREFERENCE_DESCRIPTION)){
339            String summary = newValue.toString().trim();
340            if (TextUtils.isEmpty(summary)) {
341                summary = mAccount.mEmailAddress;
342            }
343            mAccountDescription.setSummary(summary);
344            mAccountDescription.setText(summary);
345            preferenceChanged(PREFERENCE_DESCRIPTION, summary);
346            return false;
347        } else if (key.equals(PREFERENCE_FREQUENCY)) {
348            final String summary = newValue.toString();
349            final int index = mCheckFrequency.findIndexOfValue(summary);
350            mCheckFrequency.setSummary(mCheckFrequency.getEntries()[index]);
351            mCheckFrequency.setValue(summary);
352            preferenceChanged(PREFERENCE_FREQUENCY, newValue);
353            return false;
354        } else if (key.equals(PREFERENCE_SIGNATURE)) {
355            // Clean up signature if it's only whitespace (which is easy to do on a
356            // soft keyboard) but leave whitespace in place otherwise, to give the user
357            // maximum flexibility, e.g. the ability to indent
358            String signature = newValue.toString();
359            if (signature.trim().isEmpty()) {
360                signature = "";
361            }
362            mAccountSignature.setText(signature);
363            SettingsUtils.updatePreferenceSummary(mAccountSignature, signature,
364                    R.string.preferences_signature_summary_not_set);
365            preferenceChanged(PREFERENCE_SIGNATURE, signature);
366            return false;
367        } else if (key.equals(PREFERENCE_NAME)) {
368            final String summary = newValue.toString().trim();
369            if (!TextUtils.isEmpty(summary)) {
370                mAccountName.setSummary(summary);
371                mAccountName.setText(summary);
372                preferenceChanged(PREFERENCE_NAME, summary);
373            }
374            return false;
375        } else if (FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE.equals(key)) {
376            final boolean vibrateSetting = (Boolean) newValue;
377            mInboxVibrate.setChecked(vibrateSetting);
378            mInboxFolderPreferences.setNotificationVibrateEnabled(vibrateSetting);
379            preferenceChanged(FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE, newValue);
380            return true;
381        } else if (FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED.equals(key)) {
382            mInboxFolderPreferences.setNotificationsEnabled((Boolean) newValue);
383            preferenceChanged(FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED, newValue);
384            return true;
385        } else {
386            // Default behavior, just indicate that the preferences were written
387            preferenceChanged(key, newValue);
388            return true;
389        }
390    }
391
392    /**
393     * Called when the fragment is no longer in use.
394     */
395    @Override
396    public void onDestroy() {
397        if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) {
398            LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onDestroy");
399        }
400        super.onDestroy();
401
402        Utility.cancelTaskInterrupt(mLoadAccountTask);
403        mLoadAccountTask = null;
404    }
405
406    @Override
407    public void onSaveInstanceState(Bundle outState) {
408        if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) {
409            LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onSaveInstanceState");
410        }
411        super.onSaveInstanceState(outState);
412    }
413
414    @Override
415    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
416        menu.clear();
417        inflater.inflate(R.menu.settings_fragment_menu, menu);
418    }
419
420    /**
421     * Activity provides callbacks here
422     */
423    public void setCallback(Callback callback) {
424        mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback;
425    }
426
427    /**
428     * Start loading a single account in preparation for editing it
429     */
430    public void startLoadingAccount(long accountId) {
431        Utility.cancelTaskInterrupt(mLoadAccountTask);
432        mLoadAccountTask = new LoadAccountTask().executeOnExecutor(
433                AsyncTask.THREAD_POOL_EXECUTOR, accountId);
434    }
435
436    /**
437     * Async task to load account in order to view/edit it
438     */
439    private class LoadAccountTask extends AsyncTask<Long, Void, Account> {
440        @Override
441        protected Account doInBackground(Long... params) {
442            long accountId = params[0];
443            Account account = Account.restoreAccountWithId(mContext, accountId);
444            if (account != null) {
445                account.mHostAuthRecv =
446                    HostAuth.restoreHostAuthWithId(mContext, account.mHostAuthKeyRecv);
447                account.mHostAuthSend =
448                    HostAuth.restoreHostAuthWithId(mContext, account.mHostAuthKeySend);
449                if (account.mHostAuthRecv == null) {
450                    account = null;
451                }
452            }
453            return account;
454        }
455
456        @Override
457        protected void onPostExecute(Account account) {
458            if (!isCancelled()) {
459                if (account == null) {
460                    mSaveOnExit = false;
461                    mCallback.abandonEdit();
462                } else {
463                    mAccount = account;
464                    if (mStarted && !mLoaded) {
465                        loadSettings();
466                    }
467                }
468            }
469        }
470    }
471
472    /**
473     * From a Policy, create and return an ArrayList of Strings that describe (simply) those
474     * policies that are supported by the OS.  At the moment, the strings are simple (e.g.
475     * "password required"); we should probably add more information (# characters, etc.), though
476     */
477    private ArrayList<String> getSystemPoliciesList(Policy policy) {
478        Resources res = mContext.getResources();
479        ArrayList<String> policies = new ArrayList<String>();
480        if (policy.mPasswordMode != Policy.PASSWORD_MODE_NONE) {
481            policies.add(res.getString(R.string.policy_require_password));
482        }
483        if (policy.mPasswordHistory > 0) {
484            policies.add(res.getString(R.string.policy_password_history));
485        }
486        if (policy.mPasswordExpirationDays > 0) {
487            policies.add(res.getString(R.string.policy_password_expiration));
488        }
489        if (policy.mMaxScreenLockTime > 0) {
490            policies.add(res.getString(R.string.policy_screen_timeout));
491        }
492        if (policy.mDontAllowCamera) {
493            policies.add(res.getString(R.string.policy_dont_allow_camera));
494        }
495        if (policy.mMaxEmailLookback != 0) {
496            policies.add(res.getString(R.string.policy_email_age));
497        }
498        if (policy.mMaxCalendarLookback != 0) {
499            policies.add(res.getString(R.string.policy_calendar_age));
500        }
501        return policies;
502    }
503
504    private void setPolicyListSummary(ArrayList<String> policies, String policiesToAdd,
505            String preferenceName) {
506        Policy.addPolicyStringToList(policiesToAdd, policies);
507        if (policies.size() > 0) {
508            Preference p = findPreference(preferenceName);
509            StringBuilder sb = new StringBuilder();
510            for (String desc: policies) {
511                sb.append(desc);
512                sb.append('\n');
513            }
514            p.setSummary(sb.toString());
515        }
516    }
517
518    /**
519     * Loads settings that are dependent on a {@link com.android.mail.providers.Account}, which
520     * must be obtained off the main thread. This will also call {@link #loadMainThreadSettings()}.
521     */
522    private void loadSettingsOffMainThread() {
523        new Thread(new Runnable() {
524            @Override
525            public void run() {
526                final Cursor accountCursor = mContext.getContentResolver().query(EmailProvider
527                        .uiUri("uiaccount", mAccount.mId), UIProvider.ACCOUNTS_PROJECTION, null,
528                        null, null);
529                if (accountCursor == null) {
530                    return;
531                }
532
533                final com.android.mail.providers.Account account;
534                try {
535                    if (accountCursor.moveToFirst()) {
536                        account = new com.android.mail.providers.Account(accountCursor);
537                    } else {
538                        return;
539                    }
540                } finally {
541                    accountCursor.close();
542                }
543
544                final Cursor folderCursor = mContext.getContentResolver().query(
545                        account.settings.defaultInbox, UIProvider.FOLDERS_PROJECTION, null, null,
546                        null);
547                if (folderCursor == null) {
548                    return;
549                }
550
551                final Folder folder;
552                try {
553                    if (folderCursor.moveToFirst()) {
554                        folder = new Folder(folderCursor);
555                    } else {
556                        return;
557                    }
558                } finally {
559                    folderCursor.close();
560                }
561
562                mAccountPreferences = new AccountPreferences(mContext, account.name);
563                mInboxFolderPreferences =
564                        new FolderPreferences(mContext, account.name, folder, true);
565
566                NotificationUtils.moveNotificationSetting(
567                        mAccountPreferences, mInboxFolderPreferences);
568
569                final String ringtoneUri = mInboxFolderPreferences.getNotificationRingtoneUri();
570                if (!TextUtils.isEmpty(ringtoneUri)) {
571                    mRingtone = RingtoneManager.getRingtone(getActivity(), Uri.parse(ringtoneUri));
572                }
573
574                final Activity activity = getActivity();
575                if (activity != null) {
576                    activity.runOnUiThread(new Runnable() {
577                        @Override
578                        public void run() {
579                            mInboxNotify.setChecked(
580                                    mInboxFolderPreferences.areNotificationsEnabled());
581                            mInboxVibrate.setChecked(
582                                    mInboxFolderPreferences.isNotificationVibrateEnabled());
583                            setRingtoneSummary();
584                            // Notification preferences must be disabled until after
585                            // mInboxFolderPreferences is available, so enable them here.
586                            mNotificationsCategory.setEnabled(true);
587                        }
588                    });
589                }
590            }
591        }).start();
592    }
593
594    /**
595     * Load account data into preference UI. This must be called on the main thread.
596     */
597    private void loadSettings() {
598        // We can only do this once, so prevent repeat
599        mLoaded = true;
600        // Once loaded the data is ready to be saved, as well
601        mSaveOnExit = false;
602
603        loadSettingsOffMainThread();
604
605        mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION);
606        mAccountDescription.setSummary(mAccount.getDisplayName());
607        mAccountDescription.setText(mAccount.getDisplayName());
608        mAccountDescription.setOnPreferenceChangeListener(this);
609
610        mAccountName = (EditTextPreference) findPreference(PREFERENCE_NAME);
611        String senderName = mAccount.getSenderName();
612        // In rare cases, sendername will be null;  Change this to empty string to avoid NPE's
613        if (senderName == null) senderName = "";
614        mAccountName.setSummary(senderName);
615        mAccountName.setText(senderName);
616        mAccountName.setOnPreferenceChangeListener(this);
617
618        final String accountSignature = mAccount.getSignature();
619        mAccountSignature = (EditTextPreference) findPreference(PREFERENCE_SIGNATURE);
620        mAccountSignature.setText(accountSignature);
621        mAccountSignature.setOnPreferenceChangeListener(this);
622        SettingsUtils.updatePreferenceSummary(mAccountSignature, accountSignature,
623                R.string.preferences_signature_summary_not_set);
624
625
626        mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
627        final String protocol = Account.getProtocol(mContext, mAccount.mId);
628        final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mContext, protocol);
629        mCheckFrequency.setEntries(info.syncIntervalStrings);
630        mCheckFrequency.setEntryValues(info.syncIntervals);
631        mCheckFrequency.setValue(String.valueOf(mAccount.getSyncInterval()));
632        mCheckFrequency.setSummary(mCheckFrequency.getEntry());
633        mCheckFrequency.setOnPreferenceChangeListener(this);
634
635        findPreference(PREFERENCE_QUICK_RESPONSES).setOnPreferenceClickListener(
636                new Preference.OnPreferenceClickListener() {
637                    @Override
638                    public boolean onPreferenceClick(Preference preference) {
639                        mAccountDirty = true;
640                        mCallback.onEditQuickResponses(mAccount);
641                        return true;
642                    }
643                });
644
645        // Add check window preference
646        PreferenceCategory dataUsageCategory =
647                (PreferenceCategory) findPreference(PREFERENCE_CATEGORY_DATA_USAGE);
648
649        final Policy policy;
650        if (mAccount.mPolicyKey != 0) {
651            // Make sure we have most recent data from account
652            mAccount.refresh(mContext);
653            policy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey);
654            if (policy == null) {
655                // The account has been deleted?  Crazy, but not impossible
656                return;
657            }
658        } else {
659            policy = null;
660        }
661
662        mSyncWindow = null;
663        if (info.offerLookback) {
664            mSyncWindow = new ListPreference(mContext);
665            mSyncWindow.setTitle(R.string.account_setup_options_mail_window_label);
666            mSyncWindow.setValue(String.valueOf(mAccount.getSyncLookback()));
667            final int maxLookback;
668            if (policy != null) {
669                maxLookback = policy.mMaxEmailLookback;
670            } else {
671                maxLookback = 0;
672            }
673
674            MailboxSettings.setupLookbackPreferenceOptions(mContext, mSyncWindow, maxLookback,
675                    false);
676
677            // Must correspond to the hole in the XML file that's reserved.
678            mSyncWindow.setOrder(2);
679            mSyncWindow.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
680                @Override
681                public boolean onPreferenceChange(Preference preference, Object newValue) {
682                    final String summary = newValue.toString();
683                    int index = mSyncWindow.findIndexOfValue(summary);
684                    mSyncWindow.setSummary(mSyncWindow.getEntries()[index]);
685                    mSyncWindow.setValue(summary);
686                    preferenceChanged(preference.getKey(), newValue);
687                    return false;
688                }
689            });
690            dataUsageCategory.addPreference(mSyncWindow);
691        }
692
693        PreferenceCategory folderPrefs =
694                (PreferenceCategory) findPreference(PREFERENCE_SYSTEM_FOLDERS);
695        if (info.requiresSetup) {
696            Preference trashPreference = findPreference(PREFERENCE_SYSTEM_FOLDERS_TRASH);
697            Intent i = new Intent(mContext, FolderPickerActivity.class);
698            Uri uri = EmailContent.CONTENT_URI.buildUpon().appendQueryParameter(
699                    "account", Long.toString(mAccount.mId)).build();
700            i.setData(uri);
701            i.putExtra(FolderPickerActivity.MAILBOX_TYPE_EXTRA, Mailbox.TYPE_TRASH);
702            trashPreference.setIntent(i);
703
704            Preference sentPreference = findPreference(PREFERENCE_SYSTEM_FOLDERS_SENT);
705            i = new Intent(mContext, FolderPickerActivity.class);
706            i.setData(uri);
707            i.putExtra(FolderPickerActivity.MAILBOX_TYPE_EXTRA, Mailbox.TYPE_SENT);
708            sentPreference.setIntent(i);
709        } else {
710            getPreferenceScreen().removePreference(folderPrefs);
711        }
712
713        mAccountBackgroundAttachments = (CheckBoxPreference)
714                findPreference(PREFERENCE_BACKGROUND_ATTACHMENTS);
715        if (!info.offerAttachmentPreload) {
716            dataUsageCategory.removePreference(mAccountBackgroundAttachments);
717        } else {
718            mAccountBackgroundAttachments.setChecked(
719                    0 != (mAccount.getFlags() & Account.FLAGS_BACKGROUND_ATTACHMENTS));
720            mAccountBackgroundAttachments.setOnPreferenceChangeListener(this);
721        }
722
723        mInboxNotify = (CheckBoxPreference) findPreference(
724                FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED);
725        mInboxNotify.setOnPreferenceChangeListener(this);
726
727        mInboxRingtone = findPreference(FolderPreferences.PreferenceKeys.NOTIFICATION_RINGTONE);
728        mInboxRingtone.setOnPreferenceChangeListener(this);
729        mInboxRingtone.setOnPreferenceClickListener(new OnPreferenceClickListener() {
730            @Override
731            public boolean onPreferenceClick(final Preference preference) {
732                showRingtonePicker();
733
734                return true;
735            }
736        });
737
738        mNotificationsCategory =
739                (PreferenceCategory) findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS);
740
741        // Set the vibrator value, or hide it on devices w/o a vibrator
742        mInboxVibrate = (CheckBoxPreference) findPreference(
743                FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE);
744        Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
745        if (vibrator.hasVibrator()) {
746            // Checked state will be set when we obtain it in #loadSettingsOffMainThread()
747
748            // When the value is changed, update the setting.
749            mInboxVibrate.setOnPreferenceChangeListener(this);
750        } else {
751            // No vibrator present. Remove the preference altogether.
752            mNotificationsCategory.removePreference(mInboxVibrate);
753        }
754
755        final Preference retryAccount = findPreference(PREFERENCE_POLICIES_RETRY_ACCOUNT);
756        final PreferenceCategory policiesCategory = (PreferenceCategory) findPreference(
757                PREFERENCE_CATEGORY_POLICIES);
758        if (policy != null) {
759            if (policy.mProtocolPoliciesEnforced != null) {
760                ArrayList<String> policies = getSystemPoliciesList(policy);
761                setPolicyListSummary(policies, policy.mProtocolPoliciesEnforced,
762                        PREFERENCE_POLICIES_ENFORCED);
763            }
764            if (policy.mProtocolPoliciesUnsupported != null) {
765                ArrayList<String> policies = new ArrayList<String>();
766                setPolicyListSummary(policies, policy.mProtocolPoliciesUnsupported,
767                        PREFERENCE_POLICIES_UNSUPPORTED);
768            } else {
769                // Don't show "retry" unless we have unsupported policies
770                policiesCategory.removePreference(retryAccount);
771            }
772        } else {
773            // Remove the category completely if there are no policies
774            getPreferenceScreen().removePreference(policiesCategory);
775        }
776
777        retryAccount.setOnPreferenceClickListener(
778                new Preference.OnPreferenceClickListener() {
779                    @Override
780                    public boolean onPreferenceClick(Preference preference) {
781                        // Release the account
782                        SecurityPolicy.setAccountHoldFlag(mContext, mAccount, false);
783                        // Remove the preference
784                        policiesCategory.removePreference(retryAccount);
785                        return true;
786                    }
787                });
788        findPreference(PREFERENCE_INCOMING).setOnPreferenceClickListener(
789                new Preference.OnPreferenceClickListener() {
790                    @Override
791                    public boolean onPreferenceClick(Preference preference) {
792                        mAccountDirty = true;
793                        mCallback.onIncomingSettings(mAccount);
794                        return true;
795                    }
796                });
797
798        // Hide the outgoing account setup link if it's not activated
799        Preference prefOutgoing = findPreference(PREFERENCE_OUTGOING);
800        if (info.usesSmtp && mAccount.mHostAuthSend != null) {
801            prefOutgoing.setOnPreferenceClickListener(
802                    new Preference.OnPreferenceClickListener() {
803                        @Override
804                        public boolean onPreferenceClick(Preference preference) {
805                            mAccountDirty = true;
806                            mCallback.onOutgoingSettings(mAccount);
807                            return true;
808                        }
809                    });
810        } else {
811            if (info.usesSmtp) {
812                // We really ought to have an outgoing host auth but we don't.
813                // There's nothing we can do at this point, so just log the error.
814                LogUtils.e(Logging.LOG_TAG, "Account %d has a bad outbound hostauth", mAccount.mId);
815            }
816            PreferenceCategory serverCategory = (PreferenceCategory) findPreference(
817                    PREFERENCE_CATEGORY_SERVER);
818            serverCategory.removePreference(prefOutgoing);
819        }
820
821        mSyncContacts = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CONTACTS);
822        mSyncCalendar = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CALENDAR);
823        mSyncEmail = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_EMAIL);
824        if (info.syncContacts || info.syncCalendar) {
825            android.accounts.Account acct = new android.accounts.Account(mAccount.mEmailAddress,
826                    info.accountType);
827            if (info.syncContacts) {
828                mSyncContacts.setChecked(ContentResolver
829                        .getSyncAutomatically(acct, ContactsContract.AUTHORITY));
830                mSyncContacts.setOnPreferenceChangeListener(this);
831            } else {
832                mSyncContacts.setChecked(false);
833                mSyncContacts.setEnabled(false);
834            }
835            if (info.syncCalendar) {
836                mSyncCalendar.setChecked(ContentResolver
837                        .getSyncAutomatically(acct, CalendarContract.AUTHORITY));
838                mSyncCalendar.setOnPreferenceChangeListener(this);
839            } else {
840                mSyncCalendar.setChecked(false);
841                mSyncCalendar.setEnabled(false);
842            }
843            mSyncEmail.setChecked(ContentResolver
844                    .getSyncAutomatically(acct, EmailContent.AUTHORITY));
845            mSyncEmail.setOnPreferenceChangeListener(this);
846        } else {
847            dataUsageCategory.removePreference(mSyncContacts);
848            dataUsageCategory.removePreference(mSyncCalendar);
849            dataUsageCategory.removePreference(mSyncEmail);
850        }
851    }
852
853    /**
854     * Called any time a preference is changed.
855     */
856    private void preferenceChanged(String preference, Object value) {
857        mCallback.onSettingsChanged(mAccount, preference, value);
858        mSaveOnExit = true;
859    }
860
861    /*
862     * Note: This writes the settings on the UI thread.  This has to be done so the settings are
863     * committed before we might be killed.
864     */
865    private void saveSettings() {
866        // Turn off all controlled flags - will turn them back on while checking UI elements
867        int newFlags = mAccount.getFlags() & ~(Account.FLAGS_BACKGROUND_ATTACHMENTS);
868
869        newFlags |= mAccountBackgroundAttachments.isChecked() ?
870                Account.FLAGS_BACKGROUND_ATTACHMENTS : 0;
871        // If the display name has been cleared, we'll reset it to the default value (email addr)
872        mAccount.setDisplayName(mAccountDescription.getText().trim());
873        // The sender name must never be empty (this is enforced by the preference editor)
874        mAccount.setSenderName(mAccountName.getText().trim());
875        mAccount.setSignature(mAccountSignature.getText());
876        mAccount.setSyncInterval(Integer.parseInt(mCheckFrequency.getValue()));
877        if (mSyncWindow != null) {
878            mAccount.setSyncLookback(Integer.parseInt(mSyncWindow.getValue()));
879        }
880        mAccount.setFlags(newFlags);
881
882        EmailServiceInfo info =
883                EmailServiceUtils.getServiceInfo(mContext, mAccount.getProtocol(mContext));
884        if (info.syncContacts || info.syncCalendar) {
885            android.accounts.Account acct = new android.accounts.Account(mAccount.mEmailAddress,
886                    info.accountType);
887            ContentResolver.setSyncAutomatically(acct, ContactsContract.AUTHORITY,
888                    mSyncContacts.isChecked());
889            ContentResolver.setSyncAutomatically(acct, CalendarContract.AUTHORITY,
890                    mSyncCalendar.isChecked());
891            ContentResolver.setSyncAutomatically(acct, EmailContent.AUTHORITY,
892                    mSyncEmail.isChecked());
893        }
894
895        // Commit the changes
896        // Note, this is done in the UI thread because at this point, we must commit
897        // all changes - any time after onPause completes, we could be killed.  This is analogous
898        // to the way that SharedPreferences tries to work off-thread in apply(), but will pause
899        // until completion in onPause().
900        ContentValues cv = AccountSettingsUtils.getAccountContentValues(mAccount);
901        mAccount.update(mContext, cv);
902
903        // Run the remaining changes off-thread
904        MailActivityEmail.setServicesEnabledAsync(mContext);
905    }
906
907    public String getAccountEmail() {
908        // Get the e-mail address of the account being editted, if this is for an existing account.
909        return mAccountEmail;
910    }
911
912    /**
913     * Shows the system ringtone picker.
914     */
915    private void showRingtonePicker() {
916        Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
917        final String ringtoneUri = mInboxFolderPreferences.getNotificationRingtoneUri();
918        if (!TextUtils.isEmpty(ringtoneUri)) {
919            intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(ringtoneUri));
920        }
921        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
922        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
923                Settings.System.DEFAULT_NOTIFICATION_URI);
924        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
925        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
926        startActivityForResult(intent, RINGTONE_REQUEST_CODE);
927    }
928}
929