PreferenceFragment.java revision c56fc753e2e2d35221a1a4df353a435098268ec4
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 = 1;
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    /**
129     * Interface that PreferenceFragment's containing activity should
130     * implement to be able to process preference items that wish to
131     * switch to a new fragment.
132     */
133    public interface OnPreferenceStartFragmentCallback {
134        /**
135         * Called when the user has clicked on a Preference that has
136         * a fragment class name associated with it.  The implementation
137         * to should instantiate and switch to an instance of the given
138         * fragment.
139         */
140        boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
141    }
142
143    @Override
144    public void onCreate(Bundle savedInstanceState) {
145        super.onCreate(savedInstanceState);
146        mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
147        mPreferenceManager.setFragment(this);
148    }
149
150    @Override
151    public View onCreateView(LayoutInflater inflater, ViewGroup container,
152            Bundle savedInstanceState) {
153        return inflater.inflate(com.android.internal.R.layout.preference_list_fragment,
154                container, false);
155    }
156
157    @Override
158    public void onActivityCreated(Bundle savedInstanceState) {
159        super.onActivityCreated(savedInstanceState);
160        getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
161
162        if (mHavePrefs) {
163            bindPreferences();
164        }
165
166        mInitDone = true;
167
168        if (savedInstanceState != null) {
169            Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
170            if (container != null) {
171                final PreferenceScreen preferenceScreen = getPreferenceScreen();
172                if (preferenceScreen != null) {
173                    preferenceScreen.restoreHierarchyState(container);
174                }
175            }
176        }
177    }
178
179    @Override
180    public void onStart() {
181        super.onStart();
182        mPreferenceManager.setOnPreferenceTreeClickListener(this);
183    }
184
185    @Override
186    public void onStop() {
187        super.onStop();
188        mPreferenceManager.dispatchActivityStop();
189        mPreferenceManager.setOnPreferenceTreeClickListener(null);
190    }
191
192    @Override
193    public void onDestroyView() {
194        mList = null;
195        mHandler.removeCallbacks(mRequestFocus);
196        mHandler.removeMessages(MSG_BIND_PREFERENCES);
197        super.onDestroyView();
198    }
199
200    @Override
201    public void onDestroy() {
202        super.onDestroy();
203        mPreferenceManager.dispatchActivityDestroy();
204    }
205
206    @Override
207    public void onSaveInstanceState(Bundle outState) {
208        super.onSaveInstanceState(outState);
209
210        final PreferenceScreen preferenceScreen = getPreferenceScreen();
211        if (preferenceScreen != null) {
212            Bundle container = new Bundle();
213            preferenceScreen.saveHierarchyState(container);
214            outState.putBundle(PREFERENCES_TAG, container);
215        }
216    }
217
218    @Override
219    public void onActivityResult(int requestCode, int resultCode, Intent data) {
220        super.onActivityResult(requestCode, resultCode, data);
221
222        mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
223    }
224
225    /**
226     * Returns the {@link PreferenceManager} used by this fragment.
227     * @return The {@link PreferenceManager}.
228     */
229    public PreferenceManager getPreferenceManager() {
230        return mPreferenceManager;
231    }
232
233    /**
234     * Sets the root of the preference hierarchy that this fragment is showing.
235     *
236     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
237     */
238    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
239        if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
240            mHavePrefs = true;
241            if (mInitDone) {
242                postBindPreferences();
243            }
244        }
245    }
246
247    /**
248     * Gets the root of the preference hierarchy that this fragment is showing.
249     *
250     * @return The {@link PreferenceScreen} that is the root of the preference
251     *         hierarchy.
252     */
253    public PreferenceScreen getPreferenceScreen() {
254        return mPreferenceManager.getPreferenceScreen();
255    }
256
257    /**
258     * Adds preferences from activities that match the given {@link Intent}.
259     *
260     * @param intent The {@link Intent} to query activities.
261     */
262    public void addPreferencesFromIntent(Intent intent) {
263        requirePreferenceManager();
264
265        setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
266    }
267
268    /**
269     * Inflates the given XML resource and adds the preference hierarchy to the current
270     * preference hierarchy.
271     *
272     * @param preferencesResId The XML resource ID to inflate.
273     */
274    public void addPreferencesFromResource(int preferencesResId) {
275        requirePreferenceManager();
276
277        setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
278                preferencesResId, getPreferenceScreen()));
279    }
280
281    /**
282     * {@inheritDoc}
283     */
284    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
285            Preference preference) {
286        if (preference.getFragment() != null &&
287                getActivity() instanceof OnPreferenceStartFragmentCallback) {
288            return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
289                    this, preference);
290        }
291        return false;
292    }
293
294    /**
295     * Finds a {@link Preference} based on its key.
296     *
297     * @param key The key of the preference to retrieve.
298     * @return The {@link Preference} with the key, or null.
299     * @see PreferenceGroup#findPreference(CharSequence)
300     */
301    public Preference findPreference(CharSequence key) {
302        if (mPreferenceManager == null) {
303            return null;
304        }
305        return mPreferenceManager.findPreference(key);
306    }
307
308    private void requirePreferenceManager() {
309        if (mPreferenceManager == null) {
310            throw new RuntimeException("This should be called after super.onCreate.");
311        }
312    }
313
314    private void postBindPreferences() {
315        if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
316        mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
317    }
318
319    private void bindPreferences() {
320        final PreferenceScreen preferenceScreen = getPreferenceScreen();
321        if (preferenceScreen != null) {
322            preferenceScreen.bind(getListView());
323        }
324    }
325
326    /** @hide */
327    public ListView getListView() {
328        ensureList();
329        return mList;
330    }
331
332    private void ensureList() {
333        if (mList != null) {
334            return;
335        }
336        View root = getView();
337        if (root == null) {
338            throw new IllegalStateException("Content view not yet created");
339        }
340        View rawListView = root.findViewById(android.R.id.list);
341        if (!(rawListView instanceof ListView)) {
342            throw new RuntimeException(
343                    "Content has view with id attribute 'android.R.id.list' "
344                    + "that is not a ListView class");
345        }
346        mList = (ListView)rawListView;
347        if (mList == null) {
348            throw new RuntimeException(
349                    "Your content must have a ListView whose id attribute is " +
350                    "'android.R.id.list'");
351        }
352        mHandler.post(mRequestFocus);
353    }
354}
355