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 androidx.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