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