PreferenceManager.java revision f9799b58e543fbddf7a88262a73147b9b959d916
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package android.support.v7.preference;
18
19import android.content.Context;
20import android.content.SharedPreferences;
21import android.support.v4.content.SharedPreferencesCompat;
22
23/**
24 * Used to help create {@link Preference} hierarchies
25 * from activities or XML.
26 * <p>
27 * In most cases, clients should use
28 * {@link PreferenceFragment#addPreferencesFromResource(int)}, or
29 * {@link PreferenceFragmentCompat#addPreferencesFromResource(int)}.
30 *
31 * @see PreferenceFragment
32 * @see PreferenceFragmentCompat
33 */
34public class PreferenceManager {
35
36    private static final String TAG = "PreferenceManager";
37
38    public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
39
40    /**
41     * The context to use. This should always be set.
42     */
43    private Context mContext;
44
45    /**
46     * The counter for unique IDs.
47     */
48    private long mNextId = 0;
49
50    /**
51     * Cached shared preferences.
52     */
53    private SharedPreferences mSharedPreferences;
54
55    /**
56     * If in no-commit mode, the shared editor to give out (which will be
57     * committed when exiting no-commit mode).
58     */
59    private SharedPreferences.Editor mEditor;
60
61    /**
62     * Blocks commits from happening on the shared editor. This is used when
63     * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)}
64     */
65    private boolean mNoCommit;
66
67    /**
68     * The SharedPreferences name that will be used for all {@link Preference}s
69     * managed by this instance.
70     */
71    private String mSharedPreferencesName;
72
73    /**
74     * The SharedPreferences mode that will be used for all {@link Preference}s
75     * managed by this instance.
76     */
77    private int mSharedPreferencesMode;
78
79    /**
80     * The {@link PreferenceScreen} at the root of the preference hierarchy.
81     */
82    private PreferenceScreen mPreferenceScreen;
83
84    private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener;
85    private OnDisplayPreferenceDialogListener mOnDisplayPreferenceDialogListener;
86    private OnNavigateToScreenListener mOnNavigateToScreenListener;
87
88    /**
89     * @hide
90     */
91    public PreferenceManager(Context context) {
92        mContext = context;
93
94        setSharedPreferencesName(getDefaultSharedPreferencesName(context));
95    }
96
97    /**
98     * Inflates a preference hierarchy from XML. If a preference hierarchy is
99     * given, the new preference hierarchies will be merged in.
100     *
101     * @param context The context of the resource.
102     * @param resId The resource ID of the XML to inflate.
103     * @param rootPreferences Optional existing hierarchy to merge the new
104     *            hierarchies into.
105     * @return The root hierarchy (if one was not provided, the new hierarchy's
106     *         root).
107     * @hide
108     */
109    public PreferenceScreen inflateFromResource(Context context, int resId,
110            PreferenceScreen rootPreferences) {
111        // Block commits
112        setNoCommit(true);
113
114        final PreferenceInflater inflater = new PreferenceInflater(context, this);
115        rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences);
116        rootPreferences.onAttachedToHierarchy(this);
117
118        // Unblock commits
119        setNoCommit(false);
120
121        return rootPreferences;
122    }
123
124    public PreferenceScreen createPreferenceScreen(Context context) {
125        final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null);
126        preferenceScreen.onAttachedToHierarchy(this);
127        return preferenceScreen;
128    }
129
130    /**
131     * Called by a preference to get a unique ID in its hierarchy.
132     *
133     * @return A unique ID.
134     */
135    long getNextId() {
136        synchronized (this) {
137            return mNextId++;
138        }
139    }
140
141    /**
142     * Returns the current name of the SharedPreferences file that preferences managed by
143     * this will use.
144     *
145     * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}.
146     * @see Context#getSharedPreferences(String, int)
147     */
148    public String getSharedPreferencesName() {
149        return mSharedPreferencesName;
150    }
151
152    /**
153     * Sets the name of the SharedPreferences file that preferences managed by this
154     * will use.
155     *
156     * @param sharedPreferencesName The name of the SharedPreferences file.
157     * @see Context#getSharedPreferences(String, int)
158     */
159    public void setSharedPreferencesName(String sharedPreferencesName) {
160        mSharedPreferencesName = sharedPreferencesName;
161        mSharedPreferences = null;
162    }
163
164    /**
165     * Returns the current mode of the SharedPreferences file that preferences managed by
166     * this will use.
167     *
168     * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}.
169     * @see Context#getSharedPreferences(String, int)
170     */
171    public int getSharedPreferencesMode() {
172        return mSharedPreferencesMode;
173    }
174
175    /**
176     * Sets the mode of the SharedPreferences file that preferences managed by this
177     * will use.
178     *
179     * @param sharedPreferencesMode The mode of the SharedPreferences file.
180     * @see Context#getSharedPreferences(String, int)
181     */
182    public void setSharedPreferencesMode(int sharedPreferencesMode) {
183        mSharedPreferencesMode = sharedPreferencesMode;
184        mSharedPreferences = null;
185    }
186
187    /**
188     * Gets a SharedPreferences instance that preferences managed by this will
189     * use.
190     *
191     * @return A SharedPreferences instance pointing to the file that contains
192     *         the values of preferences that are managed by this.
193     */
194    public SharedPreferences getSharedPreferences() {
195        if (mSharedPreferences == null) {
196            mSharedPreferences = mContext.getSharedPreferences(mSharedPreferencesName,
197                    mSharedPreferencesMode);
198        }
199
200        return mSharedPreferences;
201    }
202
203    /**
204     * Gets a SharedPreferences instance that points to the default file that is
205     * used by the preference framework in the given context.
206     *
207     * @param context The context of the preferences whose values are wanted.
208     * @return A SharedPreferences instance that can be used to retrieve and
209     *         listen to values of the preferences.
210     */
211    public static SharedPreferences getDefaultSharedPreferences(Context context) {
212        return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
213                getDefaultSharedPreferencesMode());
214    }
215
216    private static String getDefaultSharedPreferencesName(Context context) {
217        return context.getPackageName() + "_preferences";
218    }
219
220    private static int getDefaultSharedPreferencesMode() {
221        return Context.MODE_PRIVATE;
222    }
223
224    /**
225     * Returns the root of the preference hierarchy managed by this class.
226     *
227     * @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
228     */
229    public PreferenceScreen getPreferenceScreen() {
230        return mPreferenceScreen;
231    }
232
233    /**
234     * Sets the root of the preference hierarchy.
235     *
236     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
237     * @return Whether the {@link PreferenceScreen} given is different than the previous.
238     */
239    public boolean setPreferences(PreferenceScreen preferenceScreen) {
240        if (preferenceScreen != mPreferenceScreen) {
241            mPreferenceScreen = preferenceScreen;
242            return true;
243        }
244
245        return false;
246    }
247
248    /**
249     * Finds a {@link Preference} based on its key.
250     *
251     * @param key The key of the preference to retrieve.
252     * @return The {@link Preference} with the key, or null.
253     * @see PreferenceGroup#findPreference(CharSequence)
254     */
255    public Preference findPreference(CharSequence key) {
256        if (mPreferenceScreen == null) {
257            return null;
258        }
259
260        return mPreferenceScreen.findPreference(key);
261    }
262
263    /**
264     * Sets the default values from an XML preference file by reading the values defined
265     * by each {@link Preference} item's {@code android:defaultValue} attribute. This should
266     * be called by the application's main activity.
267     * <p>
268     *
269     * @param context The context of the shared preferences.
270     * @param resId The resource ID of the preference XML file.
271     * @param readAgain Whether to re-read the default values.
272     * If false, this method sets the default values only if this
273     * method has never been called in the past (or if the
274     * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
275     * preferences file is false). To attempt to set the default values again
276     * bypassing this check, set {@code readAgain} to true.
277     *            <p class="note">
278     *            Note: this will NOT reset preferences back to their default
279     *            values. For that functionality, use
280     *            {@link PreferenceManager#getDefaultSharedPreferences(Context)}
281     *            and clear it followed by a call to this method with this
282     *            parameter set to true.
283     */
284    public static void setDefaultValues(Context context, int resId, boolean readAgain) {
285
286        // Use the default shared preferences name and mode
287        setDefaultValues(context, getDefaultSharedPreferencesName(context),
288                getDefaultSharedPreferencesMode(), resId, readAgain);
289    }
290
291    /**
292     * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows
293     * the client to provide the filename and mode of the shared preferences
294     * file.
295     *
296     * @param context The context of the shared preferences.
297     * @param sharedPreferencesName A custom name for the shared preferences file.
298     * @param sharedPreferencesMode The file creation mode for the shared preferences file, such
299     * as {@link android.content.Context#MODE_PRIVATE} or {@link
300     * android.content.Context#MODE_PRIVATE}
301     * @param resId The resource ID of the preference XML file.
302     * @param readAgain Whether to re-read the default values.
303     * If false, this method will set the default values only if this
304     * method has never been called in the past (or if the
305     * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
306     * preferences file is false). To attempt to set the default values again
307     * bypassing this check, set {@code readAgain} to true.
308     *            <p class="note">
309     *            Note: this will NOT reset preferences back to their default
310     *            values. For that functionality, use
311     *            {@link PreferenceManager#getDefaultSharedPreferences(Context)}
312     *            and clear it followed by a call to this method with this
313     *            parameter set to true.
314     *
315     * @see #setDefaultValues(Context, int, boolean)
316     * @see #setSharedPreferencesName(String)
317     * @see #setSharedPreferencesMode(int)
318     */
319    public static void setDefaultValues(Context context, String sharedPreferencesName,
320            int sharedPreferencesMode, int resId, boolean readAgain) {
321        final SharedPreferences defaultValueSp = context.getSharedPreferences(
322                KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE);
323
324        if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) {
325            final PreferenceManager pm = new PreferenceManager(context);
326            pm.setSharedPreferencesName(sharedPreferencesName);
327            pm.setSharedPreferencesMode(sharedPreferencesMode);
328            pm.inflateFromResource(context, resId, null);
329
330            SharedPreferences.Editor editor =
331                    defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true);
332
333            SharedPreferencesCompat.EditorCompat.getInstance().apply(editor);
334        }
335    }
336
337    /**
338     * Returns an editor to use when modifying the shared preferences.
339     * <p>
340     * Do NOT commit unless {@link #shouldCommit()} returns true.
341     *
342     * @return An editor to use to write to shared preferences.
343     * @see #shouldCommit()
344     */
345    SharedPreferences.Editor getEditor() {
346
347        if (mNoCommit) {
348            if (mEditor == null) {
349                mEditor = getSharedPreferences().edit();
350            }
351
352            return mEditor;
353        } else {
354            return getSharedPreferences().edit();
355        }
356    }
357
358    /**
359     * Whether it is the client's responsibility to commit on the
360     * {@link #getEditor()}. This will return false in cases where the writes
361     * should be batched, for example when inflating preferences from XML.
362     *
363     * @return Whether the client should commit.
364     */
365    boolean shouldCommit() {
366        return !mNoCommit;
367    }
368
369    private void setNoCommit(boolean noCommit) {
370        if (!noCommit && mEditor != null) {
371            SharedPreferencesCompat.EditorCompat.getInstance().apply(mEditor);
372        }
373        mNoCommit = noCommit;
374    }
375
376    /**
377     * Returns the context.
378     *
379     * @return The context.
380     */
381    Context getContext() {
382        return mContext;
383    }
384
385    public OnDisplayPreferenceDialogListener getOnDisplayPreferenceDialogListener() {
386        return mOnDisplayPreferenceDialogListener;
387    }
388
389    public void setOnDisplayPreferenceDialogListener(
390            OnDisplayPreferenceDialogListener onDisplayPreferenceDialogListener) {
391        mOnDisplayPreferenceDialogListener = onDisplayPreferenceDialogListener;
392    }
393
394    /**
395     * Called when a preference requests that a dialog be shown to complete a user interaction.
396     *
397     * @param preference The preference requesting the dialog.
398     */
399    public void showDialog(Preference preference) {
400        if (mOnDisplayPreferenceDialogListener != null) {
401            mOnDisplayPreferenceDialogListener.onDisplayPreferenceDialog(preference);
402        }
403    }
404
405    /**
406     * Sets the callback to be invoked when a {@link Preference} in the
407     * hierarchy rooted at this {@link PreferenceManager} is clicked.
408     *
409     * @param listener The callback to be invoked.
410     */
411    public void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) {
412        mOnPreferenceTreeClickListener = listener;
413    }
414
415    public OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() {
416        return mOnPreferenceTreeClickListener;
417    }
418
419    /**
420     * Sets the callback to be invoked when a {@link PreferenceScreen} in the hierarchy rooted at
421     * this {@link PreferenceManager} is clicked.
422     *
423     * @param listener The callback to be invoked.
424     */
425    public void setOnNavigateToScreenListener(OnNavigateToScreenListener listener) {
426        mOnNavigateToScreenListener = listener;
427    }
428
429    /**
430     * Returns the {@link PreferenceManager.OnNavigateToScreenListener}, if one has been set.
431     */
432    public OnNavigateToScreenListener getOnNavigateToScreenListener() {
433        return mOnNavigateToScreenListener;
434    }
435
436    /**
437     * Interface definition for a callback to be invoked when a
438     * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is
439     * clicked.
440     */
441    public interface OnPreferenceTreeClickListener {
442        /**
443         * Called when a preference in the tree rooted at this
444         * {@link PreferenceScreen} has been clicked.
445         *
446         * @param preference The preference that was clicked.
447         * @return Whether the click was handled.
448         */
449        boolean onPreferenceTreeClick(Preference preference);
450    }
451
452    /**
453     * Interface definition for a class that will be called when a
454     * {@link android.support.v7.preference.Preference} requests to display a dialog.
455     */
456    public interface OnDisplayPreferenceDialogListener {
457
458        /**
459         * Called when a preference in the tree requests to display a dialog.
460         *
461         * @param preference The Preference object requesting the dialog.
462         */
463        void onDisplayPreferenceDialog(Preference preference);
464    }
465
466    /**
467     * Interface definition for a class that will be called when a
468     * {@link android.support.v7.preference.PreferenceScreen} requests navigation.
469     */
470    public interface OnNavigateToScreenListener {
471
472        /**
473         * Called when a PreferenceScreen in the tree requests to navigate to its contents.
474         *
475         * @param preferenceScreen The PreferenceScreen requesting navigation.
476         */
477        void onNavigateToScreen(PreferenceScreen preferenceScreen);
478    }
479
480}
481