/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.preference;
import android.animation.LayoutTransition;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.XmlRes;
import android.app.Fragment;
import android.app.FragmentBreadCrumbs;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.ListActivity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.Xml;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
This is the base class for an activity to show a hierarchy of preferences to the user. Prior to VERSION_CODES.HONEYCOMB
this class only allowed the display of a single set of preference; this functionality should now be found in the new PreferenceFragment
class. If you are using PreferenceActivity in its old mode, the documentation there applies to the deprecated APIs here. This activity shows one or more headers of preferences, each of which is associated with a PreferenceFragment
to display the preferences of that header. The actual layout and display of these associations can however vary; currently there are two major approaches it may take:
- On a small screen it may display only the headers as a single list when first launched.
Selecting one of the header items will only show the PreferenceFragment of that header (on
Android N and lower a new Activity is launched).
- On a large screen it may display both the headers and current PreferenceFragment together as
panes. Selecting a header item switches to showing the correct PreferenceFragment for that item.
Subclasses of PreferenceActivity should implement onBuildHeaders
to populate the header list with the desired items. Doing this implicitly switches the class into its new "headers + fragments" mode rather than the old style of just showing a single preferences list.
Developer Guides
For information about using PreferenceActivity
, read the Settings
guide.
Sample Code
The following sample code shows a simple preference activity that
has two different sets of preferences. The implementation, consisting
of the activity itself as well as its two preference fragments is:
{@sample development/samples/ApiDemos/src/com/example/android/apis/preference/PreferenceWithHeaders.java
activity}
The preference_headers resource describes the headers to be displayed
and the fragments associated with them. It is:
{@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
The first header is shown by Prefs1Fragment, which populates itself
from the following XML resource:
{@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
Note that this XML resource contains a preference screen holding another
fragment, the Prefs1FragmentInner implemented here. This allows the user
to traverse down a hierarchy of preferences; pressing back will pop each
fragment off the stack to return to the previous preferences.
See PreferenceFragment
for information on implementing the fragments themselves.
/**
* This is the base class for an activity to show a hierarchy of preferences
* to the user. Prior to {@link android.os.Build.VERSION_CODES#HONEYCOMB}
* this class only allowed the display of a single set of preference; this
* functionality should now be found in the new {@link PreferenceFragment}
* class. If you are using PreferenceActivity in its old mode, the documentation
* there applies to the deprecated APIs here.
*
* <p>This activity shows one or more headers of preferences, each of which
* is associated with a {@link PreferenceFragment} to display the preferences
* of that header. The actual layout and display of these associations can
* however vary; currently there are two major approaches it may take:
*
* <ul>
* <li>On a small screen it may display only the headers as a single list when first launched.
* Selecting one of the header items will only show the PreferenceFragment of that header (on
* Android N and lower a new Activity is launched).
* <li>On a large screen it may display both the headers and current PreferenceFragment together as
* panes. Selecting a header item switches to showing the correct PreferenceFragment for that item.
* </ul>
*
* <p>Subclasses of PreferenceActivity should implement
* {@link #onBuildHeaders} to populate the header list with the desired
* items. Doing this implicitly switches the class into its new "headers
* + fragments" mode rather than the old style of just showing a single
* preferences list.
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For information about using {@code PreferenceActivity},
* read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
* guide.</p>
* </div>
*
* <a name="SampleCode"></a>
* <h3>Sample Code</h3>
*
* <p>The following sample code shows a simple preference activity that
* has two different sets of preferences. The implementation, consisting
* of the activity itself as well as its two preference fragments is:</p>
*
* {@sample development/samples/ApiDemos/src/com/example/android/apis/preference/PreferenceWithHeaders.java
* activity}
*
* <p>The preference_headers resource describes the headers to be displayed
* and the fragments associated with them. It is:
*
* {@sample development/samples/ApiDemos/res/xml/preference_headers.xml headers}
*
* <p>The first header is shown by Prefs1Fragment, which populates itself
* from the following XML resource:</p>
*
* {@sample development/samples/ApiDemos/res/xml/fragmented_preferences.xml preferences}
*
* <p>Note that this XML resource contains a preference screen holding another
* fragment, the Prefs1FragmentInner implemented here. This allows the user
* to traverse down a hierarchy of preferences; pressing back will pop each
* fragment off the stack to return to the previous preferences.
*
* <p>See {@link PreferenceFragment} for information on implementing the
* fragments themselves.
*/
public abstract class PreferenceActivity extends ListActivity implements
PreferenceManager.OnPreferenceTreeClickListener,
PreferenceFragment.OnPreferenceStartFragmentCallback {
private static final String TAG = "PreferenceActivity";
// Constants for state save/restore
private static final String HEADERS_TAG = ":android:headers";
private static final String CUR_HEADER_TAG = ":android:cur_header";
private static final String PREFERENCES_TAG = ":android:preferences";
When starting this activity, the invoking Intent can contain this extra
string to specify which fragment should be initially displayed.
Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity
will call isValidFragment() to confirm that the fragment class name is valid for this
activity.
/**
* When starting this activity, the invoking Intent can contain this extra
* string to specify which fragment should be initially displayed.
* <p/>Starting from Key Lime Pie, when this argument is passed in, the PreferenceActivity
* will call isValidFragment() to confirm that the fragment class name is valid for this
* activity.
*/
public static final String EXTRA_SHOW_FRAGMENT = ":android:show_fragment";
When starting this activity and using EXTRA_SHOW_FRAGMENT
, this extra can also be specified to supply a Bundle of arguments to pass to that fragment when it is instantiated during the initial creation of PreferenceActivity. /**
* When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
* this extra can also be specified to supply a Bundle of arguments to pass
* to that fragment when it is instantiated during the initial creation
* of PreferenceActivity.
*/
public static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":android:show_fragment_args";
When starting this activity and using EXTRA_SHOW_FRAGMENT
, this extra can also be specify to supply the title to be shown for that fragment. /**
* When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
* this extra can also be specify to supply the title to be shown for
* that fragment.
*/
public static final String EXTRA_SHOW_FRAGMENT_TITLE = ":android:show_fragment_title";
When starting this activity and using EXTRA_SHOW_FRAGMENT
, this extra can also be specify to supply the short title to be shown for that fragment. /**
* When starting this activity and using {@link #EXTRA_SHOW_FRAGMENT},
* this extra can also be specify to supply the short title to be shown for
* that fragment.
*/
public static final String EXTRA_SHOW_FRAGMENT_SHORT_TITLE
= ":android:show_fragment_short_title";
When starting this activity, the invoking Intent can contain this extra boolean that the header list should not be displayed. This is most often used in conjunction with EXTRA_SHOW_FRAGMENT
to launch the activity to display a specific fragment that the user has navigated to. /**
* When starting this activity, the invoking Intent can contain this extra
* boolean that the header list should not be displayed. This is most often
* used in conjunction with {@link #EXTRA_SHOW_FRAGMENT} to launch
* the activity to display a specific fragment that the user has navigated
* to.
*/
public static final String EXTRA_NO_HEADERS = ":android:no_headers";
private static final String BACK_STACK_PREFS = ":android:prefs";
// extras that allow any preference activity to be launched as part of a wizard
// show Back and Next buttons? takes boolean parameter
// Back will then return RESULT_CANCELED and Next RESULT_OK
private static final String EXTRA_PREFS_SHOW_BUTTON_BAR = "extra_prefs_show_button_bar";
// add a Skip button?
private static final String EXTRA_PREFS_SHOW_SKIP = "extra_prefs_show_skip";
// specify custom text for the Back or Next buttons, or cause a button to not appear
// at all by setting it to null
private static final String EXTRA_PREFS_SET_NEXT_TEXT = "extra_prefs_set_next_text";
private static final String EXTRA_PREFS_SET_BACK_TEXT = "extra_prefs_set_back_text";
// --- State for new mode when showing a list of headers + prefs fragment
private final ArrayList<Header> mHeaders = new ArrayList<Header>();
private FrameLayout mListFooter;
private ViewGroup mPrefsContainer;
// Backup of the original activity title. This is used when navigating back to the headers list
// in onBackPress to restore the title.
private CharSequence mActivityTitle;
// Null if in legacy mode.
private ViewGroup mHeadersContainer;
private FragmentBreadCrumbs mFragmentBreadCrumbs;
private boolean mSinglePane;
private Header mCurHeader;
// --- State for old mode when showing a single preference list
private PreferenceManager mPreferenceManager;
private Bundle mSavedInstanceState;
// --- Common state
private Button mNextButton;
private int mPreferenceHeaderItemResId = 0;
private boolean mPreferenceHeaderRemoveEmptyIcon = false;
The starting request code given out to preference framework.
/**
* The starting request code given out to preference framework.
*/
private static final int FIRST_REQUEST_CODE = 100;
private static final int MSG_BIND_PREFERENCES = 1;
private static final int MSG_BUILD_HEADERS = 2;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_BIND_PREFERENCES: {
bindPreferences();
} break;
case MSG_BUILD_HEADERS: {
ArrayList<Header> oldHeaders = new ArrayList<Header>(mHeaders);
mHeaders.clear();
onBuildHeaders(mHeaders);
if (mAdapter instanceof BaseAdapter) {
((BaseAdapter) mAdapter).notifyDataSetChanged();
}
Header header = onGetNewHeader();
if (header != null && header.fragment != null) {
Header mappedHeader = findBestMatchingHeader(header, oldHeaders);
if (mappedHeader == null || mCurHeader != mappedHeader) {
switchToHeader(header);
}
} else if (mCurHeader != null) {
Header mappedHeader = findBestMatchingHeader(mCurHeader, mHeaders);
if (mappedHeader != null) {
setSelectedHeader(mappedHeader);
}
}
} break;
}
}
};
private static class HeaderAdapter extends ArrayAdapter<Header> {
private static class HeaderViewHolder {
ImageView icon;
TextView title;
TextView summary;
}
private LayoutInflater mInflater;
private int mLayoutResId;
private boolean mRemoveIconIfEmpty;
public HeaderAdapter(Context context, List<Header> objects, int layoutResId,
boolean removeIconBehavior) {
super(context, 0, objects);
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mLayoutResId = layoutResId;
mRemoveIconIfEmpty = removeIconBehavior;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
HeaderViewHolder holder;
View view;
if (convertView == null) {
view = mInflater.inflate(mLayoutResId, parent, false);
holder = new HeaderViewHolder();
holder.icon = (ImageView) view.findViewById(com.android.internal.R.id.icon);
holder.title = (TextView) view.findViewById(com.android.internal.R.id.title);
holder.summary = (TextView) view.findViewById(com.android.internal.R.id.summary);
view.setTag(holder);
} else {
view = convertView;
holder = (HeaderViewHolder) view.getTag();
}
// All view fields must be updated every time, because the view may be recycled
Header header = getItem(position);
if (mRemoveIconIfEmpty) {
if (header.iconRes == 0) {
holder.icon.setVisibility(View.GONE);
} else {
holder.icon.setVisibility(View.VISIBLE);
holder.icon.setImageResource(header.iconRes);
}
} else {
holder.icon.setImageResource(header.iconRes);
}
holder.title.setText(header.getTitle(getContext().getResources()));
CharSequence summary = header.getSummary(getContext().getResources());
if (!TextUtils.isEmpty(summary)) {
holder.summary.setVisibility(View.VISIBLE);
holder.summary.setText(summary);
} else {
holder.summary.setVisibility(View.GONE);
}
return view;
}
}
Default value for Header.id
indicating that no identifier value is set. All other values (including those below -1) are valid. /**
* Default value for {@link Header#id Header.id} indicating that no
* identifier value is set. All other values (including those below -1)
* are valid.
*/
public static final long HEADER_ID_UNDEFINED = -1;
Description of a single Header item that the user can select.
/**
* Description of a single Header item that the user can select.
*/
public static final class Header implements Parcelable {
Identifier for this header, to correlate with a new list when it is updated. The default value is PreferenceActivity.HEADER_ID_UNDEFINED
, meaning no id. @attr ref android.R.styleable#PreferenceHeader_id
/**
* Identifier for this header, to correlate with a new list when
* it is updated. The default value is
* {@link PreferenceActivity#HEADER_ID_UNDEFINED}, meaning no id.
* @attr ref android.R.styleable#PreferenceHeader_id
*/
public long id = HEADER_ID_UNDEFINED;
Resource ID of title of the header that is shown to the user.
@attr ref android.R.styleable#PreferenceHeader_title
/**
* Resource ID of title of the header that is shown to the user.
* @attr ref android.R.styleable#PreferenceHeader_title
*/
@StringRes
public int titleRes;
Title of the header that is shown to the user.
@attr ref android.R.styleable#PreferenceHeader_title
/**
* Title of the header that is shown to the user.
* @attr ref android.R.styleable#PreferenceHeader_title
*/
public CharSequence title;
Resource ID of optional summary describing what this header controls.
@attr ref android.R.styleable#PreferenceHeader_summary
/**
* Resource ID of optional summary describing what this header controls.
* @attr ref android.R.styleable#PreferenceHeader_summary
*/
@StringRes
public int summaryRes;
Optional summary describing what this header controls.
@attr ref android.R.styleable#PreferenceHeader_summary
/**
* Optional summary describing what this header controls.
* @attr ref android.R.styleable#PreferenceHeader_summary
*/
public CharSequence summary;
Resource ID of optional text to show as the title in the bread crumb.
@attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
/**
* Resource ID of optional text to show as the title in the bread crumb.
* @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
*/
@StringRes
public int breadCrumbTitleRes;
Optional text to show as the title in the bread crumb.
@attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
/**
* Optional text to show as the title in the bread crumb.
* @attr ref android.R.styleable#PreferenceHeader_breadCrumbTitle
*/
public CharSequence breadCrumbTitle;
Resource ID of optional text to show as the short title in the bread crumb.
@attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
/**
* Resource ID of optional text to show as the short title in the bread crumb.
* @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
*/
@StringRes
public int breadCrumbShortTitleRes;
Optional text to show as the short title in the bread crumb.
@attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
/**
* Optional text to show as the short title in the bread crumb.
* @attr ref android.R.styleable#PreferenceHeader_breadCrumbShortTitle
*/
public CharSequence breadCrumbShortTitle;
Optional icon resource to show for this header.
@attr ref android.R.styleable#PreferenceHeader_icon
/**
* Optional icon resource to show for this header.
* @attr ref android.R.styleable#PreferenceHeader_icon
*/
public int iconRes;
Full class name of the fragment to display when this header is
selected.
@attr ref android.R.styleable#PreferenceHeader_fragment
/**
* Full class name of the fragment to display when this header is
* selected.
* @attr ref android.R.styleable#PreferenceHeader_fragment
*/
public String fragment;
Optional arguments to supply to the fragment when it is
instantiated.
/**
* Optional arguments to supply to the fragment when it is
* instantiated.
*/
public Bundle fragmentArguments;
Intent to launch when the preference is selected.
/**
* Intent to launch when the preference is selected.
*/
public Intent intent;
Optional additional data for use by subclasses of PreferenceActivity.
/**
* Optional additional data for use by subclasses of PreferenceActivity.
*/
public Bundle extras;
public Header() {
// Empty
}
/**
* Return the currently set title. If {@link #titleRes} is set,
* this resource is loaded from <var>res</var> and returned. Otherwise
* {@link #title} is returned.
*/
public CharSequence getTitle(Resources res) {
if (titleRes != 0) {
return res.getText(titleRes);
}
return title;
}
Return the currently set summary. If summaryRes
is set, this resource is loaded from res and returned. Otherwise summary
is returned. /**
* Return the currently set summary. If {@link #summaryRes} is set,
* this resource is loaded from <var>res</var> and returned. Otherwise
* {@link #summary} is returned.
*/
public CharSequence getSummary(Resources res) {
if (summaryRes != 0) {
return res.getText(summaryRes);
}
return summary;
}
Return the currently set bread crumb title. If breadCrumbTitleRes
is set, this resource is loaded from res and returned. Otherwise breadCrumbTitle
is returned. /**
* Return the currently set bread crumb title. If {@link #breadCrumbTitleRes} is set,
* this resource is loaded from <var>res</var> and returned. Otherwise
* {@link #breadCrumbTitle} is returned.
*/
public CharSequence getBreadCrumbTitle(Resources res) {
if (breadCrumbTitleRes != 0) {
return res.getText(breadCrumbTitleRes);
}
return breadCrumbTitle;
}
Return the currently set bread crumb short title. If breadCrumbShortTitleRes
is set, this resource is loaded from res and returned. Otherwise breadCrumbShortTitle
is returned. /**
* Return the currently set bread crumb short title. If
* {@link #breadCrumbShortTitleRes} is set,
* this resource is loaded from <var>res</var> and returned. Otherwise
* {@link #breadCrumbShortTitle} is returned.
*/
public CharSequence getBreadCrumbShortTitle(Resources res) {
if (breadCrumbShortTitleRes != 0) {
return res.getText(breadCrumbShortTitleRes);
}
return breadCrumbShortTitle;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeInt(titleRes);
TextUtils.writeToParcel(title, dest, flags);
dest.writeInt(summaryRes);
TextUtils.writeToParcel(summary, dest, flags);
dest.writeInt(breadCrumbTitleRes);
TextUtils.writeToParcel(breadCrumbTitle, dest, flags);
dest.writeInt(breadCrumbShortTitleRes);
TextUtils.writeToParcel(breadCrumbShortTitle, dest, flags);
dest.writeInt(iconRes);
dest.writeString(fragment);
dest.writeBundle(fragmentArguments);
if (intent != null) {
dest.writeInt(1);
intent.writeToParcel(dest, flags);
} else {
dest.writeInt(0);
}
dest.writeBundle(extras);
}
public void readFromParcel(Parcel in) {
id = in.readLong();
titleRes = in.readInt();
title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
summaryRes = in.readInt();
summary = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
breadCrumbTitleRes = in.readInt();
breadCrumbTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
breadCrumbShortTitleRes = in.readInt();
breadCrumbShortTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
iconRes = in.readInt();
fragment = in.readString();
fragmentArguments = in.readBundle();
if (in.readInt() != 0) {
intent = Intent.CREATOR.createFromParcel(in);
}
extras = in.readBundle();
}
Header(Parcel in) {
readFromParcel(in);
}
public static final Creator<Header> CREATOR = new Creator<Header>() {
public Header createFromParcel(Parcel source) {
return new Header(source);
}
public Header[] newArray(int size) {
return new Header[size];
}
};
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
// Override home navigation button to call onBackPressed (b/35152749).
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Theming for the PreferenceActivity layout and for the Preference Header(s) layout
TypedArray sa = obtainStyledAttributes(null,
com.android.internal.R.styleable.PreferenceActivity,
com.android.internal.R.attr.preferenceActivityStyle,
0);
final int layoutResId = sa.getResourceId(
com.android.internal.R.styleable.PreferenceActivity_layout,
com.android.internal.R.layout.preference_list_content);
mPreferenceHeaderItemResId = sa.getResourceId(
com.android.internal.R.styleable.PreferenceActivity_headerLayout,
com.android.internal.R.layout.preference_header_item);
mPreferenceHeaderRemoveEmptyIcon = sa.getBoolean(
com.android.internal.R.styleable.PreferenceActivity_headerRemoveIconIfEmpty,
false);
sa.recycle();
setContentView(layoutResId);
mListFooter = (FrameLayout)findViewById(com.android.internal.R.id.list_footer);
mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs_frame);
mHeadersContainer = (ViewGroup) findViewById(com.android.internal.R.id.headers);
boolean hidingHeaders = onIsHidingHeaders();
mSinglePane = hidingHeaders || !onIsMultiPane();
String initialFragment = getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT);
Bundle initialArguments = getIntent().getBundleExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS);
int initialTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_TITLE, 0);
int initialShortTitle = getIntent().getIntExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, 0);
mActivityTitle = getTitle();
if (savedInstanceState != null) {
// We are restarting from a previous saved state; used that to
// initialize, instead of starting fresh.
ArrayList<Header> headers = savedInstanceState.getParcelableArrayList(HEADERS_TAG);
if (headers != null) {
mHeaders.addAll(headers);
int curHeader = savedInstanceState.getInt(CUR_HEADER_TAG,
(int) HEADER_ID_UNDEFINED);
if (curHeader >= 0 && curHeader < mHeaders.size()) {
setSelectedHeader(mHeaders.get(curHeader));
} else if (!mSinglePane && initialFragment == null) {
switchToHeader(onGetInitialHeader());
}
} else {
// This will for instance hide breadcrumbs for single pane.
showBreadCrumbs(getTitle(), null);
}
} else {
if (!onIsHidingHeaders()) {
onBuildHeaders(mHeaders);
}
if (initialFragment != null) {
switchToHeader(initialFragment, initialArguments);
} else if (!mSinglePane && mHeaders.size() > 0) {
switchToHeader(onGetInitialHeader());
}
}
if (mHeaders.size() > 0) {
setListAdapter(new HeaderAdapter(this, mHeaders, mPreferenceHeaderItemResId,
mPreferenceHeaderRemoveEmptyIcon));
if (!mSinglePane) {
getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
}
}
if (mSinglePane && initialFragment != null && initialTitle != 0) {
CharSequence initialTitleStr = getText(initialTitle);
CharSequence initialShortTitleStr = initialShortTitle != 0
? getText(initialShortTitle) : null;
showBreadCrumbs(initialTitleStr, initialShortTitleStr);
}
if (mHeaders.size() == 0 && initialFragment == null) {
// If there are no headers, we are in the old "just show a screen
// of preferences" mode.
setContentView(com.android.internal.R.layout.preference_list_content_single);
mListFooter = (FrameLayout) findViewById(com.android.internal.R.id.list_footer);
mPrefsContainer = (ViewGroup) findViewById(com.android.internal.R.id.prefs);
mPreferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
mPreferenceManager.setOnPreferenceTreeClickListener(this);
mHeadersContainer = null;
} else if (mSinglePane) {
// Single-pane so one of the header or prefs containers must be hidden.
if (initialFragment != null || mCurHeader != null) {
mHeadersContainer.setVisibility(View.GONE);
} else {
mPrefsContainer.setVisibility(View.GONE);
}
// This animates our manual transitions between headers and prefs panel in single-pane.
// It also comes last so we don't animate any initial layout changes done above.
ViewGroup container = (ViewGroup) findViewById(
com.android.internal.R.id.prefs_container);
container.setLayoutTransition(new LayoutTransition());
} else {
// Multi-pane
if (mHeaders.size() > 0 && mCurHeader != null) {
setSelectedHeader(mCurHeader);
}
}
// see if we should show Back/Next buttons
Intent intent = getIntent();
if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false)) {
findViewById(com.android.internal.R.id.button_bar).setVisibility(View.VISIBLE);
Button backButton = (Button)findViewById(com.android.internal.R.id.back_button);
backButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_CANCELED);
finish();
}
});
Button skipButton = (Button)findViewById(com.android.internal.R.id.skip_button);
skipButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_OK);
finish();
}
});
mNextButton = (Button)findViewById(com.android.internal.R.id.next_button);
mNextButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
setResult(RESULT_OK);
finish();
}
});
// set our various button parameters
if (intent.hasExtra(EXTRA_PREFS_SET_NEXT_TEXT)) {
String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_NEXT_TEXT);
if (TextUtils.isEmpty(buttonText)) {
mNextButton.setVisibility(View.GONE);
}
else {
mNextButton.setText(buttonText);
}
}
if (intent.hasExtra(EXTRA_PREFS_SET_BACK_TEXT)) {
String buttonText = intent.getStringExtra(EXTRA_PREFS_SET_BACK_TEXT);
if (TextUtils.isEmpty(buttonText)) {
backButton.setVisibility(View.GONE);
}
else {
backButton.setText(buttonText);
}
}
if (intent.getBooleanExtra(EXTRA_PREFS_SHOW_SKIP, false)) {
skipButton.setVisibility(View.VISIBLE);
}
}
}
@Override
public void onBackPressed() {
if (mCurHeader != null && mSinglePane && getFragmentManager().getBackStackEntryCount() == 0
&& getIntent().getStringExtra(EXTRA_SHOW_FRAGMENT) == null) {
mCurHeader = null;
mPrefsContainer.setVisibility(View.GONE);
mHeadersContainer.setVisibility(View.VISIBLE);
if (mActivityTitle != null) {
showBreadCrumbs(mActivityTitle, null);
}
getListView().clearChoices();
} else {
super.onBackPressed();
}
}
Returns true if this activity is currently showing the header list.
/**
* Returns true if this activity is currently showing the header list.
*/
public boolean hasHeaders() {
return mHeadersContainer != null && mHeadersContainer.getVisibility() == View.VISIBLE;
}
Returns the Header list
@hide
/**
* Returns the Header list
* @hide
*/
public List<Header> getHeaders() {
return mHeaders;
}
Returns true if this activity is showing multiple panes -- the headers
and a preference fragment.
/**
* Returns true if this activity is showing multiple panes -- the headers
* and a preference fragment.
*/
public boolean isMultiPane() {
return !mSinglePane;
}
Called to determine if the activity should run in multi-pane mode.
The default implementation returns true if the screen is large
enough.
/**
* Called to determine if the activity should run in multi-pane mode.
* The default implementation returns true if the screen is large
* enough.
*/
public boolean onIsMultiPane() {
boolean preferMultiPane = getResources().getBoolean(
com.android.internal.R.bool.preferences_prefer_dual_pane);
return preferMultiPane;
}
Called to determine whether the header list should be hidden. The default implementation returns the value given in EXTRA_NO_HEADERS
or false if it is not supplied. This is set to false, for example, when the activity is being re-launched to show a particular preference activity. /**
* Called to determine whether the header list should be hidden.
* The default implementation returns the
* value given in {@link #EXTRA_NO_HEADERS} or false if it is not supplied.
* This is set to false, for example, when the activity is being re-launched
* to show a particular preference activity.
*/
public boolean onIsHidingHeaders() {
return getIntent().getBooleanExtra(EXTRA_NO_HEADERS, false);
}
Called to determine the initial header to be shown. The default
implementation simply returns the fragment of the first header. Note
that the returned Header object does not actually need to exist in
your header list -- whatever its fragment is will simply be used to
show for the initial UI.
/**
* Called to determine the initial header to be shown. The default
* implementation simply returns the fragment of the first header. Note
* that the returned Header object does not actually need to exist in
* your header list -- whatever its fragment is will simply be used to
* show for the initial UI.
*/
public Header onGetInitialHeader() {
for (int i=0; i<mHeaders.size(); i++) {
Header h = mHeaders.get(i);
if (h.fragment != null) {
return h;
}
}
throw new IllegalStateException("Must have at least one header with a fragment");
}
Called after the header list has been updated (onBuildHeaders
has been called and returned due to invalidateHeaders()
) to specify the header that should now be selected. The default implementation returns null to keep whatever header is currently selected. /**
* Called after the header list has been updated ({@link #onBuildHeaders}
* has been called and returned due to {@link #invalidateHeaders()}) to
* specify the header that should now be selected. The default implementation
* returns null to keep whatever header is currently selected.
*/
public Header onGetNewHeader() {
return null;
}
Called when the activity needs its list of headers build. By
implementing this and adding at least one item to the list, you
will cause the activity to run in its modern fragment mode. Note
that this function may not always be called; for example, if the
activity has been asked to display a particular fragment without
the header list, there is no need to build the headers.
Typical implementations will use loadHeadersFromResource
to fill in the list from a resource.
Params: - target – The list in which to place the headers.
/**
* Called when the activity needs its list of headers build. By
* implementing this and adding at least one item to the list, you
* will cause the activity to run in its modern fragment mode. Note
* that this function may not always be called; for example, if the
* activity has been asked to display a particular fragment without
* the header list, there is no need to build the headers.
*
* <p>Typical implementations will use {@link #loadHeadersFromResource}
* to fill in the list from a resource.
*
* @param target The list in which to place the headers.
*/
public void onBuildHeaders(List<Header> target) {
// Should be overloaded by subclasses
}
Call when you need to change the headers being displayed. Will result
in onBuildHeaders() later being called to retrieve the new list.
/**
* Call when you need to change the headers being displayed. Will result
* in onBuildHeaders() later being called to retrieve the new list.
*/
public void invalidateHeaders() {
if (!mHandler.hasMessages(MSG_BUILD_HEADERS)) {
mHandler.sendEmptyMessage(MSG_BUILD_HEADERS);
}
}
Parse the given XML file as a header description, adding each
parsed Header into the target list.
Params: - resid – The XML resource to load and parse.
- target – The list in which the parsed headers should be placed.
/**
* Parse the given XML file as a header description, adding each
* parsed Header into the target list.
*
* @param resid The XML resource to load and parse.
* @param target The list in which the parsed headers should be placed.
*/
public void loadHeadersFromResource(@XmlRes int resid, List<Header> target) {
XmlResourceParser parser = null;
try {
parser = getResources().getXml(resid);
AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& type != XmlPullParser.START_TAG) {
// Parse next until start tag is found
}
String nodeName = parser.getName();
if (!"preference-headers".equals(nodeName)) {
throw new RuntimeException(
"XML document must start with <preference-headers> tag; found"
+ nodeName + " at " + parser.getPositionDescription());
}
Bundle curBundle = null;
final int outerDepth = parser.getDepth();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
nodeName = parser.getName();
if ("header".equals(nodeName)) {
Header header = new Header();
TypedArray sa = obtainStyledAttributes(
attrs, com.android.internal.R.styleable.PreferenceHeader);
header.id = sa.getResourceId(
com.android.internal.R.styleable.PreferenceHeader_id,
(int)HEADER_ID_UNDEFINED);
TypedValue tv = sa.peekValue(
com.android.internal.R.styleable.PreferenceHeader_title);
if (tv != null && tv.type == TypedValue.TYPE_STRING) {
if (tv.resourceId != 0) {
header.titleRes = tv.resourceId;
} else {
header.title = tv.string;
}
}
tv = sa.peekValue(
com.android.internal.R.styleable.PreferenceHeader_summary);
if (tv != null && tv.type == TypedValue.TYPE_STRING) {
if (tv.resourceId != 0) {
header.summaryRes = tv.resourceId;
} else {
header.summary = tv.string;
}
}
tv = sa.peekValue(
com.android.internal.R.styleable.PreferenceHeader_breadCrumbTitle);
if (tv != null && tv.type == TypedValue.TYPE_STRING) {
if (tv.resourceId != 0) {
header.breadCrumbTitleRes = tv.resourceId;
} else {
header.breadCrumbTitle = tv.string;
}
}
tv = sa.peekValue(
com.android.internal.R.styleable.PreferenceHeader_breadCrumbShortTitle);
if (tv != null && tv.type == TypedValue.TYPE_STRING) {
if (tv.resourceId != 0) {
header.breadCrumbShortTitleRes = tv.resourceId;
} else {
header.breadCrumbShortTitle = tv.string;
}
}
header.iconRes = sa.getResourceId(
com.android.internal.R.styleable.PreferenceHeader_icon, 0);
header.fragment = sa.getString(
com.android.internal.R.styleable.PreferenceHeader_fragment);
sa.recycle();
if (curBundle == null) {
curBundle = new Bundle();
}
final int innerDepth = parser.getDepth();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
continue;
}
String innerNodeName = parser.getName();
if (innerNodeName.equals("extra")) {
getResources().parseBundleExtra("extra", attrs, curBundle);
XmlUtils.skipCurrentTag(parser);
} else if (innerNodeName.equals("intent")) {
header.intent = Intent.parseIntent(getResources(), parser, attrs);
} else {
XmlUtils.skipCurrentTag(parser);
}
}
if (curBundle.size() > 0) {
header.fragmentArguments = curBundle;
curBundle = null;
}
target.add(header);
} else {
XmlUtils.skipCurrentTag(parser);
}
}
} catch (XmlPullParserException e) {
throw new RuntimeException("Error parsing headers", e);
} catch (IOException e) {
throw new RuntimeException("Error parsing headers", e);
} finally {
if (parser != null) parser.close();
}
}
Subclasses should override this method and verify that the given fragment is a valid type
to be attached to this activity. The default implementation returns true
for
apps built for android:targetSdkVersion
older than VERSION_CODES.KITKAT
. For later versions, it will throw an exception. Params: - fragmentName – the class name of the Fragment about to be attached to this activity.
Returns: true if the fragment class name is valid for this Activity and false otherwise.
/**
* Subclasses should override this method and verify that the given fragment is a valid type
* to be attached to this activity. The default implementation returns <code>true</code> for
* apps built for <code>android:targetSdkVersion</code> older than
* {@link android.os.Build.VERSION_CODES#KITKAT}. For later versions, it will throw an exception.
* @param fragmentName the class name of the Fragment about to be attached to this activity.
* @return true if the fragment class name is valid for this Activity and false otherwise.
*/
protected boolean isValidFragment(String fragmentName) {
if (getApplicationInfo().targetSdkVersion >= android.os.Build.VERSION_CODES.KITKAT) {
throw new RuntimeException(
"Subclasses of PreferenceActivity must override isValidFragment(String)"
+ " to verify that the Fragment class is valid! "
+ this.getClass().getName()
+ " has not checked if fragment " + fragmentName + " is valid.");
} else {
return true;
}
}
Set a footer that should be shown at the bottom of the header list.
/**
* Set a footer that should be shown at the bottom of the header list.
*/
public void setListFooter(View view) {
mListFooter.removeAllViews();
mListFooter.addView(view, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT));
}
@Override
protected void onStop() {
super.onStop();
if (mPreferenceManager != null) {
mPreferenceManager.dispatchActivityStop();
}
}
@Override
protected void onDestroy() {
mHandler.removeMessages(MSG_BIND_PREFERENCES);
mHandler.removeMessages(MSG_BUILD_HEADERS);
super.onDestroy();
if (mPreferenceManager != null) {
mPreferenceManager.dispatchActivityDestroy();
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mHeaders.size() > 0) {
outState.putParcelableArrayList(HEADERS_TAG, mHeaders);
if (mCurHeader != null) {
int index = mHeaders.indexOf(mCurHeader);
if (index >= 0) {
outState.putInt(CUR_HEADER_TAG, index);
}
}
}
if (mPreferenceManager != null) {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
Bundle container = new Bundle();
preferenceScreen.saveHierarchyState(container);
outState.putBundle(PREFERENCES_TAG, container);
}
}
}
@Override
protected void onRestoreInstanceState(Bundle state) {
if (mPreferenceManager != null) {
Bundle container = state.getBundle(PREFERENCES_TAG);
if (container != null) {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
preferenceScreen.restoreHierarchyState(container);
mSavedInstanceState = state;
return;
}
}
}
// Only call this if we didn't save the instance state for later.
// If we did save it, it will be restored when we bind the adapter.
super.onRestoreInstanceState(state);
if (!mSinglePane) {
// Multi-pane.
if (mCurHeader != null) {
setSelectedHeader(mCurHeader);
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (mPreferenceManager != null) {
mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onContentChanged() {
super.onContentChanged();
if (mPreferenceManager != null) {
postBindPreferences();
}
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
if (!isResumed()) {
return;
}
super.onListItemClick(l, v, position, id);
if (mAdapter != null) {
Object item = mAdapter.getItem(position);
if (item instanceof Header) onHeaderClick((Header) item, position);
}
}
Called when the user selects an item in the header list. The default implementation will call either startWithFragment(String, Bundle, Fragment, int, int, int)
or switchToHeader(Header)
as appropriate. Params: - header – The header that was selected.
- position – The header's position in the list.
/**
* Called when the user selects an item in the header list. The default
* implementation will call either
* {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
* or {@link #switchToHeader(Header)} as appropriate.
*
* @param header The header that was selected.
* @param position The header's position in the list.
*/
public void onHeaderClick(Header header, int position) {
if (header.fragment != null) {
switchToHeader(header);
} else if (header.intent != null) {
startActivity(header.intent);
}
}
Called by startWithFragment(String, Bundle, Fragment, int, int, int)
when in single-pane mode, to build an Intent to launch a new activity showing the selected fragment. The default implementation constructs an Intent that re-launches the current activity with the appropriate arguments to display the fragment. Params: - fragmentName – The name of the fragment to display.
- args – Optional arguments to supply to the fragment.
- titleRes – Optional resource ID of title to show for this item.
- shortTitleRes – Optional resource ID of short title to show for this item.
Returns: Returns an Intent that can be launched to display the given
fragment.
/**
* Called by {@link #startWithFragment(String, Bundle, Fragment, int, int, int)} when
* in single-pane mode, to build an Intent to launch a new activity showing
* the selected fragment. The default implementation constructs an Intent
* that re-launches the current activity with the appropriate arguments to
* display the fragment.
*
* @param fragmentName The name of the fragment to display.
* @param args Optional arguments to supply to the fragment.
* @param titleRes Optional resource ID of title to show for this item.
* @param shortTitleRes Optional resource ID of short title to show for this item.
* @return Returns an Intent that can be launched to display the given
* fragment.
*/
public Intent onBuildStartFragmentIntent(String fragmentName, Bundle args,
@StringRes int titleRes, int shortTitleRes) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClass(this, getClass());
intent.putExtra(EXTRA_SHOW_FRAGMENT, fragmentName);
intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args);
intent.putExtra(EXTRA_SHOW_FRAGMENT_TITLE, titleRes);
intent.putExtra(EXTRA_SHOW_FRAGMENT_SHORT_TITLE, shortTitleRes);
intent.putExtra(EXTRA_NO_HEADERS, true);
return intent;
}
Like startWithFragment(String, Bundle, Fragment, int, int, int)
but uses a 0 titleRes. /**
* Like {@link #startWithFragment(String, Bundle, Fragment, int, int, int)}
* but uses a 0 titleRes.
*/
public void startWithFragment(String fragmentName, Bundle args,
Fragment resultTo, int resultRequestCode) {
startWithFragment(fragmentName, args, resultTo, resultRequestCode, 0, 0);
}
Start a new instance of this activity, showing only the given
preference fragment. When launched in this mode, the header list
will be hidden and the given preference fragment will be instantiated
and fill the entire activity.
Params: - fragmentName – The name of the fragment to display.
- args – Optional arguments to supply to the fragment.
- resultTo – Option fragment that should receive the result of
the activity launch.
- resultRequestCode – If resultTo is non-null, this is the request
code in which to report the result.
- titleRes – Resource ID of string to display for the title of
this set of preferences.
- shortTitleRes – Resource ID of string to display for the short title of
this set of preferences.
/**
* Start a new instance of this activity, showing only the given
* preference fragment. When launched in this mode, the header list
* will be hidden and the given preference fragment will be instantiated
* and fill the entire activity.
*
* @param fragmentName The name of the fragment to display.
* @param args Optional arguments to supply to the fragment.
* @param resultTo Option fragment that should receive the result of
* the activity launch.
* @param resultRequestCode If resultTo is non-null, this is the request
* code in which to report the result.
* @param titleRes Resource ID of string to display for the title of
* this set of preferences.
* @param shortTitleRes Resource ID of string to display for the short title of
* this set of preferences.
*/
public void startWithFragment(String fragmentName, Bundle args,
Fragment resultTo, int resultRequestCode, @StringRes int titleRes,
@StringRes int shortTitleRes) {
Intent intent = onBuildStartFragmentIntent(fragmentName, args, titleRes, shortTitleRes);
if (resultTo == null) {
startActivity(intent);
} else {
resultTo.startActivityForResult(intent, resultRequestCode);
}
}
Change the base title of the bread crumbs for the current preferences. This will normally be called for you. See FragmentBreadCrumbs
for more information. /**
* Change the base title of the bread crumbs for the current preferences.
* This will normally be called for you. See
* {@link android.app.FragmentBreadCrumbs} for more information.
*/
public void showBreadCrumbs(CharSequence title, CharSequence shortTitle) {
if (mFragmentBreadCrumbs == null) {
View crumbs = findViewById(android.R.id.title);
// For screens with a different kind of title, don't create breadcrumbs.
try {
mFragmentBreadCrumbs = (FragmentBreadCrumbs)crumbs;
} catch (ClassCastException e) {
setTitle(title);
return;
}
if (mFragmentBreadCrumbs == null) {
if (title != null) {
setTitle(title);
}
return;
}
if (mSinglePane) {
mFragmentBreadCrumbs.setVisibility(View.GONE);
// Hide the breadcrumb section completely for single-pane
View bcSection = findViewById(com.android.internal.R.id.breadcrumb_section);
if (bcSection != null) bcSection.setVisibility(View.GONE);
setTitle(title);
}
mFragmentBreadCrumbs.setMaxVisible(2);
mFragmentBreadCrumbs.setActivity(this);
}
if (mFragmentBreadCrumbs.getVisibility() != View.VISIBLE) {
setTitle(title);
} else {
mFragmentBreadCrumbs.setTitle(title, shortTitle);
mFragmentBreadCrumbs.setParentTitle(null, null, null);
}
}
Should be called after onCreate to ensure that the breadcrumbs, if any, were created.
This prepends a title to the fragment breadcrumbs and attaches a listener to any clicks
on the parent entry.
Params: - title – the title for the breadcrumb
- shortTitle – the short title for the breadcrumb
/**
* Should be called after onCreate to ensure that the breadcrumbs, if any, were created.
* This prepends a title to the fragment breadcrumbs and attaches a listener to any clicks
* on the parent entry.
* @param title the title for the breadcrumb
* @param shortTitle the short title for the breadcrumb
*/
public void setParentTitle(CharSequence title, CharSequence shortTitle,
OnClickListener listener) {
if (mFragmentBreadCrumbs != null) {
mFragmentBreadCrumbs.setParentTitle(title, shortTitle, listener);
}
}
void setSelectedHeader(Header header) {
mCurHeader = header;
int index = mHeaders.indexOf(header);
if (index >= 0) {
getListView().setItemChecked(index, true);
} else {
getListView().clearChoices();
}
showBreadCrumbs(header);
}
void showBreadCrumbs(Header header) {
if (header != null) {
CharSequence title = header.getBreadCrumbTitle(getResources());
if (title == null) title = header.getTitle(getResources());
if (title == null) title = getTitle();
showBreadCrumbs(title, header.getBreadCrumbShortTitle(getResources()));
} else {
showBreadCrumbs(getTitle(), null);
}
}
private void switchToHeaderInner(String fragmentName, Bundle args) {
getFragmentManager().popBackStack(BACK_STACK_PREFS,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
if (!isValidFragment(fragmentName)) {
throw new IllegalArgumentException("Invalid fragment for this activity: "
+ fragmentName);
}
Fragment f = Fragment.instantiate(this, fragmentName, args);
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(mSinglePane
? FragmentTransaction.TRANSIT_NONE
: FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.replace(com.android.internal.R.id.prefs, f);
transaction.commitAllowingStateLoss();
if (mSinglePane && mPrefsContainer.getVisibility() == View.GONE) {
// We are transitioning from headers to preferences panel in single-pane so we need
// to hide headers and show the prefs container.
mPrefsContainer.setVisibility(View.VISIBLE);
mHeadersContainer.setVisibility(View.GONE);
}
}
When in two-pane mode, switch the fragment pane to show the given
preference fragment.
Params: - fragmentName – The name of the fragment to display.
- args – Optional arguments to supply to the fragment.
/**
* When in two-pane mode, switch the fragment pane to show the given
* preference fragment.
*
* @param fragmentName The name of the fragment to display.
* @param args Optional arguments to supply to the fragment.
*/
public void switchToHeader(String fragmentName, Bundle args) {
Header selectedHeader = null;
for (int i = 0; i < mHeaders.size(); i++) {
if (fragmentName.equals(mHeaders.get(i).fragment)) {
selectedHeader = mHeaders.get(i);
break;
}
}
setSelectedHeader(selectedHeader);
switchToHeaderInner(fragmentName, args);
}
When in two-pane mode, switch to the fragment pane to show the given
preference fragment.
Params: - header – The new header to display.
/**
* When in two-pane mode, switch to the fragment pane to show the given
* preference fragment.
*
* @param header The new header to display.
*/
public void switchToHeader(Header header) {
if (mCurHeader == header) {
// This is the header we are currently displaying. Just make sure
// to pop the stack up to its root state.
getFragmentManager().popBackStack(BACK_STACK_PREFS,
FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
if (header.fragment == null) {
throw new IllegalStateException("can't switch to header that has no fragment");
}
switchToHeaderInner(header.fragment, header.fragmentArguments);
setSelectedHeader(header);
}
}
Header findBestMatchingHeader(Header cur, ArrayList<Header> from) {
ArrayList<Header> matches = new ArrayList<Header>();
for (int j=0; j<from.size(); j++) {
Header oh = from.get(j);
if (cur == oh || (cur.id != HEADER_ID_UNDEFINED && cur.id == oh.id)) {
// Must be this one.
matches.clear();
matches.add(oh);
break;
}
if (cur.fragment != null) {
if (cur.fragment.equals(oh.fragment)) {
matches.add(oh);
}
} else if (cur.intent != null) {
if (cur.intent.equals(oh.intent)) {
matches.add(oh);
}
} else if (cur.title != null) {
if (cur.title.equals(oh.title)) {
matches.add(oh);
}
}
}
final int NM = matches.size();
if (NM == 1) {
return matches.get(0);
} else if (NM > 1) {
for (int j=0; j<NM; j++) {
Header oh = matches.get(j);
if (cur.fragmentArguments != null &&
cur.fragmentArguments.equals(oh.fragmentArguments)) {
return oh;
}
if (cur.extras != null && cur.extras.equals(oh.extras)) {
return oh;
}
if (cur.title != null && cur.title.equals(oh.title)) {
return oh;
}
}
}
return null;
}
Start a new fragment.
Params: - fragment – The fragment to start
- push – If true, the current fragment will be pushed onto the back stack. If false,
the current fragment will be replaced.
/**
* Start a new fragment.
*
* @param fragment The fragment to start
* @param push If true, the current fragment will be pushed onto the back stack. If false,
* the current fragment will be replaced.
*/
public void startPreferenceFragment(Fragment fragment, boolean push) {
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(com.android.internal.R.id.prefs, fragment);
if (push) {
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(BACK_STACK_PREFS);
} else {
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
}
transaction.commitAllowingStateLoss();
}
Start a new fragment containing a preference panel. If the preferences
are being displayed in multi-pane mode, the given fragment class will
be instantiated and placed in the appropriate pane. If running in
single-pane mode, a new activity will be launched in which to show the
fragment.
Params: - fragmentClass – Full name of the class implementing the fragment.
- args – Any desired arguments to supply to the fragment.
- titleRes – Optional resource identifier of the title of this
fragment.
- titleText – Optional text of the title of this fragment.
- resultTo – Optional fragment that result data should be sent to. If non-null, resultTo.onActivityResult() will be called when this preference panel is done. The launched panel must use
finishPreferencePanel(Fragment, int, Intent)
when done. - resultRequestCode – If resultTo is non-null, this is the caller's
request code to be received with the result.
/**
* Start a new fragment containing a preference panel. If the preferences
* are being displayed in multi-pane mode, the given fragment class will
* be instantiated and placed in the appropriate pane. If running in
* single-pane mode, a new activity will be launched in which to show the
* fragment.
*
* @param fragmentClass Full name of the class implementing the fragment.
* @param args Any desired arguments to supply to the fragment.
* @param titleRes Optional resource identifier of the title of this
* fragment.
* @param titleText Optional text of the title of this fragment.
* @param resultTo Optional fragment that result data should be sent to.
* If non-null, resultTo.onActivityResult() will be called when this
* preference panel is done. The launched panel must use
* {@link #finishPreferencePanel(Fragment, int, Intent)} when done.
* @param resultRequestCode If resultTo is non-null, this is the caller's
* request code to be received with the result.
*/
public void startPreferencePanel(String fragmentClass, Bundle args, @StringRes int titleRes,
CharSequence titleText, Fragment resultTo, int resultRequestCode) {
Fragment f = Fragment.instantiate(this, fragmentClass, args);
if (resultTo != null) {
f.setTargetFragment(resultTo, resultRequestCode);
}
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(com.android.internal.R.id.prefs, f);
if (titleRes != 0) {
transaction.setBreadCrumbTitle(titleRes);
} else if (titleText != null) {
transaction.setBreadCrumbTitle(titleText);
}
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(BACK_STACK_PREFS);
transaction.commitAllowingStateLoss();
}
Called by a preference panel fragment to finish itself.
Params: - caller – The fragment that is asking to be finished.
- resultCode – Optional result code to send back to the original
launching fragment.
- resultData – Optional result data to send back to the original
launching fragment.
/**
* Called by a preference panel fragment to finish itself.
*
* @param caller The fragment that is asking to be finished.
* @param resultCode Optional result code to send back to the original
* launching fragment.
* @param resultData Optional result data to send back to the original
* launching fragment.
*/
public void finishPreferencePanel(Fragment caller, int resultCode, Intent resultData) {
// TODO: be smarter about popping the stack.
onBackPressed();
if (caller != null) {
if (caller.getTargetFragment() != null) {
caller.getTargetFragment().onActivityResult(caller.getTargetRequestCode(),
resultCode, resultData);
}
}
}
@Override
public boolean onPreferenceStartFragment(PreferenceFragment caller, Preference pref) {
startPreferencePanel(pref.getFragment(), pref.getExtras(), pref.getTitleRes(),
pref.getTitle(), null, 0);
return true;
}
Posts a message to bind the preferences to the list view.
Binding late is preferred as any custom preference types created in onCreate(Bundle)
are able to have their views recycled.
/**
* Posts a message to bind the preferences to the list view.
* <p>
* Binding late is preferred as any custom preference types created in
* {@link #onCreate(Bundle)} are able to have their views recycled.
*/
private void postBindPreferences() {
if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) return;
mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget();
}
private void bindPreferences() {
final PreferenceScreen preferenceScreen = getPreferenceScreen();
if (preferenceScreen != null) {
preferenceScreen.bind(getListView());
if (mSavedInstanceState != null) {
super.onRestoreInstanceState(mSavedInstanceState);
mSavedInstanceState = null;
}
}
}
Returns the PreferenceManager
used by this activity. Returns: The PreferenceManager
. Deprecated: This function is not relevant for a modern fragment-based
PreferenceActivity.
/**
* Returns the {@link PreferenceManager} used by this activity.
* @return The {@link PreferenceManager}.
*
* @deprecated This function is not relevant for a modern fragment-based
* PreferenceActivity.
*/
@Deprecated
public PreferenceManager getPreferenceManager() {
return mPreferenceManager;
}
private void requirePreferenceManager() {
if (mPreferenceManager == null) {
if (mAdapter == null) {
throw new RuntimeException("This should be called after super.onCreate.");
}
throw new RuntimeException(
"Modern two-pane PreferenceActivity requires use of a PreferenceFragment");
}
}
Sets the root of the preference hierarchy that this activity is showing.
Params: - preferenceScreen – The root
PreferenceScreen
of the preference hierarchy.
Deprecated: This function is not relevant for a modern fragment-based
PreferenceActivity.
/**
* Sets the root of the preference hierarchy that this activity is showing.
*
* @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
*
* @deprecated This function is not relevant for a modern fragment-based
* PreferenceActivity.
*/
@Deprecated
public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
requirePreferenceManager();
if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
postBindPreferences();
CharSequence title = getPreferenceScreen().getTitle();
// Set the title of the activity
if (title != null) {
setTitle(title);
}
}
}
Gets the root of the preference hierarchy that this activity is showing.
Returns: The PreferenceScreen
that is the root of the preference hierarchy. Deprecated: This function is not relevant for a modern fragment-based
PreferenceActivity.
/**
* Gets the root of the preference hierarchy that this activity is showing.
*
* @return The {@link PreferenceScreen} that is the root of the preference
* hierarchy.
*
* @deprecated This function is not relevant for a modern fragment-based
* PreferenceActivity.
*/
@Deprecated
public PreferenceScreen getPreferenceScreen() {
if (mPreferenceManager != null) {
return mPreferenceManager.getPreferenceScreen();
}
return null;
}
Adds preferences from activities that match the given Intent
. Params: - intent – The
Intent
to query activities.
Deprecated: This function is not relevant for a modern fragment-based
PreferenceActivity.
/**
* Adds preferences from activities that match the given {@link Intent}.
*
* @param intent The {@link Intent} to query activities.
*
* @deprecated This function is not relevant for a modern fragment-based
* PreferenceActivity.
*/
@Deprecated
public void addPreferencesFromIntent(Intent intent) {
requirePreferenceManager();
setPreferenceScreen(mPreferenceManager.inflateFromIntent(intent, getPreferenceScreen()));
}
Inflates the given XML resource and adds the preference hierarchy to the current
preference hierarchy.
Params: - preferencesResId – The XML resource ID to inflate.
Deprecated: This function is not relevant for a modern fragment-based
PreferenceActivity.
/**
* Inflates the given XML resource and adds the preference hierarchy to the current
* preference hierarchy.
*
* @param preferencesResId The XML resource ID to inflate.
*
* @deprecated This function is not relevant for a modern fragment-based
* PreferenceActivity.
*/
@Deprecated
public void addPreferencesFromResource(int preferencesResId) {
requirePreferenceManager();
setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
getPreferenceScreen()));
}
{@inheritDoc}
Deprecated: This function is not relevant for a modern fragment-based
PreferenceActivity.
/**
* {@inheritDoc}
*
* @deprecated This function is not relevant for a modern fragment-based
* PreferenceActivity.
*/
@Deprecated
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
return false;
}
Finds a Preference
based on its key. Params: - key – The key of the preference to retrieve.
See Also: Returns: The Preference
with the key, or null. Deprecated: This function is not relevant for a modern fragment-based
PreferenceActivity.
/**
* Finds a {@link Preference} based on its key.
*
* @param key The key of the preference to retrieve.
* @return The {@link Preference} with the key, or null.
* @see PreferenceGroup#findPreference(CharSequence)
*
* @deprecated This function is not relevant for a modern fragment-based
* PreferenceActivity.
*/
@Deprecated
public Preference findPreference(CharSequence key) {
if (mPreferenceManager == null) {
return null;
}
return mPreferenceManager.findPreference(key);
}
@Override
protected void onNewIntent(Intent intent) {
if (mPreferenceManager != null) {
mPreferenceManager.dispatchNewIntent(intent);
}
}
// give subclasses access to the Next button
@hide
/** @hide */
protected boolean hasNextButton() {
return mNextButton != null;
}
@hide
/** @hide */
protected Button getNextButton() {
return mNextButton;
}
}