2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.preference;
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.content.Intent;
23 import android.content.SharedPreferences;
24 import android.content.pm.ActivityInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.res.XmlResourceParser;
29 import android.os.Bundle;
30 import android.util.Log;
32 import java.util.ArrayList;
33 import java.util.HashSet;
34 import java.util.List;
37 * Used to help create {@link Preference} hierarchies
38 * from activities or XML.
40 * In most cases, clients should use
41 * {@link PreferenceActivity#addPreferencesFromIntent} or
42 * {@link PreferenceActivity#addPreferencesFromResource(int)}.
44 * @see PreferenceActivity
46 public class PreferenceManager {
48 private static final String TAG = "PreferenceManager";
51 * The Activity meta-data key for its XML preference hierarchy.
53 public static final String METADATA_KEY_PREFERENCES = "android.preference";
55 public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
60 private Activity mActivity;
63 * Fragment that owns this instance.
65 private PreferenceFragment mFragment;
68 * The context to use. This should always be set.
72 private Context mContext;
75 * The counter for unique IDs.
77 private long mNextId = 0;
80 * The counter for unique request codes.
82 private int mNextRequestCode;
85 * Cached shared preferences.
87 private SharedPreferences mSharedPreferences;
90 * If in no-commit mode, the shared editor to give out (which will be
91 * committed when exiting no-commit mode).
93 private SharedPreferences.Editor mEditor;
96 * Blocks commits from happening on the shared editor. This is used when
97 * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)}
99 private boolean mNoCommit;
102 * The SharedPreferences name that will be used for all {@link Preference}s
103 * managed by this instance.
105 private String mSharedPreferencesName;
108 * The SharedPreferences mode that will be used for all {@link Preference}s
109 * managed by this instance.
111 private int mSharedPreferencesMode;
114 * The {@link PreferenceScreen} at the root of the preference hierarchy.
116 private PreferenceScreen mPreferenceScreen;
119 * List of activity result listeners.
121 private List<OnActivityResultListener> mActivityResultListeners;
124 * List of activity stop listeners.
126 private List<OnActivityStopListener> mActivityStopListeners;
129 * List of activity destroy listeners.
131 private List<OnActivityDestroyListener> mActivityDestroyListeners;
134 * List of dialogs that should be dismissed when we receive onNewIntent in
135 * our PreferenceActivity.
137 private List<DialogInterface> mPreferencesScreens;
139 private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener;
144 public PreferenceManager(Activity activity, int firstRequestCode) {
145 mActivity = activity;
146 mNextRequestCode = firstRequestCode;
152 * This constructor should ONLY be used when getting default values from
153 * an XML preference hierarchy.
155 * The {@link PreferenceManager#PreferenceManager(Activity)}
156 * should be used ANY time a preference will be displayed, since some preference
157 * types need an Activity for managed queries.
159 private PreferenceManager(Context context) {
163 private void init(Context context) {
166 setSharedPreferencesName(getDefaultSharedPreferencesName(context));
170 * Sets the owning preference fragment
172 void setFragment(PreferenceFragment fragment) {
173 mFragment = fragment;
177 * Returns the owning preference fragment, if any.
179 PreferenceFragment getFragment() {
184 * Returns a list of {@link Activity} (indirectly) that match a given
187 * @param queryIntent The Intent to match.
188 * @return The list of {@link ResolveInfo} that point to the matched
191 private List<ResolveInfo> queryIntentActivities(Intent queryIntent) {
192 return mContext.getPackageManager().queryIntentActivities(queryIntent,
193 PackageManager.GET_META_DATA);
197 * Inflates a preference hierarchy from the preference hierarchies of
198 * {@link Activity Activities} that match the given {@link Intent}. An
199 * {@link Activity} defines its preference hierarchy with meta-data using
200 * the {@link #METADATA_KEY_PREFERENCES} key.
202 * If a preference hierarchy is given, the new preference hierarchies will
205 * @param queryIntent The intent to match activities.
206 * @param rootPreferences Optional existing hierarchy to merge the new
208 * @return The root hierarchy (if one was not provided, the new hierarchy's
211 PreferenceScreen inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences) {
212 final List<ResolveInfo> activities = queryIntentActivities(queryIntent);
213 final HashSet<String> inflatedRes = new HashSet<String>();
215 for (int i = activities.size() - 1; i >= 0; i--) {
216 final ActivityInfo activityInfo = activities.get(i).activityInfo;
217 final Bundle metaData = activityInfo.metaData;
219 if ((metaData == null) || !metaData.containsKey(METADATA_KEY_PREFERENCES)) {
223 // Need to concat the package with res ID since the same res ID
224 // can be re-used across contexts
225 final String uniqueResId = activityInfo.packageName + ":"
226 + activityInfo.metaData.getInt(METADATA_KEY_PREFERENCES);
228 if (!inflatedRes.contains(uniqueResId)) {
229 inflatedRes.add(uniqueResId);
231 final Context context;
233 context = mContext.createPackageContext(activityInfo.packageName, 0);
234 } catch (NameNotFoundException e) {
235 Log.w(TAG, "Could not create context for " + activityInfo.packageName + ": "
236 + Log.getStackTraceString(e));
240 final PreferenceInflater inflater = new PreferenceInflater(context, this);
241 final XmlResourceParser parser = activityInfo.loadXmlMetaData(context
242 .getPackageManager(), METADATA_KEY_PREFERENCES);
243 rootPreferences = (PreferenceScreen) inflater
244 .inflate(parser, rootPreferences, true);
249 rootPreferences.onAttachedToHierarchy(this);
251 return rootPreferences;
255 * Inflates a preference hierarchy from XML. If a preference hierarchy is
256 * given, the new preference hierarchies will be merged in.
258 * @param context The context of the resource.
259 * @param resId The resource ID of the XML to inflate.
260 * @param rootPreferences Optional existing hierarchy to merge the new
262 * @return The root hierarchy (if one was not provided, the new hierarchy's
266 public PreferenceScreen inflateFromResource(Context context, int resId,
267 PreferenceScreen rootPreferences) {
271 final PreferenceInflater inflater = new PreferenceInflater(context, this);
272 rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences, true);
273 rootPreferences.onAttachedToHierarchy(this);
278 return rootPreferences;
281 public PreferenceScreen createPreferenceScreen(Context context) {
282 final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null);
283 preferenceScreen.onAttachedToHierarchy(this);
284 return preferenceScreen;
288 * Called by a preference to get a unique ID in its hierarchy.
290 * @return A unique ID.
293 synchronized (this) {
299 * Returns the current name of the SharedPreferences file that preferences managed by
302 * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}.
303 * @see Context#getSharedPreferences(String, int)
305 public String getSharedPreferencesName() {
306 return mSharedPreferencesName;
310 * Sets the name of the SharedPreferences file that preferences managed by this
313 * @param sharedPreferencesName The name of the SharedPreferences file.
314 * @see Context#getSharedPreferences(String, int)
316 public void setSharedPreferencesName(String sharedPreferencesName) {
317 mSharedPreferencesName = sharedPreferencesName;
318 mSharedPreferences = null;
322 * Returns the current mode of the SharedPreferences file that preferences managed by
325 * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}.
326 * @see Context#getSharedPreferences(String, int)
328 public int getSharedPreferencesMode() {
329 return mSharedPreferencesMode;
333 * Sets the mode of the SharedPreferences file that preferences managed by this
336 * @param sharedPreferencesMode The mode of the SharedPreferences file.
337 * @see Context#getSharedPreferences(String, int)
339 public void setSharedPreferencesMode(int sharedPreferencesMode) {
340 mSharedPreferencesMode = sharedPreferencesMode;
341 mSharedPreferences = null;
345 * Gets a SharedPreferences instance that preferences managed by this will
348 * @return A SharedPreferences instance pointing to the file that contains
349 * the values of preferences that are managed by this.
351 public SharedPreferences getSharedPreferences() {
352 if (mSharedPreferences == null) {
353 mSharedPreferences = mContext.getSharedPreferences(mSharedPreferencesName,
354 mSharedPreferencesMode);
357 return mSharedPreferences;
361 * Gets a SharedPreferences instance that points to the default file that is
362 * used by the preference framework in the given context.
364 * @param context The context of the preferences whose values are wanted.
365 * @return A SharedPreferences instance that can be used to retrieve and
366 * listen to values of the preferences.
368 public static SharedPreferences getDefaultSharedPreferences(Context context) {
369 return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
370 getDefaultSharedPreferencesMode());
373 private static String getDefaultSharedPreferencesName(Context context) {
374 return context.getPackageName() + "_preferences";
377 private static int getDefaultSharedPreferencesMode() {
378 return Context.MODE_PRIVATE;
382 * Returns the root of the preference hierarchy managed by this class.
384 * @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
386 PreferenceScreen getPreferenceScreen() {
387 return mPreferenceScreen;
391 * Sets the root of the preference hierarchy.
393 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
394 * @return Whether the {@link PreferenceScreen} given is different than the previous.
396 boolean setPreferences(PreferenceScreen preferenceScreen) {
397 if (preferenceScreen != mPreferenceScreen) {
398 mPreferenceScreen = preferenceScreen;
406 * Finds a {@link Preference} based on its key.
408 * @param key The key of the preference to retrieve.
409 * @return The {@link Preference} with the key, or null.
410 * @see PreferenceGroup#findPreference(CharSequence)
412 public Preference findPreference(CharSequence key) {
413 if (mPreferenceScreen == null) {
417 return mPreferenceScreen.findPreference(key);
421 * Sets the default values from an XML preference file by reading the values defined
422 * by each {@link Preference} item's {@code android:defaultValue} attribute. This should
423 * be called by the application's main activity.
426 * @param context The context of the shared preferences.
427 * @param resId The resource ID of the preference XML file.
428 * @param readAgain Whether to re-read the default values.
429 * If false, this method sets the default values only if this
430 * method has never been called in the past (or if the
431 * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
432 * preferences file is false). To attempt to set the default values again
433 * bypassing this check, set {@code readAgain} to true.
435 * Note: this will NOT reset preferences back to their default
436 * values. For that functionality, use
437 * {@link PreferenceManager#getDefaultSharedPreferences(Context)}
438 * and clear it followed by a call to this method with this
439 * parameter set to true.
441 public static void setDefaultValues(Context context, int resId, boolean readAgain) {
443 // Use the default shared preferences name and mode
444 setDefaultValues(context, getDefaultSharedPreferencesName(context),
445 getDefaultSharedPreferencesMode(), resId, readAgain);
449 * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows
450 * the client to provide the filename and mode of the shared preferences
453 * @param context The context of the shared preferences.
454 * @param sharedPreferencesName A custom name for the shared preferences file.
455 * @param sharedPreferencesMode The file creation mode for the shared preferences file, such
456 * as {@link android.content.Context#MODE_PRIVATE} or {@link
457 * android.content.Context#MODE_PRIVATE}
458 * @param resId The resource ID of the preference XML file.
459 * @param readAgain Whether to re-read the default values.
460 * If false, this method will set the default values only if this
461 * method has never been called in the past (or if the
462 * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
463 * preferences file is false). To attempt to set the default values again
464 * bypassing this check, set {@code readAgain} to true.
466 * Note: this will NOT reset preferences back to their default
467 * values. For that functionality, use
468 * {@link PreferenceManager#getDefaultSharedPreferences(Context)}
469 * and clear it followed by a call to this method with this
470 * parameter set to true.
472 * @see #setDefaultValues(Context, int, boolean)
473 * @see #setSharedPreferencesName(String)
474 * @see #setSharedPreferencesMode(int)
476 public static void setDefaultValues(Context context, String sharedPreferencesName,
477 int sharedPreferencesMode, int resId, boolean readAgain) {
478 final SharedPreferences defaultValueSp = context.getSharedPreferences(
479 KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE);
481 if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) {
482 final PreferenceManager pm = new PreferenceManager(context);
483 pm.setSharedPreferencesName(sharedPreferencesName);
484 pm.setSharedPreferencesMode(sharedPreferencesMode);
485 pm.inflateFromResource(context, resId, null);
487 SharedPreferences.Editor editor =
488 defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true);
491 } catch (AbstractMethodError unused) {
492 // The app injected its own pre-Gingerbread
493 // SharedPreferences.Editor implementation without
501 * Returns an editor to use when modifying the shared preferences.
503 * Do NOT commit unless {@link #shouldCommit()} returns true.
505 * @return An editor to use to write to shared preferences.
506 * @see #shouldCommit()
508 SharedPreferences.Editor getEditor() {
511 if (mEditor == null) {
512 mEditor = getSharedPreferences().edit();
517 return getSharedPreferences().edit();
522 * Whether it is the client's responsibility to commit on the
523 * {@link #getEditor()}. This will return false in cases where the writes
524 * should be batched, for example when inflating preferences from XML.
526 * @return Whether the client should commit.
528 boolean shouldCommit() {
532 private void setNoCommit(boolean noCommit) {
533 if (!noCommit && mEditor != null) {
536 } catch (AbstractMethodError unused) {
537 // The app injected its own pre-Gingerbread
538 // SharedPreferences.Editor implementation without
543 mNoCommit = noCommit;
547 * Returns the activity that shows the preferences. This is useful for doing
548 * managed queries, but in most cases the use of {@link #getContext()} is
551 * This will return null if this class was instantiated with a Context
552 * instead of Activity. For example, when setting the default values.
554 * @return The activity that shows the preferences.
557 Activity getActivity() {
562 * Returns the context. This is preferred over {@link #getActivity()} when
565 * @return The context.
567 Context getContext() {
572 * Registers a listener.
574 * @see OnActivityResultListener
576 void registerOnActivityResultListener(OnActivityResultListener listener) {
577 synchronized (this) {
578 if (mActivityResultListeners == null) {
579 mActivityResultListeners = new ArrayList<OnActivityResultListener>();
582 if (!mActivityResultListeners.contains(listener)) {
583 mActivityResultListeners.add(listener);
589 * Unregisters a listener.
591 * @see OnActivityResultListener
593 void unregisterOnActivityResultListener(OnActivityResultListener listener) {
594 synchronized (this) {
595 if (mActivityResultListeners != null) {
596 mActivityResultListeners.remove(listener);
602 * Called by the {@link PreferenceManager} to dispatch a subactivity result.
604 void dispatchActivityResult(int requestCode, int resultCode, Intent data) {
605 List<OnActivityResultListener> list;
607 synchronized (this) {
608 if (mActivityResultListeners == null) return;
609 list = new ArrayList<OnActivityResultListener>(mActivityResultListeners);
612 final int N = list.size();
613 for (int i = 0; i < N; i++) {
614 if (list.get(i).onActivityResult(requestCode, resultCode, data)) {
621 * Registers a listener.
623 * @see OnActivityStopListener
625 void registerOnActivityStopListener(OnActivityStopListener listener) {
626 synchronized (this) {
627 if (mActivityStopListeners == null) {
628 mActivityStopListeners = new ArrayList<OnActivityStopListener>();
631 if (!mActivityStopListeners.contains(listener)) {
632 mActivityStopListeners.add(listener);
638 * Unregisters a listener.
640 * @see OnActivityStopListener
642 void unregisterOnActivityStopListener(OnActivityStopListener listener) {
643 synchronized (this) {
644 if (mActivityStopListeners != null) {
645 mActivityStopListeners.remove(listener);
651 * Called by the {@link PreferenceManager} to dispatch the activity stop
654 void dispatchActivityStop() {
655 List<OnActivityStopListener> list;
657 synchronized (this) {
658 if (mActivityStopListeners == null) return;
659 list = new ArrayList<OnActivityStopListener>(mActivityStopListeners);
662 final int N = list.size();
663 for (int i = 0; i < N; i++) {
664 list.get(i).onActivityStop();
669 * Registers a listener.
671 * @see OnActivityDestroyListener
673 void registerOnActivityDestroyListener(OnActivityDestroyListener listener) {
674 synchronized (this) {
675 if (mActivityDestroyListeners == null) {
676 mActivityDestroyListeners = new ArrayList<OnActivityDestroyListener>();
679 if (!mActivityDestroyListeners.contains(listener)) {
680 mActivityDestroyListeners.add(listener);
686 * Unregisters a listener.
688 * @see OnActivityDestroyListener
690 void unregisterOnActivityDestroyListener(OnActivityDestroyListener listener) {
691 synchronized (this) {
692 if (mActivityDestroyListeners != null) {
693 mActivityDestroyListeners.remove(listener);
699 * Called by the {@link PreferenceManager} to dispatch the activity destroy
702 void dispatchActivityDestroy() {
703 List<OnActivityDestroyListener> list = null;
705 synchronized (this) {
706 if (mActivityDestroyListeners != null) {
707 list = new ArrayList<OnActivityDestroyListener>(mActivityDestroyListeners);
712 final int N = list.size();
713 for (int i = 0; i < N; i++) {
714 list.get(i).onActivityDestroy();
718 // Dismiss any PreferenceScreens still showing
723 * Returns a request code that is unique for the activity. Each subsequent
724 * call to this method should return another unique request code.
726 * @return A unique request code that will never be used by anyone other
727 * than the caller of this method.
729 int getNextRequestCode() {
730 synchronized (this) {
731 return mNextRequestCode++;
735 void addPreferencesScreen(DialogInterface screen) {
736 synchronized (this) {
738 if (mPreferencesScreens == null) {
739 mPreferencesScreens = new ArrayList<DialogInterface>();
742 mPreferencesScreens.add(screen);
746 void removePreferencesScreen(DialogInterface screen) {
747 synchronized (this) {
749 if (mPreferencesScreens == null) {
753 mPreferencesScreens.remove(screen);
758 * Called by {@link PreferenceActivity} to dispatch the new Intent event.
760 * @param intent The new Intent.
762 void dispatchNewIntent(Intent intent) {
766 private void dismissAllScreens() {
767 // Remove any of the previously shown preferences screens
768 ArrayList<DialogInterface> screensToDismiss;
770 synchronized (this) {
772 if (mPreferencesScreens == null) {
776 screensToDismiss = new ArrayList<DialogInterface>(mPreferencesScreens);
777 mPreferencesScreens.clear();
780 for (int i = screensToDismiss.size() - 1; i >= 0; i--) {
781 screensToDismiss.get(i).dismiss();
786 * Sets the callback to be invoked when a {@link Preference} in the
787 * hierarchy rooted at this {@link PreferenceManager} is clicked.
789 * @param listener The callback to be invoked.
791 void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) {
792 mOnPreferenceTreeClickListener = listener;
795 OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() {
796 return mOnPreferenceTreeClickListener;
800 * Interface definition for a callback to be invoked when a
801 * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is
804 interface OnPreferenceTreeClickListener {
806 * Called when a preference in the tree rooted at this
807 * {@link PreferenceScreen} has been clicked.
809 * @param preferenceScreen The {@link PreferenceScreen} that the
810 * preference is located in.
811 * @param preference The preference that was clicked.
812 * @return Whether the click was handled.
814 boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference);
818 * Interface definition for a class that will be called when the container's activity
819 * receives an activity result.
821 public interface OnActivityResultListener {
824 * See Activity's onActivityResult.
826 * @return Whether the request code was handled (in which case
827 * subsequent listeners will not be called.
829 boolean onActivityResult(int requestCode, int resultCode, Intent data);
833 * Interface definition for a class that will be called when the container's activity
836 public interface OnActivityStopListener {
839 * See Activity's onStop.
841 void onActivityStop();
845 * Interface definition for a class that will be called when the container's activity
848 public interface OnActivityDestroyListener {
851 * See Activity's onDestroy.
853 void onActivityDestroy();