PreferenceManager.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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 17package android.preference; 18 19import java.util.ArrayList; 20import java.util.HashSet; 21import java.util.List; 22 23import android.app.Activity; 24import android.app.Dialog; 25import android.content.Context; 26import android.content.DialogInterface; 27import android.content.Intent; 28import android.content.SharedPreferences; 29import android.content.pm.ActivityInfo; 30import android.content.pm.PackageManager; 31import android.content.pm.ResolveInfo; 32import android.content.pm.PackageManager.NameNotFoundException; 33import android.content.res.XmlResourceParser; 34import android.os.Bundle; 35import android.util.Log; 36 37/** 38 * Used to help create {@link Preference} hierarchies 39 * from activities or XML. 40 * <p> 41 * In most cases, clients should use 42 * {@link PreferenceActivity#addPreferencesFromIntent} or 43 * {@link PreferenceActivity#addPreferencesFromResource(int)}. 44 * 45 * @see PreferenceActivity 46 */ 47public class PreferenceManager { 48 49 private static final String TAG = "PreferenceManager"; 50 51 /** 52 * The Activity meta-data key for its XML preference hierarchy. 53 */ 54 public static final String METADATA_KEY_PREFERENCES = "android.preference"; 55 56 public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values"; 57 58 /** 59 * @see #getActivity() 60 */ 61 private Activity mActivity; 62 63 /** 64 * The context to use. This should always be set. 65 * 66 * @see #mActivity 67 */ 68 private Context mContext; 69 70 /** 71 * The counter for unique IDs. 72 */ 73 private long mNextId = 0; 74 75 /** 76 * The counter for unique request codes. 77 */ 78 private int mNextRequestCode; 79 80 /** 81 * Cached shared preferences. 82 */ 83 private SharedPreferences mSharedPreferences; 84 85 /** 86 * If in no-commit mode, the shared editor to give out (which will be 87 * committed when exiting no-commit mode). 88 */ 89 private SharedPreferences.Editor mEditor; 90 91 /** 92 * Blocks commits from happening on the shared editor. This is used when 93 * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)} 94 */ 95 private boolean mNoCommit; 96 97 /** 98 * The SharedPreferences name that will be used for all {@link Preference}s 99 * managed by this instance. 100 */ 101 private String mSharedPreferencesName; 102 103 /** 104 * The SharedPreferences mode that will be used for all {@link Preference}s 105 * managed by this instance. 106 */ 107 private int mSharedPreferencesMode; 108 109 /** 110 * The {@link PreferenceScreen} at the root of the preference hierarchy. 111 */ 112 private PreferenceScreen mPreferenceScreen; 113 114 /** 115 * List of activity result listeners. 116 */ 117 private List<OnActivityResultListener> mActivityResultListeners; 118 119 /** 120 * List of activity stop listeners. 121 */ 122 private List<OnActivityStopListener> mActivityStopListeners; 123 124 /** 125 * List of activity destroy listeners. 126 */ 127 private List<OnActivityDestroyListener> mActivityDestroyListeners; 128 129 /** 130 * List of dialogs that should be dismissed when we receive onNewIntent in 131 * our PreferenceActivity. 132 */ 133 private List<DialogInterface> mPreferencesScreens; 134 135 private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener; 136 137 PreferenceManager(Activity activity, int firstRequestCode) { 138 mActivity = activity; 139 mNextRequestCode = firstRequestCode; 140 141 init(activity); 142 } 143 144 /** 145 * This constructor should ONLY be used when getting default values from 146 * an XML preference hierarchy. 147 * <p> 148 * The {@link PreferenceManager#PreferenceManager(Activity)} 149 * should be used ANY time a preference will be displayed, since some preference 150 * types need an Activity for managed queries. 151 */ 152 private PreferenceManager(Context context) { 153 init(context); 154 } 155 156 private void init(Context context) { 157 mContext = context; 158 159 setSharedPreferencesName(getDefaultSharedPreferencesName(context)); 160 } 161 162 /** 163 * Returns a list of {@link Activity} (indirectly) that match a given 164 * {@link Intent}. 165 * 166 * @param queryIntent The Intent to match. 167 * @return The list of {@link ResolveInfo} that point to the matched 168 * activities. 169 */ 170 private List<ResolveInfo> queryIntentActivities(Intent queryIntent) { 171 return mContext.getPackageManager().queryIntentActivities(queryIntent, 172 PackageManager.GET_META_DATA); 173 } 174 175 /** 176 * Inflates a preference hierarchy from the preference hierarchies of 177 * {@link Activity Activities} that match the given {@link Intent}. An 178 * {@link Activity} defines its preference hierarchy with meta-data using 179 * the {@link #METADATA_KEY_PREFERENCES} key. 180 * <p> 181 * If a preference hierarchy is given, the new preference hierarchies will 182 * be merged in. 183 * 184 * @param queryIntent The intent to match activities. 185 * @param rootPreferences Optional existing hierarchy to merge the new 186 * hierarchies into. 187 * @return The root hierarchy (if one was not provided, the new hierarchy's 188 * root). 189 */ 190 PreferenceScreen inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences) { 191 final List<ResolveInfo> activities = queryIntentActivities(queryIntent); 192 final HashSet<String> inflatedRes = new HashSet<String>(); 193 194 for (int i = activities.size() - 1; i >= 0; i--) { 195 final ActivityInfo activityInfo = activities.get(i).activityInfo; 196 final Bundle metaData = activityInfo.metaData; 197 198 if ((metaData == null) || !metaData.containsKey(METADATA_KEY_PREFERENCES)) { 199 continue; 200 } 201 202 // Need to concat the package with res ID since the same res ID 203 // can be re-used across contexts 204 final String uniqueResId = activityInfo.packageName + ":" 205 + activityInfo.metaData.getInt(METADATA_KEY_PREFERENCES); 206 207 if (!inflatedRes.contains(uniqueResId)) { 208 inflatedRes.add(uniqueResId); 209 210 final Context context; 211 try { 212 context = mContext.createPackageContext(activityInfo.packageName, 0); 213 } catch (NameNotFoundException e) { 214 Log.w(TAG, "Could not create context for " + activityInfo.packageName + ": " 215 + Log.getStackTraceString(e)); 216 continue; 217 } 218 219 final PreferenceInflater inflater = new PreferenceInflater(context, this); 220 final XmlResourceParser parser = activityInfo.loadXmlMetaData(context 221 .getPackageManager(), METADATA_KEY_PREFERENCES); 222 rootPreferences = (PreferenceScreen) inflater 223 .inflate(parser, rootPreferences, true); 224 parser.close(); 225 } 226 } 227 228 rootPreferences.onAttachedToHierarchy(this); 229 230 return rootPreferences; 231 } 232 233 /** 234 * Inflates a preference hierarchy from XML. If a preference hierarchy is 235 * given, the new preference hierarchies will be merged in. 236 * 237 * @param context The context of the resource. 238 * @param resId The resource ID of the XML to inflate. 239 * @param rootPreferences Optional existing hierarchy to merge the new 240 * hierarchies into. 241 * @return The root hierarchy (if one was not provided, the new hierarchy's 242 * root). 243 */ 244 PreferenceScreen inflateFromResource(Context context, int resId, 245 PreferenceScreen rootPreferences) { 246 // Block commits 247 setNoCommit(true); 248 249 final PreferenceInflater inflater = new PreferenceInflater(context, this); 250 rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences, true); 251 rootPreferences.onAttachedToHierarchy(this); 252 253 // Unblock commits 254 setNoCommit(false); 255 256 return rootPreferences; 257 } 258 259 public PreferenceScreen createPreferenceScreen(Context context) { 260 final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null); 261 preferenceScreen.onAttachedToHierarchy(this); 262 return preferenceScreen; 263 } 264 265 /** 266 * Called by a preference to get a unique ID in its hierarchy. 267 * 268 * @return A unique ID. 269 */ 270 long getNextId() { 271 synchronized (this) { 272 return mNextId++; 273 } 274 } 275 276 /** 277 * Returns the current name of the SharedPreferences file that preferences managed by 278 * this will use. 279 * 280 * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}. 281 * @see Context#getSharedPreferences(String, int) 282 */ 283 public String getSharedPreferencesName() { 284 return mSharedPreferencesName; 285 } 286 287 /** 288 * Sets the name of the SharedPreferences file that preferences managed by this 289 * will use. 290 * 291 * @param sharedPreferencesName The name of the SharedPreferences file. 292 * @see Context#getSharedPreferences(String, int) 293 */ 294 public void setSharedPreferencesName(String sharedPreferencesName) { 295 mSharedPreferencesName = sharedPreferencesName; 296 mSharedPreferences = null; 297 } 298 299 /** 300 * Returns the current mode of the SharedPreferences file that preferences managed by 301 * this will use. 302 * 303 * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}. 304 * @see Context#getSharedPreferences(String, int) 305 */ 306 public int getSharedPreferencesMode() { 307 return mSharedPreferencesMode; 308 } 309 310 /** 311 * Sets the mode of the SharedPreferences file that preferences managed by this 312 * will use. 313 * 314 * @param sharedPreferencesMode The mode of the SharedPreferences file. 315 * @see Context#getSharedPreferences(String, int) 316 */ 317 public void setSharedPreferencesMode(int sharedPreferencesMode) { 318 mSharedPreferencesMode = sharedPreferencesMode; 319 mSharedPreferences = null; 320 } 321 322 /** 323 * Gets a SharedPreferences instance that preferences managed by this will 324 * use. 325 * 326 * @return A SharedPreferences instance pointing to the file that contains 327 * the values of preferences that are managed by this. 328 */ 329 public SharedPreferences getSharedPreferences() { 330 if (mSharedPreferences == null) { 331 mSharedPreferences = mContext.getSharedPreferences(mSharedPreferencesName, 332 mSharedPreferencesMode); 333 } 334 335 return mSharedPreferences; 336 } 337 338 /** 339 * Gets a SharedPreferences instance that points to the default file that is 340 * used by the preference framework in the given context. 341 * 342 * @param context The context of the preferences whose values are wanted. 343 * @return A SharedPreferences instance that can be used to retrieve and 344 * listen to values of the preferences. 345 */ 346 public static SharedPreferences getDefaultSharedPreferences(Context context) { 347 return context.getSharedPreferences(getDefaultSharedPreferencesName(context), 348 getDefaultSharedPreferencesMode()); 349 } 350 351 private static String getDefaultSharedPreferencesName(Context context) { 352 return context.getPackageName() + "_preferences"; 353 } 354 355 private static int getDefaultSharedPreferencesMode() { 356 return Context.MODE_PRIVATE; 357 } 358 359 /** 360 * Returns the root of the preference hierarchy managed by this class. 361 * 362 * @return The {@link PreferenceScreen} object that is at the root of the hierarchy. 363 */ 364 PreferenceScreen getPreferenceScreen() { 365 return mPreferenceScreen; 366 } 367 368 /** 369 * Sets the root of the preference hierarchy. 370 * 371 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy. 372 * @return Whether the {@link PreferenceScreen} given is different than the previous. 373 */ 374 boolean setPreferences(PreferenceScreen preferenceScreen) { 375 if (preferenceScreen != mPreferenceScreen) { 376 mPreferenceScreen = preferenceScreen; 377 return true; 378 } 379 380 return false; 381 } 382 383 /** 384 * Finds a {@link Preference} based on its key. 385 * 386 * @param key The key of the preference to retrieve. 387 * @return The {@link Preference} with the key, or null. 388 * @see PreferenceGroup#findPreference(CharSequence) 389 */ 390 public Preference findPreference(CharSequence key) { 391 if (mPreferenceScreen == null) { 392 return null; 393 } 394 395 return mPreferenceScreen.findPreference(key); 396 } 397 398 /** 399 * Sets the default values from a preference hierarchy in XML. This should 400 * be called by the application's main activity. 401 * <p> 402 * If {@code readAgain} is false, this will only set the default values if this 403 * method has never been called in the past (or the 404 * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared 405 * preferences file is false). To attempt to set the default values again 406 * bypassing this check, set {@code readAgain} to true. 407 * 408 * @param context The context of the shared preferences. 409 * @param resId The resource ID of the preference hierarchy XML file. 410 * @param readAgain Whether to re-read the default values. 411 * <p> 412 * Note: this will NOT reset preferences back to their default 413 * values. For that functionality, use 414 * {@link PreferenceManager#getDefaultSharedPreferences(Context)} 415 * and clear it followed by a call to this method with this 416 * parameter set to true. 417 */ 418 public static void setDefaultValues(Context context, int resId, boolean readAgain) { 419 420 // Use the default shared preferences name and mode 421 setDefaultValues(context, getDefaultSharedPreferencesName(context), 422 getDefaultSharedPreferencesMode(), resId, readAgain); 423 } 424 425 /** 426 * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows 427 * the client to provide the filename and mode of the shared preferences 428 * file. 429 * 430 * @see #setDefaultValues(Context, int, boolean) 431 * @see #setSharedPreferencesName(String) 432 * @see #setSharedPreferencesMode(int) 433 */ 434 public static void setDefaultValues(Context context, String sharedPreferencesName, 435 int sharedPreferencesMode, int resId, boolean readAgain) { 436 final SharedPreferences defaultValueSp = context.getSharedPreferences( 437 KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE); 438 439 if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) { 440 final PreferenceManager pm = new PreferenceManager(context); 441 pm.setSharedPreferencesName(sharedPreferencesName); 442 pm.setSharedPreferencesMode(sharedPreferencesMode); 443 pm.inflateFromResource(context, resId, null); 444 445 defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true).commit(); 446 } 447 } 448 449 /** 450 * Returns an editor to use when modifying the shared preferences. 451 * <p> 452 * Do NOT commit unless {@link #shouldCommit()} returns true. 453 * 454 * @return An editor to use to write to shared preferences. 455 * @see #shouldCommit() 456 */ 457 SharedPreferences.Editor getEditor() { 458 459 if (mNoCommit) { 460 if (mEditor == null) { 461 mEditor = getSharedPreferences().edit(); 462 } 463 464 return mEditor; 465 } else { 466 return getSharedPreferences().edit(); 467 } 468 } 469 470 /** 471 * Whether it is the client's responsibility to commit on the 472 * {@link #getEditor()}. This will return false in cases where the writes 473 * should be batched, for example when inflating preferences from XML. 474 * 475 * @return Whether the client should commit. 476 */ 477 boolean shouldCommit() { 478 return !mNoCommit; 479 } 480 481 private void setNoCommit(boolean noCommit) { 482 if (!noCommit && mEditor != null) { 483 mEditor.commit(); 484 } 485 486 mNoCommit = noCommit; 487 } 488 489 /** 490 * Returns the activity that shows the preferences. This is useful for doing 491 * managed queries, but in most cases the use of {@link #getContext()} is 492 * preferred. 493 * <p> 494 * This will return null if this class was instantiated with a Context 495 * instead of Activity. For example, when setting the default values. 496 * 497 * @return The activity that shows the preferences. 498 * @see #mContext 499 */ 500 Activity getActivity() { 501 return mActivity; 502 } 503 504 /** 505 * Returns the context. This is preferred over {@link #getActivity()} when 506 * possible. 507 * 508 * @return The context. 509 */ 510 Context getContext() { 511 return mContext; 512 } 513 514 /** 515 * Registers a listener. 516 * 517 * @see OnActivityResultListener 518 */ 519 void registerOnActivityResultListener(OnActivityResultListener listener) { 520 synchronized (this) { 521 if (mActivityResultListeners == null) { 522 mActivityResultListeners = new ArrayList<OnActivityResultListener>(); 523 } 524 525 if (!mActivityResultListeners.contains(listener)) { 526 mActivityResultListeners.add(listener); 527 } 528 } 529 } 530 531 /** 532 * Unregisters a listener. 533 * 534 * @see OnActivityResultListener 535 */ 536 void unregisterOnActivityResultListener(OnActivityResultListener listener) { 537 synchronized (this) { 538 if (mActivityResultListeners != null) { 539 mActivityResultListeners.remove(listener); 540 } 541 } 542 } 543 544 /** 545 * Called by the {@link PreferenceManager} to dispatch a subactivity result. 546 */ 547 void dispatchActivityResult(int requestCode, int resultCode, Intent data) { 548 List<OnActivityResultListener> list; 549 550 synchronized (this) { 551 if (mActivityResultListeners == null) return; 552 list = new ArrayList<OnActivityResultListener>(mActivityResultListeners); 553 } 554 555 final int N = list.size(); 556 for (int i = 0; i < N; i++) { 557 if (list.get(i).onActivityResult(requestCode, resultCode, data)) { 558 break; 559 } 560 } 561 } 562 563 /** 564 * Registers a listener. 565 * 566 * @see OnActivityStopListener 567 */ 568 void registerOnActivityStopListener(OnActivityStopListener listener) { 569 synchronized (this) { 570 if (mActivityStopListeners == null) { 571 mActivityStopListeners = new ArrayList<OnActivityStopListener>(); 572 } 573 574 if (!mActivityStopListeners.contains(listener)) { 575 mActivityStopListeners.add(listener); 576 } 577 } 578 } 579 580 /** 581 * Unregisters a listener. 582 * 583 * @see OnActivityStopListener 584 */ 585 void unregisterOnActivityStopListener(OnActivityStopListener listener) { 586 synchronized (this) { 587 if (mActivityStopListeners != null) { 588 mActivityStopListeners.remove(listener); 589 } 590 } 591 } 592 593 /** 594 * Called by the {@link PreferenceManager} to dispatch the activity stop 595 * event. 596 */ 597 void dispatchActivityStop() { 598 List<OnActivityStopListener> list; 599 600 synchronized (this) { 601 if (mActivityStopListeners == null) return; 602 list = new ArrayList<OnActivityStopListener>(mActivityStopListeners); 603 } 604 605 final int N = list.size(); 606 for (int i = 0; i < N; i++) { 607 list.get(i).onActivityStop(); 608 } 609 } 610 611 /** 612 * Registers a listener. 613 * 614 * @see OnActivityDestroyListener 615 */ 616 void registerOnActivityDestroyListener(OnActivityDestroyListener listener) { 617 synchronized (this) { 618 if (mActivityDestroyListeners == null) { 619 mActivityDestroyListeners = new ArrayList<OnActivityDestroyListener>(); 620 } 621 622 if (!mActivityDestroyListeners.contains(listener)) { 623 mActivityDestroyListeners.add(listener); 624 } 625 } 626 } 627 628 /** 629 * Unregisters a listener. 630 * 631 * @see OnActivityDestroyListener 632 */ 633 void unregisterOnActivityDestroyListener(OnActivityDestroyListener listener) { 634 synchronized (this) { 635 if (mActivityDestroyListeners != null) { 636 mActivityDestroyListeners.remove(listener); 637 } 638 } 639 } 640 641 /** 642 * Called by the {@link PreferenceManager} to dispatch the activity destroy 643 * event. 644 */ 645 void dispatchActivityDestroy() { 646 List<OnActivityDestroyListener> list = null; 647 648 synchronized (this) { 649 if (mActivityDestroyListeners != null) { 650 list = new ArrayList<OnActivityDestroyListener>(mActivityDestroyListeners); 651 } 652 } 653 654 if (list != null) { 655 final int N = list.size(); 656 for (int i = 0; i < N; i++) { 657 list.get(i).onActivityDestroy(); 658 } 659 } 660 661 // Dismiss any PreferenceScreens still showing 662 dismissAllScreens(); 663 } 664 665 /** 666 * Returns a request code that is unique for the activity. Each subsequent 667 * call to this method should return another unique request code. 668 * 669 * @return A unique request code that will never be used by anyone other 670 * than the caller of this method. 671 */ 672 int getNextRequestCode() { 673 synchronized (this) { 674 return mNextRequestCode++; 675 } 676 } 677 678 void addPreferencesScreen(DialogInterface screen) { 679 synchronized (this) { 680 681 if (mPreferencesScreens == null) { 682 mPreferencesScreens = new ArrayList<DialogInterface>(); 683 } 684 685 mPreferencesScreens.add(screen); 686 } 687 } 688 689 void removePreferencesScreen(DialogInterface screen) { 690 synchronized (this) { 691 692 if (mPreferencesScreens == null) { 693 return; 694 } 695 696 mPreferencesScreens.remove(screen); 697 } 698 } 699 700 /** 701 * Called by {@link PreferenceActivity} to dispatch the new Intent event. 702 * 703 * @param intent The new Intent. 704 */ 705 void dispatchNewIntent(Intent intent) { 706 dismissAllScreens(); 707 } 708 709 private void dismissAllScreens() { 710 // Remove any of the previously shown preferences screens 711 ArrayList<DialogInterface> screensToDismiss; 712 713 synchronized (this) { 714 715 if (mPreferencesScreens == null) { 716 return; 717 } 718 719 screensToDismiss = new ArrayList<DialogInterface>(mPreferencesScreens); 720 mPreferencesScreens.clear(); 721 } 722 723 for (int i = screensToDismiss.size() - 1; i >= 0; i--) { 724 screensToDismiss.get(i).dismiss(); 725 } 726 } 727 728 /** 729 * Sets the callback to be invoked when a {@link Preference} in the 730 * hierarchy rooted at this {@link PreferenceManager} is clicked. 731 * 732 * @param listener The callback to be invoked. 733 */ 734 void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) { 735 mOnPreferenceTreeClickListener = listener; 736 } 737 738 OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() { 739 return mOnPreferenceTreeClickListener; 740 } 741 742 /** 743 * Interface definition for a callback to be invoked when a 744 * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is 745 * clicked. 746 */ 747 interface OnPreferenceTreeClickListener { 748 /** 749 * Called when a preference in the tree rooted at this 750 * {@link PreferenceScreen} has been clicked. 751 * 752 * @param preferenceScreen The {@link PreferenceScreen} that the 753 * preference is located in. 754 * @param preference The preference that was clicked. 755 * @return Whether the click was handled. 756 */ 757 boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference); 758 } 759 760 /** 761 * Interface definition for a class that will be called when the container's activity 762 * receives an activity result. 763 */ 764 public interface OnActivityResultListener { 765 766 /** 767 * See Activity's onActivityResult. 768 * 769 * @return Whether the request code was handled (in which case 770 * subsequent listeners will not be called. 771 */ 772 boolean onActivityResult(int requestCode, int resultCode, Intent data); 773 } 774 775 /** 776 * Interface definition for a class that will be called when the container's activity 777 * is stopped. 778 */ 779 public interface OnActivityStopListener { 780 781 /** 782 * See Activity's onStop. 783 */ 784 void onActivityStop(); 785 } 786 787 /** 788 * Interface definition for a class that will be called when the container's activity 789 * is destroyed. 790 */ 791 public interface OnActivityDestroyListener { 792 793 /** 794 * See Activity's onDestroy. 795 */ 796 void onActivityDestroy(); 797 } 798 799} 800