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