1 /* 2 * Copyright (C) 2007 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.preference; 18 19 import android.annotation.Nullable; 20 import android.annotation.SystemApi; 21 import android.annotation.XmlRes; 22 import android.app.Activity; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.Intent; 26 import android.content.SharedPreferences; 27 import android.content.pm.ActivityInfo; 28 import android.content.pm.PackageManager; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.pm.ResolveInfo; 31 import android.content.res.XmlResourceParser; 32 import android.os.Bundle; 33 import android.util.Log; 34 35 import java.util.ArrayList; 36 import java.util.HashSet; 37 import java.util.List; 38 39 /** 40 * Used to help create {@link Preference} hierarchies 41 * from activities or XML. 42 * <p> 43 * In most cases, clients should use 44 * {@link PreferenceActivity#addPreferencesFromIntent} or 45 * {@link PreferenceActivity#addPreferencesFromResource(int)}. 46 * 47 * @see PreferenceActivity 48 */ 49 public class PreferenceManager { 50 51 private static final String TAG = "PreferenceManager"; 52 53 /** 54 * The Activity meta-data key for its XML preference hierarchy. 55 */ 56 public static final String METADATA_KEY_PREFERENCES = "android.preference"; 57 58 public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values"; 59 60 /** 61 * @see #getActivity() 62 */ 63 @Nullable 64 private Activity mActivity; 65 66 /** 67 * Fragment that owns this instance. 68 */ 69 @Nullable 70 private PreferenceFragment mFragment; 71 72 /** 73 * The context to use. This should always be set. 74 * 75 * @see #mActivity 76 */ 77 private Context mContext; 78 79 /** 80 * The counter for unique IDs. 81 */ 82 private long mNextId = 0; 83 84 /** 85 * The counter for unique request codes. 86 */ 87 private int mNextRequestCode; 88 89 /** 90 * Cached shared preferences. 91 */ 92 @Nullable 93 private SharedPreferences mSharedPreferences; 94 95 /** 96 * Data store to be used by the Preferences or {@code null} if 97 * {@link android.content.SharedPreferences} should be used. 98 */ 99 @Nullable 100 private PreferenceDataStore mPreferenceDataStore; 101 102 /** 103 * If in no-commit mode, the shared editor to give out (which will be 104 * committed when exiting no-commit mode). 105 */ 106 @Nullable 107 private SharedPreferences.Editor mEditor; 108 109 /** 110 * Blocks commits from happening on the shared editor. This is used when 111 * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)} 112 */ 113 private boolean mNoCommit; 114 115 /** 116 * The SharedPreferences name that will be used for all {@link Preference}s 117 * managed by this instance. 118 */ 119 private String mSharedPreferencesName; 120 121 /** 122 * The SharedPreferences mode that will be used for all {@link Preference}s 123 * managed by this instance. 124 */ 125 private int mSharedPreferencesMode; 126 127 private static final int STORAGE_DEFAULT = 0; 128 private static final int STORAGE_DEVICE_PROTECTED = 1; 129 private static final int STORAGE_CREDENTIAL_PROTECTED = 2; 130 131 private int mStorage = STORAGE_DEFAULT; 132 133 /** 134 * The {@link PreferenceScreen} at the root of the preference hierarchy. 135 */ 136 @Nullable 137 private PreferenceScreen mPreferenceScreen; 138 139 /** 140 * List of activity result listeners. 141 */ 142 @Nullable 143 private List<OnActivityResultListener> mActivityResultListeners; 144 145 /** 146 * List of activity stop listeners. 147 */ 148 @Nullable 149 private List<OnActivityStopListener> mActivityStopListeners; 150 151 /** 152 * List of activity destroy listeners. 153 */ 154 @Nullable 155 private List<OnActivityDestroyListener> mActivityDestroyListeners; 156 157 /** 158 * List of dialogs that should be dismissed when we receive onNewIntent in 159 * our PreferenceActivity. 160 */ 161 @Nullable 162 private List<DialogInterface> mPreferencesScreens; 163 164 private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener; 165 166 /** 167 * @hide 168 */ PreferenceManager(Activity activity, int firstRequestCode)169 public PreferenceManager(Activity activity, int firstRequestCode) { 170 mActivity = activity; 171 mNextRequestCode = firstRequestCode; 172 173 init(activity); 174 } 175 176 /** 177 * This constructor should ONLY be used when getting default values from 178 * an XML preference hierarchy. 179 * <p> 180 * The {@link PreferenceManager#PreferenceManager(Activity)} 181 * should be used ANY time a preference will be displayed, since some preference 182 * types need an Activity for managed queries. 183 */ PreferenceManager(Context context)184 /*package*/ PreferenceManager(Context context) { 185 init(context); 186 } 187 init(Context context)188 private void init(Context context) { 189 mContext = context; 190 191 setSharedPreferencesName(getDefaultSharedPreferencesName(context)); 192 } 193 194 /** 195 * Sets the owning preference fragment 196 */ setFragment(PreferenceFragment fragment)197 void setFragment(PreferenceFragment fragment) { 198 mFragment = fragment; 199 } 200 201 /** 202 * Returns the owning preference fragment, if any. 203 */ 204 @Nullable getFragment()205 PreferenceFragment getFragment() { 206 return mFragment; 207 } 208 209 /** 210 * Sets a {@link PreferenceDataStore} to be used by all Preferences associated with this manager 211 * that don't have a custom {@link PreferenceDataStore} assigned via 212 * {@link Preference#setPreferenceDataStore(PreferenceDataStore)}. Also if the data store is 213 * set, the child preferences won't use {@link android.content.SharedPreferences} as long as 214 * they are assigned to this manager. 215 * 216 * @param dataStore The {@link PreferenceDataStore} to be used by this manager. 217 * @see Preference#setPreferenceDataStore(PreferenceDataStore) 218 */ setPreferenceDataStore(PreferenceDataStore dataStore)219 public void setPreferenceDataStore(PreferenceDataStore dataStore) { 220 mPreferenceDataStore = dataStore; 221 } 222 223 /** 224 * Returns the {@link PreferenceDataStore} associated with this manager or {@code null} if 225 * the default {@link android.content.SharedPreferences} are used instead. 226 * 227 * @return The {@link PreferenceDataStore} associated with this manager or {@code null} if none. 228 * @see #setPreferenceDataStore(PreferenceDataStore) 229 */ 230 @Nullable getPreferenceDataStore()231 public PreferenceDataStore getPreferenceDataStore() { 232 return mPreferenceDataStore; 233 } 234 235 /** 236 * Returns a list of {@link Activity} (indirectly) that match a given 237 * {@link Intent}. 238 * 239 * @param queryIntent The Intent to match. 240 * @return The list of {@link ResolveInfo} that point to the matched 241 * activities. 242 */ queryIntentActivities(Intent queryIntent)243 private List<ResolveInfo> queryIntentActivities(Intent queryIntent) { 244 return mContext.getPackageManager().queryIntentActivities(queryIntent, 245 PackageManager.GET_META_DATA); 246 } 247 248 /** 249 * Inflates a preference hierarchy from the preference hierarchies of 250 * {@link Activity Activities} that match the given {@link Intent}. An 251 * {@link Activity} defines its preference hierarchy with meta-data using 252 * the {@link #METADATA_KEY_PREFERENCES} key. 253 * <p> 254 * If a preference hierarchy is given, the new preference hierarchies will 255 * be merged in. 256 * 257 * @param queryIntent The intent to match activities. 258 * @param rootPreferences Optional existing hierarchy to merge the new 259 * hierarchies into. 260 * @return The root hierarchy (if one was not provided, the new hierarchy's 261 * root). 262 */ inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences)263 PreferenceScreen inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences) { 264 final List<ResolveInfo> activities = queryIntentActivities(queryIntent); 265 final HashSet<String> inflatedRes = new HashSet<String>(); 266 267 for (int i = activities.size() - 1; i >= 0; i--) { 268 final ActivityInfo activityInfo = activities.get(i).activityInfo; 269 final Bundle metaData = activityInfo.metaData; 270 271 if ((metaData == null) || !metaData.containsKey(METADATA_KEY_PREFERENCES)) { 272 continue; 273 } 274 275 // Need to concat the package with res ID since the same res ID 276 // can be re-used across contexts 277 final String uniqueResId = activityInfo.packageName + ":" 278 + activityInfo.metaData.getInt(METADATA_KEY_PREFERENCES); 279 280 if (!inflatedRes.contains(uniqueResId)) { 281 inflatedRes.add(uniqueResId); 282 283 final Context context; 284 try { 285 context = mContext.createPackageContext(activityInfo.packageName, 0); 286 } catch (NameNotFoundException e) { 287 Log.w(TAG, "Could not create context for " + activityInfo.packageName + ": " 288 + Log.getStackTraceString(e)); 289 continue; 290 } 291 292 final PreferenceInflater inflater = new PreferenceInflater(context, this); 293 final XmlResourceParser parser = activityInfo.loadXmlMetaData(context 294 .getPackageManager(), METADATA_KEY_PREFERENCES); 295 rootPreferences = (PreferenceScreen) inflater 296 .inflate(parser, rootPreferences, true); 297 parser.close(); 298 } 299 } 300 301 rootPreferences.onAttachedToHierarchy(this); 302 303 return rootPreferences; 304 } 305 306 /** 307 * Inflates a preference hierarchy from XML. If a preference hierarchy is 308 * given, the new preference hierarchies will be merged in. 309 * 310 * @param context The context of the resource. 311 * @param resId The resource ID of the XML to inflate. 312 * @param rootPreferences Optional existing hierarchy to merge the new 313 * hierarchies into. 314 * @return The root hierarchy (if one was not provided, the new hierarchy's 315 * root). 316 * @hide 317 */ inflateFromResource(Context context, @XmlRes int resId, PreferenceScreen rootPreferences)318 public PreferenceScreen inflateFromResource(Context context, @XmlRes int resId, 319 PreferenceScreen rootPreferences) { 320 // Block commits 321 setNoCommit(true); 322 323 final PreferenceInflater inflater = new PreferenceInflater(context, this); 324 rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences, true); 325 rootPreferences.onAttachedToHierarchy(this); 326 327 // Unblock commits 328 setNoCommit(false); 329 330 return rootPreferences; 331 } 332 createPreferenceScreen(Context context)333 public PreferenceScreen createPreferenceScreen(Context context) { 334 final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null); 335 preferenceScreen.onAttachedToHierarchy(this); 336 return preferenceScreen; 337 } 338 339 /** 340 * Called by a preference to get a unique ID in its hierarchy. 341 * 342 * @return A unique ID. 343 */ getNextId()344 long getNextId() { 345 synchronized (this) { 346 return mNextId++; 347 } 348 } 349 350 /** 351 * Returns the current name of the SharedPreferences file that preferences managed by 352 * this will use. 353 * 354 * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}. 355 * @see Context#getSharedPreferences(String, int) 356 */ getSharedPreferencesName()357 public String getSharedPreferencesName() { 358 return mSharedPreferencesName; 359 } 360 361 /** 362 * Sets the name of the SharedPreferences file that preferences managed by this 363 * will use. 364 * 365 * <p>If custom {@link PreferenceDataStore} is set, this won't override its usage. 366 * 367 * @param sharedPreferencesName The name of the SharedPreferences file. 368 * @see Context#getSharedPreferences(String, int) 369 * @see #setPreferenceDataStore(PreferenceDataStore) 370 */ setSharedPreferencesName(String sharedPreferencesName)371 public void setSharedPreferencesName(String sharedPreferencesName) { 372 mSharedPreferencesName = sharedPreferencesName; 373 mSharedPreferences = null; 374 } 375 376 /** 377 * Returns the current mode of the SharedPreferences file that preferences managed by 378 * this will use. 379 * 380 * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}. 381 * @see Context#getSharedPreferences(String, int) 382 */ getSharedPreferencesMode()383 public int getSharedPreferencesMode() { 384 return mSharedPreferencesMode; 385 } 386 387 /** 388 * Sets the mode of the SharedPreferences file that preferences managed by this 389 * will use. 390 * 391 * @param sharedPreferencesMode The mode of the SharedPreferences file. 392 * @see Context#getSharedPreferences(String, int) 393 */ setSharedPreferencesMode(int sharedPreferencesMode)394 public void setSharedPreferencesMode(int sharedPreferencesMode) { 395 mSharedPreferencesMode = sharedPreferencesMode; 396 mSharedPreferences = null; 397 } 398 399 /** 400 * Sets the storage location used internally by this class to be the default 401 * provided by the hosting {@link Context}. 402 */ setStorageDefault()403 public void setStorageDefault() { 404 mStorage = STORAGE_DEFAULT; 405 mSharedPreferences = null; 406 } 407 408 /** 409 * Explicitly set the storage location used internally by this class to be 410 * device-protected storage. 411 * <p> 412 * On devices with direct boot, data stored in this location is encrypted 413 * with a key tied to the physical device, and it can be accessed 414 * immediately after the device has booted successfully, both 415 * <em>before and after</em> the user has authenticated with their 416 * credentials (such as a lock pattern or PIN). 417 * <p> 418 * Because device-protected data is available without user authentication, 419 * you should carefully limit the data you store using this Context. For 420 * example, storing sensitive authentication tokens or passwords in the 421 * device-protected area is strongly discouraged. 422 * 423 * @see Context#createDeviceProtectedStorageContext() 424 */ setStorageDeviceProtected()425 public void setStorageDeviceProtected() { 426 mStorage = STORAGE_DEVICE_PROTECTED; 427 mSharedPreferences = null; 428 } 429 430 /** 431 * Explicitly set the storage location used internally by this class to be 432 * credential-protected storage. This is the default storage area for apps 433 * unless {@code forceDeviceProtectedStorage} was requested. 434 * <p> 435 * On devices with direct boot, data stored in this location is encrypted 436 * with a key tied to user credentials, which can be accessed 437 * <em>only after</em> the user has entered their credentials (such as a 438 * lock pattern or PIN). 439 * 440 * @see Context#createCredentialProtectedStorageContext() 441 * @hide 442 */ 443 @SystemApi setStorageCredentialProtected()444 public void setStorageCredentialProtected() { 445 mStorage = STORAGE_CREDENTIAL_PROTECTED; 446 mSharedPreferences = null; 447 } 448 449 /** 450 * Indicates if the storage location used internally by this class is the 451 * default provided by the hosting {@link Context}. 452 * 453 * @see #setStorageDefault() 454 * @see #setStorageDeviceProtected() 455 */ isStorageDefault()456 public boolean isStorageDefault() { 457 return mStorage == STORAGE_DEFAULT; 458 } 459 460 /** 461 * Indicates if the storage location used internally by this class is backed 462 * by device-protected storage. 463 * 464 * @see #setStorageDefault() 465 * @see #setStorageDeviceProtected() 466 */ isStorageDeviceProtected()467 public boolean isStorageDeviceProtected() { 468 return mStorage == STORAGE_DEVICE_PROTECTED; 469 } 470 471 /** 472 * Indicates if the storage location used internally by this class is backed 473 * by credential-protected storage. 474 * 475 * @see #setStorageDefault() 476 * @see #setStorageDeviceProtected() 477 * @hide 478 */ 479 @SystemApi isStorageCredentialProtected()480 public boolean isStorageCredentialProtected() { 481 return mStorage == STORAGE_CREDENTIAL_PROTECTED; 482 } 483 484 /** 485 * Gets a {@link SharedPreferences} instance that preferences managed by this will use. 486 * 487 * @return a {@link SharedPreferences} instance pointing to the file that contains the values of 488 * preferences that are managed by this PreferenceManager. If a 489 * {@link PreferenceDataStore} has been set, this method returns {@code null}. 490 */ getSharedPreferences()491 public SharedPreferences getSharedPreferences() { 492 if (mPreferenceDataStore != null) { 493 return null; 494 } 495 496 if (mSharedPreferences == null) { 497 final Context storageContext; 498 switch (mStorage) { 499 case STORAGE_DEVICE_PROTECTED: 500 storageContext = mContext.createDeviceProtectedStorageContext(); 501 break; 502 case STORAGE_CREDENTIAL_PROTECTED: 503 storageContext = mContext.createCredentialProtectedStorageContext(); 504 break; 505 default: 506 storageContext = mContext; 507 break; 508 } 509 510 mSharedPreferences = storageContext.getSharedPreferences(mSharedPreferencesName, 511 mSharedPreferencesMode); 512 } 513 514 return mSharedPreferences; 515 } 516 517 /** 518 * Gets a {@link SharedPreferences} instance that points to the default file that is used by 519 * the preference framework in the given context. 520 * 521 * @param context The context of the preferences whose values are wanted. 522 * @return A {@link SharedPreferences} instance that can be used to retrieve and listen 523 * to values of the preferences. 524 */ getDefaultSharedPreferences(Context context)525 public static SharedPreferences getDefaultSharedPreferences(Context context) { 526 return context.getSharedPreferences(getDefaultSharedPreferencesName(context), 527 getDefaultSharedPreferencesMode()); 528 } 529 530 /** 531 * Returns the name used for storing default shared preferences. 532 * 533 * @see #getDefaultSharedPreferences(Context) 534 * @see Context#getSharedPreferencesPath(String) 535 */ getDefaultSharedPreferencesName(Context context)536 public static String getDefaultSharedPreferencesName(Context context) { 537 return context.getPackageName() + "_preferences"; 538 } 539 getDefaultSharedPreferencesMode()540 private static int getDefaultSharedPreferencesMode() { 541 return Context.MODE_PRIVATE; 542 } 543 544 /** 545 * Returns the root of the preference hierarchy managed by this class. 546 * 547 * @return The {@link PreferenceScreen} object that is at the root of the hierarchy. 548 */ 549 @Nullable getPreferenceScreen()550 PreferenceScreen getPreferenceScreen() { 551 return mPreferenceScreen; 552 } 553 554 /** 555 * Sets the root of the preference hierarchy. 556 * 557 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 558 * @return Whether the {@link PreferenceScreen} given is different than the previous. 559 */ setPreferences(PreferenceScreen preferenceScreen)560 boolean setPreferences(PreferenceScreen preferenceScreen) { 561 if (preferenceScreen != mPreferenceScreen) { 562 mPreferenceScreen = preferenceScreen; 563 return true; 564 } 565 566 return false; 567 } 568 569 /** 570 * Finds a {@link Preference} based on its key. 571 * 572 * @param key the key of the preference to retrieve 573 * @return the {@link Preference} with the key, or {@code null} 574 * @see PreferenceGroup#findPreference(CharSequence) 575 */ 576 @Nullable findPreference(CharSequence key)577 public Preference findPreference(CharSequence key) { 578 if (mPreferenceScreen == null) { 579 return null; 580 } 581 582 return mPreferenceScreen.findPreference(key); 583 } 584 585 /** 586 * Sets the default values from an XML preference file by reading the values defined 587 * by each {@link Preference} item's {@code android:defaultValue} attribute. This should 588 * be called by the application's main activity. 589 * <p> 590 * 591 * @param context The context of the shared preferences. 592 * @param resId The resource ID of the preference XML file. 593 * @param readAgain Whether to re-read the default values. 594 * If false, this method sets the default values only if this 595 * method has never been called in the past (or if the 596 * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared 597 * preferences file is false). To attempt to set the default values again 598 * bypassing this check, set {@code readAgain} to true. 599 * <p class="note"> 600 * Note: this will NOT reset preferences back to their default 601 * values. For that functionality, use 602 * {@link PreferenceManager#getDefaultSharedPreferences(Context)} 603 * and clear it followed by a call to this method with this 604 * parameter set to true. 605 */ setDefaultValues(Context context, @XmlRes int resId, boolean readAgain)606 public static void setDefaultValues(Context context, @XmlRes int resId, boolean readAgain) { 607 608 // Use the default shared preferences name and mode 609 setDefaultValues(context, getDefaultSharedPreferencesName(context), 610 getDefaultSharedPreferencesMode(), resId, readAgain); 611 } 612 613 /** 614 * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows 615 * the client to provide the filename and mode of the shared preferences 616 * file. 617 * 618 * @param context The context of the shared preferences. 619 * @param sharedPreferencesName A custom name for the shared preferences file. 620 * @param sharedPreferencesMode The file creation mode for the shared preferences file, such 621 * as {@link android.content.Context#MODE_PRIVATE} or {@link 622 * android.content.Context#MODE_PRIVATE} 623 * @param resId The resource ID of the preference XML file. 624 * @param readAgain Whether to re-read the default values. 625 * If false, this method will set the default values only if this 626 * method has never been called in the past (or if the 627 * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared 628 * preferences file is false). To attempt to set the default values again 629 * bypassing this check, set {@code readAgain} to true. 630 * <p class="note"> 631 * Note: this will NOT reset preferences back to their default 632 * values. For that functionality, use 633 * {@link PreferenceManager#getDefaultSharedPreferences(Context)} 634 * and clear it followed by a call to this method with this 635 * parameter set to true. 636 * 637 * @see #setDefaultValues(Context, int, boolean) 638 * @see #setSharedPreferencesName(String) 639 * @see #setSharedPreferencesMode(int) 640 */ setDefaultValues(Context context, String sharedPreferencesName, int sharedPreferencesMode, int resId, boolean readAgain)641 public static void setDefaultValues(Context context, String sharedPreferencesName, 642 int sharedPreferencesMode, int resId, boolean readAgain) { 643 final SharedPreferences defaultValueSp = context.getSharedPreferences( 644 KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE); 645 646 if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) { 647 final PreferenceManager pm = new PreferenceManager(context); 648 pm.setSharedPreferencesName(sharedPreferencesName); 649 pm.setSharedPreferencesMode(sharedPreferencesMode); 650 pm.inflateFromResource(context, resId, null); 651 652 SharedPreferences.Editor editor = 653 defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true); 654 try { 655 editor.apply(); 656 } catch (AbstractMethodError unused) { 657 // The app injected its own pre-Gingerbread 658 // SharedPreferences.Editor implementation without 659 // an apply method. 660 editor.commit(); 661 } 662 } 663 } 664 665 /** 666 * Returns an editor to use when modifying the shared preferences. 667 * 668 * <p>Do NOT commit unless {@link #shouldCommit()} returns true. 669 * 670 * @return an editor to use to write to shared preferences. If a {@link PreferenceDataStore} 671 * has been set, this method returns {@code null}. 672 * @see #shouldCommit() 673 */ getEditor()674 SharedPreferences.Editor getEditor() { 675 if (mPreferenceDataStore != null) { 676 return null; 677 } 678 679 if (mNoCommit) { 680 if (mEditor == null) { 681 mEditor = getSharedPreferences().edit(); 682 } 683 684 return mEditor; 685 } else { 686 return getSharedPreferences().edit(); 687 } 688 } 689 690 /** 691 * Whether it is the client's responsibility to commit on the 692 * {@link #getEditor()}. This will return false in cases where the writes 693 * should be batched, for example when inflating preferences from XML. 694 * 695 * <p>If preferences are using {@link PreferenceDataStore} this value is irrelevant. 696 * 697 * @return Whether the client should commit. 698 */ shouldCommit()699 boolean shouldCommit() { 700 return !mNoCommit; 701 } 702 setNoCommit(boolean noCommit)703 private void setNoCommit(boolean noCommit) { 704 if (!noCommit && mEditor != null) { 705 try { 706 mEditor.apply(); 707 } catch (AbstractMethodError unused) { 708 // The app injected its own pre-Gingerbread 709 // SharedPreferences.Editor implementation without 710 // an apply method. 711 mEditor.commit(); 712 } 713 } 714 mNoCommit = noCommit; 715 } 716 717 /** 718 * Returns the activity that shows the preferences. This is useful for doing 719 * managed queries, but in most cases the use of {@link #getContext()} is 720 * preferred. 721 * 722 * <p>This will return {@code null} if this class was instantiated with a Context 723 * instead of Activity. For example, when setting the default values. 724 * 725 * @return The activity that shows the preferences. 726 * @see #mContext 727 */ 728 @Nullable getActivity()729 Activity getActivity() { 730 return mActivity; 731 } 732 733 /** 734 * Returns the context. This is preferred over {@link #getActivity()} when 735 * possible. 736 * 737 * @return The context. 738 */ getContext()739 Context getContext() { 740 return mContext; 741 } 742 743 /** 744 * Registers a listener. 745 * 746 * @see OnActivityResultListener 747 */ registerOnActivityResultListener(OnActivityResultListener listener)748 void registerOnActivityResultListener(OnActivityResultListener listener) { 749 synchronized (this) { 750 if (mActivityResultListeners == null) { 751 mActivityResultListeners = new ArrayList<OnActivityResultListener>(); 752 } 753 754 if (!mActivityResultListeners.contains(listener)) { 755 mActivityResultListeners.add(listener); 756 } 757 } 758 } 759 760 /** 761 * Unregisters a listener. 762 * 763 * @see OnActivityResultListener 764 */ unregisterOnActivityResultListener(OnActivityResultListener listener)765 void unregisterOnActivityResultListener(OnActivityResultListener listener) { 766 synchronized (this) { 767 if (mActivityResultListeners != null) { 768 mActivityResultListeners.remove(listener); 769 } 770 } 771 } 772 773 /** 774 * Called by the {@link PreferenceManager} to dispatch a subactivity result. 775 */ dispatchActivityResult(int requestCode, int resultCode, Intent data)776 void dispatchActivityResult(int requestCode, int resultCode, Intent data) { 777 List<OnActivityResultListener> list; 778 779 synchronized (this) { 780 if (mActivityResultListeners == null) return; 781 list = new ArrayList<OnActivityResultListener>(mActivityResultListeners); 782 } 783 784 final int N = list.size(); 785 for (int i = 0; i < N; i++) { 786 if (list.get(i).onActivityResult(requestCode, resultCode, data)) { 787 break; 788 } 789 } 790 } 791 792 /** 793 * Registers a listener. 794 * 795 * @see OnActivityStopListener 796 * @hide 797 */ registerOnActivityStopListener(OnActivityStopListener listener)798 public void registerOnActivityStopListener(OnActivityStopListener listener) { 799 synchronized (this) { 800 if (mActivityStopListeners == null) { 801 mActivityStopListeners = new ArrayList<OnActivityStopListener>(); 802 } 803 804 if (!mActivityStopListeners.contains(listener)) { 805 mActivityStopListeners.add(listener); 806 } 807 } 808 } 809 810 /** 811 * Unregisters a listener. 812 * 813 * @see OnActivityStopListener 814 * @hide 815 */ unregisterOnActivityStopListener(OnActivityStopListener listener)816 public void unregisterOnActivityStopListener(OnActivityStopListener listener) { 817 synchronized (this) { 818 if (mActivityStopListeners != null) { 819 mActivityStopListeners.remove(listener); 820 } 821 } 822 } 823 824 /** 825 * Called by the {@link PreferenceManager} to dispatch the activity stop 826 * event. 827 */ dispatchActivityStop()828 void dispatchActivityStop() { 829 List<OnActivityStopListener> list; 830 831 synchronized (this) { 832 if (mActivityStopListeners == null) return; 833 list = new ArrayList<OnActivityStopListener>(mActivityStopListeners); 834 } 835 836 final int N = list.size(); 837 for (int i = 0; i < N; i++) { 838 list.get(i).onActivityStop(); 839 } 840 } 841 842 /** 843 * Registers a listener. 844 * 845 * @see OnActivityDestroyListener 846 */ registerOnActivityDestroyListener(OnActivityDestroyListener listener)847 void registerOnActivityDestroyListener(OnActivityDestroyListener listener) { 848 synchronized (this) { 849 if (mActivityDestroyListeners == null) { 850 mActivityDestroyListeners = new ArrayList<OnActivityDestroyListener>(); 851 } 852 853 if (!mActivityDestroyListeners.contains(listener)) { 854 mActivityDestroyListeners.add(listener); 855 } 856 } 857 } 858 859 /** 860 * Unregisters a listener. 861 * 862 * @see OnActivityDestroyListener 863 */ unregisterOnActivityDestroyListener(OnActivityDestroyListener listener)864 void unregisterOnActivityDestroyListener(OnActivityDestroyListener listener) { 865 synchronized (this) { 866 if (mActivityDestroyListeners != null) { 867 mActivityDestroyListeners.remove(listener); 868 } 869 } 870 } 871 872 /** 873 * Called by the {@link PreferenceManager} to dispatch the activity destroy 874 * event. 875 */ dispatchActivityDestroy()876 void dispatchActivityDestroy() { 877 List<OnActivityDestroyListener> list = null; 878 879 synchronized (this) { 880 if (mActivityDestroyListeners != null) { 881 list = new ArrayList<OnActivityDestroyListener>(mActivityDestroyListeners); 882 } 883 } 884 885 if (list != null) { 886 final int N = list.size(); 887 for (int i = 0; i < N; i++) { 888 list.get(i).onActivityDestroy(); 889 } 890 } 891 892 // Dismiss any PreferenceScreens still showing 893 dismissAllScreens(); 894 } 895 896 /** 897 * Returns a request code that is unique for the activity. Each subsequent 898 * call to this method should return another unique request code. 899 * 900 * @return A unique request code that will never be used by anyone other 901 * than the caller of this method. 902 */ getNextRequestCode()903 int getNextRequestCode() { 904 synchronized (this) { 905 return mNextRequestCode++; 906 } 907 } 908 addPreferencesScreen(DialogInterface screen)909 void addPreferencesScreen(DialogInterface screen) { 910 synchronized (this) { 911 912 if (mPreferencesScreens == null) { 913 mPreferencesScreens = new ArrayList<DialogInterface>(); 914 } 915 916 mPreferencesScreens.add(screen); 917 } 918 } 919 removePreferencesScreen(DialogInterface screen)920 void removePreferencesScreen(DialogInterface screen) { 921 synchronized (this) { 922 923 if (mPreferencesScreens == null) { 924 return; 925 } 926 927 mPreferencesScreens.remove(screen); 928 } 929 } 930 931 /** 932 * Called by {@link PreferenceActivity} to dispatch the new Intent event. 933 * 934 * @param intent The new Intent. 935 */ dispatchNewIntent(Intent intent)936 void dispatchNewIntent(Intent intent) { 937 dismissAllScreens(); 938 } 939 dismissAllScreens()940 private void dismissAllScreens() { 941 // Remove any of the previously shown preferences screens 942 ArrayList<DialogInterface> screensToDismiss; 943 944 synchronized (this) { 945 946 if (mPreferencesScreens == null) { 947 return; 948 } 949 950 screensToDismiss = new ArrayList<DialogInterface>(mPreferencesScreens); 951 mPreferencesScreens.clear(); 952 } 953 954 for (int i = screensToDismiss.size() - 1; i >= 0; i--) { 955 screensToDismiss.get(i).dismiss(); 956 } 957 } 958 959 /** 960 * Sets the callback to be invoked when a {@link Preference} in the 961 * hierarchy rooted at this {@link PreferenceManager} is clicked. 962 * 963 * @param listener The callback to be invoked. 964 */ setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener)965 void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) { 966 mOnPreferenceTreeClickListener = listener; 967 } 968 969 @Nullable getOnPreferenceTreeClickListener()970 OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() { 971 return mOnPreferenceTreeClickListener; 972 } 973 974 /** 975 * Interface definition for a callback to be invoked when a 976 * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is 977 * clicked. 978 * 979 * @hide 980 */ 981 public interface OnPreferenceTreeClickListener { 982 /** 983 * Called when a preference in the tree rooted at this 984 * {@link PreferenceScreen} has been clicked. 985 * 986 * @param preferenceScreen The {@link PreferenceScreen} that the 987 * preference is located in. 988 * @param preference The preference that was clicked. 989 * @return Whether the click was handled. 990 */ onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)991 boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference); 992 } 993 994 /** 995 * Interface definition for a class that will be called when the container's activity 996 * receives an activity result. 997 */ 998 public interface OnActivityResultListener { 999 1000 /** 1001 * See Activity's onActivityResult. 1002 * 1003 * @return Whether the request code was handled (in which case 1004 * subsequent listeners will not be called. 1005 */ onActivityResult(int requestCode, int resultCode, Intent data)1006 boolean onActivityResult(int requestCode, int resultCode, Intent data); 1007 } 1008 1009 /** 1010 * Interface definition for a class that will be called when the container's activity 1011 * is stopped. 1012 */ 1013 public interface OnActivityStopListener { 1014 1015 /** 1016 * See Activity's onStop. 1017 */ onActivityStop()1018 void onActivityStop(); 1019 } 1020 1021 /** 1022 * Interface definition for a class that will be called when the container's activity 1023 * is destroyed. 1024 */ 1025 public interface OnActivityDestroyListener { 1026 1027 /** 1028 * See Activity's onDestroy. 1029 */ onActivityDestroy()1030 void onActivityDestroy(); 1031 } 1032 1033 } 1034