Home | History | Annotate | Download | only in settings
      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 }
    810