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