blob: 3f6e5051a3f83a8f8bfc8e90a2830d6c8912075b [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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.annotation.Nullable;
20import android.annotation.XmlRes;
21import android.app.Activity;
22import android.app.Fragment;
23import android.compat.annotation.UnsupportedAppUsage;
24import android.content.Intent;
25import android.content.SharedPreferences;
26import android.content.res.TypedArray;
27import android.os.Bundle;
28import android.os.Handler;
29import android.os.Message;
30import android.text.TextUtils;
31import android.view.KeyEvent;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.View.OnKeyListener;
35import android.view.ViewGroup;
36import android.widget.ListView;
37import android.widget.TextView;
38
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>
48 * 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>
59 * 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)}.
83 *
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>
90 *
91 * @see Preference
92 * @see PreferenceScreen
93 *
94 * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
95 * <a href="{@docRoot}reference/androidx/preference/package-summary.html">
96 * Preference Library</a> for consistent behavior across all devices. For more information on
97 * using the AndroidX Preference Library see
98 * <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>.
99 */
100@Deprecated
101public abstract class PreferenceFragment extends Fragment implements
102 PreferenceManager.OnPreferenceTreeClickListener {
103
104 private static final String PREFERENCES_TAG = "android:preferences";
105
106 @UnsupportedAppUsage
107 private PreferenceManager mPreferenceManager;
108 private ListView mList;
109 private boolean mHavePrefs;
110 private boolean mInitDone;
111
112 private int mLayoutResId = com.android.internal.R.layout.preference_list_fragment;
113
114 /**
115 * The starting request code given out to preference framework.
116 */
117 private static final int FIRST_REQUEST_CODE = 100;
118
119 private static final int MSG_BIND_PREFERENCES = 1;
120 private Handler mHandler = new Handler() {
121 @Override
122 public void handleMessage(Message msg) {
123 switch (msg.what) {
124
125 case MSG_BIND_PREFERENCES:
126 bindPreferences();
127 break;
128 }
129 }
130 };
131
132 final private Runnable mRequestFocus = new Runnable() {
133 public void run() {
134 mList.focusableViewAvailable(mList);
135 }
136 };
137
138 /**
139 * Interface that PreferenceFragment's containing activity should
140 * implement to be able to process preference items that wish to
141 * switch to a new fragment.
142 *
143 * @deprecated Use {@link
144 * android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
145 */
146 @Deprecated
147 public interface OnPreferenceStartFragmentCallback {
148 /**
149 * Called when the user has clicked on a Preference that has
150 * a fragment class name associated with it. The implementation
151 * to should instantiate and switch to an instance of the given
152 * fragment.
153 */
154 boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
155 }
156
157 @Override
158 public void onCreate(@Nullable Bundle savedInstanceState) {
159 super.onCreate(savedInstanceState);
160 mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
161 mPreferenceManager.setFragment(this);
162 }
163
164 @Override
165 public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
166 @Nullable Bundle savedInstanceState) {
167
168 TypedArray a = getActivity().obtainStyledAttributes(null,
169 com.android.internal.R.styleable.PreferenceFragment,
170 com.android.internal.R.attr.preferenceFragmentStyle,
171 0);
172
173 mLayoutResId = a.getResourceId(com.android.internal.R.styleable.PreferenceFragment_layout,
174 mLayoutResId);
175
176 a.recycle();
177
178 return inflater.inflate(mLayoutResId, container, false);
179 }
180
181 @Override
182 public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
183 super.onViewCreated(view, savedInstanceState);
184
185 TypedArray a = getActivity().obtainStyledAttributes(null,
186 com.android.internal.R.styleable.PreferenceFragment,
187 com.android.internal.R.attr.preferenceFragmentStyle,
188 0);
189
190 ListView lv = (ListView) view.findViewById(android.R.id.list);
191 if (lv != null
192 && a.hasValueOrEmpty(com.android.internal.R.styleable.PreferenceFragment_divider)) {
193 lv.setDivider(
194 a.getDrawable(com.android.internal.R.styleable.PreferenceFragment_divider));
195 }
196
197 a.recycle();
198 }
199
200 @Override
201 public void onActivityCreated(@Nullable Bundle savedInstanceState) {
202 super.onActivityCreated(savedInstanceState);
203
204 if (mHavePrefs) {
205 bindPreferences();
206 }
207
208 mInitDone = true;
209
210 if (savedInstanceState != null) {
211 Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
212 if (container != null) {
213 final PreferenceScreen preferenceScreen = getPreferenceScreen();
214 if (preferenceScreen != null) {
215 preferenceScreen.restoreHierarchyState(container);
216 }
217 }
218 }
219 }
220
221 @Override
222 public void onStart() {
223 super.onStart();
224 mPreferenceManager.setOnPreferenceTreeClickListener(this);
225 }
226
227 @Override
228 public void onStop() {
229 super.onStop();
230 mPreferenceManager.dispatchActivityStop();
231 mPreferenceManager.setOnPreferenceTreeClickListener(null);
232 }
233
234 @Override
235 public void onDestroyView() {
236 if (mList != null) {
237 mList.setOnKeyListener(null);
238 }
239 mList = null;
240 mHandler.removeCallbacks(mRequestFocus);
241 mHandler.removeMessages(MSG_BIND_PREFERENCES);
242 super.onDestroyView();
243 }
244
245 @Override
246 public void onDestroy() {
247 super.onDestroy();
248 mPreferenceManager.dispatchActivityDestroy();
249 }
250
251 @Override
252 public void onSaveInstanceState(Bundle outState) {
253 super.onSaveInstanceState(outState);
254
255 final PreferenceScreen preferenceScreen = getPreferenceScreen();
256 if (preferenceScreen != null) {
257 Bundle container = new Bundle();
258 preferenceScreen.saveHierarchyState(container);
259 outState.putBundle(PREFERENCES_TAG, container);
260 }
261 }
262
263 @Override
264 public void onActivityResult(int requestCode, int resultCode, Intent data) {
265 super.onActivityResult(requestCode, resultCode, data);
266
267 mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
268 }
269
270 /**
271 * Returns the {@link PreferenceManager} used by this fragment.
272 * @return The {@link PreferenceManager}.
273 */
274 public PreferenceManager getPreferenceManager() {
275 return mPreferenceManager;
276 }
277
278 /**
279 * Sets the root of the preference hierarchy that this fragment is showing.
280 *
281 * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
282 */
283 public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
284 if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
285 onUnbindPreferences();
286 mHavePrefs = true;
287 if (mInitDone) {
288 postBindPreferences();
289 }
290 }
291 }
292
293 /**
294 * Gets the root of the preference hierarchy that this fragment is showing.
295 *
296 * @return The {@link PreferenceScreen} that is the root of the preference
297 * hierarchy.
298 */
299 public PreferenceScreen getPreferenceScreen() {
300 return mPreferenceManager.getPreferenceScreen();
301 }
302
303 /**
304 * Adds preferences from activities that match the given {@link Intent}.
305 *
306 * @param intent The {@link Intent} to query activities.
307 */
308 public void addPreferencesFromIntent(Intent intent) {
309 requirePreferenceManager();
310
311 setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
312 }
313
314 /**
315 * Inflates the given XML resource and adds the preference hierarchy to the current
316 * preference hierarchy.
317 *
318 * @param preferencesResId The XML resource ID to inflate.
319 */
320 public void addPreferencesFromResource(@XmlRes int preferencesResId) {
321 requirePreferenceManager();
322
323 setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
324 preferencesResId, getPreferenceScreen()));
325 }
326
327 /**
328 * {@inheritDoc}
329 */
330 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
331 Preference preference) {
332 if (preference.getFragment() != null &&
333 getActivity() instanceof OnPreferenceStartFragmentCallback) {
334 return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
335 this, preference);
336 }
337 return false;
338 }
339
340 /**
341 * Finds a {@link Preference} based on its key.
342 *
343 * @param key The key of the preference to retrieve.
344 * @return The {@link Preference} with the key, or null.
345 * @see PreferenceGroup#findPreference(CharSequence)
346 */
347 public Preference findPreference(CharSequence key) {
348 if (mPreferenceManager == null) {
349 return null;
350 }
351 return mPreferenceManager.findPreference(key);
352 }
353
354 private void requirePreferenceManager() {
355 if (mPreferenceManager == null) {
356 throw new RuntimeException("This should be called after super.onCreate.");
357 }
358 }
359
360 private void postBindPreferences() {
361 if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
362 mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
363 }
364
365 private void bindPreferences() {
366 final PreferenceScreen preferenceScreen = getPreferenceScreen();
367 if (preferenceScreen != null) {
368 View root = getView();
369 if (root != null) {
370 View titleView = root.findViewById(android.R.id.title);
371 if (titleView instanceof TextView) {
372 CharSequence title = preferenceScreen.getTitle();
373 if (TextUtils.isEmpty(title)) {
374 titleView.setVisibility(View.GONE);
375 } else {
376 ((TextView) titleView).setText(title);
377 titleView.setVisibility(View.VISIBLE);
378 }
379 }
380 }
381
382 preferenceScreen.bind(getListView());
383 }
384 onBindPreferences();
385 }
386
387 /** @hide */
388 protected void onBindPreferences() {
389 }
390
391 /** @hide */
392 protected void onUnbindPreferences() {
393 }
394
395 /** @hide */
396 @UnsupportedAppUsage
397 public ListView getListView() {
398 ensureList();
399 return mList;
400 }
401
402 /** @hide */
403 public boolean hasListView() {
404 if (mList != null) {
405 return true;
406 }
407 View root = getView();
408 if (root == null) {
409 return false;
410 }
411 View rawListView = root.findViewById(android.R.id.list);
412 if (!(rawListView instanceof ListView)) {
413 return false;
414 }
415 mList = (ListView)rawListView;
416 if (mList == null) {
417 return false;
418 }
419 return true;
420 }
421
422 private void ensureList() {
423 if (mList != null) {
424 return;
425 }
426 View root = getView();
427 if (root == null) {
428 throw new IllegalStateException("Content view not yet created");
429 }
430 View rawListView = root.findViewById(android.R.id.list);
431 if (!(rawListView instanceof ListView)) {
432 throw new RuntimeException(
433 "Content has view with id attribute 'android.R.id.list' "
434 + "that is not a ListView class");
435 }
436 mList = (ListView)rawListView;
437 if (mList == null) {
438 throw new RuntimeException(
439 "Your content must have a ListView whose id attribute is " +
440 "'android.R.id.list'");
441 }
442 mList.setOnKeyListener(mListOnKeyListener);
443 mHandler.post(mRequestFocus);
444 }
445
446 private OnKeyListener mListOnKeyListener = new OnKeyListener() {
447
448 @Override
449 public boolean onKey(View v, int keyCode, KeyEvent event) {
450 Object selectedItem = mList.getSelectedItem();
451 if (selectedItem instanceof Preference) {
452 View selectedView = mList.getSelectedView();
453 return ((Preference)selectedItem).onKey(
454 selectedView, keyCode, event);
455 }
456 return false;
457 }
458
459 };
460}