OSDN Git Service

Merge "Fix search indexing for encryption_and_credential page" into oc-dr1-dev
[android-x86/packages-apps-Settings.git] / src / com / android / settings / SettingsPreferenceFragment.java
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
17 package com.android.settings;
18
19 import android.app.Activity;
20 import android.app.Dialog;
21 import android.app.DialogFragment;
22 import android.app.Fragment;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.pm.PackageManager;
28 import android.os.Bundle;
29 import android.support.annotation.VisibleForTesting;
30 import android.support.annotation.XmlRes;
31 import android.support.v7.preference.Preference;
32 import android.support.v7.preference.PreferenceGroup;
33 import android.support.v7.preference.PreferenceGroupAdapter;
34 import android.support.v7.preference.PreferenceScreen;
35 import android.support.v7.preference.PreferenceViewHolder;
36 import android.support.v7.widget.LinearLayoutManager;
37 import android.support.v7.widget.RecyclerView;
38 import android.text.TextUtils;
39 import android.util.ArrayMap;
40 import android.util.Log;
41 import android.view.LayoutInflater;
42 import android.view.Menu;
43 import android.view.MenuInflater;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.widget.Button;
47
48 import com.android.settings.applications.LayoutPreference;
49 import com.android.settings.core.InstrumentedPreferenceFragment;
50 import com.android.settings.core.instrumentation.Instrumentable;
51 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
52 import com.android.settings.widget.LoadingViewController;
53 import com.android.settingslib.CustomDialogPreference;
54 import com.android.settingslib.CustomEditTextPreference;
55 import com.android.settingslib.HelpUtils;
56 import com.android.settingslib.widget.FooterPreferenceMixin;
57
58 import java.util.UUID;
59
60 /**
61  * Base class for Settings fragments, with some helper functions and dialog management.
62  */
63 public 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 }