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