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