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.LoaderManager;
21import android.content.ContentResolver;
22import android.content.ContentValues;
23import android.content.Context;
24import android.content.Intent;
25import android.content.Loader;
26import android.content.res.Resources;
27import android.database.Cursor;
28import android.media.Ringtone;
29import android.media.RingtoneManager;
30import android.net.Uri;
31import android.os.Bundle;
32import android.os.Vibrator;
33import android.preference.CheckBoxPreference;
34import android.preference.EditTextPreference;
35import android.preference.ListPreference;
36import android.preference.Preference;
37import android.preference.PreferenceActivity;
38import android.preference.PreferenceCategory;
39import android.preference.Preference.OnPreferenceClickListener;
40import android.preference.PreferenceScreen;
41import android.provider.CalendarContract;
42import android.provider.ContactsContract;
43import android.provider.Settings;
44import android.support.annotation.NonNull;
45import android.text.TextUtils;
46import android.view.Menu;
47import android.view.MenuInflater;
48
49import com.android.email.R;
50import com.android.email.SecurityPolicy;
51import com.android.email.provider.EmailProvider;
52import com.android.email.provider.FolderPickerActivity;
53import com.android.email.service.EmailServiceUtils;
54import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
55import com.android.email2.ui.MailActivityEmail;
56import com.android.emailcommon.provider.Account;
57import com.android.emailcommon.provider.EmailContent;
58import com.android.emailcommon.provider.EmailContent.AccountColumns;
59import com.android.emailcommon.provider.Mailbox;
60import com.android.emailcommon.provider.Policy;
61import com.android.mail.preferences.AccountPreferences;
62import com.android.mail.preferences.FolderPreferences;
63import com.android.mail.providers.Folder;
64import com.android.mail.providers.UIProvider;
65import com.android.mail.ui.MailAsyncTaskLoader;
66import com.android.mail.ui.settings.MailAccountPrefsFragment;
67import com.android.mail.ui.settings.SettingsUtils;
68import com.android.mail.utils.ContentProviderTask.UpdateTask;
69import com.android.mail.utils.LogUtils;
70import com.android.mail.utils.NotificationUtils;
71
72import java.util.ArrayList;
73import java.util.HashMap;
74import java.util.Map;
75
76/**
77 * Fragment containing the main logic for account settings.  This also calls out to other
78 * fragments for server settings.
79 *
80 * TODO: Can we defer calling addPreferencesFromResource() until after we load the account?  This
81 *       could reduce flicker.
82 */
83public class AccountSettingsFragment extends MailAccountPrefsFragment
84        implements Preference.OnPreferenceChangeListener {
85
86    private static final String ARG_ACCOUNT_ID = "account_id";
87
88    public static final String PREFERENCE_DESCRIPTION = "account_description";
89    private static final String PREFERENCE_NAME = "account_name";
90    private static final String PREFERENCE_SIGNATURE = "account_signature";
91    private static final String PREFERENCE_QUICK_RESPONSES = "account_quick_responses";
92    private static final String PREFERENCE_FREQUENCY = "account_check_frequency";
93    private static final String PREFERENCE_SYNC_WINDOW = "account_sync_window";
94    private static final String PREFERENCE_SYNC_SETTINGS = "account_sync_settings";
95    private static final String PREFERENCE_SYNC_EMAIL = "account_sync_email";
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_BACKGROUND_ATTACHMENTS =
99            "account_background_attachments";
100    private static final String PREFERENCE_CATEGORY_DATA_USAGE = "data_usage";
101    private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "account_notifications";
102    private static final String PREFERENCE_CATEGORY_SERVER = "account_servers";
103    private static final String PREFERENCE_CATEGORY_POLICIES = "account_policies";
104    @SuppressWarnings("unused") // temporarily unused pending policy UI
105    private static final String PREFERENCE_POLICIES_ENFORCED = "policies_enforced";
106    @SuppressWarnings("unused") // temporarily unused pending policy UI
107    private static final String PREFERENCE_POLICIES_UNSUPPORTED = "policies_unsupported";
108    private static final String PREFERENCE_POLICIES_RETRY_ACCOUNT = "policies_retry_account";
109    private static final String PREFERENCE_INCOMING = "incoming";
110    private static final String PREFERENCE_OUTGOING = "outgoing";
111
112    private static final String PREFERENCE_SYSTEM_FOLDERS = "system_folders";
113    private static final String PREFERENCE_SYSTEM_FOLDERS_TRASH = "system_folders_trash";
114    private static final String PREFERENCE_SYSTEM_FOLDERS_SENT = "system_folders_sent";
115
116    private static final String SAVESTATE_SYNC_INTERVALS = "savestate_sync_intervals";
117    private static final String SAVESTATE_SYNC_INTERVAL_STRINGS = "savestate_sync_interval_strings";
118
119    // Request code to start different activities.
120    private static final int RINGTONE_REQUEST_CODE = 0;
121
122    private EditTextPreference mAccountDescription;
123    private EditTextPreference mAccountName;
124    private EditTextPreference mAccountSignature;
125    private ListPreference mCheckFrequency;
126    private ListPreference mSyncWindow;
127    private Preference mSyncSettings;
128    private CheckBoxPreference mInboxVibrate;
129    private Preference mInboxRingtone;
130
131    private Context mContext;
132
133    private Account mAccount;
134    private com.android.mail.providers.Account mUiAccount;
135    private EmailServiceInfo mServiceInfo;
136    private Folder mInboxFolder;
137
138    private Ringtone mRingtone;
139
140    /**
141     * This may be null if the account exists but the inbox has not yet been created in the database
142     * (waiting for initial sync)
143     */
144    private FolderPreferences mInboxFolderPreferences;
145
146    // The email of the account being edited
147    private String mAccountEmail;
148
149    /**
150     * If launching with an email address, use this method to build the arguments.
151     */
152    public static Bundle buildArguments(final String email) {
153        final Bundle b = new Bundle(1);
154        b.putString(ARG_ACCOUNT_EMAIL, email);
155        return b;
156    }
157
158    /**
159     * If launching with an account ID, use this method to build the arguments.
160     */
161    public static Bundle buildArguments(final long accountId) {
162        final Bundle b = new Bundle(1);
163        b.putLong(ARG_ACCOUNT_ID, accountId);
164        return b;
165    }
166
167    @Override
168    public void onAttach(Activity activity) {
169        super.onAttach(activity);
170        mContext = activity;
171    }
172
173    /**
174     * Called to do initial creation of a fragment.  This is called after
175     * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}.
176     */
177    @Override
178    public void onCreate(Bundle savedInstanceState) {
179        super.onCreate(savedInstanceState);
180
181        setHasOptionsMenu(true);
182
183        // Load the preferences from an XML resource
184        addPreferencesFromResource(R.xml.account_settings_preferences);
185
186        if (!getResources().getBoolean(R.bool.quickresponse_supported)) {
187            final Preference quickResponsePref = findPreference(PREFERENCE_QUICK_RESPONSES);
188            if (quickResponsePref != null) {
189                getPreferenceScreen().removePreference(quickResponsePref);
190            }
191        }
192
193        // Start loading the account data, if provided in the arguments
194        // If not, activity must call startLoadingAccount() directly
195        Bundle b = getArguments();
196        if (b != null) {
197            mAccountEmail = b.getString(ARG_ACCOUNT_EMAIL);
198        }
199        if (savedInstanceState != null) {
200            // We won't know what the correct set of sync interval values and strings are until
201            // our loader completes. The problem is, that if the sync frequency chooser is
202            // displayed when the screen rotates, it reinitializes it to the defaults, and doesn't
203            // correct it after the loader finishes again. See b/13624066
204            // To work around this, we'll save the current set of sync interval values and strings,
205            // in onSavedInstanceState, and restore them here.
206            final CharSequence [] syncIntervalStrings =
207                    savedInstanceState.getCharSequenceArray(SAVESTATE_SYNC_INTERVAL_STRINGS);
208            final CharSequence [] syncIntervals =
209                    savedInstanceState.getCharSequenceArray(SAVESTATE_SYNC_INTERVALS);
210            mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
211            if (mCheckFrequency != null) {
212                mCheckFrequency.setEntries(syncIntervalStrings);
213                mCheckFrequency.setEntryValues(syncIntervals);
214            }
215        }
216    }
217
218    @Override
219    public void onSaveInstanceState(@NonNull Bundle outstate) {
220        super.onSaveInstanceState(outstate);
221        if (mCheckFrequency != null) {
222            outstate.putCharSequenceArray(SAVESTATE_SYNC_INTERVAL_STRINGS,
223                    mCheckFrequency.getEntries());
224            outstate.putCharSequenceArray(SAVESTATE_SYNC_INTERVALS,
225                    mCheckFrequency.getEntryValues());
226        }
227    }
228
229    @Override
230    public void onActivityCreated(Bundle savedInstanceState) {
231        super.onActivityCreated(savedInstanceState);
232        final Bundle args = new Bundle(1);
233        if (!TextUtils.isEmpty(mAccountEmail)) {
234            args.putString(AccountLoaderCallbacks.ARG_ACCOUNT_EMAIL, mAccountEmail);
235        } else {
236            args.putLong(AccountLoaderCallbacks.ARG_ACCOUNT_ID,
237                    getArguments().getLong(ARG_ACCOUNT_ID, -1));
238        }
239        getLoaderManager().initLoader(0, args, new AccountLoaderCallbacks(getActivity()));
240    }
241
242    @Override
243    public void onActivityResult(int requestCode, int resultCode, Intent data) {
244        switch (requestCode) {
245            case RINGTONE_REQUEST_CODE:
246                if (resultCode == Activity.RESULT_OK && data != null) {
247                    Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
248                    setRingtone(uri);
249                }
250                break;
251        }
252    }
253
254    /**
255     * Sets the current ringtone.
256     */
257    private void setRingtone(Uri ringtone) {
258        if (ringtone != null) {
259            mInboxFolderPreferences.setNotificationRingtoneUri(ringtone.toString());
260            mRingtone = RingtoneManager.getRingtone(getActivity(), ringtone);
261        } else {
262            // Null means silent was selected.
263            mInboxFolderPreferences.setNotificationRingtoneUri("");
264            mRingtone = null;
265        }
266
267        setRingtoneSummary();
268    }
269
270    private void setRingtoneSummary() {
271        final String summary = mRingtone != null ? mRingtone.getTitle(mContext)
272                : mContext.getString(R.string.silent_ringtone);
273
274        mInboxRingtone.setSummary(summary);
275    }
276
277    @Override
278    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
279            @NonNull Preference preference) {
280        final String key = preference.getKey();
281        if (key.equals(PREFERENCE_SYNC_SETTINGS)) {
282            startActivity(MailboxSettings.getIntent(getActivity(), mUiAccount.fullFolderListUri,
283                    mInboxFolder));
284            return true;
285        } else {
286            return super.onPreferenceTreeClick(preferenceScreen, preference);
287        }
288    }
289
290    /**
291     * Listen to all preference changes in this class.
292     * @param preference The changed Preference
293     * @param newValue The new value of the Preference
294     * @return True to update the state of the Preference with the new value
295     */
296    @Override
297    public boolean onPreferenceChange(Preference preference, Object newValue) {
298        // Can't use a switch here. Falling back to a giant conditional.
299        final String key = preference.getKey();
300        final ContentValues cv = new ContentValues(1);
301        if (key.equals(PREFERENCE_DESCRIPTION)){
302            String summary = newValue.toString().trim();
303            if (TextUtils.isEmpty(summary)) {
304                summary = mUiAccount.getEmailAddress();
305            }
306            mAccountDescription.setSummary(summary);
307            mAccountDescription.setText(summary);
308            cv.put(AccountColumns.DISPLAY_NAME, summary);
309        } else if (key.equals(PREFERENCE_NAME)) {
310            final String summary = newValue.toString().trim();
311            if (!TextUtils.isEmpty(summary)) {
312                mAccountName.setSummary(summary);
313                mAccountName.setText(summary);
314                cv.put(AccountColumns.SENDER_NAME, summary);
315            }
316        } else if (key.equals(PREFERENCE_SIGNATURE)) {
317            // Clean up signature if it's only whitespace (which is easy to do on a
318            // soft keyboard) but leave whitespace in place otherwise, to give the user
319            // maximum flexibility, e.g. the ability to indent
320            String signature = newValue.toString();
321            if (signature.trim().isEmpty()) {
322                signature = "";
323            }
324            mAccountSignature.setText(signature);
325            SettingsUtils.updatePreferenceSummary(mAccountSignature, signature,
326                    R.string.preferences_signature_summary_not_set);
327            cv.put(AccountColumns.SIGNATURE, signature);
328        } else if (key.equals(PREFERENCE_FREQUENCY)) {
329            final String summary = newValue.toString();
330            final int index = mCheckFrequency.findIndexOfValue(summary);
331            mCheckFrequency.setSummary(mCheckFrequency.getEntries()[index]);
332            mCheckFrequency.setValue(summary);
333            if (mServiceInfo.syncContacts || mServiceInfo.syncCalendar) {
334                // This account allows syncing of contacts and/or calendar, so we will always have
335                // separate preferences to enable or disable syncing of email, contacts, and
336                // calendar.
337                // The "sync frequency" preference really just needs to control the frequency value
338                // in our database.
339                cv.put(AccountColumns.SYNC_INTERVAL, Integer.parseInt(summary));
340            } else {
341                // This account only syncs email (not contacts or calendar), which means that we
342                // will hide the preference to turn syncing on and off. In this case, we want the
343                // sync frequency preference to also control whether or not syncing is enabled at
344                // all. If sync is turned off, we will display "sync never" regardless of what the
345                // numeric value we have stored says.
346                final android.accounts.Account androidAcct = new android.accounts.Account(
347                        mAccount.mEmailAddress, mServiceInfo.accountType);
348                if (Integer.parseInt(summary) == Account.CHECK_INTERVAL_NEVER) {
349                    // Disable syncing from the account manager. Leave the current sync frequency
350                    // in the database.
351                    ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY,
352                            false);
353                } else {
354                    // Enable syncing from the account manager.
355                    ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY,
356                            true);
357                    cv.put(AccountColumns.SYNC_INTERVAL, Integer.parseInt(summary));
358                }
359            }
360        } else if (key.equals(PREFERENCE_SYNC_WINDOW)) {
361            final String summary = newValue.toString();
362            int index = mSyncWindow.findIndexOfValue(summary);
363            mSyncWindow.setSummary(mSyncWindow.getEntries()[index]);
364            mSyncWindow.setValue(summary);
365            cv.put(AccountColumns.SYNC_LOOKBACK, Integer.parseInt(summary));
366        } else if (key.equals(PREFERENCE_SYNC_EMAIL)) {
367            final android.accounts.Account androidAcct = new android.accounts.Account(
368                    mAccount.mEmailAddress, mServiceInfo.accountType);
369            ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY,
370                    (Boolean) newValue);
371        } else if (key.equals(PREFERENCE_SYNC_CONTACTS)) {
372            final android.accounts.Account androidAcct = new android.accounts.Account(
373                    mAccount.mEmailAddress, mServiceInfo.accountType);
374            ContentResolver.setSyncAutomatically(androidAcct, ContactsContract.AUTHORITY,
375                    (Boolean) newValue);
376        } else if (key.equals(PREFERENCE_SYNC_CALENDAR)) {
377            final android.accounts.Account androidAcct = new android.accounts.Account(
378                    mAccount.mEmailAddress, mServiceInfo.accountType);
379            ContentResolver.setSyncAutomatically(androidAcct, CalendarContract.AUTHORITY,
380                    (Boolean) newValue);
381        } else if (key.equals(PREFERENCE_BACKGROUND_ATTACHMENTS)) {
382            int newFlags = mAccount.getFlags() & ~(Account.FLAGS_BACKGROUND_ATTACHMENTS);
383
384            newFlags |= (Boolean) newValue ?
385                    Account.FLAGS_BACKGROUND_ATTACHMENTS : 0;
386
387            cv.put(AccountColumns.FLAGS, newFlags);
388        } else if (FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED.equals(key)) {
389            mInboxFolderPreferences.setNotificationsEnabled((Boolean) newValue);
390            return true;
391        } else if (FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE.equals(key)) {
392            final boolean vibrateSetting = (Boolean) newValue;
393            mInboxVibrate.setChecked(vibrateSetting);
394            mInboxFolderPreferences.setNotificationVibrateEnabled(vibrateSetting);
395            return true;
396        } else if (FolderPreferences.PreferenceKeys.NOTIFICATION_RINGTONE.equals(key)) {
397            return true;
398        } else {
399            // Default behavior, just indicate that the preferences were written
400            LogUtils.d(LogUtils.TAG, "Unknown preference key %s", key);
401            return true;
402        }
403        if (cv.size() > 0) {
404            new UpdateTask().run(mContext.getContentResolver(), mAccount.getUri(), cv, null, null);
405            MailActivityEmail.setServicesEnabledAsync(mContext);
406        }
407        return false;
408    }
409
410    @Override
411    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
412        menu.clear();
413        inflater.inflate(R.menu.settings_fragment_menu, menu);
414    }
415
416    /**
417     * Async task loader to load account in order to view/edit it
418     */
419    private static class AccountLoader extends MailAsyncTaskLoader<Map<String, Object>> {
420        public static final String RESULT_KEY_ACCOUNT = "account";
421        private static final String RESULT_KEY_UIACCOUNT_CURSOR = "uiAccountCursor";
422        public static final String RESULT_KEY_UIACCOUNT = "uiAccount";
423        public static final String RESULT_KEY_INBOX = "inbox";
424
425        private final ForceLoadContentObserver mObserver;
426        private final String mAccountEmail;
427        private final long mAccountId;
428
429        private AccountLoader(Context context, String accountEmail, long accountId) {
430            super(context);
431            mObserver = new ForceLoadContentObserver();
432            mAccountEmail = accountEmail;
433            mAccountId = accountId;
434        }
435
436        @Override
437        public Map<String, Object> loadInBackground() {
438            final Map<String, Object> map = new HashMap<>();
439
440            final Account account;
441            if (!TextUtils.isEmpty(mAccountEmail)) {
442                account = Account.restoreAccountWithAddress(getContext(), mAccountEmail, mObserver);
443            } else {
444                account = Account.restoreAccountWithId(getContext(), mAccountId, mObserver);
445            }
446            if (account == null) {
447                return map;
448            }
449
450            map.put(RESULT_KEY_ACCOUNT, account);
451
452            // We don't monitor these for changes, but they probably won't change in any meaningful
453            // way
454            account.getOrCreateHostAuthRecv(getContext());
455            account.getOrCreateHostAuthSend(getContext());
456
457            if (account.mHostAuthRecv == null) {
458                return map;
459            }
460
461            account.mPolicy =
462                    Policy.restorePolicyWithId(getContext(), account.mPolicyKey, mObserver);
463
464            final Cursor uiAccountCursor = getContext().getContentResolver().query(
465                    EmailProvider.uiUri("uiaccount", account.getId()),
466                    UIProvider.ACCOUNTS_PROJECTION,
467                    null, null, null);
468
469            if (uiAccountCursor != null) {
470                map.put(RESULT_KEY_UIACCOUNT_CURSOR, uiAccountCursor);
471                uiAccountCursor.registerContentObserver(mObserver);
472            } else {
473                return map;
474            }
475
476            if (!uiAccountCursor.moveToFirst()) {
477                return map;
478            }
479
480            final com.android.mail.providers.Account uiAccount =
481                    com.android.mail.providers.Account.builder().buildFrom(uiAccountCursor);
482
483            map.put(RESULT_KEY_UIACCOUNT, uiAccount);
484
485            final Cursor folderCursor = getContext().getContentResolver().query(
486                    uiAccount.settings.defaultInbox, UIProvider.FOLDERS_PROJECTION, null, null,
487                    null);
488
489            final Folder inbox;
490            try {
491                if (folderCursor != null && folderCursor.moveToFirst()) {
492                    inbox = new Folder(folderCursor);
493                } else {
494                    return map;
495                }
496            } finally {
497                if (folderCursor != null) {
498                    folderCursor.close();
499                }
500            }
501
502            map.put(RESULT_KEY_INBOX, inbox);
503            return map;
504        }
505
506        @Override
507        protected void onDiscardResult(Map<String, Object> result) {
508            final Account account = (Account) result.get(RESULT_KEY_ACCOUNT);
509            if (account != null) {
510                if (account.mPolicy != null) {
511                    account.mPolicy.close(getContext());
512                }
513                account.close(getContext());
514            }
515            final Cursor uiAccountCursor = (Cursor) result.get(RESULT_KEY_UIACCOUNT_CURSOR);
516            if (uiAccountCursor != null) {
517                uiAccountCursor.close();
518            }
519        }
520    }
521
522    private class AccountLoaderCallbacks
523            implements LoaderManager.LoaderCallbacks<Map<String, Object>> {
524        public static final String ARG_ACCOUNT_EMAIL = "accountEmail";
525        public static final String ARG_ACCOUNT_ID = "accountId";
526        private final Context mContext;
527
528        private AccountLoaderCallbacks(Context context) {
529            mContext = context;
530        }
531
532        @Override
533        public void onLoadFinished(Loader<Map<String, Object>> loader, Map<String, Object> data) {
534            final Activity activity = getActivity();
535            if (activity == null) {
536                return;
537            }
538            if (data == null) {
539                activity.finish();
540                return;
541            }
542
543            mUiAccount = (com.android.mail.providers.Account)
544                    data.get(AccountLoader.RESULT_KEY_UIACCOUNT);
545            mAccount = (Account) data.get(AccountLoader.RESULT_KEY_ACCOUNT);
546
547            if (mAccount != null && (mAccount.mFlags & Account.FLAGS_SECURITY_HOLD) != 0) {
548                final Intent i = AccountSecurity.actionUpdateSecurityIntent(mContext,
549                        mAccount.getId(), true);
550                mContext.startActivity(i);
551                activity.finish();
552                return;
553            }
554
555            mInboxFolder = (Folder) data.get(AccountLoader.RESULT_KEY_INBOX);
556
557            if (mUiAccount == null || mAccount == null) {
558                activity.finish();
559                return;
560            }
561
562            mServiceInfo =
563                    EmailServiceUtils.getServiceInfo(mContext, mAccount.getProtocol(mContext));
564
565            if (mInboxFolder == null) {
566                mInboxFolderPreferences = null;
567            } else {
568                mInboxFolderPreferences = new FolderPreferences(mContext,
569                        mUiAccount.getEmailAddress(), mInboxFolder, true);
570            }
571            loadSettings();
572        }
573
574        @Override
575        public Loader<Map<String, Object>> onCreateLoader(int id, Bundle args) {
576            return new AccountLoader(mContext, args.getString(ARG_ACCOUNT_EMAIL),
577                    args.getLong(ARG_ACCOUNT_ID));
578        }
579
580        @Override
581        public void onLoaderReset(Loader<Map<String, Object>> loader) {}
582    }
583
584    /**
585     * From a Policy, create and return an ArrayList of Strings that describe (simply) those
586     * policies that are supported by the OS.  At the moment, the strings are simple (e.g.
587     * "password required"); we should probably add more information (# characters, etc.), though
588     */
589    @SuppressWarnings("unused") // temporarily unused pending policy UI
590    private ArrayList<String> getSystemPoliciesList(Policy policy) {
591        Resources res = mContext.getResources();
592        ArrayList<String> policies = new ArrayList<>();
593        if (policy.mPasswordMode != Policy.PASSWORD_MODE_NONE) {
594            policies.add(res.getString(R.string.policy_require_password));
595        }
596        if (policy.mPasswordHistory > 0) {
597            policies.add(res.getString(R.string.policy_password_history));
598        }
599        if (policy.mPasswordExpirationDays > 0) {
600            policies.add(res.getString(R.string.policy_password_expiration));
601        }
602        if (policy.mMaxScreenLockTime > 0) {
603            policies.add(res.getString(R.string.policy_screen_timeout));
604        }
605        if (policy.mDontAllowCamera) {
606            policies.add(res.getString(R.string.policy_dont_allow_camera));
607        }
608        if (policy.mMaxEmailLookback != 0) {
609            policies.add(res.getString(R.string.policy_email_age));
610        }
611        if (policy.mMaxCalendarLookback != 0) {
612            policies.add(res.getString(R.string.policy_calendar_age));
613        }
614        return policies;
615    }
616
617    @SuppressWarnings("unused") // temporarily unused pending policy UI
618    private void setPolicyListSummary(ArrayList<String> policies, String policiesToAdd,
619            String preferenceName) {
620        Policy.addPolicyStringToList(policiesToAdd, policies);
621        if (policies.size() > 0) {
622            Preference p = findPreference(preferenceName);
623            StringBuilder sb = new StringBuilder();
624            for (String desc: policies) {
625                sb.append(desc);
626                sb.append('\n');
627            }
628            p.setSummary(sb.toString());
629        }
630    }
631
632    /**
633     * Load account data into preference UI. This must be called on the main thread.
634     */
635    private void loadSettings() {
636        final AccountPreferences accountPreferences =
637                new AccountPreferences(mContext, mUiAccount.getEmailAddress());
638        if (mInboxFolderPreferences != null) {
639            NotificationUtils.moveNotificationSetting(
640                    accountPreferences, mInboxFolderPreferences);
641        }
642
643        final String protocol = mAccount.getProtocol(mContext);
644        if (mServiceInfo == null) {
645            LogUtils.e(LogUtils.TAG,
646                    "Could not find service info for account %d with protocol %s", mAccount.mId,
647                    protocol);
648            getActivity().onBackPressed();
649            // TODO: put up some sort of dialog/toast here to tell the user something went wrong
650            return;
651        }
652        final android.accounts.Account androidAcct = mUiAccount.getAccountManagerAccount();
653
654        mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION);
655        mAccountDescription.setSummary(mAccount.getDisplayName());
656        mAccountDescription.setText(mAccount.getDisplayName());
657        mAccountDescription.setOnPreferenceChangeListener(this);
658
659        mAccountName = (EditTextPreference) findPreference(PREFERENCE_NAME);
660        String senderName = mUiAccount.getSenderName();
661        // In rare cases, sendername will be null;  Change this to empty string to avoid NPE's
662        if (senderName == null) {
663            senderName = "";
664        }
665        mAccountName.setSummary(senderName);
666        mAccountName.setText(senderName);
667        mAccountName.setOnPreferenceChangeListener(this);
668
669        final String accountSignature = mAccount.getSignature();
670        mAccountSignature = (EditTextPreference) findPreference(PREFERENCE_SIGNATURE);
671        mAccountSignature.setText(accountSignature);
672        mAccountSignature.setOnPreferenceChangeListener(this);
673        SettingsUtils.updatePreferenceSummary(mAccountSignature, accountSignature,
674                R.string.preferences_signature_summary_not_set);
675
676        mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
677        mCheckFrequency.setEntries(mServiceInfo.syncIntervalStrings);
678        mCheckFrequency.setEntryValues(mServiceInfo.syncIntervals);
679        if (mServiceInfo.syncContacts || mServiceInfo.syncCalendar) {
680            // This account allows syncing of contacts and/or calendar, so we will always have
681            // separate preferences to enable or disable syncing of email, contacts, and calendar.
682            // The "sync frequency" preference really just needs to control the frequency value
683            // in our database.
684            mCheckFrequency.setValue(String.valueOf(mAccount.getSyncInterval()));
685        } else {
686            // This account only syncs email (not contacts or calendar), which means that we will
687            // hide the preference to turn syncing on and off. In this case, we want the sync
688            // frequency preference to also control whether or not syncing is enabled at all. If
689            // sync is turned off, we will display "sync never" regardless of what the numeric
690            // value we have stored says.
691            boolean synced = ContentResolver.getSyncAutomatically(androidAcct,
692                    EmailContent.AUTHORITY);
693            if (synced) {
694                mCheckFrequency.setValue(String.valueOf(mAccount.getSyncInterval()));
695            } else {
696                mCheckFrequency.setValue(String.valueOf(Account.CHECK_INTERVAL_NEVER));
697            }
698        }
699        mCheckFrequency.setSummary(mCheckFrequency.getEntry());
700        mCheckFrequency.setOnPreferenceChangeListener(this);
701
702        final Preference quickResponsePref = findPreference(PREFERENCE_QUICK_RESPONSES);
703        if (quickResponsePref != null) {
704            quickResponsePref.setOnPreferenceClickListener(
705                    new Preference.OnPreferenceClickListener() {
706                        @Override
707                        public boolean onPreferenceClick(Preference preference) {
708                            onEditQuickResponses(mUiAccount);
709                            return true;
710                        }
711                    });
712        }
713
714        // Add check window preference
715        final PreferenceCategory dataUsageCategory =
716                (PreferenceCategory) findPreference(PREFERENCE_CATEGORY_DATA_USAGE);
717
718        if (mServiceInfo.offerLookback) {
719            if (mSyncWindow == null) {
720                mSyncWindow = new ListPreference(mContext);
721                mSyncWindow.setKey(PREFERENCE_SYNC_WINDOW);
722                dataUsageCategory.addPreference(mSyncWindow);
723            }
724            mSyncWindow.setTitle(R.string.account_setup_options_mail_window_label);
725            mSyncWindow.setValue(String.valueOf(mAccount.getSyncLookback()));
726            final int maxLookback;
727            if (mAccount.mPolicy != null) {
728                maxLookback = mAccount.mPolicy.mMaxEmailLookback;
729            } else {
730                maxLookback = 0;
731            }
732
733            MailboxSettings.setupLookbackPreferenceOptions(mContext, mSyncWindow, maxLookback,
734                    false);
735
736            // Must correspond to the hole in the XML file that's reserved.
737            mSyncWindow.setOrder(2);
738            mSyncWindow.setOnPreferenceChangeListener(this);
739
740            if (mSyncSettings == null) {
741                mSyncSettings = new Preference(mContext);
742                mSyncSettings.setKey(PREFERENCE_SYNC_SETTINGS);
743                dataUsageCategory.addPreference(mSyncSettings);
744            }
745
746            mSyncSettings.setTitle(R.string.folder_sync_settings_pref_title);
747            mSyncSettings.setOrder(3);
748        }
749
750        final PreferenceCategory folderPrefs =
751                (PreferenceCategory) findPreference(PREFERENCE_SYSTEM_FOLDERS);
752        if (folderPrefs != null) {
753            if (mServiceInfo.requiresSetup) {
754                Preference trashPreference = findPreference(PREFERENCE_SYSTEM_FOLDERS_TRASH);
755                Intent i = new Intent(mContext, FolderPickerActivity.class);
756                Uri uri = EmailContent.CONTENT_URI.buildUpon().appendQueryParameter(
757                        "account", Long.toString(mAccount.getId())).build();
758                i.setData(uri);
759                i.putExtra(FolderPickerActivity.MAILBOX_TYPE_EXTRA, Mailbox.TYPE_TRASH);
760                trashPreference.setIntent(i);
761
762                Preference sentPreference = findPreference(PREFERENCE_SYSTEM_FOLDERS_SENT);
763                i = new Intent(mContext, FolderPickerActivity.class);
764                i.setData(uri);
765                i.putExtra(FolderPickerActivity.MAILBOX_TYPE_EXTRA, Mailbox.TYPE_SENT);
766                sentPreference.setIntent(i);
767            } else {
768                getPreferenceScreen().removePreference(folderPrefs);
769            }
770        }
771
772        final CheckBoxPreference backgroundAttachments = (CheckBoxPreference)
773                findPreference(PREFERENCE_BACKGROUND_ATTACHMENTS);
774        if (backgroundAttachments != null) {
775            if (!mServiceInfo.offerAttachmentPreload) {
776                dataUsageCategory.removePreference(backgroundAttachments);
777            } else {
778                backgroundAttachments.setChecked(
779                        0 != (mAccount.getFlags() & Account.FLAGS_BACKGROUND_ATTACHMENTS));
780                backgroundAttachments.setOnPreferenceChangeListener(this);
781            }
782        }
783
784        final PreferenceCategory notificationsCategory =
785                (PreferenceCategory) findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS);
786
787        if (mInboxFolderPreferences != null) {
788            final CheckBoxPreference inboxNotify = (CheckBoxPreference) findPreference(
789                FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED);
790            inboxNotify.setChecked(mInboxFolderPreferences.areNotificationsEnabled());
791            inboxNotify.setOnPreferenceChangeListener(this);
792
793            mInboxRingtone = findPreference(FolderPreferences.PreferenceKeys.NOTIFICATION_RINGTONE);
794            final String ringtoneUri = mInboxFolderPreferences.getNotificationRingtoneUri();
795            if (!TextUtils.isEmpty(ringtoneUri)) {
796                mRingtone = RingtoneManager.getRingtone(getActivity(), Uri.parse(ringtoneUri));
797            }
798            setRingtoneSummary();
799            mInboxRingtone.setOnPreferenceChangeListener(this);
800            mInboxRingtone.setOnPreferenceClickListener(new OnPreferenceClickListener() {
801                @Override
802                public boolean onPreferenceClick(final Preference preference) {
803                    showRingtonePicker();
804
805                    return true;
806                }
807            });
808
809            notificationsCategory.setEnabled(true);
810
811            // Set the vibrator value, or hide it on devices w/o a vibrator
812            mInboxVibrate = (CheckBoxPreference) findPreference(
813                    FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE);
814            if (mInboxVibrate != null) {
815                mInboxVibrate.setChecked(
816                        mInboxFolderPreferences.isNotificationVibrateEnabled());
817                Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
818                if (vibrator.hasVibrator()) {
819                    // When the value is changed, update the setting.
820                    mInboxVibrate.setOnPreferenceChangeListener(this);
821                } else {
822                    // No vibrator present. Remove the preference altogether.
823                    notificationsCategory.removePreference(mInboxVibrate);
824                    mInboxVibrate = null;
825                }
826            }
827        } else {
828            notificationsCategory.setEnabled(false);
829        }
830
831        final Preference retryAccount = findPreference(PREFERENCE_POLICIES_RETRY_ACCOUNT);
832        final PreferenceCategory policiesCategory = (PreferenceCategory) findPreference(
833                PREFERENCE_CATEGORY_POLICIES);
834        if (policiesCategory != null) {
835            // TODO: This code for showing policies isn't working. For KLP, just don't even bother
836            // showing this data; we'll fix this later.
837    /*
838            if (policy != null) {
839                if (policy.mProtocolPoliciesEnforced != null) {
840                    ArrayList<String> policies = getSystemPoliciesList(policy);
841                    setPolicyListSummary(policies, policy.mProtocolPoliciesEnforced,
842                            PREFERENCE_POLICIES_ENFORCED);
843                }
844                if (policy.mProtocolPoliciesUnsupported != null) {
845                    ArrayList<String> policies = new ArrayList<String>();
846                    setPolicyListSummary(policies, policy.mProtocolPoliciesUnsupported,
847                            PREFERENCE_POLICIES_UNSUPPORTED);
848                } else {
849                    // Don't show "retry" unless we have unsupported policies
850                    policiesCategory.removePreference(retryAccount);
851                }
852            } else {
853    */
854            // Remove the category completely if there are no policies
855            getPreferenceScreen().removePreference(policiesCategory);
856
857            //}
858        }
859
860        if (retryAccount != null) {
861            retryAccount.setOnPreferenceClickListener(
862                    new Preference.OnPreferenceClickListener() {
863                        @Override
864                        public boolean onPreferenceClick(Preference preference) {
865                            // Release the account
866                            SecurityPolicy.setAccountHoldFlag(mContext, mAccount, false);
867                            // Remove the preference
868                            if (policiesCategory != null) {
869                                policiesCategory.removePreference(retryAccount);
870                            }
871                            return true;
872                        }
873                    });
874        }
875        findPreference(PREFERENCE_INCOMING).setOnPreferenceClickListener(
876                new Preference.OnPreferenceClickListener() {
877                    @Override
878                    public boolean onPreferenceClick(Preference preference) {
879                        onIncomingSettings(mAccount);
880                        return true;
881                    }
882                });
883
884        // Hide the outgoing account setup link if it's not activated
885        final Preference prefOutgoing = findPreference(PREFERENCE_OUTGOING);
886        if (prefOutgoing != null) {
887            if (mServiceInfo.usesSmtp && mAccount.mHostAuthSend != null) {
888                prefOutgoing.setOnPreferenceClickListener(
889                        new Preference.OnPreferenceClickListener() {
890                            @Override
891                            public boolean onPreferenceClick(Preference preference) {
892                                onOutgoingSettings(mAccount);
893                                return true;
894                            }
895                        });
896            } else {
897                if (mServiceInfo.usesSmtp) {
898                    // We really ought to have an outgoing host auth but we don't.
899                    // There's nothing we can do at this point, so just log the error.
900                    LogUtils.e(LogUtils.TAG, "Account %d has a bad outbound hostauth",
901                            mAccount.getId());
902                }
903                PreferenceCategory serverCategory = (PreferenceCategory) findPreference(
904                        PREFERENCE_CATEGORY_SERVER);
905                serverCategory.removePreference(prefOutgoing);
906            }
907        }
908
909        final CheckBoxPreference syncContacts =
910                (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CONTACTS);
911        final CheckBoxPreference syncCalendar =
912                (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CALENDAR);
913        final CheckBoxPreference syncEmail =
914                (CheckBoxPreference) findPreference(PREFERENCE_SYNC_EMAIL);
915        if (syncContacts != null && syncCalendar != null && syncEmail != null) {
916            if (mServiceInfo.syncContacts || mServiceInfo.syncCalendar) {
917                if (mServiceInfo.syncContacts) {
918                    syncContacts.setChecked(ContentResolver
919                            .getSyncAutomatically(androidAcct, ContactsContract.AUTHORITY));
920                    syncContacts.setOnPreferenceChangeListener(this);
921                } else {
922                    syncContacts.setChecked(false);
923                    syncContacts.setEnabled(false);
924                }
925                if (mServiceInfo.syncCalendar) {
926                    syncCalendar.setChecked(ContentResolver
927                            .getSyncAutomatically(androidAcct, CalendarContract.AUTHORITY));
928                    syncCalendar.setOnPreferenceChangeListener(this);
929                } else {
930                    syncCalendar.setChecked(false);
931                    syncCalendar.setEnabled(false);
932                }
933                syncEmail.setChecked(ContentResolver
934                        .getSyncAutomatically(androidAcct, EmailContent.AUTHORITY));
935                syncEmail.setOnPreferenceChangeListener(this);
936            } else {
937                dataUsageCategory.removePreference(syncContacts);
938                dataUsageCategory.removePreference(syncCalendar);
939                dataUsageCategory.removePreference(syncEmail);
940            }
941        }
942    }
943
944    /**
945     * Shows the system ringtone picker.
946     */
947    private void showRingtonePicker() {
948        Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
949        final String ringtoneUri = mInboxFolderPreferences.getNotificationRingtoneUri();
950        if (!TextUtils.isEmpty(ringtoneUri)) {
951            intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(ringtoneUri));
952        }
953        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
954        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
955                Settings.System.DEFAULT_NOTIFICATION_URI);
956        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
957        intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION);
958        startActivityForResult(intent, RINGTONE_REQUEST_CODE);
959    }
960
961    /**
962     * Dispatch to edit quick responses.
963     */
964    public void onEditQuickResponses(com.android.mail.providers.Account account) {
965        final Bundle args = AccountSettingsEditQuickResponsesFragment.createArgs(account);
966        final PreferenceActivity activity = (PreferenceActivity) getActivity();
967        activity.startPreferencePanel(AccountSettingsEditQuickResponsesFragment.class.getName(),
968                args, R.string.account_settings_edit_quick_responses_label, null, null, 0);
969    }
970
971    /**
972     * Dispatch to edit incoming settings.
973     */
974    public void onIncomingSettings(Account account) {
975        final Intent intent =
976                AccountServerSettingsActivity.getIntentForIncoming(getActivity(), account);
977        getActivity().startActivity(intent);
978    }
979
980    /**
981     * Dispatch to edit outgoing settings.
982     */
983    public void onOutgoingSettings(Account account) {
984        final Intent intent =
985                AccountServerSettingsActivity.getIntentForOutgoing(getActivity(), account);
986        getActivity().startActivity(intent);
987    }
988}
989