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