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