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