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