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