blob: 548895e1b618dd01d7ee270f539795cef041557b [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;
Mathew Inwoodeac8d0a2018-08-17 13:51:26 +010020import android.annotation.UnsupportedAppUsage;
Tor Norbye417ee5b2015-03-10 20:57:37 -070021import android.annotation.XmlRes;
Dianne Hackborn42c29362010-07-28 14:32:15 -070022import android.app.Activity;
23import android.app.Fragment;
24import android.content.Intent;
25import android.content.SharedPreferences;
Fabrice Di Megliof9499b32014-07-02 17:28:20 -070026import android.content.res.TypedArray;
Dianne Hackborn42c29362010-07-28 14:32:15 -070027import android.os.Bundle;
28import android.os.Handler;
29import android.os.Message;
Michael Kwan744be162016-07-22 18:37:31 -070030import android.text.TextUtils;
John Reck014fea22011-06-15 16:46:36 -070031import android.view.KeyEvent;
Dianne Hackborn42c29362010-07-28 14:32:15 -070032import android.view.LayoutInflater;
33import android.view.View;
John Reck014fea22011-06-15 16:46:36 -070034import android.view.View.OnKeyListener;
Jason Monk7fc97c92015-10-28 14:33:39 -040035import android.view.ViewGroup;
Dianne Hackborn42c29362010-07-28 14:32:15 -070036import android.widget.ListView;
Michael Kwan744be162016-07-22 18:37:31 -070037import android.widget.TextView;
Dianne Hackborn42c29362010-07-28 14:32:15 -070038
39/**
40 * Shows a hierarchy of {@link Preference} objects as
41 * lists. These preferences will
42 * automatically save to {@link SharedPreferences} as the user interacts with
43 * them. To retrieve an instance of {@link SharedPreferences} that the
44 * preference hierarchy in this fragment will use, call
45 * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
46 * with a context in the same package as this fragment.
47 * <p>
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070048 * Furthermore, the preferences shown will follow the visual style of system
49 * preferences. It is easy to create a hierarchy of preferences (that can be
50 * shown on multiple screens) via XML. For these reasons, it is recommended to
51 * use this fragment (as a superclass) to deal with preferences in applications.
52 * <p>
53 * A {@link PreferenceScreen} object should be at the top of the preference
54 * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
55 * denote a screen break--that is the preferences contained within subsequent
56 * {@link PreferenceScreen} should be shown on another screen. The preference
57 * framework handles showing these other screens from the preference hierarchy.
58 * <p>
Dianne Hackborn42c29362010-07-28 14:32:15 -070059 * The preference hierarchy can be formed in multiple ways:
60 * <li> From an XML file specifying the hierarchy
61 * <li> From different {@link Activity Activities} that each specify its own
62 * preferences in an XML file via {@link Activity} meta-data
63 * <li> From an object hierarchy rooted with {@link PreferenceScreen}
64 * <p>
65 * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
66 * root element should be a {@link PreferenceScreen}. Subsequent elements can point
67 * to actual {@link Preference} subclasses. As mentioned above, subsequent
68 * {@link PreferenceScreen} in the hierarchy will result in the screen break.
69 * <p>
70 * To specify an {@link Intent} to query {@link Activity Activities} that each
71 * have preferences, use {@link #addPreferencesFromIntent}. Each
72 * {@link Activity} can specify meta-data in the manifest (via the key
73 * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML
74 * resource. These XML resources will be inflated into a single preference
75 * hierarchy and shown by this fragment.
76 * <p>
77 * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
78 * {@link #setPreferenceScreen(PreferenceScreen)}.
79 * <p>
80 * As a convenience, this fragment implements a click listener for any
81 * preference in the current hierarchy, see
82 * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
Scott Maincdd0c592012-07-26 17:03:51 -070083 *
84 * <div class="special reference">
85 * <h3>Developer Guides</h3>
86 * <p>For information about using {@code PreferenceFragment},
87 * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
88 * guide.</p>
89 * </div>
Dianne Hackborn42c29362010-07-28 14:32:15 -070090 *
91 * <a name="SampleCode"></a>
92 * <h3>Sample Code</h3>
93 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070094 * <p>The following sample code shows a simple preference fragment that is
95 * populated from a resource. The resource it loads is:</p>
Dianne Hackborn42c29362010-07-28 14:32:15 -070096 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070097 * {@sample development/samples/ApiDemos/res/xml/preferences.xml preferences}
Dianne Hackborn42c29362010-07-28 14:32:15 -070098 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -070099 * <p>The fragment implementation itself simply populates the preferences
100 * when created. Note that the preferences framework takes care of loading
101 * the current values out of the app preferences and writing them when changed:</p>
Dianne Hackborn42c29362010-07-28 14:32:15 -0700102 *
Dianne Hackbornb1ad5972010-08-02 17:30:33 -0700103 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/FragmentPreferences.java
104 * fragment}
Dianne Hackborn42c29362010-07-28 14:32:15 -0700105 *
106 * @see Preference
107 * @see PreferenceScreen
Ian Lake0a1feb82017-11-13 10:26:46 -0800108 *
109 * @deprecated Use {@link android.support.v7.preference.PreferenceFragmentCompat}
Dianne Hackborn42c29362010-07-28 14:32:15 -0700110 */
Ian Lake0a1feb82017-11-13 10:26:46 -0800111@Deprecated
Dianne Hackborn42c29362010-07-28 14:32:15 -0700112public abstract class PreferenceFragment extends Fragment implements
113 PreferenceManager.OnPreferenceTreeClickListener {
114
115 private static final String PREFERENCES_TAG = "android:preferences";
116
Mathew Inwoodeac8d0a2018-08-17 13:51:26 +0100117 @UnsupportedAppUsage
Dianne Hackborn42c29362010-07-28 14:32:15 -0700118 private PreferenceManager mPreferenceManager;
119 private ListView mList;
120 private boolean mHavePrefs;
121 private boolean mInitDone;
122
Fabrice Di Megliof9499b32014-07-02 17:28:20 -0700123 private int mLayoutResId = com.android.internal.R.layout.preference_list_fragment;
124
Dianne Hackborn42c29362010-07-28 14:32:15 -0700125 /**
126 * The starting request code given out to preference framework.
127 */
128 private static final int FIRST_REQUEST_CODE = 100;
129
Dianne Hackborn3e449ce2010-09-11 20:52:31 -0700130 private static final int MSG_BIND_PREFERENCES = 1;
Dianne Hackborn42c29362010-07-28 14:32:15 -0700131 private Handler mHandler = new Handler() {
132 @Override
133 public void handleMessage(Message msg) {
134 switch (msg.what) {
135
136 case MSG_BIND_PREFERENCES:
137 bindPreferences();
138 break;
139 }
140 }
141 };
142
143 final private Runnable mRequestFocus = new Runnable() {
144 public void run() {
145 mList.focusableViewAvailable(mList);
146 }
147 };
148
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700149 /**
150 * Interface that PreferenceFragment's containing activity should
151 * implement to be able to process preference items that wish to
152 * switch to a new fragment.
Ian Lake0a1feb82017-11-13 10:26:46 -0800153 *
154 * @deprecated Use {@link
155 * android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700156 */
Ian Lake0a1feb82017-11-13 10:26:46 -0800157 @Deprecated
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700158 public interface OnPreferenceStartFragmentCallback {
159 /**
160 * Called when the user has clicked on a Preference that has
161 * a fragment class name associated with it. The implementation
162 * to should instantiate and switch to an instance of the given
163 * fragment.
164 */
165 boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
166 }
167
Dianne Hackborn42c29362010-07-28 14:32:15 -0700168 @Override
Scott Kennedy3c32b622015-02-22 16:58:58 -0800169 public void onCreate(@Nullable Bundle savedInstanceState) {
Dianne Hackborn42c29362010-07-28 14:32:15 -0700170 super.onCreate(savedInstanceState);
171 mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
Amith Yamasani82e7bc12010-09-23 15:07:58 -0700172 mPreferenceManager.setFragment(this);
Dianne Hackborn42c29362010-07-28 14:32:15 -0700173 }
174
175 @Override
Scott Kennedy3c32b622015-02-22 16:58:58 -0800176 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
177 @Nullable Bundle savedInstanceState) {
Fabrice Di Megliof9499b32014-07-02 17:28:20 -0700178
179 TypedArray a = getActivity().obtainStyledAttributes(null,
180 com.android.internal.R.styleable.PreferenceFragment,
181 com.android.internal.R.attr.preferenceFragmentStyle,
182 0);
183
184 mLayoutResId = a.getResourceId(com.android.internal.R.styleable.PreferenceFragment_layout,
185 mLayoutResId);
186
187 a.recycle();
188
189 return inflater.inflate(mLayoutResId, container, false);
Dianne Hackborn42c29362010-07-28 14:32:15 -0700190 }
191
192 @Override
Jason Monk7fc97c92015-10-28 14:33:39 -0400193 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
194 super.onViewCreated(view, savedInstanceState);
195
196 TypedArray a = getActivity().obtainStyledAttributes(null,
197 com.android.internal.R.styleable.PreferenceFragment,
198 com.android.internal.R.attr.preferenceFragmentStyle,
199 0);
200
201 ListView lv = (ListView) view.findViewById(android.R.id.list);
Michael Kwan90852302016-07-02 19:31:32 -0700202 if (lv != null
203 && a.hasValueOrEmpty(com.android.internal.R.styleable.PreferenceFragment_divider)) {
204 lv.setDivider(
205 a.getDrawable(com.android.internal.R.styleable.PreferenceFragment_divider));
Jason Monk7fc97c92015-10-28 14:33:39 -0400206 }
207
208 a.recycle();
209 }
210
211 @Override
Scott Kennedy3c32b622015-02-22 16:58:58 -0800212 public void onActivityCreated(@Nullable Bundle savedInstanceState) {
Dianne Hackborn42c29362010-07-28 14:32:15 -0700213 super.onActivityCreated(savedInstanceState);
Dianne Hackborn42c29362010-07-28 14:32:15 -0700214
215 if (mHavePrefs) {
216 bindPreferences();
217 }
218
219 mInitDone = true;
220
221 if (savedInstanceState != null) {
222 Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
223 if (container != null) {
224 final PreferenceScreen preferenceScreen = getPreferenceScreen();
225 if (preferenceScreen != null) {
226 preferenceScreen.restoreHierarchyState(container);
227 }
228 }
229 }
230 }
231
232 @Override
Amith Yamasanic56fc752011-06-14 15:07:49 -0700233 public void onStart() {
234 super.onStart();
235 mPreferenceManager.setOnPreferenceTreeClickListener(this);
236 }
237
238 @Override
Dianne Hackborn42c29362010-07-28 14:32:15 -0700239 public void onStop() {
240 super.onStop();
241 mPreferenceManager.dispatchActivityStop();
Amith Yamasanic56fc752011-06-14 15:07:49 -0700242 mPreferenceManager.setOnPreferenceTreeClickListener(null);
Dianne Hackborn42c29362010-07-28 14:32:15 -0700243 }
244
245 @Override
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700246 public void onDestroyView() {
Mykola Kondratenko5e18c402015-02-09 19:20:28 +0100247 if (mList != null) {
248 mList.setOnKeyListener(null);
249 }
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700250 mList = null;
251 mHandler.removeCallbacks(mRequestFocus);
Amith Yamasani81d86002011-03-02 16:11:31 -0800252 mHandler.removeMessages(MSG_BIND_PREFERENCES);
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700253 super.onDestroyView();
254 }
255
256 @Override
Dianne Hackborn42c29362010-07-28 14:32:15 -0700257 public void onDestroy() {
258 super.onDestroy();
259 mPreferenceManager.dispatchActivityDestroy();
260 }
261
262 @Override
263 public void onSaveInstanceState(Bundle outState) {
264 super.onSaveInstanceState(outState);
265
266 final PreferenceScreen preferenceScreen = getPreferenceScreen();
267 if (preferenceScreen != null) {
268 Bundle container = new Bundle();
269 preferenceScreen.saveHierarchyState(container);
270 outState.putBundle(PREFERENCES_TAG, container);
271 }
272 }
273
274 @Override
275 public void onActivityResult(int requestCode, int resultCode, Intent data) {
276 super.onActivityResult(requestCode, resultCode, data);
277
278 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
279 }
280
281 /**
282 * Returns the {@link PreferenceManager} used by this fragment.
283 * @return The {@link PreferenceManager}.
284 */
285 public PreferenceManager getPreferenceManager() {
286 return mPreferenceManager;
287 }
288
289 /**
290 * Sets the root of the preference hierarchy that this fragment is showing.
291 *
292 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
293 */
294 public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
295 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
Fabrice Di Megliob1a50f22014-08-13 10:41:40 -0700296 onUnbindPreferences();
Dianne Hackborn42c29362010-07-28 14:32:15 -0700297 mHavePrefs = true;
298 if (mInitDone) {
299 postBindPreferences();
300 }
301 }
302 }
303
304 /**
305 * Gets the root of the preference hierarchy that this fragment is showing.
306 *
307 * @return The {@link PreferenceScreen} that is the root of the preference
308 * hierarchy.
309 */
310 public PreferenceScreen getPreferenceScreen() {
311 return mPreferenceManager.getPreferenceScreen();
312 }
313
314 /**
315 * Adds preferences from activities that match the given {@link Intent}.
316 *
317 * @param intent The {@link Intent} to query activities.
318 */
319 public void addPreferencesFromIntent(Intent intent) {
320 requirePreferenceManager();
321
322 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
323 }
324
325 /**
326 * Inflates the given XML resource and adds the preference hierarchy to the current
327 * preference hierarchy.
328 *
329 * @param preferencesResId The XML resource ID to inflate.
330 */
Tor Norbye417ee5b2015-03-10 20:57:37 -0700331 public void addPreferencesFromResource(@XmlRes int preferencesResId) {
Dianne Hackborn42c29362010-07-28 14:32:15 -0700332 requirePreferenceManager();
333
334 setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
335 preferencesResId, getPreferenceScreen()));
336 }
337
338 /**
339 * {@inheritDoc}
340 */
Dianne Hackbornb3cf10f2010-08-03 13:07:11 -0700341 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
342 Preference preference) {
343 if (preference.getFragment() != null &&
344 getActivity() instanceof OnPreferenceStartFragmentCallback) {
345 return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
346 this, preference);
347 }
Dianne Hackborn42c29362010-07-28 14:32:15 -0700348 return false;
349 }
350
351 /**
352 * Finds a {@link Preference} based on its key.
353 *
354 * @param key The key of the preference to retrieve.
355 * @return The {@link Preference} with the key, or null.
356 * @see PreferenceGroup#findPreference(CharSequence)
357 */
358 public Preference findPreference(CharSequence key) {
359 if (mPreferenceManager == null) {
360 return null;
361 }
362 return mPreferenceManager.findPreference(key);
363 }
364
365 private void requirePreferenceManager() {
366 if (mPreferenceManager == null) {
367 throw new RuntimeException("This should be called after super.onCreate.");
368 }
369 }
370
371 private void postBindPreferences() {
372 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
373 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
374 }
375
376 private void bindPreferences() {
377 final PreferenceScreen preferenceScreen = getPreferenceScreen();
378 if (preferenceScreen != null) {
Michael Kwan744be162016-07-22 18:37:31 -0700379 View root = getView();
380 if (root != null) {
381 View titleView = root.findViewById(android.R.id.title);
382 if (titleView instanceof TextView) {
383 CharSequence title = preferenceScreen.getTitle();
384 if (TextUtils.isEmpty(title)) {
385 titleView.setVisibility(View.GONE);
386 } else {
387 ((TextView) titleView).setText(title);
388 titleView.setVisibility(View.VISIBLE);
389 }
390 }
391 }
392
Dianne Hackborn42c29362010-07-28 14:32:15 -0700393 preferenceScreen.bind(getListView());
394 }
Fabrice Di Meglio8a86d732014-04-17 17:14:59 -0700395 onBindPreferences();
396 }
397
398 /** @hide */
399 protected void onBindPreferences() {
Dianne Hackborn42c29362010-07-28 14:32:15 -0700400 }
401
Amith Yamasani9ae5473e2010-08-17 16:49:58 -0700402 /** @hide */
Fabrice Di Megliob1a50f22014-08-13 10:41:40 -0700403 protected void onUnbindPreferences() {
404 }
405
406 /** @hide */
Mathew Inwoodeac8d0a2018-08-17 13:51:26 +0100407 @UnsupportedAppUsage
Amith Yamasani9ae5473e2010-08-17 16:49:58 -0700408 public ListView getListView() {
Dianne Hackborn42c29362010-07-28 14:32:15 -0700409 ensureList();
410 return mList;
411 }
412
Fabrice Di Meglio35d7b892014-04-15 19:15:20 -0700413 /** @hide */
414 public boolean hasListView() {
415 if (mList != null) {
416 return true;
417 }
418 View root = getView();
419 if (root == null) {
420 return false;
421 }
422 View rawListView = root.findViewById(android.R.id.list);
423 if (!(rawListView instanceof ListView)) {
424 return false;
425 }
426 mList = (ListView)rawListView;
427 if (mList == null) {
428 return false;
429 }
430 return true;
431 }
432
Dianne Hackborn42c29362010-07-28 14:32:15 -0700433 private void ensureList() {
434 if (mList != null) {
435 return;
436 }
437 View root = getView();
438 if (root == null) {
439 throw new IllegalStateException("Content view not yet created");
440 }
441 View rawListView = root.findViewById(android.R.id.list);
442 if (!(rawListView instanceof ListView)) {
443 throw new RuntimeException(
444 "Content has view with id attribute 'android.R.id.list' "
445 + "that is not a ListView class");
446 }
447 mList = (ListView)rawListView;
448 if (mList == null) {
449 throw new RuntimeException(
450 "Your content must have a ListView whose id attribute is " +
451 "'android.R.id.list'");
452 }
John Reck014fea22011-06-15 16:46:36 -0700453 mList.setOnKeyListener(mListOnKeyListener);
Dianne Hackborn42c29362010-07-28 14:32:15 -0700454 mHandler.post(mRequestFocus);
455 }
John Reck014fea22011-06-15 16:46:36 -0700456
457 private OnKeyListener mListOnKeyListener = new OnKeyListener() {
458
459 @Override
460 public boolean onKey(View v, int keyCode, KeyEvent event) {
461 Object selectedItem = mList.getSelectedItem();
462 if (selectedItem instanceof Preference) {
463 View selectedView = mList.getSelectedView();
464 return ((Preference)selectedItem).onKey(
465 selectedView, keyCode, event);
466 }
467 return false;
468 }
469
470 };
Dianne Hackborn42c29362010-07-28 14:32:15 -0700471}