• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.preference;
18 
19 import android.app.Activity;
20 import android.app.Fragment;
21 import android.content.Intent;
22 import android.content.SharedPreferences;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.Message;
26 import android.view.KeyEvent;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.View.OnKeyListener;
31 import 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  * <div class="special reference">
79  * <h3>Developer Guides</h3>
80  * <p>For information about using {@code PreferenceFragment},
81  * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
82  * guide.</p>
83  * </div>
84  *
85  * <a name="SampleCode"></a>
86  * <h3>Sample Code</h3>
87  *
88  * <p>The following sample code shows a simple preference fragment that is
89  * populated from a resource.  The resource it loads is:</p>
90  *
91  * {@sample development/samples/ApiDemos/res/xml/preferences.xml preferences}
92  *
93  * <p>The fragment implementation itself simply populates the preferences
94  * when created.  Note that the preferences framework takes care of loading
95  * the current values out of the app preferences and writing them when changed:</p>
96  *
97  * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/FragmentPreferences.java
98  *      fragment}
99  *
100  * @see Preference
101  * @see PreferenceScreen
102  */
103 public abstract class PreferenceFragment extends Fragment implements
104         PreferenceManager.OnPreferenceTreeClickListener {
105 
106     private static final String PREFERENCES_TAG = "android:preferences";
107 
108     private PreferenceManager mPreferenceManager;
109     private ListView mList;
110     private boolean mHavePrefs;
111     private boolean mInitDone;
112 
113     /**
114      * The starting request code given out to preference framework.
115      */
116     private static final int FIRST_REQUEST_CODE = 100;
117 
118     private static final int MSG_BIND_PREFERENCES = 1;
119     private Handler mHandler = new Handler() {
120         @Override
121         public void handleMessage(Message msg) {
122             switch (msg.what) {
123 
124                 case MSG_BIND_PREFERENCES:
125                     bindPreferences();
126                     break;
127             }
128         }
129     };
130 
131     final private Runnable mRequestFocus = new Runnable() {
132         public void run() {
133             mList.focusableViewAvailable(mList);
134         }
135     };
136 
137     /**
138      * Interface that PreferenceFragment's containing activity should
139      * implement to be able to process preference items that wish to
140      * switch to a new fragment.
141      */
142     public interface OnPreferenceStartFragmentCallback {
143         /**
144          * Called when the user has clicked on a Preference that has
145          * a fragment class name associated with it.  The implementation
146          * to should instantiate and switch to an instance of the given
147          * fragment.
148          */
onPreferenceStartFragment(PreferenceFragment caller, Preference pref)149         boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
150     }
151 
152     @Override
onCreate(Bundle savedInstanceState)153     public void onCreate(Bundle savedInstanceState) {
154         super.onCreate(savedInstanceState);
155         mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
156         mPreferenceManager.setFragment(this);
157     }
158 
159     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)160     public View onCreateView(LayoutInflater inflater, ViewGroup container,
161             Bundle savedInstanceState) {
162         return inflater.inflate(com.android.internal.R.layout.preference_list_fragment, container,
163                 false);
164     }
165 
166     @Override
onActivityCreated(Bundle savedInstanceState)167     public void onActivityCreated(Bundle savedInstanceState) {
168         super.onActivityCreated(savedInstanceState);
169 
170         if (mHavePrefs) {
171             bindPreferences();
172         }
173 
174         mInitDone = true;
175 
176         if (savedInstanceState != null) {
177             Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
178             if (container != null) {
179                 final PreferenceScreen preferenceScreen = getPreferenceScreen();
180                 if (preferenceScreen != null) {
181                     preferenceScreen.restoreHierarchyState(container);
182                 }
183             }
184         }
185     }
186 
187     @Override
onStart()188     public void onStart() {
189         super.onStart();
190         mPreferenceManager.setOnPreferenceTreeClickListener(this);
191     }
192 
193     @Override
onStop()194     public void onStop() {
195         super.onStop();
196         mPreferenceManager.dispatchActivityStop();
197         mPreferenceManager.setOnPreferenceTreeClickListener(null);
198     }
199 
200     @Override
onDestroyView()201     public void onDestroyView() {
202         mList = null;
203         mHandler.removeCallbacks(mRequestFocus);
204         mHandler.removeMessages(MSG_BIND_PREFERENCES);
205         super.onDestroyView();
206     }
207 
208     @Override
onDestroy()209     public void onDestroy() {
210         super.onDestroy();
211         mPreferenceManager.dispatchActivityDestroy();
212     }
213 
214     @Override
onSaveInstanceState(Bundle outState)215     public void onSaveInstanceState(Bundle outState) {
216         super.onSaveInstanceState(outState);
217 
218         final PreferenceScreen preferenceScreen = getPreferenceScreen();
219         if (preferenceScreen != null) {
220             Bundle container = new Bundle();
221             preferenceScreen.saveHierarchyState(container);
222             outState.putBundle(PREFERENCES_TAG, container);
223         }
224     }
225 
226     @Override
onActivityResult(int requestCode, int resultCode, Intent data)227     public void onActivityResult(int requestCode, int resultCode, Intent data) {
228         super.onActivityResult(requestCode, resultCode, data);
229 
230         mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
231     }
232 
233     /**
234      * Returns the {@link PreferenceManager} used by this fragment.
235      * @return The {@link PreferenceManager}.
236      */
getPreferenceManager()237     public PreferenceManager getPreferenceManager() {
238         return mPreferenceManager;
239     }
240 
241     /**
242      * Sets the root of the preference hierarchy that this fragment is showing.
243      *
244      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
245      */
setPreferenceScreen(PreferenceScreen preferenceScreen)246     public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
247         if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
248             mHavePrefs = true;
249             if (mInitDone) {
250                 postBindPreferences();
251             }
252         }
253     }
254 
255     /**
256      * Gets the root of the preference hierarchy that this fragment is showing.
257      *
258      * @return The {@link PreferenceScreen} that is the root of the preference
259      *         hierarchy.
260      */
getPreferenceScreen()261     public PreferenceScreen getPreferenceScreen() {
262         return mPreferenceManager.getPreferenceScreen();
263     }
264 
265     /**
266      * Adds preferences from activities that match the given {@link Intent}.
267      *
268      * @param intent The {@link Intent} to query activities.
269      */
addPreferencesFromIntent(Intent intent)270     public void addPreferencesFromIntent(Intent intent) {
271         requirePreferenceManager();
272 
273         setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
274     }
275 
276     /**
277      * Inflates the given XML resource and adds the preference hierarchy to the current
278      * preference hierarchy.
279      *
280      * @param preferencesResId The XML resource ID to inflate.
281      */
addPreferencesFromResource(int preferencesResId)282     public void addPreferencesFromResource(int preferencesResId) {
283         requirePreferenceManager();
284 
285         setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
286                 preferencesResId, getPreferenceScreen()));
287     }
288 
289     /**
290      * {@inheritDoc}
291      */
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)292     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
293             Preference preference) {
294         if (preference.getFragment() != null &&
295                 getActivity() instanceof OnPreferenceStartFragmentCallback) {
296             return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
297                     this, preference);
298         }
299         return false;
300     }
301 
302     /**
303      * Finds a {@link Preference} based on its key.
304      *
305      * @param key The key of the preference to retrieve.
306      * @return The {@link Preference} with the key, or null.
307      * @see PreferenceGroup#findPreference(CharSequence)
308      */
findPreference(CharSequence key)309     public Preference findPreference(CharSequence key) {
310         if (mPreferenceManager == null) {
311             return null;
312         }
313         return mPreferenceManager.findPreference(key);
314     }
315 
requirePreferenceManager()316     private void requirePreferenceManager() {
317         if (mPreferenceManager == null) {
318             throw new RuntimeException("This should be called after super.onCreate.");
319         }
320     }
321 
postBindPreferences()322     private void postBindPreferences() {
323         if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
324         mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
325     }
326 
bindPreferences()327     private void bindPreferences() {
328         final PreferenceScreen preferenceScreen = getPreferenceScreen();
329         if (preferenceScreen != null) {
330             preferenceScreen.bind(getListView());
331         }
332     }
333 
334     /** @hide */
getListView()335     public ListView getListView() {
336         ensureList();
337         return mList;
338     }
339 
ensureList()340     private void ensureList() {
341         if (mList != null) {
342             return;
343         }
344         View root = getView();
345         if (root == null) {
346             throw new IllegalStateException("Content view not yet created");
347         }
348         View rawListView = root.findViewById(android.R.id.list);
349         if (!(rawListView instanceof ListView)) {
350             throw new RuntimeException(
351                     "Content has view with id attribute 'android.R.id.list' "
352                     + "that is not a ListView class");
353         }
354         mList = (ListView)rawListView;
355         if (mList == null) {
356             throw new RuntimeException(
357                     "Your content must have a ListView whose id attribute is " +
358                     "'android.R.id.list'");
359         }
360         mList.setOnKeyListener(mListOnKeyListener);
361         mHandler.post(mRequestFocus);
362     }
363 
364     private OnKeyListener mListOnKeyListener = new OnKeyListener() {
365 
366         @Override
367         public boolean onKey(View v, int keyCode, KeyEvent event) {
368             Object selectedItem = mList.getSelectedItem();
369             if (selectedItem instanceof Preference) {
370                 View selectedView = mList.getSelectedView();
371                 return ((Preference)selectedItem).onKey(
372                         selectedView, keyCode, event);
373             }
374             return false;
375         }
376 
377     };
378 }
379