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