1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.email.activity.setup; 18 19 import android.app.Activity; 20 import android.content.ContentResolver; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.res.Resources; 25 import android.database.Cursor; 26 import android.media.Ringtone; 27 import android.media.RingtoneManager; 28 import android.net.Uri; 29 import android.os.AsyncTask; 30 import android.os.Bundle; 31 import android.os.Vibrator; 32 import android.preference.CheckBoxPreference; 33 import android.preference.EditTextPreference; 34 import android.preference.ListPreference; 35 import android.preference.Preference; 36 import android.preference.PreferenceCategory; 37 import android.preference.Preference.OnPreferenceClickListener; 38 import android.preference.PreferenceFragment; 39 import android.provider.CalendarContract; 40 import android.provider.ContactsContract; 41 import android.provider.Settings; 42 import android.text.TextUtils; 43 import android.view.Menu; 44 import android.view.MenuInflater; 45 46 import com.android.email.R; 47 import com.android.email.SecurityPolicy; 48 import com.android.email.provider.EmailProvider; 49 import com.android.email.provider.FolderPickerActivity; 50 import com.android.email.service.EmailServiceUtils; 51 import com.android.email.service.EmailServiceUtils.EmailServiceInfo; 52 import com.android.email2.ui.MailActivityEmail; 53 import com.android.emailcommon.Logging; 54 import com.android.emailcommon.provider.Account; 55 import com.android.emailcommon.provider.EmailContent; 56 import com.android.emailcommon.provider.HostAuth; 57 import com.android.emailcommon.provider.Mailbox; 58 import com.android.emailcommon.provider.Policy; 59 import com.android.emailcommon.utility.Utility; 60 import com.android.mail.preferences.AccountPreferences; 61 import com.android.mail.preferences.FolderPreferences; 62 import com.android.mail.providers.Folder; 63 import com.android.mail.providers.UIProvider; 64 import com.android.mail.ui.settings.SettingsUtils; 65 import com.android.mail.utils.LogUtils; 66 import com.android.mail.utils.NotificationUtils; 67 68 import java.util.ArrayList; 69 import java.util.HashMap; 70 import 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 */ 80 public 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 if (mInboxNotify == null) { 602 // Should only happen if we've aborted the settings screen 603 return; 604 } 605 mInboxNotify.setChecked( 606 mInboxFolderPreferences.areNotificationsEnabled()); 607 mInboxVibrate.setChecked( 608 mInboxFolderPreferences.isNotificationVibrateEnabled()); 609 setRingtoneSummary(); 610 // Notification preferences must be disabled until after 611 // mInboxFolderPreferences is available, so enable them here. 612 mNotificationsCategory.setEnabled(true); 613 } 614 }); 615 } 616 } 617 }).start(); 618 } 619 620 /** 621 * Load account data into preference UI. This must be called on the main thread. 622 */ 623 private void loadSettings() { 624 // We can only do this once, so prevent repeat 625 mLoaded = true; 626 // Once loaded the data is ready to be saved, as well 627 mSaveOnExit = false; 628 629 loadSettingsOffMainThread(); 630 631 final String protocol = Account.getProtocol(mContext, mAccount.mId); 632 final EmailServiceInfo info = EmailServiceUtils.getServiceInfo(mContext, protocol); 633 if (info == null) { 634 LogUtils.e(Logging.LOG_TAG, "Could not find service info for account " + mAccount.mId 635 + " with protocol " + protocol); 636 getActivity().onBackPressed(); 637 // TODO: put up some sort of dialog here to tell the user something went wrong 638 return; 639 } 640 final android.accounts.Account androidAcct = new android.accounts.Account( 641 mAccount.mEmailAddress, info.accountType); 642 643 mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION); 644 mAccountDescription.setSummary(mAccount.getDisplayName()); 645 mAccountDescription.setText(mAccount.getDisplayName()); 646 mAccountDescription.setOnPreferenceChangeListener(this); 647 648 mAccountName = (EditTextPreference) findPreference(PREFERENCE_NAME); 649 String senderName = mAccount.getSenderName(); 650 // In rare cases, sendername will be null; Change this to empty string to avoid NPE's 651 if (senderName == null) senderName = ""; 652 mAccountName.setSummary(senderName); 653 mAccountName.setText(senderName); 654 mAccountName.setOnPreferenceChangeListener(this); 655 656 final String accountSignature = mAccount.getSignature(); 657 mAccountSignature = (EditTextPreference) findPreference(PREFERENCE_SIGNATURE); 658 mAccountSignature.setText(accountSignature); 659 mAccountSignature.setOnPreferenceChangeListener(this); 660 SettingsUtils.updatePreferenceSummary(mAccountSignature, accountSignature, 661 R.string.preferences_signature_summary_not_set); 662 663 mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY); 664 mCheckFrequency.setEntries(info.syncIntervalStrings); 665 mCheckFrequency.setEntryValues(info.syncIntervals); 666 if (info.syncContacts || info.syncCalendar) { 667 // This account allows syncing of contacts and/or calendar, so we will always have 668 // separate preferences to enable or disable syncing of email, contacts, and calendar. 669 // The "sync frequency" preference really just needs to control the frequency value 670 // in our database. 671 mCheckFrequency.setValue(String.valueOf(mAccount.getSyncInterval())); 672 } else { 673 // This account only syncs email (not contacts or calendar), which means that we will 674 // hide the preference to turn syncing on and off. In this case, we want the sync 675 // frequency preference to also control whether or not syncing is enabled at all. If 676 // sync is turned off, we will display "sync never" regardless of what the numeric 677 // value we have stored says. 678 boolean synced = ContentResolver.getSyncAutomatically(androidAcct, 679 EmailContent.AUTHORITY); 680 if (synced) { 681 mCheckFrequency.setValue(String.valueOf(mAccount.getSyncInterval())); 682 } else { 683 mCheckFrequency.setValue(String.valueOf(Account.CHECK_INTERVAL_NEVER)); 684 } 685 } 686 mCheckFrequency.setSummary(mCheckFrequency.getEntry()); 687 mCheckFrequency.setOnPreferenceChangeListener(this); 688 689 findPreference(PREFERENCE_QUICK_RESPONSES).setOnPreferenceClickListener( 690 new Preference.OnPreferenceClickListener() { 691 @Override 692 public boolean onPreferenceClick(Preference preference) { 693 mAccountDirty = true; 694 mCallback.onEditQuickResponses(mUiAccount); 695 return true; 696 } 697 }); 698 699 // Add check window preference 700 PreferenceCategory dataUsageCategory = 701 (PreferenceCategory) findPreference(PREFERENCE_CATEGORY_DATA_USAGE); 702 703 final Policy policy; 704 if (mAccount.mPolicyKey != 0) { 705 // Make sure we have most recent data from account 706 mAccount.refresh(mContext); 707 policy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey); 708 if (policy == null) { 709 // The account has been deleted? Crazy, but not impossible 710 return; 711 } 712 } else { 713 policy = null; 714 } 715 716 mSyncWindow = null; 717 if (info.offerLookback) { 718 mSyncWindow = new ListPreference(mContext); 719 mSyncWindow.setTitle(R.string.account_setup_options_mail_window_label); 720 mSyncWindow.setValue(String.valueOf(mAccount.getSyncLookback())); 721 final int maxLookback; 722 if (policy != null) { 723 maxLookback = policy.mMaxEmailLookback; 724 } else { 725 maxLookback = 0; 726 } 727 728 MailboxSettings.setupLookbackPreferenceOptions(mContext, mSyncWindow, maxLookback, 729 false); 730 731 // Must correspond to the hole in the XML file that's reserved. 732 mSyncWindow.setOrder(2); 733 mSyncWindow.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { 734 @Override 735 public boolean onPreferenceChange(Preference preference, Object newValue) { 736 final String summary = newValue.toString(); 737 int index = mSyncWindow.findIndexOfValue(summary); 738 mSyncWindow.setSummary(mSyncWindow.getEntries()[index]); 739 mSyncWindow.setValue(summary); 740 preferenceChanged(preference.getKey(), newValue); 741 return false; 742 } 743 }); 744 dataUsageCategory.addPreference(mSyncWindow); 745 } 746 747 PreferenceCategory folderPrefs = 748 (PreferenceCategory) findPreference(PREFERENCE_SYSTEM_FOLDERS); 749 if (info.requiresSetup) { 750 Preference trashPreference = findPreference(PREFERENCE_SYSTEM_FOLDERS_TRASH); 751 Intent i = new Intent(mContext, FolderPickerActivity.class); 752 Uri uri = EmailContent.CONTENT_URI.buildUpon().appendQueryParameter( 753 "account", Long.toString(mAccount.mId)).build(); 754 i.setData(uri); 755 i.putExtra(FolderPickerActivity.MAILBOX_TYPE_EXTRA, Mailbox.TYPE_TRASH); 756 trashPreference.setIntent(i); 757 758 Preference sentPreference = findPreference(PREFERENCE_SYSTEM_FOLDERS_SENT); 759 i = new Intent(mContext, FolderPickerActivity.class); 760 i.setData(uri); 761 i.putExtra(FolderPickerActivity.MAILBOX_TYPE_EXTRA, Mailbox.TYPE_SENT); 762 sentPreference.setIntent(i); 763 } else { 764 getPreferenceScreen().removePreference(folderPrefs); 765 } 766 767 mAccountBackgroundAttachments = (CheckBoxPreference) 768 findPreference(PREFERENCE_BACKGROUND_ATTACHMENTS); 769 if (!info.offerAttachmentPreload) { 770 dataUsageCategory.removePreference(mAccountBackgroundAttachments); 771 } else { 772 mAccountBackgroundAttachments.setChecked( 773 0 != (mAccount.getFlags() & Account.FLAGS_BACKGROUND_ATTACHMENTS)); 774 mAccountBackgroundAttachments.setOnPreferenceChangeListener(this); 775 } 776 777 mInboxNotify = (CheckBoxPreference) findPreference( 778 FolderPreferences.PreferenceKeys.NOTIFICATIONS_ENABLED); 779 mInboxNotify.setOnPreferenceChangeListener(this); 780 781 mInboxRingtone = findPreference(FolderPreferences.PreferenceKeys.NOTIFICATION_RINGTONE); 782 mInboxRingtone.setOnPreferenceChangeListener(this); 783 mInboxRingtone.setOnPreferenceClickListener(new OnPreferenceClickListener() { 784 @Override 785 public boolean onPreferenceClick(final Preference preference) { 786 showRingtonePicker(); 787 788 return true; 789 } 790 }); 791 792 mNotificationsCategory = 793 (PreferenceCategory) findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS); 794 795 // Set the vibrator value, or hide it on devices w/o a vibrator 796 mInboxVibrate = (CheckBoxPreference) findPreference( 797 FolderPreferences.PreferenceKeys.NOTIFICATION_VIBRATE); 798 Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); 799 if (vibrator.hasVibrator()) { 800 // Checked state will be set when we obtain it in #loadSettingsOffMainThread() 801 802 // When the value is changed, update the setting. 803 mInboxVibrate.setOnPreferenceChangeListener(this); 804 } else { 805 // No vibrator present. Remove the preference altogether. 806 mNotificationsCategory.removePreference(mInboxVibrate); 807 } 808 809 final Preference retryAccount = findPreference(PREFERENCE_POLICIES_RETRY_ACCOUNT); 810 final PreferenceCategory policiesCategory = (PreferenceCategory) findPreference( 811 PREFERENCE_CATEGORY_POLICIES); 812 // TODO: This code for showing policies isn't working. For KLP, just don't even bother 813 // showing this data; we'll fix this later. 814 /* 815 if (policy != null) { 816 if (policy.mProtocolPoliciesEnforced != null) { 817 ArrayList<String> policies = getSystemPoliciesList(policy); 818 setPolicyListSummary(policies, policy.mProtocolPoliciesEnforced, 819 PREFERENCE_POLICIES_ENFORCED); 820 } 821 if (policy.mProtocolPoliciesUnsupported != null) { 822 ArrayList<String> policies = new ArrayList<String>(); 823 setPolicyListSummary(policies, policy.mProtocolPoliciesUnsupported, 824 PREFERENCE_POLICIES_UNSUPPORTED); 825 } else { 826 // Don't show "retry" unless we have unsupported policies 827 policiesCategory.removePreference(retryAccount); 828 } 829 } else { 830 */ 831 // Remove the category completely if there are no policies 832 getPreferenceScreen().removePreference(policiesCategory); 833 834 //} 835 836 retryAccount.setOnPreferenceClickListener( 837 new Preference.OnPreferenceClickListener() { 838 @Override 839 public boolean onPreferenceClick(Preference preference) { 840 // Release the account 841 SecurityPolicy.setAccountHoldFlag(mContext, mAccount, false); 842 // Remove the preference 843 policiesCategory.removePreference(retryAccount); 844 return true; 845 } 846 }); 847 findPreference(PREFERENCE_INCOMING).setOnPreferenceClickListener( 848 new Preference.OnPreferenceClickListener() { 849 @Override 850 public boolean onPreferenceClick(Preference preference) { 851 mAccountDirty = true; 852 mCallback.onIncomingSettings(mAccount); 853 return true; 854 } 855 }); 856 857 // Hide the outgoing account setup link if it's not activated 858 Preference prefOutgoing = findPreference(PREFERENCE_OUTGOING); 859 if (info.usesSmtp && mAccount.mHostAuthSend != null) { 860 prefOutgoing.setOnPreferenceClickListener( 861 new Preference.OnPreferenceClickListener() { 862 @Override 863 public boolean onPreferenceClick(Preference preference) { 864 mAccountDirty = true; 865 mCallback.onOutgoingSettings(mAccount); 866 return true; 867 } 868 }); 869 } else { 870 if (info.usesSmtp) { 871 // We really ought to have an outgoing host auth but we don't. 872 // There's nothing we can do at this point, so just log the error. 873 LogUtils.e(Logging.LOG_TAG, "Account %d has a bad outbound hostauth", mAccount.mId); 874 } 875 PreferenceCategory serverCategory = (PreferenceCategory) findPreference( 876 PREFERENCE_CATEGORY_SERVER); 877 serverCategory.removePreference(prefOutgoing); 878 } 879 880 mSyncContacts = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CONTACTS); 881 mSyncCalendar = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CALENDAR); 882 mSyncEmail = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_EMAIL); 883 if (info.syncContacts || info.syncCalendar) { 884 if (info.syncContacts) { 885 mSyncContacts.setChecked(ContentResolver 886 .getSyncAutomatically(androidAcct, ContactsContract.AUTHORITY)); 887 mSyncContacts.setOnPreferenceChangeListener(this); 888 } else { 889 mSyncContacts.setChecked(false); 890 mSyncContacts.setEnabled(false); 891 } 892 if (info.syncCalendar) { 893 mSyncCalendar.setChecked(ContentResolver 894 .getSyncAutomatically(androidAcct, CalendarContract.AUTHORITY)); 895 mSyncCalendar.setOnPreferenceChangeListener(this); 896 } else { 897 mSyncCalendar.setChecked(false); 898 mSyncCalendar.setEnabled(false); 899 } 900 mSyncEmail.setChecked(ContentResolver 901 .getSyncAutomatically(androidAcct, EmailContent.AUTHORITY)); 902 mSyncEmail.setOnPreferenceChangeListener(this); 903 } else { 904 dataUsageCategory.removePreference(mSyncContacts); 905 dataUsageCategory.removePreference(mSyncCalendar); 906 dataUsageCategory.removePreference(mSyncEmail); 907 } 908 } 909 910 /** 911 * Called any time a preference is changed. 912 */ 913 private void preferenceChanged(String preference, Object value) { 914 mCallback.onSettingsChanged(mAccount, preference, value); 915 mSaveOnExit = true; 916 } 917 918 /* 919 * Note: This writes the settings on the UI thread. This has to be done so the settings are 920 * committed before we might be killed. 921 */ 922 private void saveSettings() { 923 // Turn off all controlled flags - will turn them back on while checking UI elements 924 int newFlags = mAccount.getFlags() & ~(Account.FLAGS_BACKGROUND_ATTACHMENTS); 925 926 newFlags |= mAccountBackgroundAttachments.isChecked() ? 927 Account.FLAGS_BACKGROUND_ATTACHMENTS : 0; 928 929 final EmailServiceInfo info = 930 EmailServiceUtils.getServiceInfo(mContext, mAccount.getProtocol(mContext)); 931 final android.accounts.Account androidAcct = new android.accounts.Account( 932 mAccount.mEmailAddress, info.accountType); 933 934 // If the display name has been cleared, we'll reset it to the default value (email addr) 935 mAccount.setDisplayName(mAccountDescription.getText().trim()); 936 // The sender name must never be empty (this is enforced by the preference editor) 937 mAccount.setSenderName(mAccountName.getText().trim()); 938 mAccount.setSignature(mAccountSignature.getText()); 939 int freq = Integer.parseInt(mCheckFrequency.getValue()); 940 if (info.syncContacts || info.syncCalendar) { 941 // This account allows syncing of contacts and/or calendar, so we will always have 942 // separate preferences to enable or disable syncing of email, contacts, and calendar. 943 // The "sync frequency" preference really just needs to control the frequency value 944 // in our database. 945 mAccount.setSyncInterval(Integer.parseInt(mCheckFrequency.getValue())); 946 } else { 947 // This account only syncs email (not contacts or calendar), which means that we will 948 // hide the preference to turn syncing on and off. In this case, we want the sync 949 // frequency preference to also control whether or not syncing is enabled at all. If 950 // sync is turned off, we will display "sync never" regardless of what the numeric 951 // value we have stored says. 952 if (freq == Account.CHECK_INTERVAL_NEVER) { 953 // Disable syncing from the account manager. Leave the current sync frequency 954 // in the database. 955 ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY, false); 956 } else { 957 // Enable syncing from the account manager. 958 ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY, true); 959 mAccount.setSyncInterval(Integer.parseInt(mCheckFrequency.getValue())); 960 } 961 } 962 if (mSyncWindow != null) { 963 mAccount.setSyncLookback(Integer.parseInt(mSyncWindow.getValue())); 964 } 965 mAccount.setFlags(newFlags); 966 967 if (info.syncContacts || info.syncCalendar) { 968 ContentResolver.setSyncAutomatically(androidAcct, ContactsContract.AUTHORITY, 969 mSyncContacts.isChecked()); 970 ContentResolver.setSyncAutomatically(androidAcct, CalendarContract.AUTHORITY, 971 mSyncCalendar.isChecked()); 972 ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY, 973 mSyncEmail.isChecked()); 974 } 975 976 // Commit the changes 977 // Note, this is done in the UI thread because at this point, we must commit 978 // all changes - any time after onPause completes, we could be killed. This is analogous 979 // to the way that SharedPreferences tries to work off-thread in apply(), but will pause 980 // until completion in onPause(). 981 ContentValues cv = AccountSettingsUtils.getAccountContentValues(mAccount); 982 mAccount.update(mContext, cv); 983 984 // Run the remaining changes off-thread 985 MailActivityEmail.setServicesEnabledAsync(mContext); 986 } 987 988 public String getAccountEmail() { 989 // Get the e-mail address of the account being editted, if this is for an existing account. 990 return mAccountEmail; 991 } 992 993 /** 994 * Shows the system ringtone picker. 995 */ 996 private void showRingtonePicker() { 997 Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); 998 final String ringtoneUri = mInboxFolderPreferences.getNotificationRingtoneUri(); 999 if (!TextUtils.isEmpty(ringtoneUri)) { 1000 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, Uri.parse(ringtoneUri)); 1001 } 1002 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true); 1003 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI, 1004 Settings.System.DEFAULT_NOTIFICATION_URI); 1005 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); 1006 intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); 1007 startActivityForResult(intent, RINGTONE_REQUEST_CODE); 1008 } 1009 } 1010