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