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.Bundle;
28 import android.os.Handler;
29 import android.os.Message;
30 import android.text.TextUtils;
31 import android.view.KeyEvent;
32 import android.view.LayoutInflater;
33 import android.view.View;
34 import android.view.View.OnKeyListener;
35 import android.view.ViewGroup;
36 import android.widget.ListView;
37 import 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
101 public 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          */
onPreferenceStartFragment(PreferenceFragment caller, Preference pref)154         boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
155     }
156 
157     @Override
onCreate(@ullable Bundle savedInstanceState)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
onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)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
onViewCreated(View view, @Nullable Bundle savedInstanceState)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
onActivityCreated(@ullable Bundle savedInstanceState)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
onStart()222     public void onStart() {
223         super.onStart();
224         mPreferenceManager.setOnPreferenceTreeClickListener(this);
225     }
226 
227     @Override
onStop()228     public void onStop() {
229         super.onStop();
230         mPreferenceManager.dispatchActivityStop();
231         mPreferenceManager.setOnPreferenceTreeClickListener(null);
232     }
233 
234     @Override
onDestroyView()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
onDestroy()246     public void onDestroy() {
247         super.onDestroy();
248         mPreferenceManager.dispatchActivityDestroy();
249     }
250 
251     @Override
onSaveInstanceState(Bundle outState)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
onActivityResult(int requestCode, int resultCode, Intent data)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      */
getPreferenceManager()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      */
setPreferenceScreen(PreferenceScreen preferenceScreen)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      */
getPreferenceScreen()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      */
addPreferencesFromIntent(Intent intent)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      */
addPreferencesFromResource(@mlRes int preferencesResId)320     public void addPreferencesFromResource(@XmlRes int preferencesResId) {
321         requirePreferenceManager();
322 
323         setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
324                 preferencesResId, getPreferenceScreen()));
325     }
326 
327     /**
328      * {@inheritDoc}
329      */
onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference)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      */
findPreference(CharSequence key)347     public Preference findPreference(CharSequence key) {
348         if (mPreferenceManager == null) {
349             return null;
350         }
351         return mPreferenceManager.findPreference(key);
352     }
353 
requirePreferenceManager()354     private void requirePreferenceManager() {
355         if (mPreferenceManager == null) {
356             throw new RuntimeException("This should be called after super.onCreate.");
357         }
358     }
359 
postBindPreferences()360     private void postBindPreferences() {
361         if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
362         mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
363     }
364 
bindPreferences()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 */
onBindPreferences()388     protected void onBindPreferences() {
389     }
390 
391     /** @hide */
onUnbindPreferences()392     protected void onUnbindPreferences() {
393     }
394 
395     /** @hide */
396     @UnsupportedAppUsage
getListView()397     public ListView getListView() {
398         ensureList();
399         return mList;
400     }
401 
402     /** @hide */
hasListView()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 
ensureList()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 }
461