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