PreferenceActivity.java revision 3e449ce00ed2d3b271e50bc7a52798f630973bf1
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 com.android.internal.util.XmlUtils;
20
21import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.app.Fragment;
25import android.app.FragmentTransaction;
26import android.app.ListActivity;
27import android.content.Context;
28import android.content.Intent;
29import android.content.res.Configuration;
30import android.content.res.TypedArray;
31import android.content.res.XmlResourceParser;
32import android.graphics.drawable.Drawable;
33import android.os.Bundle;
34import android.os.Handler;
35import android.os.Message;
36import android.text.TextUtils;
37import android.util.AttributeSet;
38import android.util.Xml;
39import android.view.LayoutInflater;
40import android.view.View;
41import android.view.View.OnClickListener;
42import android.view.ViewGroup;
43import android.widget.ArrayAdapter;
44import android.widget.Button;
45import android.widget.FrameLayout;
46import android.widget.ImageView;
47import android.widget.ListView;
48import android.widget.TextView;
49
50import java.io.IOException;
51import java.util.ArrayList;
52import java.util.List;
53
54/**
55 * This is the base class for an activity to show a hierarchy of preferences
56 * to the user.  Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB}
57 * this class only allowed the display of a single set of preference; this
58 * functionality should now be found in the new {@link PreferenceFragment}
59 * class.  If you are using PreferenceActivity in its old mode, the documentation
60 * there applies to the deprecated APIs here.
61 *
62 * <p>This activity shows one or more headers of preferences, each of with
63 * is associated with a {@link PreferenceFragment} to display the preferences
64 * of that header.  The actual layout and display of these associations can
65 * however vary; currently there are two major approaches it may take:
66 *
67 * <ul>
68 * <li>On a small screen it may display only the headers as a single list
69 * when first launched.  Selecting one of the header items will re-launch
70 * the activity with it only showing the PreferenceFragment of that header.
71 * <li>On a large screen in may display both the headers and current
72 * PreferenceFragment together as panes.  Selecting a header item switches
73 * to showing the correct PreferenceFragment for that item.
74 * </ul>
75 *
76 * <p>Subclasses of PreferenceActivity should implement
77 * {@link #onBuildHeaders} to populate the header list with the desired
78 * items.  Doing this implicitly switches the class into its new "headers
79 * + fragments" mode rather than the old style of just showing a single
80 * preferences list.
81 *
82 * <a name="SampleCode"></a>
83 * <h3>Sample Code</h3>
84 *
85 * <p>The following sample code shows a simple preference activity that
86 * has two different sets of preferences.  The implementation, consisting
87 * of the activity itself as well as its two preference fragments is:</p>
88 *
89 * {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/PreferenceWithHeaders.java
90 *      activity}
91 *
92 * <p>The preference_headers resource describes the headers to be displayed
93 * and the fragments associated with them.  It is:
94 *
95 * {@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
96 *
97 * <p>The first header is shown by Prefs1Fragment, which populates itself
98 * from the following XML resource:</p>
99 *
100 * {@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
101 *
102 * <p>Note that this XML resource contains a preference screen holding another
103 * fragment, the Prefs1FragmentInner implemented here.  This allows the user
104 * to traverse down a hierarchy of preferences; pressing back will pop each
105 * fragment off the stack to return to the previous preferences.
106 *
107 * <p>See {@link PreferenceFragment} for information on implementing the
108 * fragments themselves.
109 */
110public abstract class PreferenceActivity extends ListActivity implements
111        PreferenceManager.OnPreferenceTreeClickListener,
112        PreferenceFragment.OnPreferenceStartFragmentCallback {
113    private static final String TAG = "PreferenceActivity";
114
115    private static final String PREFERENCES_TAG = "android:preferences";
116
117    /**
118     * When starting this activity, the invoking Intent can contain this extra
119     * string to specify which fragment should be initially displayed.
120     */
121    public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
122
123    /**
124     * When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
125     * this extra can also be specify to supply a Bundle of arguments to pass
126     * to that fragment when it is instantiated during the initial creation
127     * of PreferenceActivity.
128     */
129    public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
130
131    /**
132     * When starting this activity, the invoking Intent can contain this extra
133     * boolean that the header list should not be displayed.  This is most often
134     * used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
135     * the activity to display a specific fragment that the user has navigated
136     * to.
137     */
138    public static final String EXTRA_NO_HEADERS = ":android:no_headers";
139
140    private static final String BACK_STACK_PREFS = ":android:prefs";
141
142    // extras that allow any preference activity to be launched as part of a wizard
143
144    // show Back and Next buttons? takes boolean parameter
145    // Back will then return RESULT_CANCELED and Next RESULT_OK
146    private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
147
148    // add a Skip button?
149    private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
150
151    // specify custom text for the Back or Next buttons, or cause a button to not appear
152    // at all by setting it to null
153    private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
154    private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
155
156    // --- State for new mode when showing a list of headers + prefs fragment
157
158    private final ArrayList<Header> mHeaders = new ArrayList<Header>();
159
160    private HeaderAdapter mAdapter;
161
162    private FrameLayout mListFooter;
163
164    private View mPrefsContainer;
165
166    private boolean mSinglePane;
167
168    // --- State for old mode when showing a single preference list
169
170    private PreferenceManager mPreferenceManager;
171
172    private Bundle mSavedInstanceState;
173
174    // --- Common state
175
176    private Button mNextButton;
177
178    /**
179     * The starting request code given out to preference framework.
180     */
181    private static final int FIRST_REQUEST_CODE = 100;
182
183    private static final int MSG_BIND_PREFERENCES = 1;
184    private static final int MSG_BUILD_HEADERS = 2;
185    private Handler mHandler = new Handler() {
186        @Override
187        public void handleMessage(Message msg) {
188            switch (msg.what) {
189                case MSG_BIND_PREFERENCES:
190                    bindPreferences();
191                    break;
192                case MSG_BUILD_HEADERS:
193                    onBuildHeaders(mHeaders);
194                    mAdapter.notifyDataSetChanged();
195                    Header header = onGetNewHeader();
196                    if (header != null && header.fragment != null) {
197                        switchToHeader(header.fragment, header.fragmentArguments);
198                    }
199                    break;
200            }
201        }
202    };
203
204    private static class HeaderAdapter extends ArrayAdapter<Header> {
205        private static class HeaderViewHolder {
206            ImageView icon;
207            TextView title;
208            TextView summary;
209        }
210
211        private LayoutInflater mInflater;
212
213        public HeaderAdapter(Context context, List<Header> objects) {
214            super(context, 0, objects);
215            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
216        }
217
218        @Override
219        public View getView(int position, View convertView, ViewGroup parent) {
220            HeaderViewHolder holder;
221            View view;
222
223            if (convertView == null) {
224                view = mInflater.inflate(com.android.internal.R.layout.preference_list_item,
225                        parent, false);
226                holder = new HeaderViewHolder();
227                holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
228                holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);
229                holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);
230                view.setTag(holder);
231            } else {
232                view = convertView;
233                holder = (HeaderViewHolder) view.getTag();
234            }
235
236            // All view fields must be updated every time, because the view may be recycled
237            Header header = getItem(position);
238            if (header.icon == null) {
239                holder.icon.setImageDrawable(null);
240                holder.icon.setImageResource(header.iconRes);
241            } else {
242                holder.icon.setImageResource(0);
243                holder.icon.setImageDrawable(header.icon);
244            }
245            holder.title.setText(header.title);
246            if (TextUtils.isEmpty(header.summary)) {
247                holder.summary.setVisibility(View.GONE);
248            } else {
249                holder.summary.setVisibility(View.VISIBLE);
250                holder.summary.setText(header.summary);
251            }
252
253            return view;
254        }
255    }
256
257    /**
258     * Description of a single Header item that the user can select.
259     */
260    public static class Header {
261        /**
262         * Title of the header that is shown to the user.
263         * @attr ref android.R.styleable#PreferenceHeader_title
264         */
265        public CharSequence title;
266
267        /**
268         * Optional summary describing what this header controls.
269         * @attr ref android.R.styleable#PreferenceHeader_summary
270         */
271        public CharSequence summary;
272
273        /**
274         * Optional icon resource to show for this header.
275         * @attr ref android.R.styleable#PreferenceHeader_icon
276         */
277        public int iconRes;
278
279        /**
280         * Optional icon drawable to show for this header.  (If this is non-null,
281         * the iconRes will be ignored.)
282         */
283        public Drawable icon;
284
285        /**
286         * Full class name of the fragment to display when this header is
287         * selected.
288         * @attr ref android.R.styleable#PreferenceHeader_fragment
289         */
290        public String fragment;
291
292        /**
293         * Optional arguments to supply to the fragment when it is
294         * instantiated.
295         */
296        public Bundle fragmentArguments;
297
298        /**
299         * Intent to launch when the preference is selected.
300         */
301        public Intent intent;
302    }
303
304    @Override
305    protected void onCreate(Bundle savedInstanceState) {
306        super.onCreate(savedInstanceState);
307
308        setContentView(com.android.internal.R.layout.preference_list_content);
309
310        mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
311        mPrefsContainer = findViewById(com.android.internal.R.id.prefs);
312        boolean hidingHeaders = onIsHidingHeaders();
313        mSinglePane = hidingHeaders || !onIsMultiPane();
314        String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
315        Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
316
317        if (initialFragment != null && mSinglePane) {
318            // If we are just showing a fragment, we want to run in
319            // new fragment mode, but don't need to compute and show
320            // the headers.
321            getListView().setVisibility(View.GONE);
322            mPrefsContainer.setVisibility(View.VISIBLE);
323            switchToHeader(initialFragment, initialArguments);
324
325        } else {
326            // We need to try to build the headers.
327            onBuildHeaders(mHeaders);
328
329            // If there are headers, then at this point we need to show
330            // them and, depending on the screen, we may also show in-line
331            // the currently selected preference fragment.
332            if (mHeaders.size() > 0) {
333                mAdapter = new HeaderAdapter(this, mHeaders);
334                setListAdapter(mAdapter);
335                if (!mSinglePane) {
336                    mPrefsContainer.setVisibility(View.VISIBLE);
337                    if (initialFragment == null) {
338                        Header h = onGetInitialHeader();
339                        initialFragment = h.fragment;
340                        initialArguments = h.fragmentArguments;
341                    }
342                    switchToHeader(initialFragment, initialArguments);
343                }
344
345            // If there are no headers, we are in the old "just show a screen
346            // of preferences" mode.
347            } else {
348                mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
349                mPreferenceManager.setOnPreferenceTreeClickListener(this);
350            }
351        }
352
353        getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY);
354
355        // see if we should show Back/Next buttons
356        Intent intent = getIntent();
357        if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
358
359            findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
360
361            Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
362            backButton.setOnClickListener(new OnClickListener() {
363                public void onClick(View v) {
364                    setResult(RESULT_CANCELED);
365                    finish();
366                }
367            });
368            Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
369            skipButton.setOnClickListener(new OnClickListener() {
370                public void onClick(View v) {
371                    setResult(RESULT_OK);
372                    finish();
373                }
374            });
375            mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
376            mNextButton.setOnClickListener(new OnClickListener() {
377                public void onClick(View v) {
378                    setResult(RESULT_OK);
379                    finish();
380                }
381            });
382
383            // set our various button parameters
384            if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
385                String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
386                if (TextUtils.isEmpty(buttonText)) {
387                    mNextButton.setVisibility(View.GONE);
388                }
389                else {
390                    mNextButton.setText(buttonText);
391                }
392            }
393            if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
394                String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
395                if (TextUtils.isEmpty(buttonText)) {
396                    backButton.setVisibility(View.GONE);
397                }
398                else {
399                    backButton.setText(buttonText);
400                }
401            }
402            if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
403                skipButton.setVisibility(View.VISIBLE);
404            }
405        }
406    }
407
408    /**
409     * Returns true if this activity is currently showing the header list.
410     */
411    public boolean hasHeaders() {
412        return getListView().getVisibility() == View.VISIBLE
413                && mPreferenceManager == null;
414    }
415
416    /**
417     * Returns true if this activity is showing multiple panes -- the headers
418     * and a preference fragment.
419     */
420    public boolean isMultiPane() {
421        return hasHeaders() && mPrefsContainer.getVisibility() == View.VISIBLE;
422    }
423
424    /**
425     * Called to determine if the activity should run in multi-pane mode.
426     * The default implementation returns true if the screen is large
427     * enough.
428     */
429    public boolean onIsMultiPane() {
430        Configuration config = getResources().getConfiguration();
431        if ((config.screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK)
432                == Configuration.SCREENLAYOUT_SIZE_XLARGE
433                && config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
434            return true;
435        }
436        return false;
437    }
438
439    /**
440     * Called to determine whether the header list should be hidden.
441     * The default implementation returns the
442     * value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
443     * This is set to false, for example, when the activity is being re-launched
444     * to show a particular preference activity.
445     */
446    public boolean onIsHidingHeaders() {
447        return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
448    }
449
450    /**
451     * Called to determine the initial header to be shown.  The default
452     * implementation simply returns the fragment of the first header.  Note
453     * that the returned Header object does not actually need to exist in
454     * your header list -- whatever its fragment is will simply be used to
455     * show for the initial UI.
456     */
457    public Header onGetInitialHeader() {
458        return mHeaders.get(0);
459    }
460
461    /**
462     * Called after the header list has been updated ({@link #onBuildHeaders}
463     * has been called and returned due to {@link #invalidateHeaders()}) to
464     * specify the header that should now be selected.  The default implementation
465     * returns null to keep whatever header is currently selected.
466     */
467    public Header onGetNewHeader() {
468        return null;
469    }
470
471    /**
472     * Called when the activity needs its list of headers build.  By
473     * implementing this and adding at least one item to the list, you
474     * will cause the activity to run in its modern fragment mode.  Note
475     * that this function may not always be called; for example, if the
476     * activity has been asked to display a particular fragment without
477     * the header list, there is no need to build the headers.
478     *
479     * <p>Typical implementations will use {@link #loadHeadersFromResource}
480     * to fill in the list from a resource.
481     *
482     * @param target The list in which to place the headers.
483     */
484    public void onBuildHeaders(List<Header> target) {
485    }
486
487    /**
488     * Call when you need to change the headers being displayed.  Will result
489     * in onBuildHeaders() later being called to retrieve the new list.
490     */
491    public void invalidateHeaders() {
492        if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
493            mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
494        }
495    }
496
497    /**
498     * Parse the given XML file as a header description, adding each
499     * parsed Header into the target list.
500     *
501     * @param resid The XML resource to load and parse.
502     * @param target The list in which the parsed headers should be placed.
503     */
504    public void loadHeadersFromResource(int resid, List<Header> target) {
505        XmlResourceParser parser = null;
506        try {
507            parser = getResources().getXml(resid);
508            AttributeSet attrs = Xml.asAttributeSet(parser);
509
510            int type;
511            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
512                    && type != XmlPullParser.START_TAG) {
513            }
514
515            String nodeName = parser.getName();
516            if (!"preference-headers".equals(nodeName)) {
517                throw new RuntimeException(
518                        "XML document must start with <preference-headers> tag; found"
519                        + nodeName + " at " + parser.getPositionDescription());
520            }
521
522            Bundle curBundle = null;
523
524            final int outerDepth = parser.getDepth();
525            while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
526                   && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
527                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
528                    continue;
529                }
530
531                nodeName = parser.getName();
532                if ("header".equals(nodeName)) {
533                    Header header = new Header();
534
535                    TypedArray sa = getResources().obtainAttributes(attrs,
536                            com.android.internal.R.styleable.PreferenceHeader);
537                    header.title = sa.getText(
538                            com.android.internal.R.styleable.PreferenceHeader_title);
539                    header.summary = sa.getText(
540                            com.android.internal.R.styleable.PreferenceHeader_summary);
541                    header.iconRes = sa.getResourceId(
542                            com.android.internal.R.styleable.PreferenceHeader_icon, 0);
543                    header.fragment = sa.getString(
544                            com.android.internal.R.styleable.PreferenceHeader_fragment);
545                    sa.recycle();
546
547                    if (curBundle == null) {
548                        curBundle = new Bundle();
549                    }
550
551                    final int innerDepth = parser.getDepth();
552                    while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
553                           && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
554                        if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
555                            continue;
556                        }
557
558                        String innerNodeName = parser.getName();
559                        if (innerNodeName.equals("extra")) {
560                            getResources().parseBundleExtra("extra", attrs, curBundle);
561                            XmlUtils.skipCurrentTag(parser);
562
563                        } else if (innerNodeName.equals("intent")) {
564                            header.intent = Intent.parseIntent(getResources(), parser, attrs);
565
566                        } else {
567                            XmlUtils.skipCurrentTag(parser);
568                        }
569                    }
570
571                    if (curBundle.size() > 0) {
572                        header.fragmentArguments = curBundle;
573                        curBundle = null;
574                    }
575
576                    target.add(header);
577                } else {
578                    XmlUtils.skipCurrentTag(parser);
579                }
580            }
581
582        } catch (XmlPullParserException e) {
583            throw new RuntimeException("Error parsing headers", e);
584        } catch (IOException e) {
585            throw new RuntimeException("Error parsing headers", e);
586        } finally {
587            if (parser != null) parser.close();
588        }
589
590    }
591
592    /**
593     * Set a footer that should be shown at the bottom of the header list.
594     */
595    public void setListFooter(View view) {
596        mListFooter.removeAllViews();
597        mListFooter.addView(view, new FrameLayout.LayoutParams(
598                FrameLayout.LayoutParams.MATCH_PARENT,
599                FrameLayout.LayoutParams.WRAP_CONTENT));
600    }
601
602    @Override
603    protected void onStop() {
604        super.onStop();
605
606        if (mPreferenceManager != null) {
607            mPreferenceManager.dispatchActivityStop();
608        }
609    }
610
611    @Override
612    protected void onDestroy() {
613        super.onDestroy();
614
615        if (mPreferenceManager != null) {
616            mPreferenceManager.dispatchActivityDestroy();
617        }
618    }
619
620    @Override
621    protected void onSaveInstanceState(Bundle outState) {
622        super.onSaveInstanceState(outState);
623
624        if (mPreferenceManager != null) {
625            final PreferenceScreen preferenceScreen = getPreferenceScreen();
626            if (preferenceScreen != null) {
627                Bundle container = new Bundle();
628                preferenceScreen.saveHierarchyState(container);
629                outState.putBundle(PREFERENCES_TAG, container);
630            }
631        }
632    }
633
634    @Override
635    protected void onRestoreInstanceState(Bundle state) {
636        if (mPreferenceManager != null) {
637            Bundle container = state.getBundle(PREFERENCES_TAG);
638            if (container != null) {
639                final PreferenceScreen preferenceScreen = getPreferenceScreen();
640                if (preferenceScreen != null) {
641                    preferenceScreen.restoreHierarchyState(container);
642                    mSavedInstanceState = state;
643                    return;
644                }
645            }
646        }
647
648        // Only call this if we didn't save the instance state for later.
649        // If we did save it, it will be restored when we bind the adapter.
650        super.onRestoreInstanceState(state);
651    }
652
653    @Override
654    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
655        super.onActivityResult(requestCode, resultCode, data);
656
657        if (mPreferenceManager != null) {
658            mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
659        }
660    }
661
662    @Override
663    public void onContentChanged() {
664        super.onContentChanged();
665
666        if (mPreferenceManager != null) {
667            postBindPreferences();
668        }
669    }
670
671    @Override
672    protected void onListItemClick(ListView l, View v, int position, long id) {
673        super.onListItemClick(l, v, position, id);
674
675        if (mAdapter != null) {
676            onHeaderClick(mHeaders.get(position), position);
677        }
678    }
679
680    /**
681     * Called when the user selects an item in the header list.  The default
682     * implementation will call either {@link #startWithFragment(String, Bundle)}
683     * or {@link #switchToHeader(String, Bundle)} as appropriate.
684     *
685     * @param header The header that was selected.
686     * @param position The header's position in the list.
687     */
688    public void onHeaderClick(Header header, int position) {
689        if (header.fragment != null) {
690            if (mSinglePane) {
691                startWithFragment(header.fragment, header.fragmentArguments);
692            } else {
693                switchToHeader(header.fragment, header.fragmentArguments);
694            }
695        } else if (header.intent != null) {
696            startActivity(header.intent);
697        }
698    }
699
700    /**
701     * Start a new instance of this activity, showing only the given
702     * preference fragment.  When launched in this mode, the header list
703     * will be hidden and the given preference fragment will be instantiated
704     * and fill the entire activity.
705     *
706     * @param fragmentName The name of the fragment to display.
707     * @param args Optional arguments to supply to the fragment.
708     */
709    public void startWithFragment(String fragmentName, Bundle args) {
710        Intent intent = new Intent(Intent.ACTION_MAIN);
711        intent.setClass(this, getClass());
712        intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
713        intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
714        intent.putExtra(EXTRA_NO_HEADERS, true);
715        startActivity(intent);
716    }
717
718    /**
719     * When in two-pane mode, switch the fragment pane to show the given
720     * preference fragment.
721     *
722     * @param fragmentName The name of the fragment to display.
723     * @param args Optional arguments to supply to the fragment.
724     */
725    public void switchToHeader(String fragmentName, Bundle args) {
726        getFragmentManager().popBackStack(BACK_STACK_PREFS, POP_BACK_STACK_INCLUSIVE);
727
728        Fragment f = Fragment.instantiate(this, fragmentName, args);
729        getFragmentManager().openTransaction().replace(
730                com.android.internal.R.id.prefs, f).commit();
731    }
732
733    /**
734     * Start a new fragment.
735     *
736     * @param fragment The fragment to start
737     * @param push If true, the current fragment will be pushed onto the back stack.  If false,
738     * the current fragment will be replaced.
739     */
740    public void startPreferenceFragment(Fragment fragment, boolean push) {
741        FragmentTransaction transaction = getFragmentManager().openTransaction();
742        transaction.replace(com.android.internal.R.id.prefs, fragment);
743        if (push) {
744            transaction.addToBackStack(BACK_STACK_PREFS);
745        }
746        transaction.commit();
747    }
748
749    @Override
750    public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
751        Fragment f = Fragment.instantiate(this, pref.getFragment(), pref.getExtras());
752        startPreferenceFragment(f, true);
753        return true;
754    }
755
756    /**
757     * Posts a message to bind the preferences to the list view.
758     * <p>
759     * Binding late is preferred as any custom preference types created in
760     * {@link #onCreate(Bundle)} are able to have their views recycled.
761     */
762    private void postBindPreferences() {
763        if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
764        mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
765    }
766
767    private void bindPreferences() {
768        final PreferenceScreen preferenceScreen = getPreferenceScreen();
769        if (preferenceScreen != null) {
770            preferenceScreen.bind(getListView());
771            if (mSavedInstanceState != null) {
772                super.onRestoreInstanceState(mSavedInstanceState);
773                mSavedInstanceState = null;
774            }
775        }
776    }
777
778    /**
779     * Returns the {@link PreferenceManager} used by this activity.
780     * @return The {@link PreferenceManager}.
781     *
782     * @deprecated This function is not relevant for a modern fragment-based
783     * PreferenceActivity.
784     */
785    @Deprecated
786    public PreferenceManager getPreferenceManager() {
787        return mPreferenceManager;
788    }
789
790    private void requirePreferenceManager() {
791        if (mPreferenceManager == null) {
792            if (mAdapter == null) {
793                throw new RuntimeException("This should be called after super.onCreate.");
794            }
795            throw new RuntimeException(
796                    "Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
797        }
798    }
799
800    /**
801     * Sets the root of the preference hierarchy that this activity is showing.
802     *
803     * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
804     *
805     * @deprecated This function is not relevant for a modern fragment-based
806     * PreferenceActivity.
807     */
808    @Deprecated
809    public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
810        requirePreferenceManager();
811
812        if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
813            postBindPreferences();
814            CharSequence title = getPreferenceScreen().getTitle();
815            // Set the title of the activity
816            if (title != null) {
817                setTitle(title);
818            }
819        }
820    }
821
822    /**
823     * Gets the root of the preference hierarchy that this activity is showing.
824     *
825     * @return The {@link PreferenceScreen} that is the root of the preference
826     *         hierarchy.
827     *
828     * @deprecated This function is not relevant for a modern fragment-based
829     * PreferenceActivity.
830     */
831    @Deprecated
832    public PreferenceScreen getPreferenceScreen() {
833        if (mPreferenceManager != null) {
834            return mPreferenceManager.getPreferenceScreen();
835        }
836        return null;
837    }
838
839    /**
840     * Adds preferences from activities that match the given {@link Intent}.
841     *
842     * @param intent The {@link Intent} to query activities.
843     *
844     * @deprecated This function is not relevant for a modern fragment-based
845     * PreferenceActivity.
846     */
847    @Deprecated
848    public void addPreferencesFromIntent(Intent intent) {
849        requirePreferenceManager();
850
851        setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
852    }
853
854    /**
855     * Inflates the given XML resource and adds the preference hierarchy to the current
856     * preference hierarchy.
857     *
858     * @param preferencesResId The XML resource ID to inflate.
859     *
860     * @deprecated This function is not relevant for a modern fragment-based
861     * PreferenceActivity.
862     */
863    @Deprecated
864    public void addPreferencesFromResource(int preferencesResId) {
865        requirePreferenceManager();
866
867        setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
868                getPreferenceScreen()));
869    }
870
871    /**
872     * {@inheritDoc}
873     *
874     * @deprecated This function is not relevant for a modern fragment-based
875     * PreferenceActivity.
876     */
877    @Deprecated
878    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
879        return false;
880    }
881
882    /**
883     * Finds a {@link Preference} based on its key.
884     *
885     * @param key The key of the preference to retrieve.
886     * @return The {@link Preference} with the key, or null.
887     * @see PreferenceGroup#findPreference(CharSequence)
888     *
889     * @deprecated This function is not relevant for a modern fragment-based
890     * PreferenceActivity.
891     */
892    @Deprecated
893    public Preference findPreference(CharSequence key) {
894
895        if (mPreferenceManager == null) {
896            return null;
897        }
898
899        return mPreferenceManager.findPreference(key);
900    }
901
902    @Override
903    protected void onNewIntent(Intent intent) {
904        if (mPreferenceManager != null) {
905            mPreferenceManager.dispatchNewIntent(intent);
906        }
907    }
908
909    // give subclasses access to the Next button
910    /** @hide */
911    protected boolean hasNextButton() {
912        return mNextButton != null;
913    }
914    /** @hide */
915    protected Button getNextButton() {
916        return mNextButton;
917    }
918}
919