AccountSettingsFragment.java revision 2736c1a11ce3ecdcd9d19aa9c324fb9ce0910c7b
1/* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.email.activity.setup; 18 19import android.app.Activity; 20import android.app.AlertDialog; 21import android.app.Dialog; 22import android.app.DialogFragment; 23import android.app.Fragment; 24import android.app.FragmentTransaction; 25import android.content.ContentResolver; 26import android.content.ContentValues; 27import android.content.Context; 28import android.content.DialogInterface; 29import android.content.SharedPreferences; 30import android.content.res.Resources; 31import android.os.AsyncTask; 32import android.os.Bundle; 33import android.os.Vibrator; 34import android.preference.CheckBoxPreference; 35import android.preference.EditTextPreference; 36import android.preference.ListPreference; 37import android.preference.Preference; 38import android.preference.PreferenceCategory; 39import android.preference.PreferenceFragment; 40import android.preference.RingtonePreference; 41import android.provider.ContactsContract; 42import android.text.TextUtils; 43import android.util.Log; 44 45import com.android.email.Email; 46import com.android.email.R; 47import com.android.email.SecurityPolicy; 48import com.android.email.mail.Sender; 49import com.android.emailcommon.AccountManagerTypes; 50import com.android.emailcommon.CalendarProviderStub; 51import com.android.emailcommon.Logging; 52import com.android.emailcommon.mail.MessagingException; 53import com.android.emailcommon.provider.Account; 54import com.android.emailcommon.provider.EmailContent; 55import com.android.emailcommon.provider.HostAuth; 56import com.android.emailcommon.provider.Policy; 57import com.android.emailcommon.utility.Utility; 58 59import java.util.ArrayList; 60 61/** 62 * Fragment containing the main logic for account settings. This also calls out to other 63 * fragments for server settings. 64 * 65 * TODO: Remove or make async the mAccountDirty reload logic. Probably no longer needed. 66 * TODO: Can we defer calling addPreferencesFromResource() until after we load the account? This 67 * could reduce flicker. 68 */ 69public class AccountSettingsFragment extends PreferenceFragment { 70 71 // Keys used for arguments bundle 72 private static final String BUNDLE_KEY_ACCOUNT_ID = "AccountSettingsFragment.AccountId"; 73 private static final String BUNDLE_KEY_ACCOUNT_EMAIL = "AccountSettingsFragment.Email"; 74 75 public static final String PREFERENCE_DESCRIPTION = "account_description"; 76 private static final String PREFERENCE_NAME = "account_name"; 77 private static final String PREFERENCE_SIGNATURE = "account_signature"; 78 private static final String PREFERENCE_QUICK_RESPONSES = "account_quick_responses"; 79 private static final String PREFERENCE_FREQUENCY = "account_check_frequency"; 80 private static final String PREFERENCE_BACKGROUND_ATTACHMENTS = 81 "account_background_attachments"; 82 private static final String PREFERENCE_DEFAULT = "account_default"; 83 private static final String PREFERENCE_CATEGORY_DATA_USAGE = "data_usage"; 84 private static final String PREFERENCE_CATEGORY_NOTIFICATIONS = "account_notifications"; 85 private static final String PREFERENCE_NOTIFY = "account_notify"; 86 private static final String PREFERENCE_VIBRATE_WHEN = "account_settings_vibrate_when"; 87 private static final String PREFERENCE_RINGTONE = "account_ringtone"; 88 private static final String PREFERENCE_CATEGORY_SERVER = "account_servers"; 89 private static final String PREFERENCE_CATEGORY_POLICIES = "account_policies"; 90 private static final String PREFERENCE_POLICIES_ENFORCED = "policies_enforced"; 91 private static final String PREFERENCE_POLICIES_UNSUPPORTED = "policies_unsupported"; 92 private static final String PREFERENCE_POLICIES_RETRY_ACCOUNT = "policies_retry_account"; 93 private static final String PREFERENCE_INCOMING = "incoming"; 94 private static final String PREFERENCE_OUTGOING = "outgoing"; 95 private static final String PREFERENCE_SYNC_CONTACTS = "account_sync_contacts"; 96 private static final String PREFERENCE_SYNC_CALENDAR = "account_sync_calendar"; 97 private static final String PREFERENCE_SYNC_EMAIL = "account_sync_email"; 98 private static final String PREFERENCE_DELETE_ACCOUNT = "delete_account"; 99 100 // These strings must match account_settings_vibrate_when_* strings in strings.xml 101 private static final String PREFERENCE_VALUE_VIBRATE_WHEN_ALWAYS = "always"; 102 private static final String PREFERENCE_VALUE_VIBRATE_WHEN_SILENT = "silent"; 103 private static final String PREFERENCE_VALUE_VIBRATE_WHEN_NEVER = "never"; 104 105 private EditTextPreference mAccountDescription; 106 private EditTextPreference mAccountName; 107 private EditTextPreference mAccountSignature; 108 private ListPreference mCheckFrequency; 109 private ListPreference mSyncWindow; 110 private CheckBoxPreference mAccountBackgroundAttachments; 111 private CheckBoxPreference mAccountDefault; 112 private CheckBoxPreference mAccountNotify; 113 private ListPreference mAccountVibrateWhen; 114 private RingtonePreference mAccountRingtone; 115 private CheckBoxPreference mSyncContacts; 116 private CheckBoxPreference mSyncCalendar; 117 private CheckBoxPreference mSyncEmail; 118 119 private Context mContext; 120 private Account mAccount; 121 private boolean mAccountDirty; 122 private long mDefaultAccountId; 123 private Callback mCallback = EmptyCallback.INSTANCE; 124 private boolean mStarted; 125 private boolean mLoaded; 126 private boolean mSaveOnExit; 127 128 /** The e-mail of the account being edited. */ 129 private String mAccountEmail; 130 131 // Async Tasks 132 private AsyncTask<?,?,?> mLoadAccountTask; 133 134 /** 135 * Callback interface that owning activities must provide 136 */ 137 public interface Callback { 138 public void onSettingsChanged(Account account, String preference, Object value); 139 public void onEditQuickResponses(Account account); 140 public void onIncomingSettings(Account account); 141 public void onOutgoingSettings(Account account); 142 public void abandonEdit(); 143 public void deleteAccount(Account account); 144 } 145 146 private static class EmptyCallback implements Callback { 147 public static final Callback INSTANCE = new EmptyCallback(); 148 @Override public void onSettingsChanged(Account account, String preference, Object value) {} 149 @Override public void onEditQuickResponses(Account account) {} 150 @Override public void onIncomingSettings(Account account) {} 151 @Override public void onOutgoingSettings(Account account) {} 152 @Override public void abandonEdit() {} 153 @Override public void deleteAccount(Account account) {} 154 } 155 156 /** 157 * If launching with an arguments bundle, use this method to build the arguments. 158 */ 159 public static Bundle buildArguments(long accountId, String email) { 160 Bundle b = new Bundle(); 161 b.putLong(BUNDLE_KEY_ACCOUNT_ID, accountId); 162 b.putString(BUNDLE_KEY_ACCOUNT_EMAIL, email); 163 return b; 164 } 165 166 public static String getTitleFromArgs(Bundle args) { 167 return (args == null) ? null : args.getString(BUNDLE_KEY_ACCOUNT_EMAIL); 168 } 169 170 @Override 171 public void onAttach(Activity activity) { 172 super.onAttach(activity); 173 mContext = activity; 174 } 175 176 /** 177 * Called to do initial creation of a fragment. This is called after 178 * {@link #onAttach(Activity)} and before {@link #onActivityCreated(Bundle)}. 179 */ 180 @Override 181 public void onCreate(Bundle savedInstanceState) { 182 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 183 Log.d(Logging.LOG_TAG, "AccountSettingsFragment onCreate"); 184 } 185 super.onCreate(savedInstanceState); 186 187 // Load the preferences from an XML resource 188 addPreferencesFromResource(R.xml.account_settings_preferences); 189 190 // Start loading the account data, if provided in the arguments 191 // If not, activity must call startLoadingAccount() directly 192 Bundle b = getArguments(); 193 if (b != null) { 194 long accountId = b.getLong(BUNDLE_KEY_ACCOUNT_ID, -1); 195 mAccountEmail = b.getString(BUNDLE_KEY_ACCOUNT_EMAIL); 196 if (accountId >= 0 && !mLoaded) { 197 startLoadingAccount(accountId); 198 } 199 } 200 201 mAccountDirty = false; 202 } 203 204 @Override 205 public void onActivityCreated(Bundle savedInstanceState) { 206 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 207 Log.d(Logging.LOG_TAG, "AccountSettingsFragment onActivityCreated"); 208 } 209 super.onActivityCreated(savedInstanceState); 210 } 211 212 /** 213 * Called when the Fragment is visible to the user. 214 */ 215 @Override 216 public void onStart() { 217 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 218 Log.d(Logging.LOG_TAG, "AccountSettingsFragment onStart"); 219 } 220 super.onStart(); 221 mStarted = true; 222 223 // If the loaded account is ready now, load the UI 224 if (mAccount != null && !mLoaded) { 225 loadSettings(); 226 } 227 } 228 229 /** 230 * Called when the fragment is visible to the user and actively running. 231 * TODO: Don't read account data on UI thread. This should be fixed by removing the need 232 * to do this, not by spinning up yet another thread. 233 */ 234 @Override 235 public void onResume() { 236 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 237 Log.d(Logging.LOG_TAG, "AccountSettingsFragment onResume"); 238 } 239 super.onResume(); 240 241 if (mAccountDirty) { 242 // if we are coming back from editing incoming or outgoing settings, 243 // we need to refresh them here so we don't accidentally overwrite the 244 // old values we're still holding here 245 mAccount.mHostAuthRecv = 246 HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv); 247 mAccount.mHostAuthSend = 248 HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeySend); 249 // Because "delete policy" UI is on edit incoming settings, we have 250 // to refresh that as well. 251 Account refreshedAccount = Account.restoreAccountWithId(mContext, mAccount.mId); 252 if (refreshedAccount == null || mAccount.mHostAuthRecv == null 253 || mAccount.mHostAuthSend == null) { 254 mSaveOnExit = false; 255 mCallback.abandonEdit(); 256 return; 257 } 258 mAccount.setDeletePolicy(refreshedAccount.getDeletePolicy()); 259 mAccountDirty = false; 260 } 261 } 262 263 @Override 264 public void onPause() { 265 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 266 Log.d(Logging.LOG_TAG, "AccountSettingsFragment onPause"); 267 } 268 super.onPause(); 269 if (mSaveOnExit) { 270 saveSettings(); 271 } 272 } 273 274 /** 275 * Called when the Fragment is no longer started. 276 */ 277 @Override 278 public void onStop() { 279 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 280 Log.d(Logging.LOG_TAG, "AccountSettingsFragment onStop"); 281 } 282 super.onStop(); 283 mStarted = false; 284 } 285 286 /** 287 * Called when the fragment is no longer in use. 288 */ 289 @Override 290 public void onDestroy() { 291 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 292 Log.d(Logging.LOG_TAG, "AccountSettingsFragment onDestroy"); 293 } 294 super.onDestroy(); 295 296 Utility.cancelTaskInterrupt(mLoadAccountTask); 297 mLoadAccountTask = null; 298 } 299 300 @Override 301 public void onSaveInstanceState(Bundle outState) { 302 if (Logging.DEBUG_LIFECYCLE && Email.DEBUG) { 303 Log.d(Logging.LOG_TAG, "AccountSettingsFragment onSaveInstanceState"); 304 } 305 super.onSaveInstanceState(outState); 306 } 307 308 /** 309 * Activity provides callbacks here 310 */ 311 public void setCallback(Callback callback) { 312 mCallback = (callback == null) ? EmptyCallback.INSTANCE : callback; 313 } 314 315 /** 316 * Start loading a single account in preparation for editing it 317 */ 318 public void startLoadingAccount(long accountId) { 319 Utility.cancelTaskInterrupt(mLoadAccountTask); 320 mLoadAccountTask = new LoadAccountTask().executeOnExecutor( 321 AsyncTask.THREAD_POOL_EXECUTOR, accountId); 322 } 323 324 /** 325 * Async task to load account in order to view/edit it 326 */ 327 private class LoadAccountTask extends AsyncTask<Long, Void, Object[]> { 328 @Override 329 protected Object[] doInBackground(Long... params) { 330 long accountId = params[0]; 331 Account account = Account.restoreAccountWithId(mContext, accountId); 332 if (account != null) { 333 account.mHostAuthRecv = 334 HostAuth.restoreHostAuthWithId(mContext, account.mHostAuthKeyRecv); 335 account.mHostAuthSend = 336 HostAuth.restoreHostAuthWithId(mContext, account.mHostAuthKeySend); 337 if (account.mHostAuthRecv == null || account.mHostAuthSend == null) { 338 account = null; 339 } 340 } 341 long defaultAccountId = Account.getDefaultAccountId(mContext); 342 return new Object[] { account, Long.valueOf(defaultAccountId) }; 343 } 344 345 @Override 346 protected void onPostExecute(Object[] results) { 347 if (results != null && !isCancelled()) { 348 Account account = (Account) results[0]; 349 if (account == null) { 350 mSaveOnExit = false; 351 mCallback.abandonEdit(); 352 } else { 353 mAccount = account; 354 mDefaultAccountId = (Long) results[1]; 355 if (mStarted && !mLoaded) { 356 loadSettings(); 357 } 358 } 359 } 360 } 361 } 362 363 /** 364 * From a Policy, create and return an ArrayList of Strings that describe (simply) those 365 * policies that are supported by the OS. At the moment, the strings are simple (e.g. 366 * "password required"); we should probably add more information (# characters, etc.), though 367 */ 368 private ArrayList<String> getSystemPoliciesList(Policy policy) { 369 Resources res = mContext.getResources(); 370 ArrayList<String> policies = new ArrayList<String>(); 371 if (policy.mPasswordMode != Policy.PASSWORD_MODE_NONE) { 372 policies.add(res.getString(R.string.policy_require_password)); 373 } 374 if (policy.mPasswordHistory > 0) { 375 policies.add(res.getString(R.string.policy_password_history)); 376 } 377 if (policy.mPasswordExpirationDays > 0) { 378 policies.add(res.getString(R.string.policy_password_expiration)); 379 } 380 if (policy.mMaxScreenLockTime > 0) { 381 policies.add(res.getString(R.string.policy_screen_timeout)); 382 } 383 if (policy.mDontAllowCamera) { 384 policies.add(res.getString(R.string.policy_dont_allow_camera)); 385 } 386 return policies; 387 } 388 389 private void setPolicyListSummary(ArrayList<String> policies, String policiesToAdd, 390 String preferenceName) { 391 Policy.addPolicyStringToList(policiesToAdd, policies); 392 if (policies.size() > 0) { 393 Preference p = findPreference(preferenceName); 394 StringBuilder sb = new StringBuilder(); 395 for (String desc: policies) { 396 sb.append(desc); 397 sb.append('\n'); 398 } 399 p.setSummary(sb.toString()); 400 } 401 } 402 403 /** 404 * Load account data into preference UI 405 */ 406 private void loadSettings() { 407 // We can only do this once, so prevent repeat 408 mLoaded = true; 409 // Once loaded the data is ready to be saved, as well 410 mSaveOnExit = false; 411 412 mAccountDescription = (EditTextPreference) findPreference(PREFERENCE_DESCRIPTION); 413 mAccountDescription.setSummary(mAccount.getDisplayName()); 414 mAccountDescription.setText(mAccount.getDisplayName()); 415 mAccountDescription.setOnPreferenceChangeListener( 416 new Preference.OnPreferenceChangeListener() { 417 public boolean onPreferenceChange(Preference preference, Object newValue) { 418 String summary = newValue.toString().trim(); 419 if (TextUtils.isEmpty(summary)) { 420 summary = mAccount.mEmailAddress; 421 } 422 mAccountDescription.setSummary(summary); 423 mAccountDescription.setText(summary); 424 onPreferenceChanged(PREFERENCE_DESCRIPTION, summary); 425 return false; 426 } 427 } 428 ); 429 430 mAccountName = (EditTextPreference) findPreference(PREFERENCE_NAME); 431 String senderName = mAccount.getSenderName(); 432 // In rare cases, sendername will be null; Change this to empty string to avoid NPE's 433 if (senderName == null) senderName = ""; 434 mAccountName.setSummary(senderName); 435 mAccountName.setText(senderName); 436 mAccountName.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { 437 public boolean onPreferenceChange(Preference preference, Object newValue) { 438 final String summary = newValue.toString().trim(); 439 if (!TextUtils.isEmpty(summary)) { 440 mAccountName.setSummary(summary); 441 mAccountName.setText(summary); 442 onPreferenceChanged(PREFERENCE_NAME, summary); 443 } 444 return false; 445 } 446 }); 447 448 mAccountSignature = (EditTextPreference) findPreference(PREFERENCE_SIGNATURE); 449 mAccountSignature.setText(mAccount.getSignature()); 450 mAccountSignature.setOnPreferenceChangeListener( 451 new Preference.OnPreferenceChangeListener() { 452 public boolean onPreferenceChange(Preference preference, Object newValue) { 453 // Clean up signature if it's only whitespace (which is easy to do on a 454 // soft keyboard) but leave whitespace in place otherwise, to give the user 455 // maximum flexibility, e.g. the ability to indent 456 String signature = newValue.toString(); 457 if (signature.trim().isEmpty()) { 458 signature = ""; 459 } 460 mAccountSignature.setText(signature); 461 onPreferenceChanged(PREFERENCE_SIGNATURE, signature); 462 return false; 463 } 464 }); 465 466 mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY); 467 468 // TODO Move protocol into Account to avoid retrieving the HostAuth (implicitly) 469 String protocol = Account.getProtocol(mContext, mAccount.mId); 470 if (HostAuth.SCHEME_EAS.equals(protocol)) { 471 mCheckFrequency.setEntries(R.array.account_settings_check_frequency_entries_push); 472 mCheckFrequency.setEntryValues(R.array.account_settings_check_frequency_values_push); 473 } 474 475 mCheckFrequency.setValue(String.valueOf(mAccount.getSyncInterval())); 476 mCheckFrequency.setSummary(mCheckFrequency.getEntry()); 477 mCheckFrequency.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { 478 public boolean onPreferenceChange(Preference preference, Object newValue) { 479 final String summary = newValue.toString(); 480 int index = mCheckFrequency.findIndexOfValue(summary); 481 mCheckFrequency.setSummary(mCheckFrequency.getEntries()[index]); 482 mCheckFrequency.setValue(summary); 483 onPreferenceChanged(PREFERENCE_FREQUENCY, newValue); 484 return false; 485 } 486 }); 487 488 findPreference(PREFERENCE_QUICK_RESPONSES).setOnPreferenceClickListener( 489 new Preference.OnPreferenceClickListener() { 490 @Override 491 public boolean onPreferenceClick(Preference preference) { 492 mAccountDirty = true; 493 mCallback.onEditQuickResponses(mAccount); 494 return true; 495 } 496 }); 497 498 // Add check window preference 499 PreferenceCategory dataUsageCategory = 500 (PreferenceCategory) findPreference(PREFERENCE_CATEGORY_DATA_USAGE); 501 502 mSyncWindow = null; 503 if (HostAuth.SCHEME_EAS.equals(protocol)) { 504 mSyncWindow = new ListPreference(mContext); 505 mSyncWindow.setTitle(R.string.account_setup_options_mail_window_label); 506 mSyncWindow.setEntries(R.array.account_settings_mail_window_entries); 507 mSyncWindow.setEntryValues(R.array.account_settings_mail_window_values); 508 mSyncWindow.setValue(String.valueOf(mAccount.getSyncLookback())); 509 mSyncWindow.setSummary(mSyncWindow.getEntry()); 510 511 // Must correspond to the hole in the XML file that's reserved. 512 mSyncWindow.setOrder(2); 513 mSyncWindow.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { 514 public boolean onPreferenceChange(Preference preference, Object newValue) { 515 final String summary = newValue.toString(); 516 int index = mSyncWindow.findIndexOfValue(summary); 517 mSyncWindow.setSummary(mSyncWindow.getEntries()[index]); 518 mSyncWindow.setValue(summary); 519 onPreferenceChanged(preference.getKey(), newValue); 520 return false; 521 } 522 }); 523 dataUsageCategory.addPreference(mSyncWindow); 524 } 525 526 // Show "background attachments" for IMAP & EAS - hide it for POP3. 527 mAccountBackgroundAttachments = (CheckBoxPreference) 528 findPreference(PREFERENCE_BACKGROUND_ATTACHMENTS); 529 if (HostAuth.SCHEME_POP3.equals(mAccount.mHostAuthRecv.mProtocol)) { 530 dataUsageCategory.removePreference(mAccountBackgroundAttachments); 531 } else { 532 mAccountBackgroundAttachments.setChecked( 533 0 != (mAccount.getFlags() & Account.FLAGS_BACKGROUND_ATTACHMENTS)); 534 mAccountBackgroundAttachments.setOnPreferenceChangeListener(mPreferenceChangeListener); 535 } 536 537 mAccountDefault = (CheckBoxPreference) findPreference(PREFERENCE_DEFAULT); 538 mAccountDefault.setChecked(mAccount.mId == mDefaultAccountId); 539 mAccountDefault.setOnPreferenceChangeListener(mPreferenceChangeListener); 540 541 mAccountNotify = (CheckBoxPreference) findPreference(PREFERENCE_NOTIFY); 542 mAccountNotify.setChecked(0 != (mAccount.getFlags() & Account.FLAGS_NOTIFY_NEW_MAIL)); 543 mAccountNotify.setOnPreferenceChangeListener(mPreferenceChangeListener); 544 545 mAccountRingtone = (RingtonePreference) findPreference(PREFERENCE_RINGTONE); 546 mAccountRingtone.setOnPreferenceChangeListener(mPreferenceChangeListener); 547 548 // The following two lines act as a workaround for the RingtonePreference 549 // which does not let us set/get the value programmatically 550 SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences(); 551 prefs.edit().putString(PREFERENCE_RINGTONE, mAccount.getRingtone()).apply(); 552 553 // Set the vibrator value, or hide it on devices w/o a vibrator 554 mAccountVibrateWhen = (ListPreference) findPreference(PREFERENCE_VIBRATE_WHEN); 555 Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE); 556 if (vibrator.hasVibrator()) { 557 boolean flagsVibrate = 0 != (mAccount.getFlags() & Account.FLAGS_VIBRATE_ALWAYS); 558 boolean flagsVibrateSilent = 559 0 != (mAccount.getFlags() & Account.FLAGS_VIBRATE_WHEN_SILENT); 560 mAccountVibrateWhen.setValue( 561 flagsVibrate ? PREFERENCE_VALUE_VIBRATE_WHEN_ALWAYS : 562 flagsVibrateSilent ? PREFERENCE_VALUE_VIBRATE_WHEN_SILENT : 563 PREFERENCE_VALUE_VIBRATE_WHEN_NEVER); 564 mAccountVibrateWhen.setOnPreferenceChangeListener(mPreferenceChangeListener); 565 } else { 566 PreferenceCategory notificationsCategory = (PreferenceCategory) 567 findPreference(PREFERENCE_CATEGORY_NOTIFICATIONS); 568 notificationsCategory.removePreference(mAccountVibrateWhen); 569 } 570 571 final Preference retryAccount = findPreference(PREFERENCE_POLICIES_RETRY_ACCOUNT); 572 final PreferenceCategory policiesCategory = (PreferenceCategory) findPreference( 573 PREFERENCE_CATEGORY_POLICIES); 574 if (mAccount.mPolicyKey > 0) { 575 // Make sure we have most recent data from account 576 mAccount.refresh(mContext); 577 Policy policy = Policy.restorePolicyWithId(mContext, mAccount.mPolicyKey); 578 if (policy == null) { 579 // The account has been deleted? Crazy, but not impossible 580 return; 581 } 582 if (policy.mProtocolPoliciesEnforced != null) { 583 ArrayList<String> policies = getSystemPoliciesList(policy); 584 setPolicyListSummary(policies, policy.mProtocolPoliciesEnforced, 585 PREFERENCE_POLICIES_ENFORCED); 586 } 587 if (policy.mProtocolPoliciesUnsupported != null) { 588 ArrayList<String> policies = new ArrayList<String>(); 589 setPolicyListSummary(policies, policy.mProtocolPoliciesUnsupported, 590 PREFERENCE_POLICIES_UNSUPPORTED); 591 } else { 592 // Don't show "retry" unless we have unsupported policies 593 policiesCategory.removePreference(retryAccount); 594 } 595 } else { 596 // Remove the category completely if there are no policies 597 getPreferenceScreen().removePreference(policiesCategory); 598 } 599 600 retryAccount.setOnPreferenceClickListener( 601 new Preference.OnPreferenceClickListener() { 602 public boolean onPreferenceClick(Preference preference) { 603 // Release the account 604 SecurityPolicy.setAccountHoldFlag(mContext, mAccount, false); 605 // Remove the preference 606 policiesCategory.removePreference(retryAccount); 607 return true; 608 } 609 }); 610 findPreference(PREFERENCE_INCOMING).setOnPreferenceClickListener( 611 new Preference.OnPreferenceClickListener() { 612 public boolean onPreferenceClick(Preference preference) { 613 mAccountDirty = true; 614 mCallback.onIncomingSettings(mAccount); 615 return true; 616 } 617 }); 618 619 // Hide the outgoing account setup link if it's not activated 620 Preference prefOutgoing = findPreference(PREFERENCE_OUTGOING); 621 boolean showOutgoing = true; 622 try { 623 Sender sender = Sender.getInstance(mContext, mAccount); 624 if (sender != null) { 625 Class<? extends android.app.Activity> setting = sender.getSettingActivityClass(); 626 showOutgoing = (setting != null); 627 } 628 } catch (MessagingException me) { 629 // just leave showOutgoing as true - bias towards showing it, so user can fix it 630 } 631 if (showOutgoing) { 632 prefOutgoing.setOnPreferenceClickListener( 633 new Preference.OnPreferenceClickListener() { 634 public boolean onPreferenceClick(Preference preference) { 635 mAccountDirty = true; 636 mCallback.onOutgoingSettings(mAccount); 637 return true; 638 } 639 }); 640 } else { 641 PreferenceCategory serverCategory = (PreferenceCategory) findPreference( 642 PREFERENCE_CATEGORY_SERVER); 643 serverCategory.removePreference(prefOutgoing); 644 } 645 646 mSyncContacts = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CONTACTS); 647 mSyncCalendar = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_CALENDAR); 648 mSyncEmail = (CheckBoxPreference) findPreference(PREFERENCE_SYNC_EMAIL); 649 if (mAccount.mHostAuthRecv.mProtocol.equals(HostAuth.SCHEME_EAS)) { 650 android.accounts.Account acct = new android.accounts.Account(mAccount.mEmailAddress, 651 AccountManagerTypes.TYPE_EXCHANGE); 652 mSyncContacts.setChecked(ContentResolver 653 .getSyncAutomatically(acct, ContactsContract.AUTHORITY)); 654 mSyncContacts.setOnPreferenceChangeListener(mPreferenceChangeListener); 655 mSyncCalendar.setChecked(ContentResolver 656 .getSyncAutomatically(acct, CalendarProviderStub.AUTHORITY)); 657 mSyncCalendar.setOnPreferenceChangeListener(mPreferenceChangeListener); 658 mSyncEmail.setChecked(ContentResolver 659 .getSyncAutomatically(acct, EmailContent.AUTHORITY)); 660 mSyncEmail.setOnPreferenceChangeListener(mPreferenceChangeListener); 661 } else { 662 dataUsageCategory.removePreference(mSyncContacts); 663 dataUsageCategory.removePreference(mSyncCalendar); 664 dataUsageCategory.removePreference(mSyncEmail); 665 } 666 667 // Temporary home for delete account 668 Preference prefDeleteAccount = findPreference(PREFERENCE_DELETE_ACCOUNT); 669 prefDeleteAccount.setOnPreferenceClickListener( 670 new Preference.OnPreferenceClickListener() { 671 public boolean onPreferenceClick(Preference preference) { 672 DeleteAccountFragment dialogFragment = DeleteAccountFragment.newInstance( 673 mAccount, AccountSettingsFragment.this); 674 FragmentTransaction ft = getFragmentManager().beginTransaction(); 675 ft.addToBackStack(null); 676 dialogFragment.show(ft, DeleteAccountFragment.TAG); 677 return true; 678 } 679 }); 680 } 681 682 /** 683 * Generic onPreferenceChanged listener for the preferences (above) that just need 684 * to be written, without extra tweaks 685 */ 686 private final Preference.OnPreferenceChangeListener mPreferenceChangeListener = 687 new Preference.OnPreferenceChangeListener() { 688 public boolean onPreferenceChange(Preference preference, Object newValue) { 689 onPreferenceChanged(preference.getKey(), newValue); 690 return true; 691 } 692 }; 693 694 /** 695 * Called any time a preference is changed. 696 */ 697 private void onPreferenceChanged(String preference, Object value) { 698 mCallback.onSettingsChanged(mAccount, preference, value); 699 mSaveOnExit = true; 700 } 701 702 /* 703 * Note: This writes the settings on the UI thread. This has to be done so the settings are 704 * committed before we might be killed. 705 */ 706 private void saveSettings() { 707 // Turn off all controlled flags - will turn them back on while checking UI elements 708 int newFlags = mAccount.getFlags() & 709 ~(Account.FLAGS_NOTIFY_NEW_MAIL | 710 Account.FLAGS_VIBRATE_ALWAYS | Account.FLAGS_VIBRATE_WHEN_SILENT | 711 Account.FLAGS_BACKGROUND_ATTACHMENTS); 712 713 newFlags |= mAccountBackgroundAttachments.isChecked() ? 714 Account.FLAGS_BACKGROUND_ATTACHMENTS : 0; 715 mAccount.setDefaultAccount(mAccountDefault.isChecked()); 716 // If the display name has been cleared, we'll reset it to the default value (email addr) 717 mAccount.setDisplayName(mAccountDescription.getText().trim()); 718 // The sender name must never be empty (this is enforced by the preference editor) 719 mAccount.setSenderName(mAccountName.getText().trim()); 720 mAccount.setSignature(mAccountSignature.getText()); 721 newFlags |= mAccountNotify.isChecked() ? Account.FLAGS_NOTIFY_NEW_MAIL : 0; 722 mAccount.setSyncInterval(Integer.parseInt(mCheckFrequency.getValue())); 723 if (mSyncWindow != null) { 724 mAccount.setSyncLookback(Integer.parseInt(mSyncWindow.getValue())); 725 } 726 if (mAccountVibrateWhen.getValue().equals(PREFERENCE_VALUE_VIBRATE_WHEN_ALWAYS)) { 727 newFlags |= Account.FLAGS_VIBRATE_ALWAYS; 728 } else if (mAccountVibrateWhen.getValue().equals(PREFERENCE_VALUE_VIBRATE_WHEN_SILENT)) { 729 newFlags |= Account.FLAGS_VIBRATE_WHEN_SILENT; 730 } 731 SharedPreferences prefs = mAccountRingtone.getPreferenceManager().getSharedPreferences(); 732 mAccount.setRingtone(prefs.getString(PREFERENCE_RINGTONE, null)); 733 mAccount.setFlags(newFlags); 734 735 if (mAccount.mHostAuthRecv.mProtocol.equals("eas")) { 736 android.accounts.Account acct = new android.accounts.Account(mAccount.mEmailAddress, 737 AccountManagerTypes.TYPE_EXCHANGE); 738 ContentResolver.setSyncAutomatically(acct, ContactsContract.AUTHORITY, 739 mSyncContacts.isChecked()); 740 ContentResolver.setSyncAutomatically(acct, CalendarProviderStub.AUTHORITY, 741 mSyncCalendar.isChecked()); 742 ContentResolver.setSyncAutomatically(acct, EmailContent.AUTHORITY, 743 mSyncEmail.isChecked()); 744 } 745 746 // Commit the changes 747 // Note, this is done in the UI thread because at this point, we must commit 748 // all changes - any time after onPause completes, we could be killed. This is analogous 749 // to the way that SharedPreferences tries to work off-thread in apply(), but will pause 750 // until completion in onPause(). 751 ContentValues cv = AccountSettingsUtils.getAccountContentValues(mAccount); 752 mAccount.update(mContext, cv); 753 754 // Run the remaining changes off-thread 755 Email.setServicesEnabledAsync(mContext); 756 } 757 758 /** 759 * Dialog fragment to show "remove account?" dialog 760 */ 761 public static class DeleteAccountFragment extends DialogFragment { 762 private final static String TAG = "DeleteAccountFragment"; 763 764 // Argument bundle keys 765 private final static String BUNDLE_KEY_ACCOUNT_NAME = "DeleteAccountFragment.Name"; 766 767 /** 768 * Create the dialog with parameters 769 */ 770 public static DeleteAccountFragment newInstance(Account account, Fragment parentFragment) { 771 DeleteAccountFragment f = new DeleteAccountFragment(); 772 Bundle b = new Bundle(); 773 b.putString(BUNDLE_KEY_ACCOUNT_NAME, account.getDisplayName()); 774 f.setArguments(b); 775 f.setTargetFragment(parentFragment, 0); 776 return f; 777 } 778 779 @Override 780 public Dialog onCreateDialog(Bundle savedInstanceState) { 781 Context context = getActivity(); 782 final String name = getArguments().getString(BUNDLE_KEY_ACCOUNT_NAME); 783 784 return new AlertDialog.Builder(context) 785 .setIconAttribute(android.R.attr.alertDialogIcon) 786 .setTitle(R.string.account_delete_dlg_title) 787 .setMessage(context.getString(R.string.account_delete_dlg_instructions_fmt, name)) 788 .setPositiveButton( 789 R.string.okay_action, 790 new DialogInterface.OnClickListener() { 791 public void onClick(DialogInterface dialog, int whichButton) { 792 Fragment f = getTargetFragment(); 793 if (f instanceof AccountSettingsFragment) { 794 ((AccountSettingsFragment)f).finishDeleteAccount(); 795 } 796 dismiss(); 797 } 798 }) 799 .setNegativeButton( 800 R.string.cancel_action, 801 new DialogInterface.OnClickListener() { 802 public void onClick(DialogInterface dialog, int whichButton) { 803 dismiss(); 804 } 805 }) 806 .create(); 807 } 808 } 809 810 /** 811 * Callback from delete account dialog - passes the delete command up to the activity 812 */ 813 private void finishDeleteAccount() { 814 mSaveOnExit = false; 815 mCallback.deleteAccount(mAccount); 816 } 817 818 public String getAccountEmail() { 819 // Get the e-mail address of the account being editted, if this is for an existing account. 820 return mAccountEmail; 821 } 822} 823