AccountSettingsFragment.java revision 9c455277797a3564fea2a85d0765509f1dd1a45c
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.content.ContentResolver; 21import android.content.ContentValues; 22import android.content.Context; 23import android.content.Intent; 24import android.content.res.Resources; 25import android.database.Cursor; 26import android.media.Ringtone; 27import android.media.RingtoneManager; 28import android.net.Uri; 29import android.os.AsyncTask; 30import android.os.Bundle; 31import android.os.Vibrator; 32import android.preference.CheckBoxPreference; 33import android.preference.EditTextPreference; 34import android.preference.ListPreference; 35import android.preference.Preference; 36import android.preference.PreferenceCategory; 37import android.preference.Preference.OnPreferenceClickListener; 38import android.provider.CalendarContract; 39import android.provider.ContactsContract; 40import android.provider.Settings; 41import android.text.TextUtils; 42import android.view.Menu; 43import android.view.MenuInflater; 44 45import com.android.email.R; 46import com.android.email.SecurityPolicy; 47import com.android.email.provider.EmailProvider; 48import com.android.email.provider.FolderPickerActivity; 49import com.android.email.service.EmailServiceUtils; 50import com.android.email.service.EmailServiceUtils.EmailServiceInfo; 51import com.android.email2.ui.MailActivityEmail; 52import com.android.emailcommon.Logging; 53import com.android.emailcommon.provider.Account; 54import com.android.emailcommon.provider.EmailContent; 55import com.android.emailcommon.provider.HostAuth; 56import com.android.emailcommon.provider.Mailbox; 57import com.android.emailcommon.provider.Policy; 58import com.android.emailcommon.utility.Utility; 59import com.android.mail.preferences.AccountPreferences; 60import com.android.mail.preferences.FolderPreferences; 61import com.android.mail.providers.Folder; 62import com.android.mail.providers.UIProvider; 63import com.android.mail.ui.settings.SettingsUtils; 64import com.android.mail.utils.LogUtils; 65import com.android.mail.utils.NotificationUtils; 66 67import java.util.ArrayList; 68 69/** 70 * Fragment containing the main logic for account settings. This also calls out to other 71 * fragments for server settings. 72 * 73 * TODO: Remove or make async the mAccountDirty reload logic. Probably no longer needed. 74 * TODO: Can we defer calling addPreferencesFromResource() until after we load the account? This 75 * could reduce flicker. 76 */ 77public class AccountSettingsFragment extends EmailPreferenceFragment 78 implements Preference.OnPreferenceChangeListener { 79 80 // Keys used for arguments bundle 81 private static final String BUNDLE_KEY_ACCOUNT_ID = "AccountSettingsFragment.AccountId"; 82 private static final String BUNDLE_KEY_ACCOUNT_EMAIL = "AccountSettingsFragment.Email"; 83 84 public static final String PREFERENCE_DESCRIPTION = "account_description"; 85 private static final String PREFERENCE_NAME = "account_name"; 86 private static final String PREFERENCE_SIGNATURE = "account_signature"; 87 private static final String PREFERENCE_QUICK_RESPONSES = "account_quick_responses"; 88 private static final String PREFERENCE_FREQUENCY = "account_check_frequency"; 89 private static final String PREFERENCE_BACKGROUND_ATTACHMENTS = 90 "account_background_attachments"; 91 private static final String PREFERENCE_CATEGORY_DATA_USAGE = "data_usage"; 92 private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "account_notifications"; 93 private static final String PREFERENCE_CATEGORY_SERVER = "account_servers"; 94 private static final String PREFERENCE_CATEGORY_POLICIES = "account_policies"; 95 private static final String PREFERENCE_POLICIES_ENFORCED = "policies_enforced"; 96 private static final String PREFERENCE_POLICIES_UNSUPPORTED = "policies_unsupported"; 97 private static final String PREFERENCE_POLICIES_RETRY_ACCOUNT = "policies_retry_account"; 98 private static final String PREFERENCE_INCOMING = "incoming"; 99 private static final String PREFERENCE_OUTGOING = "outgoing"; 100 private static final String PREFERENCE_SYNC_CONTACTS = "account_sync_contacts"; 101 private static final String PREFERENCE_SYNC_CALENDAR = "account_sync_calendar"; 102 private static final String PREFERENCE_SYNC_EMAIL = "account_sync_email"; 103 104 private static final String PREFERENCE_SYSTEM_FOLDERS = "system_folders"; 105 private static final String PREFERENCE_SYSTEM_FOLDERS_TRASH = "system_folders_trash"; 106 private static final String PREFERENCE_SYSTEM_FOLDERS_SENT = "system_folders_sent"; 107 108 // Request code to start different activities. 109 private static final int RINGTONE_REQUEST_CODE = 0; 110 111 private EditTextPreference mAccountDescription; 112 private EditTextPreference mAccountName; 113 private EditTextPreference mAccountSignature; 114 private ListPreference mCheckFrequency; 115 private ListPreference mSyncWindow; 116 private CheckBoxPreference mAccountBackgroundAttachments; 117 private CheckBoxPreference mInboxNotify; 118 private CheckBoxPreference mInboxVibrate; 119 private Preference mInboxRingtone; 120 private PreferenceCategory mNotificationsCategory; 121 private CheckBoxPreference mSyncContacts; 122 private CheckBoxPreference mSyncCalendar; 123 private CheckBoxPreference mSyncEmail; 124 125 private Context mContext; 126 private Account mAccount; 127 private boolean mAccountDirty; 128 private Callback mCallback = EmptyCallback.INSTANCE; 129 private boolean mStarted; 130 private boolean mLoaded; 131 private boolean mSaveOnExit; 132 133 private Ringtone mRingtone; 134 135 private AccountPreferences mAccountPreferences; 136 private FolderPreferences mInboxFolderPreferences; 137 138 /** The e-mail of the account being edited. */ 139 private String mAccountEmail; 140 141 // Async Tasks 142 private AsyncTask<?,?,?> mLoadAccountTask; 143 144 /** 145 * Callback interface that owning activities must provide 146 */ 147 public interface Callback { 148 public void onSettingsChanged(Account account, String preference, Object value); 149 public void onEditQuickResponses(Account account); 150 public void onIncomingSettings(Account account); 151 public void onOutgoingSettings(Account account); 152 public void abandonEdit(); 153 } 154 155 private static class EmptyCallback implements Callback { 156 public static final Callback INSTANCE = new EmptyCallback(); 157 @Override public void onSettingsChanged(Account account, String preference, Object value) {} 158 @Override public void onEditQuickResponses(Account account) {} 159 @Override public void onIncomingSettings(Account account) {} 160 @Override public void onOutgoingSettings(Account account) {} 161 @Override public void abandonEdit() {} 162 } 163 164 /** 165 * If launching with an arguments bundle, use this method to build the arguments. 166 */ 167 public static Bundle buildArguments(long accountId, String email) { 168 Bundle b = new Bundle(); 169 b.putLong(BUNDLE_KEY_ACCOUNT_ID, accountId); 170 b.putString(BUNDLE_KEY_ACCOUNT_EMAIL, email); 171 return b; 172 } 173 174 public static String getTitleFromArgs(Bundle args) { 175 return (args == null) ? null : args.getString(BUNDLE_KEY_ACCOUNT_EMAIL); 176 } 177 178 @Override 179 public void onAttach(Activity activity) { 180 super.onAttach(activity); 181 mContext = activity; 182 } 183 184 /** 185 * Called to do initial creation of a fragment. This is called after 186 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 187 */ 188 @Override 189 public void onCreate(Bundle savedInstanceState) { 190 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 191 LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onCreate"); 192 } 193 super.onCreate(savedInstanceState); 194 195 // Load the preferences from an XML resource 196 addPreferencesFromResource(R.xml.account_settings_preferences); 197 198 // Start loading the account data, if provided in the arguments 199 // If not, activity must call startLoadingAccount() directly 200 Bundle b = getArguments(); 201 if (b != null) { 202 long accountId = b.getLong(BUNDLE_KEY_ACCOUNT_ID, -1); 203 mAccountEmail = b.getString(BUNDLE_KEY_ACCOUNT_EMAIL); 204 if (accountId >= 0 && !mLoaded) { 205 startLoadingAccount(accountId); 206 } 207 } 208 209 mAccountDirty = false; 210 } 211 212 @Override 213 public void onActivityCreated(Bundle savedInstanceState) { 214 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 215 LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onActivityCreated"); 216 } 217 super.onActivityCreated(savedInstanceState); 218 } 219 220 /** 221 * Called when the Fragment is visible to the user. 222 */ 223 @Override 224 public void onStart() { 225 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 226 LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onStart"); 227 } 228 super.onStart(); 229 mStarted = true; 230 231 // If the loaded account is ready now, load the UI 232 if (mAccount != null && !mLoaded) { 233 loadSettings(); 234 } 235 } 236 237 /** 238 * Called when the fragment is visible to the user and actively running. 239 * TODO: Don't read account data on UI thread. This should be fixed by removing the need 240 * to do this, not by spinning up yet another thread. 241 */ 242 @Override 243 public void onResume() { 244 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 245 LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onResume"); 246 } 247 super.onResume(); 248 249 if (mAccountDirty) { 250 // if we are coming back from editing incoming or outgoing settings, 251 // we need to refresh them here so we don't accidentally overwrite the 252 // old values we're still holding here 253 mAccount.mHostAuthRecv = 254 HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv); 255 mAccount.mHostAuthSend = 256 HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeySend); 257 // Because "delete policy" UI is on edit incoming settings, we have 258 // to refresh that as well. 259 Account refreshedAccount = Account.restoreAccountWithId(mContext, mAccount.mId); 260 if (refreshedAccount == null || mAccount.mHostAuthRecv == null) { 261 mSaveOnExit = false; 262 mCallback.abandonEdit(); 263 return; 264 } 265 mAccount.setDeletePolicy(refreshedAccount.getDeletePolicy()); 266 mAccountDirty = false; 267 } 268 } 269 270 @Override 271 public void onPause() { 272 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 273 LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onPause"); 274 } 275 super.onPause(); 276 if (mSaveOnExit) { 277 saveSettings(); 278 } 279 } 280 281 /** 282 * Called when the Fragment is no longer started. 283 */ 284 @Override 285 public void onStop() { 286 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 287 LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onStop"); 288 } 289 super.onStop(); 290 mStarted = false; 291 } 292 293 @Override 294 public void onActivityResult(int requestCode, int resultCode, Intent data) { 295 switch (requestCode) { 296 case RINGTONE_REQUEST_CODE: 297 if (resultCode == Activity.RESULT_OK && data != null) { 298 Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); 299 setRingtone(uri); 300 } 301 break; 302 } 303 } 304 305 /** 306 * Sets the current ringtone. 307 */ 308 private void setRingtone(Uri ringtone) { 309 if (ringtone != null) { 310 mInboxFolderPreferences.setNotificationRingtoneUri(ringtone.toString()); 311 mRingtone = RingtoneManager.getRingtone(getActivity(), ringtone); 312 } else { 313 // Null means silent was selected. 314 mInboxFolderPreferences.setNotificationRingtoneUri(""); 315 mRingtone = null; 316 } 317 318 setRingtoneSummary(); 319 } 320 321 private void setRingtoneSummary() { 322 final String summary = mRingtone != null ? mRingtone.getTitle(mContext) 323 : mContext.getString(R.string.silent_ringtone); 324 325 mInboxRingtone.setSummary(summary); 326 } 327 328 /** 329 * Listen to all preference changes in this class. 330 * @param preference 331 * @param newValue 332 * @return 333 */ 334 @Override 335 public boolean onPreferenceChange(Preference preference, Object newValue){ 336 // Can't use a switch here. Falling back to a giant conditional. 337 final String key = preference.getKey(); 338 if (key.equals(PREFERENCE_DESCRIPTION)){ 339 String summary = newValue.toString().trim(); 340 if (TextUtils.isEmpty(summary)) { 341 summary = mAccount.mEmailAddress; 342 } 343 mAccountDescription.setSummary(summary); 344 mAccountDescription.setText(summary); 345 preferenceChanged(PREFERENCE_DESCRIPTION, summary); 346 return false; 347 } else if (key.equals(PREFERENCE_FREQUENCY)) { 348 final String summary = newValue.toString(); 349 final int index = mCheckFrequency.findIndexOfValue(summary); 350 mCheckFrequency.setSummary(mCheckFrequency.getEntries()[index]); 351 mCheckFrequency.setValue(summary); 352 preferenceChanged(PREFERENCE_FREQUENCY, newValue); 353 return false; 354 } else if (key.equals(PREFERENCE_SIGNATURE)) { 355 // Clean up signature if it's only whitespace (which is easy to do on a 356 // soft keyboard) but leave whitespace in place otherwise, to give the user 357 // maximum flexibility, e.g. the ability to indent 358 String signature = newValue.toString(); 359 if (signature.trim().isEmpty()) { 360 signature = ""; 361 } 362 mAccountSignature.setText(signature); 363 SettingsUtils.updatePreferenceSummary(mAccountSignature, signature, 364 R.string.preferences_signature_summary_not_set); 365 preferenceChanged(PREFERENCE_SIGNATURE, signature); 366 return false; 367 } else if (key.equals(PREFERENCE_NAME)) { 368 final String summary = newValue.toString().trim(); 369 if (!TextUtils.isEmpty(summary)) { 370 mAccountName.setSummary(summary); 371 mAccountName.setText(summary); 372 preferenceChanged(PREFERENCE_NAME, summary); 373 } 374 return false; 375 } else if (FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE.equals(key)) { 376 final boolean vibrateSetting = (Boolean) newValue; 377 mInboxVibrate.setChecked(vibrateSetting); 378 mInboxFolderPreferences.setNotificationVibrateEnabled(vibrateSetting); 379 preferenceChanged(FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE, newValue); 380 return true; 381 } else if (FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED.equals(key)) { 382 mInboxFolderPreferences.setNotificationsEnabled((Boolean) newValue); 383 preferenceChanged(FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED, newValue); 384 return true; 385 } else { 386 // Default behavior, just indicate that the preferences were written 387 preferenceChanged(key, newValue); 388 return true; 389 } 390 } 391 392 /** 393 * Called when the fragment is no longer in use. 394 */ 395 @Override 396 public void onDestroy() { 397 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 398 LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onDestroy"); 399 } 400 super.onDestroy(); 401 402 Utility.cancelTaskInterrupt(mLoadAccountTask); 403 mLoadAccountTask = null; 404 } 405 406 @Override 407 public void onSaveInstanceState(Bundle outState) { 408 if (Logging.DEBUG_LIFECYCLE && MailActivityEmail.DEBUG) { 409 LogUtils.d(Logging.LOG_TAG, "AccountSettingsFragment onSaveInstanceState"); 410 } 411 super.onSaveInstanceState(outState); 412 } 413 414 @Override 415 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 416 menu.clear(); 417 inflater.inflate(R.menu.settings_fragment_menu, menu); 418 } 419 420 /** 421 * Activity provides callbacks here 422 */ 423 public void setCallback(Callback callback) { 424 mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback; 425 } 426 427 /** 428 * Start loading a single account in preparation for editing it 429 */ 430 public void startLoadingAccount(long accountId) { 431 Utility.cancelTaskInterrupt(mLoadAccountTask); 432 mLoadAccountTask = new LoadAccountTask().executeOnExecutor( 433 AsyncTask.THREAD_POOL_EXECUTOR, accountId); 434 } 435 436 /** 437 * Async task to load account in order to view/edit it 438 */ 439 private class LoadAccountTask extends AsyncTask<Long, Void, Account> { 440 @Override 441 protected Account doInBackground(Long... params) { 442 long accountId = params[0]; 443 Account account = Account.restoreAccountWithId(mContext, accountId); 444 if (account != null) { 445 account.mHostAuthRecv = 446 HostAuth.restoreHostAuthWithId(mContext, account.mHostAuthKeyRecv); 447 account.mHostAuthSend = 448 HostAuth.restoreHostAuthWithId(mContext, account.mHostAuthKeySend); 449 if (account.mHostAuthRecv == null) { 450 account = null; 451 } 452 } 453 return account; 454 } 455 456 @Override 457 protected void onPostExecute(Account account) { 458 if (!isCancelled()) { 459 if (account == null) { 460 mSaveOnExit = false; 461 mCallback.abandonEdit(); 462 } else { 463 mAccount = account; 464 if (mStarted && !mLoaded) { 465 loadSettings(); 466 } 467 } 468 } 469 } 470 } 471 472 /** 473 * From a Policy, create and return an ArrayList of Strings that describe (simply) those 474 * policies that are supported by the OS. At the moment, the strings are simple (e.g. 475 * "password required"); we should probably add more information (# characters, etc.), though 476 */ 477 private ArrayList<String> getSystemPoliciesList(Policy policy) { 478 Resources res = mContext.getResources(); 479 ArrayList<String> policies = new ArrayList<String>(); 480 if (policy.mPasswordMode != Policy.PASSWORD_MODE_NONE) { 481 policies.add(res.getString(R.string.policy_require_password)); 482 } 483 if (policy.mPasswordHistory > 0) { 484 policies.add(res.getString(R.string.policy_password_history)); 485 } 486 if (policy.mPasswordExpirationDays > 0) { 487 policies.add(res.getString(R.string.policy_password_expiration)); 488 } 489 if (policy.mMaxScreenLockTime > 0) { 490 policies.add(res.getString(R.string.policy_screen_timeout)); 491 } 492 if (policy.mDontAllowCamera) { 493 policies.add(res.getString(R.string.policy_dont_allow_camera)); 494 } 495 if (policy.mMaxEmailLookback != 0) { 496 policies.add(res.getString(R.string.policy_email_age)); 497 } 498 if (policy.mMaxCalendarLookback != 0) { 499 policies.add(res.getString(R.string.policy_calendar_age)); 500 } 501 return policies; 502 } 503 504 private void setPolicyListSummary(ArrayList<String> policies, String policiesToAdd, 505 String preferenceName) { 506 Policy.addPolicyStringToList(policiesToAdd, policies); 507 if (policies.size() > 0) { 508 Preference p = findPreference(preferenceName); 509 StringBuilder sb = new StringBuilder(); 510 for (String desc: policies) { 511 sb.append(desc); 512 sb.append('\n'); 513 } 514 p.setSummary(sb.toString()); 515 } 516 } 517 518 /** 519 * Loads settings that are dependent on a {@link com.android.mail.providers.Account}, which 520 * must be obtained off the main thread. This will also call {@link #loadMainThreadSettings()}. 521 */ 522 private void loadSettingsOffMainThread() { 523 new Thread(new Runnable() { 524 @Override 525 public void run() { 526 final Cursor accountCursor = mContext.getContentResolver().query(EmailProvider 527 .uiUri("uiaccount", mAccount.mId), UIProvider.ACCOUNTS_PROJECTION, null, 528 null, null); 529 if (accountCursor == null) { 530 return; 531 } 532 533 final com.android.mail.providers.Account account; 534 try { 535 if (accountCursor.moveToFirst()) { 536 account = new com.android.mail.providers.Account(accountCursor); 537 } else { 538 return; 539 } 540 } finally { 541 accountCursor.close(); 542 } 543 544 final Cursor folderCursor = mContext.getContentResolver().query( 545 account.settings.defaultInbox, UIProvider.FOLDERS_PROJECTION, null, null, 546 null); 547 if (folderCursor == null) { 548 return; 549 } 550 551 final Folder folder; 552 try { 553 if (folderCursor.moveToFirst()) { 554 folder = new Folder(folderCursor); 555 } else { 556 return; 557 } 558 } finally { 559 folderCursor.close(); 560 } 561 562 mAccountPreferences = new AccountPreferences(mContext, account.name); 563 mInboxFolderPreferences = 564 new FolderPreferences(mContext, account.name, folder, true); 565 566 NotificationUtils.moveNotificationSetting( 567 mAccountPreferences, mInboxFolderPreferences); 568 569 final String ringtoneUri = mInboxFolderPreferences.getNotificationRingtoneUri(); 570 if (!TextUtils.isEmpty(ringtoneUri)) { 571 mRingtone = RingtoneManager.getRingtone(getActivity(), Uri.parse(ringtoneUri)); 572 } 573 574 final Activity activity = getActivity(); 575 if (activity != null) { 576 activity.runOnUiThread(new Runnable() { 577 @Override 578 public void run() { 579 mInboxNotify.setChecked( 580 mInboxFolderPreferences.areNotificationsEnabled()); 581 mInboxVibrate.setChecked( 582 mInboxFolderPreferences.isNotificationVibrateEnabled()); 583 setRingtoneSummary(); 584 // Notification preferences must be disabled until after 585 // mInboxFolderPreferences is available, so enable them here. 586 mNotificationsCategory.setEnabled(true); 587 } 588 }); 589 } 590 } 591 }).start(); 592 } 593 594 /** 595 * Load account data into preference UI. This must be called on the main thread. 596 */ 597 private void loadSettings() { 598 // We can only do this once, so prevent repeat 599 mLoaded = true; 600 // Once loaded the data is ready to be saved, as well 601 mSaveOnExit = false; 602 603 loadSettingsOffMainThread(); 604 605 mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION); 606 mAccountDescription.setSummary(mAccount.getDisplayName()); 607 mAccountDescription.setText(mAccount.getDisplayName()); 608 mAccountDescription.setOnPreferenceChangeListener(this); 609 610 mAccountName = (EditTextPreference) findPreference(PREFERENCE_NAME); 611 String senderName = mAccount.getSenderName(); 612 // In rare cases, sendername will be null; Change this to empty string to avoid NPE's 613 if (senderName == null) senderName = ""; 614 mAccountName.setSummary(senderName); 615 mAccountName.setText(senderName); 616 mAccountName.setOnPreferenceChangeListener(this); 617 618 final String accountSignature = mAccount.getSignature(); 619 mAccountSignature = (EditTextPreference) findPreference(PREFERENCE_SIGNATURE); 620 mAccountSignature.setText(accountSignature); 621 mAccountSignature.setOnPreferenceChangeListener(this); 622 SettingsUtils.updatePreferenceSummary(mAccountSignature, accountSignature, 623 R.string.preferences_signature_summary_not_set); 624 625 626 mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY); 627 final String protocol = Account.getProtocol(mContext, mAccount.mId); 628 final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mContext, protocol); 629 mCheckFrequency.setEntries(info.syncIntervalStrings); 630 mCheckFrequency.setEntryValues(info.syncIntervals); 631 mCheckFrequency.setValue(String.valueOf(mAccount.getSyncInterval())); 632 mCheckFrequency.setSummary(mCheckFrequency.getEntry()); 633 mCheckFrequency.setOnPreferenceChangeListener(this); 634 635 findPreference(PREFERENCE_QUICK_RESPONSES).setOnPreferenceClickListener( 636 new Preference.OnPreferenceClickListener() { 637 @Override 638 public boolean onPreferenceClick(Preference preference) { 639 mAccountDirty = true; 640 mCallback.onEditQuickResponses(mAccount); 641 return true; 642 } 643 }); 644 645 // Add check window preference 646 PreferenceCategory dataUsageCategory = 647 (PreferenceCategory) findPreference(PREFERENCE_CATEGORY_DATA_USAGE); 648 649 final Policy policy; 650 if (mAccount.mPolicyKey != 0) { 651 // Make sure we have most recent data from account 652 mAccount.refresh(mContext); 653 policy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey); 654 if (policy == null) { 655 // The account has been deleted? Crazy, but not impossible 656 return; 657 } 658 } else { 659 policy = null; 660 } 661 662 mSyncWindow = null; 663 if (info.offerLookback) { 664 mSyncWindow = new ListPreference(mContext); 665 mSyncWindow.setTitle(R.string.account_setup_options_mail_window_label); 666 mSyncWindow.setValue(String.valueOf(mAccount.getSyncLookback())); 667 final int maxLookback; 668 if (policy != null) { 669 maxLookback = policy.mMaxEmailLookback; 670 } else { 671 maxLookback = 0; 672 } 673 674 MailboxSettings.setupLookbackPreferenceOptions(mContext, mSyncWindow, maxLookback, 675 false); 676 677 // Must correspond to the hole in the XML file that's reserved. 678 mSyncWindow.setOrder(2); 679 mSyncWindow.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { 680 @Override 681 public boolean onPreferenceChange(Preference preference, Object newValue) { 682 final String summary = newValue.toString(); 683 int index = mSyncWindow.findIndexOfValue(summary); 684 mSyncWindow.setSummary(mSyncWindow.getEntries()[index]); 685 mSyncWindow.setValue(summary); 686 preferenceChanged(preference.getKey(), newValue); 687 return false; 688 } 689 }); 690 dataUsageCategory.addPreference(mSyncWindow); 691 } 692 693 PreferenceCategory folderPrefs = 694 (PreferenceCategory) findPreference(PREFERENCE_SYSTEM_FOLDERS); 695 if (info.requiresSetup) { 696 Preference trashPreference = findPreference(PREFERENCE_SYSTEM_FOLDERS_TRASH); 697 Intent i = new Intent(mContext, FolderPickerActivity.class); 698 Uri uri = EmailContent.CONTENT_URI.buildUpon().appendQueryParameter( 699 "account", Long.toString(mAccount.mId)).build(); 700 i.setData(uri); 701 i.putExtra(FolderPickerActivity.MAILBOX_TYPE_EXTRA, Mailbox.TYPE_TRASH); 702 trashPreference.setIntent(i); 703 704 Preference sentPreference = findPreference(PREFERENCE_SYSTEM_FOLDERS_SENT); 705 i = new Intent(mContext, FolderPickerActivity.class); 706 i.setData(uri); 707 i.putExtra(FolderPickerActivity.MAILBOX_TYPE_EXTRA, Mailbox.TYPE_SENT); 708 sentPreference.setIntent(i); 709 } else { 710 getPreferenceScreen().removePreference(folderPrefs); 711 } 712 713 mAccountBackgroundAttachments = (CheckBoxPreference) 714 findPreference(PREFERENCE_BACKGROUND_ATTACHMENTS); 715 if (!info.offerAttachmentPreload) { 716 dataUsageCategory.removePreference(mAccountBackgroundAttachments); 717 } else { 718 mAccountBackgroundAttachments.setChecked( 719 0 != (mAccount.getFlags() & Account.FLAGS_BACKGROUND_ATTACHMENTS)); 720 mAccountBackgroundAttachments.setOnPreferenceChangeListener(this); 721 } 722 723 mInboxNotify = (CheckBoxPreference) findPreference( 724 FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED); 725 mInboxNotify.setOnPreferenceChangeListener(this); 726 727 mInboxRingtone = findPreference(FolderPreferences.PreferenceKeys.NOTIFICATION_RINGTONE); 728 mInboxRingtone.setOnPreferenceChangeListener(this); 729 mInboxRingtone.setOnPreferenceClickListener(new OnPreferenceClickListener() { 730 @Override 731 public boolean onPreferenceClick(final Preference preference) { 732 showRingtonePicker(); 733 734 return true; 735 } 736 }); 737 738 mNotificationsCategory = 739 (PreferenceCategory) findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS); 740 741 // Set the vibrator value, or hide it on devices w/o a vibrator 742 mInboxVibrate = (CheckBoxPreference) findPreference( 743 FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE); 744 Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); 745 if (vibrator.hasVibrator()) { 746 // Checked state will be set when we obtain it in #loadSettingsOffMainThread() 747 748 // When the value is changed, update the setting. 749 mInboxVibrate.setOnPreferenceChangeListener(this); 750 } else { 751 // No vibrator present. Remove the preference altogether. 752 mNotificationsCategory.removePreference(mInboxVibrate); 753 } 754 755 final Preference retryAccount = findPreference(PREFERENCE_POLICIES_RETRY_ACCOUNT); 756 final PreferenceCategory policiesCategory = (PreferenceCategory) findPreference( 757 PREFERENCE_CATEGORY_POLICIES); 758 if (policy != null) { 759 if (policy.mProtocolPoliciesEnforced != null) { 760 ArrayList<String> policies = getSystemPoliciesList(policy); 761 setPolicyListSummary(policies, policy.mProtocolPoliciesEnforced, 762 PREFERENCE_POLICIES_ENFORCED); 763 } 764 if (policy.mProtocolPoliciesUnsupported != null) { 765 ArrayList<String> policies = new ArrayList<String>(); 766 setPolicyListSummary(policies, policy.mProtocolPoliciesUnsupported, 767 PREFERENCE_POLICIES_UNSUPPORTED); 768 } else { 769 // Don't show "retry" unless we have unsupported policies 770 policiesCategory.removePreference(retryAccount); 771 } 772 } else { 773 // Remove the category completely if there are no policies 774 getPreferenceScreen().removePreference(policiesCategory); 775 } 776 777 retryAccount.setOnPreferenceClickListener( 778 new Preference.OnPreferenceClickListener() { 779 @Override 780 public boolean onPreferenceClick(Preference preference) { 781 // Release the account 782 SecurityPolicy.setAccountHoldFlag(mContext, mAccount, false); 783 // Remove the preference 784 policiesCategory.removePreference(retryAccount); 785 return true; 786 } 787 }); 788 findPreference(PREFERENCE_INCOMING).setOnPreferenceClickListener( 789 new Preference.OnPreferenceClickListener() { 790 @Override 791 public boolean onPreferenceClick(Preference preference) { 792 mAccountDirty = true; 793 mCallback.onIncomingSettings(mAccount); 794 return true; 795 } 796 }); 797 798 // Hide the outgoing account setup link if it's not activated 799 Preference prefOutgoing = findPreference(PREFERENCE_OUTGOING); 800 if (info.usesSmtp && mAccount.mHostAuthSend != null) { 801 prefOutgoing.setOnPreferenceClickListener( 802 new Preference.OnPreferenceClickListener() { 803 @Override 804 public boolean onPreferenceClick(Preference preference) { 805 mAccountDirty = true; 806 mCallback.onOutgoingSettings(mAccount); 807 return true; 808 } 809 }); 810 } else { 811 if (info.usesSmtp) { 812 // We really ought to have an outgoing host auth but we don't. 813 // There's nothing we can do at this point, so just log the error. 814 LogUtils.e(Logging.LOG_TAG, "Account %d has a bad outbound hostauth", mAccount.mId); 815 } 816 PreferenceCategory serverCategory = (PreferenceCategory) findPreference( 817 PREFERENCE_CATEGORY_SERVER); 818 serverCategory.removePreference(prefOutgoing); 819 } 820 821 mSyncContacts = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CONTACTS); 822 mSyncCalendar = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CALENDAR); 823 mSyncEmail = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_EMAIL); 824 if (info.syncContacts || info.syncCalendar) { 825 android.accounts.Account acct = new android.accounts.Account(mAccount.mEmailAddress, 826 info.accountType); 827 if (info.syncContacts) { 828 mSyncContacts.setChecked(ContentResolver 829 .getSyncAutomatically(acct, ContactsContract.AUTHORITY)); 830 mSyncContacts.setOnPreferenceChangeListener(this); 831 } else { 832 mSyncContacts.setChecked(false); 833 mSyncContacts.setEnabled(false); 834 } 835 if (info.syncCalendar) { 836 mSyncCalendar.setChecked(ContentResolver 837 .getSyncAutomatically(acct, CalendarContract.AUTHORITY)); 838 mSyncCalendar.setOnPreferenceChangeListener(this); 839 } else { 840 mSyncCalendar.setChecked(false); 841 mSyncCalendar.setEnabled(false); 842 } 843 mSyncEmail.setChecked(ContentResolver 844 .getSyncAutomatically(acct, EmailContent.AUTHORITY)); 845 mSyncEmail.setOnPreferenceChangeListener(this); 846 } else { 847 dataUsageCategory.removePreference(mSyncContacts); 848 dataUsageCategory.removePreference(mSyncCalendar); 849 dataUsageCategory.removePreference(mSyncEmail); 850 } 851 } 852 853 /** 854 * Called any time a preference is changed. 855 */ 856 private void preferenceChanged(String preference, Object value) { 857 mCallback.onSettingsChanged(mAccount, preference, value); 858 mSaveOnExit = true; 859 } 860 861 /* 862 * Note: This writes the settings on the UI thread. This has to be done so the settings are 863 * committed before we might be killed. 864 */ 865 private void saveSettings() { 866 // Turn off all controlled flags - will turn them back on while checking UI elements 867 int newFlags = mAccount.getFlags() & ~(Account.FLAGS_BACKGROUND_ATTACHMENTS); 868 869 newFlags |= mAccountBackgroundAttachments.isChecked() ? 870 Account.FLAGS_BACKGROUND_ATTACHMENTS : 0; 871 // If the display name has been cleared, we'll reset it to the default value (email addr) 872 mAccount.setDisplayName(mAccountDescription.getText().trim()); 873 // The sender name must never be empty (this is enforced by the preference editor) 874 mAccount.setSenderName(mAccountName.getText().trim()); 875 mAccount.setSignature(mAccountSignature.getText()); 876 mAccount.setSyncInterval(Integer.parseInt(mCheckFrequency.getValue())); 877 if (mSyncWindow != null) { 878 mAccount.setSyncLookback(Integer.parseInt(mSyncWindow.getValue())); 879 } 880 mAccount.setFlags(newFlags); 881 882 EmailServiceInfo info = 883 EmailServiceUtils.getServiceInfo(mContext, mAccount.getProtocol(mContext)); 884 if (info.syncContacts || info.syncCalendar) { 885 android.accounts.Account acct = new android.accounts.Account(mAccount.mEmailAddress, 886 info.accountType); 887 ContentResolver.setSyncAutomatically(acct, ContactsContract.AUTHORITY, 888 mSyncContacts.isChecked()); 889 ContentResolver.setSyncAutomatically(acct, CalendarContract.AUTHORITY, 890 mSyncCalendar.isChecked()); 891 ContentResolver.setSyncAutomatically(acct, EmailContent.AUTHORITY, 892 mSyncEmail.isChecked()); 893 } 894 895 // Commit the changes 896 // Note, this is done in the UI thread because at this point, we must commit 897 // all changes - any time after onPause completes, we could be killed. This is analogous 898 // to the way that SharedPreferences tries to work off-thread in apply(), but will pause 899 // until completion in onPause(). 900 ContentValues cv = AccountSettingsUtils.getAccountContentValues(mAccount); 901 mAccount.update(mContext, cv); 902 903 // Run the remaining changes off-thread 904 MailActivityEmail.setServicesEnabledAsync(mContext); 905 } 906 907 public String getAccountEmail() { 908 // Get the e-mail address of the account being editted, if this is for an existing account. 909 return mAccountEmail; 910 } 911 912 /** 913 * Shows the system ringtone picker. 914 */ 915 private void showRingtonePicker() { 916 Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); 917 final String ringtoneUri = mInboxFolderPreferences.getNotificationRingtoneUri(); 918 if (!TextUtils.isEmpty(ringtoneUri)) { 919 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(ringtoneUri)); 920 } 921 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); 922 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, 923 Settings.System.DEFAULT_NOTIFICATION_URI); 924 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); 925 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); 926 startActivityForResult(intent, RINGTONE_REQUEST_CODE); 927 } 928} 929