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