PreferenceManager.java revision a8ed73fa46d66350ad4e3499fbbebcfc8c20be6a
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.v4.content.ContextCompat; 22import android.support.v4.content.SharedPreferencesCompat; 23import 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 */ 36public 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 225 public void setStorageDeviceProtected() { 226 if (BuildCompat.isAtLeastN()) { 227 mStorage = STORAGE_DEVICE_PROTECTED; 228 mSharedPreferences = null; 229 } 230 } 231 232 /** @removed */ 233 @Deprecated 234 public void setStorageDeviceEncrypted() { 235 setStorageDeviceProtected(); 236 } 237 238 /** 239 * Indicates if the storage location used internally by this class is the 240 * default provided by the hosting {@link Context}. 241 * 242 * @see #setStorageDefault() 243 * @see #setStorageDeviceProtected() 244 */ 245 public boolean isStorageDefault() { 246 if (BuildCompat.isAtLeastN()) { 247 return mStorage == STORAGE_DEFAULT; 248 } else { 249 return true; 250 } 251 } 252 253 /** 254 * Indicates if the storage location used internally by this class is backed 255 * by device-protected storage. 256 * 257 * @see #setStorageDefault() 258 * @see #setStorageDeviceProtected() 259 */ 260 public boolean isStorageDeviceProtected() { 261 if (BuildCompat.isAtLeastN()) { 262 return mStorage == STORAGE_DEVICE_PROTECTED; 263 } else { 264 return false; 265 } 266 } 267 268 /** 269 * Gets a SharedPreferences instance that preferences managed by this will 270 * use. 271 * 272 * @return A SharedPreferences instance pointing to the file that contains 273 * the values of preferences that are managed by this. 274 */ 275 public SharedPreferences getSharedPreferences() { 276 if (mSharedPreferences == null) { 277 final Context storageContext; 278 switch (mStorage) { 279 case STORAGE_DEVICE_PROTECTED: 280 storageContext = ContextCompat.createDeviceProtectedStorageContext(mContext); 281 break; 282 default: 283 storageContext = mContext; 284 break; 285 } 286 287 mSharedPreferences = storageContext.getSharedPreferences(mSharedPreferencesName, 288 mSharedPreferencesMode); 289 } 290 291 return mSharedPreferences; 292 } 293 294 /** 295 * Gets a SharedPreferences instance that points to the default file that is 296 * used by the preference framework in the given context. 297 * 298 * @param context The context of the preferences whose values are wanted. 299 * @return A SharedPreferences instance that can be used to retrieve and 300 * listen to values of the preferences. 301 */ 302 public static SharedPreferences getDefaultSharedPreferences(Context context) { 303 return context.getSharedPreferences(getDefaultSharedPreferencesName(context), 304 getDefaultSharedPreferencesMode()); 305 } 306 307 private static String getDefaultSharedPreferencesName(Context context) { 308 return context.getPackageName() + "_preferences"; 309 } 310 311 private static int getDefaultSharedPreferencesMode() { 312 return Context.MODE_PRIVATE; 313 } 314 315 /** 316 * Returns the root of the preference hierarchy managed by this class. 317 * 318 * @return The {@link PreferenceScreen} object that is at the root of the hierarchy. 319 */ 320 public PreferenceScreen getPreferenceScreen() { 321 return mPreferenceScreen; 322 } 323 324 /** 325 * Sets the root of the preference hierarchy. 326 * 327 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 328 * @return Whether the {@link PreferenceScreen} given is different than the previous. 329 */ 330 public boolean setPreferences(PreferenceScreen preferenceScreen) { 331 if (preferenceScreen != mPreferenceScreen) { 332 if (mPreferenceScreen != null) { 333 mPreferenceScreen.onDetached(); 334 } 335 mPreferenceScreen = preferenceScreen; 336 return true; 337 } 338 339 return false; 340 } 341 342 /** 343 * Finds a {@link Preference} based on its key. 344 * 345 * @param key The key of the preference to retrieve. 346 * @return The {@link Preference} with the key, or null. 347 * @see PreferenceGroup#findPreference(CharSequence) 348 */ 349 public Preference findPreference(CharSequence key) { 350 if (mPreferenceScreen == null) { 351 return null; 352 } 353 354 return mPreferenceScreen.findPreference(key); 355 } 356 357 /** 358 * Sets the default values from an XML preference file by reading the values defined 359 * by each {@link Preference} item's {@code android:defaultValue} attribute. This should 360 * be called by the application's main activity. 361 * <p> 362 * 363 * @param context The context of the shared preferences. 364 * @param resId The resource ID of the preference XML file. 365 * @param readAgain Whether to re-read the default values. 366 * If false, this method sets the default values only if this 367 * method has never been called in the past (or if the 368 * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared 369 * preferences file is false). To attempt to set the default values again 370 * bypassing this check, set {@code readAgain} to true. 371 * <p class="note"> 372 * Note: this will NOT reset preferences back to their default 373 * values. For that functionality, use 374 * {@link PreferenceManager#getDefaultSharedPreferences(Context)} 375 * and clear it followed by a call to this method with this 376 * parameter set to true. 377 */ 378 public static void setDefaultValues(Context context, int resId, boolean readAgain) { 379 380 // Use the default shared preferences name and mode 381 setDefaultValues(context, getDefaultSharedPreferencesName(context), 382 getDefaultSharedPreferencesMode(), resId, readAgain); 383 } 384 385 /** 386 * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows 387 * the client to provide the filename and mode of the shared preferences 388 * file. 389 * 390 * @param context The context of the shared preferences. 391 * @param sharedPreferencesName A custom name for the shared preferences file. 392 * @param sharedPreferencesMode The file creation mode for the shared preferences file, such 393 * as {@link android.content.Context#MODE_PRIVATE} or {@link 394 * android.content.Context#MODE_PRIVATE} 395 * @param resId The resource ID of the preference XML file. 396 * @param readAgain Whether to re-read the default values. 397 * If false, this method will set the default values only if this 398 * method has never been called in the past (or if the 399 * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared 400 * preferences file is false). To attempt to set the default values again 401 * bypassing this check, set {@code readAgain} to true. 402 * <p class="note"> 403 * Note: this will NOT reset preferences back to their default 404 * values. For that functionality, use 405 * {@link PreferenceManager#getDefaultSharedPreferences(Context)} 406 * and clear it followed by a call to this method with this 407 * parameter set to true. 408 * 409 * @see #setDefaultValues(Context, int, boolean) 410 * @see #setSharedPreferencesName(String) 411 * @see #setSharedPreferencesMode(int) 412 */ 413 public static void setDefaultValues(Context context, String sharedPreferencesName, 414 int sharedPreferencesMode, int resId, boolean readAgain) { 415 final SharedPreferences defaultValueSp = context.getSharedPreferences( 416 KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE); 417 418 if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) { 419 final PreferenceManager pm = new PreferenceManager(context); 420 pm.setSharedPreferencesName(sharedPreferencesName); 421 pm.setSharedPreferencesMode(sharedPreferencesMode); 422 pm.inflateFromResource(context, resId, null); 423 424 SharedPreferences.Editor editor = 425 defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true); 426 427 SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); 428 } 429 } 430 431 /** 432 * Returns an editor to use when modifying the shared preferences. 433 * <p> 434 * Do NOT commit unless {@link #shouldCommit()} returns true. 435 * 436 * @return An editor to use to write to shared preferences. 437 * @see #shouldCommit() 438 */ 439 SharedPreferences.Editor getEditor() { 440 441 if (mNoCommit) { 442 if (mEditor == null) { 443 mEditor = getSharedPreferences().edit(); 444 } 445 446 return mEditor; 447 } else { 448 return getSharedPreferences().edit(); 449 } 450 } 451 452 /** 453 * Whether it is the client's responsibility to commit on the 454 * {@link #getEditor()}. This will return false in cases where the writes 455 * should be batched, for example when inflating preferences from XML. 456 * 457 * @return Whether the client should commit. 458 */ 459 boolean shouldCommit() { 460 return !mNoCommit; 461 } 462 463 private void setNoCommit(boolean noCommit) { 464 if (!noCommit && mEditor != null) { 465 SharedPreferencesCompat.EditorCompat.getInstance().apply(mEditor); 466 } 467 mNoCommit = noCommit; 468 } 469 470 /** 471 * Returns the context. 472 * 473 * @return The context. 474 */ 475 public Context getContext() { 476 return mContext; 477 } 478 479 public OnDisplayPreferenceDialogListener getOnDisplayPreferenceDialogListener() { 480 return mOnDisplayPreferenceDialogListener; 481 } 482 483 public void setOnDisplayPreferenceDialogListener( 484 OnDisplayPreferenceDialogListener onDisplayPreferenceDialogListener) { 485 mOnDisplayPreferenceDialogListener = onDisplayPreferenceDialogListener; 486 } 487 488 /** 489 * Called when a preference requests that a dialog be shown to complete a user interaction. 490 * 491 * @param preference The preference requesting the dialog. 492 */ 493 public void showDialog(Preference preference) { 494 if (mOnDisplayPreferenceDialogListener != null) { 495 mOnDisplayPreferenceDialogListener.onDisplayPreferenceDialog(preference); 496 } 497 } 498 499 /** 500 * Sets the callback to be invoked when a {@link Preference} in the 501 * hierarchy rooted at this {@link PreferenceManager} is clicked. 502 * 503 * @param listener The callback to be invoked. 504 */ 505 public void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) { 506 mOnPreferenceTreeClickListener = listener; 507 } 508 509 public OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() { 510 return mOnPreferenceTreeClickListener; 511 } 512 513 /** 514 * Sets the callback to be invoked when a {@link PreferenceScreen} in the hierarchy rooted at 515 * this {@link PreferenceManager} is clicked. 516 * 517 * @param listener The callback to be invoked. 518 */ 519 public void setOnNavigateToScreenListener(OnNavigateToScreenListener listener) { 520 mOnNavigateToScreenListener = listener; 521 } 522 523 /** 524 * Returns the {@link PreferenceManager.OnNavigateToScreenListener}, if one has been set. 525 */ 526 public OnNavigateToScreenListener getOnNavigateToScreenListener() { 527 return mOnNavigateToScreenListener; 528 } 529 530 /** 531 * Interface definition for a callback to be invoked when a 532 * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is 533 * clicked. 534 */ 535 public interface OnPreferenceTreeClickListener { 536 /** 537 * Called when a preference in the tree rooted at this 538 * {@link PreferenceScreen} has been clicked. 539 * 540 * @param preference The preference that was clicked. 541 * @return Whether the click was handled. 542 */ 543 boolean onPreferenceTreeClick(Preference preference); 544 } 545 546 /** 547 * Interface definition for a class that will be called when a 548 * {@link android.support.v7.preference.Preference} requests to display a dialog. 549 */ 550 public interface OnDisplayPreferenceDialogListener { 551 552 /** 553 * Called when a preference in the tree requests to display a dialog. 554 * 555 * @param preference The Preference object requesting the dialog. 556 */ 557 void onDisplayPreferenceDialog(Preference preference); 558 } 559 560 /** 561 * Interface definition for a class that will be called when a 562 * {@link android.support.v7.preference.PreferenceScreen} requests navigation. 563 */ 564 public interface OnNavigateToScreenListener { 565 566 /** 567 * Called when a PreferenceScreen in the tree requests to navigate to its contents. 568 * 569 * @param preferenceScreen The PreferenceScreen requesting navigation. 570 */ 571 void onNavigateToScreen(PreferenceScreen preferenceScreen); 572 } 573 574} 575