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