Home | History | Annotate | Download | only in preference
      1 /*
      2  * Copyright (C) 2007 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.Fragment;
     20 import android.app.FragmentBreadCrumbs;
     21 import android.app.FragmentManager;
     22 import android.app.FragmentTransaction;
     23 import android.app.ListActivity;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.res.Resources;
     27 import android.content.res.TypedArray;
     28 import android.content.res.XmlResourceParser;
     29 import android.os.Bundle;
     30 import android.os.Handler;
     31 import android.os.Message;
     32 import android.os.Parcel;
     33 import android.os.Parcelable;
     34 import android.text.TextUtils;
     35 import android.util.AttributeSet;
     36 import android.util.TypedValue;
     37 import android.util.Xml;
     38 import android.view.LayoutInflater;
     39 import android.view.View;
     40 import android.view.View.OnClickListener;
     41 import android.view.ViewGroup;
     42 import android.widget.AbsListView;
     43 import android.widget.ArrayAdapter;
     44 import android.widget.BaseAdapter;
     45 import android.widget.Button;
     46 import android.widget.FrameLayout;
     47 import android.widget.ImageView;
     48 import android.widget.ListView;
     49 import android.widget.TextView;
     50 
     51 import com.android.internal.util.XmlUtils;
     52 
     53 import org.xmlpull.v1.XmlPullParser;
     54 import org.xmlpull.v1.XmlPullParserException;
     55 
     56 import java.io.IOException;
     57 import java.util.ArrayList;
     58 import java.util.List;
     59 
     60 /**
     61  * This is the base class for an activity to show a hierarchy of preferences
     62  * to the user.  Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB}
     63  * this class only allowed the display of a single set of preference; this
     64  * functionality should now be found in the new {@link PreferenceFragment}
     65  * class.  If you are using PreferenceActivity in its old mode, the documentation
     66  * there applies to the deprecated APIs here.
     67  *
     68  * <p>This activity shows one or more headers of preferences, each of which
     69  * is associated with a {@link PreferenceFragment} to display the preferences
     70  * of that header.  The actual layout and display of these associations can
     71  * however vary; currently there are two major approaches it may take:
     72  *
     73  * <ul>
     74  * <li>On a small screen it may display only the headers as a single list
     75  * when first launched.  Selecting one of the header items will re-launch
     76  * the activity with it only showing the PreferenceFragment of that header.
     77  * <li>On a large screen in may display both the headers and current
     78  * PreferenceFragment together as panes.  Selecting a header item switches
     79  * to showing the correct PreferenceFragment for that item.
     80  * </ul>
     81  *
     82  * <p>Subclasses of PreferenceActivity should implement
     83  * {@link #onBuildHeaders} to populate the header list with the desired
     84  * items.  Doing this implicitly switches the class into its new "headers
     85  * + fragments" mode rather than the old style of just showing a single
     86  * preferences list.
     87  *
     88  * <div class="special reference">
     89  * <h3>Developer Guides</h3>
     90  * <p>For information about using {@code PreferenceActivity},
     91  * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
     92  * guide.</p>
     93  * </div>
     94  *
     95  * <a name="SampleCode"></a>
     96  * <h3>Sample Code</h3>
     97  *
     98  * <p>The following sample code shows a simple preference activity that
     99  * has two different sets of preferences.  The implementation, consisting
    100  * of the activity itself as well as its two preference fragments is:</p>
    101  *
    102  * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/PreferenceWithHeaders.java
    103  *      activity}
    104  *
    105  * <p>The preference_headers resource describes the headers to be displayed
    106  * and the fragments associated with them.  It is:
    107  *
    108  * {@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
    109  *
    110  * <p>The first header is shown by Prefs1Fragment, which populates itself
    111  * from the following XML resource:</p>
    112  *
    113  * {@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
    114  *
    115  * <p>Note that this XML resource contains a preference screen holding another
    116  * fragment, the Prefs1FragmentInner implemented here.  This allows the user
    117  * to traverse down a hierarchy of preferences; pressing back will pop each
    118  * fragment off the stack to return to the previous preferences.
    119  *
    120  * <p>See {@link PreferenceFragment} for information on implementing the
    121  * fragments themselves.
    122  */
    123 public abstract class PreferenceActivity extends ListActivity implements
    124         PreferenceManager.OnPreferenceTreeClickListener,
    125         PreferenceFragment.OnPreferenceStartFragmentCallback {
    126 
    127     private static final String TAG = "PreferenceActivity";
    128 
    129     // Constants for state save/restore
    130     private static final String HEADERS_TAG = ":android:headers";
    131     private static final String CUR_HEADER_TAG = ":android:cur_header";
    132     private static final String PREFERENCES_TAG = ":android:preferences";
    133 
    134     /**
    135      * When starting this activity, the invoking Intent can contain this extra
    136      * string to specify which fragment should be initially displayed.
    137      * <p/>Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity
    138      * will call isValidFragment() to confirm that the fragment class name is valid for this
    139      * activity.
    140      */
    141     public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
    142 
    143     /**
    144      * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
    145      * this extra can also be specified to supply a Bundle of arguments to pass
    146      * to that fragment when it is instantiated during the initial creation
    147      * of PreferenceActivity.
    148      */
    149     public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
    150 
    151     /**
    152      * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
    153      * this extra can also be specify to supply the title to be shown for
    154      * that fragment.
    155      */
    156     public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title";
    157 
    158     /**
    159      * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
    160      * this extra can also be specify to supply the short title to be shown for
    161      * that fragment.
    162      */
    163     public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE
    164             = ":android:show_fragment_short_title";
    165 
    166     /**
    167      * When starting this activity, the invoking Intent can contain this extra
    168      * boolean that the header list should not be displayed.  This is most often
    169      * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
    170      * the activity to display a specific fragment that the user has navigated
    171      * to.
    172      */
    173     public static final String EXTRA_NO_HEADERS = ":android:no_headers";
    174 
    175     private static final String BACK_STACK_PREFS = ":android:prefs";
    176 
    177     // extras that allow any preference activity to be launched as part of a wizard
    178 
    179     // show Back and Next buttons? takes boolean parameter
    180     // Back will then return RESULT_CANCELED and Next RESULT_OK
    181     private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
    182 
    183     // add a Skip button?
    184     private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
    185 
    186     // specify custom text for the Back or Next buttons, or cause a button to not appear
    187     // at all by setting it to null
    188     private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
    189     private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
    190 
    191     // --- State for new mode when showing a list of headers + prefs fragment
    192 
    193     private final ArrayList<Header> mHeaders = new ArrayList<Header>();
    194 
    195     private FrameLayout mListFooter;
    196 
    197     private ViewGroup mPrefsContainer;
    198 
    199     private FragmentBreadCrumbs mFragmentBreadCrumbs;
    200 
    201     private boolean mSinglePane;
    202 
    203     private Header mCurHeader;
    204 
    205     // --- State for old mode when showing a single preference list
    206 
    207     private PreferenceManager mPreferenceManager;
    208 
    209     private Bundle mSavedInstanceState;
    210 
    211     // --- Common state
    212 
    213     private Button mNextButton;
    214 
    215     private int mPreferenceHeaderItemResId = 0;
    216     private boolean mPreferenceHeaderRemoveEmptyIcon = false;
    217 
    218     /**
    219      * The starting request code given out to preference framework.
    220      */
    221     private static final int FIRST_REQUEST_CODE = 100;
    222 
    223     private static final int MSG_BIND_PREFERENCES = 1;
    224     private static final int MSG_BUILD_HEADERS = 2;
    225     private Handler mHandler = new Handler() {
    226         @Override
    227         public void handleMessage(Message msg) {
    228             switch (msg.what) {
    229                 case MSG_BIND_PREFERENCES: {
    230                     bindPreferences();
    231                 } break;
    232                 case MSG_BUILD_HEADERS: {
    233                     ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders);
    234                     mHeaders.clear();
    235                     onBuildHeaders(mHeaders);
    236                     if (mAdapter instanceof BaseAdapter) {
    237                         ((BaseAdapter) mAdapter).notifyDataSetChanged();
    238                     }
    239                     Header header = onGetNewHeader();
    240                     if (header != null && header.fragment != null) {
    241                         Header mappedHeader = findBestMatchingHeader(header, oldHeaders);
    242                         if (mappedHeader == null || mCurHeader != mappedHeader) {
    243                             switchToHeader(header);
    244                         }
    245                     } else if (mCurHeader != null) {
    246                         Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders);
    247                         if (mappedHeader != null) {
    248                             setSelectedHeader(mappedHeader);
    249                         }
    250                     }
    251                 } break;
    252             }
    253         }
    254     };
    255 
    256     private static class HeaderAdapter extends ArrayAdapter<Header> {
    257         private static class HeaderViewHolder {
    258             ImageView icon;
    259             TextView title;
    260             TextView summary;
    261         }
    262 
    263         private LayoutInflater mInflater;
    264         private int mLayoutResId;
    265         private boolean mRemoveIconIfEmpty;
    266 
    267         public HeaderAdapter(Context context, List<Header> objects, int layoutResId,
    268                 boolean removeIconBehavior) {
    269             super(context, 0, objects);
    270             mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    271             mLayoutResId = layoutResId;
    272             mRemoveIconIfEmpty = removeIconBehavior;
    273         }
    274 
    275         @Override
    276         public View getView(int position, View convertView, ViewGroup parent) {
    277             HeaderViewHolder holder;
    278             View view;
    279 
    280             if (convertView == null) {
    281                 view = mInflater.inflate(mLayoutResId, parent, false);
    282                 holder = new HeaderViewHolder();
    283                 holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
    284                 holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);
    285                 holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);
    286                 view.setTag(holder);
    287             } else {
    288                 view = convertView;
    289                 holder = (HeaderViewHolder) view.getTag();
    290             }
    291 
    292             // All view fields must be updated every time, because the view may be recycled
    293             Header header = getItem(position);
    294             if (mRemoveIconIfEmpty) {
    295                 if (header.iconRes == 0) {
    296                     holder.icon.setVisibility(View.GONE);
    297                 } else {
    298                     holder.icon.setVisibility(View.VISIBLE);
    299                     holder.icon.setImageResource(header.iconRes);
    300                 }
    301             } else {
    302                 holder.icon.setImageResource(header.iconRes);
    303             }
    304             holder.title.setText(header.getTitle(getContext().getResources()));
    305             CharSequence summary = header.getSummary(getContext().getResources());
    306             if (!TextUtils.isEmpty(summary)) {
    307                 holder.summary.setVisibility(View.VISIBLE);
    308                 holder.summary.setText(summary);
    309             } else {
    310                 holder.summary.setVisibility(View.GONE);
    311             }
    312 
    313             return view;
    314         }
    315     }
    316 
    317     /**
    318      * Default value for {@link Header#id Header.id} indicating that no
    319      * identifier value is set.  All other values (including those below -1)
    320      * are valid.
    321      */
    322     public static final long HEADER_ID_UNDEFINED = -1;
    323 
    324     /**
    325      * Description of a single Header item that the user can select.
    326      */
    327     public static final class Header implements Parcelable {
    328         /**
    329          * Identifier for this header, to correlate with a new list when
    330          * it is updated.  The default value is
    331          * {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id.
    332          * @attr ref android.R.styleable#PreferenceHeader_id
    333          */
    334         public long id = HEADER_ID_UNDEFINED;
    335 
    336         /**
    337          * Resource ID of title of the header that is shown to the user.
    338          * @attr ref android.R.styleable#PreferenceHeader_title
    339          */
    340         public int titleRes;
    341 
    342         /**
    343          * Title of the header that is shown to the user.
    344          * @attr ref android.R.styleable#PreferenceHeader_title
    345          */
    346         public CharSequence title;
    347 
    348         /**
    349          * Resource ID of optional summary describing what this header controls.
    350          * @attr ref android.R.styleable#PreferenceHeader_summary
    351          */
    352         public int summaryRes;
    353 
    354         /**
    355          * Optional summary describing what this header controls.
    356          * @attr ref android.R.styleable#PreferenceHeader_summary
    357          */
    358         public CharSequence summary;
    359 
    360         /**
    361          * Resource ID of optional text to show as the title in the bread crumb.
    362          * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
    363          */
    364         public int breadCrumbTitleRes;
    365 
    366         /**
    367          * Optional text to show as the title in the bread crumb.
    368          * @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
    369          */
    370         public CharSequence breadCrumbTitle;
    371 
    372         /**
    373          * Resource ID of optional text to show as the short title in the bread crumb.
    374          * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
    375          */
    376         public int breadCrumbShortTitleRes;
    377 
    378         /**
    379          * Optional text to show as the short title in the bread crumb.
    380          * @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
    381          */
    382         public CharSequence breadCrumbShortTitle;
    383 
    384         /**
    385          * Optional icon resource to show for this header.
    386          * @attr ref android.R.styleable#PreferenceHeader_icon
    387          */
    388         public int iconRes;
    389 
    390         /**
    391          * Full class name of the fragment to display when this header is
    392          * selected.
    393          * @attr ref android.R.styleable#PreferenceHeader_fragment
    394          */
    395         public String fragment;
    396 
    397         /**
    398          * Optional arguments to supply to the fragment when it is
    399          * instantiated.
    400          */
    401         public Bundle fragmentArguments;
    402 
    403         /**
    404          * Intent to launch when the preference is selected.
    405          */
    406         public Intent intent;
    407 
    408         /**
    409          * Optional additional data for use by subclasses of PreferenceActivity.
    410          */
    411         public Bundle extras;
    412 
    413         public Header() {
    414             // Empty
    415         }
    416 
    417         /**
    418          * Return the currently set title.  If {@link #titleRes} is set,
    419          * this resource is loaded from <var>res</var> and returned.  Otherwise
    420          * {@link #title} is returned.
    421          */
    422         public CharSequence getTitle(Resources res) {
    423             if (titleRes != 0) {
    424                 return res.getText(titleRes);
    425             }
    426             return title;
    427         }
    428 
    429         /**
    430          * Return the currently set summary.  If {@link #summaryRes} is set,
    431          * this resource is loaded from <var>res</var> and returned.  Otherwise
    432          * {@link #summary} is returned.
    433          */
    434         public CharSequence getSummary(Resources res) {
    435             if (summaryRes != 0) {
    436                 return res.getText(summaryRes);
    437             }
    438             return summary;
    439         }
    440 
    441         /**
    442          * Return the currently set bread crumb title.  If {@link #breadCrumbTitleRes} is set,
    443          * this resource is loaded from <var>res</var> and returned.  Otherwise
    444          * {@link #breadCrumbTitle} is returned.
    445          */
    446         public CharSequence getBreadCrumbTitle(Resources res) {
    447             if (breadCrumbTitleRes != 0) {
    448                 return res.getText(breadCrumbTitleRes);
    449             }
    450             return breadCrumbTitle;
    451         }
    452 
    453         /**
    454          * Return the currently set bread crumb short title.  If
    455          * {@link #breadCrumbShortTitleRes} is set,
    456          * this resource is loaded from <var>res</var> and returned.  Otherwise
    457          * {@link #breadCrumbShortTitle} is returned.
    458          */
    459         public CharSequence getBreadCrumbShortTitle(Resources res) {
    460             if (breadCrumbShortTitleRes != 0) {
    461                 return res.getText(breadCrumbShortTitleRes);
    462             }
    463             return breadCrumbShortTitle;
    464         }
    465 
    466         @Override
    467         public int describeContents() {
    468             return 0;
    469         }
    470 
    471         @Override
    472         public void writeToParcel(Parcel dest, int flags) {
    473             dest.writeLong(id);
    474             dest.writeInt(titleRes);
    475             TextUtils.writeToParcel(title, dest, flags);
    476             dest.writeInt(summaryRes);
    477             TextUtils.writeToParcel(summary, dest, flags);
    478             dest.writeInt(breadCrumbTitleRes);
    479             TextUtils.writeToParcel(breadCrumbTitle, dest, flags);
    480             dest.writeInt(breadCrumbShortTitleRes);
    481             TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags);
    482             dest.writeInt(iconRes);
    483             dest.writeString(fragment);
    484             dest.writeBundle(fragmentArguments);
    485             if (intent != null) {
    486                 dest.writeInt(1);
    487                 intent.writeToParcel(dest, flags);
    488             } else {
    489                 dest.writeInt(0);
    490             }
    491             dest.writeBundle(extras);
    492         }
    493 
    494         public void readFromParcel(Parcel in) {
    495             id = in.readLong();
    496             titleRes = in.readInt();
    497             title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    498             summaryRes = in.readInt();
    499             summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    500             breadCrumbTitleRes = in.readInt();
    501             breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    502             breadCrumbShortTitleRes = in.readInt();
    503             breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    504             iconRes = in.readInt();
    505             fragment = in.readString();
    506             fragmentArguments = in.readBundle();
    507             if (in.readInt() != 0) {
    508                 intent = Intent.CREATOR.createFromParcel(in);
    509             }
    510             extras = in.readBundle();
    511         }
    512 
    513         Header(Parcel in) {
    514             readFromParcel(in);
    515         }
    516 
    517         public static final Creator<Header> CREATOR = new Creator<Header>() {
    518             public Header createFromParcel(Parcel source) {
    519                 return new Header(source);
    520             }
    521             public Header[] newArray(int size) {
    522                 return new Header[size];
    523             }
    524         };
    525     }
    526 
    527     @Override
    528     protected void onCreate(Bundle savedInstanceState) {
    529         super.onCreate(savedInstanceState);
    530 
    531         // Theming for the PreferenceActivity layout and for the Preference Header(s) layout
    532         TypedArray sa = obtainStyledAttributes(null,
    533                 com.android.internal.R.styleable.PreferenceActivity,
    534                 com.android.internal.R.attr.preferenceActivityStyle,
    535                 0);
    536 
    537         final int layoutResId = sa.getResourceId(
    538                 com.android.internal.R.styleable.PreferenceActivity_layout,
    539                 com.android.internal.R.layout.preference_list_content);
    540 
    541         mPreferenceHeaderItemResId = sa.getResourceId(
    542                 com.android.internal.R.styleable.PreferenceActivity_headerLayout,
    543                 com.android.internal.R.layout.preference_header_item);
    544         mPreferenceHeaderRemoveEmptyIcon = sa.getBoolean(
    545                 com.android.internal.R.styleable.PreferenceActivity_headerRemoveIconIfEmpty,
    546                 false);
    547 
    548         sa.recycle();
    549 
    550         setContentView(layoutResId);
    551 
    552         mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
    553         mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame);
    554         boolean hidingHeaders = onIsHidingHeaders();
    555         mSinglePane = hidingHeaders || !onIsMultiPane();
    556         String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
    557         Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
    558         int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
    559         int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
    560 
    561         if (savedInstanceState != null) {
    562             // We are restarting from a previous saved state; used that to
    563             // initialize, instead of starting fresh.
    564             ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG);
    565             if (headers != null) {
    566                 mHeaders.addAll(headers);
    567                 int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG,
    568                         (int) HEADER_ID_UNDEFINED);
    569                 if (curHeader >= 0 && curHeader < mHeaders.size()) {
    570                     setSelectedHeader(mHeaders.get(curHeader));
    571                 }
    572             }
    573 
    574         } else {
    575             if (initialFragment != null && mSinglePane) {
    576                 // If we are just showing a fragment, we want to run in
    577                 // new fragment mode, but don't need to compute and show
    578                 // the headers.
    579                 switchToHeader(initialFragment, initialArguments);
    580                 if (initialTitle != 0) {
    581                     CharSequence initialTitleStr = getText(initialTitle);
    582                     CharSequence initialShortTitleStr = initialShortTitle != 0
    583                             ? getText(initialShortTitle) : null;
    584                     showBreadCrumbs(initialTitleStr, initialShortTitleStr);
    585                 }
    586 
    587             } else {
    588                 // We need to try to build the headers.
    589                 onBuildHeaders(mHeaders);
    590 
    591                 // If there are headers, then at this point we need to show
    592                 // them and, depending on the screen, we may also show in-line
    593                 // the currently selected preference fragment.
    594                 if (mHeaders.size() > 0) {
    595                     if (!mSinglePane) {
    596                         if (initialFragment == null) {
    597                             Header h = onGetInitialHeader();
    598                             switchToHeader(h);
    599                         } else {
    600                             switchToHeader(initialFragment, initialArguments);
    601                         }
    602                     }
    603                 }
    604             }
    605         }
    606 
    607         // The default configuration is to only show the list view.  Adjust
    608         // visibility for other configurations.
    609         if (initialFragment != null && mSinglePane) {
    610             // Single pane, showing just a prefs fragment.
    611             findViewById(com.android.internal.R.id.headers).setVisibility(View.GONE);
    612             mPrefsContainer.setVisibility(View.VISIBLE);
    613             if (initialTitle != 0) {
    614                 CharSequence initialTitleStr = getText(initialTitle);
    615                 CharSequence initialShortTitleStr = initialShortTitle != 0
    616                         ? getText(initialShortTitle) : null;
    617                 showBreadCrumbs(initialTitleStr, initialShortTitleStr);
    618             }
    619         } else if (mHeaders.size() > 0) {
    620             setListAdapter(new HeaderAdapter(this, mHeaders, mPreferenceHeaderItemResId,
    621                     mPreferenceHeaderRemoveEmptyIcon));
    622             if (!mSinglePane) {
    623                 // Multi-pane.
    624                 getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
    625                 if (mCurHeader != null) {
    626                     setSelectedHeader(mCurHeader);
    627                 }
    628                 mPrefsContainer.setVisibility(View.VISIBLE);
    629             }
    630         } else {
    631             // If there are no headers, we are in the old "just show a screen
    632             // of preferences" mode.
    633             setContentView(com.android.internal.R.layout.preference_list_content_single);
    634             mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer);
    635             mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs);
    636             mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
    637             mPreferenceManager.setOnPreferenceTreeClickListener(this);
    638         }
    639 
    640         // see if we should show Back/Next buttons
    641         Intent intent = getIntent();
    642         if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
    643 
    644             findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
    645 
    646             Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
    647             backButton.setOnClickListener(new OnClickListener() {
    648                 public void onClick(View v) {
    649                     setResult(RESULT_CANCELED);
    650                     finish();
    651                 }
    652             });
    653             Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
    654             skipButton.setOnClickListener(new OnClickListener() {
    655                 public void onClick(View v) {
    656                     setResult(RESULT_OK);
    657                     finish();
    658                 }
    659             });
    660             mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
    661             mNextButton.setOnClickListener(new OnClickListener() {
    662                 public void onClick(View v) {
    663                     setResult(RESULT_OK);
    664                     finish();
    665                 }
    666             });
    667 
    668             // set our various button parameters
    669             if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
    670                 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
    671                 if (TextUtils.isEmpty(buttonText)) {
    672                     mNextButton.setVisibility(View.GONE);
    673                 }
    674                 else {
    675                     mNextButton.setText(buttonText);
    676                 }
    677             }
    678             if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
    679                 String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
    680                 if (TextUtils.isEmpty(buttonText)) {
    681                     backButton.setVisibility(View.GONE);
    682                 }
    683                 else {
    684                     backButton.setText(buttonText);
    685                 }
    686             }
    687             if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
    688                 skipButton.setVisibility(View.VISIBLE);
    689             }
    690         }
    691     }
    692 
    693     /**
    694      * Returns true if this activity is currently showing the header list.
    695      */
    696     public boolean hasHeaders() {
    697         return getListView().getVisibility() == View.VISIBLE
    698                 && mPreferenceManager == null;
    699     }
    700 
    701     /**
    702      * Returns the Header list
    703      * @hide
    704      */
    705     public List<Header> getHeaders() {
    706         return mHeaders;
    707     }
    708 
    709     /**
    710      * Returns true if this activity is showing multiple panes -- the headers
    711      * and a preference fragment.
    712      */
    713     public boolean isMultiPane() {
    714         return hasHeaders() && mPrefsContainer.getVisibility() == View.VISIBLE;
    715     }
    716 
    717     /**
    718      * Called to determine if the activity should run in multi-pane mode.
    719      * The default implementation returns true if the screen is large
    720      * enough.
    721      */
    722     public boolean onIsMultiPane() {
    723         boolean preferMultiPane = getResources().getBoolean(
    724                 com.android.internal.R.bool.preferences_prefer_dual_pane);
    725         return preferMultiPane;
    726     }
    727 
    728     /**
    729      * Called to determine whether the header list should be hidden.
    730      * The default implementation returns the
    731      * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
    732      * This is set to false, for example, when the activity is being re-launched
    733      * to show a particular preference activity.
    734      */
    735     public boolean onIsHidingHeaders() {
    736         return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
    737     }
    738 
    739     /**
    740      * Called to determine the initial header to be shown.  The default
    741      * implementation simply returns the fragment of the first header.  Note
    742      * that the returned Header object does not actually need to exist in
    743      * your header list -- whatever its fragment is will simply be used to
    744      * show for the initial UI.
    745      */
    746     public Header onGetInitialHeader() {
    747         for (int i=0; i<mHeaders.size(); i++) {
    748             Header h = mHeaders.get(i);
    749             if (h.fragment != null) {
    750                 return h;
    751             }
    752         }
    753         throw new IllegalStateException("Must have at least one header with a fragment");
    754     }
    755 
    756     /**
    757      * Called after the header list has been updated ({@link #onBuildHeaders}
    758      * has been called and returned due to {@link #invalidateHeaders()}) to
    759      * specify the header that should now be selected.  The default implementation
    760      * returns null to keep whatever header is currently selected.
    761      */
    762     public Header onGetNewHeader() {
    763         return null;
    764     }
    765 
    766     /**
    767      * Called when the activity needs its list of headers build.  By
    768      * implementing this and adding at least one item to the list, you
    769      * will cause the activity to run in its modern fragment mode.  Note
    770      * that this function may not always be called; for example, if the
    771      * activity has been asked to display a particular fragment without
    772      * the header list, there is no need to build the headers.
    773      *
    774      * <p>Typical implementations will use {@link #loadHeadersFromResource}
    775      * to fill in the list from a resource.
    776      *
    777      * @param target The list in which to place the headers.
    778      */
    779     public void onBuildHeaders(List<Header> target) {
    780         // Should be overloaded by subclasses
    781     }
    782 
    783     /**
    784      * Call when you need to change the headers being displayed.  Will result
    785      * in onBuildHeaders() later being called to retrieve the new list.
    786      */
    787     public void invalidateHeaders() {
    788         if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
    789             mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
    790         }
    791     }
    792 
    793     /**
    794      * Parse the given XML file as a header description, adding each
    795      * parsed Header into the target list.
    796      *
    797      * @param resid The XML resource to load and parse.
    798      * @param target The list in which the parsed headers should be placed.
    799      */
    800     public void loadHeadersFromResource(int resid, List<Header> target) {
    801         XmlResourceParser parser = null;
    802         try {
    803             parser = getResources().getXml(resid);
    804             AttributeSet attrs = Xml.asAttributeSet(parser);
    805 
    806             int type;
    807             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    808                     && type != XmlPullParser.START_TAG) {
    809                 // Parse next until start tag is found
    810             }
    811 
    812             String nodeName = parser.getName();
    813             if (!"preference-headers".equals(nodeName)) {
    814                 throw new RuntimeException(
    815                         "XML document must start with <preference-headers> tag; found"
    816                         + nodeName + " at " + parser.getPositionDescription());
    817             }
    818 
    819             Bundle curBundle = null;
    820 
    821             final int outerDepth = parser.getDepth();
    822             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    823                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    824                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    825                     continue;
    826                 }
    827 
    828                 nodeName = parser.getName();
    829                 if ("header".equals(nodeName)) {
    830                     Header header = new Header();
    831 
    832                     TypedArray sa = obtainStyledAttributes(
    833                             attrs, com.android.internal.R.styleable.PreferenceHeader);
    834                     header.id = sa.getResourceId(
    835                             com.android.internal.R.styleable.PreferenceHeader_id,
    836                             (int)HEADER_ID_UNDEFINED);
    837                     TypedValue tv = sa.peekValue(
    838                             com.android.internal.R.styleable.PreferenceHeader_title);
    839                     if (tv != null && tv.type == TypedValue.TYPE_STRING) {
    840                         if (tv.resourceId != 0) {
    841                             header.titleRes = tv.resourceId;
    842                         } else {
    843                             header.title = tv.string;
    844                         }
    845                     }
    846                     tv = sa.peekValue(
    847                             com.android.internal.R.styleable.PreferenceHeader_summary);
    848                     if (tv != null && tv.type == TypedValue.TYPE_STRING) {
    849                         if (tv.resourceId != 0) {
    850                             header.summaryRes = tv.resourceId;
    851                         } else {
    852                             header.summary = tv.string;
    853                         }
    854                     }
    855                     tv = sa.peekValue(
    856                             com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
    857                     if (tv != null && tv.type == TypedValue.TYPE_STRING) {
    858                         if (tv.resourceId != 0) {
    859                             header.breadCrumbTitleRes = tv.resourceId;
    860                         } else {
    861                             header.breadCrumbTitle = tv.string;
    862                         }
    863                     }
    864                     tv = sa.peekValue(
    865                             com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
    866                     if (tv != null && tv.type == TypedValue.TYPE_STRING) {
    867                         if (tv.resourceId != 0) {
    868                             header.breadCrumbShortTitleRes = tv.resourceId;
    869                         } else {
    870                             header.breadCrumbShortTitle = tv.string;
    871                         }
    872                     }
    873                     header.iconRes = sa.getResourceId(
    874                             com.android.internal.R.styleable.PreferenceHeader_icon, 0);
    875                     header.fragment = sa.getString(
    876                             com.android.internal.R.styleable.PreferenceHeader_fragment);
    877                     sa.recycle();
    878 
    879                     if (curBundle == null) {
    880                         curBundle = new Bundle();
    881                     }
    882 
    883                     final int innerDepth = parser.getDepth();
    884                     while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    885                            && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
    886                         if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    887                             continue;
    888                         }
    889 
    890                         String innerNodeName = parser.getName();
    891                         if (innerNodeName.equals("extra")) {
    892                             getResources().parseBundleExtra("extra", attrs, curBundle);
    893                             XmlUtils.skipCurrentTag(parser);
    894 
    895                         } else if (innerNodeName.equals("intent")) {
    896                             header.intent = Intent.parseIntent(getResources(), parser, attrs);
    897 
    898                         } else {
    899                             XmlUtils.skipCurrentTag(parser);
    900                         }
    901                     }
    902 
    903                     if (curBundle.size() > 0) {
    904                         header.fragmentArguments = curBundle;
    905                         curBundle = null;
    906                     }
    907 
    908                     target.add(header);
    909                 } else {
    910                     XmlUtils.skipCurrentTag(parser);
    911                 }
    912             }
    913 
    914         } catch (XmlPullParserException e) {
    915             throw new RuntimeException("Error parsing headers", e);
    916         } catch (IOException e) {
    917             throw new RuntimeException("Error parsing headers", e);
    918         } finally {
    919             if (parser != null) parser.close();
    920         }
    921     }
    922 
    923     /**
    924      * Subclasses should override this method and verify that the given fragment is a valid type
    925      * to be attached to this activity. The default implementation returns <code>true</code> for
    926      * apps built for <code>android:targetSdkVersion</code> older than
    927      * {@link android.os.Build.VERSION_CODES#KITKAT}. For later versions, it will throw an exception.
    928      * @param fragmentName the class name of the Fragment about to be attached to this activity.
    929      * @return true if the fragment class name is valid for this Activity and false otherwise.
    930      */
    931     protected boolean isValidFragment(String fragmentName) {
    932         if (getApplicationInfo().targetSdkVersion  >= android.os.Build.VERSION_CODES.KITKAT) {
    933             throw new RuntimeException(
    934                     "Subclasses of PreferenceActivity must override isValidFragment(String)"
    935                     + " to verify that the Fragment class is valid! " + this.getClass().getName()
    936                     + " has not checked if fragment " + fragmentName + " is valid.");
    937         } else {
    938             return true;
    939         }
    940     }
    941 
    942     /**
    943      * Set a footer that should be shown at the bottom of the header list.
    944      */
    945     public void setListFooter(View view) {
    946         mListFooter.removeAllViews();
    947         mListFooter.addView(view, new FrameLayout.LayoutParams(
    948                 FrameLayout.LayoutParams.MATCH_PARENT,
    949                 FrameLayout.LayoutParams.WRAP_CONTENT));
    950     }
    951 
    952     @Override
    953     protected void onStop() {
    954         super.onStop();
    955 
    956         if (mPreferenceManager != null) {
    957             mPreferenceManager.dispatchActivityStop();
    958         }
    959     }
    960 
    961     @Override
    962     protected void onDestroy() {
    963         mHandler.removeMessages(MSG_BIND_PREFERENCES);
    964         mHandler.removeMessages(MSG_BUILD_HEADERS);
    965         super.onDestroy();
    966 
    967         if (mPreferenceManager != null) {
    968             mPreferenceManager.dispatchActivityDestroy();
    969         }
    970     }
    971 
    972     @Override
    973     protected void onSaveInstanceState(Bundle outState) {
    974         super.onSaveInstanceState(outState);
    975 
    976         if (mHeaders.size() > 0) {
    977             outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
    978             if (mCurHeader != null) {
    979                 int index = mHeaders.indexOf(mCurHeader);
    980                 if (index >= 0) {
    981                     outState.putInt(CUR_HEADER_TAG, index);
    982                 }
    983             }
    984         }
    985 
    986         if (mPreferenceManager != null) {
    987             final PreferenceScreen preferenceScreen = getPreferenceScreen();
    988             if (preferenceScreen != null) {
    989                 Bundle container = new Bundle();
    990                 preferenceScreen.saveHierarchyState(container);
    991                 outState.putBundle(PREFERENCES_TAG, container);
    992             }
    993         }
    994     }
    995 
    996     @Override
    997     protected void onRestoreInstanceState(Bundle state) {
    998         if (mPreferenceManager != null) {
    999             Bundle container = state.getBundle(PREFERENCES_TAG);
   1000             if (container != null) {
   1001                 final PreferenceScreen preferenceScreen = getPreferenceScreen();
   1002                 if (preferenceScreen != null) {
   1003                     preferenceScreen.restoreHierarchyState(container);
   1004                     mSavedInstanceState = state;
   1005                     return;
   1006                 }
   1007             }
   1008         }
   1009 
   1010         // Only call this if we didn't save the instance state for later.
   1011         // If we did save it, it will be restored when we bind the adapter.
   1012         super.onRestoreInstanceState(state);
   1013     }
   1014 
   1015     @Override
   1016     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
   1017         super.onActivityResult(requestCode, resultCode, data);
   1018 
   1019         if (mPreferenceManager != null) {
   1020             mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
   1021         }
   1022     }
   1023 
   1024     @Override
   1025     public void onContentChanged() {
   1026         super.onContentChanged();
   1027 
   1028         if (mPreferenceManager != null) {
   1029             postBindPreferences();
   1030         }
   1031     }
   1032 
   1033     @Override
   1034     protected void onListItemClick(ListView l, View v, int position, long id) {
   1035         if (!isResumed()) {
   1036             return;
   1037         }
   1038         super.onListItemClick(l, v, position, id);
   1039 
   1040         if (mAdapter != null) {
   1041             Object item = mAdapter.getItem(position);
   1042             if (item instanceof Header) onHeaderClick((Header) item, position);
   1043         }
   1044     }
   1045 
   1046     /**
   1047      * Called when the user selects an item in the header list.  The default
   1048      * implementation will call either
   1049      * {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
   1050      * or {@link #switchToHeader(Header)} as appropriate.
   1051      *
   1052      * @param header The header that was selected.
   1053      * @param position The header's position in the list.
   1054      */
   1055     public void onHeaderClick(Header header, int position) {
   1056         if (header.fragment != null) {
   1057             if (mSinglePane) {
   1058                 int titleRes = header.breadCrumbTitleRes;
   1059                 int shortTitleRes = header.breadCrumbShortTitleRes;
   1060                 if (titleRes == 0) {
   1061                     titleRes = header.titleRes;
   1062                     shortTitleRes = 0;
   1063                 }
   1064                 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
   1065                         titleRes, shortTitleRes);
   1066             } else {
   1067                 switchToHeader(header);
   1068             }
   1069         } else if (header.intent != null) {
   1070             startActivity(header.intent);
   1071         }
   1072     }
   1073 
   1074     /**
   1075      * Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when
   1076      * in single-pane mode, to build an Intent to launch a new activity showing
   1077      * the selected fragment.  The default implementation constructs an Intent
   1078      * that re-launches the current activity with the appropriate arguments to
   1079      * display the fragment.
   1080      *
   1081      * @param fragmentName The name of the fragment to display.
   1082      * @param args Optional arguments to supply to the fragment.
   1083      * @param titleRes Optional resource ID of title to show for this item.
   1084      * @param shortTitleRes Optional resource ID of short title to show for this item.
   1085      * @return Returns an Intent that can be launched to display the given
   1086      * fragment.
   1087      */
   1088     public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
   1089             int titleRes, int shortTitleRes) {
   1090         Intent intent = new Intent(Intent.ACTION_MAIN);
   1091         intent.setClass(this, getClass());
   1092         intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
   1093         intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
   1094         intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes);
   1095         intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes);
   1096         intent.putExtra(EXTRA_NO_HEADERS, true);
   1097         return intent;
   1098     }
   1099 
   1100     /**
   1101      * Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
   1102      * but uses a 0 titleRes.
   1103      */
   1104     public void startWithFragment(String fragmentName, Bundle args,
   1105             Fragment resultTo, int resultRequestCode) {
   1106         startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0);
   1107     }
   1108 
   1109     /**
   1110      * Start a new instance of this activity, showing only the given
   1111      * preference fragment.  When launched in this mode, the header list
   1112      * will be hidden and the given preference fragment will be instantiated
   1113      * and fill the entire activity.
   1114      *
   1115      * @param fragmentName The name of the fragment to display.
   1116      * @param args Optional arguments to supply to the fragment.
   1117      * @param resultTo Option fragment that should receive the result of
   1118      * the activity launch.
   1119      * @param resultRequestCode If resultTo is non-null, this is the request
   1120      * code in which to report the result.
   1121      * @param titleRes Resource ID of string to display for the title of
   1122      * this set of preferences.
   1123      * @param shortTitleRes Resource ID of string to display for the short title of
   1124      * this set of preferences.
   1125      */
   1126     public void startWithFragment(String fragmentName, Bundle args,
   1127             Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) {
   1128         Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
   1129         if (resultTo == null) {
   1130             startActivity(intent);
   1131         } else {
   1132             resultTo.startActivityForResult(intent, resultRequestCode);
   1133         }
   1134     }
   1135 
   1136     /**
   1137      * Change the base title of the bread crumbs for the current preferences.
   1138      * This will normally be called for you.  See
   1139      * {@link android.app.FragmentBreadCrumbs} for more information.
   1140      */
   1141     public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
   1142         if (mFragmentBreadCrumbs == null) {
   1143             View crumbs = findViewById(android.R.id.title);
   1144             // For screens with a different kind of title, don't create breadcrumbs.
   1145             try {
   1146                 mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
   1147             } catch (ClassCastException e) {
   1148                 setTitle(title);
   1149                 return;
   1150             }
   1151             if (mFragmentBreadCrumbs == null) {
   1152                 if (title != null) {
   1153                     setTitle(title);
   1154                 }
   1155                 return;
   1156             }
   1157             if (mSinglePane) {
   1158                 mFragmentBreadCrumbs.setVisibility(View.GONE);
   1159                 // Hide the breadcrumb section completely for single-pane
   1160                 View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section);
   1161                 if (bcSection != null) bcSection.setVisibility(View.GONE);
   1162                 setTitle(title);
   1163             }
   1164             mFragmentBreadCrumbs.setMaxVisible(2);
   1165             mFragmentBreadCrumbs.setActivity(this);
   1166         }
   1167         if (mFragmentBreadCrumbs.getVisibility() != View.VISIBLE) {
   1168             setTitle(title);
   1169         } else {
   1170             mFragmentBreadCrumbs.setTitle(title, shortTitle);
   1171             mFragmentBreadCrumbs.setParentTitle(null, null, null);
   1172         }
   1173     }
   1174 
   1175     /**
   1176      * Should be called after onCreate to ensure that the breadcrumbs, if any, were created.
   1177      * This prepends a title to the fragment breadcrumbs and attaches a listener to any clicks
   1178      * on the parent entry.
   1179      * @param title the title for the breadcrumb
   1180      * @param shortTitle the short title for the breadcrumb
   1181      */
   1182     public void setParentTitle(CharSequence title, CharSequence shortTitle,
   1183             OnClickListener listener) {
   1184         if (mFragmentBreadCrumbs != null) {
   1185             mFragmentBreadCrumbs.setParentTitle(title, shortTitle, listener);
   1186         }
   1187     }
   1188 
   1189     void setSelectedHeader(Header header) {
   1190         mCurHeader = header;
   1191         int index = mHeaders.indexOf(header);
   1192         if (index >= 0) {
   1193             getListView().setItemChecked(index, true);
   1194         } else {
   1195             getListView().clearChoices();
   1196         }
   1197         showBreadCrumbs(header);
   1198     }
   1199 
   1200     void showBreadCrumbs(Header header) {
   1201         if (header != null) {
   1202             CharSequence title = header.getBreadCrumbTitle(getResources());
   1203             if (title == null) title = header.getTitle(getResources());
   1204             if (title == null) title = getTitle();
   1205             showBreadCrumbs(title, header.getBreadCrumbShortTitle(getResources()));
   1206         } else {
   1207             showBreadCrumbs(getTitle(), null);
   1208         }
   1209     }
   1210 
   1211     private void switchToHeaderInner(String fragmentName, Bundle args) {
   1212         getFragmentManager().popBackStack(BACK_STACK_PREFS,
   1213                 FragmentManager.POP_BACK_STACK_INCLUSIVE);
   1214         if (!isValidFragment(fragmentName)) {
   1215             throw new IllegalArgumentException("Invalid fragment for this activity: "
   1216                     + fragmentName);
   1217         }
   1218         Fragment f = Fragment.instantiate(this, fragmentName, args);
   1219         FragmentTransaction transaction = getFragmentManager().beginTransaction();
   1220         transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
   1221         transaction.replace(com.android.internal.R.id.prefs, f);
   1222         transaction.commitAllowingStateLoss();
   1223     }
   1224 
   1225     /**
   1226      * When in two-pane mode, switch the fragment pane to show the given
   1227      * preference fragment.
   1228      *
   1229      * @param fragmentName The name of the fragment to display.
   1230      * @param args Optional arguments to supply to the fragment.
   1231      */
   1232     public void switchToHeader(String fragmentName, Bundle args) {
   1233         Header selectedHeader = null;
   1234         for (int i = 0; i < mHeaders.size(); i++) {
   1235             if (fragmentName.equals(mHeaders.get(i).fragment)) {
   1236                 selectedHeader = mHeaders.get(i);
   1237                 break;
   1238             }
   1239         }
   1240         setSelectedHeader(selectedHeader);
   1241         switchToHeaderInner(fragmentName, args);
   1242     }
   1243 
   1244     /**
   1245      * When in two-pane mode, switch to the fragment pane to show the given
   1246      * preference fragment.
   1247      *
   1248      * @param header The new header to display.
   1249      */
   1250     public void switchToHeader(Header header) {
   1251         if (mCurHeader == header) {
   1252             // This is the header we are currently displaying.  Just make sure
   1253             // to pop the stack up to its root state.
   1254             getFragmentManager().popBackStack(BACK_STACK_PREFS,
   1255                     FragmentManager.POP_BACK_STACK_INCLUSIVE);
   1256         } else {
   1257             if (header.fragment == null) {
   1258                 throw new IllegalStateException("can't switch to header that has no fragment");
   1259             }
   1260             switchToHeaderInner(header.fragment, header.fragmentArguments);
   1261             setSelectedHeader(header);
   1262         }
   1263     }
   1264 
   1265     Header findBestMatchingHeader(Header cur, ArrayList<Header> from) {
   1266         ArrayList<Header> matches = new ArrayList<Header>();
   1267         for (int j=0; j<from.size(); j++) {
   1268             Header oh = from.get(j);
   1269             if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) {
   1270                 // Must be this one.
   1271                 matches.clear();
   1272                 matches.add(oh);
   1273                 break;
   1274             }
   1275             if (cur.fragment != null) {
   1276                 if (cur.fragment.equals(oh.fragment)) {
   1277                     matches.add(oh);
   1278                 }
   1279             } else if (cur.intent != null) {
   1280                 if (cur.intent.equals(oh.intent)) {
   1281                     matches.add(oh);
   1282                 }
   1283             } else if (cur.title != null) {
   1284                 if (cur.title.equals(oh.title)) {
   1285                     matches.add(oh);
   1286                 }
   1287             }
   1288         }
   1289         final int NM = matches.size();
   1290         if (NM == 1) {
   1291             return matches.get(0);
   1292         } else if (NM > 1) {
   1293             for (int j=0; j<NM; j++) {
   1294                 Header oh = matches.get(j);
   1295                 if (cur.fragmentArguments != null &&
   1296                         cur.fragmentArguments.equals(oh.fragmentArguments)) {
   1297                     return oh;
   1298                 }
   1299                 if (cur.extras != null && cur.extras.equals(oh.extras)) {
   1300                     return oh;
   1301                 }
   1302                 if (cur.title != null && cur.title.equals(oh.title)) {
   1303                     return oh;
   1304                 }
   1305             }
   1306         }
   1307         return null;
   1308     }
   1309 
   1310     /**
   1311      * Start a new fragment.
   1312      *
   1313      * @param fragment The fragment to start
   1314      * @param push If true, the current fragment will be pushed onto the back stack.  If false,
   1315      * the current fragment will be replaced.
   1316      */
   1317     public void startPreferenceFragment(Fragment fragment, boolean push) {
   1318         FragmentTransaction transaction = getFragmentManager().beginTransaction();
   1319         transaction.replace(com.android.internal.R.id.prefs, fragment);
   1320         if (push) {
   1321             transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
   1322             transaction.addToBackStack(BACK_STACK_PREFS);
   1323         } else {
   1324             transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
   1325         }
   1326         transaction.commitAllowingStateLoss();
   1327     }
   1328 
   1329     /**
   1330      * Start a new fragment containing a preference panel.  If the preferences
   1331      * are being displayed in multi-pane mode, the given fragment class will
   1332      * be instantiated and placed in the appropriate pane.  If running in
   1333      * single-pane mode, a new activity will be launched in which to show the
   1334      * fragment.
   1335      *
   1336      * @param fragmentClass Full name of the class implementing the fragment.
   1337      * @param args Any desired arguments to supply to the fragment.
   1338      * @param titleRes Optional resource identifier of the title of this
   1339      * fragment.
   1340      * @param titleText Optional text of the title of this fragment.
   1341      * @param resultTo Optional fragment that result data should be sent to.
   1342      * If non-null, resultTo.onActivityResult() will be called when this
   1343      * preference panel is done.  The launched panel must use
   1344      * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
   1345      * @param resultRequestCode If resultTo is non-null, this is the caller's
   1346      * request code to be received with the resut.
   1347      */
   1348     public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
   1349             CharSequence titleText, Fragment resultTo, int resultRequestCode) {
   1350         if (mSinglePane) {
   1351             startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, 0);
   1352         } else {
   1353             Fragment f = Fragment.instantiate(this, fragmentClass, args);
   1354             if (resultTo != null) {
   1355                 f.setTargetFragment(resultTo, resultRequestCode);
   1356             }
   1357             FragmentTransaction transaction = getFragmentManager().beginTransaction();
   1358             transaction.replace(com.android.internal.R.id.prefs, f);
   1359             if (titleRes != 0) {
   1360                 transaction.setBreadCrumbTitle(titleRes);
   1361             } else if (titleText != null) {
   1362                 transaction.setBreadCrumbTitle(titleText);
   1363             }
   1364             transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
   1365             transaction.addToBackStack(BACK_STACK_PREFS);
   1366             transaction.commitAllowingStateLoss();
   1367         }
   1368     }
   1369 
   1370     /**
   1371      * Called by a preference panel fragment to finish itself.
   1372      *
   1373      * @param caller The fragment that is asking to be finished.
   1374      * @param resultCode Optional result code to send back to the original
   1375      * launching fragment.
   1376      * @param resultData Optional result data to send back to the original
   1377      * launching fragment.
   1378      */
   1379     public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
   1380         if (mSinglePane) {
   1381             setResult(resultCode, resultData);
   1382             finish();
   1383         } else {
   1384             // XXX be smarter about popping the stack.
   1385             onBackPressed();
   1386             if (caller != null) {
   1387                 if (caller.getTargetFragment() != null) {
   1388                     caller.getTargetFragment().onActivityResult(caller.getTargetRequestCode(),
   1389                             resultCode, resultData);
   1390                 }
   1391             }
   1392         }
   1393     }
   1394 
   1395     @Override
   1396     public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
   1397         startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(),
   1398                 pref.getTitle(), null, 0);
   1399         return true;
   1400     }
   1401 
   1402     /**
   1403      * Posts a message to bind the preferences to the list view.
   1404      * <p>
   1405      * Binding late is preferred as any custom preference types created in
   1406      * {@link #onCreate(Bundle)} are able to have their views recycled.
   1407      */
   1408     private void postBindPreferences() {
   1409         if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
   1410         mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
   1411     }
   1412 
   1413     private void bindPreferences() {
   1414         final PreferenceScreen preferenceScreen = getPreferenceScreen();
   1415         if (preferenceScreen != null) {
   1416             preferenceScreen.bind(getListView());
   1417             if (mSavedInstanceState != null) {
   1418                 super.onRestoreInstanceState(mSavedInstanceState);
   1419                 mSavedInstanceState = null;
   1420             }
   1421         }
   1422     }
   1423 
   1424     /**
   1425      * Returns the {@link PreferenceManager} used by this activity.
   1426      * @return The {@link PreferenceManager}.
   1427      *
   1428      * @deprecated This function is not relevant for a modern fragment-based
   1429      * PreferenceActivity.
   1430      */
   1431     @Deprecated
   1432     public PreferenceManager getPreferenceManager() {
   1433         return mPreferenceManager;
   1434     }
   1435 
   1436     private void requirePreferenceManager() {
   1437         if (mPreferenceManager == null) {
   1438             if (mAdapter == null) {
   1439                 throw new RuntimeException("This should be called after super.onCreate.");
   1440             }
   1441             throw new RuntimeException(
   1442                     "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
   1443         }
   1444     }
   1445 
   1446     /**
   1447      * Sets the root of the preference hierarchy that this activity is showing.
   1448      *
   1449      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
   1450      *
   1451      * @deprecated This function is not relevant for a modern fragment-based
   1452      * PreferenceActivity.
   1453      */
   1454     @Deprecated
   1455     public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
   1456         requirePreferenceManager();
   1457 
   1458         if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
   1459             postBindPreferences();
   1460             CharSequence title = getPreferenceScreen().getTitle();
   1461             // Set the title of the activity
   1462             if (title != null) {
   1463                 setTitle(title);
   1464             }
   1465         }
   1466     }
   1467 
   1468     /**
   1469      * Gets the root of the preference hierarchy that this activity is showing.
   1470      *
   1471      * @return The {@link PreferenceScreen} that is the root of the preference
   1472      *         hierarchy.
   1473      *
   1474      * @deprecated This function is not relevant for a modern fragment-based
   1475      * PreferenceActivity.
   1476      */
   1477     @Deprecated
   1478     public PreferenceScreen getPreferenceScreen() {
   1479         if (mPreferenceManager != null) {
   1480             return mPreferenceManager.getPreferenceScreen();
   1481         }
   1482         return null;
   1483     }
   1484 
   1485     /**
   1486      * Adds preferences from activities that match the given {@link Intent}.
   1487      *
   1488      * @param intent The {@link Intent} to query activities.
   1489      *
   1490      * @deprecated This function is not relevant for a modern fragment-based
   1491      * PreferenceActivity.
   1492      */
   1493     @Deprecated
   1494     public void addPreferencesFromIntent(Intent intent) {
   1495         requirePreferenceManager();
   1496 
   1497         setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
   1498     }
   1499 
   1500     /**
   1501      * Inflates the given XML resource and adds the preference hierarchy to the current
   1502      * preference hierarchy.
   1503      *
   1504      * @param preferencesResId The XML resource ID to inflate.
   1505      *
   1506      * @deprecated This function is not relevant for a modern fragment-based
   1507      * PreferenceActivity.
   1508      */
   1509     @Deprecated
   1510     public void addPreferencesFromResource(int preferencesResId) {
   1511         requirePreferenceManager();
   1512 
   1513         setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
   1514                 getPreferenceScreen()));
   1515     }
   1516 
   1517     /**
   1518      * {@inheritDoc}
   1519      *
   1520      * @deprecated This function is not relevant for a modern fragment-based
   1521      * PreferenceActivity.
   1522      */
   1523     @Deprecated
   1524     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
   1525         return false;
   1526     }
   1527 
   1528     /**
   1529      * Finds a {@link Preference} based on its key.
   1530      *
   1531      * @param key The key of the preference to retrieve.
   1532      * @return The {@link Preference} with the key, or null.
   1533      * @see PreferenceGroup#findPreference(CharSequence)
   1534      *
   1535      * @deprecated This function is not relevant for a modern fragment-based
   1536      * PreferenceActivity.
   1537      */
   1538     @Deprecated
   1539     public Preference findPreference(CharSequence key) {
   1540 
   1541         if (mPreferenceManager == null) {
   1542             return null;
   1543         }
   1544 
   1545         return mPreferenceManager.findPreference(key);
   1546     }
   1547 
   1548     @Override
   1549     protected void onNewIntent(Intent intent) {
   1550         if (mPreferenceManager != null) {
   1551             mPreferenceManager.dispatchNewIntent(intent);
   1552         }
   1553     }
   1554 
   1555     // give subclasses access to the Next button
   1556     /** @hide */
   1557     protected boolean hasNextButton() {
   1558         return mNextButton != null;
   1559     }
   1560     /** @hide */
   1561     protected Button getNextButton() {
   1562         return mNextButton;
   1563     }
   1564 }
   1565