PreferenceFragment.java revision 405c1af75607fafdb1d6faf34e13e032e4934787
1/*
2 * Copyright (C) 2010 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.app.Activity;
20import android.app.Fragment;
21import android.content.Intent;
22import android.content.SharedPreferences;
23import android.content.res.Configuration;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.Message;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.view.ViewGroup;
30import android.widget.ListView;
31
32/**
33 * Shows a hierarchy of {@link Preference} objects as
34 * lists. These preferences will
35 * automatically save to {@link SharedPreferences} as the user interacts with
36 * them. To retrieve an instance of {@link SharedPreferences} that the
37 * preference hierarchy in this fragment will use, call
38 * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
39 * with a context in the same package as this fragment.
40 * <p>
41 * Furthermore, the preferences shown will follow the visual style of system
42 * preferences. It is easy to create a hierarchy of preferences (that can be
43 * shown on multiple screens) via XML. For these reasons, it is recommended to
44 * use this fragment (as a superclass) to deal with preferences in applications.
45 * <p>
46 * A {@link PreferenceScreen} object should be at the top of the preference
47 * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
48 * denote a screen break--that is the preferences contained within subsequent
49 * {@link PreferenceScreen} should be shown on another screen. The preference
50 * framework handles showing these other screens from the preference hierarchy.
51 * <p>
52 * The preference hierarchy can be formed in multiple ways:
53 * <li> From an XML file specifying the hierarchy
54 * <li> From different {@link Activity Activities} that each specify its own
55 * preferences in an XML file via {@link Activity} meta-data
56 * <li> From an object hierarchy rooted with {@link PreferenceScreen}
57 * <p>
58 * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
59 * root element should be a {@link PreferenceScreen}. Subsequent elements can point
60 * to actual {@link Preference} subclasses. As mentioned above, subsequent
61 * {@link PreferenceScreen} in the hierarchy will result in the screen break.
62 * <p>
63 * To specify an {@link Intent} to query {@link Activity Activities} that each
64 * have preferences, use {@link #addPreferencesFromIntent}. Each
65 * {@link Activity} can specify meta-data in the manifest (via the key
66 * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML
67 * resource. These XML resources will be inflated into a single preference
68 * hierarchy and shown by this fragment.
69 * <p>
70 * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
71 * {@link #setPreferenceScreen(PreferenceScreen)}.
72 * <p>
73 * As a convenience, this fragment implements a click listener for any
74 * preference in the current hierarchy, see
75 * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
76 *
77 * <a name="SampleCode"></a>
78 * <h3>Sample Code</h3>
79 *
80 * <p>The following sample code shows a simple preference fragment that is
81 * populated from a resource.  The resource it loads is:</p>
82 *
83 * {@sample development/samples/ApiDemos/res/xml/preferences.xml preferences}
84 *
85 * <p>The fragment implementation itself simply populates the preferences
86 * when created.  Note that the preferences framework takes care of loading
87 * the current values out of the app preferences and writing them when changed:</p>
88 *
89 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/FragmentPreferences.java
90 *      fragment}
91 *
92 * @see Preference
93 * @see PreferenceScreen
94 */
95public abstract class PreferenceFragment extends Fragment implements
96        PreferenceManager.OnPreferenceTreeClickListener {
97
98    private static final String PREFERENCES_TAG = "android:preferences";
99
100    private PreferenceManager mPreferenceManager;
101    private ListView mList;
102    private boolean mHavePrefs;
103    private boolean mInitDone;
104
105    /**
106     * The starting request code given out to preference framework.
107     */
108    private static final int FIRST_REQUEST_CODE = 100;
109
110    private static final int MSG_BIND_PREFERENCES = 1;
111    private Handler mHandler = new Handler() {
112        @Override
113        public void handleMessage(Message msg) {
114            switch (msg.what) {
115
116                case MSG_BIND_PREFERENCES:
117                    bindPreferences();
118                    break;
119            }
120        }
121    };
122
123    final private Runnable mRequestFocus = new Runnable() {
124        public void run() {
125            mList.focusableViewAvailable(mList);
126        }
127    };
128
129    /**
130     * Interface that PreferenceFragment's containing activity should
131     * implement to be able to process preference items that wish to
132     * switch to a new fragment.
133     */
134    public interface OnPreferenceStartFragmentCallback {
135        /**
136         * Called when the user has clicked on a Preference that has
137         * a fragment class name associated with it.  The implementation
138         * to should instantiate and switch to an instance of the given
139         * fragment.
140         */
141        boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
142    }
143
144    @Override
145    public void onCreate(Bundle savedInstanceState) {
146        super.onCreate(savedInstanceState);
147        mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
148        mPreferenceManager.setFragment(this);
149        mPreferenceManager.setOnPreferenceTreeClickListener(this);
150    }
151
152    @Override
153    public View onCreateView(LayoutInflater inflater, ViewGroup container,
154            Bundle savedInstanceState) {
155        return inflater.inflate(com.android.internal.R.layout.preference_list_fragment, container,
156                false);
157    }
158
159    @Override
160    public void onActivityCreated(Bundle savedInstanceState) {
161        super.onActivityCreated(savedInstanceState);
162        getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
163
164        if (mHavePrefs) {
165            bindPreferences();
166        }
167
168        mInitDone = true;
169
170        if (savedInstanceState != null) {
171            Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
172            if (container != null) {
173                final PreferenceScreen preferenceScreen = getPreferenceScreen();
174                if (preferenceScreen != null) {
175                    preferenceScreen.restoreHierarchyState(container);
176                }
177            }
178        }
179    }
180
181    @Override
182    public void onStop() {
183        super.onStop();
184        mPreferenceManager.dispatchActivityStop();
185    }
186
187    @Override
188    public void onDestroyView() {
189        mList = null;
190        mHandler.removeCallbacks(mRequestFocus);
191        mHandler.removeMessages(MSG_BIND_PREFERENCES);
192        super.onDestroyView();
193    }
194
195    @Override
196    public void onDestroy() {
197        super.onDestroy();
198        mPreferenceManager.dispatchActivityDestroy();
199        mPreferenceManager.setOnPreferenceTreeClickListener(null);
200    }
201
202    @Override
203    public void onSaveInstanceState(Bundle outState) {
204        super.onSaveInstanceState(outState);
205
206        final PreferenceScreen preferenceScreen = getPreferenceScreen();
207        if (preferenceScreen != null) {
208            Bundle container = new Bundle();
209            preferenceScreen.saveHierarchyState(container);
210            outState.putBundle(PREFERENCES_TAG, container);
211        }
212    }
213
214    @Override
215    public void onActivityResult(int requestCode, int resultCode, Intent data) {
216        super.onActivityResult(requestCode, resultCode, data);
217
218        mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
219    }
220
221    /**
222     * Returns the {@link PreferenceManager} used by this fragment.
223     * @return The {@link PreferenceManager}.
224     */
225    public PreferenceManager getPreferenceManager() {
226        return mPreferenceManager;
227    }
228
229    /**
230     * Sets the root of the preference hierarchy that this fragment is showing.
231     *
232     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
233     */
234    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
235        if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
236            mHavePrefs = true;
237            if (mInitDone) {
238                postBindPreferences();
239            }
240        }
241    }
242
243    /**
244     * Gets the root of the preference hierarchy that this fragment is showing.
245     *
246     * @return The {@link PreferenceScreen} that is the root of the preference
247     *         hierarchy.
248     */
249    public PreferenceScreen getPreferenceScreen() {
250        return mPreferenceManager.getPreferenceScreen();
251    }
252
253    /**
254     * Adds preferences from activities that match the given {@link Intent}.
255     *
256     * @param intent The {@link Intent} to query activities.
257     */
258    public void addPreferencesFromIntent(Intent intent) {
259        requirePreferenceManager();
260
261        setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
262    }
263
264    /**
265     * Inflates the given XML resource and adds the preference hierarchy to the current
266     * preference hierarchy.
267     *
268     * @param preferencesResId The XML resource ID to inflate.
269     */
270    public void addPreferencesFromResource(int preferencesResId) {
271        requirePreferenceManager();
272
273        setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
274                preferencesResId, getPreferenceScreen()));
275    }
276
277    /**
278     * {@inheritDoc}
279     */
280    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
281            Preference preference) {
282        if (preference.getFragment() != null &&
283                getActivity() instanceof OnPreferenceStartFragmentCallback) {
284            return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
285                    this, preference);
286        }
287        return false;
288    }
289
290    /**
291     * Finds a {@link Preference} based on its key.
292     *
293     * @param key The key of the preference to retrieve.
294     * @return The {@link Preference} with the key, or null.
295     * @see PreferenceGroup#findPreference(CharSequence)
296     */
297    public Preference findPreference(CharSequence key) {
298        if (mPreferenceManager == null) {
299            return null;
300        }
301        return mPreferenceManager.findPreference(key);
302    }
303
304    private void requirePreferenceManager() {
305        if (mPreferenceManager == null) {
306            throw new RuntimeException("This should be called after super.onCreate.");
307        }
308    }
309
310    private void postBindPreferences() {
311        if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
312        mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
313    }
314
315    private void bindPreferences() {
316        final PreferenceScreen preferenceScreen = getPreferenceScreen();
317        if (preferenceScreen != null) {
318            preferenceScreen.bind(getListView());
319        }
320    }
321
322    /** @hide */
323    public ListView getListView() {
324        ensureList();
325        return mList;
326    }
327
328    private void ensureList() {
329        if (mList != null) {
330            return;
331        }
332        View root = getView();
333        if (root == null) {
334            throw new IllegalStateException("Content view not yet created");
335        }
336        View rawListView = root.findViewById(android.R.id.list);
337        if (!(rawListView instanceof ListView)) {
338            throw new RuntimeException(
339                    "Content has view with id attribute 'android.R.id.list' "
340                    + "that is not a ListView class");
341        }
342        mList = (ListView)rawListView;
343        if (mList == null) {
344            throw new RuntimeException(
345                    "Your content must have a ListView whose id attribute is " +
346                    "'android.R.id.list'");
347        }
348        mHandler.post(mRequestFocus);
349    }
350}
351