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