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