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         for (int i=0; i<mHeaders.size(); i++) {
    707             Header h = mHeaders.get(i);
    708             if (h.fragment != null) {
    709                 return h;
    710             }
    711         }
    712         throw new IllegalStateException("Must have at least one header with a fragment");
    713     }
    714 
    715     /**
    716      * Called after the header list has been updated ({@link #onBuildHeaders}
    717      * has been called and returned due to {@link #invalidateHeaders()}) to
    718      * specify the header that should now be selected.  The default implementation
    719      * returns null to keep whatever header is currently selected.
    720      */
    721     public Header onGetNewHeader() {
    722         return null;
    723     }
    724 
    725     /**
    726      * Called when the activity needs its list of headers build.  By
    727      * implementing this and adding at least one item to the list, you
    728      * will cause the activity to run in its modern fragment mode.  Note
    729      * that this function may not always be called; for example, if the
    730      * activity has been asked to display a particular fragment without
    731      * the header list, there is no need to build the headers.
    732      *
    733      * <p>Typical implementations will use {@link #loadHeadersFromResource}
    734      * to fill in the list from a resource.
    735      *
    736      * @param target The list in which to place the headers.
    737      */
    738     public void onBuildHeaders(List<Header> target) {
    739         // Should be overloaded by subclasses
    740     }
    741 
    742     /**
    743      * Call when you need to change the headers being displayed.  Will result
    744      * in onBuildHeaders() later being called to retrieve the new list.
    745      */
    746     public void invalidateHeaders() {
    747         if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
    748             mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
    749         }
    750     }
    751 
    752     /**
    753      * Parse the given XML file as a header description, adding each
    754      * parsed Header into the target list.
    755      *
    756      * @param resid The XML resource to load and parse.
    757      * @param target The list in which the parsed headers should be placed.
    758      */
    759     public void loadHeadersFromResource(int resid, List<Header> target) {
    760         XmlResourceParser parser = null;
    761         try {
    762             parser = getResources().getXml(resid);
    763             AttributeSet attrs = Xml.asAttributeSet(parser);
    764 
    765             int type;
    766             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    767                     && type != XmlPullParser.START_TAG) {
    768                 // Parse next until start tag is found
    769             }
    770 
    771             String nodeName = parser.getName();
    772             if (!"preference-headers".equals(nodeName)) {
    773                 throw new RuntimeException(
    774                         "XML document must start with <preference-headers> tag; found"
    775                         + nodeName + " at " + parser.getPositionDescription());
    776             }
    777 
    778             Bundle curBundle = null;
    779 
    780             final int outerDepth = parser.getDepth();
    781             while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    782                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
    783                 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    784                     continue;
    785                 }
    786 
    787                 nodeName = parser.getName();
    788                 if ("header".equals(nodeName)) {
    789                     Header header = new Header();
    790 
    791                     TypedArray sa = getResources().obtainAttributes(attrs,
    792                             com.android.internal.R.styleable.PreferenceHeader);
    793                     header.id = sa.getResourceId(
    794                             com.android.internal.R.styleable.PreferenceHeader_id,
    795                             (int)HEADER_ID_UNDEFINED);
    796                     TypedValue tv = sa.peekValue(
    797                             com.android.internal.R.styleable.PreferenceHeader_title);
    798                     if (tv != null && tv.type == TypedValue.TYPE_STRING) {
    799                         if (tv.resourceId != 0) {
    800                             header.titleRes = tv.resourceId;
    801                         } else {
    802                             header.title = tv.string;
    803                         }
    804                     }
    805                     tv = sa.peekValue(
    806                             com.android.internal.R.styleable.PreferenceHeader_summary);
    807                     if (tv != null && tv.type == TypedValue.TYPE_STRING) {
    808                         if (tv.resourceId != 0) {
    809                             header.summaryRes = tv.resourceId;
    810                         } else {
    811                             header.summary = tv.string;
    812                         }
    813                     }
    814                     tv = sa.peekValue(
    815                             com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
    816                     if (tv != null && tv.type == TypedValue.TYPE_STRING) {
    817                         if (tv.resourceId != 0) {
    818                             header.breadCrumbTitleRes = tv.resourceId;
    819                         } else {
    820                             header.breadCrumbTitle = tv.string;
    821                         }
    822                     }
    823                     tv = sa.peekValue(
    824                             com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
    825                     if (tv != null && tv.type == TypedValue.TYPE_STRING) {
    826                         if (tv.resourceId != 0) {
    827                             header.breadCrumbShortTitleRes = tv.resourceId;
    828                         } else {
    829                             header.breadCrumbShortTitle = tv.string;
    830                         }
    831                     }
    832                     header.iconRes = sa.getResourceId(
    833                             com.android.internal.R.styleable.PreferenceHeader_icon, 0);
    834                     header.fragment = sa.getString(
    835                             com.android.internal.R.styleable.PreferenceHeader_fragment);
    836                     sa.recycle();
    837 
    838                     if (curBundle == null) {
    839                         curBundle = new Bundle();
    840                     }
    841 
    842                     final int innerDepth = parser.getDepth();
    843                     while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
    844                            && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
    845                         if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
    846                             continue;
    847                         }
    848 
    849                         String innerNodeName = parser.getName();
    850                         if (innerNodeName.equals("extra")) {
    851                             getResources().parseBundleExtra("extra", attrs, curBundle);
    852                             XmlUtils.skipCurrentTag(parser);
    853 
    854                         } else if (innerNodeName.equals("intent")) {
    855                             header.intent = Intent.parseIntent(getResources(), parser, attrs);
    856 
    857                         } else {
    858                             XmlUtils.skipCurrentTag(parser);
    859                         }
    860                     }
    861 
    862                     if (curBundle.size() > 0) {
    863                         header.fragmentArguments = curBundle;
    864                         curBundle = null;
    865                     }
    866 
    867                     target.add(header);
    868                 } else {
    869                     XmlUtils.skipCurrentTag(parser);
    870                 }
    871             }
    872 
    873         } catch (XmlPullParserException e) {
    874             throw new RuntimeException("Error parsing headers", e);
    875         } catch (IOException e) {
    876             throw new RuntimeException("Error parsing headers", e);
    877         } finally {
    878             if (parser != null) parser.close();
    879         }
    880 
    881     }
    882 
    883     /**
    884      * Set a footer that should be shown at the bottom of the header list.
    885      */
    886     public void setListFooter(View view) {
    887         mListFooter.removeAllViews();
    888         mListFooter.addView(view, new FrameLayout.LayoutParams(
    889                 FrameLayout.LayoutParams.MATCH_PARENT,
    890                 FrameLayout.LayoutParams.WRAP_CONTENT));
    891     }
    892 
    893     @Override
    894     protected void onStop() {
    895         super.onStop();
    896 
    897         if (mPreferenceManager != null) {
    898             mPreferenceManager.dispatchActivityStop();
    899         }
    900     }
    901 
    902     @Override
    903     protected void onDestroy() {
    904         super.onDestroy();
    905 
    906         if (mPreferenceManager != null) {
    907             mPreferenceManager.dispatchActivityDestroy();
    908         }
    909     }
    910 
    911     @Override
    912     protected void onSaveInstanceState(Bundle outState) {
    913         super.onSaveInstanceState(outState);
    914 
    915         if (mHeaders.size() > 0) {
    916             outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
    917             if (mCurHeader != null) {
    918                 int index = mHeaders.indexOf(mCurHeader);
    919                 if (index >= 0) {
    920                     outState.putInt(CUR_HEADER_TAG, index);
    921                 }
    922             }
    923         }
    924 
    925         if (mPreferenceManager != null) {
    926             final PreferenceScreen preferenceScreen = getPreferenceScreen();
    927             if (preferenceScreen != null) {
    928                 Bundle container = new Bundle();
    929                 preferenceScreen.saveHierarchyState(container);
    930                 outState.putBundle(PREFERENCES_TAG, container);
    931             }
    932         }
    933     }
    934 
    935     @Override
    936     protected void onRestoreInstanceState(Bundle state) {
    937         if (mPreferenceManager != null) {
    938             Bundle container = state.getBundle(PREFERENCES_TAG);
    939             if (container != null) {
    940                 final PreferenceScreen preferenceScreen = getPreferenceScreen();
    941                 if (preferenceScreen != null) {
    942                     preferenceScreen.restoreHierarchyState(container);
    943                     mSavedInstanceState = state;
    944                     return;
    945                 }
    946             }
    947         }
    948 
    949         // Only call this if we didn't save the instance state for later.
    950         // If we did save it, it will be restored when we bind the adapter.
    951         super.onRestoreInstanceState(state);
    952     }
    953 
    954     @Override
    955     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    956         super.onActivityResult(requestCode, resultCode, data);
    957 
    958         if (mPreferenceManager != null) {
    959             mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
    960         }
    961     }
    962 
    963     @Override
    964     public void onContentChanged() {
    965         super.onContentChanged();
    966 
    967         if (mPreferenceManager != null) {
    968             postBindPreferences();
    969         }
    970     }
    971 
    972     @Override
    973     protected void onListItemClick(ListView l, View v, int position, long id) {
    974         super.onListItemClick(l, v, position, id);
    975 
    976         if (mAdapter != null) {
    977             Object item = mAdapter.getItem(position);
    978             if (item instanceof Header) onHeaderClick((Header) item, position);
    979         }
    980     }
    981 
    982     /**
    983      * Called when the user selects an item in the header list.  The default
    984      * implementation will call either
    985      * {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
    986      * or {@link #switchToHeader(Header)} as appropriate.
    987      *
    988      * @param header The header that was selected.
    989      * @param position The header's position in the list.
    990      */
    991     public void onHeaderClick(Header header, int position) {
    992         if (header.fragment != null) {
    993             if (mSinglePane) {
    994                 int titleRes = header.breadCrumbTitleRes;
    995                 int shortTitleRes = header.breadCrumbShortTitleRes;
    996                 if (titleRes == 0) {
    997                     titleRes = header.titleRes;
    998                     shortTitleRes = 0;
    999                 }
   1000                 startWithFragment(header.fragment, header.fragmentArguments, null, 0,
   1001                         titleRes, shortTitleRes);
   1002             } else {
   1003                 switchToHeader(header);
   1004             }
   1005         } else if (header.intent != null) {
   1006             startActivity(header.intent);
   1007         }
   1008     }
   1009 
   1010     /**
   1011      * Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when
   1012      * in single-pane mode, to build an Intent to launch a new activity showing
   1013      * the selected fragment.  The default implementation constructs an Intent
   1014      * that re-launches the current activity with the appropriate arguments to
   1015      * display the fragment.
   1016      *
   1017      * @param fragmentName The name of the fragment to display.
   1018      * @param args Optional arguments to supply to the fragment.
   1019      * @param titleRes Optional resource ID of title to show for this item.
   1020      * @param shortTitleRes Optional resource ID of short title to show for this item.
   1021      * @return Returns an Intent that can be launched to display the given
   1022      * fragment.
   1023      */
   1024     public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
   1025             int titleRes, int shortTitleRes) {
   1026         Intent intent = new Intent(Intent.ACTION_MAIN);
   1027         intent.setClass(this, getClass());
   1028         intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
   1029         intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
   1030         intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes);
   1031         intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes);
   1032         intent.putExtra(EXTRA_NO_HEADERS, true);
   1033         return intent;
   1034     }
   1035 
   1036     /**
   1037      * Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
   1038      * but uses a 0 titleRes.
   1039      */
   1040     public void startWithFragment(String fragmentName, Bundle args,
   1041             Fragment resultTo, int resultRequestCode) {
   1042         startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0);
   1043     }
   1044 
   1045     /**
   1046      * Start a new instance of this activity, showing only the given
   1047      * preference fragment.  When launched in this mode, the header list
   1048      * will be hidden and the given preference fragment will be instantiated
   1049      * and fill the entire activity.
   1050      *
   1051      * @param fragmentName The name of the fragment to display.
   1052      * @param args Optional arguments to supply to the fragment.
   1053      * @param resultTo Option fragment that should receive the result of
   1054      * the activity launch.
   1055      * @param resultRequestCode If resultTo is non-null, this is the request
   1056      * code in which to report the result.
   1057      * @param titleRes Resource ID of string to display for the title of
   1058      * this set of preferences.
   1059      * @param shortTitleRes Resource ID of string to display for the short title of
   1060      * this set of preferences.
   1061      */
   1062     public void startWithFragment(String fragmentName, Bundle args,
   1063             Fragment resultTo, int resultRequestCode, int titleRes, int shortTitleRes) {
   1064         Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
   1065         if (resultTo == null) {
   1066             startActivity(intent);
   1067         } else {
   1068             resultTo.startActivityForResult(intent, resultRequestCode);
   1069         }
   1070     }
   1071 
   1072     /**
   1073      * Change the base title of the bread crumbs for the current preferences.
   1074      * This will normally be called for you.  See
   1075      * {@link android.app.FragmentBreadCrumbs} for more information.
   1076      */
   1077     public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
   1078         if (mFragmentBreadCrumbs == null) {
   1079             View crumbs = findViewById(android.R.id.title);
   1080             // For screens with a different kind of title, don't create breadcrumbs.
   1081             try {
   1082                 mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
   1083             } catch (ClassCastException e) {
   1084                 return;
   1085             }
   1086             if (mFragmentBreadCrumbs == null) {
   1087                 if (title != null) {
   1088                     setTitle(title);
   1089                 }
   1090                 return;
   1091             }
   1092             if (mSinglePane) {
   1093                 mFragmentBreadCrumbs.setVisibility(View.GONE);
   1094                 // Hide the breadcrumb section completely for single-pane
   1095                 View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section);
   1096                 if (bcSection != null) bcSection.setVisibility(View.GONE);
   1097             }
   1098             mFragmentBreadCrumbs.setMaxVisible(2);
   1099             mFragmentBreadCrumbs.setActivity(this);
   1100         }
   1101         mFragmentBreadCrumbs.setTitle(title, shortTitle);
   1102         mFragmentBreadCrumbs.setParentTitle(null, null, null);
   1103     }
   1104 
   1105     /**
   1106      * Should be called after onCreate to ensure that the breadcrumbs, if any, were created.
   1107      * This prepends a title to the fragment breadcrumbs and attaches a listener to any clicks
   1108      * on the parent entry.
   1109      * @param title the title for the breadcrumb
   1110      * @param shortTitle the short title for the breadcrumb
   1111      */
   1112     public void setParentTitle(CharSequence title, CharSequence shortTitle,
   1113             OnClickListener listener) {
   1114         if (mFragmentBreadCrumbs != null) {
   1115             mFragmentBreadCrumbs.setParentTitle(title, shortTitle, listener);
   1116         }
   1117     }
   1118 
   1119     void setSelectedHeader(Header header) {
   1120         mCurHeader = header;
   1121         int index = mHeaders.indexOf(header);
   1122         if (index >= 0) {
   1123             getListView().setItemChecked(index, true);
   1124         } else {
   1125             getListView().clearChoices();
   1126         }
   1127         showBreadCrumbs(header);
   1128     }
   1129 
   1130     void showBreadCrumbs(Header header) {
   1131         if (header != null) {
   1132             CharSequence title = header.getBreadCrumbTitle(getResources());
   1133             if (title == null) title = header.getTitle(getResources());
   1134             if (title == null) title = getTitle();
   1135             showBreadCrumbs(title, header.getBreadCrumbShortTitle(getResources()));
   1136         } else {
   1137             showBreadCrumbs(getTitle(), null);
   1138         }
   1139     }
   1140 
   1141     private void switchToHeaderInner(String fragmentName, Bundle args, int direction) {
   1142         getFragmentManager().popBackStack(BACK_STACK_PREFS,
   1143                 FragmentManager.POP_BACK_STACK_INCLUSIVE);
   1144         Fragment f = Fragment.instantiate(this, fragmentName, args);
   1145         FragmentTransaction transaction = getFragmentManager().beginTransaction();
   1146         transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
   1147         transaction.replace(com.android.internal.R.id.prefs, f);
   1148         transaction.commitAllowingStateLoss();
   1149     }
   1150 
   1151     /**
   1152      * When in two-pane mode, switch the fragment pane to show the given
   1153      * preference fragment.
   1154      *
   1155      * @param fragmentName The name of the fragment to display.
   1156      * @param args Optional arguments to supply to the fragment.
   1157      */
   1158     public void switchToHeader(String fragmentName, Bundle args) {
   1159         setSelectedHeader(null);
   1160         switchToHeaderInner(fragmentName, args, 0);
   1161     }
   1162 
   1163     /**
   1164      * When in two-pane mode, switch to the fragment pane to show the given
   1165      * preference fragment.
   1166      *
   1167      * @param header The new header to display.
   1168      */
   1169     public void switchToHeader(Header header) {
   1170         if (mCurHeader == header) {
   1171             // This is the header we are currently displaying.  Just make sure
   1172             // to pop the stack up to its root state.
   1173             getFragmentManager().popBackStack(BACK_STACK_PREFS,
   1174                     FragmentManager.POP_BACK_STACK_INCLUSIVE);
   1175         } else {
   1176             if (header.fragment == null) {
   1177                 throw new IllegalStateException("can't switch to header that has no fragment");
   1178             }
   1179             int direction = mHeaders.indexOf(header) - mHeaders.indexOf(mCurHeader);
   1180             switchToHeaderInner(header.fragment, header.fragmentArguments, direction);
   1181             setSelectedHeader(header);
   1182         }
   1183     }
   1184 
   1185     Header findBestMatchingHeader(Header cur, ArrayList<Header> from) {
   1186         ArrayList<Header> matches = new ArrayList<Header>();
   1187         for (int j=0; j<from.size(); j++) {
   1188             Header oh = from.get(j);
   1189             if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) {
   1190                 // Must be this one.
   1191                 matches.clear();
   1192                 matches.add(oh);
   1193                 break;
   1194             }
   1195             if (cur.fragment != null) {
   1196                 if (cur.fragment.equals(oh.fragment)) {
   1197                     matches.add(oh);
   1198                 }
   1199             } else if (cur.intent != null) {
   1200                 if (cur.intent.equals(oh.intent)) {
   1201                     matches.add(oh);
   1202                 }
   1203             } else if (cur.title != null) {
   1204                 if (cur.title.equals(oh.title)) {
   1205                     matches.add(oh);
   1206                 }
   1207             }
   1208         }
   1209         final int NM = matches.size();
   1210         if (NM == 1) {
   1211             return matches.get(0);
   1212         } else if (NM > 1) {
   1213             for (int j=0; j<NM; j++) {
   1214                 Header oh = matches.get(j);
   1215                 if (cur.fragmentArguments != null &&
   1216                         cur.fragmentArguments.equals(oh.fragmentArguments)) {
   1217                     return oh;
   1218                 }
   1219                 if (cur.extras != null && cur.extras.equals(oh.extras)) {
   1220                     return oh;
   1221                 }
   1222                 if (cur.title != null && cur.title.equals(oh.title)) {
   1223                     return oh;
   1224                 }
   1225             }
   1226         }
   1227         return null;
   1228     }
   1229 
   1230     /**
   1231      * Start a new fragment.
   1232      *
   1233      * @param fragment The fragment to start
   1234      * @param push If true, the current fragment will be pushed onto the back stack.  If false,
   1235      * the current fragment will be replaced.
   1236      */
   1237     public void startPreferenceFragment(Fragment fragment, boolean push) {
   1238         FragmentTransaction transaction = getFragmentManager().beginTransaction();
   1239         transaction.replace(com.android.internal.R.id.prefs, fragment);
   1240         if (push) {
   1241             transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
   1242             transaction.addToBackStack(BACK_STACK_PREFS);
   1243         } else {
   1244             transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
   1245         }
   1246         transaction.commitAllowingStateLoss();
   1247     }
   1248 
   1249     /**
   1250      * Start a new fragment containing a preference panel.  If the prefences
   1251      * are being displayed in multi-pane mode, the given fragment class will
   1252      * be instantiated and placed in the appropriate pane.  If running in
   1253      * single-pane mode, a new activity will be launched in which to show the
   1254      * fragment.
   1255      *
   1256      * @param fragmentClass Full name of the class implementing the fragment.
   1257      * @param args Any desired arguments to supply to the fragment.
   1258      * @param titleRes Optional resource identifier of the title of this
   1259      * fragment.
   1260      * @param titleText Optional text of the title of this fragment.
   1261      * @param resultTo Optional fragment that result data should be sent to.
   1262      * If non-null, resultTo.onActivityResult() will be called when this
   1263      * preference panel is done.  The launched panel must use
   1264      * {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
   1265      * @param resultRequestCode If resultTo is non-null, this is the caller's
   1266      * request code to be received with the resut.
   1267      */
   1268     public void startPreferencePanel(String fragmentClass, Bundle args, int titleRes,
   1269             CharSequence titleText, Fragment resultTo, int resultRequestCode) {
   1270         if (mSinglePane) {
   1271             startWithFragment(fragmentClass, args, resultTo, resultRequestCode, titleRes, 0);
   1272         } else {
   1273             Fragment f = Fragment.instantiate(this, fragmentClass, args);
   1274             if (resultTo != null) {
   1275                 f.setTargetFragment(resultTo, resultRequestCode);
   1276             }
   1277             FragmentTransaction transaction = getFragmentManager().beginTransaction();
   1278             transaction.replace(com.android.internal.R.id.prefs, f);
   1279             if (titleRes != 0) {
   1280                 transaction.setBreadCrumbTitle(titleRes);
   1281             } else if (titleText != null) {
   1282                 transaction.setBreadCrumbTitle(titleText);
   1283             }
   1284             transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
   1285             transaction.addToBackStack(BACK_STACK_PREFS);
   1286             transaction.commitAllowingStateLoss();
   1287         }
   1288     }
   1289 
   1290     /**
   1291      * Called by a preference panel fragment to finish itself.
   1292      *
   1293      * @param caller The fragment that is asking to be finished.
   1294      * @param resultCode Optional result code to send back to the original
   1295      * launching fragment.
   1296      * @param resultData Optional result data to send back to the original
   1297      * launching fragment.
   1298      */
   1299     public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
   1300         if (mSinglePane) {
   1301             setResult(resultCode, resultData);
   1302             finish();
   1303         } else {
   1304             // XXX be smarter about popping the stack.
   1305             onBackPressed();
   1306             if (caller != null) {
   1307                 if (caller.getTargetFragment() != null) {
   1308                     caller.getTargetFragment().onActivityResult(caller.getTargetRequestCode(),
   1309                             resultCode, resultData);
   1310                 }
   1311             }
   1312         }
   1313     }
   1314 
   1315     @Override
   1316     public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
   1317         startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(),
   1318                 pref.getTitle(), null, 0);
   1319         return true;
   1320     }
   1321 
   1322     /**
   1323      * Posts a message to bind the preferences to the list view.
   1324      * <p>
   1325      * Binding late is preferred as any custom preference types created in
   1326      * {@link #onCreate(Bundle)} are able to have their views recycled.
   1327      */
   1328     private void postBindPreferences() {
   1329         if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
   1330         mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
   1331     }
   1332 
   1333     private void bindPreferences() {
   1334         final PreferenceScreen preferenceScreen = getPreferenceScreen();
   1335         if (preferenceScreen != null) {
   1336             preferenceScreen.bind(getListView());
   1337             if (mSavedInstanceState != null) {
   1338                 super.onRestoreInstanceState(mSavedInstanceState);
   1339                 mSavedInstanceState = null;
   1340             }
   1341         }
   1342     }
   1343 
   1344     /**
   1345      * Returns the {@link PreferenceManager} used by this activity.
   1346      * @return The {@link PreferenceManager}.
   1347      *
   1348      * @deprecated This function is not relevant for a modern fragment-based
   1349      * PreferenceActivity.
   1350      */
   1351     @Deprecated
   1352     public PreferenceManager getPreferenceManager() {
   1353         return mPreferenceManager;
   1354     }
   1355 
   1356     private void requirePreferenceManager() {
   1357         if (mPreferenceManager == null) {
   1358             if (mAdapter == null) {
   1359                 throw new RuntimeException("This should be called after super.onCreate.");
   1360             }
   1361             throw new RuntimeException(
   1362                     "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
   1363         }
   1364     }
   1365 
   1366     /**
   1367      * Sets the root of the preference hierarchy that this activity is showing.
   1368      *
   1369      * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
   1370      *
   1371      * @deprecated This function is not relevant for a modern fragment-based
   1372      * PreferenceActivity.
   1373      */
   1374     @Deprecated
   1375     public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
   1376         requirePreferenceManager();
   1377 
   1378         if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
   1379             postBindPreferences();
   1380             CharSequence title = getPreferenceScreen().getTitle();
   1381             // Set the title of the activity
   1382             if (title != null) {
   1383                 setTitle(title);
   1384             }
   1385         }
   1386     }
   1387 
   1388     /**
   1389      * Gets the root of the preference hierarchy that this activity is showing.
   1390      *
   1391      * @return The {@link PreferenceScreen} that is the root of the preference
   1392      *         hierarchy.
   1393      *
   1394      * @deprecated This function is not relevant for a modern fragment-based
   1395      * PreferenceActivity.
   1396      */
   1397     @Deprecated
   1398     public PreferenceScreen getPreferenceScreen() {
   1399         if (mPreferenceManager != null) {
   1400             return mPreferenceManager.getPreferenceScreen();
   1401         }
   1402         return null;
   1403     }
   1404 
   1405     /**
   1406      * Adds preferences from activities that match the given {@link Intent}.
   1407      *
   1408      * @param intent The {@link Intent} to query activities.
   1409      *
   1410      * @deprecated This function is not relevant for a modern fragment-based
   1411      * PreferenceActivity.
   1412      */
   1413     @Deprecated
   1414     public void addPreferencesFromIntent(Intent intent) {
   1415         requirePreferenceManager();
   1416 
   1417         setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
   1418     }
   1419 
   1420     /**
   1421      * Inflates the given XML resource and adds the preference hierarchy to the current
   1422      * preference hierarchy.
   1423      *
   1424      * @param preferencesResId The XML resource ID to inflate.
   1425      *
   1426      * @deprecated This function is not relevant for a modern fragment-based
   1427      * PreferenceActivity.
   1428      */
   1429     @Deprecated
   1430     public void addPreferencesFromResource(int preferencesResId) {
   1431         requirePreferenceManager();
   1432 
   1433         setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
   1434                 getPreferenceScreen()));
   1435     }
   1436 
   1437     /**
   1438      * {@inheritDoc}
   1439      *
   1440      * @deprecated This function is not relevant for a modern fragment-based
   1441      * PreferenceActivity.
   1442      */
   1443     @Deprecated
   1444     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
   1445         return false;
   1446     }
   1447 
   1448     /**
   1449      * Finds a {@link Preference} based on its key.
   1450      *
   1451      * @param key The key of the preference to retrieve.
   1452      * @return The {@link Preference} with the key, or null.
   1453      * @see PreferenceGroup#findPreference(CharSequence)
   1454      *
   1455      * @deprecated This function is not relevant for a modern fragment-based
   1456      * PreferenceActivity.
   1457      */
   1458     @Deprecated
   1459     public Preference findPreference(CharSequence key) {
   1460 
   1461         if (mPreferenceManager == null) {
   1462             return null;
   1463         }
   1464 
   1465         return mPreferenceManager.findPreference(key);
   1466     }
   1467 
   1468     @Override
   1469     protected void onNewIntent(Intent intent) {
   1470         if (mPreferenceManager != null) {
   1471             mPreferenceManager.dispatchNewIntent(intent);
   1472         }
   1473     }
   1474 
   1475     // give subclasses access to the Next button
   1476     /** @hide */
   1477     protected boolean hasNextButton() {
   1478         return mNextButton != null;
   1479     }
   1480     /** @hide */
   1481     protected Button getNextButton() {
   1482         return mNextButton;
   1483     }
   1484 }
   1485