1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
19import android.app.Activity;
20import android.app.Dialog;
21import android.app.DialogFragment;
22import android.app.Fragment;
23import android.content.ContentResolver;
24import android.content.Context;
25import android.content.DialogInterface;
26import android.content.Intent;
27import android.content.pm.PackageManager;
28import android.os.Bundle;
29import android.support.annotation.XmlRes;
30import android.support.v7.preference.Preference;
31import android.support.v7.preference.PreferenceGroup;
32import android.support.v7.preference.PreferenceGroupAdapter;
33import android.support.v7.preference.PreferenceScreen;
34import android.support.v7.preference.PreferenceViewHolder;
35import android.support.v7.widget.LinearLayoutManager;
36import android.support.v7.widget.RecyclerView;
37import android.text.TextUtils;
38import android.util.ArrayMap;
39import android.util.Log;
40import android.view.LayoutInflater;
41import android.view.Menu;
42import android.view.MenuInflater;
43import android.view.View;
44import android.view.ViewGroup;
45import android.widget.Button;
46
47import com.android.settings.applications.LayoutPreference;
48import com.android.settings.widget.FloatingActionButton;
49import com.android.settingslib.HelpUtils;
50
51import java.util.UUID;
52
53/**
54 * Base class for Settings fragments, with some helper functions and dialog management.
55 */
56public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment
57        implements DialogCreatable {
58
59    /**
60     * The Help Uri Resource key. This can be passed as an extra argument when creating the
61     * Fragment.
62     **/
63    public static final String HELP_URI_RESOURCE_KEY = "help_uri_resource";
64
65    private static final String TAG = "SettingsPreference";
66
67    private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
68
69    private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
70
71    private SettingsDialogFragment mDialogFragment;
72
73    private String mHelpUri;
74
75    private static final int ORDER_FIRST = -1;
76    private static final int ORDER_LAST = Integer.MAX_VALUE -1;
77
78    // Cache the content resolver for async callbacks
79    private ContentResolver mContentResolver;
80
81    private String mPreferenceKey;
82    private boolean mPreferenceHighlighted = false;
83
84    private RecyclerView.Adapter mCurrentRootAdapter;
85    private boolean mIsDataSetObserverRegistered = false;
86    private RecyclerView.AdapterDataObserver mDataSetObserver =
87            new RecyclerView.AdapterDataObserver() {
88        @Override
89        public void onChanged() {
90            onDataSetChanged();
91        }
92    };
93
94    private ViewGroup mPinnedHeaderFrameLayout;
95    private FloatingActionButton mFloatingActionButton;
96    private ViewGroup mButtonBar;
97
98    private LayoutPreference mHeader;
99
100    private LayoutPreference mFooter;
101    private View mEmptyView;
102    private LinearLayoutManager mLayoutManager;
103    private HighlightablePreferenceGroupAdapter mAdapter;
104    private ArrayMap<String, Preference> mPreferenceCache;
105    private boolean mAnimationAllowed;
106
107    @Override
108    public void onCreate(Bundle icicle) {
109        super.onCreate(icicle);
110
111        if (icicle != null) {
112            mPreferenceHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY);
113        }
114
115        // Prepare help url and enable menu if necessary
116        Bundle arguments = getArguments();
117        int helpResource;
118        if (arguments != null && arguments.containsKey(HELP_URI_RESOURCE_KEY)) {
119            helpResource = arguments.getInt(HELP_URI_RESOURCE_KEY);
120        } else {
121            helpResource = getHelpResource();
122        }
123        if (helpResource != 0) {
124            mHelpUri = getResources().getString(helpResource);
125        }
126    }
127
128    @Override
129    public View onCreateView(LayoutInflater inflater, ViewGroup container,
130            Bundle savedInstanceState) {
131        final View root = super.onCreateView(inflater, container, savedInstanceState);
132        mPinnedHeaderFrameLayout = (ViewGroup) root.findViewById(R.id.pinned_header);
133        mFloatingActionButton = (FloatingActionButton) root.findViewById(R.id.fab);
134        mButtonBar = (ViewGroup) root.findViewById(R.id.button_bar);
135        return root;
136    }
137
138    @Override
139    public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
140    }
141
142    @Override
143    public void addPreferencesFromResource(@XmlRes int preferencesResId) {
144        super.addPreferencesFromResource(preferencesResId);
145        checkAvailablePrefs(getPreferenceScreen());
146    }
147
148    private void checkAvailablePrefs(PreferenceGroup preferenceGroup) {
149        if (preferenceGroup == null) return;
150        for (int i = 0; i < preferenceGroup.getPreferenceCount(); i++) {
151            Preference pref = preferenceGroup.getPreference(i);
152            if (pref instanceof SelfAvailablePreference
153                    && !((SelfAvailablePreference) pref).isAvailable(getContext())) {
154                preferenceGroup.removePreference(pref);
155            } else if (pref instanceof PreferenceGroup) {
156                checkAvailablePrefs((PreferenceGroup) pref);
157            }
158        }
159    }
160
161    public FloatingActionButton getFloatingActionButton() {
162        return mFloatingActionButton;
163    }
164
165    public ViewGroup getButtonBar() {
166        return mButtonBar;
167    }
168
169    public View setPinnedHeaderView(int layoutResId) {
170        final LayoutInflater inflater = getActivity().getLayoutInflater();
171        final View pinnedHeader =
172                inflater.inflate(layoutResId, mPinnedHeaderFrameLayout, false);
173        setPinnedHeaderView(pinnedHeader);
174        return pinnedHeader;
175    }
176
177    public void setPinnedHeaderView(View pinnedHeader) {
178        mPinnedHeaderFrameLayout.addView(pinnedHeader);
179        mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE);
180    }
181
182    @Override
183    public void onSaveInstanceState(Bundle outState) {
184        super.onSaveInstanceState(outState);
185
186        outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
187    }
188
189    @Override
190    public void onActivityCreated(Bundle savedInstanceState) {
191        super.onActivityCreated(savedInstanceState);
192        setHasOptionsMenu(true);
193    }
194
195    @Override
196    public void onResume() {
197        super.onResume();
198
199        final Bundle args = getArguments();
200        if (args != null) {
201            mPreferenceKey = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY);
202            highlightPreferenceIfNeeded();
203        }
204    }
205
206    @Override
207    protected void onBindPreferences() {
208        registerObserverIfNeeded();
209    }
210
211    @Override
212    protected void onUnbindPreferences() {
213        unregisterObserverIfNeeded();
214    }
215
216    public void showLoadingWhenEmpty() {
217        View loading = getView().findViewById(R.id.loading_container);
218        setEmptyView(loading);
219    }
220
221    public void setLoading(boolean loading, boolean animate) {
222        View loading_container = getView().findViewById(R.id.loading_container);
223        Utils.handleLoadingContainer(loading_container, getListView(), !loading, animate);
224    }
225
226    public void registerObserverIfNeeded() {
227        if (!mIsDataSetObserverRegistered) {
228            if (mCurrentRootAdapter != null) {
229                mCurrentRootAdapter.unregisterAdapterDataObserver(mDataSetObserver);
230            }
231            mCurrentRootAdapter = getListView().getAdapter();
232            mCurrentRootAdapter.registerAdapterDataObserver(mDataSetObserver);
233            mIsDataSetObserverRegistered = true;
234            onDataSetChanged();
235        }
236    }
237
238    public void unregisterObserverIfNeeded() {
239        if (mIsDataSetObserverRegistered) {
240            if (mCurrentRootAdapter != null) {
241                mCurrentRootAdapter.unregisterAdapterDataObserver(mDataSetObserver);
242                mCurrentRootAdapter = null;
243            }
244            mIsDataSetObserverRegistered = false;
245        }
246    }
247
248    public void highlightPreferenceIfNeeded() {
249        if (isAdded() && !mPreferenceHighlighted &&!TextUtils.isEmpty(mPreferenceKey)) {
250            highlightPreference(mPreferenceKey);
251        }
252    }
253
254    protected void onDataSetChanged() {
255        highlightPreferenceIfNeeded();
256        updateEmptyView();
257    }
258
259    public LayoutPreference getHeaderView() {
260        return mHeader;
261    }
262
263    public LayoutPreference getFooterView() {
264        return mFooter;
265    }
266
267    protected void setHeaderView(int resource) {
268        mHeader = new LayoutPreference(getPrefContext(), resource);
269        addPreferenceToTop(mHeader);
270    }
271
272    protected void setHeaderView(View view) {
273        mHeader = new LayoutPreference(getPrefContext(), view);
274        addPreferenceToTop(mHeader);
275    }
276
277    private void addPreferenceToTop(LayoutPreference preference) {
278        preference.setOrder(ORDER_FIRST);
279        if (getPreferenceScreen() != null) {
280            getPreferenceScreen().addPreference(preference);
281        }
282    }
283
284    protected void setFooterView(int resource) {
285        setFooterView(resource != 0 ? new LayoutPreference(getPrefContext(), resource) : null);
286    }
287
288    protected void setFooterView(View v) {
289        setFooterView(v != null ? new LayoutPreference(getPrefContext(), v) : null);
290    }
291
292    private void setFooterView(LayoutPreference footer) {
293        if (getPreferenceScreen() != null && mFooter != null) {
294            getPreferenceScreen().removePreference(mFooter);
295        }
296        if (footer != null) {
297            mFooter = footer;
298            mFooter.setOrder(ORDER_LAST);
299            if (getPreferenceScreen() != null) {
300                getPreferenceScreen().addPreference(mFooter);
301            }
302        } else {
303            mFooter = null;
304        }
305    }
306
307    @Override
308    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
309        if (preferenceScreen != null && !preferenceScreen.isAttached()) {
310            // Without ids generated, the RecyclerView won't animate changes to the preferences.
311            preferenceScreen.setShouldUseGeneratedIds(mAnimationAllowed);
312        }
313        super.setPreferenceScreen(preferenceScreen);
314        if (preferenceScreen != null) {
315            if (mHeader != null) {
316                preferenceScreen.addPreference(mHeader);
317            }
318            if (mFooter != null) {
319                preferenceScreen.addPreference(mFooter);
320            }
321        }
322    }
323
324    private void updateEmptyView() {
325        if (mEmptyView == null) return;
326        if (getPreferenceScreen() != null) {
327            boolean show = (getPreferenceScreen().getPreferenceCount()
328                    - (mHeader != null ? 1 : 0)
329                    - (mFooter != null ? 1 : 0)) <= 0;
330            mEmptyView.setVisibility(show ? View.VISIBLE : View.GONE);
331        } else {
332            mEmptyView.setVisibility(View.VISIBLE);
333        }
334    }
335
336    public void setEmptyView(View v) {
337        if (mEmptyView != null) {
338            mEmptyView.setVisibility(View.GONE);
339        }
340        mEmptyView = v;
341        updateEmptyView();
342    }
343
344    public View getEmptyView() {
345        return mEmptyView;
346    }
347
348    /**
349     * Return a valid ListView position or -1 if none is found
350     */
351    private int canUseListViewForHighLighting(String key) {
352        if (getListView() == null) {
353            return -1;
354        }
355
356        RecyclerView listView = getListView();
357        RecyclerView.Adapter adapter = listView.getAdapter();
358
359        if (adapter != null && adapter instanceof PreferenceGroupAdapter) {
360            return findListPositionFromKey((PreferenceGroupAdapter) adapter, key);
361        }
362
363        return -1;
364    }
365
366    @Override
367    public RecyclerView.LayoutManager onCreateLayoutManager() {
368        mLayoutManager = new LinearLayoutManager(getContext());
369        return mLayoutManager;
370    }
371
372    @Override
373    protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
374        mAdapter = new HighlightablePreferenceGroupAdapter(preferenceScreen);
375        return mAdapter;
376    }
377
378    protected void setAnimationAllowed(boolean animationAllowed) {
379        mAnimationAllowed = animationAllowed;
380    }
381
382    protected void cacheRemoveAllPrefs(PreferenceGroup group) {
383        mPreferenceCache = new ArrayMap<String, Preference>();
384        final int N = group.getPreferenceCount();
385        for (int i = 0; i < N; i++) {
386            Preference p = group.getPreference(i);
387            if (TextUtils.isEmpty(p.getKey())) {
388                continue;
389            }
390            mPreferenceCache.put(p.getKey(), p);
391        }
392    }
393
394    protected Preference getCachedPreference(String key) {
395        return mPreferenceCache != null ? mPreferenceCache.remove(key) : null;
396    }
397
398    protected void removeCachedPrefs(PreferenceGroup group) {
399        for (Preference p : mPreferenceCache.values()) {
400            group.removePreference(p);
401        }
402        mPreferenceCache = null;
403    }
404
405    protected int getCachedCount() {
406        return mPreferenceCache != null ? mPreferenceCache.size() : 0;
407    }
408
409    private void highlightPreference(String key) {
410        final int position = canUseListViewForHighLighting(key);
411        if (position >= 0) {
412            mPreferenceHighlighted = true;
413            mLayoutManager.scrollToPosition(position);
414
415            getView().postDelayed(new Runnable() {
416                @Override
417                public void run() {
418                    mAdapter.highlight(position);
419                }
420            }, DELAY_HIGHLIGHT_DURATION_MILLIS);
421        }
422    }
423
424    private int findListPositionFromKey(PreferenceGroupAdapter adapter, String key) {
425        final int count = adapter.getItemCount();
426        for (int n = 0; n < count; n++) {
427            final Preference preference = adapter.getItem(n);
428            final String preferenceKey = preference.getKey();
429            if (preferenceKey != null && preferenceKey.equals(key)) {
430                return n;
431            }
432        }
433        return -1;
434    }
435
436    protected void removePreference(String key) {
437        Preference pref = findPreference(key);
438        if (pref != null) {
439            getPreferenceScreen().removePreference(pref);
440        }
441    }
442
443    /**
444     * Override this if you want to show a help item in the menu, by returning the resource id.
445     * @return the resource id for the help url
446     */
447    protected int getHelpResource() {
448        return R.string.help_uri_default;
449    }
450
451    @Override
452    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
453        if (mHelpUri != null && getActivity() != null) {
454            HelpUtils.prepareHelpMenuItem(getActivity(), menu, mHelpUri, getClass().getName());
455        }
456    }
457
458    /*
459     * The name is intentionally made different from Activity#finish(), so that
460     * users won't misunderstand its meaning.
461     */
462    public final void finishFragment() {
463        getActivity().onBackPressed();
464    }
465
466    // Some helpers for functions used by the settings fragments when they were activities
467
468    /**
469     * Returns the ContentResolver from the owning Activity.
470     */
471    protected ContentResolver getContentResolver() {
472        Context context = getActivity();
473        if (context != null) {
474            mContentResolver = context.getContentResolver();
475        }
476        return mContentResolver;
477    }
478
479    /**
480     * Returns the specified system service from the owning Activity.
481     */
482    protected Object getSystemService(final String name) {
483        return getActivity().getSystemService(name);
484    }
485
486    /**
487     * Returns the PackageManager from the owning Activity.
488     */
489    protected PackageManager getPackageManager() {
490        return getActivity().getPackageManager();
491    }
492
493    @Override
494    public void onDetach() {
495        if (isRemoving()) {
496            if (mDialogFragment != null) {
497                mDialogFragment.dismiss();
498                mDialogFragment = null;
499            }
500        }
501        super.onDetach();
502    }
503
504    // Dialog management
505
506    protected void showDialog(int dialogId) {
507        if (mDialogFragment != null) {
508            Log.e(TAG, "Old dialog fragment not null!");
509        }
510        mDialogFragment = new SettingsDialogFragment(this, dialogId);
511        mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId));
512    }
513
514    public Dialog onCreateDialog(int dialogId) {
515        return null;
516    }
517
518    protected void removeDialog(int dialogId) {
519        // mDialogFragment may not be visible yet in parent fragment's onResume().
520        // To be able to dismiss dialog at that time, don't check
521        // mDialogFragment.isVisible().
522        if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) {
523            mDialogFragment.dismissAllowingStateLoss();
524        }
525        mDialogFragment = null;
526    }
527
528    /**
529     * Sets the OnCancelListener of the dialog shown. This method can only be
530     * called after showDialog(int) and before removeDialog(int). The method
531     * does nothing otherwise.
532     */
533    protected void setOnCancelListener(DialogInterface.OnCancelListener listener) {
534        if (mDialogFragment != null) {
535            mDialogFragment.mOnCancelListener = listener;
536        }
537    }
538
539    /**
540     * Sets the OnDismissListener of the dialog shown. This method can only be
541     * called after showDialog(int) and before removeDialog(int). The method
542     * does nothing otherwise.
543     */
544    protected void setOnDismissListener(DialogInterface.OnDismissListener listener) {
545        if (mDialogFragment != null) {
546            mDialogFragment.mOnDismissListener = listener;
547        }
548    }
549
550    public void onDialogShowing() {
551        // override in subclass to attach a dismiss listener, for instance
552    }
553
554    @Override
555    public void onDisplayPreferenceDialog(Preference preference) {
556        if (preference.getKey() == null) {
557            // Auto-key preferences that don't have a key, so the dialog can find them.
558            preference.setKey(UUID.randomUUID().toString());
559        }
560        DialogFragment f = null;
561        if (preference instanceof RestrictedListPreference) {
562            f = RestrictedListPreference.RestrictedListPreferenceDialogFragment
563                    .newInstance(preference.getKey());
564        } else if (preference instanceof CustomListPreference) {
565            f = CustomListPreference.CustomListPreferenceDialogFragment
566                    .newInstance(preference.getKey());
567        } else if (preference instanceof CustomDialogPreference) {
568            f = CustomDialogPreference.CustomPreferenceDialogFragment
569                    .newInstance(preference.getKey());
570        } else if (preference instanceof CustomEditTextPreference) {
571            f = CustomEditTextPreference.CustomPreferenceDialogFragment
572                    .newInstance(preference.getKey());
573        } else {
574            super.onDisplayPreferenceDialog(preference);
575            return;
576        }
577        f.setTargetFragment(this, 0);
578        f.show(getFragmentManager(), "dialog_preference");
579        onDialogShowing();
580    }
581
582    public static class SettingsDialogFragment extends DialogFragment {
583        private static final String KEY_DIALOG_ID = "key_dialog_id";
584        private static final String KEY_PARENT_FRAGMENT_ID = "key_parent_fragment_id";
585
586        private int mDialogId;
587
588        private Fragment mParentFragment;
589
590        private DialogInterface.OnCancelListener mOnCancelListener;
591        private DialogInterface.OnDismissListener mOnDismissListener;
592
593        public SettingsDialogFragment() {
594            /* do nothing */
595        }
596
597        public SettingsDialogFragment(DialogCreatable fragment, int dialogId) {
598            mDialogId = dialogId;
599            if (!(fragment instanceof Fragment)) {
600                throw new IllegalArgumentException("fragment argument must be an instance of "
601                        + Fragment.class.getName());
602            }
603            mParentFragment = (Fragment) fragment;
604        }
605
606        @Override
607        public void onSaveInstanceState(Bundle outState) {
608            super.onSaveInstanceState(outState);
609            if (mParentFragment != null) {
610                outState.putInt(KEY_DIALOG_ID, mDialogId);
611                outState.putInt(KEY_PARENT_FRAGMENT_ID, mParentFragment.getId());
612            }
613        }
614
615        @Override
616        public void onStart() {
617            super.onStart();
618
619            if (mParentFragment != null && mParentFragment instanceof SettingsPreferenceFragment) {
620                ((SettingsPreferenceFragment) mParentFragment).onDialogShowing();
621            }
622        }
623
624        @Override
625        public Dialog onCreateDialog(Bundle savedInstanceState) {
626            if (savedInstanceState != null) {
627                mDialogId = savedInstanceState.getInt(KEY_DIALOG_ID, 0);
628                mParentFragment = getParentFragment();
629                int mParentFragmentId = savedInstanceState.getInt(KEY_PARENT_FRAGMENT_ID, -1);
630                if (mParentFragment == null) {
631                    mParentFragment = getFragmentManager().findFragmentById(mParentFragmentId);
632                }
633                if (!(mParentFragment instanceof DialogCreatable)) {
634                    throw new IllegalArgumentException(
635                            (mParentFragment != null
636                                    ? mParentFragment.getClass().getName()
637                                    : mParentFragmentId)
638                                    + " must implement "
639                                    + DialogCreatable.class.getName());
640                }
641                // This dialog fragment could be created from non-SettingsPreferenceFragment
642                if (mParentFragment instanceof SettingsPreferenceFragment) {
643                    // restore mDialogFragment in mParentFragment
644                    ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = this;
645                }
646            }
647            return ((DialogCreatable) mParentFragment).onCreateDialog(mDialogId);
648        }
649
650        @Override
651        public void onCancel(DialogInterface dialog) {
652            super.onCancel(dialog);
653            if (mOnCancelListener != null) {
654                mOnCancelListener.onCancel(dialog);
655            }
656        }
657
658        @Override
659        public void onDismiss(DialogInterface dialog) {
660            super.onDismiss(dialog);
661            if (mOnDismissListener != null) {
662                mOnDismissListener.onDismiss(dialog);
663            }
664        }
665
666        public int getDialogId() {
667            return mDialogId;
668        }
669
670        @Override
671        public void onDetach() {
672            super.onDetach();
673
674            // This dialog fragment could be created from non-SettingsPreferenceFragment
675            if (mParentFragment instanceof SettingsPreferenceFragment) {
676                // in case the dialog is not explicitly removed by removeDialog()
677                if (((SettingsPreferenceFragment) mParentFragment).mDialogFragment == this) {
678                    ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = null;
679                }
680            }
681        }
682    }
683
684    protected boolean hasNextButton() {
685        return ((ButtonBarHandler)getActivity()).hasNextButton();
686    }
687
688    protected Button getNextButton() {
689        return ((ButtonBarHandler)getActivity()).getNextButton();
690    }
691
692    public void finish() {
693        Activity activity = getActivity();
694        if (activity == null) return;
695        if (getFragmentManager().getBackStackEntryCount() > 0) {
696            getFragmentManager().popBackStack();
697        } else {
698            activity.finish();
699        }
700    }
701
702    protected Intent getIntent() {
703        if (getActivity() == null) {
704            return null;
705        }
706        return getActivity().getIntent();
707    }
708
709    protected void setResult(int result, Intent intent) {
710        if (getActivity() == null) {
711            return;
712        }
713        getActivity().setResult(result, intent);
714    }
715
716    protected void setResult(int result) {
717        if (getActivity() == null) {
718            return;
719        }
720        getActivity().setResult(result);
721    }
722
723    protected final Context getPrefContext() {
724        return getPreferenceManager().getContext();
725    }
726
727    public boolean startFragment(Fragment caller, String fragmentClass, int titleRes,
728            int requestCode, Bundle extras) {
729        final Activity activity = getActivity();
730        if (activity instanceof SettingsActivity) {
731            SettingsActivity sa = (SettingsActivity) activity;
732            sa.startPreferencePanel(fragmentClass, extras, titleRes, null, caller, requestCode);
733            return true;
734        } else {
735            Log.w(TAG,
736                    "Parent isn't SettingsActivity nor PreferenceActivity, thus there's no way to "
737                    + "launch the given Fragment (name: " + fragmentClass
738                    + ", requestCode: " + requestCode + ")");
739            return false;
740        }
741    }
742
743    public static class HighlightablePreferenceGroupAdapter extends PreferenceGroupAdapter {
744
745        private int mHighlightPosition = -1;
746
747        public HighlightablePreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
748            super(preferenceGroup);
749        }
750
751        public void highlight(int position) {
752            mHighlightPosition = position;
753            notifyDataSetChanged();
754        }
755
756        @Override
757        public void onBindViewHolder(PreferenceViewHolder holder, int position) {
758            super.onBindViewHolder(holder, position);
759            if (position == mHighlightPosition) {
760                View v = holder.itemView;
761                if (v.getBackground() != null) {
762                    final int centerX = v.getWidth() / 2;
763                    final int centerY = v.getHeight() / 2;
764                    v.getBackground().setHotspot(centerX, centerY);
765                }
766                v.setPressed(true);
767                v.setPressed(false);
768                mHighlightPosition = -1;
769            }
770        }
771    }
772}
773