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