• 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.contacts.preference;
18 
19 import android.app.backup.BackupManager;
20 import android.content.Context;
21 import android.content.SharedPreferences;
22 import android.content.SharedPreferences.Editor;
23 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
24 import android.os.Handler;
25 import android.os.Looper;
26 import android.preference.PreferenceManager;
27 import android.provider.Settings;
28 import android.provider.Settings.SettingNotFoundException;
29 import android.text.TextUtils;
30 
31 import androidx.annotation.NonNull;
32 import androidx.annotation.VisibleForTesting;
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 
ContactsPreferences(Context context)92     public ContactsPreferences(Context context) {
93         this(context,
94                 context.getResources().getBoolean(R.bool.config_default_account_user_changeable));
95     }
96 
97     @VisibleForTesting
ContactsPreferences(Context context, boolean isDefaultAccountUserChangeable)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 
isSortOrderUserChangeable()112     public boolean isSortOrderUserChangeable() {
113         return mContext.getResources().getBoolean(R.bool.config_sort_order_user_changeable);
114     }
115 
getDefaultSortOrder()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 
getSortOrder()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 
setSortOrder(int sortOrder)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 
isDisplayOrderUserChangeable()142     public boolean isDisplayOrderUserChangeable() {
143         return mContext.getResources().getBoolean(R.bool.config_display_order_user_changeable);
144     }
145 
getDefaultDisplayOrder()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 
getDisplayOrder()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 
setDisplayOrder(int displayOrder)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 
getDefaultPhoneticNameDisplayPreference()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 
isPhoneticNameDisplayPreferenceChangeable()180     public boolean isPhoneticNameDisplayPreferenceChangeable() {
181         return mContext.getResources().getBoolean(
182                 R.bool.config_phonetic_name_display_user_changeable);
183     }
184 
setPhoneticNameDisplayPreference(int phoneticNameDisplayPreference)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 
getPhoneticNameDisplayPreference()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 
shouldHidePhoneticNamesIfEmpty()204     public boolean shouldHidePhoneticNamesIfEmpty() {
205         return getPhoneticNameDisplayPreference() == PhoneticNameDisplayPreference.HIDE_IF_EMPTY;
206     }
207 
isDefaultAccountUserChangeable()208     public boolean isDefaultAccountUserChangeable() {
209         return mIsDefaultAccountUserChangeable;
210     }
211 
getDefaultAccount()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 
clearDefaultAccount()226     public void clearDefaultAccount() {
227         mDefaultAccount = null;
228         mPreferences.edit().remove(mDefaultAccountKey).commit();
229     }
230 
setDefaultAccount(@onNull AccountWithDataSet accountWithDataSet)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 
isDefaultAccountSet()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 soundness 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      */
shouldShowAccountChangedNotification(List<AccountWithDataSet> currentWritableAccounts)255     public boolean shouldShowAccountChangedNotification(List<AccountWithDataSet>
256             currentWritableAccounts) {
257         final AccountWithDataSet defaultAccount = getDefaultAccount();
258 
259         AccountWithDataSet localAccount = AccountWithDataSet.getLocalAccount(mContext);
260         // This shouldn't occur anymore because a "device" account is added in the case that there
261         // are no other accounts but if there are no writable accounts then the default has been
262         // initialized if it is "device"
263         if (currentWritableAccounts.isEmpty()) {
264             return defaultAccount == null || !defaultAccount.equals(localAccount);
265         }
266 
267         if (currentWritableAccounts.size() == 1
268                 && !currentWritableAccounts.get(0).equals(localAccount)) {
269             return false;
270         }
271 
272         if (defaultAccount == null) {
273             return true;
274         }
275 
276         if (!currentWritableAccounts.contains(defaultAccount)) {
277             return true;
278         }
279 
280         // All good.
281         return false;
282     }
283 
registerChangeListener(ChangeListener listener)284     public void registerChangeListener(ChangeListener listener) {
285         if (mListener != null) unregisterChangeListener();
286 
287         mListener = listener;
288 
289         // Reset preferences to "unknown" because they may have changed while the
290         // listener was unregistered.
291         mDisplayOrder = PREFERENCE_UNASSIGNED;
292         mSortOrder = PREFERENCE_UNASSIGNED;
293         mPhoneticNameDisplayPreference = PREFERENCE_UNASSIGNED;
294         mDefaultAccount = null;
295 
296         mPreferences.registerOnSharedPreferenceChangeListener(this);
297     }
298 
unregisterChangeListener()299     public void unregisterChangeListener() {
300         if (mListener != null) {
301             mListener = null;
302         }
303 
304         mPreferences.unregisterOnSharedPreferenceChangeListener(this);
305     }
306 
307     @Override
onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key)308     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
309         // This notification is not sent on the Ui thread. Use the previously created Handler
310         // to switch to the Ui thread
311         mHandler.post(new Runnable() {
312             @Override
313             public void run() {
314                 refreshValue(key);
315             }
316         });
317     }
318 
319     /**
320      * Forces the value for the given key to be looked up from shared preferences and notifies
321      * the registered {@link ChangeListener}
322      *
323      * @param key the {@link SharedPreferences} key to look up
324      */
refreshValue(String key)325     public void refreshValue(String key) {
326         if (DISPLAY_ORDER_KEY.equals(key)) {
327             mDisplayOrder = PREFERENCE_UNASSIGNED;
328             mDisplayOrder = getDisplayOrder();
329         } else if (SORT_ORDER_KEY.equals(key)) {
330             mSortOrder = PREFERENCE_UNASSIGNED;
331             mSortOrder = getSortOrder();
332         } else if (PHONETIC_NAME_DISPLAY_KEY.equals(key)) {
333             mPhoneticNameDisplayPreference = PREFERENCE_UNASSIGNED;
334             mPhoneticNameDisplayPreference = getPhoneticNameDisplayPreference();
335         } else if (mDefaultAccountKey.equals(key)) {
336             mDefaultAccount = null;
337             mDefaultAccount = getDefaultAccount();
338         }
339         if (mListener != null) mListener.onChange();
340     }
341 
342     public interface ChangeListener {
onChange()343         void onChange();
344     }
345 
346     /**
347      * If there are currently no preferences (which means this is the first time we are run),
348      * For sort order and display order, check to see if there are any preferences stored in
349      * system settings (pre-L) which can be copied into our own SharedPreferences.
350      * For default account setting, check to see if there are any preferences stored in the previous
351      * SharedPreferences which can be copied into current SharedPreferences.
352      */
maybeMigrateSystemSettings()353     private void maybeMigrateSystemSettings() {
354         if (!mPreferences.contains(SORT_ORDER_KEY)) {
355             int sortOrder = getDefaultSortOrder();
356             try {
357                  sortOrder = Settings.System.getInt(mContext.getContentResolver(),
358                         SORT_ORDER_KEY);
359             } catch (SettingNotFoundException e) {
360             }
361             setSortOrder(sortOrder);
362         }
363 
364         if (!mPreferences.contains(DISPLAY_ORDER_KEY)) {
365             int displayOrder = getDefaultDisplayOrder();
366             try {
367                 displayOrder = Settings.System.getInt(mContext.getContentResolver(),
368                         DISPLAY_ORDER_KEY);
369             } catch (SettingNotFoundException e) {
370             }
371             setDisplayOrder(displayOrder);
372         }
373 
374         if (!mPreferences.contains(PHONETIC_NAME_DISPLAY_KEY)) {
375             int phoneticNameFieldsDisplay = getDefaultPhoneticNameDisplayPreference();
376             try {
377                 phoneticNameFieldsDisplay = Settings.System.getInt(mContext.getContentResolver(),
378                         PHONETIC_NAME_DISPLAY_KEY);
379             } catch (SettingNotFoundException e) {
380             }
381             setPhoneticNameDisplayPreference(phoneticNameFieldsDisplay);
382         }
383 
384         if (!mPreferences.contains(mDefaultAccountKey)) {
385             final SharedPreferences previousPrefs =
386                     PreferenceManager.getDefaultSharedPreferences(mContext);
387             final String defaultAccount = previousPrefs.getString(mDefaultAccountKey, null);
388             if (!TextUtils.isEmpty(defaultAccount)) {
389                 final AccountWithDataSet accountWithDataSet = AccountWithDataSet.unstringify(
390                         defaultAccount);
391                 setDefaultAccount(accountWithDataSet);
392             }
393         }
394     }
395 
396 }
397