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