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