Home | History | Annotate | Download | only in preference
      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.contacts.preference;
     18 
     19 import android.app.backup.BackupAgent;
     20 import android.app.backup.BackupManager;
     21 import android.content.Context;
     22 import android.content.SharedPreferences;
     23 import android.content.SharedPreferences.Editor;
     24 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
     25 import android.os.Handler;
     26 import android.os.Looper;
     27 import android.preference.PreferenceManager;
     28 import android.provider.Settings;
     29 import android.provider.Settings.SettingNotFoundException;
     30 import android.support.annotation.NonNull;
     31 import android.support.annotation.VisibleForTesting;
     32 import android.text.TextUtils;
     33 
     34 import com.android.contacts.R;
     35 import com.android.contacts.model.account.AccountWithDataSet;
     36 
     37 import java.util.List;
     38 
     39 /**
     40  * Manages user preferences for contacts.
     41  */
     42 public class ContactsPreferences implements OnSharedPreferenceChangeListener {
     43 
     44     /**
     45      * The value for the DISPLAY_ORDER key to show the given name first.
     46      */
     47     public static final int DISPLAY_ORDER_PRIMARY = 1;
     48 
     49     /**
     50      * The value for the DISPLAY_ORDER key to show the family name first.
     51      */
     52     public static final int DISPLAY_ORDER_ALTERNATIVE = 2;
     53 
     54     public static final String DISPLAY_ORDER_KEY = "android.contacts.DISPLAY_ORDER";
     55 
     56     /**
     57      * The value for the SORT_ORDER key corresponding to sort by given name first.
     58      */
     59     public static final int SORT_ORDER_PRIMARY = 1;
     60 
     61     public static final String SORT_ORDER_KEY = "android.contacts.SORT_ORDER";
     62 
     63     /**
     64      * The value for the SORT_ORDER key corresponding to sort by family name first.
     65      */
     66     public static final int SORT_ORDER_ALTERNATIVE = 2;
     67 
     68     public static final String PREF_DISPLAY_ONLY_PHONES = "only_phones";
     69 
     70     public static final boolean PREF_DISPLAY_ONLY_PHONES_DEFAULT = false;
     71 
     72     public static final String PHONETIC_NAME_DISPLAY_KEY = "Phonetic_name_display";
     73 
     74     /**
     75      * Value to use when a preference is unassigned and needs to be read from the shared preferences
     76      */
     77     private static final int PREFERENCE_UNASSIGNED = -1;
     78 
     79     private final Context mContext;
     80     private int mSortOrder = PREFERENCE_UNASSIGNED;
     81     private int mDisplayOrder = PREFERENCE_UNASSIGNED;
     82     private int mPhoneticNameDisplayPreference = PREFERENCE_UNASSIGNED;
     83 
     84     private AccountWithDataSet mDefaultAccount = null;
     85     private ChangeListener mListener = null;
     86     private Handler mHandler;
     87     private final SharedPreferences mPreferences;
     88     private final BackupManager mBackupManager;
     89     private final boolean mIsDefaultAccountUserChangeable;
     90     private String mDefaultAccountKey;
     91 
     92     public ContactsPreferences(Context context) {
     93         this(context,
     94                 context.getResources().getBoolean(R.bool.config_default_account_user_changeable));
     95     }
     96 
     97     @VisibleForTesting
     98     ContactsPreferences(Context context, boolean isDefaultAccountUserChangeable) {
     99         mContext = context;
    100         mIsDefaultAccountUserChangeable = isDefaultAccountUserChangeable;
    101 
    102         mBackupManager = new BackupManager(mContext);
    103 
    104         mHandler = new Handler(Looper.getMainLooper());
    105         mPreferences = mContext.getSharedPreferences(context.getPackageName(),
    106                 Context.MODE_PRIVATE);
    107         mDefaultAccountKey = mContext.getResources().getString(
    108                 R.string.contact_editor_default_account_key);
    109         maybeMigrateSystemSettings();
    110     }
    111 
    112     public boolean isSortOrderUserChangeable() {
    113         return mContext.getResources().getBoolean(R.bool.config_sort_order_user_changeable);
    114     }
    115 
    116     public int getDefaultSortOrder() {
    117         if (mContext.getResources().getBoolean(R.bool.config_default_sort_order_primary)) {
    118             return SORT_ORDER_PRIMARY;
    119         } else {
    120             return SORT_ORDER_ALTERNATIVE;
    121         }
    122     }
    123 
    124     public int getSortOrder() {
    125         if (!isSortOrderUserChangeable()) {
    126             return getDefaultSortOrder();
    127         }
    128         if (mSortOrder == PREFERENCE_UNASSIGNED) {
    129             mSortOrder = mPreferences.getInt(SORT_ORDER_KEY, getDefaultSortOrder());
    130         }
    131         return mSortOrder;
    132     }
    133 
    134     public void setSortOrder(int sortOrder) {
    135         mSortOrder = sortOrder;
    136         final Editor editor = mPreferences.edit();
    137         editor.putInt(SORT_ORDER_KEY, sortOrder);
    138         editor.commit();
    139         mBackupManager.dataChanged();
    140     }
    141 
    142     public boolean isDisplayOrderUserChangeable() {
    143         return mContext.getResources().getBoolean(R.bool.config_display_order_user_changeable);
    144     }
    145 
    146     public int getDefaultDisplayOrder() {
    147         if (mContext.getResources().getBoolean(R.bool.config_default_display_order_primary)) {
    148             return DISPLAY_ORDER_PRIMARY;
    149         } else {
    150             return DISPLAY_ORDER_ALTERNATIVE;
    151         }
    152     }
    153 
    154     public int getDisplayOrder() {
    155         if (!isDisplayOrderUserChangeable()) {
    156             return getDefaultDisplayOrder();
    157         }
    158         if (mDisplayOrder == PREFERENCE_UNASSIGNED) {
    159             mDisplayOrder = mPreferences.getInt(DISPLAY_ORDER_KEY, getDefaultDisplayOrder());
    160         }
    161         return mDisplayOrder;
    162     }
    163 
    164     public void setDisplayOrder(int displayOrder) {
    165         mDisplayOrder = displayOrder;
    166         final Editor editor = mPreferences.edit();
    167         editor.putInt(DISPLAY_ORDER_KEY, displayOrder);
    168         editor.commit();
    169         mBackupManager.dataChanged();
    170     }
    171 
    172     public int getDefaultPhoneticNameDisplayPreference() {
    173         if (mContext.getResources().getBoolean(R.bool.config_default_hide_phonetic_name_if_empty)) {
    174             return PhoneticNameDisplayPreference.HIDE_IF_EMPTY;
    175         } else {
    176             return PhoneticNameDisplayPreference.SHOW_ALWAYS;
    177         }
    178     }
    179 
    180     public boolean isPhoneticNameDisplayPreferenceChangeable() {
    181         return mContext.getResources().getBoolean(
    182                 R.bool.config_phonetic_name_display_user_changeable);
    183     }
    184 
    185     public void setPhoneticNameDisplayPreference(int phoneticNameDisplayPreference) {
    186         mPhoneticNameDisplayPreference = phoneticNameDisplayPreference;
    187         final Editor editor = mPreferences.edit();
    188         editor.putInt(PHONETIC_NAME_DISPLAY_KEY, phoneticNameDisplayPreference);
    189         editor.commit();
    190         mBackupManager.dataChanged();
    191     }
    192 
    193     public int getPhoneticNameDisplayPreference() {
    194         if (!isPhoneticNameDisplayPreferenceChangeable()) {
    195             return getDefaultPhoneticNameDisplayPreference();
    196         }
    197         if (mPhoneticNameDisplayPreference == PREFERENCE_UNASSIGNED) {
    198             mPhoneticNameDisplayPreference = mPreferences.getInt(PHONETIC_NAME_DISPLAY_KEY,
    199                     getDefaultPhoneticNameDisplayPreference());
    200         }
    201         return mPhoneticNameDisplayPreference;
    202     }
    203 
    204     public boolean shouldHidePhoneticNamesIfEmpty() {
    205         return getPhoneticNameDisplayPreference() == PhoneticNameDisplayPreference.HIDE_IF_EMPTY;
    206     }
    207 
    208     public boolean isDefaultAccountUserChangeable() {
    209         return mIsDefaultAccountUserChangeable;
    210     }
    211 
    212     public AccountWithDataSet getDefaultAccount() {
    213         if (!isDefaultAccountUserChangeable()) {
    214             return mDefaultAccount;
    215         }
    216         if (mDefaultAccount == null) {
    217             final String accountString = mPreferences
    218                     .getString(mDefaultAccountKey, null);
    219             if (!TextUtils.isEmpty(accountString)) {
    220                 mDefaultAccount = AccountWithDataSet.unstringify(accountString);
    221             }
    222         }
    223         return mDefaultAccount;
    224     }
    225 
    226     public void clearDefaultAccount() {
    227         mDefaultAccount = null;
    228         mPreferences.edit().remove(mDefaultAccountKey).commit();
    229     }
    230 
    231     public void setDefaultAccount(@NonNull AccountWithDataSet accountWithDataSet) {
    232         if (accountWithDataSet == null) {
    233             throw new IllegalArgumentException(
    234                     "argument should not be null");
    235         }
    236         mDefaultAccount = accountWithDataSet;
    237         mPreferences.edit().putString(mDefaultAccountKey, accountWithDataSet.stringify()).commit();
    238     }
    239 
    240     public boolean isDefaultAccountSet() {
    241         return mDefaultAccount != null || mPreferences.contains(mDefaultAccountKey);
    242     }
    243 
    244     /**
    245      * @return false if there is only one writable account or no requirement to return true is met.
    246      *         true if the contact editor should show the "accounts changed" notification, that is:
    247      *              - If it's the first launch.
    248      *              - Or, if the default account has been removed.
    249      *              (And some extra sanity check)
    250      *
    251      * Note if this method returns {@code false}, the caller can safely assume that
    252      * {@link #getDefaultAccount} will return a valid account.  (Either an account which still
    253      * exists, or {@code null} which should be interpreted as "local only".)
    254      */
    255     public boolean shouldShowAccountChangedNotification(List<AccountWithDataSet>
    256             currentWritableAccounts) {
    257         final AccountWithDataSet defaultAccount = getDefaultAccount();
    258 
    259         // This shouldn't occur anymore because a "device" account is added in the case that there
    260         // are no other accounts but if there are no writable accounts then the default has been
    261         // initialized if it is "device"
    262         if (currentWritableAccounts.isEmpty()) {
    263             return defaultAccount == null || !defaultAccount.isNullAccount();
    264         }
    265 
    266         if (currentWritableAccounts.size() == 1
    267                 && !currentWritableAccounts.get(0).isNullAccount()) {
    268             return false;
    269         }
    270 
    271         if (defaultAccount == null) {
    272             return true;
    273         }
    274 
    275         if (!currentWritableAccounts.contains(defaultAccount)) {
    276             return true;
    277         }
    278 
    279         // All good.
    280         return false;
    281     }
    282 
    283     public void registerChangeListener(ChangeListener listener) {
    284         if (mListener != null) unregisterChangeListener();
    285 
    286         mListener = listener;
    287 
    288         // Reset preferences to "unknown" because they may have changed while the
    289         // listener was unregistered.
    290         mDisplayOrder = PREFERENCE_UNASSIGNED;
    291         mSortOrder = PREFERENCE_UNASSIGNED;
    292         mPhoneticNameDisplayPreference = PREFERENCE_UNASSIGNED;
    293         mDefaultAccount = null;
    294 
    295         mPreferences.registerOnSharedPreferenceChangeListener(this);
    296     }
    297 
    298     public void unregisterChangeListener() {
    299         if (mListener != null) {
    300             mListener = null;
    301         }
    302 
    303         mPreferences.unregisterOnSharedPreferenceChangeListener(this);
    304     }
    305 
    306     @Override
    307     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
    308         // This notification is not sent on the Ui thread. Use the previously created Handler
    309         // to switch to the Ui thread
    310         mHandler.post(new Runnable() {
    311             @Override
    312             public void run() {
    313                 refreshValue(key);
    314             }
    315         });
    316     }
    317 
    318     /**
    319      * Forces the value for the given key to be looked up from shared preferences and notifies
    320      * the registered {@link ChangeListener}
    321      *
    322      * @param key the {@link SharedPreferences} key to look up
    323      */
    324     public void refreshValue(String key) {
    325         if (DISPLAY_ORDER_KEY.equals(key)) {
    326             mDisplayOrder = PREFERENCE_UNASSIGNED;
    327             mDisplayOrder = getDisplayOrder();
    328         } else if (SORT_ORDER_KEY.equals(key)) {
    329             mSortOrder = PREFERENCE_UNASSIGNED;
    330             mSortOrder = getSortOrder();
    331         } else if (PHONETIC_NAME_DISPLAY_KEY.equals(key)) {
    332             mPhoneticNameDisplayPreference = PREFERENCE_UNASSIGNED;
    333             mPhoneticNameDisplayPreference = getPhoneticNameDisplayPreference();
    334         } else if (mDefaultAccountKey.equals(key)) {
    335             mDefaultAccount = null;
    336             mDefaultAccount = getDefaultAccount();
    337         }
    338         if (mListener != null) mListener.onChange();
    339     }
    340 
    341     public interface ChangeListener {
    342         void onChange();
    343     }
    344 
    345     /**
    346      * If there are currently no preferences (which means this is the first time we are run),
    347      * For sort order and display order, check to see if there are any preferences stored in
    348      * system settings (pre-L) which can be copied into our own SharedPreferences.
    349      * For default account setting, check to see if there are any preferences stored in the previous
    350      * SharedPreferences which can be copied into current SharedPreferences.
    351      */
    352     private void maybeMigrateSystemSettings() {
    353         if (!mPreferences.contains(SORT_ORDER_KEY)) {
    354             int sortOrder = getDefaultSortOrder();
    355             try {
    356                  sortOrder = Settings.System.getInt(mContext.getContentResolver(),
    357                         SORT_ORDER_KEY);
    358             } catch (SettingNotFoundException e) {
    359             }
    360             setSortOrder(sortOrder);
    361         }
    362 
    363         if (!mPreferences.contains(DISPLAY_ORDER_KEY)) {
    364             int displayOrder = getDefaultDisplayOrder();
    365             try {
    366                 displayOrder = Settings.System.getInt(mContext.getContentResolver(),
    367                         DISPLAY_ORDER_KEY);
    368             } catch (SettingNotFoundException e) {
    369             }
    370             setDisplayOrder(displayOrder);
    371         }
    372 
    373         if (!mPreferences.contains(PHONETIC_NAME_DISPLAY_KEY)) {
    374             int phoneticNameFieldsDisplay = getDefaultPhoneticNameDisplayPreference();
    375             try {
    376                 phoneticNameFieldsDisplay = Settings.System.getInt(mContext.getContentResolver(),
    377                         PHONETIC_NAME_DISPLAY_KEY);
    378             } catch (SettingNotFoundException e) {
    379             }
    380             setPhoneticNameDisplayPreference(phoneticNameFieldsDisplay);
    381         }
    382 
    383         if (!mPreferences.contains(mDefaultAccountKey)) {
    384             final SharedPreferences previousPrefs =
    385                     PreferenceManager.getDefaultSharedPreferences(mContext);
    386             final String defaultAccount = previousPrefs.getString(mDefaultAccountKey, null);
    387             if (!TextUtils.isEmpty(defaultAccount)) {
    388                 final AccountWithDataSet accountWithDataSet = AccountWithDataSet.unstringify(
    389                         defaultAccount);
    390                 setDefaultAccount(accountWithDataSet);
    391             }
    392         }
    393     }
    394 
    395 }
    396