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