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