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.pm.PackageManager;
     27 import android.database.DataSetObserver;
     28 import android.graphics.drawable.Drawable;
     29 import android.os.Bundle;
     30 import android.preference.Preference;
     31 import android.preference.PreferenceActivity;
     32 import android.preference.PreferenceFragment;
     33 import android.preference.PreferenceGroupAdapter;
     34 import android.text.TextUtils;
     35 import android.util.Log;
     36 import android.view.LayoutInflater;
     37 import android.view.Menu;
     38 import android.view.MenuInflater;
     39 import android.view.MenuItem;
     40 import android.view.View;
     41 import android.view.ViewGroup;
     42 import android.widget.Button;
     43 import android.widget.ListAdapter;
     44 import android.widget.ListView;
     45 
     46 /**
     47  * Base class for Settings fragments, with some helper functions and dialog management.
     48  */
     49 public class SettingsPreferenceFragment extends PreferenceFragment implements DialogCreatable {
     50 
     51     private static final String TAG = "SettingsPreferenceFragment";
     52 
     53     private static final int MENU_HELP = Menu.FIRST + 100;
     54     private static final int DELAY_HIGHLIGHT_DURATION_MILLIS = 600;
     55 
     56     private static final String SAVE_HIGHLIGHTED_KEY = "android:preference_highlighted";
     57 
     58     private SettingsDialogFragment mDialogFragment;
     59 
     60     private String mHelpUrl;
     61 
     62     // Cache the content resolver for async callbacks
     63     private ContentResolver mContentResolver;
     64 
     65     private String mPreferenceKey;
     66     private boolean mPreferenceHighlighted = false;
     67     private Drawable mHighlightDrawable;
     68 
     69     private ListAdapter mCurrentRootAdapter;
     70     private boolean mIsDataSetObserverRegistered = false;
     71     private DataSetObserver mDataSetObserver = new DataSetObserver() {
     72         @Override
     73         public void onChanged() {
     74             highlightPreferenceIfNeeded();
     75         }
     76 
     77         @Override
     78         public void onInvalidated() {
     79             highlightPreferenceIfNeeded();
     80         }
     81     };
     82 
     83     private ViewGroup mPinnedHeaderFrameLayout;
     84 
     85     @Override
     86     public void onCreate(Bundle icicle) {
     87         super.onCreate(icicle);
     88 
     89         if (icicle != null) {
     90             mPreferenceHighlighted = icicle.getBoolean(SAVE_HIGHLIGHTED_KEY);
     91         }
     92 
     93         // Prepare help url and enable menu if necessary
     94         int helpResource = getHelpResource();
     95         if (helpResource != 0) {
     96             mHelpUrl = getResources().getString(helpResource);
     97         }
     98     }
     99 
    100     @Override
    101     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    102             Bundle savedInstanceState) {
    103         final View root = super.onCreateView(inflater, container, savedInstanceState);
    104         mPinnedHeaderFrameLayout = (ViewGroup) root.findViewById(R.id.pinned_header);
    105         return root;
    106     }
    107 
    108     public void setPinnedHeaderView(View pinnedHeader) {
    109         mPinnedHeaderFrameLayout.addView(pinnedHeader);
    110         mPinnedHeaderFrameLayout.setVisibility(View.VISIBLE);
    111     }
    112 
    113     public void clearPinnedHeaderView() {
    114         mPinnedHeaderFrameLayout.removeAllViews();
    115         mPinnedHeaderFrameLayout.setVisibility(View.GONE);
    116     }
    117 
    118     @Override
    119     public void onSaveInstanceState(Bundle outState) {
    120         super.onSaveInstanceState(outState);
    121 
    122         outState.putBoolean(SAVE_HIGHLIGHTED_KEY, mPreferenceHighlighted);
    123     }
    124 
    125     @Override
    126     public void onActivityCreated(Bundle savedInstanceState) {
    127         super.onActivityCreated(savedInstanceState);
    128         if (!TextUtils.isEmpty(mHelpUrl)) {
    129             setHasOptionsMenu(true);
    130         }
    131     }
    132 
    133     @Override
    134     public void onResume() {
    135         super.onResume();
    136 
    137         final Bundle args = getArguments();
    138         if (args != null) {
    139             mPreferenceKey = args.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY);
    140             highlightPreferenceIfNeeded();
    141         }
    142     }
    143 
    144     @Override
    145     protected void onBindPreferences() {
    146         registerObserverIfNeeded();
    147     }
    148 
    149     @Override
    150     protected void onUnbindPreferences() {
    151         unregisterObserverIfNeeded();
    152     }
    153 
    154     @Override
    155     public void onStop() {
    156         super.onStop();
    157 
    158         unregisterObserverIfNeeded();
    159     }
    160 
    161     public void registerObserverIfNeeded() {
    162         if (!mIsDataSetObserverRegistered) {
    163             if (mCurrentRootAdapter != null) {
    164                 mCurrentRootAdapter.unregisterDataSetObserver(mDataSetObserver);
    165             }
    166             mCurrentRootAdapter = getPreferenceScreen().getRootAdapter();
    167             mCurrentRootAdapter.registerDataSetObserver(mDataSetObserver);
    168             mIsDataSetObserverRegistered = true;
    169         }
    170     }
    171 
    172     public void unregisterObserverIfNeeded() {
    173         if (mIsDataSetObserverRegistered) {
    174             if (mCurrentRootAdapter != null) {
    175                 mCurrentRootAdapter.unregisterDataSetObserver(mDataSetObserver);
    176                 mCurrentRootAdapter = null;
    177             }
    178             mIsDataSetObserverRegistered = false;
    179         }
    180     }
    181 
    182     public void highlightPreferenceIfNeeded() {
    183         if (isAdded() && !mPreferenceHighlighted &&!TextUtils.isEmpty(mPreferenceKey)) {
    184             highlightPreference(mPreferenceKey);
    185         }
    186     }
    187 
    188     private Drawable getHighlightDrawable() {
    189         if (mHighlightDrawable == null) {
    190             mHighlightDrawable = getActivity().getDrawable(R.drawable.preference_highlight);
    191         }
    192         return mHighlightDrawable;
    193     }
    194 
    195     /**
    196      * Return a valid ListView position or -1 if none is found
    197      */
    198     private int canUseListViewForHighLighting(String key) {
    199         if (!hasListView()) {
    200             return -1;
    201         }
    202 
    203         ListView listView = getListView();
    204         ListAdapter adapter = listView.getAdapter();
    205 
    206         if (adapter != null && adapter instanceof PreferenceGroupAdapter) {
    207             return findListPositionFromKey(adapter, key);
    208         }
    209 
    210         return -1;
    211     }
    212 
    213     private void highlightPreference(String key) {
    214         final Drawable highlight = getHighlightDrawable();
    215 
    216         final int position = canUseListViewForHighLighting(key);
    217         if (position >= 0) {
    218             mPreferenceHighlighted = true;
    219 
    220             final ListView listView = getListView();
    221             final ListAdapter adapter = listView.getAdapter();
    222 
    223             ((PreferenceGroupAdapter) adapter).setHighlightedDrawable(highlight);
    224             ((PreferenceGroupAdapter) adapter).setHighlighted(position);
    225 
    226             listView.post(new Runnable() {
    227                 @Override
    228                 public void run() {
    229                     listView.setSelection(position);
    230                     listView.postDelayed(new Runnable() {
    231                         @Override
    232                         public void run() {
    233                             final int index = position - listView.getFirstVisiblePosition();
    234                             if (index >= 0 && index < listView.getChildCount()) {
    235                                 final View v = listView.getChildAt(index);
    236                                 final int centerX = v.getWidth() / 2;
    237                                 final int centerY = v.getHeight() / 2;
    238                                 highlight.setHotspot(centerX, centerY);
    239                                 v.setPressed(true);
    240                                 v.setPressed(false);
    241                             }
    242                         }
    243                     }, DELAY_HIGHLIGHT_DURATION_MILLIS);
    244                 }
    245             });
    246         }
    247     }
    248 
    249     private int findListPositionFromKey(ListAdapter adapter, String key) {
    250         final int count = adapter.getCount();
    251         for (int n = 0; n < count; n++) {
    252             final Object item = adapter.getItem(n);
    253             if (item instanceof Preference) {
    254                 Preference preference = (Preference) item;
    255                 final String preferenceKey = preference.getKey();
    256                 if (preferenceKey != null && preferenceKey.equals(key)) {
    257                     return n;
    258                 }
    259             }
    260         }
    261         return -1;
    262     }
    263 
    264     protected void removePreference(String key) {
    265         Preference pref = findPreference(key);
    266         if (pref != null) {
    267             getPreferenceScreen().removePreference(pref);
    268         }
    269     }
    270 
    271     /**
    272      * Override this if you want to show a help item in the menu, by returning the resource id.
    273      * @return the resource id for the help url
    274      */
    275     protected int getHelpResource() {
    276         return 0;
    277     }
    278 
    279     @Override
    280     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    281         if (mHelpUrl != null && getActivity() != null) {
    282             MenuItem helpItem = menu.add(0, MENU_HELP, 0, R.string.help_label);
    283             HelpUtils.prepareHelpMenuItem(getActivity(), helpItem, mHelpUrl);
    284         }
    285     }
    286 
    287     /*
    288      * The name is intentionally made different from Activity#finish(), so that
    289      * users won't misunderstand its meaning.
    290      */
    291     public final void finishFragment() {
    292         getActivity().onBackPressed();
    293     }
    294 
    295     // Some helpers for functions used by the settings fragments when they were activities
    296 
    297     /**
    298      * Returns the ContentResolver from the owning Activity.
    299      */
    300     protected ContentResolver getContentResolver() {
    301         Context context = getActivity();
    302         if (context != null) {
    303             mContentResolver = context.getContentResolver();
    304         }
    305         return mContentResolver;
    306     }
    307 
    308     /**
    309      * Returns the specified system service from the owning Activity.
    310      */
    311     protected Object getSystemService(final String name) {
    312         return getActivity().getSystemService(name);
    313     }
    314 
    315     /**
    316      * Returns the PackageManager from the owning Activity.
    317      */
    318     protected PackageManager getPackageManager() {
    319         return getActivity().getPackageManager();
    320     }
    321 
    322     @Override
    323     public void onDetach() {
    324         if (isRemoving()) {
    325             if (mDialogFragment != null) {
    326                 mDialogFragment.dismiss();
    327                 mDialogFragment = null;
    328             }
    329         }
    330         super.onDetach();
    331     }
    332 
    333     // Dialog management
    334 
    335     protected void showDialog(int dialogId) {
    336         if (mDialogFragment != null) {
    337             Log.e(TAG, "Old dialog fragment not null!");
    338         }
    339         mDialogFragment = new SettingsDialogFragment(this, dialogId);
    340         mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId));
    341     }
    342 
    343     public Dialog onCreateDialog(int dialogId) {
    344         return null;
    345     }
    346 
    347     protected void removeDialog(int dialogId) {
    348         // mDialogFragment may not be visible yet in parent fragment's onResume().
    349         // To be able to dismiss dialog at that time, don't check
    350         // mDialogFragment.isVisible().
    351         if (mDialogFragment != null && mDialogFragment.getDialogId() == dialogId) {
    352             mDialogFragment.dismiss();
    353         }
    354         mDialogFragment = null;
    355     }
    356 
    357     /**
    358      * Sets the OnCancelListener of the dialog shown. This method can only be
    359      * called after showDialog(int) and before removeDialog(int). The method
    360      * does nothing otherwise.
    361      */
    362     protected void setOnCancelListener(DialogInterface.OnCancelListener listener) {
    363         if (mDialogFragment != null) {
    364             mDialogFragment.mOnCancelListener = listener;
    365         }
    366     }
    367 
    368     /**
    369      * Sets the OnDismissListener of the dialog shown. This method can only be
    370      * called after showDialog(int) and before removeDialog(int). The method
    371      * does nothing otherwise.
    372      */
    373     protected void setOnDismissListener(DialogInterface.OnDismissListener listener) {
    374         if (mDialogFragment != null) {
    375             mDialogFragment.mOnDismissListener = listener;
    376         }
    377     }
    378 
    379     public void onDialogShowing() {
    380         // override in subclass to attach a dismiss listener, for instance
    381     }
    382 
    383     public static class SettingsDialogFragment extends DialogFragment {
    384         private static final String KEY_DIALOG_ID = "key_dialog_id";
    385         private static final String KEY_PARENT_FRAGMENT_ID = "key_parent_fragment_id";
    386 
    387         private int mDialogId;
    388 
    389         private Fragment mParentFragment;
    390 
    391         private DialogInterface.OnCancelListener mOnCancelListener;
    392         private DialogInterface.OnDismissListener mOnDismissListener;
    393 
    394         public SettingsDialogFragment() {
    395             /* do nothing */
    396         }
    397 
    398         public SettingsDialogFragment(DialogCreatable fragment, int dialogId) {
    399             mDialogId = dialogId;
    400             if (!(fragment instanceof Fragment)) {
    401                 throw new IllegalArgumentException("fragment argument must be an instance of "
    402                         + Fragment.class.getName());
    403             }
    404             mParentFragment = (Fragment) fragment;
    405         }
    406 
    407         @Override
    408         public void onSaveInstanceState(Bundle outState) {
    409             super.onSaveInstanceState(outState);
    410             if (mParentFragment != null) {
    411                 outState.putInt(KEY_DIALOG_ID, mDialogId);
    412                 outState.putInt(KEY_PARENT_FRAGMENT_ID, mParentFragment.getId());
    413             }
    414         }
    415 
    416         @Override
    417         public void onStart() {
    418             super.onStart();
    419 
    420             if (mParentFragment != null && mParentFragment instanceof SettingsPreferenceFragment) {
    421                 ((SettingsPreferenceFragment) mParentFragment).onDialogShowing();
    422             }
    423         }
    424 
    425         @Override
    426         public Dialog onCreateDialog(Bundle savedInstanceState) {
    427             if (savedInstanceState != null) {
    428                 mDialogId = savedInstanceState.getInt(KEY_DIALOG_ID, 0);
    429                 mParentFragment = getParentFragment();
    430                 int mParentFragmentId = savedInstanceState.getInt(KEY_PARENT_FRAGMENT_ID, -1);
    431                 if (mParentFragment == null) {
    432                     mParentFragment = getFragmentManager().findFragmentById(mParentFragmentId);
    433                 }
    434                 if (!(mParentFragment instanceof DialogCreatable)) {
    435                     throw new IllegalArgumentException(
    436                             (mParentFragment != null
    437                                     ? mParentFragment.getClass().getName()
    438                                     : mParentFragmentId)
    439                                     + " must implement "
    440                                     + DialogCreatable.class.getName());
    441                 }
    442                 // This dialog fragment could be created from non-SettingsPreferenceFragment
    443                 if (mParentFragment instanceof SettingsPreferenceFragment) {
    444                     // restore mDialogFragment in mParentFragment
    445                     ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = this;
    446                 }
    447             }
    448             return ((DialogCreatable) mParentFragment).onCreateDialog(mDialogId);
    449         }
    450 
    451         @Override
    452         public void onCancel(DialogInterface dialog) {
    453             super.onCancel(dialog);
    454             if (mOnCancelListener != null) {
    455                 mOnCancelListener.onCancel(dialog);
    456             }
    457         }
    458 
    459         @Override
    460         public void onDismiss(DialogInterface dialog) {
    461             super.onDismiss(dialog);
    462             if (mOnDismissListener != null) {
    463                 mOnDismissListener.onDismiss(dialog);
    464             }
    465         }
    466 
    467         public int getDialogId() {
    468             return mDialogId;
    469         }
    470 
    471         @Override
    472         public void onDetach() {
    473             super.onDetach();
    474 
    475             // This dialog fragment could be created from non-SettingsPreferenceFragment
    476             if (mParentFragment instanceof SettingsPreferenceFragment) {
    477                 // in case the dialog is not explicitly removed by removeDialog()
    478                 if (((SettingsPreferenceFragment) mParentFragment).mDialogFragment == this) {
    479                     ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = null;
    480                 }
    481             }
    482         }
    483     }
    484 
    485     protected boolean hasNextButton() {
    486         return ((ButtonBarHandler)getActivity()).hasNextButton();
    487     }
    488 
    489     protected Button getNextButton() {
    490         return ((ButtonBarHandler)getActivity()).getNextButton();
    491     }
    492 
    493     public void finish() {
    494         getActivity().onBackPressed();
    495     }
    496 
    497     public boolean startFragment(Fragment caller, String fragmentClass, int titleRes,
    498             int requestCode, Bundle extras) {
    499         final Activity activity = getActivity();
    500         if (activity instanceof SettingsActivity) {
    501             SettingsActivity sa = (SettingsActivity) activity;
    502             sa.startPreferencePanel(fragmentClass, extras, titleRes, null, caller, requestCode);
    503             return true;
    504         } else if (activity instanceof PreferenceActivity) {
    505             PreferenceActivity sa = (PreferenceActivity) activity;
    506             sa.startPreferencePanel(fragmentClass, extras, titleRes, null, caller, requestCode);
    507             return true;
    508         } else {
    509             Log.w(TAG,
    510                     "Parent isn't SettingsActivity nor PreferenceActivity, thus there's no way to "
    511                     + "launch the given Fragment (name: " + fragmentClass
    512                     + ", requestCode: " + requestCode + ")");
    513             return false;
    514         }
    515     }
    516 }
    517