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