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