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.UnsupportedAppUsage;
     21 import android.annotation.XmlRes;
     22 import android.app.Activity;
     23 import android.app.Fragment;
     24 import android.content.Intent;
     25 import android.content.SharedPreferences;
     26 import android.content.res.TypedArray;
     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  * @see Preference
     92  * @see PreferenceScreen
     93  *
     94  * @deprecated Use the <a href="{@docRoot}jetpack/androidx.html">AndroidX</a>
     95  *      <a href="{@docRoot}reference/androidx/preference/package-summary.html">
     96  *      Preference Library</a> for consistent behavior across all devices. For more information on
     97  *      using the AndroidX Preference Library see
     98  *      <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>.
     99  */
    100 @Deprecated
    101 public abstract class PreferenceFragment extends Fragment implements
    102         PreferenceManager.OnPreferenceTreeClickListener {
    103 
    104     private static final String PREFERENCES_TAG = "android:preferences";
    105 
    106     @UnsupportedAppUsage
    107     private PreferenceManager mPreferenceManager;
    108     private ListView mList;
    109     private boolean mHavePrefs;
    110     private boolean mInitDone;
    111 
    112     private int mLayoutResId = com.android.internal.R.layout.preference_list_fragment;
    113 
    114     /**
    115      * The starting request code given out to preference framework.
    116      */
    117     private static final int FIRST_REQUEST_CODE = 100;
    118 
    119     private static final int MSG_BIND_PREFERENCES = 1;
    120     private Handler mHandler = new Handler() {
    121         @Override
    122         public void handleMessage(Message msg) {
    123             switch (msg.what) {
    124 
    125                 case MSG_BIND_PREFERENCES:
    126                     bindPreferences();
    127                     break;
    128             }
    129         }
    130     };
    131 
    132     final private Runnable mRequestFocus = new Runnable() {
    133         public void run() {
    134             mList.focusableViewAvailable(mList);
    135         }
    136     };
    137 
    138     /**
    139      * Interface that PreferenceFragment's containing activity should
    140      * implement to be able to process preference items that wish to
    141      * switch to a new fragment.
    142      *
    143      * @deprecated Use {@link
    144      * android.support.v7.preference.PreferenceFragmentCompat.OnPreferenceStartFragmentCallback}
    145      */
    146     @Deprecated
    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 onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    183         super.onViewCreated(view, savedInstanceState);
    184 
    185         TypedArray a = getActivity().obtainStyledAttributes(null,
    186                 com.android.internal.R.styleable.PreferenceFragment,
    187                 com.android.internal.R.attr.preferenceFragmentStyle,
    188                 0);
    189 
    190         ListView lv = (ListView) view.findViewById(android.R.id.list);
    191         if (lv != null
    192                 && a.hasValueOrEmpty(com.android.internal.R.styleable.PreferenceFragment_divider)) {
    193             lv.setDivider(
    194                     a.getDrawable(com.android.internal.R.styleable.PreferenceFragment_divider));
    195         }
    196 
    197         a.recycle();
    198     }
    199 
    200     @Override
    201     public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    202         super.onActivityCreated(savedInstanceState);
    203 
    204         if (mHavePrefs) {
    205             bindPreferences();
    206         }
    207 
    208         mInitDone = true;
    209 
    210         if (savedInstanceState != null) {
    211             Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
    212             if (container != null) {
    213                 final PreferenceScreen preferenceScreen = getPreferenceScreen();
    214                 if (preferenceScreen != null) {
    215                     preferenceScreen.restoreHierarchyState(container);
    216                 }
    217             }
    218         }
    219     }
    220 
    221     @Override
    222     public void onStart() {
    223         super.onStart();
    224         mPreferenceManager.setOnPreferenceTreeClickListener(this);
    225     }
    226 
    227     @Override
    228     public void onStop() {
    229         super.onStop();
    230         mPreferenceManager.dispatchActivityStop();
    231         mPreferenceManager.setOnPreferenceTreeClickListener(null);
    232     }
    233 
    234     @Override
    235     public void onDestroyView() {
    236         if (mList != null) {
    237             mList.setOnKeyListener(null);
    238         }
    239         mList = null;
    240         mHandler.removeCallbacks(mRequestFocus);
    241         mHandler.removeMessages(MSG_BIND_PREFERENCES);
    242         super.onDestroyView();
    243     }
    244 
    245     @Override
    246     public void onDestroy() {
    247         super.onDestroy();
    248         mPreferenceManager.dispatchActivityDestroy();
    249     }
    250 
    251     @Override
    252     public void onSaveInstanceState(Bundle outState) {
    253         super.onSaveInstanceState(outState);
    254 
    255         final PreferenceScreen preferenceScreen = getPreferenceScreen();
    256         if (preferenceScreen != null) {
    257             Bundle container = new Bundle();
    258             preferenceScreen.saveHierarchyState(container);
    259             outState.putBundle(PREFERENCES_TAG, container);
    260         }
    261     }
    262 
    263     @Override
    264     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    265         super.onActivityResult(requestCode, resultCode, data);
    266 
    267         mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
    268     }
    269 
    270     /**
    271      * Returns the {@link PreferenceManager} used by this fragment.
    272      * @return The {@link PreferenceManager}.
    273      */
    274     public PreferenceManager getPreferenceManager() {
    275         return mPreferenceManager;
    276     }
    277 
    278     /**
    279      * Sets the root of the preference hierarchy that this fragment is showing.
    280      *
    281      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
    282      */
    283     public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
    284         if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
    285             onUnbindPreferences();
    286             mHavePrefs = true;
    287             if (mInitDone) {
    288                 postBindPreferences();
    289             }
    290         }
    291     }
    292 
    293     /**
    294      * Gets the root of the preference hierarchy that this fragment is showing.
    295      *
    296      * @return The {@link PreferenceScreen} that is the root of the preference
    297      *         hierarchy.
    298      */
    299     public PreferenceScreen getPreferenceScreen() {
    300         return mPreferenceManager.getPreferenceScreen();
    301     }
    302 
    303     /**
    304      * Adds preferences from activities that match the given {@link Intent}.
    305      *
    306      * @param intent The {@link Intent} to query activities.
    307      */
    308     public void addPreferencesFromIntent(Intent intent) {
    309         requirePreferenceManager();
    310 
    311         setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
    312     }
    313 
    314     /**
    315      * Inflates the given XML resource and adds the preference hierarchy to the current
    316      * preference hierarchy.
    317      *
    318      * @param preferencesResId The XML resource ID to inflate.
    319      */
    320     public void addPreferencesFromResource(@XmlRes int preferencesResId) {
    321         requirePreferenceManager();
    322 
    323         setPreferenceScreen(mPreferenceManager.inflateFromResource(getActivity(),
    324                 preferencesResId, getPreferenceScreen()));
    325     }
    326 
    327     /**
    328      * {@inheritDoc}
    329      */
    330     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
    331             Preference preference) {
    332         if (preference.getFragment() != null &&
    333                 getActivity() instanceof OnPreferenceStartFragmentCallback) {
    334             return ((OnPreferenceStartFragmentCallback)getActivity()).onPreferenceStartFragment(
    335                     this, preference);
    336         }
    337         return false;
    338     }
    339 
    340     /**
    341      * Finds a {@link Preference} based on its key.
    342      *
    343      * @param key The key of the preference to retrieve.
    344      * @return The {@link Preference} with the key, or null.
    345      * @see PreferenceGroup#findPreference(CharSequence)
    346      */
    347     public Preference findPreference(CharSequence key) {
    348         if (mPreferenceManager == null) {
    349             return null;
    350         }
    351         return mPreferenceManager.findPreference(key);
    352     }
    353 
    354     private void requirePreferenceManager() {
    355         if (mPreferenceManager == null) {
    356             throw new RuntimeException("This should be called after super.onCreate.");
    357         }
    358     }
    359 
    360     private void postBindPreferences() {
    361         if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
    362         mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
    363     }
    364 
    365     private void bindPreferences() {
    366         final PreferenceScreen preferenceScreen = getPreferenceScreen();
    367         if (preferenceScreen != null) {
    368             View root = getView();
    369             if (root != null) {
    370                 View titleView = root.findViewById(android.R.id.title);
    371                 if (titleView instanceof TextView) {
    372                     CharSequence title = preferenceScreen.getTitle();
    373                     if (TextUtils.isEmpty(title)) {
    374                         titleView.setVisibility(View.GONE);
    375                     } else {
    376                         ((TextView) titleView).setText(title);
    377                         titleView.setVisibility(View.VISIBLE);
    378                     }
    379                 }
    380             }
    381 
    382             preferenceScreen.bind(getListView());
    383         }
    384         onBindPreferences();
    385     }
    386 
    387     /** @hide */
    388     protected void onBindPreferences() {
    389     }
    390 
    391     /** @hide */
    392     protected void onUnbindPreferences() {
    393     }
    394 
    395     /** @hide */
    396     @UnsupportedAppUsage
    397     public ListView getListView() {
    398         ensureList();
    399         return mList;
    400     }
    401 
    402     /** @hide */
    403     public boolean hasListView() {
    404         if (mList != null) {
    405             return true;
    406         }
    407         View root = getView();
    408         if (root == null) {
    409             return false;
    410         }
    411         View rawListView = root.findViewById(android.R.id.list);
    412         if (!(rawListView instanceof ListView)) {
    413             return false;
    414         }
    415         mList = (ListView)rawListView;
    416         if (mList == null) {
    417             return false;
    418         }
    419         return true;
    420     }
    421 
    422     private void ensureList() {
    423         if (mList != null) {
    424             return;
    425         }
    426         View root = getView();
    427         if (root == null) {
    428             throw new IllegalStateException("Content view not yet created");
    429         }
    430         View rawListView = root.findViewById(android.R.id.list);
    431         if (!(rawListView instanceof ListView)) {
    432             throw new RuntimeException(
    433                     "Content has view with id attribute 'android.R.id.list' "
    434                     + "that is not a ListView class");
    435         }
    436         mList = (ListView)rawListView;
    437         if (mList == null) {
    438             throw new RuntimeException(
    439                     "Your content must have a ListView whose id attribute is " +
    440                     "'android.R.id.list'");
    441         }
    442         mList.setOnKeyListener(mListOnKeyListener);
    443         mHandler.post(mRequestFocus);
    444     }
    445 
    446     private OnKeyListener mListOnKeyListener = new OnKeyListener() {
    447 
    448         @Override
    449         public boolean onKey(View v, int keyCode, KeyEvent event) {
    450             Object selectedItem = mList.getSelectedItem();
    451             if (selectedItem instanceof Preference) {
    452                 View selectedView = mList.getSelectedView();
    453                 return ((Preference)selectedItem).onKey(
    454                         selectedView, keyCode, event);
    455             }
    456             return false;
    457         }
    458 
    459     };
    460 }
    461