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 androidx.annotation.NonNull; 31 import androidx.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 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 // 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 registerChangeListener(ChangeListener listener)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 unregisterChangeListener()298 public void unregisterChangeListener() { 299 if (mListener != null) { 300 mListener = null; 301 } 302 303 mPreferences.unregisterOnSharedPreferenceChangeListener(this); 304 } 305 306 @Override onSharedPreferenceChanged(SharedPreferences sharedPreferences, final String key)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 */ refreshValue(String key)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 { onChange()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 */ maybeMigrateSystemSettings()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