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 android.annotation.Nullable;
20import android.annotation.SystemApi;
21import android.annotation.XmlRes;
22import android.app.Activity;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.Intent;
26import android.content.SharedPreferences;
27import android.content.pm.ActivityInfo;
28import android.content.pm.PackageManager;
29import android.content.pm.PackageManager.NameNotFoundException;
30import android.content.pm.ResolveInfo;
31import android.content.res.XmlResourceParser;
32import android.os.Bundle;
33import android.util.Log;
34
35import java.util.ArrayList;
36import java.util.HashSet;
37import 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 */
49public 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     */
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     */
184    /*package*/ PreferenceManager(Context context) {
185        init(context);
186    }
187
188    private void init(Context context) {
189        mContext = context;
190
191        setSharedPreferencesName(getDefaultSharedPreferencesName(context));
192    }
193
194    /**
195     * Sets the owning preference fragment
196     */
197    void setFragment(PreferenceFragment fragment) {
198        mFragment = fragment;
199    }
200
201    /**
202     * Returns the owning preference fragment, if any.
203     */
204    @Nullable
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     */
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
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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
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     */
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     */
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     */
536    public static String getDefaultSharedPreferencesName(Context context) {
537        return context.getPackageName() + "_preferences";
538    }
539
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
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     */
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
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     */
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     */
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     */
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     */
699    boolean shouldCommit() {
700        return !mNoCommit;
701    }
702
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
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     */
739    Context getContext() {
740        return mContext;
741    }
742
743    /**
744     * Registers a listener.
745     *
746     * @see OnActivityResultListener
747     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
903    int getNextRequestCode() {
904        synchronized (this) {
905            return mNextRequestCode++;
906        }
907    }
908
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
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     */
936    void dispatchNewIntent(Intent intent) {
937        dismissAllScreens();
938    }
939
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     */
965    void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) {
966        mOnPreferenceTreeClickListener = listener;
967    }
968
969    @Nullable
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         */
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         */
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         */
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         */
1030        void onActivityDestroy();
1031    }
1032
1033}
1034