• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.support.v14.preference;
18 
19 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
20 
21 import android.app.DialogFragment;
22 import android.app.Fragment;
23 import android.content.Context;
24 import android.content.res.TypedArray;
25 import android.graphics.Canvas;
26 import android.graphics.Rect;
27 import android.graphics.drawable.Drawable;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.support.annotation.NonNull;
32 import android.support.annotation.Nullable;
33 import android.support.annotation.RestrictTo;
34 import android.support.annotation.XmlRes;
35 import android.support.v4.content.res.TypedArrayUtils;
36 import android.support.v7.preference.AndroidResources;
37 import android.support.v7.preference.DialogPreference;
38 import android.support.v7.preference.EditTextPreference;
39 import android.support.v7.preference.ListPreference;
40 import android.support.v7.preference.Preference;
41 import android.support.v7.preference.PreferenceGroup;
42 import android.support.v7.preference.PreferenceGroupAdapter;
43 import android.support.v7.preference.PreferenceManager;
44 import android.support.v7.preference.PreferenceRecyclerViewAccessibilityDelegate;
45 import android.support.v7.preference.PreferenceScreen;
46 import android.support.v7.preference.PreferenceViewHolder;
47 import android.support.v7.widget.LinearLayoutManager;
48 import android.support.v7.widget.RecyclerView;
49 import android.util.TypedValue;
50 import android.view.ContextThemeWrapper;
51 import android.view.LayoutInflater;
52 import android.view.View;
53 import android.view.ViewGroup;
54 
55 /**
56  * Shows a hierarchy of {@link Preference} objects as
57  * lists. These preferences will
58  * automatically save to {@link android.content.SharedPreferences} as the user interacts with
59  * them. To retrieve an instance of {@link android.content.SharedPreferences} that the
60  * preference hierarchy in this fragment will use, call
61  * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
62  * with a context in the same package as this fragment.
63  * <p>
64  * Furthermore, the preferences shown will follow the visual style of system
65  * preferences. It is easy to create a hierarchy of preferences (that can be
66  * shown on multiple screens) via XML. For these reasons, it is recommended to
67  * use this fragment (as a superclass) to deal with preferences in applications.
68  * <p>
69  * A {@link PreferenceScreen} object should be at the top of the preference
70  * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
71  * denote a screen break--that is the preferences contained within subsequent
72  * {@link PreferenceScreen} should be shown on another screen. The preference
73  * framework handles this by calling {@link #onNavigateToScreen(PreferenceScreen)}.
74  * <p>
75  * The preference hierarchy can be formed in multiple ways:
76  * <li> From an XML file specifying the hierarchy
77  * <li> From different {@link android.app.Activity Activities} that each specify its own
78  * preferences in an XML file via {@link android.app.Activity} meta-data
79  * <li> From an object hierarchy rooted with {@link PreferenceScreen}
80  * <p>
81  * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
82  * root element should be a {@link PreferenceScreen}. Subsequent elements can point
83  * to actual {@link Preference} subclasses. As mentioned above, subsequent
84  * {@link PreferenceScreen} in the hierarchy will result in the screen break.
85  * <p>
86  * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
87  * {@link #setPreferenceScreen(PreferenceScreen)}.
88  * <p>
89  * As a convenience, this fragment implements a click listener for any
90  * preference in the current hierarchy, see
91  * {@link #onPreferenceTreeClick(Preference)}.
92  *
93  * <div class="special reference">
94  * <h3>Developer Guides</h3>
95  * <p>For information about using {@code PreferenceFragment},
96  * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
97  * guide.</p>
98  * </div>
99  *
100  * <a name="SampleCode"></a>
101  * <h3>Sample Code</h3>
102  *
103  * <p>The following sample code shows a simple preference fragment that is
104  * populated from a resource.  The resource it loads is:</p>
105  *
106  * {@sample frameworks/support/samples/SupportPreferenceDemos/res/xml/preferences.xml preferences}
107  *
108  * <p>The fragment implementation itself simply populates the preferences
109  * when created.  Note that the preferences framework takes care of loading
110  * the current values out of the app preferences and writing them when changed:</p>
111  *
112  * {@sample frameworks/support/samples/SupportPreferenceDemos/src/com/example/android/supportpreference/FragmentSupportPreferences.java
113  *      support_fragment}
114  *
115  * @see Preference
116  * @see PreferenceScreen
117  */
118 public abstract class PreferenceFragment extends Fragment implements
119         PreferenceManager.OnPreferenceTreeClickListener,
120         PreferenceManager.OnDisplayPreferenceDialogListener,
121         PreferenceManager.OnNavigateToScreenListener,
122         DialogPreference.TargetFragment {
123 
124     /**
125      * Fragment argument used to specify the tag of the desired root
126      * {@link android.support.v7.preference.PreferenceScreen} object.
127      */
128     public static final String ARG_PREFERENCE_ROOT =
129             "android.support.v7.preference.PreferenceFragmentCompat.PREFERENCE_ROOT";
130 
131     private static final String PREFERENCES_TAG = "android:preferences";
132 
133     private static final String DIALOG_FRAGMENT_TAG =
134             "android.support.v14.preference.PreferenceFragment.DIALOG";
135 
136     private PreferenceManager mPreferenceManager;
137     private RecyclerView mList;
138     private boolean mHavePrefs;
139     private boolean mInitDone;
140 
141     private Context mStyledContext;
142 
143     private int mLayoutResId = android.support.v7.preference.R.layout.preference_list_fragment;
144 
145     private final DividerDecoration mDividerDecoration = new DividerDecoration();
146 
147     private static final int MSG_BIND_PREFERENCES = 1;
148     private Handler mHandler = new Handler() {
149         @Override
150         public void handleMessage(Message msg) {
151             switch (msg.what) {
152 
153                 case MSG_BIND_PREFERENCES:
154                     bindPreferences();
155                     break;
156             }
157         }
158     };
159 
160     final private Runnable mRequestFocus = new Runnable() {
161         @Override
162         public void run() {
163             mList.focusableViewAvailable(mList);
164         }
165     };
166 
167     private Runnable mSelectPreferenceRunnable;
168 
169     /**
170      * Interface that PreferenceFragment's containing activity should
171      * implement to be able to process preference items that wish to
172      * switch to a specified fragment.
173      */
174     public interface OnPreferenceStartFragmentCallback {
175         /**
176          * Called when the user has clicked on a Preference that has
177          * a fragment class name associated with it.  The implementation
178          * should instantiate and switch to an instance of the given
179          * fragment.
180          * @param caller The fragment requesting navigation.
181          * @param pref The preference requesting the fragment.
182          * @return true if the fragment creation has been handled
183          */
onPreferenceStartFragment(PreferenceFragment caller, Preference pref)184         boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
185     }
186 
187     /**
188      * Interface that PreferenceFragment's containing activity should
189      * implement to be able to process preference items that wish to
190      * switch to a new screen of preferences.
191      */
192     public interface OnPreferenceStartScreenCallback {
193         /**
194          * Called when the user has clicked on a PreferenceScreen item in order to navigate to a new
195          * screen of preferences.
196          * @param caller The fragment requesting navigation.
197          * @param pref The preference screen to navigate to.
198          * @return true if the screen navigation has been handled
199          */
onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref)200         boolean onPreferenceStartScreen(PreferenceFragment caller, PreferenceScreen pref);
201     }
202 
203     public interface OnPreferenceDisplayDialogCallback {
204 
205         /**
206          *
207          * @param caller The fragment containing the preference requesting the dialog.
208          * @param pref The preference requesting the dialog.
209          * @return true if the dialog creation has been handled.
210          */
onPreferenceDisplayDialog(@onNull PreferenceFragment caller, Preference pref)211         boolean onPreferenceDisplayDialog(@NonNull PreferenceFragment caller, Preference pref);
212     }
213 
214     @Override
onCreate(Bundle savedInstanceState)215     public void onCreate(Bundle savedInstanceState) {
216         super.onCreate(savedInstanceState);
217         final TypedValue tv = new TypedValue();
218         getActivity().getTheme().resolveAttribute(
219                 android.support.v7.preference.R.attr.preferenceTheme, tv, true);
220         final int theme = tv.resourceId;
221         if (theme == 0) {
222             throw new IllegalStateException("Must specify preferenceTheme in theme");
223         }
224         mStyledContext = new ContextThemeWrapper(getActivity(), theme);
225 
226         mPreferenceManager = new PreferenceManager(mStyledContext);
227         mPreferenceManager.setOnNavigateToScreenListener(this);
228         final Bundle args = getArguments();
229         final String rootKey;
230         if (args != null) {
231             rootKey = getArguments().getString(ARG_PREFERENCE_ROOT);
232         } else {
233             rootKey = null;
234         }
235         onCreatePreferences(savedInstanceState, rootKey);
236     }
237 
238     /**
239      * Called during {@link #onCreate(Bundle)} to supply the preferences for this fragment.
240      * Subclasses are expected to call {@link #setPreferenceScreen(PreferenceScreen)} either
241      * directly or via helper methods such as {@link #addPreferencesFromResource(int)}.
242      *
243      * @param savedInstanceState If the fragment is being re-created from
244      *                           a previous saved state, this is the state.
245      * @param rootKey If non-null, this preference fragment should be rooted at the
246      *                {@link android.support.v7.preference.PreferenceScreen} with this key.
247      */
onCreatePreferences(Bundle savedInstanceState, String rootKey)248     public abstract void onCreatePreferences(Bundle savedInstanceState, String rootKey);
249 
250     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)251     public View onCreateView(LayoutInflater inflater, ViewGroup container,
252             Bundle savedInstanceState) {
253 
254         TypedArray a = mStyledContext.obtainStyledAttributes(null,
255                 R.styleable.PreferenceFragment,
256                 TypedArrayUtils.getAttr(mStyledContext,
257                         android.support.v7.preference.R.attr.preferenceFragmentStyle,
258                         AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE),
259                 0);
260 
261         mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_android_layout, mLayoutResId);
262 
263         final Drawable divider = a.getDrawable(R.styleable.PreferenceFragment_android_divider);
264         final int dividerHeight = a.getDimensionPixelSize(
265                 R.styleable.PreferenceFragment_android_dividerHeight, -1);
266         final boolean allowDividerAfterLastItem = a.getBoolean(
267                 R.styleable.PreferenceFragment_allowDividerAfterLastItem, true);
268         a.recycle();
269 
270         // Need to theme the inflater to pick up the preferenceFragmentListStyle
271         final TypedValue tv = new TypedValue();
272         getActivity().getTheme().resolveAttribute(
273                 android.support.v7.preference.R.attr.preferenceTheme, tv, true);
274         final int theme = tv.resourceId;
275 
276         final Context themedContext = new ContextThemeWrapper(inflater.getContext(), theme);
277         final LayoutInflater themedInflater = inflater.cloneInContext(themedContext);
278 
279         final View view = themedInflater.inflate(mLayoutResId, container, false);
280 
281         final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
282         if (!(rawListContainer instanceof ViewGroup)) {
283             throw new RuntimeException("Content has view with id attribute "
284                     + "'android.R.id.list_container' that is not a ViewGroup class");
285         }
286 
287         final ViewGroup listContainer = (ViewGroup) rawListContainer;
288 
289         final RecyclerView listView = onCreateRecyclerView(themedInflater, listContainer,
290                 savedInstanceState);
291         if (listView == null) {
292             throw new RuntimeException("Could not create RecyclerView");
293         }
294 
295         mList = listView;
296 
297         listView.addItemDecoration(mDividerDecoration);
298         setDivider(divider);
299         if (dividerHeight != -1) {
300             setDividerHeight(dividerHeight);
301         }
302         mDividerDecoration.setAllowDividerAfterLastItem(allowDividerAfterLastItem);
303 
304         listContainer.addView(mList);
305         mHandler.post(mRequestFocus);
306 
307         return view;
308     }
309 
310     /**
311      * Sets the drawable that will be drawn between each item in the list.
312      * <p>
313      * <strong>Note:</strong> If the drawable does not have an intrinsic
314      * height, you should also call {@link #setDividerHeight(int)}.
315      *
316      * @param divider the drawable to use
317      * @attr ref R.styleable#PreferenceFragment_android_divider
318      */
setDivider(Drawable divider)319     public void setDivider(Drawable divider) {
320         mDividerDecoration.setDivider(divider);
321     }
322 
323     /**
324      * Sets the height of the divider that will be drawn between each item in the list. Calling
325      * this will override the intrinsic height as set by {@link #setDivider(Drawable)}
326      *
327      * @param height The new height of the divider in pixels.
328      * @attr ref R.styleable#PreferenceFragment_android_dividerHeight
329      */
setDividerHeight(int height)330     public void setDividerHeight(int height) {
331         mDividerDecoration.setDividerHeight(height);
332     }
333 
334     @Override
onViewCreated(View view, Bundle savedInstanceState)335     public void onViewCreated(View view, Bundle savedInstanceState) {
336         super.onViewCreated(view, savedInstanceState);
337 
338         if (mHavePrefs) {
339             bindPreferences();
340             if (mSelectPreferenceRunnable != null) {
341                 mSelectPreferenceRunnable.run();
342                 mSelectPreferenceRunnable = null;
343             }
344         }
345 
346         mInitDone = true;
347     }
348 
349     @Override
onActivityCreated(Bundle savedInstanceState)350     public void onActivityCreated(Bundle savedInstanceState) {
351         super.onActivityCreated(savedInstanceState);
352 
353         if (savedInstanceState != null) {
354             Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
355             if (container != null) {
356                 final PreferenceScreen preferenceScreen = getPreferenceScreen();
357                 if (preferenceScreen != null) {
358                     preferenceScreen.restoreHierarchyState(container);
359                 }
360             }
361         }
362     }
363 
364     @Override
onStart()365     public void onStart() {
366         super.onStart();
367         mPreferenceManager.setOnPreferenceTreeClickListener(this);
368         mPreferenceManager.setOnDisplayPreferenceDialogListener(this);
369     }
370 
371     @Override
onStop()372     public void onStop() {
373         super.onStop();
374         mPreferenceManager.setOnPreferenceTreeClickListener(null);
375         mPreferenceManager.setOnDisplayPreferenceDialogListener(null);
376     }
377 
378     @Override
onDestroyView()379     public void onDestroyView() {
380         mHandler.removeCallbacks(mRequestFocus);
381         mHandler.removeMessages(MSG_BIND_PREFERENCES);
382         if (mHavePrefs) {
383             unbindPreferences();
384         }
385         mList = null;
386         super.onDestroyView();
387     }
388 
389     @Override
onSaveInstanceState(Bundle outState)390     public void onSaveInstanceState(Bundle outState) {
391         super.onSaveInstanceState(outState);
392 
393         final PreferenceScreen preferenceScreen = getPreferenceScreen();
394         if (preferenceScreen != null) {
395             Bundle container = new Bundle();
396             preferenceScreen.saveHierarchyState(container);
397             outState.putBundle(PREFERENCES_TAG, container);
398         }
399     }
400 
401     /**
402      * Returns the {@link PreferenceManager} used by this fragment.
403      * @return The {@link PreferenceManager}.
404      */
getPreferenceManager()405     public PreferenceManager getPreferenceManager() {
406         return mPreferenceManager;
407     }
408 
409     /**
410      * Sets the root of the preference hierarchy that this fragment is showing.
411      *
412      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
413      */
setPreferenceScreen(PreferenceScreen preferenceScreen)414     public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
415         if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
416             onUnbindPreferences();
417             mHavePrefs = true;
418             if (mInitDone) {
419                 postBindPreferences();
420             }
421         }
422     }
423 
424     /**
425      * Gets the root of the preference hierarchy that this fragment is showing.
426      *
427      * @return The {@link PreferenceScreen} that is the root of the preference
428      *         hierarchy.
429      */
getPreferenceScreen()430     public PreferenceScreen getPreferenceScreen() {
431         return mPreferenceManager.getPreferenceScreen();
432     }
433 
434     /**
435      * Inflates the given XML resource and adds the preference hierarchy to the current
436      * preference hierarchy.
437      *
438      * @param preferencesResId The XML resource ID to inflate.
439      */
addPreferencesFromResource(@mlRes int preferencesResId)440     public void addPreferencesFromResource(@XmlRes int preferencesResId) {
441         requirePreferenceManager();
442 
443         setPreferenceScreen(mPreferenceManager.inflateFromResource(mStyledContext,
444                 preferencesResId, getPreferenceScreen()));
445     }
446 
447     /**
448      * Inflates the given XML resource and replaces the current preference hierarchy (if any) with
449      * the preference hierarchy rooted at {@code key}.
450      *
451      * @param preferencesResId The XML resource ID to inflate.
452      * @param key The preference key of the {@link android.support.v7.preference.PreferenceScreen}
453      *            to use as the root of the preference hierarchy, or null to use the root
454      *            {@link android.support.v7.preference.PreferenceScreen}.
455      */
setPreferencesFromResource(@mlRes int preferencesResId, @Nullable String key)456     public void setPreferencesFromResource(@XmlRes int preferencesResId, @Nullable String key) {
457         requirePreferenceManager();
458 
459         final PreferenceScreen xmlRoot = mPreferenceManager.inflateFromResource(mStyledContext,
460                 preferencesResId, null);
461 
462         final Preference root;
463         if (key != null) {
464             root = xmlRoot.findPreference(key);
465             if (!(root instanceof PreferenceScreen)) {
466                 throw new IllegalArgumentException("Preference object with key " + key
467                         + " is not a PreferenceScreen");
468             }
469         } else {
470             root = xmlRoot;
471         }
472 
473         setPreferenceScreen((PreferenceScreen) root);
474     }
475 
476     /**
477      * {@inheritDoc}
478      */
479     @Override
onPreferenceTreeClick(Preference preference)480     public boolean onPreferenceTreeClick(Preference preference) {
481         if (preference.getFragment() != null) {
482             boolean handled = false;
483             if (getCallbackFragment() instanceof OnPreferenceStartFragmentCallback) {
484                 handled = ((OnPreferenceStartFragmentCallback) getCallbackFragment())
485                         .onPreferenceStartFragment(this, preference);
486             }
487             if (!handled && getActivity() instanceof OnPreferenceStartFragmentCallback){
488                 handled = ((OnPreferenceStartFragmentCallback) getActivity())
489                         .onPreferenceStartFragment(this, preference);
490             }
491             return handled;
492         }
493         return false;
494     }
495 
496     /**
497      * Called by
498      * {@link android.support.v7.preference.PreferenceScreen#onClick()} in order to navigate to a
499      * new screen of preferences. Calls
500      * {@link PreferenceFragment.OnPreferenceStartScreenCallback#onPreferenceStartScreen}
501      * if the target fragment or containing activity implements
502      * {@link PreferenceFragment.OnPreferenceStartScreenCallback}.
503      * @param preferenceScreen The {@link android.support.v7.preference.PreferenceScreen} to
504      *                         navigate to.
505      */
506     @Override
onNavigateToScreen(PreferenceScreen preferenceScreen)507     public void onNavigateToScreen(PreferenceScreen preferenceScreen) {
508         boolean handled = false;
509         if (getCallbackFragment() instanceof OnPreferenceStartScreenCallback) {
510             handled = ((OnPreferenceStartScreenCallback) getCallbackFragment())
511                     .onPreferenceStartScreen(this, preferenceScreen);
512         }
513         if (!handled && getActivity() instanceof OnPreferenceStartScreenCallback) {
514             ((OnPreferenceStartScreenCallback) getActivity())
515                     .onPreferenceStartScreen(this, preferenceScreen);
516         }
517     }
518 
519     /**
520      * Finds a {@link Preference} based on its key.
521      *
522      * @param key The key of the preference to retrieve.
523      * @return The {@link Preference} with the key, or null.
524      * @see android.support.v7.preference.PreferenceGroup#findPreference(CharSequence)
525      */
526     @Override
findPreference(CharSequence key)527     public Preference findPreference(CharSequence key) {
528         if (mPreferenceManager == null) {
529             return null;
530         }
531         return mPreferenceManager.findPreference(key);
532     }
533 
requirePreferenceManager()534     private void requirePreferenceManager() {
535         if (mPreferenceManager == null) {
536             throw new RuntimeException("This should be called after super.onCreate.");
537         }
538     }
539 
postBindPreferences()540     private void postBindPreferences() {
541         if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
542         mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
543     }
544 
bindPreferences()545     private void bindPreferences() {
546         final PreferenceScreen preferenceScreen = getPreferenceScreen();
547         if (preferenceScreen != null) {
548             getListView().setAdapter(onCreateAdapter(preferenceScreen));
549             preferenceScreen.onAttached();
550         }
551         onBindPreferences();
552     }
553 
unbindPreferences()554     private void unbindPreferences() {
555         final PreferenceScreen preferenceScreen = getPreferenceScreen();
556         if (preferenceScreen != null) {
557             preferenceScreen.onDetached();
558         }
559         onUnbindPreferences();
560     }
561 
562     /** @hide */
563     @RestrictTo(LIBRARY_GROUP)
onBindPreferences()564     protected void onBindPreferences() {
565     }
566 
567     /** @hide */
568     @RestrictTo(LIBRARY_GROUP)
onUnbindPreferences()569     protected void onUnbindPreferences() {
570     }
571 
getListView()572     public final RecyclerView getListView() {
573         return mList;
574     }
575 
576     /**
577      * Creates the {@link android.support.v7.widget.RecyclerView} used to display the preferences.
578      * Subclasses may override this to return a customized
579      * {@link android.support.v7.widget.RecyclerView}.
580      * @param inflater The LayoutInflater object that can be used to inflate the
581      *                 {@link android.support.v7.widget.RecyclerView}.
582      * @param parent The parent {@link android.view.View} that the RecyclerView will be attached to.
583      *               This method should not add the view itself, but this can be used to generate
584      *               the LayoutParams of the view.
585      * @param savedInstanceState If non-null, this view is being re-constructed from a previous
586      *                           saved state as given here
587      * @return A new RecyclerView object to be placed into the view hierarchy
588      */
onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState)589     public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
590             Bundle savedInstanceState) {
591         RecyclerView recyclerView = (RecyclerView) inflater
592                 .inflate(android.support.v7.preference.R.layout.preference_recyclerview,
593                         parent, false);
594 
595         recyclerView.setLayoutManager(onCreateLayoutManager());
596         recyclerView.setAccessibilityDelegateCompat(
597                 new PreferenceRecyclerViewAccessibilityDelegate(recyclerView));
598 
599         return recyclerView;
600     }
601 
602     /**
603      * Called from {@link #onCreateRecyclerView} to create the
604      * {@link android.support.v7.widget.RecyclerView.LayoutManager} for the created
605      * {@link android.support.v7.widget.RecyclerView}.
606      * @return A new {@link android.support.v7.widget.RecyclerView.LayoutManager} instance.
607      */
onCreateLayoutManager()608     public RecyclerView.LayoutManager onCreateLayoutManager() {
609         return new LinearLayoutManager(getActivity());
610     }
611 
612     /**
613      * Creates the root adapter.
614      *
615      * @param preferenceScreen Preference screen object to create the adapter for.
616      * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}.
617      */
onCreateAdapter(PreferenceScreen preferenceScreen)618     protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
619         return new PreferenceGroupAdapter(preferenceScreen);
620     }
621 
622     /**
623      * Called when a preference in the tree requests to display a dialog. Subclasses should
624      * override this method to display custom dialogs or to handle dialogs for custom preference
625      * classes.
626      *
627      * @param preference The Preference object requesting the dialog.
628      */
629     @Override
onDisplayPreferenceDialog(Preference preference)630     public void onDisplayPreferenceDialog(Preference preference) {
631 
632         boolean handled = false;
633         if (getCallbackFragment() instanceof OnPreferenceDisplayDialogCallback) {
634             handled = ((OnPreferenceDisplayDialogCallback) getCallbackFragment())
635                     .onPreferenceDisplayDialog(this, preference);
636         }
637         if (!handled && getActivity() instanceof OnPreferenceDisplayDialogCallback) {
638             handled = ((OnPreferenceDisplayDialogCallback) getActivity())
639                     .onPreferenceDisplayDialog(this, preference);
640         }
641 
642         if (handled) {
643             return;
644         }
645 
646         // check if dialog is already showing
647         if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) {
648             return;
649         }
650 
651         final DialogFragment f;
652         if (preference instanceof EditTextPreference) {
653             f = EditTextPreferenceDialogFragment.newInstance(preference.getKey());
654         } else if (preference instanceof ListPreference) {
655             f = ListPreferenceDialogFragment.newInstance(preference.getKey());
656         } else if (preference instanceof MultiSelectListPreference) {
657             f = MultiSelectListPreferenceDialogFragment.newInstance(preference.getKey());
658         } else {
659             throw new IllegalArgumentException("Tried to display dialog for unknown " +
660                     "preference type. Did you forget to override onDisplayPreferenceDialog()?");
661         }
662         f.setTargetFragment(this, 0);
663         f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
664     }
665 
666     /**
667      * Basically a wrapper for getParentFragment which is v17+. Used by the leanback preference lib.
668      * @return Fragment to possibly use as a callback
669      * @hide
670      */
671     @RestrictTo(LIBRARY_GROUP)
getCallbackFragment()672     public Fragment getCallbackFragment() {
673         return null;
674     }
675 
scrollToPreference(final String key)676     public void scrollToPreference(final String key) {
677         scrollToPreferenceInternal(null, key);
678     }
679 
scrollToPreference(final Preference preference)680     public void scrollToPreference(final Preference preference) {
681         scrollToPreferenceInternal(preference, null);
682     }
683 
scrollToPreferenceInternal(final Preference preference, final String key)684     private void scrollToPreferenceInternal(final Preference preference, final String key) {
685         final Runnable r = new Runnable() {
686             @Override
687             public void run() {
688                 final RecyclerView.Adapter adapter = mList.getAdapter();
689                 if (!(adapter instanceof
690                         PreferenceGroup.PreferencePositionCallback)) {
691                     if (adapter != null) {
692                         throw new IllegalStateException("Adapter must implement "
693                                 + "PreferencePositionCallback");
694                     } else {
695                         // Adapter was set to null, so don't scroll I guess?
696                         return;
697                     }
698                 }
699                 final int position;
700                 if (preference != null) {
701                     position = ((PreferenceGroup.PreferencePositionCallback) adapter)
702                             .getPreferenceAdapterPosition(preference);
703                 } else {
704                     position = ((PreferenceGroup.PreferencePositionCallback) adapter)
705                             .getPreferenceAdapterPosition(key);
706                 }
707                 if (position != RecyclerView.NO_POSITION) {
708                     mList.scrollToPosition(position);
709                 } else {
710                     // Item not found, wait for an update and try again
711                     adapter.registerAdapterDataObserver(
712                             new ScrollToPreferenceObserver(adapter, mList, preference, key));
713                 }
714             }
715         };
716         if (mList == null) {
717             mSelectPreferenceRunnable = r;
718         } else {
719             r.run();
720         }
721     }
722 
723     private static class ScrollToPreferenceObserver extends RecyclerView.AdapterDataObserver {
724         private final RecyclerView.Adapter mAdapter;
725         private final RecyclerView mList;
726         private final Preference mPreference;
727         private final String mKey;
728 
ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list, Preference preference, String key)729         public ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list,
730                 Preference preference, String key) {
731             mAdapter = adapter;
732             mList = list;
733             mPreference = preference;
734             mKey = key;
735         }
736 
scrollToPreference()737         private void scrollToPreference() {
738             mAdapter.unregisterAdapterDataObserver(this);
739             final int position;
740             if (mPreference != null) {
741                 position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
742                         .getPreferenceAdapterPosition(mPreference);
743             } else {
744                 position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
745                         .getPreferenceAdapterPosition(mKey);
746             }
747             if (position != RecyclerView.NO_POSITION) {
748                 mList.scrollToPosition(position);
749             }
750         }
751 
752         @Override
onChanged()753         public void onChanged() {
754             scrollToPreference();
755         }
756 
757         @Override
onItemRangeChanged(int positionStart, int itemCount)758         public void onItemRangeChanged(int positionStart, int itemCount) {
759             scrollToPreference();
760         }
761 
762         @Override
onItemRangeChanged(int positionStart, int itemCount, Object payload)763         public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
764             scrollToPreference();
765         }
766 
767         @Override
onItemRangeInserted(int positionStart, int itemCount)768         public void onItemRangeInserted(int positionStart, int itemCount) {
769             scrollToPreference();
770         }
771 
772         @Override
onItemRangeRemoved(int positionStart, int itemCount)773         public void onItemRangeRemoved(int positionStart, int itemCount) {
774             scrollToPreference();
775         }
776 
777         @Override
onItemRangeMoved(int fromPosition, int toPosition, int itemCount)778         public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
779             scrollToPreference();
780         }
781     }
782 
783     private class DividerDecoration extends RecyclerView.ItemDecoration {
784 
785         private Drawable mDivider;
786         private int mDividerHeight;
787         private boolean mAllowDividerAfterLastItem = true;
788 
789         @Override
onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)790         public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
791             if (mDivider == null) {
792                 return;
793             }
794             final int childCount = parent.getChildCount();
795             final int width = parent.getWidth();
796             for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
797                 final View view = parent.getChildAt(childViewIndex);
798                 if (shouldDrawDividerBelow(view, parent)) {
799                     int top = (int) view.getY() + view.getHeight();
800                     mDivider.setBounds(0, top, width, top + mDividerHeight);
801                     mDivider.draw(c);
802                 }
803             }
804         }
805 
806         @Override
getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)807         public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
808                 RecyclerView.State state) {
809             if (shouldDrawDividerBelow(view, parent)) {
810                 outRect.bottom = mDividerHeight;
811             }
812         }
813 
shouldDrawDividerBelow(View view, RecyclerView parent)814         private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
815             final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
816             final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder
817                     && ((PreferenceViewHolder) holder).isDividerAllowedBelow();
818             if (!dividerAllowedBelow) {
819                 return false;
820             }
821             boolean nextAllowed = mAllowDividerAfterLastItem;
822             int index = parent.indexOfChild(view);
823             if (index < parent.getChildCount() - 1) {
824                 final View nextView = parent.getChildAt(index + 1);
825                 final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
826                 nextAllowed = nextHolder instanceof PreferenceViewHolder
827                         && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove();
828             }
829             return nextAllowed;
830         }
831 
setDivider(Drawable divider)832         public void setDivider(Drawable divider) {
833             if (divider != null) {
834                 mDividerHeight = divider.getIntrinsicHeight();
835             } else {
836                 mDividerHeight = 0;
837             }
838             mDivider = divider;
839             mList.invalidateItemDecorations();
840         }
841 
setDividerHeight(int dividerHeight)842         public void setDividerHeight(int dividerHeight) {
843             mDividerHeight = dividerHeight;
844             mList.invalidateItemDecorations();
845         }
846 
setAllowDividerAfterLastItem(boolean allowDividerAfterLastItem)847         public void setAllowDividerAfterLastItem(boolean allowDividerAfterLastItem) {
848             mAllowDividerAfterLastItem = allowDividerAfterLastItem;
849         }
850     }
851 }
852