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