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