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