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