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 
17 package com.android.email.activity.setup;
18 
19 import android.app.Activity;
20 import android.app.LoaderManager;
21 import android.content.ContentResolver;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.Loader;
26 import android.content.res.Resources;
27 import android.database.Cursor;
28 import android.media.Ringtone;
29 import android.media.RingtoneManager;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.Vibrator;
33 import android.preference.CheckBoxPreference;
34 import android.preference.EditTextPreference;
35 import android.preference.ListPreference;
36 import android.preference.Preference;
37 import android.preference.PreferenceActivity;
38 import android.preference.PreferenceCategory;
39 import android.preference.Preference.OnPreferenceClickListener;
40 import android.preference.PreferenceScreen;
41 import android.provider.CalendarContract;
42 import android.provider.ContactsContract;
43 import android.provider.Settings;
44 import android.support.annotation.NonNull;
45 import android.text.TextUtils;
46 import android.view.Menu;
47 import android.view.MenuInflater;
48 
49 import com.android.email.R;
50 import com.android.email.SecurityPolicy;
51 import com.android.email.provider.EmailProvider;
52 import com.android.email.provider.FolderPickerActivity;
53 import com.android.email.service.EmailServiceUtils;
54 import com.android.email.service.EmailServiceUtils.EmailServiceInfo;
55 import com.android.emailcommon.provider.Account;
56 import com.android.emailcommon.provider.EmailContent;
57 import com.android.emailcommon.provider.EmailContent.AccountColumns;
58 import com.android.emailcommon.provider.Mailbox;
59 import com.android.emailcommon.provider.Policy;
60 import com.android.mail.preferences.AccountPreferences;
61 import com.android.mail.preferences.FolderPreferences;
62 import com.android.mail.providers.Folder;
63 import com.android.mail.providers.UIProvider;
64 import com.android.mail.ui.MailAsyncTaskLoader;
65 import com.android.mail.ui.settings.MailAccountPrefsFragment;
66 import com.android.mail.ui.settings.SettingsUtils;
67 import com.android.mail.utils.ContentProviderTask.UpdateTask;
68 import com.android.mail.utils.LogUtils;
69 import com.android.mail.utils.NotificationUtils;
70 
71 import java.util.ArrayList;
72 import java.util.HashMap;
73 import 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  */
82 public 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      */
buildArguments(final String email)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      */
buildArguments(final long accountId)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
onAttach(Activity activity)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
onCreate(Bundle savedInstanceState)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
onSaveInstanceState(@onNull Bundle outstate)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
onActivityCreated(Bundle savedInstanceState)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
onActivityResult(int requestCode, int resultCode, Intent data)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      */
setRingtone(Uri ringtone)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 
setRingtoneSummary()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
onPreferenceTreeClick(PreferenceScreen preferenceScreen, @NonNull Preference preference)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
onPreferenceChange(Preference preference, Object newValue)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
onCreateOptionsMenu(Menu menu, MenuInflater inflater)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 
AccountLoader(Context context, String accountEmail, long accountId)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
loadInBackground()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
onDiscardResult(Map<String, Object> result)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 
AccountLoaderCallbacks(Context context)530         private AccountLoaderCallbacks(Context context) {
531             mContext = context;
532         }
533 
534         @Override
onLoadFinished(Loader<Map<String, Object>> loader, Map<String, Object> data)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
onCreateLoader(int id, Bundle args)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
onLoaderReset(Loader<Map<String, Object>> loader)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
getSystemPoliciesList(Policy policy)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
setPolicyListSummary(ArrayList<String> policies, String policiesToAdd, String preferenceName)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      */
loadSettings()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      */
showRingtonePicker()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      */
onEditQuickResponses(com.android.mail.providers.Account account)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      */
onIncomingSettings(Account account)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      */
onOutgoingSettings(Account account)985     public void onOutgoingSettings(Account account) {
986         final Intent intent =
987                 AccountServerSettingsActivity.getIntentForOutgoing(getActivity(), account);
988         getActivity().startActivity(intent);
989     }
990 }
991