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