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