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