[go: nahoru, domu]

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