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