• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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