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