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