Home | History | Annotate | Download | only in preference
      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 android.preference;
     18 
     19 import android.app.Activity;
     20 import android.app.Fragment;
     21 import android.content.Intent;
     22 import android.content.SharedPreferences;
     23 import android.os.Bundle;
     24 import android.os.Handler;
     25 import android.os.Message;
     26 import android.view.KeyEvent;
     27 import android.view.LayoutInflater;
     28 import android.view.View;
     29 import android.view.ViewGroup;
     30 import android.view.View.OnKeyListener;
     31 import android.widget.ListView;
     32 
     33 /**
     34  * Shows a hierarchy of {@link Preference} objects as
     35  * lists. These preferences will
     36  * automatically save to {@link SharedPreferences} as the user interacts with
     37  * them. To retrieve an instance of {@link SharedPreferences} that the
     38  * preference hierarchy in this fragment will use, call
     39  * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
     40  * with a context in the same package as this fragment.
     41  * <p>
     42  * Furthermore, the preferences shown will follow the visual style of system
     43  * preferences. It is easy to create a hierarchy of preferences (that can be
     44  * shown on multiple screens) via XML. For these reasons, it is recommended to
     45  * use this fragment (as a superclass) to deal with preferences in applications.
     46  * <p>
     47  * A {@link PreferenceScreen} object should be at the top of the preference
     48  * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
     49  * denote a screen break--that is the preferences contained within subsequent
     50  * {@link PreferenceScreen} should be shown on another screen. The preference
     51  * framework handles showing these other screens from the preference hierarchy.
     52  * <p>
     53  * The preference hierarchy can be formed in multiple ways:
     54  * <li> From an XML file specifying the hierarchy
     55  * <li> From different {@link Activity Activities} that each specify its own
     56  * preferences in an XML file via {@link Activity} meta-data
     57  * <li> From an object hierarchy rooted with {@link PreferenceScreen}
     58  * <p>
     59  * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
     60  * root element should be a {@link PreferenceScreen}. Subsequent elements can point
     61  * to actual {@link Preference} subclasses. As mentioned above, subsequent
     62  * {@link PreferenceScreen} in the hierarchy will result in the screen break.
     63  * <p>
     64  * To specify an {@link Intent} to query {@link Activity Activities} that each
     65  * have preferences, use {@link #addPreferencesFromIntent}. Each
     66  * {@link Activity} can specify meta-data in the manifest (via the key
     67  * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML
     68  * resource. These XML resources will be inflated into a single preference
     69  * hierarchy and shown by this fragment.
     70  * <p>
     71  * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
     72  * {@link #setPreferenceScreen(PreferenceScreen)}.
     73  * <p>
     74  * As a convenience, this fragment implements a click listener for any
     75  * preference in the current hierarchy, see
     76  * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
     77  *
     78  * <div class="special reference">
     79  * <h3>Developer Guides</h3>
     80  * <p>For information about using {@code PreferenceFragment},
     81  * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
     82  * guide.</p>
     83  * </div>
     84  *
     85  * <a name="SampleCode"></a>
     86  * <h3>Sample Code</h3>
     87  *
     88  * <p>The following sample code shows a simple preference fragment that is
     89  * populated from a resource.  The resource it loads is:</p>
     90  *
     91  * {@sample development/samples/ApiDemos/res/xml/preferences.xml preferences}
     92  *
     93  * <p>The fragment implementation itself simply populates the preferences
     94  * when created.  Note that the preferences framework takes care of loading
     95  * the current values out of the app preferences and writing them when changed:</p>
     96  *
     97  * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/FragmentPreferences.java
     98  *      fragment}
     99  *
    100  * @see Preference
    101  * @see PreferenceScreen
    102  */
    103 public abstract class PreferenceFragment extends Fragment implements
    104         PreferenceManager.OnPreferenceTreeClickListener {
    105 
    106     private static final String PREFERENCES_TAG = "android:preferences";
    107 
    108     private PreferenceManager mPreferenceManager;
    109     private ListView mList;
    110     private boolean mHavePrefs;
    111     private boolean mInitDone;
    112 
    113     /**
    114      * The starting request code given out to preference framework.
    115      */
    116     private static final int FIRST_REQUEST_CODE = 100;
    117 
    118     private static final int MSG_BIND_PREFERENCES = 1;
    119     private Handler mHandler = new Handler() {
    120         @Override
    121         public void handleMessage(Message msg) {
    122             switch (msg.what) {
    123 
    124                 case MSG_BIND_PREFERENCES:
    125                     bindPreferences();
    126                     break;
    127             }
    128         }
    129     };
    130 
    131     final private Runnable mRequestFocus = new Runnable() {
    132         public void run() {
    133             mList.focusableViewAvailable(mList);
    134         }
    135     };
    136 
    137     /**
    138      * Interface that PreferenceFragment's containing activity should
    139      * implement to be able to process preference items that wish to
    140      * switch to a new fragment.
    141      */
    142     public interface OnPreferenceStartFragmentCallback {
    143         /**
    144          * Called when the user has clicked on a Preference that has
    145          * a fragment class name associated with it.  The implementation
    146          * to should instantiate and switch to an instance of the given
    147          * fragment.
    148          */
    149         boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref);
    150     }
    151 
    152     @Override
    153     public void onCreate(Bundle savedInstanceState) {
    154         super.onCreate(savedInstanceState);
    155         mPreferenceManager = new PreferenceManager(getActivity(), FIRST_REQUEST_CODE);
    156         mPreferenceManager.setFragment(this);
    157     }
    158 
    159     @Override
    160     public View onCreateView(LayoutInflater inflater, ViewGroup container,
    161             Bundle savedInstanceState) {
    162         return inflater.inflate(com.android.internal.R.layout.preference_list_fragment, container,
    163                 false);
    164     }
    165 
    166     @Override
    167     public void onActivityCreated(Bundle savedInstanceState) {
    168         super.onActivityCreated(savedInstanceState);
    169 
    170         if (mHavePrefs) {
    171             bindPreferences();
    172         }
    173 
    174         mInitDone = true;
    175 
    176         if (savedInstanceState != null) {
    177             Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
    178             if (container != null) {
    179                 final PreferenceScreen preferenceScreen = getPreferenceScreen();
    180                 if (preferenceScreen != null) {
    181                     preferenceScreen.restoreHierarchyState(container);
    182                 }
    183             }
    184         }
    185     }
    186 
    187     @Override
    188     public void onStart() {
    189         super.onStart();
    190         mPreferenceManager.setOnPreferenceTreeClickListener(this);
    191     }
    192 
    193     @Override
    194     public void onStop() {
    195         super.onStop();
    196         mPreferenceManager.dispatchActivityStop();
    197         mPreferenceManager.setOnPreferenceTreeClickListener(null);
    198     }
    199 
    200     @Override
    201     public void onDestroyView() {
    202         mList = null;
    203         mHandler.removeCallbacks(mRequestFocus);
    204         mHandler.removeMessages(MSG_BIND_PREFERENCES);
    205         super.onDestroyView();
    206     }
    207 
    208     @Override
    209     public void onDestroy() {
    210         super.onDestroy();
    211         mPreferenceManager.dispatchActivityDestroy();
    212     }
    213 
    214     @Override
    215     public void onSaveInstanceState(Bundle outState) {
    216         super.onSaveInstanceState(outState);
    217 
    218         final PreferenceScreen preferenceScreen = getPreferenceScreen();
    219         if (preferenceScreen != null) {
    220             Bundle container = new Bundle();
    221             preferenceScreen.saveHierarchyState(container);
    222             outState.putBundle(PREFERENCES_TAG, container);
    223         }
    224     }
    225 
    226     @Override
    227     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    228         super.onActivityResult(requestCode, resultCode, data);
    229 
    230         mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
    231     }
    232 
    233     /**
    234      * Returns the {@link PreferenceManager} used by this fragment.
    235      * @return The {@link PreferenceManager}.
    236      */
    237     public PreferenceManager getPreferenceManager() {
    238         return mPreferenceManager;
    239     }
    240 
    241     /**
    242      * Sets the root of the preference hierarchy that this fragment is showing.
    243      *
    244      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
    245      */
    246     public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
    247         if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
    248             mHavePrefs = true;
    249             if (mInitDone) {
    250                 postBindPreferences();
    251             }
    252         }
    253     }
    254 
    255     /**
    256      * Gets the root of the preference hierarchy that this fragment is showing.
    257      *
    258      * @return The {@link PreferenceScreen} that is the root of the preference
    259      *         hierarchy.
    260      */
    261     public PreferenceScreen getPreferenceScreen() {
    262         return mPreferenceManager.getPreferenceScreen();
    263     }
    264 
    265     /**
    266      * Adds preferences from activities that match the given {@link Intent}.
    267      *
    268      * @param intent The {@link Intent} to query activities.
    269      */
    270     public void addPreferencesFromIntent(Intent intent) {
    271         requirePreferenceManager();
    272 
    273         setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
    274     }
    275 
    276     /**
    277      * Inflates the given XML resource and adds the preference hierarchy to the current
    278      * preference hierarchy.
    279      *
    280      * @param preferencesResId The XML resource ID to inflate.
    281      */
    282     public void addPreferencesFromResource(int preferencesResId) {
    283         requirePreferenceManager();
    284 
    285         setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
    286                 preferencesResId, getPreferenceScreen()));
    287     }
    288 
    289     /**
    290      * {@inheritDoc}
    291      */
    292     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
    293             Preference preference) {
    294         if (preference.getFragment() != null &&
    295                 getActivity() instanceof OnPreferenceStartFragmentCallback) {
    296             return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
    297                     this, preference);
    298         }
    299         return false;
    300     }
    301 
    302     /**
    303      * Finds a {@link Preference} based on its key.
    304      *
    305      * @param key The key of the preference to retrieve.
    306      * @return The {@link Preference} with the key, or null.
    307      * @see PreferenceGroup#findPreference(CharSequence)
    308      */
    309     public Preference findPreference(CharSequence key) {
    310         if (mPreferenceManager == null) {
    311             return null;
    312         }
    313         return mPreferenceManager.findPreference(key);
    314     }
    315 
    316     private void requirePreferenceManager() {
    317         if (mPreferenceManager == null) {
    318             throw new RuntimeException("This should be called after super.onCreate.");
    319         }
    320     }
    321 
    322     private void postBindPreferences() {
    323         if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
    324         mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
    325     }
    326 
    327     private void bindPreferences() {
    328         final PreferenceScreen preferenceScreen = getPreferenceScreen();
    329         if (preferenceScreen != null) {
    330             preferenceScreen.bind(getListView());
    331         }
    332     }
    333 
    334     /** @hide */
    335     public ListView getListView() {
    336         ensureList();
    337         return mList;
    338     }
    339 
    340     private void ensureList() {
    341         if (mList != null) {
    342             return;
    343         }
    344         View root = getView();
    345         if (root == null) {
    346             throw new IllegalStateException("Content view not yet created");
    347         }
    348         View rawListView = root.findViewById(android.R.id.list);
    349         if (!(rawListView instanceof ListView)) {
    350             throw new RuntimeException(
    351                     "Content has view with id attribute 'android.R.id.list' "
    352                     + "that is not a ListView class");
    353         }
    354         mList = (ListView)rawListView;
    355         if (mList == null) {
    356             throw new RuntimeException(
    357                     "Your content must have a ListView whose id attribute is " +
    358                     "'android.R.id.list'");
    359         }
    360         mList.setOnKeyListener(mListOnKeyListener);
    361         mHandler.post(mRequestFocus);
    362     }
    363 
    364     private OnKeyListener mListOnKeyListener = new OnKeyListener() {
    365 
    366         @Override
    367         public boolean onKey(View v, int keyCode, KeyEvent event) {
    368             Object selectedItem = mList.getSelectedItem();
    369             if (selectedItem instanceof Preference) {
    370                 View selectedView = mList.getSelectedView();
    371                 return ((Preference)selectedItem).onKey(
    372                         selectedView, keyCode, event);
    373             }
    374             return false;
    375         }
    376 
    377     };
    378 }
    379