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.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
    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
    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
    172     public void addPreferencesFromResource(@XmlRes int preferencesResId) {
    173         super.addPreferencesFromResource(preferencesResId);
    174         checkAvailablePrefs(getPreferenceScreen());
    175     }
    176 
    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 
    190     public ViewGroup getButtonBar() {
    191         return mButtonBar;
    192     }
    193 
    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 
    202     public void setPinnedHeaderView(View pinnedHeader) {
    203         mPinnedHeaderFrameLayout.addView(pinnedHeader);
    204         mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE);
    205     }
    206 
    207     @Override
    208     public void onSaveInstanceState(Bundle outState) {
    209         super.onSaveInstanceState(outState);
    210 
    211         outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
    212     }
    213 
    214     @Override
    215     public void onActivityCreated(Bundle savedInstanceState) {
    216         super.onActivityCreated(savedInstanceState);
    217         setHasOptionsMenu(true);
    218     }
    219 
    220     @Override
    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
    232     protected void onBindPreferences() {
    233         registerObserverIfNeeded();
    234     }
    235 
    236     @Override
    237     protected void onUnbindPreferences() {
    238         unregisterObserverIfNeeded();
    239     }
    240 
    241     public void showLoadingWhenEmpty() {
    242         View loading = getView().findViewById(R.id.loading_container);
    243         setEmptyView(loading);
    244     }
    245 
    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 
    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     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 
    336     public void setEmptyView(View v) {
    337         if (mEmptyView != null) {
    338             mEmptyView.setVisibility(View.GONE);
    339         }
    340         mEmptyView = v;
    341         updateEmptyView();
    342     }
    343 
    344     public View getEmptyView() {
    345         return mEmptyView;
    346     }
    347 
    348     /**
    349      * Return a valid ListView position or -1 if none is found
    350      */
    351     private int canUseListViewForHighLighting(String key) {
    352         if (getListView() == null) {
    353             return -1;
    354         }
    355 
    356         RecyclerView listView = getListView();
    357         RecyclerView.Adapter adapter = listView.getAdapter();
    358 
    359         if (adapter != null && adapter instanceof PreferenceGroupAdapter) {
    360             return findListPositionFromKey((PreferenceGroupAdapter) adapter, key);
    361         }
    362 
    363         return -1;
    364     }
    365 
    366     @Override
    367     public RecyclerView.LayoutManager onCreateLayoutManager() {
    368         mLayoutManager = new LinearLayoutManager(getContext());
    369         return mLayoutManager;
    370     }
    371 
    372     @Override
    373     protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) {
    374         mAdapter = new HighlightablePreferenceGroupAdapter(preferenceScreen);
    375         return mAdapter;
    376     }
    377 
    378     protected void setAnimationAllowed(boolean animationAllowed) {
    379         mAnimationAllowed = animationAllowed;
    380     }
    381 
    382     protected void cacheRemoveAllPrefs(PreferenceGroup group) {
    383         mPreferenceCache = new ArrayMap<String, Preference>();
    384         final int N = group.getPreferenceCount();
    385         for (int i = 0; i < N; i++) {
    386             Preference p = group.getPreference(i);
    387             if (TextUtils.isEmpty(p.getKey())) {
    388                 continue;
    389             }
    390             mPreferenceCache.put(p.getKey(), p);
    391         }
    392     }
    393 
    394     protected Preference getCachedPreference(String key) {
    395         return mPreferenceCache != null ? mPreferenceCache.remove(key) : null;
    396     }
    397 
    398     protected void removeCachedPrefs(PreferenceGroup group) {
    399         for (Preference p : mPreferenceCache.values()) {
    400             group.removePreference(p);
    401         }
    402         mPreferenceCache = null;
    403     }
    404 
    405     protected int getCachedCount() {
    406         return mPreferenceCache != null ? mPreferenceCache.size() : 0;
    407     }
    408 
    409     private void highlightPreference(String key) {
    410         final int position = canUseListViewForHighLighting(key);
    411         if (position < 0) {
    412             return;
    413         }
    414 
    415         mPreferenceHighlighted = true;
    416         mLayoutManager.scrollToPosition(position);
    417         mAdapter.highlight(position);
    418     }
    419 
    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 
    432     protected boolean removePreference(String key) {
    433         return removePreference(getPreferenceScreen(), key);
    434     }
    435 
    436     @VisibleForTesting
    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      */
    460     protected int getHelpResource() {
    461         return R.string.help_uri_default;
    462     }
    463 
    464     @Override
    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      */
    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      */
    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      */
    496     protected Object getSystemService(final String name) {
    497         return getActivity().getSystemService(name);
    498     }
    499 
    500     /**
    501      * Returns the PackageManager from the owning Activity.
    502      */
    503     protected PackageManager getPackageManager() {
    504         return getActivity().getPackageManager();
    505     }
    506 
    507     @Override
    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 
    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
    529     public Dialog onCreateDialog(int dialogId) {
    530         return null;
    531     }
    532 
    533     @Override
    534     public int getDialogMetricsCategory(int dialogId) {
    535         return 0;
    536     }
    537 
    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      */
    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      */
    564     protected void setOnDismissListener(DialogInterface.OnDismissListener listener) {
    565         if (mDialogFragment != null) {
    566             mDialogFragment.mOnDismissListener = listener;
    567         }
    568     }
    569 
    570     public void onDialogShowing() {
    571         // override in subclass to attach a dismiss listener, for instance
    572     }
    573 
    574     @Override
    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 
    611         public SettingsDialogFragment() {
    612             /* do nothing */
    613         }
    614 
    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
    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
    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
    647         public void onStart() {
    648             super.onStart();
    649 
    650             if (mParentFragment != null && mParentFragment instanceof SettingsPreferenceFragment) {
    651                 ((SettingsPreferenceFragment) mParentFragment).onDialogShowing();
    652             }
    653         }
    654 
    655         @Override
    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
    682         public void onCancel(DialogInterface dialog) {
    683             super.onCancel(dialog);
    684             if (mOnCancelListener != null) {
    685                 mOnCancelListener.onCancel(dialog);
    686             }
    687         }
    688 
    689         @Override
    690         public void onDismiss(DialogInterface dialog) {
    691             super.onDismiss(dialog);
    692             if (mOnDismissListener != null) {
    693                 mOnDismissListener.onDismiss(dialog);
    694             }
    695         }
    696 
    697         public int getDialogId() {
    698             return mDialogId;
    699         }
    700 
    701         @Override
    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 
    715     protected boolean hasNextButton() {
    716         return ((ButtonBarHandler)getActivity()).hasNextButton();
    717     }
    718 
    719     protected Button getNextButton() {
    720         return ((ButtonBarHandler)getActivity()).getNextButton();
    721     }
    722 
    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 
    733     protected Intent getIntent() {
    734         if (getActivity() == null) {
    735             return null;
    736         }
    737         return getActivity().getIntent();
    738     }
    739 
    740     protected void setResult(int result, Intent intent) {
    741         if (getActivity() == null) {
    742             return;
    743         }
    744         getActivity().setResult(result, intent);
    745     }
    746 
    747     protected void setResult(int result) {
    748         if (getActivity() == null) {
    749             return;
    750         }
    751         getActivity().setResult(result);
    752     }
    753 
    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 
    778         public HighlightablePreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
    779             super(preferenceGroup);
    780         }
    781 
    782         public void highlight(int position) {
    783             mHighlightPosition = position;
    784             initialHighlightedPosition = position;
    785             notifyDataSetChanged();
    786         }
    787 
    788         @Override
    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