blob: 7511e148c6d48b1a63dfbc357fabca2bc9dcf8f2 [file] [log] [blame]
Dianne Hackborn42c29362010-07-28 14:32:15 -07001/*
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;
Dianne Hackborne72f2372011-03-16 10:43:18 -070023import android.content.res.Configuration;
Dianne Hackborn42c29362010-07-28 14:32:15 -070024import android.os.Bundle;
25import android.os.Handler;
26import android.os.Message;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.view.ViewGroup;
30import android.widget.ListView;
31
32/**
33 * Shows a hierarchy of {@link Preference} objects as
34 * lists. These preferences will
35 * automatically save to {@link SharedPreferences} as the user interacts with
36 * them. To retrieve an instance of {@link SharedPreferences} that the
37 * preference hierarchy in this fragment will use, call
38 * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
39 * with a context in the same package as this fragment.
40 * <p>
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070041 * Furthermore, the preferences shown will follow the visual style of system
42 * preferences. It is easy to create a hierarchy of preferences (that can be
43 * shown on multiple screens) via XML. For these reasons, it is recommended to
44 * use this fragment (as a superclass) to deal with preferences in applications.
45 * <p>
46 * A {@link PreferenceScreen} object should be at the top of the preference
47 * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
48 * denote a screen break--that is the preferences contained within subsequent
49 * {@link PreferenceScreen} should be shown on another screen. The preference
50 * framework handles showing these other screens from the preference hierarchy.
51 * <p>
Dianne Hackborn42c29362010-07-28 14:32:15 -070052 * The preference hierarchy can be formed in multiple ways:
53 * <li> From an XML file specifying the hierarchy
54 * <li> From different {@link Activity Activities} that each specify its own
55 * preferences in an XML file via {@link Activity} meta-data
56 * <li> From an object hierarchy rooted with {@link PreferenceScreen}
57 * <p>
58 * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
59 * root element should be a {@link PreferenceScreen}. Subsequent elements can point
60 * to actual {@link Preference} subclasses. As mentioned above, subsequent
61 * {@link PreferenceScreen} in the hierarchy will result in the screen break.
62 * <p>
63 * To specify an {@link Intent} to query {@link Activity Activities} that each
64 * have preferences, use {@link #addPreferencesFromIntent}. Each
65 * {@link Activity} can specify meta-data in the manifest (via the key
66 * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML
67 * resource. These XML resources will be inflated into a single preference
68 * hierarchy and shown by this fragment.
69 * <p>
70 * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
71 * {@link #setPreferenceScreen(PreferenceScreen)}.
72 * <p>
73 * As a convenience, this fragment implements a click listener for any
74 * preference in the current hierarchy, see
75 * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
Dianne Hackborn42c29362010-07-28 14:32:15 -070076 *
77 * <a name="SampleCode"></a>
78 * <h3>Sample Code</h3>
79 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070080 * <p>The following sample code shows a simple preference fragment that is
81 * populated from a resource. The resource it loads is:</p>
Dianne Hackborn42c29362010-07-28 14:32:15 -070082 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070083 * {@sample development/samples/ApiDemos/res/xml/preferences.xml preferences}
Dianne Hackborn42c29362010-07-28 14:32:15 -070084 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070085 * <p>The fragment implementation itself simply populates the preferences
86 * when created. Note that the preferences framework takes care of loading
87 * the current values out of the app preferences and writing them when changed:</p>
Dianne Hackborn42c29362010-07-28 14:32:15 -070088 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070089 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/FragmentPreferences.java
90 * fragment}
Dianne Hackborn42c29362010-07-28 14:32:15 -070091 *
92 * @see Preference
93 * @see PreferenceScreen
94 */
95public abstract class PreferenceFragment extends Fragment implements
96 PreferenceManager.OnPreferenceTreeClickListener {
97
98 private static final String PREFERENCES_TAG = "android:preferences";
99
100 private PreferenceManager mPreferenceManager;
101 private ListView mList;
102 private boolean mHavePrefs;
103 private boolean mInitDone;
104
105 /**
106 * The starting request code given out to preference framework.
107 */
108 private static final int FIRST_REQUEST_CODE = 100;
109
Dianne Hackborn3e449ce2010-09-11 20:52:31 -0700110 private static final int MSG_BIND_PREFERENCES = 1;
Dianne Hackborn42c29362010-07-28 14:32:15 -0700111 private Handler mHandler = new Handler() {
112 @Override
113 public void handleMessage(Message msg) {
114 switch (msg.what) {
115
116 case MSG_BIND_PREFERENCES:
117 bindPreferences();
118 break;
119 }
120 }
121 };
122
123 final private Runnable mRequestFocus = new Runnable() {
124 public void run() {
125 mList.focusableViewAvailable(mList);
126 }
127 };
128
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700129 /**
130 * Interface that PreferenceFragment's containing activity should
131 * implement to be able to process preference items that wish to
132 * switch to a new fragment.
133 */
134 public interface OnPreferenceStartFragmentCallback {
135 /**
136 * Called when the user has clicked on a Preference that has
137 * a fragment class name associated with it. The implementation
138 * to should instantiate and switch to an instance of the given
139 * fragment.
140 */
141 boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
142 }
143
Dianne Hackborn42c29362010-07-28 14:32:15 -0700144 @Override
145 public void onCreate(Bundle savedInstanceState) {
146 super.onCreate(savedInstanceState);
147 mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
Amith Yamasani82e7bc12010-09-23 15:07:58 -0700148 mPreferenceManager.setFragment(this);
Dianne Hackborn42c29362010-07-28 14:32:15 -0700149 mPreferenceManager.setOnPreferenceTreeClickListener(this);
150 }
151
152 @Override
153 public View onCreateView(LayoutInflater inflater, ViewGroup container,
154 Bundle savedInstanceState) {
Dianne Hackborne72f2372011-03-16 10:43:18 -0700155 if (getResources().getConfiguration().isLayoutSizeAtLeast(
156 Configuration.SCREENLAYOUT_SIZE_LARGE)) {
157 return inflater.inflate(com.android.internal.R.layout.preference_list_fragment_large,
158 container, false);
159 } else {
160 return inflater.inflate(com.android.internal.R.layout.preference_list_fragment,
161 container, false);
162 }
Dianne Hackborn42c29362010-07-28 14:32:15 -0700163 }
164
165 @Override
166 public void onActivityCreated(Bundle savedInstanceState) {
167 super.onActivityCreated(savedInstanceState);
168 getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
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
188 public void onStop() {
189 super.onStop();
190 mPreferenceManager.dispatchActivityStop();
191 }
192
193 @Override
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700194 public void onDestroyView() {
195 mList = null;
196 mHandler.removeCallbacks(mRequestFocus);
Amith Yamasani81d86002011-03-02 16:11:31 -0800197 mHandler.removeMessages(MSG_BIND_PREFERENCES);
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700198 super.onDestroyView();
199 }
200
201 @Override
Dianne Hackborn42c29362010-07-28 14:32:15 -0700202 public void onDestroy() {
203 super.onDestroy();
204 mPreferenceManager.dispatchActivityDestroy();
Amith Yamasani74402cf2010-12-09 11:22:15 -0800205 mPreferenceManager.setOnPreferenceTreeClickListener(null);
Dianne Hackborn42c29362010-07-28 14:32:15 -0700206 }
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 */
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700286 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 }
Dianne Hackborn42c29362010-07-28 14:32:15 -0700293 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
Amith Yamasani9ae5473e2010-08-17 16:49:58 -0700328 /** @hide */
329 public ListView getListView() {
Dianne Hackborn42c29362010-07-28 14:32:15 -0700330 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 mHandler.post(mRequestFocus);
355 }
356}