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.common.preference;
18 
19 import android.accounts.Account;
20 import android.content.ContentResolver;
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.net.Uri;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.preference.PreferenceManager;
29 import android.provider.ContactsContract;
30 import android.provider.Settings;
31 import android.provider.Settings.SettingNotFoundException;
32 import android.text.TextUtils;
33 
34 import com.android.contacts.common.R;
35 import com.android.contacts.common.model.account.AccountWithDataSet;
36 import com.android.contacts.common.model.account.GoogleAccountType;
37 import com.android.contacts.common.model.AccountTypeManager;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 /**
43  * Manages user preferences for contacts.
44  */
45 public class ContactsPreferences implements OnSharedPreferenceChangeListener {
46 
47     /**
48      * The value for the DISPLAY_ORDER key to show the given name first.
49      */
50     public static final int DISPLAY_ORDER_PRIMARY = 1;
51 
52     /**
53      * The value for the DISPLAY_ORDER key to show the family name first.
54      */
55     public static final int DISPLAY_ORDER_ALTERNATIVE = 2;
56 
57     public static final String DISPLAY_ORDER_KEY = "android.contacts.DISPLAY_ORDER";
58 
59     /**
60      * The value for the SORT_ORDER key corresponding to sort by given name first.
61      */
62     public static final int SORT_ORDER_PRIMARY = 1;
63 
64     public static final String SORT_ORDER_KEY = "android.contacts.SORT_ORDER";
65 
66     /**
67      * The value for the SORT_ORDER key corresponding to sort by family name first.
68      */
69     public static final int SORT_ORDER_ALTERNATIVE = 2;
70 
71     public static final String PREF_DISPLAY_ONLY_PHONES = "only_phones";
72 
73     public static final boolean PREF_DISPLAY_ONLY_PHONES_DEFAULT = false;
74 
75     public static final String DO_NOT_SYNC_CONTACT_METADATA_MSG = "Do not sync metadata";
76 
77     public static final String CONTACT_METADATA_AUTHORITY = "com.android.contacts.metadata";
78 
79     public static final String SHOULD_CLEAR_METADATA_BEFORE_SYNCING =
80             "should_clear_metadata_before_syncing";
81 
82     public static final String ONLY_CLEAR_DONOT_SYNC = "only_clear_donot_sync";
83     /**
84      * Value to use when a preference is unassigned and needs to be read from the shared preferences
85      */
86     private static final int PREFERENCE_UNASSIGNED = -1;
87 
88     private final Context mContext;
89     private int mSortOrder = PREFERENCE_UNASSIGNED;
90     private int mDisplayOrder = PREFERENCE_UNASSIGNED;
91     private String mDefaultAccount = null;
92     private ChangeListener mListener = null;
93     private Handler mHandler;
94     private final SharedPreferences mPreferences;
95     private String mDefaultAccountKey;
96     private String mDefaultAccountSavedKey;
97 
ContactsPreferences(Context context)98     public ContactsPreferences(Context context) {
99         mContext = context;
100         mHandler = new Handler();
101         mPreferences = mContext.getSharedPreferences(context.getPackageName(),
102                 Context.MODE_PRIVATE);
103         mDefaultAccountKey = mContext.getResources().getString(
104                 R.string.contact_editor_default_account_key);
105         mDefaultAccountSavedKey = mContext.getResources().getString(
106                 R.string.contact_editor_anything_saved_key);
107         maybeMigrateSystemSettings();
108     }
109 
isSortOrderUserChangeable()110     public boolean isSortOrderUserChangeable() {
111         return mContext.getResources().getBoolean(R.bool.config_sort_order_user_changeable);
112     }
113 
getDefaultSortOrder()114     public int getDefaultSortOrder() {
115         if (mContext.getResources().getBoolean(R.bool.config_default_sort_order_primary)) {
116             return SORT_ORDER_PRIMARY;
117         } else {
118             return SORT_ORDER_ALTERNATIVE;
119         }
120     }
121 
getSortOrder()122     public int getSortOrder() {
123         if (!isSortOrderUserChangeable()) {
124             return getDefaultSortOrder();
125         }
126         if (mSortOrder == PREFERENCE_UNASSIGNED) {
127             mSortOrder = mPreferences.getInt(SORT_ORDER_KEY, getDefaultSortOrder());
128         }
129         return mSortOrder;
130     }
131 
setSortOrder(int sortOrder)132     public void setSortOrder(int sortOrder) {
133         mSortOrder = sortOrder;
134         final Editor editor = mPreferences.edit();
135         editor.putInt(SORT_ORDER_KEY, sortOrder);
136         editor.commit();
137     }
138 
isDisplayOrderUserChangeable()139     public boolean isDisplayOrderUserChangeable() {
140         return mContext.getResources().getBoolean(R.bool.config_display_order_user_changeable);
141     }
142 
getDefaultDisplayOrder()143     public int getDefaultDisplayOrder() {
144         if (mContext.getResources().getBoolean(R.bool.config_default_display_order_primary)) {
145             return DISPLAY_ORDER_PRIMARY;
146         } else {
147             return DISPLAY_ORDER_ALTERNATIVE;
148         }
149     }
150 
getDisplayOrder()151     public int getDisplayOrder() {
152         if (!isDisplayOrderUserChangeable()) {
153             return getDefaultDisplayOrder();
154         }
155         if (mDisplayOrder == PREFERENCE_UNASSIGNED) {
156             mDisplayOrder = mPreferences.getInt(DISPLAY_ORDER_KEY, getDefaultDisplayOrder());
157         }
158         return mDisplayOrder;
159     }
160 
setDisplayOrder(int displayOrder)161     public void setDisplayOrder(int displayOrder) {
162         mDisplayOrder = displayOrder;
163         final Editor editor = mPreferences.edit();
164         editor.putInt(DISPLAY_ORDER_KEY, displayOrder);
165         editor.commit();
166     }
167 
isDefaultAccountUserChangeable()168     public boolean isDefaultAccountUserChangeable() {
169         return mContext.getResources().getBoolean(R.bool.config_default_account_user_changeable);
170     }
171 
getDefaultAccount()172     public String getDefaultAccount() {
173         if (!isDefaultAccountUserChangeable()) {
174             return mDefaultAccount;
175         }
176         if (TextUtils.isEmpty(mDefaultAccount)) {
177             final String accountString = mPreferences
178                     .getString(mDefaultAccountKey, mDefaultAccount);
179             if (!TextUtils.isEmpty(accountString)) {
180                 final AccountWithDataSet accountWithDataSet = AccountWithDataSet.unstringify(
181                         accountString);
182                 mDefaultAccount = accountWithDataSet.name;
183             }
184         }
185         return mDefaultAccount;
186     }
187 
setDefaultAccount(AccountWithDataSet accountWithDataSet)188     public void setDefaultAccount(AccountWithDataSet accountWithDataSet) {
189         mDefaultAccount = accountWithDataSet == null ? null : accountWithDataSet.name;
190         final Editor editor = mPreferences.edit();
191         if (TextUtils.isEmpty(mDefaultAccount)) {
192             editor.remove(mDefaultAccountKey);
193         } else {
194             editor.putString(mDefaultAccountKey, accountWithDataSet.stringify());
195         }
196         editor.putBoolean(mDefaultAccountSavedKey, true);
197         editor.commit();
198     }
199 
getContactMetadataSyncAccountName()200     public String getContactMetadataSyncAccountName() {
201         final Account syncAccount = getContactMetadataSyncAccount();
202         return syncAccount == null ? DO_NOT_SYNC_CONTACT_METADATA_MSG : syncAccount.name;
203     }
204 
setContactMetadataSyncAccount(AccountWithDataSet accountWithDataSet)205     public void setContactMetadataSyncAccount(AccountWithDataSet accountWithDataSet) {
206         final String mContactMetadataSyncAccount =
207                 accountWithDataSet == null ? null : accountWithDataSet.name;
208         requestMetadataSyncForAccount(mContactMetadataSyncAccount);
209     }
210 
getContactMetadataSyncAccount()211     private Account getContactMetadataSyncAccount() {
212         for (Account account : getFocusGoogleAccounts()) {
213             if (ContentResolver.getIsSyncable(account, CONTACT_METADATA_AUTHORITY) == 1
214                     && ContentResolver.getSyncAutomatically(account, CONTACT_METADATA_AUTHORITY)) {
215                 return account;
216             }
217         }
218         return null;
219     }
220 
221     /**
222      * Turn on contact metadata sync for this {@param accountName} and turn off automatic sync
223      * for other accounts. If accountName is null, then turn off automatic sync for all accounts.
224      */
requestMetadataSyncForAccount(String accountName)225     private void requestMetadataSyncForAccount(String accountName) {
226         for (Account account : getFocusGoogleAccounts()) {
227             if (!TextUtils.isEmpty(accountName) && accountName.equals(account.name)) {
228                 // Request sync.
229                 final Bundle b = new Bundle();
230                 b.putBoolean(SHOULD_CLEAR_METADATA_BEFORE_SYNCING, true);
231                 b.putBoolean(ONLY_CLEAR_DONOT_SYNC, false);
232                 b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
233                 b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
234                 ContentResolver.requestSync(account, CONTACT_METADATA_AUTHORITY, b);
235 
236                 ContentResolver.setSyncAutomatically(account, CONTACT_METADATA_AUTHORITY, true);
237             } else if (ContentResolver.getSyncAutomatically(account, CONTACT_METADATA_AUTHORITY)) {
238                 // Turn off automatic sync for previous sync account.
239                 ContentResolver.setSyncAutomatically(account, CONTACT_METADATA_AUTHORITY, false);
240                 if (TextUtils.isEmpty(accountName)) {
241                     // Request sync to clear old data.
242                     final Bundle b = new Bundle();
243                     b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
244                     b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
245                     b.putBoolean(SHOULD_CLEAR_METADATA_BEFORE_SYNCING, true);
246                     b.putBoolean(ONLY_CLEAR_DONOT_SYNC, true);
247                     ContentResolver.requestSync(account, CONTACT_METADATA_AUTHORITY, b);
248                 }
249             }
250         }
251     }
252 
253     /**
254      * @return google accounts with "com.google" account type and null data set.
255      */
getFocusGoogleAccounts()256     private List<Account> getFocusGoogleAccounts() {
257         List<Account> focusGoogleAccounts = new ArrayList<Account>();
258         final AccountTypeManager accountTypeManager = AccountTypeManager.getInstance(mContext);
259         List<AccountWithDataSet> accounts = accountTypeManager.getAccounts(true);
260         for (AccountWithDataSet account : accounts) {
261             if (GoogleAccountType.ACCOUNT_TYPE.equals(account.type) && account.dataSet == null) {
262                 focusGoogleAccounts.add(account.getAccountOrNull());
263             }
264         }
265         return focusGoogleAccounts;
266     }
267 
registerChangeListener(ChangeListener listener)268     public void registerChangeListener(ChangeListener listener) {
269         if (mListener != null) unregisterChangeListener();
270 
271         mListener = listener;
272 
273         // Reset preferences to "unknown" because they may have changed while the
274         // listener was unregistered.
275         mDisplayOrder = PREFERENCE_UNASSIGNED;
276         mSortOrder = PREFERENCE_UNASSIGNED;
277         mDefaultAccount = null;
278 
279         mPreferences.registerOnSharedPreferenceChangeListener(this);
280     }
281 
unregisterChangeListener()282     public void unregisterChangeListener() {
283         if (mListener != null) {
284             mListener = null;
285         }
286 
287         mPreferences.unregisterOnSharedPreferenceChangeListener(this);
288     }
289 
290     @Override
onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key)291     public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key) {
292         // This notification is not sent on the Ui thread. Use the previously created Handler
293         // to switch to the Ui thread
294         mHandler.post(new Runnable() {
295             @Override
296             public void run() {
297                 refreshValue(key);
298             }
299         });
300     }
301 
302     /**
303      * Forces the value for the given key to be looked up from shared preferences and notifies
304      * the registered {@link ChangeListener}
305      *
306      * @param key the {@link SharedPreferences} key to look up
307      */
refreshValue(String key)308     public void refreshValue(String key) {
309         if (DISPLAY_ORDER_KEY.equals(key)) {
310             mDisplayOrder = PREFERENCE_UNASSIGNED;
311             mDisplayOrder = getDisplayOrder();
312         } else if (SORT_ORDER_KEY.equals(key)) {
313             mSortOrder = PREFERENCE_UNASSIGNED;
314             mSortOrder = getSortOrder();
315         } else if (mDefaultAccountKey.equals(key)) {
316             mDefaultAccount = null;
317             mDefaultAccount = getDefaultAccount();
318         }
319         if (mListener != null) mListener.onChange();
320     }
321 
322     public interface ChangeListener {
onChange()323         void onChange();
324     }
325 
326     /**
327      * If there are currently no preferences (which means this is the first time we are run),
328      * For sort order and display order, check to see if there are any preferences stored in
329      * system settings (pre-L) which can be copied into our own SharedPreferences.
330      * For default account setting, check to see if there are any preferences stored in the previous
331      * SharedPreferences which can be copied into current SharedPreferences.
332      */
maybeMigrateSystemSettings()333     private void maybeMigrateSystemSettings() {
334         if (!mPreferences.contains(SORT_ORDER_KEY)) {
335             int sortOrder = getDefaultSortOrder();
336             try {
337                  sortOrder = Settings.System.getInt(mContext.getContentResolver(),
338                         SORT_ORDER_KEY);
339             } catch (SettingNotFoundException e) {
340             }
341             setSortOrder(sortOrder);
342         }
343 
344         if (!mPreferences.contains(DISPLAY_ORDER_KEY)) {
345             int displayOrder = getDefaultDisplayOrder();
346             try {
347                 displayOrder = Settings.System.getInt(mContext.getContentResolver(),
348                         DISPLAY_ORDER_KEY);
349             } catch (SettingNotFoundException e) {
350             }
351             setDisplayOrder(displayOrder);
352         }
353 
354         if (!mPreferences.contains(mDefaultAccountKey)) {
355             final SharedPreferences previousPrefs =
356                     PreferenceManager.getDefaultSharedPreferences(mContext);
357             final String defaultAccount = previousPrefs.getString(mDefaultAccountKey, null);
358             if (!TextUtils.isEmpty(defaultAccount)) {
359                 final AccountWithDataSet accountWithDataSet = AccountWithDataSet.unstringify(
360                         defaultAccount);
361                 setDefaultAccount(accountWithDataSet);
362             }
363         }
364     }
365 }
366