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