1 /* 2 * Copyright (C) 2015 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 android.support.v7.preference; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.support.v4.content.ContextCompat; 22 import android.support.v4.content.SharedPreferencesCompat; 23 import android.support.v4.os.BuildCompat; 24 25 /** 26 * Used to help create {@link Preference} hierarchies 27 * from activities or XML. 28 * <p> 29 * In most cases, clients should use 30 * {@link android.support.v14.preference.PreferenceFragment#addPreferencesFromResource(int)}, or 31 * {@link PreferenceFragmentCompat#addPreferencesFromResource(int)}. 32 * 33 * @see android.support.v14.preference.PreferenceFragment 34 * @see PreferenceFragmentCompat 35 */ 36 public class PreferenceManager { 37 38 private static final String TAG = "PreferenceManager"; 39 40 public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values"; 41 42 /** 43 * The context to use. This should always be set. 44 */ 45 private Context mContext; 46 47 /** 48 * The counter for unique IDs. 49 */ 50 private long mNextId = 0; 51 52 /** 53 * Cached shared preferences. 54 */ 55 private SharedPreferences mSharedPreferences; 56 57 /** 58 * If in no-commit mode, the shared editor to give out (which will be 59 * committed when exiting no-commit mode). 60 */ 61 private SharedPreferences.Editor mEditor; 62 63 /** 64 * Blocks commits from happening on the shared editor. This is used when 65 * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)} 66 */ 67 private boolean mNoCommit; 68 69 /** 70 * The SharedPreferences name that will be used for all {@link Preference}s 71 * managed by this instance. 72 */ 73 private String mSharedPreferencesName; 74 75 /** 76 * The SharedPreferences mode that will be used for all {@link Preference}s 77 * managed by this instance. 78 */ 79 private int mSharedPreferencesMode; 80 81 private static final int STORAGE_DEFAULT = 0; 82 private static final int STORAGE_DEVICE_PROTECTED = 1; 83 84 private int mStorage = STORAGE_DEFAULT; 85 86 /** 87 * The {@link PreferenceScreen} at the root of the preference hierarchy. 88 */ 89 private PreferenceScreen mPreferenceScreen; 90 91 private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener; 92 private OnDisplayPreferenceDialogListener mOnDisplayPreferenceDialogListener; 93 private OnNavigateToScreenListener mOnNavigateToScreenListener; 94 95 /** 96 * @hide 97 */ PreferenceManager(Context context)98 public PreferenceManager(Context context) { 99 mContext = context; 100 101 setSharedPreferencesName(getDefaultSharedPreferencesName(context)); 102 } 103 104 /** 105 * Inflates a preference hierarchy from XML. If a preference hierarchy is 106 * given, the new preference hierarchies will be merged in. 107 * 108 * @param context The context of the resource. 109 * @param resId The resource ID of the XML to inflate. 110 * @param rootPreferences Optional existing hierarchy to merge the new 111 * hierarchies into. 112 * @return The root hierarchy (if one was not provided, the new hierarchy's 113 * root). 114 * @hide 115 */ inflateFromResource(Context context, int resId, PreferenceScreen rootPreferences)116 public PreferenceScreen inflateFromResource(Context context, int resId, 117 PreferenceScreen rootPreferences) { 118 // Block commits 119 setNoCommit(true); 120 121 final PreferenceInflater inflater = new PreferenceInflater(context, this); 122 rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences); 123 rootPreferences.onAttachedToHierarchy(this); 124 125 // Unblock commits 126 setNoCommit(false); 127 128 return rootPreferences; 129 } 130 createPreferenceScreen(Context context)131 public PreferenceScreen createPreferenceScreen(Context context) { 132 final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null); 133 preferenceScreen.onAttachedToHierarchy(this); 134 return preferenceScreen; 135 } 136 137 /** 138 * Called by a preference to get a unique ID in its hierarchy. 139 * 140 * @return A unique ID. 141 */ getNextId()142 long getNextId() { 143 synchronized (this) { 144 return mNextId++; 145 } 146 } 147 148 /** 149 * Returns the current name of the SharedPreferences file that preferences managed by 150 * this will use. 151 * 152 * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}. 153 * @see Context#getSharedPreferences(String, int) 154 */ getSharedPreferencesName()155 public String getSharedPreferencesName() { 156 return mSharedPreferencesName; 157 } 158 159 /** 160 * Sets the name of the SharedPreferences file that preferences managed by this 161 * will use. 162 * 163 * @param sharedPreferencesName The name of the SharedPreferences file. 164 * @see Context#getSharedPreferences(String, int) 165 */ setSharedPreferencesName(String sharedPreferencesName)166 public void setSharedPreferencesName(String sharedPreferencesName) { 167 mSharedPreferencesName = sharedPreferencesName; 168 mSharedPreferences = null; 169 } 170 171 /** 172 * Returns the current mode of the SharedPreferences file that preferences managed by 173 * this will use. 174 * 175 * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}. 176 * @see Context#getSharedPreferences(String, int) 177 */ getSharedPreferencesMode()178 public int getSharedPreferencesMode() { 179 return mSharedPreferencesMode; 180 } 181 182 /** 183 * Sets the mode of the SharedPreferences file that preferences managed by this 184 * will use. 185 * 186 * @param sharedPreferencesMode The mode of the SharedPreferences file. 187 * @see Context#getSharedPreferences(String, int) 188 */ setSharedPreferencesMode(int sharedPreferencesMode)189 public void setSharedPreferencesMode(int sharedPreferencesMode) { 190 mSharedPreferencesMode = sharedPreferencesMode; 191 mSharedPreferences = null; 192 } 193 194 /** 195 * Sets the storage location used internally by this class to be the default 196 * provided by the hosting {@link Context}. 197 */ setStorageDefault()198 public void setStorageDefault() { 199 if (BuildCompat.isAtLeastN()) { 200 mStorage = STORAGE_DEFAULT; 201 mSharedPreferences = null; 202 } 203 } 204 205 /** 206 * Explicitly set the storage location used internally by this class to be 207 * device-protected storage. 208 * <p> 209 * On devices with direct boot, data stored in this location is encrypted 210 * with a key tied to the physical device, and it can be accessed 211 * immediately after the device has booted successfully, both 212 * <em>before and after</em> the user has authenticated with their 213 * credentials (such as a lock pattern or PIN). 214 * <p> 215 * Because device-protected data is available without user authentication, 216 * you should carefully limit the data you store using this Context. For 217 * example, storing sensitive authentication tokens or passwords in the 218 * device-protected area is strongly discouraged. 219 * <p> 220 * Prior to {@link BuildCompat#isAtLeastN()} this method has no effect, 221 * since device-protected storage is not available. 222 * 223 * @see Context#createDeviceProtectedStorageContext() 224 */ setStorageDeviceProtected()225 public void setStorageDeviceProtected() { 226 if (BuildCompat.isAtLeastN()) { 227 mStorage = STORAGE_DEVICE_PROTECTED; 228 mSharedPreferences = null; 229 } 230 } 231 232 /** 233 * @removed 234 * @deprecated 235 */ 236 @Deprecated setStorageDeviceEncrypted()237 public void setStorageDeviceEncrypted() { 238 setStorageDeviceProtected(); 239 } 240 241 /** 242 * Indicates if the storage location used internally by this class is the 243 * default provided by the hosting {@link Context}. 244 * 245 * @see #setStorageDefault() 246 * @see #setStorageDeviceProtected() 247 */ isStorageDefault()248 public boolean isStorageDefault() { 249 if (BuildCompat.isAtLeastN()) { 250 return mStorage == STORAGE_DEFAULT; 251 } else { 252 return true; 253 } 254 } 255 256 /** 257 * Indicates if the storage location used internally by this class is backed 258 * by device-protected storage. 259 * 260 * @see #setStorageDefault() 261 * @see #setStorageDeviceProtected() 262 */ isStorageDeviceProtected()263 public boolean isStorageDeviceProtected() { 264 if (BuildCompat.isAtLeastN()) { 265 return mStorage == STORAGE_DEVICE_PROTECTED; 266 } else { 267 return false; 268 } 269 } 270 271 /** 272 * Gets a SharedPreferences instance that preferences managed by this will 273 * use. 274 * 275 * @return A SharedPreferences instance pointing to the file that contains 276 * the values of preferences that are managed by this. 277 */ getSharedPreferences()278 public SharedPreferences getSharedPreferences() { 279 if (mSharedPreferences == null) { 280 final Context storageContext; 281 switch (mStorage) { 282 case STORAGE_DEVICE_PROTECTED: 283 storageContext = ContextCompat.createDeviceProtectedStorageContext(mContext); 284 break; 285 default: 286 storageContext = mContext; 287 break; 288 } 289 290 mSharedPreferences = storageContext.getSharedPreferences(mSharedPreferencesName, 291 mSharedPreferencesMode); 292 } 293 294 return mSharedPreferences; 295 } 296 297 /** 298 * Gets a SharedPreferences instance that points to the default file that is 299 * used by the preference framework in the given context. 300 * 301 * @param context The context of the preferences whose values are wanted. 302 * @return A SharedPreferences instance that can be used to retrieve and 303 * listen to values of the preferences. 304 */ getDefaultSharedPreferences(Context context)305 public static SharedPreferences getDefaultSharedPreferences(Context context) { 306 return context.getSharedPreferences(getDefaultSharedPreferencesName(context), 307 getDefaultSharedPreferencesMode()); 308 } 309 getDefaultSharedPreferencesName(Context context)310 private static String getDefaultSharedPreferencesName(Context context) { 311 return context.getPackageName() + "_preferences"; 312 } 313 getDefaultSharedPreferencesMode()314 private static int getDefaultSharedPreferencesMode() { 315 return Context.MODE_PRIVATE; 316 } 317 318 /** 319 * Returns the root of the preference hierarchy managed by this class. 320 * 321 * @return The {@link PreferenceScreen} object that is at the root of the hierarchy. 322 */ getPreferenceScreen()323 public PreferenceScreen getPreferenceScreen() { 324 return mPreferenceScreen; 325 } 326 327 /** 328 * Sets the root of the preference hierarchy. 329 * 330 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 331 * @return Whether the {@link PreferenceScreen} given is different than the previous. 332 */ setPreferences(PreferenceScreen preferenceScreen)333 public boolean setPreferences(PreferenceScreen preferenceScreen) { 334 if (preferenceScreen != mPreferenceScreen) { 335 if (mPreferenceScreen != null) { 336 mPreferenceScreen.onDetached(); 337 } 338 mPreferenceScreen = preferenceScreen; 339 return true; 340 } 341 342 return false; 343 } 344 345 /** 346 * Finds a {@link Preference} based on its key. 347 * 348 * @param key The key of the preference to retrieve. 349 * @return The {@link Preference} with the key, or null. 350 * @see PreferenceGroup#findPreference(CharSequence) 351 */ findPreference(CharSequence key)352 public Preference findPreference(CharSequence key) { 353 if (mPreferenceScreen == null) { 354 return null; 355 } 356 357 return mPreferenceScreen.findPreference(key); 358 } 359 360 /** 361 * Sets the default values from an XML preference file by reading the values defined 362 * by each {@link Preference} item's {@code android:defaultValue} attribute. This should 363 * be called by the application's main activity. 364 * <p> 365 * 366 * @param context The context of the shared preferences. 367 * @param resId The resource ID of the preference XML file. 368 * @param readAgain Whether to re-read the default values. 369 * If false, this method sets the default values only if this 370 * method has never been called in the past (or if the 371 * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared 372 * preferences file is false). To attempt to set the default values again 373 * bypassing this check, set {@code readAgain} to true. 374 * <p class="note"> 375 * Note: this will NOT reset preferences back to their default 376 * values. For that functionality, use 377 * {@link PreferenceManager#getDefaultSharedPreferences(Context)} 378 * and clear it followed by a call to this method with this 379 * parameter set to true. 380 */ setDefaultValues(Context context, int resId, boolean readAgain)381 public static void setDefaultValues(Context context, int resId, boolean readAgain) { 382 383 // Use the default shared preferences name and mode 384 setDefaultValues(context, getDefaultSharedPreferencesName(context), 385 getDefaultSharedPreferencesMode(), resId, readAgain); 386 } 387 388 /** 389 * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows 390 * the client to provide the filename and mode of the shared preferences 391 * file. 392 * 393 * @param context The context of the shared preferences. 394 * @param sharedPreferencesName A custom name for the shared preferences file. 395 * @param sharedPreferencesMode The file creation mode for the shared preferences file, such 396 * as {@link android.content.Context#MODE_PRIVATE} or {@link 397 * android.content.Context#MODE_PRIVATE} 398 * @param resId The resource ID of the preference XML file. 399 * @param readAgain Whether to re-read the default values. 400 * If false, this method will set the default values only if this 401 * method has never been called in the past (or if the 402 * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared 403 * preferences file is false). To attempt to set the default values again 404 * bypassing this check, set {@code readAgain} to true. 405 * <p class="note"> 406 * Note: this will NOT reset preferences back to their default 407 * values. For that functionality, use 408 * {@link PreferenceManager#getDefaultSharedPreferences(Context)} 409 * and clear it followed by a call to this method with this 410 * parameter set to true. 411 * 412 * @see #setDefaultValues(Context, int, boolean) 413 * @see #setSharedPreferencesName(String) 414 * @see #setSharedPreferencesMode(int) 415 */ setDefaultValues(Context context, String sharedPreferencesName, int sharedPreferencesMode, int resId, boolean readAgain)416 public static void setDefaultValues(Context context, String sharedPreferencesName, 417 int sharedPreferencesMode, int resId, boolean readAgain) { 418 final SharedPreferences defaultValueSp = context.getSharedPreferences( 419 KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE); 420 421 if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) { 422 final PreferenceManager pm = new PreferenceManager(context); 423 pm.setSharedPreferencesName(sharedPreferencesName); 424 pm.setSharedPreferencesMode(sharedPreferencesMode); 425 pm.inflateFromResource(context, resId, null); 426 427 SharedPreferences.Editor editor = 428 defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true); 429 430 SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); 431 } 432 } 433 434 /** 435 * Returns an editor to use when modifying the shared preferences. 436 * <p> 437 * Do NOT commit unless {@link #shouldCommit()} returns true. 438 * 439 * @return An editor to use to write to shared preferences. 440 * @see #shouldCommit() 441 */ getEditor()442 SharedPreferences.Editor getEditor() { 443 444 if (mNoCommit) { 445 if (mEditor == null) { 446 mEditor = getSharedPreferences().edit(); 447 } 448 449 return mEditor; 450 } else { 451 return getSharedPreferences().edit(); 452 } 453 } 454 455 /** 456 * Whether it is the client's responsibility to commit on the 457 * {@link #getEditor()}. This will return false in cases where the writes 458 * should be batched, for example when inflating preferences from XML. 459 * 460 * @return Whether the client should commit. 461 */ shouldCommit()462 boolean shouldCommit() { 463 return !mNoCommit; 464 } 465 setNoCommit(boolean noCommit)466 private void setNoCommit(boolean noCommit) { 467 if (!noCommit && mEditor != null) { 468 SharedPreferencesCompat.EditorCompat.getInstance().apply(mEditor); 469 } 470 mNoCommit = noCommit; 471 } 472 473 /** 474 * Returns the context. 475 * 476 * @return The context. 477 */ getContext()478 public Context getContext() { 479 return mContext; 480 } 481 getOnDisplayPreferenceDialogListener()482 public OnDisplayPreferenceDialogListener getOnDisplayPreferenceDialogListener() { 483 return mOnDisplayPreferenceDialogListener; 484 } 485 setOnDisplayPreferenceDialogListener( OnDisplayPreferenceDialogListener onDisplayPreferenceDialogListener)486 public void setOnDisplayPreferenceDialogListener( 487 OnDisplayPreferenceDialogListener onDisplayPreferenceDialogListener) { 488 mOnDisplayPreferenceDialogListener = onDisplayPreferenceDialogListener; 489 } 490 491 /** 492 * Called when a preference requests that a dialog be shown to complete a user interaction. 493 * 494 * @param preference The preference requesting the dialog. 495 */ showDialog(Preference preference)496 public void showDialog(Preference preference) { 497 if (mOnDisplayPreferenceDialogListener != null) { 498 mOnDisplayPreferenceDialogListener.onDisplayPreferenceDialog(preference); 499 } 500 } 501 502 /** 503 * Sets the callback to be invoked when a {@link Preference} in the 504 * hierarchy rooted at this {@link PreferenceManager} is clicked. 505 * 506 * @param listener The callback to be invoked. 507 */ setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener)508 public void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) { 509 mOnPreferenceTreeClickListener = listener; 510 } 511 getOnPreferenceTreeClickListener()512 public OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() { 513 return mOnPreferenceTreeClickListener; 514 } 515 516 /** 517 * Sets the callback to be invoked when a {@link PreferenceScreen} in the hierarchy rooted at 518 * this {@link PreferenceManager} is clicked. 519 * 520 * @param listener The callback to be invoked. 521 */ setOnNavigateToScreenListener(OnNavigateToScreenListener listener)522 public void setOnNavigateToScreenListener(OnNavigateToScreenListener listener) { 523 mOnNavigateToScreenListener = listener; 524 } 525 526 /** 527 * Returns the {@link PreferenceManager.OnNavigateToScreenListener}, if one has been set. 528 */ getOnNavigateToScreenListener()529 public OnNavigateToScreenListener getOnNavigateToScreenListener() { 530 return mOnNavigateToScreenListener; 531 } 532 533 /** 534 * Interface definition for a callback to be invoked when a 535 * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is 536 * clicked. 537 */ 538 public interface OnPreferenceTreeClickListener { 539 /** 540 * Called when a preference in the tree rooted at this 541 * {@link PreferenceScreen} has been clicked. 542 * 543 * @param preference The preference that was clicked. 544 * @return Whether the click was handled. 545 */ onPreferenceTreeClick(Preference preference)546 boolean onPreferenceTreeClick(Preference preference); 547 } 548 549 /** 550 * Interface definition for a class that will be called when a 551 * {@link android.support.v7.preference.Preference} requests to display a dialog. 552 */ 553 public interface OnDisplayPreferenceDialogListener { 554 555 /** 556 * Called when a preference in the tree requests to display a dialog. 557 * 558 * @param preference The Preference object requesting the dialog. 559 */ onDisplayPreferenceDialog(Preference preference)560 void onDisplayPreferenceDialog(Preference preference); 561 } 562 563 /** 564 * Interface definition for a class that will be called when a 565 * {@link android.support.v7.preference.PreferenceScreen} requests navigation. 566 */ 567 public interface OnNavigateToScreenListener { 568 569 /** 570 * Called when a PreferenceScreen in the tree requests to navigate to its contents. 571 * 572 * @param preferenceScreen The PreferenceScreen requesting navigation. 573 */ onNavigateToScreen(PreferenceScreen preferenceScreen)574 void onNavigateToScreen(PreferenceScreen preferenceScreen); 575 } 576 577 } 578