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