summaryrefslogtreecommitdiffstats
path: root/core/java/android/preference
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/preference')
-rw-r--r--core/java/android/preference/CheckBoxPreference.java295
-rw-r--r--core/java/android/preference/DialogPreference.java445
-rw-r--r--core/java/android/preference/EditTextPreference.java228
-rw-r--r--core/java/android/preference/GenericInflater.java520
-rw-r--r--core/java/android/preference/ListPreference.java277
-rw-r--r--core/java/android/preference/OnDependencyChangeListener.java32
-rw-r--r--core/java/android/preference/Preference.java1577
-rw-r--r--core/java/android/preference/PreferenceActivity.java284
-rw-r--r--core/java/android/preference/PreferenceCategory.java58
-rw-r--r--core/java/android/preference/PreferenceGroup.java320
-rw-r--r--core/java/android/preference/PreferenceGroupAdapter.java246
-rw-r--r--core/java/android/preference/PreferenceInflater.java103
-rw-r--r--core/java/android/preference/PreferenceManager.java790
-rw-r--r--core/java/android/preference/PreferenceScreen.java252
-rw-r--r--core/java/android/preference/RingtonePreference.java242
-rw-r--r--core/java/android/preference/SeekBarPreference.java62
-rw-r--r--core/java/android/preference/VolumePreference.java161
-rw-r--r--core/java/android/preference/package.html23
18 files changed, 5915 insertions, 0 deletions
diff --git a/core/java/android/preference/CheckBoxPreference.java b/core/java/android/preference/CheckBoxPreference.java
new file mode 100644
index 0000000..4bebf87
--- /dev/null
+++ b/core/java/android/preference/CheckBoxPreference.java
@@ -0,0 +1,295 @@
+/*
+ * 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.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Checkable;
+import android.widget.TextView;
+
+/**
+ * The {@link CheckBoxPreference} is a preference that provides checkbox widget
+ * functionality.
+ * <p>
+ * This preference will store a boolean into the SharedPreferences.
+ *
+ * @attr ref android.R.styleable#CheckBoxPreference_summaryOff
+ * @attr ref android.R.styleable#CheckBoxPreference_summaryOn
+ * @attr ref android.R.styleable#CheckBoxPreference_disableDependentsState
+ */
+public class CheckBoxPreference extends Preference {
+
+ private CharSequence mSummaryOn;
+ private CharSequence mSummaryOff;
+
+ private boolean mChecked;
+
+ private boolean mDisableDependentsState;
+
+ public CheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.CheckBoxPreference, defStyle, 0);
+ mSummaryOn = a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOn);
+ mSummaryOff = a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOff);
+ mDisableDependentsState = a.getBoolean(
+ com.android.internal.R.styleable.CheckBoxPreference_disableDependentsState, false);
+ a.recycle();
+ }
+
+ public CheckBoxPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.checkBoxPreferenceStyle);
+ }
+
+ public CheckBoxPreference(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+
+ View checkboxView = view.findViewById(com.android.internal.R.id.checkbox);
+ if (checkboxView != null && checkboxView instanceof Checkable) {
+ ((Checkable) checkboxView).setChecked(mChecked);
+ }
+
+ // Sync the summary view
+ TextView summaryView = (TextView) view.findViewById(com.android.internal.R.id.summary);
+ if (summaryView != null) {
+ boolean useDefaultSummary = true;
+ if (mChecked && mSummaryOn != null) {
+ summaryView.setText(mSummaryOn);
+ useDefaultSummary = false;
+ } else if (!mChecked && mSummaryOff != null) {
+ summaryView.setText(mSummaryOff);
+ useDefaultSummary = false;
+ }
+
+ if (useDefaultSummary) {
+ final CharSequence summary = getSummary();
+ if (summary != null) {
+ summaryView.setText(summary);
+ useDefaultSummary = false;
+ }
+ }
+
+ int newVisibility = View.GONE;
+ if (!useDefaultSummary) {
+ // Someone has written to it
+ newVisibility = View.VISIBLE;
+ }
+ if (newVisibility != summaryView.getVisibility()) {
+ summaryView.setVisibility(newVisibility);
+ }
+ }
+ }
+
+ @Override
+ protected void onClick() {
+ super.onClick();
+
+ boolean newValue = !isChecked();
+
+ if (!callChangeListener(newValue)) {
+ return;
+ }
+
+ setChecked(newValue);
+ }
+
+ /**
+ * Sets the checked state and saves it to the {@link SharedPreferences}.
+ *
+ * @param checked The checked state.
+ */
+ public void setChecked(boolean checked) {
+ mChecked = checked;
+
+ persistBoolean(checked);
+
+ notifyDependencyChange(shouldDisableDependents());
+
+ notifyChanged();
+ }
+
+ /**
+ * Returns the checked state.
+ *
+ * @return The checked state.
+ */
+ public boolean isChecked() {
+ return mChecked;
+ }
+
+ @Override
+ public boolean shouldDisableDependents() {
+ boolean shouldDisable = mDisableDependentsState ? mChecked : !mChecked;
+ return shouldDisable || super.shouldDisableDependents();
+ }
+
+ /**
+ * Sets the summary to be shown when checked.
+ *
+ * @param summary The summary to be shown when checked.
+ */
+ public void setSummaryOn(CharSequence summary) {
+ mSummaryOn = summary;
+ if (isChecked()) {
+ notifyChanged();
+ }
+ }
+
+ /**
+ * @see #setSummaryOn(CharSequence)
+ * @param summaryResId The summary as a resource.
+ */
+ public void setSummaryOn(int summaryResId) {
+ setSummaryOn(getContext().getString(summaryResId));
+ }
+
+ /**
+ * Returns the summary to be shown when checked.
+ * @return The summary.
+ */
+ public CharSequence getSummaryOn() {
+ return mSummaryOn;
+ }
+
+ /**
+ * Sets the summary to be shown when unchecked.
+ *
+ * @param summary The summary to be shown when unchecked.
+ */
+ public void setSummaryOff(CharSequence summary) {
+ mSummaryOff = summary;
+ if (!isChecked()) {
+ notifyChanged();
+ }
+ }
+
+ /**
+ * @see #setSummaryOff(CharSequence)
+ * @param summaryResId The summary as a resource.
+ */
+ public void setSummaryOff(int summaryResId) {
+ setSummaryOff(getContext().getString(summaryResId));
+ }
+
+ /**
+ * Returns the summary to be shown when unchecked.
+ * @return The summary.
+ */
+ public CharSequence getSummaryOff() {
+ return mSummaryOff;
+ }
+
+ /**
+ * Returns whether dependents are disabled when this preference is on ({@code true})
+ * or when this preference is off ({@code false}).
+ *
+ * @return Whether dependents are disabled when this preference is on ({@code true})
+ * or when this preference is off ({@code false}).
+ */
+ public boolean getDisableDependentsState() {
+ return mDisableDependentsState;
+ }
+
+ /**
+ * Sets whether dependents are disabled when this preference is on ({@code true})
+ * or when this preference is off ({@code false}).
+ *
+ * @param disableDependentsState The preference state that should disable dependents.
+ */
+ public void setDisableDependentsState(boolean disableDependentsState) {
+ mDisableDependentsState = disableDependentsState;
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getBoolean(index, false);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ setChecked(restoreValue ? getPersistedBoolean(mChecked)
+ : (Boolean) defaultValue);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (isPersistent()) {
+ // No need to save instance state since it's persistent
+ return superState;
+ }
+
+ final SavedState myState = new SavedState(superState);
+ myState.checked = isChecked();
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ setChecked(myState.checked);
+ }
+
+ private static class SavedState extends BaseSavedState {
+ boolean checked;
+
+ public SavedState(Parcel source) {
+ super(source);
+ checked = source.readInt() == 1;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(checked ? 1 : 0);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+}
diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java
new file mode 100644
index 0000000..3eb65e2
--- /dev/null
+++ b/core/java/android/preference/DialogPreference.java
@@ -0,0 +1,445 @@
+/*
+ * 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.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * The {@link DialogPreference} class is a base class for preferences that are
+ * dialog-based. These preferences will, when clicked, open a dialog showing the
+ * actual preference controls.
+ *
+ * @attr ref android.R.styleable#DialogPreference_dialogTitle
+ * @attr ref android.R.styleable#DialogPreference_dialogMessage
+ * @attr ref android.R.styleable#DialogPreference_dialogIcon
+ * @attr ref android.R.styleable#DialogPreference_dialogLayout
+ * @attr ref android.R.styleable#DialogPreference_positiveButtonText
+ * @attr ref android.R.styleable#DialogPreference_negativeButtonText
+ */
+public abstract class DialogPreference extends Preference implements
+ DialogInterface.OnClickListener, DialogInterface.OnDismissListener,
+ PreferenceManager.OnActivityDestroyListener {
+ private AlertDialog.Builder mBuilder;
+
+ private CharSequence mDialogTitle;
+ private CharSequence mDialogMessage;
+ private Drawable mDialogIcon;
+ private CharSequence mPositiveButtonText;
+ private CharSequence mNegativeButtonText;
+ private int mDialogLayoutResId;
+
+ /** The dialog, if it is showing. */
+ private Dialog mDialog;
+
+ /** Which button was clicked. */
+ private int mWhichButtonClicked;
+
+ public DialogPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.DialogPreference, defStyle, 0);
+ mDialogTitle = a.getString(com.android.internal.R.styleable.DialogPreference_dialogTitle);
+ if (mDialogTitle == null) {
+ // Fallback on the regular title of the preference
+ // (the one that is seen in the list)
+ mDialogTitle = getTitle();
+ }
+ mDialogMessage = a.getString(com.android.internal.R.styleable.DialogPreference_dialogMessage);
+ mDialogIcon = a.getDrawable(com.android.internal.R.styleable.DialogPreference_dialogIcon);
+ mPositiveButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_positiveButtonText);
+ mNegativeButtonText = a.getString(com.android.internal.R.styleable.DialogPreference_negativeButtonText);
+ mDialogLayoutResId = a.getResourceId(com.android.internal.R.styleable.DialogPreference_dialogLayout,
+ mDialogLayoutResId);
+ a.recycle();
+
+ }
+
+ public DialogPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle);
+ }
+
+ /**
+ * Sets the title of the dialog. This will be shown on subsequent dialogs.
+ *
+ * @param dialogTitle The title.
+ */
+ public void setDialogTitle(CharSequence dialogTitle) {
+ mDialogTitle = dialogTitle;
+ }
+
+ /**
+ * @see #setDialogTitle(CharSequence)
+ * @param dialogTitleResId The dialog title as a resource.
+ */
+ public void setDialogTitle(int dialogTitleResId) {
+ setDialogTitle(getContext().getString(dialogTitleResId));
+ }
+
+ /**
+ * Returns the title to be shown on subsequent dialogs.
+ * @return The title.
+ */
+ public CharSequence getDialogTitle() {
+ return mDialogTitle;
+ }
+
+ /**
+ * Sets the message of the dialog. This will be shown on subsequent dialogs.
+ * <p>
+ * This message forms the content View of the dialog and conflicts with
+ * list-based dialogs, for example. If setting a custom View on a dialog via
+ * {@link #setDialogLayoutResource(int)}, include a text View with ID
+ * {@link android.R.id#message} and it will be populated with this message.
+ *
+ * @param dialogMessage The message.
+ */
+ public void setDialogMessage(CharSequence dialogMessage) {
+ mDialogMessage = dialogMessage;
+ }
+
+ /**
+ * @see #setDialogMessage(CharSequence)
+ * @param dialogMessageResId The dialog message as a resource.
+ */
+ public void setDialogMessage(int dialogMessageResId) {
+ setDialogMessage(getContext().getString(dialogMessageResId));
+ }
+
+ /**
+ * Returns the message to be shown on subsequent dialogs.
+ * @return The message.
+ */
+ public CharSequence getDialogMessage() {
+ return mDialogMessage;
+ }
+
+ /**
+ * Sets the icon of the dialog. This will be shown on subsequent dialogs.
+ *
+ * @param dialogIcon The icon, as a {@link Drawable}.
+ */
+ public void setDialogIcon(Drawable dialogIcon) {
+ mDialogIcon = dialogIcon;
+ }
+
+ /**
+ * Sets the icon (resource ID) of the dialog. This will be shown on
+ * subsequent dialogs.
+ *
+ * @param dialogIconRes The icon, as a resource ID.
+ */
+ public void setDialogIcon(int dialogIconRes) {
+ mDialogIcon = getContext().getResources().getDrawable(dialogIconRes);
+ }
+
+ /**
+ * Returns the icon to be shown on subsequent dialogs.
+ * @return The icon, as a {@link Drawable}.
+ */
+ public Drawable getDialogIcon() {
+ return mDialogIcon;
+ }
+
+ /**
+ * Sets the text of the positive button of the dialog. This will be shown on
+ * subsequent dialogs.
+ *
+ * @param positiveButtonText The text of the positive button.
+ */
+ public void setPositiveButtonText(CharSequence positiveButtonText) {
+ mPositiveButtonText = positiveButtonText;
+ }
+
+ /**
+ * @see #setPositiveButtonText(CharSequence)
+ * @param positiveButtonTextResId The positive button text as a resource.
+ */
+ public void setPositiveButtonText(int positiveButtonTextResId) {
+ setPositiveButtonText(getContext().getString(positiveButtonTextResId));
+ }
+
+ /**
+ * Returns the text of the positive button to be shown on subsequent
+ * dialogs.
+ *
+ * @return The text of the positive button.
+ */
+ public CharSequence getPositiveButtonText() {
+ return mPositiveButtonText;
+ }
+
+ /**
+ * Sets the text of the negative button of the dialog. This will be shown on
+ * subsequent dialogs.
+ *
+ * @param negativeButtonText The text of the negative button.
+ */
+ public void setNegativeButtonText(CharSequence negativeButtonText) {
+ mNegativeButtonText = negativeButtonText;
+ }
+
+ /**
+ * @see #setNegativeButtonText(CharSequence)
+ * @param negativeButtonTextResId The negative button text as a resource.
+ */
+ public void setNegativeButtonText(int negativeButtonTextResId) {
+ setNegativeButtonText(getContext().getString(negativeButtonTextResId));
+ }
+
+ /**
+ * Returns the text of the negative button to be shown on subsequent
+ * dialogs.
+ *
+ * @return The text of the negative button.
+ */
+ public CharSequence getNegativeButtonText() {
+ return mNegativeButtonText;
+ }
+
+ /**
+ * Sets the layout resource that is inflated as the {@link View} to be shown
+ * as the content View of subsequent dialogs.
+ *
+ * @param dialogLayoutResId The layout resource ID to be inflated.
+ * @see #setDialogMessage(CharSequence)
+ */
+ public void setDialogLayoutResource(int dialogLayoutResId) {
+ mDialogLayoutResId = dialogLayoutResId;
+ }
+
+ /**
+ * Returns the layout resource that is used as the content View for
+ * subsequent dialogs.
+ *
+ * @return The layout resource.
+ */
+ public int getDialogLayoutResource() {
+ return mDialogLayoutResId;
+ }
+
+ /**
+ * Prepares the dialog builder to be shown when the preference is clicked.
+ * Use this to set custom properties on the dialog.
+ * <p>
+ * Do not {@link AlertDialog.Builder#create()} or
+ * {@link AlertDialog.Builder#show()}.
+ */
+ protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+ }
+
+ @Override
+ protected void onClick() {
+ showDialog(null);
+ }
+
+ /**
+ * Shows the dialog associated with this Preference. This is normally initiated
+ * automatically on clicking on the preference. Call this method if you need to
+ * show the dialog on some other event.
+ *
+ * @param state Optional instance state to restore on the dialog
+ */
+ protected void showDialog(Bundle state) {
+ Context context = getContext();
+
+ mWhichButtonClicked = DialogInterface.BUTTON2;
+
+ mBuilder = new AlertDialog.Builder(context)
+ .setTitle(mDialogTitle)
+ .setIcon(mDialogIcon)
+ .setPositiveButton(mPositiveButtonText, this)
+ .setNegativeButton(mNegativeButtonText, this);
+
+ View contentView = onCreateDialogView();
+ if (contentView != null) {
+ onBindDialogView(contentView);
+ mBuilder.setView(contentView);
+ } else {
+ mBuilder.setMessage(mDialogMessage);
+ }
+
+ onPrepareDialogBuilder(mBuilder);
+
+ getPreferenceManager().registerOnActivityDestroyListener(this);
+
+ // Create the dialog
+ final Dialog dialog = mDialog = mBuilder.create();
+ if (state != null) {
+ dialog.onRestoreInstanceState(state);
+ }
+ dialog.setOnDismissListener(this);
+ dialog.show();
+ }
+
+ /**
+ * Creates the content view for the dialog (if a custom content view is
+ * required). By default, it inflates the dialog layout resource if it is
+ * set.
+ *
+ * @return The content View for the dialog.
+ * @see #setLayoutResource(int)
+ */
+ protected View onCreateDialogView() {
+ if (mDialogLayoutResId == 0) {
+ return null;
+ }
+
+ LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+ return inflater.inflate(mDialogLayoutResId, null);
+ }
+
+ /**
+ * Binds views in the content View of the dialog to data.
+ * <p>
+ * Make sure to call through to the superclass implementation.
+ *
+ * @param view The content View of the dialog, if it is custom.
+ */
+ protected void onBindDialogView(View view) {
+ View dialogMessageView = view.findViewById(com.android.internal.R.id.message);
+
+ if (dialogMessageView != null) {
+ final CharSequence message = getDialogMessage();
+ int newVisibility = View.GONE;
+
+ if (!TextUtils.isEmpty(message)) {
+ if (dialogMessageView instanceof TextView) {
+ ((TextView) dialogMessageView).setText(message);
+ }
+
+ newVisibility = View.VISIBLE;
+ }
+
+ if (dialogMessageView.getVisibility() != newVisibility) {
+ dialogMessageView.setVisibility(newVisibility);
+ }
+ }
+ }
+
+ public void onClick(DialogInterface dialog, int which) {
+ mWhichButtonClicked = which;
+ }
+
+ public void onDismiss(DialogInterface dialog) {
+
+ getPreferenceManager().unregisterOnActivityDestroyListener(this);
+
+ mDialog = null;
+ onDialogClosed(mWhichButtonClicked == DialogInterface.BUTTON1);
+ }
+
+ /**
+ * Called when the dialog is dismissed and should be used to save data to
+ * the {@link SharedPreferences}.
+ *
+ * @param positiveResult Whether the positive button was clicked (true), or
+ * the negative button was clicked or the dialog was canceled (false).
+ */
+ protected void onDialogClosed(boolean positiveResult) {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onActivityDestroy() {
+
+ if (mDialog == null || !mDialog.isShowing()) {
+ return;
+ }
+
+ mDialog.dismiss();
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (mDialog == null || !mDialog.isShowing()) {
+ return superState;
+ }
+
+ final SavedState myState = new SavedState(superState);
+ myState.isDialogShowing = true;
+ myState.dialogBundle = mDialog.onSaveInstanceState();
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ if (myState.isDialogShowing) {
+ showDialog(myState.dialogBundle);
+ }
+ }
+
+ private static class SavedState extends BaseSavedState {
+ boolean isDialogShowing;
+ Bundle dialogBundle;
+
+ public SavedState(Parcel source) {
+ super(source);
+ isDialogShowing = source.readInt() == 1;
+ dialogBundle = source.readBundle();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(isDialogShowing ? 1 : 0);
+ dest.writeBundle(dialogBundle);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+}
diff --git a/core/java/android/preference/EditTextPreference.java b/core/java/android/preference/EditTextPreference.java
new file mode 100644
index 0000000..be56003
--- /dev/null
+++ b/core/java/android/preference/EditTextPreference.java
@@ -0,0 +1,228 @@
+/*
+ * 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.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+/**
+ * The {@link EditTextPreference} class is a preference that allows for string
+ * input.
+ * <p>
+ * It is a subclass of {@link DialogPreference} and shows the {@link EditText}
+ * in a dialog. This {@link EditText} can be modified either programmatically
+ * via {@link #getEditText()}, or through XML by setting any EditText
+ * attributes on the EditTextPreference.
+ * <p>
+ * This preference will store a string into the SharedPreferences.
+ * <p>
+ * See {@link android.R.styleable#EditText EditText Attributes}.
+ */
+public class EditTextPreference extends DialogPreference {
+ /**
+ * The edit text shown in the dialog.
+ */
+ private EditText mEditText;
+
+ private String mText;
+
+ public EditTextPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mEditText = new EditText(context, attrs);
+
+ // Give it an ID so it can be saved/restored
+ mEditText.setId(com.android.internal.R.id.edit);
+
+ /*
+ * The preference framework and view framework both have an 'enabled'
+ * attribute. Most likely, the 'enabled' specified in this XML is for
+ * the preference framework, but it was also given to the view framework.
+ * We reset the enabled state.
+ */
+ mEditText.setEnabled(true);
+ }
+
+ public EditTextPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.editTextPreferenceStyle);
+ }
+
+ public EditTextPreference(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Saves the text to the {@link SharedPreferences}.
+ *
+ * @param text The text to save
+ */
+ public void setText(String text) {
+ final boolean wasBlocking = shouldDisableDependents();
+
+ mText = text;
+
+ persistString(text);
+
+ final boolean isBlocking = shouldDisableDependents();
+ if (isBlocking != wasBlocking) {
+ notifyDependencyChange(isBlocking);
+ }
+ }
+
+ /**
+ * Gets the text from the {@link SharedPreferences}.
+ *
+ * @return The current preference value.
+ */
+ public String getText() {
+ return mText;
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ EditText editText = mEditText;
+ editText.setText(getText());
+
+ ViewParent oldParent = editText.getParent();
+ if (oldParent != view) {
+ if (oldParent != null) {
+ ((ViewGroup) oldParent).removeView(editText);
+ }
+ onAddEditTextToDialogView(view, editText);
+ }
+ }
+
+ /**
+ * Adds the EditText widget of this preference to the dialog's view.
+ *
+ * @param dialogView The dialog view.
+ */
+ protected void onAddEditTextToDialogView(View dialogView, EditText editText) {
+ ViewGroup container = (ViewGroup) dialogView
+ .findViewById(com.android.internal.R.id.edittext_container);
+ if (container != null) {
+ container.addView(editText, ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (positiveResult) {
+ String value = mEditText.getText().toString();
+ if (callChangeListener(value)) {
+ setText(value);
+ }
+ }
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getString(index);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ setText(restoreValue ? getPersistedString(mText) : (String) defaultValue);
+ }
+
+ @Override
+ public boolean shouldDisableDependents() {
+ return TextUtils.isEmpty(mText) || super.shouldDisableDependents();
+ }
+
+ /**
+ * Returns the {@link EditText} widget that will be shown in the dialog.
+ *
+ * @return The {@link EditText} widget that will be shown in the dialog.
+ */
+ public EditText getEditText() {
+ return mEditText;
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (isPersistent()) {
+ // No need to save instance state since it's persistent
+ return superState;
+ }
+
+ final SavedState myState = new SavedState(superState);
+ myState.text = getText();
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ setText(myState.text);
+ }
+
+ private static class SavedState extends BaseSavedState {
+ String text;
+
+ public SavedState(Parcel source) {
+ super(source);
+ text = source.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(text);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+}
diff --git a/core/java/android/preference/GenericInflater.java b/core/java/android/preference/GenericInflater.java
new file mode 100644
index 0000000..3003290
--- /dev/null
+++ b/core/java/android/preference/GenericInflater.java
@@ -0,0 +1,520 @@
+/*
+ * 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 java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.util.HashMap;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Xml;
+import android.view.ContextThemeWrapper;
+import android.view.InflateException;
+import android.view.LayoutInflater;
+
+// TODO: fix generics
+/**
+ * Generic XML inflater. This has been adapted from {@link LayoutInflater} and
+ * quickly passed over to use generics.
+ *
+ * @hide
+ * @param T The type of the items to inflate
+ * @param P The type of parents (that is those items that contain other items).
+ * Must implement {@link GenericInflater.Parent}
+ */
+abstract class GenericInflater<T, P extends GenericInflater.Parent> {
+ private final boolean DEBUG = false;
+
+ protected final Context mContext;
+
+ // these are optional, set by the caller
+ private boolean mFactorySet;
+ private Factory<T> mFactory;
+
+ private final Object[] mConstructorArgs = new Object[2];
+
+ private static final Class[] mConstructorSignature = new Class[] {
+ Context.class, AttributeSet.class};
+
+ private static final HashMap sConstructorMap = new HashMap();
+
+ private String mDefaultPackage;
+
+ public interface Parent<T> {
+ public void addItemFromInflater(T child);
+ }
+
+ public interface Factory<T> {
+ /**
+ * Hook you can supply that is called when inflating from a
+ * inflater. You can use this to customize the tag
+ * names available in your XML files.
+ * <p>
+ * Note that it is good practice to prefix these custom names with your
+ * package (i.e., com.coolcompany.apps) to avoid conflicts with system
+ * names.
+ *
+ * @param name Tag name to be inflated.
+ * @param context The context the item is being created in.
+ * @param attrs Inflation attributes as specified in XML file.
+ * @return Newly created item. Return null for the default behavior.
+ */
+ public T onCreateItem(String name, Context context, AttributeSet attrs);
+ }
+
+ private static class FactoryMerger<T> implements Factory<T> {
+ private final Factory<T> mF1, mF2;
+
+ FactoryMerger(Factory<T> f1, Factory<T> f2) {
+ mF1 = f1;
+ mF2 = f2;
+ }
+
+ public T onCreateItem(String name, Context context, AttributeSet attrs) {
+ T v = mF1.onCreateItem(name, context, attrs);
+ if (v != null) return v;
+ return mF2.onCreateItem(name, context, attrs);
+ }
+ }
+
+ /**
+ * Create a new inflater instance associated with a
+ * particular Context.
+ *
+ * @param context The Context in which this inflater will
+ * create its items; most importantly, this supplies the theme
+ * from which the default values for their attributes are
+ * retrieved.
+ */
+ protected GenericInflater(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Create a new inflater instance that is a copy of an
+ * existing inflater, optionally with its Context
+ * changed. For use in implementing {@link #cloneInContext}.
+ *
+ * @param original The original inflater to copy.
+ * @param newContext The new Context to use.
+ */
+ protected GenericInflater(GenericInflater<T,P> original, Context newContext) {
+ mContext = newContext;
+ mFactory = original.mFactory;
+ }
+
+ /**
+ * Create a copy of the existing inflater object, with the copy
+ * pointing to a different Context than the original. This is used by
+ * {@link ContextThemeWrapper} to create a new inflater to go along
+ * with the new Context theme.
+ *
+ * @param newContext The new Context to associate with the new inflater.
+ * May be the same as the original Context if desired.
+ *
+ * @return Returns a brand spanking new inflater object associated with
+ * the given Context.
+ */
+ public abstract GenericInflater cloneInContext(Context newContext);
+
+ /**
+ * Sets the default package that will be searched for classes to construct
+ * for tag names that have no explicit package.
+ *
+ * @param defaultPackage The default package. This will be prepended to the
+ * tag name, so it should end with a period.
+ */
+ public void setDefaultPackage(String defaultPackage) {
+ mDefaultPackage = defaultPackage;
+ }
+
+ /**
+ * Returns the default package, or null if it is not set.
+ *
+ * @see #setDefaultPackage(String)
+ * @return The default package.
+ */
+ public String getDefaultPackage() {
+ return mDefaultPackage;
+ }
+
+ /**
+ * Return the context we are running in, for access to resources, class
+ * loader, etc.
+ */
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Return the current factory (or null). This is called on each element
+ * name. If the factory returns an item, add that to the hierarchy. If it
+ * returns null, proceed to call onCreateItem(name).
+ */
+ public final Factory<T> getFactory() {
+ return mFactory;
+ }
+
+ /**
+ * Attach a custom Factory interface for creating items while using this
+ * inflater. This must not be null, and can only be set
+ * once; after setting, you can not change the factory. This is called on
+ * each element name as the XML is parsed. If the factory returns an item,
+ * that is added to the hierarchy. If it returns null, the next factory
+ * default {@link #onCreateItem} method is called.
+ * <p>
+ * If you have an existing inflater and want to add your
+ * own factory to it, use {@link #cloneInContext} to clone the existing
+ * instance and then you can use this function (once) on the returned new
+ * instance. This will merge your own factory with whatever factory the
+ * original instance is using.
+ */
+ public void setFactory(Factory<T> factory) {
+ if (mFactorySet) {
+ throw new IllegalStateException("" +
+ "A factory has already been set on this inflater");
+ }
+ if (factory == null) {
+ throw new NullPointerException("Given factory can not be null");
+ }
+ mFactorySet = true;
+ if (mFactory == null) {
+ mFactory = factory;
+ } else {
+ mFactory = new FactoryMerger<T>(factory, mFactory);
+ }
+ }
+
+
+ /**
+ * Inflate a new item hierarchy from the specified xml resource. Throws
+ * InflaterException if there is an error.
+ *
+ * @param resource ID for an XML resource to load (e.g.,
+ * <code>R.layout.main_page</code>)
+ * @param root Optional parent of the generated hierarchy.
+ * @return The root of the inflated hierarchy. If root was supplied,
+ * this is the root item; otherwise it is the root of the inflated
+ * XML file.
+ */
+ public T inflate(int resource, P root) {
+ return inflate(resource, root, root != null);
+ }
+
+ /**
+ * Inflate a new hierarchy from the specified xml node. Throws
+ * InflaterException if there is an error. *
+ * <p>
+ * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
+ * reasons, inflation relies heavily on pre-processing of XML files
+ * that is done at build time. Therefore, it is not currently possible to
+ * use inflater with an XmlPullParser over a plain XML file at runtime.
+ *
+ * @param parser XML dom node containing the description of the
+ * hierarchy.
+ * @param root Optional parent of the generated hierarchy.
+ * @return The root of the inflated hierarchy. If root was supplied,
+ * this is the that; otherwise it is the root of the inflated
+ * XML file.
+ */
+ public T inflate(XmlPullParser parser, P root) {
+ return inflate(parser, root, root != null);
+ }
+
+ /**
+ * Inflate a new hierarchy from the specified xml resource. Throws
+ * InflaterException if there is an error.
+ *
+ * @param resource ID for an XML resource to load (e.g.,
+ * <code>R.layout.main_page</code>)
+ * @param root Optional root to be the parent of the generated hierarchy (if
+ * <em>attachToRoot</em> is true), or else simply an object that
+ * provides a set of values for root of the returned
+ * hierarchy (if <em>attachToRoot</em> is false.)
+ * @param attachToRoot Whether the inflated hierarchy should be attached to
+ * the root parameter?
+ * @return The root of the inflated hierarchy. If root was supplied and
+ * attachToRoot is true, this is root; otherwise it is the root of
+ * the inflated XML file.
+ */
+ public T inflate(int resource, P root, boolean attachToRoot) {
+ if (DEBUG) System.out.println("INFLATING from resource: " + resource);
+ XmlResourceParser parser = getContext().getResources().getXml(resource);
+ try {
+ return inflate(parser, root, attachToRoot);
+ } finally {
+ parser.close();
+ }
+ }
+
+ /**
+ * Inflate a new hierarchy from the specified XML node. Throws
+ * InflaterException if there is an error.
+ * <p>
+ * <em><strong>Important</strong></em>&nbsp;&nbsp;&nbsp;For performance
+ * reasons, inflation relies heavily on pre-processing of XML files
+ * that is done at build time. Therefore, it is not currently possible to
+ * use inflater with an XmlPullParser over a plain XML file at runtime.
+ *
+ * @param parser XML dom node containing the description of the
+ * hierarchy.
+ * @param root Optional to be the parent of the generated hierarchy (if
+ * <em>attachToRoot</em> is true), or else simply an object that
+ * provides a set of values for root of the returned
+ * hierarchy (if <em>attachToRoot</em> is false.)
+ * @param attachToRoot Whether the inflated hierarchy should be attached to
+ * the root parameter?
+ * @return The root of the inflated hierarchy. If root was supplied and
+ * attachToRoot is true, this is root; otherwise it is the root of
+ * the inflated XML file.
+ */
+ public T inflate(XmlPullParser parser, P root,
+ boolean attachToRoot) {
+ synchronized (mConstructorArgs) {
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ mConstructorArgs[0] = mContext;
+ T result = (T) root;
+
+ try {
+ // Look for the root node.
+ int type;
+ while ((type = parser.next()) != parser.START_TAG
+ && type != parser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != parser.START_TAG) {
+ throw new InflateException(parser.getPositionDescription()
+ + ": No start tag found!");
+ }
+
+ if (DEBUG) {
+ System.out.println("**************************");
+ System.out.println("Creating root: "
+ + parser.getName());
+ System.out.println("**************************");
+ }
+ // Temp is the root that was found in the xml
+ T xmlRoot = createItemFromTag(parser, parser.getName(),
+ attrs);
+
+ result = (T) onMergeRoots(root, attachToRoot, (P) xmlRoot);
+
+ if (DEBUG) {
+ System.out.println("-----> start inflating children");
+ }
+ // Inflate all children under temp
+ rInflate(parser, result, attrs);
+ if (DEBUG) {
+ System.out.println("-----> done inflating children");
+ }
+
+ } catch (InflateException e) {
+ throw e;
+
+ } catch (XmlPullParserException e) {
+ InflateException ex = new InflateException(e.getMessage());
+ ex.initCause(e);
+ throw ex;
+ } catch (IOException e) {
+ InflateException ex = new InflateException(
+ parser.getPositionDescription()
+ + ": " + e.getMessage());
+ ex.initCause(e);
+ throw ex;
+ }
+
+ return result;
+ }
+ }
+
+ /**
+ * Low-level function for instantiating by name. This attempts to
+ * instantiate class of the given <var>name</var> found in this
+ * inflater's ClassLoader.
+ *
+ * <p>
+ * There are two things that can happen in an error case: either the
+ * exception describing the error will be thrown, or a null will be
+ * returned. You must deal with both possibilities -- the former will happen
+ * the first time createItem() is called for a class of a particular name,
+ * the latter every time there-after for that class name.
+ *
+ * @param name The full name of the class to be instantiated.
+ * @param attrs The XML attributes supplied for this instance.
+ *
+ * @return The newly instantied item, or null.
+ */
+ public final T createItem(String name, String prefix, AttributeSet attrs)
+ throws ClassNotFoundException, InflateException {
+ Constructor constructor = (Constructor) sConstructorMap.get(name);
+
+ try {
+ if (null == constructor) {
+ // Class not found in the cache, see if it's real,
+ // and try to add it
+ Class clazz = mContext.getClassLoader().loadClass(
+ prefix != null ? (prefix + name) : name);
+ constructor = clazz.getConstructor(mConstructorSignature);
+ sConstructorMap.put(name, constructor);
+ }
+
+ Object[] args = mConstructorArgs;
+ args[1] = attrs;
+ return (T) constructor.newInstance(args);
+
+ } catch (NoSuchMethodException e) {
+ InflateException ie = new InflateException(attrs
+ .getPositionDescription()
+ + ": Error inflating class "
+ + (prefix != null ? (prefix + name) : name));
+ ie.initCause(e);
+ throw ie;
+
+ } catch (ClassNotFoundException e) {
+ // If loadClass fails, we should propagate the exception.
+ throw e;
+ } catch (Exception e) {
+ InflateException ie = new InflateException(attrs
+ .getPositionDescription()
+ + ": Error inflating class "
+ + constructor.getClass().getName());
+ ie.initCause(e);
+ throw ie;
+ }
+ }
+
+ /**
+ * This routine is responsible for creating the correct subclass of item
+ * given the xml element name. Override it to handle custom item objects. If
+ * you override this in your subclass be sure to call through to
+ * super.onCreateItem(name) for names you do not recognize.
+ *
+ * @param name The fully qualified class name of the item to be create.
+ * @param attrs An AttributeSet of attributes to apply to the item.
+ * @return The item created.
+ */
+ protected T onCreateItem(String name, AttributeSet attrs) throws ClassNotFoundException {
+ return createItem(name, mDefaultPackage, attrs);
+ }
+
+ private final T createItemFromTag(XmlPullParser parser, String name, AttributeSet attrs) {
+ if (DEBUG) System.out.println("******** Creating item: " + name);
+
+ try {
+ T item = (mFactory == null) ? null : mFactory.onCreateItem(name, mContext, attrs);
+
+ if (item == null) {
+ if (-1 == name.indexOf('.')) {
+ item = onCreateItem(name, attrs);
+ } else {
+ item = createItem(name, null, attrs);
+ }
+ }
+
+ if (DEBUG) System.out.println("Created item is: " + item);
+ return item;
+
+ } catch (InflateException e) {
+ throw e;
+
+ } catch (ClassNotFoundException e) {
+ InflateException ie = new InflateException(attrs
+ .getPositionDescription()
+ + ": Error inflating class " + name);
+ ie.initCause(e);
+ throw ie;
+
+ } catch (Exception e) {
+ InflateException ie = new InflateException(attrs
+ .getPositionDescription()
+ + ": Error inflating class " + name);
+ ie.initCause(e);
+ throw ie;
+ }
+ }
+
+ /**
+ * Recursive method used to descend down the xml hierarchy and instantiate
+ * items, instantiate their children, and then call onFinishInflate().
+ */
+ private void rInflate(XmlPullParser parser, T parent, final AttributeSet attrs)
+ throws XmlPullParserException, IOException {
+ final int depth = parser.getDepth();
+
+ int type;
+ while (((type = parser.next()) != parser.END_TAG ||
+ parser.getDepth() > depth) && type != parser.END_DOCUMENT) {
+
+ if (type != parser.START_TAG) {
+ continue;
+ }
+
+ if (onCreateCustomFromTag(parser, parent, attrs)) {
+ continue;
+ }
+
+ if (DEBUG) {
+ System.out.println("Now inflating tag: " + parser.getName());
+ }
+ String name = parser.getName();
+
+ T item = createItemFromTag(parser, name, attrs);
+
+ if (DEBUG) {
+ System.out
+ .println("Creating params from parent: " + parent);
+ }
+
+ ((P) parent).addItemFromInflater(item);
+
+ if (DEBUG) {
+ System.out.println("-----> start inflating children");
+ }
+ rInflate(parser, item, attrs);
+ if (DEBUG) {
+ System.out.println("-----> done inflating children");
+ }
+ }
+
+ }
+
+ /**
+ * Before this inflater tries to create an item from the tag, this method
+ * will be called. The parser will be pointing to the start of a tag, you
+ * must stop parsing and return when you reach the end of this element!
+ *
+ * @param parser XML dom node containing the description of the hierarchy.
+ * @param parent The item that should be the parent of whatever you create.
+ * @param attrs An AttributeSet of attributes to apply to the item.
+ * @return Whether you created a custom object (true), or whether this
+ * inflater should proceed to create an item.
+ */
+ protected boolean onCreateCustomFromTag(XmlPullParser parser, T parent,
+ final AttributeSet attrs) throws XmlPullParserException {
+ return false;
+ }
+
+ protected P onMergeRoots(P givenRoot, boolean attachToGivenRoot, P xmlRoot) {
+ return xmlRoot;
+ }
+}
diff --git a/core/java/android/preference/ListPreference.java b/core/java/android/preference/ListPreference.java
new file mode 100644
index 0000000..6c98ded
--- /dev/null
+++ b/core/java/android/preference/ListPreference.java
@@ -0,0 +1,277 @@
+/*
+ * 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.app.AlertDialog.Builder;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+
+/**
+ * The {@link ListPreference} is a preference that displays a list of entries as
+ * a dialog.
+ * <p>
+ * This preference will store a string into the SharedPreferences. This string will be the value
+ * from the {@link #setEntryValues(CharSequence[])} array.
+ *
+ * @attr ref android.R.styleable#ListPreference_entries
+ * @attr ref android.R.styleable#ListPreference_entryValues
+ */
+public class ListPreference extends DialogPreference {
+ private CharSequence[] mEntries;
+ private CharSequence[] mEntryValues;
+ private String mValue;
+ private int mClickedDialogEntryIndex;
+
+ public ListPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ListPreference, 0, 0);
+ mEntries = a.getTextArray(com.android.internal.R.styleable.ListPreference_entries);
+ mEntryValues = a.getTextArray(com.android.internal.R.styleable.ListPreference_entryValues);
+ a.recycle();
+ }
+
+ public ListPreference(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Sets the human-readable entries to be shown in the list. This will be
+ * shown in subsequent dialogs.
+ * <p>
+ * Each entry must have a corresponding index in
+ * {@link #setEntryValues(CharSequence[])}.
+ *
+ * @param entries The entries.
+ * @see #setEntryValues(CharSequence[])
+ */
+ public void setEntries(CharSequence[] entries) {
+ mEntries = entries;
+ }
+
+ /**
+ * @see #setEntries(CharSequence[])
+ * @param entriesResId The entries array as a resource.
+ */
+ public void setEntries(int entriesResId) {
+ setEntries(getContext().getResources().getTextArray(entriesResId));
+ }
+
+ /**
+ * The list of entries to be shown in the list in subsequent dialogs.
+ *
+ * @return The list as an array.
+ */
+ public CharSequence[] getEntries() {
+ return mEntries;
+ }
+
+ /**
+ * The array to find the value to save for a preference when an entry from
+ * entries is selected. If a user clicks on the second item in entries, the
+ * second item in this array will be saved to the preference.
+ *
+ * @param entryValues The array to be used as values to save for the preference.
+ */
+ public void setEntryValues(CharSequence[] entryValues) {
+ mEntryValues = entryValues;
+ }
+
+ /**
+ * @see #setEntryValues(CharSequence[])
+ * @param entryValuesResId The entry values array as a resource.
+ */
+ public void setEntryValues(int entryValuesResId) {
+ setEntryValues(getContext().getResources().getTextArray(entryValuesResId));
+ }
+
+ /**
+ * Returns the array of values to be saved for the preference.
+ *
+ * @return The array of values.
+ */
+ public CharSequence[] getEntryValues() {
+ return mEntryValues;
+ }
+
+ /**
+ * Sets the value of the key. This should be one of the entries in
+ * {@link #getEntryValues()}.
+ *
+ * @param value The value to set for the key.
+ */
+ public void setValue(String value) {
+ mValue = value;
+
+ persistString(value);
+ }
+
+ /**
+ * Sets the value to the given index from the entry values.
+ *
+ * @param index The index of the value to set.
+ */
+ public void setValueIndex(int index) {
+ if (mEntryValues != null) {
+ setValue(mEntryValues[index].toString());
+ }
+ }
+
+ /**
+ * Returns the value of the key. This should be one of the entries in
+ * {@link #getEntryValues()}.
+ *
+ * @return The value of the key.
+ */
+ public String getValue() {
+ return mValue;
+ }
+
+ /**
+ * Returns the entry corresponding to the current value.
+ *
+ * @return The entry corresponding to the current value, or null.
+ */
+ public CharSequence getEntry() {
+ int index = getValueIndex();
+ return index >= 0 && mEntries != null ? mEntries[index] : null;
+ }
+
+ /**
+ * Returns the index of the given value (in the entry values array).
+ *
+ * @param value The value whose index should be returned.
+ * @return The index of the value, or -1 if not found.
+ */
+ public int findIndexOfValue(String value) {
+ if (value != null && mEntryValues != null) {
+ for (int i = mEntryValues.length - 1; i >= 0; i--) {
+ if (mEntryValues[i].equals(value)) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ private int getValueIndex() {
+ return findIndexOfValue(mValue);
+ }
+
+ @Override
+ protected void onPrepareDialogBuilder(Builder builder) {
+ super.onPrepareDialogBuilder(builder);
+
+ if (mEntries == null || mEntryValues == null) {
+ throw new IllegalStateException(
+ "ListPreference requires an entries array and an entryValues array.");
+ }
+
+ mClickedDialogEntryIndex = getValueIndex();
+ builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ mClickedDialogEntryIndex = which;
+ }
+ });
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (positiveResult && mClickedDialogEntryIndex >= 0 && mEntryValues != null) {
+ String value = mEntryValues[mClickedDialogEntryIndex].toString();
+ if (callChangeListener(value)) {
+ setValue(value);
+ }
+ }
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getString(index);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ setValue(restoreValue ? getPersistedString(mValue) : (String) defaultValue);
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ if (isPersistent()) {
+ // No need to save instance state since it's persistent
+ return superState;
+ }
+
+ final SavedState myState = new SavedState(superState);
+ myState.value = getValue();
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ setValue(myState.value);
+ }
+
+ private static class SavedState extends BaseSavedState {
+ String value;
+
+ public SavedState(Parcel source) {
+ super(source);
+ value = source.readString();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(value);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+}
diff --git a/core/java/android/preference/OnDependencyChangeListener.java b/core/java/android/preference/OnDependencyChangeListener.java
new file mode 100644
index 0000000..ce25e34
--- /dev/null
+++ b/core/java/android/preference/OnDependencyChangeListener.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+/**
+ * Interface definition for a callback to be invoked when this
+ * {@link Preference} changes with respect to enabling/disabling
+ * dependents.
+ */
+interface OnDependencyChangeListener {
+ /**
+ * Called when this preference has changed in a way that dependents should
+ * care to change their state.
+ *
+ * @param disablesDependent Whether the dependent should be disabled.
+ */
+ void onDependencyChanged(Preference dependency, boolean disablesDependent);
+}
diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java
new file mode 100644
index 0000000..1db7525
--- /dev/null
+++ b/core/java/android/preference/Preference.java
@@ -0,0 +1,1577 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import com.android.internal.util.CharSequences;
+import android.view.AbsSavedState;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * The {@link Preference} class represents the basic preference UI building
+ * block that is displayed by a {@link PreferenceActivity} in the form of a
+ * {@link ListView}. This class provides the {@link View} to be displayed in
+ * the activity and associates with a {@link SharedPreferences} to
+ * store/retrieve the preference data.
+ * <p>
+ * When specifying a preference hierarchy in XML, each tag name can point to a
+ * subclass of {@link Preference}, similar to the view hierarchy and layouts.
+ * <p>
+ * This class contains a {@code key} that will be used as the key into the
+ * {@link SharedPreferences}. It is up to the subclass to decide how to store
+ * the value.
+ *
+ * @attr ref android.R.styleable#Preference_key
+ * @attr ref android.R.styleable#Preference_title
+ * @attr ref android.R.styleable#Preference_summary
+ * @attr ref android.R.styleable#Preference_order
+ * @attr ref android.R.styleable#Preference_layout
+ * @attr ref android.R.styleable#Preference_widgetLayout
+ * @attr ref android.R.styleable#Preference_enabled
+ * @attr ref android.R.styleable#Preference_selectable
+ * @attr ref android.R.styleable#Preference_dependency
+ * @attr ref android.R.styleable#Preference_persistent
+ * @attr ref android.R.styleable#Preference_defaultValue
+ * @attr ref android.R.styleable#Preference_shouldDisableView
+ */
+public class Preference implements Comparable<Preference>, OnDependencyChangeListener {
+ /**
+ * Specify for {@link #setOrder(int)} if a specific order is not required.
+ */
+ public static final int DEFAULT_ORDER = Integer.MAX_VALUE;
+
+ private Context mContext;
+ private PreferenceManager mPreferenceManager;
+
+ /**
+ * Set when added to hierarchy since we need a unique ID within that
+ * hierarchy.
+ */
+ private long mId;
+
+ private OnPreferenceChangeListener mOnChangeListener;
+ private OnPreferenceClickListener mOnClickListener;
+
+ private int mOrder = DEFAULT_ORDER;
+ private CharSequence mTitle;
+ private CharSequence mSummary;
+ private String mKey;
+ private Intent mIntent;
+ private boolean mEnabled = true;
+ private boolean mSelectable = true;
+ private boolean mRequiresKey;
+ private boolean mPersistent = true;
+ private String mDependencyKey;
+ private Object mDefaultValue;
+
+ /**
+ * @see #setShouldDisableView(boolean)
+ */
+ private boolean mShouldDisableView = true;
+
+ private int mLayoutResId = com.android.internal.R.layout.preference;
+ private int mWidgetLayoutResId;
+ private boolean mHasSpecifiedLayout = false;
+
+ private OnPreferenceChangeInternalListener mListener;
+
+ private List<Preference> mDependents;
+
+ private boolean mBaseMethodCalled;
+
+ /**
+ * Interface definition for a callback to be invoked when this
+ * {@link Preference Preference's} value has been changed by the user and is
+ * about to be set and/or persisted. This gives the client a chance
+ * to prevent setting and/or persisting the value.
+ */
+ public interface OnPreferenceChangeListener {
+ /**
+ * Called when this preference has been changed by the user. This is
+ * called before the preference's state is about to be updated and
+ * before the state is persisted.
+ *
+ * @param preference This preference.
+ * @param newValue The new value of the preference.
+ * @return Whether or not to update this preference's state with the new value.
+ */
+ boolean onPreferenceChange(Preference preference, Object newValue);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a preference is
+ * clicked.
+ */
+ public interface OnPreferenceClickListener {
+ /**
+ * Called when a preference has been clicked.
+ *
+ * @param preference The preference that was clicked.
+ * @return Whether the click was handled.
+ */
+ boolean onPreferenceClick(Preference preference);
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when this
+ * {@link Preference} is changed or if this is a group, there is an
+ * addition/removal of {@link Preference}(s). This is used internally.
+ */
+ interface OnPreferenceChangeInternalListener {
+ /**
+ * Called when this preference has changed.
+ *
+ * @param preference This preference.
+ */
+ void onPreferenceChange(Preference preference);
+
+ /**
+ * Called when this group has added/removed {@link Preference}(s).
+ *
+ * @param preference This preference.
+ */
+ void onPreferenceHierarchyChange(Preference preference);
+ }
+
+ /**
+ * Perform inflation from XML and apply a class-specific base style. This
+ * constructor of {@link Preference} allows subclasses to use their own base
+ * style when they are inflating. For example, a {@link CheckBoxPreference}'s
+ * constructor would call this version of the super class constructor and
+ * supply {@code android.R.attr.checkBoxPreferenceStyle} for <var>defStyle</var>;
+ * this allows the theme's checkbox preference style to modify all of the base
+ * preference attributes as well as the {@link CheckBoxPreference} class's
+ * attributes.
+ *
+ * @param context The Context this is associated with, through which it can
+ * access the current theme, resources, {@link SharedPreferences},
+ * etc.
+ * @param attrs The attributes of the XML tag that is inflating the preference.
+ * @param defStyle The default style to apply to this preference. If 0, no style
+ * will be applied (beyond what is included in the theme). This
+ * may either be an attribute resource, whose value will be
+ * retrieved from the current theme, or an explicit style
+ * resource.
+ * @see #Preference(Context, AttributeSet)
+ */
+ public Preference(Context context, AttributeSet attrs, int defStyle) {
+ mContext = context;
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.Preference);
+ if (a.hasValue(com.android.internal.R.styleable.Preference_layout) ||
+ a.hasValue(com.android.internal.R.styleable.Preference_widgetLayout)) {
+ // This preference has a custom layout defined (not one taken from
+ // the default style)
+ mHasSpecifiedLayout = true;
+ }
+ a.recycle();
+
+ a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Preference,
+ defStyle, 0);
+ for (int i = a.getIndexCount(); i >= 0; i--) {
+ int attr = a.getIndex(i);
+ switch (attr) {
+ case com.android.internal.R.styleable.Preference_key:
+ mKey = a.getString(attr);
+ break;
+
+ case com.android.internal.R.styleable.Preference_title:
+ mTitle = a.getString(attr);
+ break;
+
+ case com.android.internal.R.styleable.Preference_summary:
+ mSummary = a.getString(attr);
+ break;
+
+ case com.android.internal.R.styleable.Preference_order:
+ mOrder = a.getInt(attr, mOrder);
+ break;
+
+ case com.android.internal.R.styleable.Preference_layout:
+ mLayoutResId = a.getResourceId(attr, mLayoutResId);
+ break;
+
+ case com.android.internal.R.styleable.Preference_widgetLayout:
+ mWidgetLayoutResId = a.getResourceId(attr, mWidgetLayoutResId);
+ break;
+
+ case com.android.internal.R.styleable.Preference_enabled:
+ mEnabled = a.getBoolean(attr, true);
+ break;
+
+ case com.android.internal.R.styleable.Preference_selectable:
+ mSelectable = a.getBoolean(attr, true);
+ break;
+
+ case com.android.internal.R.styleable.Preference_persistent:
+ mPersistent = a.getBoolean(attr, mPersistent);
+ break;
+
+ case com.android.internal.R.styleable.Preference_dependency:
+ mDependencyKey = a.getString(attr);
+ break;
+
+ case com.android.internal.R.styleable.Preference_defaultValue:
+ mDefaultValue = onGetDefaultValue(a, attr);
+ break;
+
+ case com.android.internal.R.styleable.Preference_shouldDisableView:
+ mShouldDisableView = a.getBoolean(attr, mShouldDisableView);
+ break;
+ }
+ }
+ a.recycle();
+ }
+
+ /**
+ * Constructor that is called when inflating a preference from XML. This is
+ * called when a preference is being constructed from an XML file, supplying
+ * attributes that were specified in the XML file. This version uses a
+ * default style of 0, so the only attribute values applied are those in the
+ * Context's Theme and the given AttributeSet.
+ *
+ * @param context The Context this is associated with, through which it can
+ * access the current theme, resources, {@link SharedPreferences},
+ * etc.
+ * @param attrs The attributes of the XML tag that is inflating the
+ * preference.
+ * @see #Preference(Context, AttributeSet, int)
+ */
+ public Preference(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Constructor to create a Preference.
+ *
+ * @param context The context to store preference values.
+ */
+ public Preference(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Called when {@link Preference} is being inflated and the default value
+ * attribute needs to be read. Since different preference types have
+ * different value types, the subclass should get and return the default
+ * value which will be its value type.
+ * <p>
+ * For example, if the value type is String, the body of the method would
+ * proxy to {@link TypedArray#getString(int)}.
+ *
+ * @param a The set of attributes.
+ * @param index The index of the default value attribute.
+ * @return The default value of this preference type.
+ */
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return null;
+ }
+
+ /**
+ * Sets an {@link Intent} to be used for
+ * {@link Context#startActivity(Intent)} when the preference is clicked.
+ *
+ * @param intent The intent associated with the preference.
+ */
+ public void setIntent(Intent intent) {
+ mIntent = intent;
+ }
+
+ /**
+ * Return the {@link Intent} associated with this preference.
+ *
+ * @return The {@link Intent} last set via {@link #setIntent(Intent)} or XML.
+ */
+ public Intent getIntent() {
+ return mIntent;
+ }
+
+ /**
+ * Sets the layout resource that is inflated as the {@link View} to be shown
+ * for this preference. In most cases, the default layout is sufficient for
+ * custom preferences and only the widget layout needs to be changed.
+ * <p>
+ * This layout should contain a {@link ViewGroup} with ID
+ * {@link android.R.id#widget_frame} to be the parent of the specific widget
+ * for this preference. It should similarly contain
+ * {@link android.R.id#title} and {@link android.R.id#summary}.
+ *
+ * @param layoutResId The layout resource ID to be inflated and returned as
+ * a {@link View}.
+ * @see #setWidgetLayoutResource(int)
+ */
+ public void setLayoutResource(int layoutResId) {
+
+ if (!mHasSpecifiedLayout) {
+ mHasSpecifiedLayout = true;
+ }
+
+ mLayoutResId = layoutResId;
+ }
+
+ /**
+ * Gets the layout resource that will be shown as the {@link View} for this preference.
+ *
+ * @return The layout resource ID.
+ */
+ public int getLayoutResource() {
+ return mLayoutResId;
+ }
+
+ /**
+ * Sets The layout for the controllable widget portion of a preference. This
+ * is inflated into the main layout. For example, a checkbox preference
+ * would specify a custom layout (consisting of just the CheckBox) here,
+ * instead of creating its own main layout.
+ *
+ * @param widgetLayoutResId The layout resource ID to be inflated into the
+ * main layout.
+ * @see #setLayoutResource(int)
+ */
+ public void setWidgetLayoutResource(int widgetLayoutResId) {
+ mWidgetLayoutResId = widgetLayoutResId;
+ }
+
+ /**
+ * Gets the layout resource for the controllable widget portion of a preference.
+ *
+ * @return The layout resource ID.
+ */
+ public int getWidgetLayoutResource() {
+ return mWidgetLayoutResId;
+ }
+
+ /**
+ * Gets the View that will be shown in the {@link PreferenceActivity}.
+ *
+ * @param convertView The old view to reuse, if possible. Note: You should
+ * check that this view is non-null and of an appropriate type
+ * before using. If it is not possible to convert this view to
+ * display the correct data, this method can create a new view.
+ * @param parent The parent that this view will eventually be attached to.
+ * @return Returns the same Preference object, for chaining multiple calls
+ * into a single statement.
+ * @see #onCreateView(ViewGroup)
+ * @see #onBindView(View)
+ */
+ public View getView(View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = onCreateView(parent);
+ }
+ onBindView(convertView);
+ return convertView;
+ }
+
+ /**
+ * Creates the View to be shown for this preference in the
+ * {@link PreferenceActivity}. The default behavior is to inflate the main
+ * layout of this preference (see {@link #setLayoutResource(int)}. If
+ * changing this behavior, please specify a {@link ViewGroup} with ID
+ * {@link android.R.id#widget_frame}.
+ * <p>
+ * Make sure to call through to the superclass's implementation.
+ *
+ * @param parent The parent that this view will eventually be attached to.
+ * @return The View that displays this preference.
+ * @see #onBindView(View)
+ */
+ protected View onCreateView(ViewGroup parent) {
+ final LayoutInflater layoutInflater =
+ (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ final View layout = layoutInflater.inflate(mLayoutResId, parent, false);
+
+ if (mWidgetLayoutResId != 0) {
+ final ViewGroup widgetFrame = (ViewGroup)layout.findViewById(com.android.internal.R.id.widget_frame);
+ layoutInflater.inflate(mWidgetLayoutResId, widgetFrame);
+ }
+
+ return layout;
+ }
+
+ /**
+ * Binds the created View to the data for the preference.
+ * <p>
+ * This is a good place to grab references to custom Views in the layout and
+ * set properties on them.
+ * <p>
+ * Make sure to call through to the superclass's implementation.
+ *
+ * @param view The View that shows this preference.
+ * @see #onCreateView(ViewGroup)
+ */
+ protected void onBindView(View view) {
+ TextView textView = (TextView) view.findViewById(com.android.internal.R.id.title);
+ if (textView != null) {
+ textView.setText(getTitle());
+ }
+
+ textView = (TextView) view.findViewById(com.android.internal.R.id.summary);
+ if (textView != null) {
+ final CharSequence summary = getSummary();
+ if (!TextUtils.isEmpty(summary)) {
+ if (textView.getVisibility() != View.VISIBLE) {
+ textView.setVisibility(View.VISIBLE);
+ }
+
+ textView.setText(getSummary());
+ } else {
+ if (textView.getVisibility() != View.GONE) {
+ textView.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ if (mShouldDisableView) {
+ setEnabledStateOnViews(view, mEnabled);
+ }
+ }
+
+ /**
+ * Makes sure the view (and any children) get the enabled state changed.
+ */
+ private void setEnabledStateOnViews(View v, boolean enabled) {
+ v.setEnabled(enabled);
+
+ if (v instanceof ViewGroup) {
+ final ViewGroup vg = (ViewGroup) v;
+ for (int i = vg.getChildCount() - 1; i >= 0; i--) {
+ setEnabledStateOnViews(vg.getChildAt(i), enabled);
+ }
+ }
+ }
+
+ /**
+ * Sets the order of this {@link Preference} with respect to other
+ * {@link Preference} on the same level. If this is not specified, the
+ * default behavior is to sort alphabetically. The
+ * {@link PreferenceGroup#setOrderingAsAdded(boolean)} can be used to order
+ * preferences based on the order they appear in the XML.
+ *
+ * @param order The order for this preference. A lower value will be shown
+ * first. Use {@link #DEFAULT_ORDER} to sort alphabetically or
+ * allow ordering from XML.
+ * @see PreferenceGroup#setOrderingAsAdded(boolean)
+ * @see #DEFAULT_ORDER
+ */
+ public void setOrder(int order) {
+ if (order != mOrder) {
+ mOrder = order;
+
+ // Reorder the list
+ notifyHierarchyChanged();
+ }
+ }
+
+ /**
+ * Gets the order of this {@link Preference}.
+ *
+ * @return The order of this {@link Preference}.
+ * @see #setOrder(int)
+ */
+ public int getOrder() {
+ return mOrder;
+ }
+
+ /**
+ * Sets the title for the preference. This title will be placed into the ID
+ * {@link android.R.id#title} within the View created by
+ * {@link #onCreateView(ViewGroup)}.
+ *
+ * @param title The title of the preference.
+ */
+ public void setTitle(CharSequence title) {
+ if (title == null && mTitle != null || title != null && !title.equals(mTitle)) {
+ mTitle = title;
+ notifyChanged();
+ }
+ }
+
+ /**
+ * @see #setTitle(CharSequence)
+ * @param titleResId The title as a resource ID.
+ */
+ public void setTitle(int titleResId) {
+ setTitle(mContext.getString(titleResId));
+ }
+
+ /**
+ * Returns the title of the preference.
+ *
+ * @return The title.
+ * @see #setTitle(CharSequence)
+ */
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns the summary of the preference.
+ *
+ * @return The summary.
+ * @see #setSummary(CharSequence)
+ */
+ public CharSequence getSummary() {
+ return mSummary;
+ }
+
+ /**
+ * Sets the summary for the preference. This summary will be placed into the
+ * ID {@link android.R.id#summary} within the View created by
+ * {@link #onCreateView(ViewGroup)}.
+ *
+ * @param summary The summary of the preference.
+ */
+ public void setSummary(CharSequence summary) {
+ if (summary == null && mSummary != null || summary != null && !summary.equals(mSummary)) {
+ mSummary = summary;
+ notifyChanged();
+ }
+ }
+
+ /**
+ * @see #setSummary(CharSequence)
+ * @param summaryResId The summary as a resource.
+ */
+ public void setSummary(int summaryResId) {
+ setSummary(mContext.getString(summaryResId));
+ }
+
+ /**
+ * Sets whether this preference is enabled. If disabled, the preference will
+ * not handle clicks.
+ *
+ * @param enabled Whether the preference is enabled.
+ */
+ public void setEnabled(boolean enabled) {
+ if (mEnabled != enabled) {
+ mEnabled = enabled;
+
+ // Enabled state can change dependent preferences' states, so notify
+ notifyDependencyChange(shouldDisableDependents());
+
+ notifyChanged();
+ }
+ }
+
+ /**
+ * Whether this {@link Preference} should be enabled in the list.
+ *
+ * @return Whether this preference is enabled.
+ */
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ /**
+ * Sets whether this preference is selectable.
+ *
+ * @param selectable Whether the preference is selectable.
+ */
+ public void setSelectable(boolean selectable) {
+ if (mSelectable != selectable) {
+ mSelectable = selectable;
+ notifyChanged();
+ }
+ }
+
+ /**
+ * Whether this {@link Preference} should be selectable in the list.
+ *
+ * @return Whether this preference is selectable.
+ */
+ public boolean isSelectable() {
+ return mSelectable;
+ }
+
+ /**
+ * Sets whether this {@link Preference} should disable its view when it gets
+ * disabled.
+ * <p>
+ * For example, set this and {@link #setEnabled(boolean)} to false for
+ * preferences that are only displaying information and 1) should not be
+ * clickable 2) should not have the view set to the disabled state.
+ *
+ * @param shouldDisableView Whether this preference should disable its view
+ * when the preference is disabled.
+ */
+ public void setShouldDisableView(boolean shouldDisableView) {
+ mShouldDisableView = shouldDisableView;
+ notifyChanged();
+ }
+
+ /**
+ * @see #setShouldDisableView(boolean)
+ * @return Whether this preference should disable its view when it is disabled.
+ */
+ public boolean getShouldDisableView() {
+ return mShouldDisableView;
+ }
+
+ /**
+ * Returns a unique ID for this preference. This ID should be unique across all
+ * preferences in a hierarchy.
+ *
+ * @return A unique ID for this preference.
+ */
+ long getId() {
+ return mId;
+ }
+
+ /**
+ * Processes a click on the preference. This includes saving the value to
+ * the {@link SharedPreferences}. However, the overridden method should
+ * call {@link #callChangeListener(Object)} to make sure the client wants to
+ * update the preference's state with the new value.
+ */
+ protected void onClick() {
+ }
+
+ /**
+ * Sets the key for the preference which is used as a key to the
+ * {@link SharedPreferences}. This should be unique for the package.
+ *
+ * @param key The key for the preference.
+ * @see #getId()
+ */
+ public void setKey(String key) {
+ mKey = key;
+
+ if (mRequiresKey && !hasKey()) {
+ requireKey();
+ }
+ }
+
+ /**
+ * Gets the key for the preference, which is also the key used for storing
+ * values into SharedPreferences.
+ *
+ * @return The key.
+ */
+ public String getKey() {
+ return mKey;
+ }
+
+ /**
+ * Checks whether the key is present, and if it isn't throws an
+ * exception. This should be called by subclasses that store preferences in
+ * the {@link SharedPreferences}.
+ */
+ void requireKey() {
+ if (mKey == null) {
+ throw new IllegalStateException("Preference does not have a key assigned.");
+ }
+
+ mRequiresKey = true;
+ }
+
+ /**
+ * Returns whether this {@link Preference} has a valid key.
+ *
+ * @return Whether the key exists and is not a blank string.
+ */
+ public boolean hasKey() {
+ return !TextUtils.isEmpty(mKey);
+ }
+
+ /**
+ * Returns whether this {@link Preference} is persistent. If it is persistent, it stores its value(s) into
+ * the persistent {@link SharedPreferences} storage.
+ *
+ * @return Whether this is persistent.
+ */
+ public boolean isPersistent() {
+ return mPersistent;
+ }
+
+ /**
+ * Convenience method of whether at the given time this method is called,
+ * the {@link Preference} should store/restore its value(s) into the
+ * {@link SharedPreferences}. This, at minimum, checks whether the
+ * {@link Preference} is persistent and it currently has a key. Before you
+ * save/restore from the {@link SharedPreferences}, check this first.
+ *
+ * @return Whether to persist the value.
+ */
+ protected boolean shouldPersist() {
+ return mPreferenceManager != null && isPersistent() && hasKey();
+ }
+
+ /**
+ * Sets whether this {@link Preference} is persistent. If it is persistent,
+ * it stores its value(s) into the persistent {@link SharedPreferences}
+ * storage.
+ *
+ * @param persistent Whether it should store its value(s) into the {@link SharedPreferences}.
+ */
+ public void setPersistent(boolean persistent) {
+ mPersistent = persistent;
+ }
+
+ /**
+ * Call this method after the user changes the preference, but before the
+ * internal state is set. This allows the client to ignore the user value.
+ *
+ * @param newValue The new value of the preference.
+ * @return Whether or not the user value should be set as the preference
+ * value (and persisted).
+ */
+ protected boolean callChangeListener(Object newValue) {
+ return mOnChangeListener == null ? true : mOnChangeListener.onPreferenceChange(this, newValue);
+ }
+
+ /**
+ * Sets the callback to be invoked when this preference is changed by the
+ * user (but before the internal state has been updated).
+ *
+ * @param onPreferenceChangeListener The callback to be invoked.
+ */
+ public void setOnPreferenceChangeListener(OnPreferenceChangeListener onPreferenceChangeListener) {
+ mOnChangeListener = onPreferenceChangeListener;
+ }
+
+ /**
+ * Gets the callback to be invoked when this preference is changed by the
+ * user (but before the internal state has been updated).
+ *
+ * @return The callback to be invoked.
+ */
+ public OnPreferenceChangeListener getOnPreferenceChangeListener() {
+ return mOnChangeListener;
+ }
+
+ /**
+ * Sets the callback to be invoked when this preference is clicked.
+ *
+ * @param onPreferenceClickListener The callback to be invoked.
+ */
+ public void setOnPreferenceClickListener(OnPreferenceClickListener onPreferenceClickListener) {
+ mOnClickListener = onPreferenceClickListener;
+ }
+
+ /**
+ * Gets the callback to be invoked when this preference is clicked.
+ *
+ * @return The callback to be invoked.
+ */
+ public OnPreferenceClickListener getOnPreferenceClickListener() {
+ return mOnClickListener;
+ }
+
+ /**
+ * Called when a click should be performed.
+ *
+ * @param preferenceScreen Optional {@link PreferenceScreen} whose hierarchy click
+ * listener should be called in the proper order (between other
+ * processing).
+ */
+ void performClick(PreferenceScreen preferenceScreen) {
+
+ if (!isEnabled()) {
+ return;
+ }
+
+ onClick();
+
+ if (mOnClickListener != null && mOnClickListener.onPreferenceClick(this)) {
+ return;
+ }
+
+ PreferenceManager preferenceManager = getPreferenceManager();
+ if (preferenceManager != null) {
+ PreferenceManager.OnPreferenceTreeClickListener listener = preferenceManager
+ .getOnPreferenceTreeClickListener();
+ if (preferenceScreen != null && listener != null
+ && listener.onPreferenceTreeClick(preferenceScreen, this)) {
+ return;
+ }
+ }
+
+ if (mIntent != null) {
+ Context context = getContext();
+ context.startActivity(mIntent);
+ }
+ }
+
+ /**
+ * Returns the context of this preference. Each preference in a preference hierarchy can be
+ * from different context (for example, if multiple activities provide preferences into a single
+ * {@link PreferenceActivity}). This context will be used to save the preference valus.
+ *
+ * @return The context of this preference.
+ */
+ public Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Returns the {@link SharedPreferences} where this preference can read its
+ * value(s). Usually, it's easier to use one of the helper read methods:
+ * {@link #getPersistedBoolean(boolean)}, {@link #getPersistedFloat(float)},
+ * {@link #getPersistedInt(int)}, {@link #getPersistedLong(long)},
+ * {@link #getPersistedString(String)}. To save values, see
+ * {@link #getEditor()}.
+ * <p>
+ * In some cases, writes to the {@link #getEditor()} will not be committed
+ * right away and hence not show up in the returned
+ * {@link SharedPreferences}, this is intended behavior to improve
+ * performance.
+ *
+ * @return The {@link SharedPreferences} where this preference reads its
+ * value(s), or null if it isn't attached to a preference hierarchy.
+ * @see #getEditor()
+ */
+ public SharedPreferences getSharedPreferences() {
+ if (mPreferenceManager == null) {
+ return null;
+ }
+
+ return mPreferenceManager.getSharedPreferences();
+ }
+
+ /**
+ * Returns an {@link SharedPreferences.Editor} where this preference can
+ * save its value(s). Usually it's easier to use one of the helper save
+ * methods: {@link #persistBoolean(boolean)}, {@link #persistFloat(float)},
+ * {@link #persistInt(int)}, {@link #persistLong(long)},
+ * {@link #persistString(String)}. To read values, see
+ * {@link #getSharedPreferences()}. If {@link #shouldCommit()} returns
+ * true, it is this Preference's responsibility to commit.
+ * <p>
+ * In some cases, writes to this will not be committed right away and hence
+ * not show up in the shared preferences, this is intended behavior to
+ * improve performance.
+ *
+ * @return A {@link SharedPreferences.Editor} where this preference saves
+ * its value(s), or null if it isn't attached to a preference
+ * hierarchy.
+ * @see #shouldCommit()
+ * @see #getSharedPreferences()
+ */
+ public SharedPreferences.Editor getEditor() {
+ if (mPreferenceManager == null) {
+ return null;
+ }
+
+ return mPreferenceManager.getEditor();
+ }
+
+ /**
+ * Returns whether the {@link Preference} should commit its saved value(s) in
+ * {@link #getEditor()}. This may return false in situations where batch
+ * committing is being done (by the manager) to improve performance.
+ *
+ * @return Whether the Preference should commit its saved value(s).
+ * @see #getEditor()
+ */
+ public boolean shouldCommit() {
+ if (mPreferenceManager == null) {
+ return false;
+ }
+
+ return mPreferenceManager.shouldCommit();
+ }
+
+ /**
+ * Compares preferences based on order (if set), otherwise alphabetically on title.
+ * <p>
+ * {@inheritDoc}
+ */
+ public int compareTo(Preference another) {
+ if (mOrder != DEFAULT_ORDER
+ || (mOrder == DEFAULT_ORDER && another.mOrder != DEFAULT_ORDER)) {
+ // Do order comparison
+ return mOrder - another.mOrder;
+ } else if (mTitle == null) {
+ return 1;
+ } else if (another.mTitle == null) {
+ return -1;
+ } else {
+ // Do name comparison
+ return CharSequences.compareToIgnoreCase(mTitle, another.mTitle);
+ }
+ }
+
+ /**
+ * Sets the internal change listener.
+ *
+ * @param listener The listener.
+ * @see #notifyChanged()
+ */
+ final void setOnPreferenceChangeInternalListener(OnPreferenceChangeInternalListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * Should be called when the data of this {@link Preference} has changed.
+ */
+ protected void notifyChanged() {
+ if (mListener != null) {
+ mListener.onPreferenceChange(this);
+ }
+ }
+
+ /**
+ * Should be called this is a group a {@link Preference} has been
+ * added/removed from this group, or the ordering should be
+ * re-evaluated.
+ */
+ protected void notifyHierarchyChanged() {
+ if (mListener != null) {
+ mListener.onPreferenceHierarchyChange(this);
+ }
+ }
+
+ /**
+ * Gets the {@link PreferenceManager} that manages this preference's tree.
+ *
+ * @return The {@link PreferenceManager}.
+ */
+ public PreferenceManager getPreferenceManager() {
+ return mPreferenceManager;
+ }
+
+ /**
+ * Called when this preference has been attached to a preference hierarchy.
+ * Make sure to call the super implementation.
+ *
+ * @param preferenceManager The preference manager of the hierarchy.
+ */
+ protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
+ mPreferenceManager = preferenceManager;
+
+ mId = preferenceManager.getNextId();
+
+ dispatchSetInitialValue();
+ }
+
+ /**
+ * Called when the preference hierarchy has been attached to the
+ * {@link PreferenceActivity}. This can also be called when this
+ * {@link Preference} has been attached to a group that was already attached
+ * to the {@link PreferenceActivity}.
+ */
+ protected void onAttachedToActivity() {
+ // At this point, the hierarchy that this preference is in is connected
+ // with all other preferences.
+ registerDependency();
+ }
+
+ private void registerDependency() {
+
+ if (TextUtils.isEmpty(mDependencyKey)) return;
+
+ Preference preference = findPreferenceInHierarchy(mDependencyKey);
+ if (preference != null) {
+ preference.registerDependent(this);
+ } else {
+ throw new IllegalStateException("Dependency \"" + mDependencyKey
+ + "\" not found for preference \"" + mKey + "\" (title: \"" + mTitle + "\"");
+ }
+ }
+
+ private void unregisterDependency() {
+ if (mDependencyKey != null) {
+ final Preference oldDependency = findPreferenceInHierarchy(mDependencyKey);
+ if (oldDependency != null) {
+ oldDependency.unregisterDependent(this);
+ }
+ }
+ }
+
+ /**
+ * Find a Preference in this hierarchy (the whole thing,
+ * even above/below your {@link PreferenceScreen} screen break) with the given
+ * key.
+ * <p>
+ * This only functions after we have been attached to a hierarchy.
+ *
+ * @param key The key of the {@link Preference} to find.
+ * @return The {@link Preference} object of a preference
+ * with the given key.
+ */
+ protected Preference findPreferenceInHierarchy(String key) {
+ if (TextUtils.isEmpty(key) || mPreferenceManager == null) {
+ return null;
+ }
+
+ return mPreferenceManager.findPreference(key);
+ }
+
+ /**
+ * Adds a dependent Preference on this preference so we can notify it.
+ * Usually, the dependent preference registers itself (it's good for it to
+ * know it depends on something), so please use
+ * {@link Preference#setDependency(String)} on the dependent preference.
+ *
+ * @param dependent The dependent Preference that will be enabled/disabled
+ * according to the state of this preference.
+ */
+ private void registerDependent(Preference dependent) {
+ if (mDependents == null) {
+ mDependents = new ArrayList<Preference>();
+ }
+
+ mDependents.add(dependent);
+
+ dependent.onDependencyChanged(this, shouldDisableDependents());
+ }
+
+ /**
+ * Removes a dependent Preference on this preference.
+ *
+ * @param dependent The dependent Preference that will be enabled/disabled
+ * according to the state of this preference.
+ * @return Returns the same Preference object, for chaining multiple calls
+ * into a single statement.
+ */
+ private void unregisterDependent(Preference dependent) {
+ if (mDependents != null) {
+ mDependents.remove(dependent);
+ }
+ }
+
+ /**
+ * Notifies any listening dependents of a change that affects the
+ * dependency.
+ *
+ * @param disableDependents Whether this {@link Preference} should disable
+ * its dependents.
+ */
+ public void notifyDependencyChange(boolean disableDependents) {
+ final List<Preference> dependents = mDependents;
+
+ if (dependents == null) {
+ return;
+ }
+
+ final int dependentsCount = dependents.size();
+ for (int i = 0; i < dependentsCount; i++) {
+ dependents.get(i).onDependencyChanged(this, disableDependents);
+ }
+ }
+
+ /**
+ * Called when the dependency changes.
+ *
+ * @param dependency The preference that this preference depends on.
+ * @param disableDependent Whether to disable this preference.
+ */
+ public void onDependencyChanged(Preference dependency, boolean disableDependent) {
+ setEnabled(!disableDependent);
+ }
+
+ /**
+ * Should return whether this preference's dependents should currently be
+ * disabled.
+ *
+ * @return True if the dependents should be disabled, otherwise false.
+ */
+ public boolean shouldDisableDependents() {
+ return !isEnabled();
+ }
+
+ /**
+ * Sets the key of a Preference that this Preference will depend on. If that
+ * Preference is not set or is off, this Preference will be disabled.
+ *
+ * @param dependencyKey The key of the Preference that this depends on.
+ */
+ public void setDependency(String dependencyKey) {
+ // Unregister the old dependency, if we had one
+ unregisterDependency();
+
+ // Register the new
+ mDependencyKey = dependencyKey;
+ registerDependency();
+ }
+
+ /**
+ * Returns the key of the dependency on this preference.
+ *
+ * @return The key of the dependency.
+ * @see #setDependency(String)
+ */
+ public String getDependency() {
+ return mDependencyKey;
+ }
+
+ /**
+ * Called when this Preference is being removed from the hierarchy. You
+ * should remove any references to this Preference that you know about. Make
+ * sure to call through to the superclass implementation.
+ */
+ protected void onPrepareForRemoval() {
+ unregisterDependency();
+ }
+
+ /**
+ * Sets the default value for the preference, which will be set either if
+ * persistence is off or persistence is on and the preference is not found
+ * in the persistent storage.
+ *
+ * @param defaultValue The default value.
+ */
+ public void setDefaultValue(Object defaultValue) {
+ mDefaultValue = defaultValue;
+ }
+
+ private void dispatchSetInitialValue() {
+ // By now, we know if we are persistent.
+ final boolean shouldPersist = shouldPersist();
+ if (!shouldPersist || !getSharedPreferences().contains(mKey)) {
+ if (mDefaultValue != null) {
+ onSetInitialValue(false, mDefaultValue);
+ }
+ } else {
+ onSetInitialValue(true, null);
+ }
+ }
+
+ /**
+ * Implement this to set the initial value of the Preference. If the
+ * restoreValue flag is true, you should restore the value from the shared
+ * preferences. If false, you should set (and possibly store to shared
+ * preferences if {@link #shouldPersist()}) to defaultValue.
+ * <p>
+ * This may not always be called. One example is if it should not persist
+ * but there is no default value given.
+ *
+ * @param restorePersistedValue Whether to restore the persisted value
+ * (true), or use the given default value (false).
+ * @param defaultValue The default value. Only use if restoreValue is false.
+ */
+ protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
+ }
+
+ private void tryCommit(SharedPreferences.Editor editor) {
+ if (mPreferenceManager.shouldCommit()) {
+ editor.commit();
+ }
+ }
+
+ /**
+ * Attempts to persist a String to the SharedPreferences.
+ * <p>
+ * This will check if the Preference is persistent, get an editor from
+ * the preference manager, put the string, check if we should commit (and
+ * commit if so).
+ *
+ * @param value The value to persist.
+ * @return Whether the Preference is persistent. (This is not whether the
+ * value was persisted, since we may not necessarily commit if there
+ * will be a batch commit later.)
+ * @see #getPersistedString(String)
+ */
+ protected boolean persistString(String value) {
+ if (shouldPersist()) {
+ // Shouldn't store null
+ if (value == getPersistedString(null)) {
+ // It's already there, so the same as persisting
+ return true;
+ }
+
+ SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+ editor.putString(mKey, value);
+ tryCommit(editor);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to get a persisted String from the SharedPreferences.
+ * <p>
+ * This will check if the Preference is persistent, get the shared
+ * preferences from the preference manager, get the value.
+ *
+ * @param defaultReturnValue The default value to return if either the
+ * Preference is not persistent or the Preference is not in the
+ * shared preferences.
+ * @return The value from the shared preferences or the default return
+ * value.
+ * @see #persistString(String)
+ */
+ protected String getPersistedString(String defaultReturnValue) {
+ if (!shouldPersist()) {
+ return defaultReturnValue;
+ }
+
+ return mPreferenceManager.getSharedPreferences().getString(mKey, defaultReturnValue);
+ }
+
+ /**
+ * Attempts to persist an int to the SharedPreferences.
+ *
+ * @param value The value to persist.
+ * @return Whether the Preference is persistent. (This is not whether the
+ * value was persisted, since we may not necessarily commit if there
+ * will be a batch commit later.)
+ * @see #persistString(String)
+ * @see #getPersistedInt(int)
+ */
+ protected boolean persistInt(int value) {
+ if (shouldPersist()) {
+ if (value == getPersistedInt(~value)) {
+ // It's already there, so the same as persisting
+ return true;
+ }
+
+ SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+ editor.putInt(mKey, value);
+ tryCommit(editor);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to get a persisted int from the SharedPreferences.
+ *
+ * @param defaultReturnValue The default value to return if either the
+ * Preference is not persistent or the Preference is not in the
+ * shared preferences.
+ * @return The value from the shared preferences or the default return
+ * value.
+ * @see #getPersistedString(String)
+ * @see #persistInt(int)
+ */
+ protected int getPersistedInt(int defaultReturnValue) {
+ if (!shouldPersist()) {
+ return defaultReturnValue;
+ }
+
+ return mPreferenceManager.getSharedPreferences().getInt(mKey, defaultReturnValue);
+ }
+
+ /**
+ * Attempts to persist a float to the SharedPreferences.
+ *
+ * @param value The value to persist.
+ * @return Whether the Preference is persistent. (This is not whether the
+ * value was persisted, since we may not necessarily commit if there
+ * will be a batch commit later.)
+ * @see #persistString(String)
+ * @see #getPersistedFloat(float)
+ */
+ protected boolean persistFloat(float value) {
+ if (shouldPersist()) {
+ if (value == getPersistedFloat(Float.NaN)) {
+ // It's already there, so the same as persisting
+ return true;
+ }
+
+ SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+ editor.putFloat(mKey, value);
+ tryCommit(editor);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to get a persisted float from the SharedPreferences.
+ *
+ * @param defaultReturnValue The default value to return if either the
+ * Preference is not persistent or the Preference is not in the
+ * shared preferences.
+ * @return The value from the shared preferences or the default return
+ * value.
+ * @see #getPersistedString(String)
+ * @see #persistFloat(float)
+ */
+ protected float getPersistedFloat(float defaultReturnValue) {
+ if (!shouldPersist()) {
+ return defaultReturnValue;
+ }
+
+ return mPreferenceManager.getSharedPreferences().getFloat(mKey, defaultReturnValue);
+ }
+
+ /**
+ * Attempts to persist a long to the SharedPreferences.
+ *
+ * @param value The value to persist.
+ * @return Whether the Preference is persistent. (This is not whether the
+ * value was persisted, since we may not necessarily commit if there
+ * will be a batch commit later.)
+ * @see #persistString(String)
+ * @see #getPersistedLong(long)
+ */
+ protected boolean persistLong(long value) {
+ if (shouldPersist()) {
+ if (value == getPersistedLong(~value)) {
+ // It's already there, so the same as persisting
+ return true;
+ }
+
+ SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+ editor.putLong(mKey, value);
+ tryCommit(editor);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to get a persisted long from the SharedPreferences.
+ *
+ * @param defaultReturnValue The default value to return if either the
+ * Preference is not persistent or the Preference is not in the
+ * shared preferences.
+ * @return The value from the shared preferences or the default return
+ * value.
+ * @see #getPersistedString(String)
+ * @see #persistLong(long)
+ */
+ protected long getPersistedLong(long defaultReturnValue) {
+ if (!shouldPersist()) {
+ return defaultReturnValue;
+ }
+
+ return mPreferenceManager.getSharedPreferences().getLong(mKey, defaultReturnValue);
+ }
+
+ /**
+ * Attempts to persist a boolean to the SharedPreferences.
+ *
+ * @param value The value to persist.
+ * @return Whether the Preference is persistent. (This is not whether the
+ * value was persisted, since we may not necessarily commit if there
+ * will be a batch commit later.)
+ * @see #persistString(String)
+ * @see #getPersistedBoolean(boolean)
+ */
+ protected boolean persistBoolean(boolean value) {
+ if (shouldPersist()) {
+ if (value == getPersistedBoolean(!value)) {
+ // It's already there, so the same as persisting
+ return true;
+ }
+
+ SharedPreferences.Editor editor = mPreferenceManager.getEditor();
+ editor.putBoolean(mKey, value);
+ tryCommit(editor);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Attempts to get a persisted boolean from the SharedPreferences.
+ *
+ * @param defaultReturnValue The default value to return if either the
+ * Preference is not persistent or the Preference is not in the
+ * shared preferences.
+ * @return The value from the shared preferences or the default return
+ * value.
+ * @see #getPersistedString(String)
+ * @see #persistBoolean(boolean)
+ */
+ protected boolean getPersistedBoolean(boolean defaultReturnValue) {
+ if (!shouldPersist()) {
+ return defaultReturnValue;
+ }
+
+ return mPreferenceManager.getSharedPreferences().getBoolean(mKey, defaultReturnValue);
+ }
+
+ boolean hasSpecifiedLayout() {
+ return mHasSpecifiedLayout;
+ }
+
+ @Override
+ public String toString() {
+ return getFilterableStringBuilder().toString();
+ }
+
+ /**
+ * Returns the text that will be used to filter this preference depending on
+ * user input.
+ * <p>
+ * If overridding and calling through to the superclass, make sure to prepend
+ * your additions with a space.
+ *
+ * @return Text as a {@link StringBuilder} that will be used to filter this
+ * preference. By default, this is the title and summary
+ * (concatenated with a space).
+ */
+ StringBuilder getFilterableStringBuilder() {
+ StringBuilder sb = new StringBuilder();
+ CharSequence title = getTitle();
+ if (!TextUtils.isEmpty(title)) {
+ sb.append(title).append(' ');
+ }
+ CharSequence summary = getSummary();
+ if (!TextUtils.isEmpty(summary)) {
+ sb.append(summary).append(' ');
+ }
+ // Drop the last space
+ sb.setLength(sb.length() - 1);
+ return sb;
+ }
+
+ /**
+ * Store this preference hierarchy's frozen state into the given container.
+ *
+ * @param container The Bundle in which to save the preference's icicles.
+ *
+ * @see #restoreHierarchyState
+ * @see #dispatchSaveInstanceState
+ * @see #onSaveInstanceState
+ */
+ public void saveHierarchyState(Bundle container) {
+ dispatchSaveInstanceState(container);
+ }
+
+ /**
+ * Called by {@link #saveHierarchyState} to store the icicles for this preference and its children.
+ * May be overridden to modify how freezing happens to a preference's children; for example, some
+ * preferences may want to not store icicles for their children.
+ *
+ * @param container The Bundle in which to save the preference's icicles.
+ *
+ * @see #dispatchRestoreInstanceState
+ * @see #saveHierarchyState
+ * @see #onSaveInstanceState
+ */
+ void dispatchSaveInstanceState(Bundle container) {
+ if (hasKey()) {
+ mBaseMethodCalled = false;
+ Parcelable state = onSaveInstanceState();
+ if (!mBaseMethodCalled) {
+ throw new IllegalStateException(
+ "Derived class did not call super.onSaveInstanceState()");
+ }
+ if (state != null) {
+ container.putParcelable(mKey, state);
+ }
+ }
+ }
+
+ /**
+ * Hook allowing a preference to generate a representation of its internal
+ * state that can later be used to create a new instance with that same
+ * state. This state should only contain information that is not persistent
+ * or can be reconstructed later.
+ *
+ * @return Returns a Parcelable object containing the preference's current
+ * dynamic state, or null if there is nothing interesting to save.
+ * The default implementation returns null.
+ * @see #onRestoreInstanceState
+ * @see #saveHierarchyState
+ * @see #dispatchSaveInstanceState
+ */
+ protected Parcelable onSaveInstanceState() {
+ mBaseMethodCalled = true;
+ return BaseSavedState.EMPTY_STATE;
+ }
+
+ /**
+ * Restore this preference hierarchy's frozen state from the given container.
+ *
+ * @param container The Bundle which holds previously frozen icicles.
+ *
+ * @see #saveHierarchyState
+ * @see #dispatchRestoreInstanceState
+ * @see #onRestoreInstanceState
+ */
+ public void restoreHierarchyState(Bundle container) {
+ dispatchRestoreInstanceState(container);
+ }
+
+ /**
+ * Called by {@link #restoreHierarchyState} to retrieve the icicles for this
+ * preference and its children. May be overridden to modify how restoreing
+ * happens to a preference's children; for example, some preferences may
+ * want to not store icicles for their children.
+ *
+ * @param container The Bundle which holds previously frozen icicles.
+ * @see #dispatchSaveInstanceState
+ * @see #restoreHierarchyState
+ * @see #onRestoreInstanceState
+ */
+ void dispatchRestoreInstanceState(Bundle container) {
+ if (hasKey()) {
+ Parcelable state = container.getParcelable(mKey);
+ if (state != null) {
+ mBaseMethodCalled = false;
+ onRestoreInstanceState(state);
+ if (!mBaseMethodCalled) {
+ throw new IllegalStateException(
+ "Derived class did not call super.onRestoreInstanceState()");
+ }
+ }
+ }
+ }
+
+ /**
+ * Hook allowing a preference to re-apply a representation of its internal
+ * state that had previously been generated by {@link #onSaveInstanceState}.
+ * This function will never be called with a null icicle.
+ *
+ * @param state The frozen state that had previously been returned by
+ * {@link #onSaveInstanceState}.
+ * @see #onSaveInstanceState
+ * @see #restoreHierarchyState
+ * @see #dispatchRestoreInstanceState
+ */
+ protected void onRestoreInstanceState(Parcelable state) {
+ mBaseMethodCalled = true;
+ if (state != BaseSavedState.EMPTY_STATE && state != null) {
+ throw new IllegalArgumentException("Wrong state class -- expecting Preference State");
+ }
+ }
+
+ public static class BaseSavedState extends AbsSavedState {
+ public BaseSavedState(Parcel source) {
+ super(source);
+ }
+
+ public BaseSavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public static final Parcelable.Creator<BaseSavedState> CREATOR =
+ new Parcelable.Creator<BaseSavedState>() {
+ public BaseSavedState createFromParcel(Parcel in) {
+ return new BaseSavedState(in);
+ }
+
+ public BaseSavedState[] newArray(int size) {
+ return new BaseSavedState[size];
+ }
+ };
+ }
+
+}
diff --git a/core/java/android/preference/PreferenceActivity.java b/core/java/android/preference/PreferenceActivity.java
new file mode 100644
index 0000000..98144ca
--- /dev/null
+++ b/core/java/android/preference/PreferenceActivity.java
@@ -0,0 +1,284 @@
+/*
+ * 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.app.Activity;
+import android.app.ListActivity;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.view.View;
+import android.view.Window;
+
+/**
+ * The {@link PreferenceActivity} activity shows a hierarchy of preferences as
+ * lists, possibly spanning multiple screens. These preferences will
+ * automatically save to {@link SharedPreferences} as the user interacts with
+ * them. To retrieve an instance of {@link SharedPreferences} that the
+ * preference hierarchy in this activity will use, call
+ * {@link PreferenceManager#getDefaultSharedPreferences(android.content.Context)}
+ * with a context in the same package as this activity.
+ * <p>
+ * Furthermore, the preferences shown will follow the visual style of system
+ * preferences. It is easy to create a hierarchy of preferences (that can be
+ * shown on multiple screens) via XML. For these reasons, it is recommended to
+ * use this activity (as a superclass) to deal with preferences in applications.
+ * <p>
+ * A {@link PreferenceScreen} object should be at the top of the preference
+ * hierarchy. Furthermore, subsequent {@link PreferenceScreen} in the hierarchy
+ * denote a screen break--that is the preferences contained within subsequent
+ * {@link PreferenceScreen} should be shown on another screen. The preference
+ * framework handles showing these other screens from the preference hierarchy.
+ * <p>
+ * The preference hierarchy can be formed in multiple ways:
+ * <li> From an XML file specifying the hierarchy
+ * <li> From different {@link Activity Activities} that each specify its own
+ * preferences in an XML file via {@link Activity} meta-data
+ * <li> From an object hierarchy rooted with {@link PreferenceScreen}
+ * <p>
+ * To inflate from XML, use the {@link #addPreferencesFromResource(int)}. The
+ * root element should be a {@link PreferenceScreen}. Subsequent elements can point
+ * to actual {@link Preference} subclasses. As mentioned above, subsequent
+ * {@link PreferenceScreen} in the hierarchy will result in the screen break.
+ * <p>
+ * To specify an {@link Intent} to query {@link Activity Activities} that each
+ * have preferences, use {@link #addPreferencesFromIntent}. Each
+ * {@link Activity} can specify meta-data in the manifest (via the key
+ * {@link PreferenceManager#METADATA_KEY_PREFERENCES}) that points to an XML
+ * resource. These XML resources will be inflated into a single preference
+ * hierarchy and shown by this activity.
+ * <p>
+ * To specify an object hierarchy rooted with {@link PreferenceScreen}, use
+ * {@link #setPreferenceScreen(PreferenceScreen)}.
+ * <p>
+ * As a convenience, this activity implements a click listener for any
+ * preference in the current hierarchy, see
+ * {@link #onPreferenceTreeClick(PreferenceScreen, Preference)}.
+ *
+ * @see Preference
+ * @see PreferenceScreen
+ */
+public abstract class PreferenceActivity extends ListActivity implements
+ PreferenceManager.OnPreferenceTreeClickListener {
+
+ private static final String PREFERENCES_TAG = "android:preferences";
+
+ private PreferenceManager mPreferenceManager;
+
+ /**
+ * The starting request code given out to preference framework.
+ */
+ private static final int FIRST_REQUEST_CODE = 100;
+
+ private static final int MSG_BIND_PREFERENCES = 0;
+ private Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+
+ case MSG_BIND_PREFERENCES:
+ bindPreferences();
+ break;
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+ setContentView(com.android.internal.R.layout.preference_list_content);
+
+ mPreferenceManager = onCreatePreferenceManager();
+ getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ mPreferenceManager.dispatchActivityStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+
+ mPreferenceManager.dispatchActivityDestroy();
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ 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) {
+ super.onRestoreInstanceState(state);
+
+ Bundle container = state.getBundle(PREFERENCES_TAG);
+ if (container != null) {
+ final PreferenceScreen preferenceScreen = getPreferenceScreen();
+ if (preferenceScreen != null) {
+ preferenceScreen.restoreHierarchyState(container);
+ }
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ mPreferenceManager.dispatchActivityResult(requestCode, resultCode, data);
+ }
+
+ @Override
+ public void onContentChanged() {
+ super.onContentChanged();
+ postBindPreferences();
+ }
+
+ /**
+ * 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());
+ }
+ }
+
+ /**
+ * Creates the {@link PreferenceManager}.
+ *
+ * @return The {@link PreferenceManager} used by this activity.
+ */
+ private PreferenceManager onCreatePreferenceManager() {
+ PreferenceManager preferenceManager = new PreferenceManager(this, FIRST_REQUEST_CODE);
+ preferenceManager.setOnPreferenceTreeClickListener(this);
+ return preferenceManager;
+ }
+
+ /**
+ * Returns the {@link PreferenceManager} used by this activity.
+ * @return The {@link PreferenceManager}.
+ */
+ public PreferenceManager getPreferenceManager() {
+ return mPreferenceManager;
+ }
+
+ private void requirePreferenceManager() {
+ if (mPreferenceManager == null) {
+ throw new RuntimeException("This should be called after super.onCreate.");
+ }
+ }
+
+ /**
+ * Sets the root of the preference hierarchy that this activity is showing.
+ *
+ * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
+ */
+ public void setPreferenceScreen(PreferenceScreen preferenceScreen) {
+ if (mPreferenceManager.setPreferences(preferenceScreen) && preferenceScreen != null) {
+ postBindPreferences();
+ }
+ }
+
+ /**
+ * Gets the root of the preference hierarchy that this activity is showing.
+ *
+ * @return The {@link PreferenceScreen} that is the root of the preference
+ * hierarchy.
+ */
+ public PreferenceScreen getPreferenceScreen() {
+ return mPreferenceManager.getPreferenceScreen();
+ }
+
+ /**
+ * Adds preferences from activities that match the given {@link Intent}.
+ *
+ * @param intent The {@link Intent} to query activities.
+ */
+ 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.
+ *
+ * @param preferencesResId The XML resource ID to inflate.
+ */
+ public void addPreferencesFromResource(int preferencesResId) {
+ requirePreferenceManager();
+
+ setPreferenceScreen(mPreferenceManager.inflateFromResource(this, preferencesResId,
+ getPreferenceScreen()));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ return false;
+ }
+
+ /**
+ * 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)
+ */
+ 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);
+ }
+ }
+
+}
diff --git a/core/java/android/preference/PreferenceCategory.java b/core/java/android/preference/PreferenceCategory.java
new file mode 100644
index 0000000..a1b6f09
--- /dev/null
+++ b/core/java/android/preference/PreferenceCategory.java
@@ -0,0 +1,58 @@
+/*
+ * 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 java.util.Map;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+/**
+ * The {@link PreferenceCategory} class is used to group {@link Preference}s
+ * and provide a disabled title above the group.
+ */
+public class PreferenceCategory extends PreferenceGroup {
+ private static final String TAG = "PreferenceCategory";
+
+ public PreferenceCategory(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public PreferenceCategory(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.preferenceCategoryStyle);
+ }
+
+ public PreferenceCategory(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ protected boolean onPrepareAddPreference(Preference preference) {
+ if (preference instanceof PreferenceCategory) {
+ throw new IllegalArgumentException(
+ "Cannot add a " + TAG + " directly to a " + TAG);
+ }
+
+ return super.onPrepareAddPreference(preference);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return false;
+ }
+
+}
diff --git a/core/java/android/preference/PreferenceGroup.java b/core/java/android/preference/PreferenceGroup.java
new file mode 100644
index 0000000..55b3753
--- /dev/null
+++ b/core/java/android/preference/PreferenceGroup.java
@@ -0,0 +1,320 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+
+/**
+ * The {@link PreferenceGroup} class is a container for multiple
+ * {@link Preference}s. It is a base class for {@link Preference} that are
+ * parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}.
+ *
+ * @attr ref android.R.styleable#PreferenceGroup_orderingFromXml
+ */
+public abstract class PreferenceGroup extends Preference implements GenericInflater.Parent<Preference> {
+ /**
+ * The container for child {@link Preference}s. This is sorted based on the
+ * ordering, please use {@link #addPreference(Preference)} instead of adding
+ * to this directly.
+ */
+ private List<Preference> mPreferenceList;
+
+ private boolean mOrderingAsAdded = true;
+
+ private int mCurrentPreferenceOrder = 0;
+
+ private boolean mAttachedToActivity = false;
+
+ public PreferenceGroup(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ mPreferenceList = new ArrayList<Preference>();
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.PreferenceGroup, defStyle, 0);
+ mOrderingAsAdded = a.getBoolean(com.android.internal.R.styleable.PreferenceGroup_orderingFromXml,
+ mOrderingAsAdded);
+ a.recycle();
+ }
+
+ public PreferenceGroup(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ /**
+ * Whether to order the {@link Preference} children of this group as they
+ * are added. If this is false, the ordering will follow each Preference
+ * order and default to alphabetic for those without an order.
+ * <p>
+ * If this is called after preferences are added, they will not be
+ * re-ordered in the order they were added, hence call this method early on.
+ *
+ * @param orderingAsAdded Whether to order according to the order added.
+ * @see Preference#setOrder(int)
+ */
+ public void setOrderingAsAdded(boolean orderingAsAdded) {
+ mOrderingAsAdded = orderingAsAdded;
+ }
+
+ /**
+ * Whether this group is ordering preferences in the order they are added.
+ *
+ * @return Whether this group orders based on the order the children are added.
+ * @see #setOrderingAsAdded(boolean)
+ */
+ public boolean isOrderingAsAdded() {
+ return mOrderingAsAdded;
+ }
+
+ /**
+ * Called by the inflater to add an item to this group.
+ */
+ public void addItemFromInflater(Preference preference) {
+ addPreference(preference);
+ }
+
+ /**
+ * Returns the number of children {@link Preference}s.
+ * @return The number of preference children in this group.
+ */
+ public int getPreferenceCount() {
+ return mPreferenceList.size();
+ }
+
+ /**
+ * Returns the {@link Preference} at a particular index.
+ *
+ * @param index The index of the {@link Preference} to retrieve.
+ * @return The {@link Preference}.
+ */
+ public Preference getPreference(int index) {
+ return mPreferenceList.get(index);
+ }
+
+ /**
+ * Adds a {@link Preference} at the correct position based on the
+ * preference's order.
+ *
+ * @param preference The preference to add.
+ * @return Whether the preference is now in this group.
+ */
+ public boolean addPreference(Preference preference) {
+ if (mPreferenceList.contains(preference)) {
+ // Exists
+ return true;
+ }
+
+ if (preference.getOrder() == Preference.DEFAULT_ORDER) {
+ if (mOrderingAsAdded) {
+ preference.setOrder(mCurrentPreferenceOrder++);
+ }
+
+ if (preference instanceof PreferenceGroup) {
+ // TODO: fix (method is called tail recursively when inflating,
+ // so we won't end up properly passing this flag down to children
+ ((PreferenceGroup)preference).setOrderingAsAdded(mOrderingAsAdded);
+ }
+ }
+
+ int insertionIndex = Collections.binarySearch(mPreferenceList, preference);
+ if (insertionIndex < 0) {
+ insertionIndex = insertionIndex * -1 - 1;
+ }
+
+ if (!onPrepareAddPreference(preference)) {
+ return false;
+ }
+
+ synchronized(this) {
+ mPreferenceList.add(insertionIndex, preference);
+ }
+
+ preference.onAttachedToHierarchy(getPreferenceManager());
+
+ if (mAttachedToActivity) {
+ preference.onAttachedToActivity();
+ }
+
+ notifyHierarchyChanged();
+
+ return true;
+ }
+
+ /**
+ * Removes a {@link Preference} from this group.
+ *
+ * @param preference The preference to remove.
+ * @return Whether the preference was found and removed.
+ */
+ public boolean removePreference(Preference preference) {
+ final boolean returnValue = removePreferenceInt(preference);
+ notifyHierarchyChanged();
+ return returnValue;
+ }
+
+ private boolean removePreferenceInt(Preference preference) {
+ synchronized(this) {
+ preference.onPrepareForRemoval();
+ return mPreferenceList.remove(preference);
+ }
+ }
+
+ /**
+ * Removes all {@link Preference Preferences} from this group.
+ */
+ public void removeAll() {
+ synchronized(this) {
+ List<Preference> preferenceList = mPreferenceList;
+ for (int i = preferenceList.size() - 1; i >= 0; i--) {
+ removePreferenceInt(preferenceList.get(0));
+ }
+ }
+ notifyHierarchyChanged();
+ }
+
+ /**
+ * Prepares a {@link Preference} to be added to the group.
+ *
+ * @param preference The preference to add.
+ * @return Whether to allow adding the preference (true), or not (false).
+ */
+ protected boolean onPrepareAddPreference(Preference preference) {
+ if (!super.isEnabled()) {
+ preference.setEnabled(false);
+ }
+
+ return true;
+ }
+
+ /**
+ * Finds a {@link Preference} based on its key. If two {@link Preference}
+ * share the same key (not recommended), the first to appear will be
+ * returned (to retrieve the other preference with the same key, call this
+ * method on the first preference). If this preference has the key, it will
+ * not be returned.
+ * <p>
+ * This will recursively search for the preference into children that are
+ * also {@link PreferenceGroup PreferenceGroups}.
+ *
+ * @param key The key of the preference to retrieve.
+ * @return The {@link Preference} with the key, or null.
+ */
+ public Preference findPreference(CharSequence key) {
+ final int preferenceCount = getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ final Preference preference = getPreference(i);
+ final String curKey = preference.getKey();
+
+ if (curKey != null && curKey.equals(key)) {
+ return preference;
+ }
+
+ if (preference instanceof PreferenceGroup) {
+ final Preference returnedPreference = ((PreferenceGroup)preference)
+ .findPreference(key);
+ if (returnedPreference != null) {
+ return returnedPreference;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Whether this preference group should be shown on the same screen as its
+ * contained preferences.
+ *
+ * @return True if the contained preferences should be shown on the same
+ * screen as this preference.
+ */
+ protected boolean isOnSameScreenAsChildren() {
+ return true;
+ }
+
+ @Override
+ protected void onAttachedToActivity() {
+ super.onAttachedToActivity();
+
+ // Mark as attached so if a preference is later added to this group, we
+ // can tell it we are already attached
+ mAttachedToActivity = true;
+
+ // Dispatch to all contained preferences
+ final int preferenceCount = getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ getPreference(i).onAttachedToActivity();
+ }
+ }
+
+ @Override
+ protected void onPrepareForRemoval() {
+ super.onPrepareForRemoval();
+
+ // We won't be attached to the activity anymore
+ mAttachedToActivity = false;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+
+ // Dispatch to all contained preferences
+ final int preferenceCount = getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ getPreference(i).setEnabled(enabled);
+ }
+ }
+
+ void sortPreferences() {
+ synchronized (this) {
+ Collections.sort(mPreferenceList);
+ }
+ }
+
+ @Override
+ protected void dispatchSaveInstanceState(Bundle container) {
+ super.dispatchSaveInstanceState(container);
+
+ // Dispatch to all contained preferences
+ final int preferenceCount = getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ getPreference(i).dispatchSaveInstanceState(container);
+ }
+ }
+
+ @Override
+ protected void dispatchRestoreInstanceState(Bundle container) {
+ super.dispatchRestoreInstanceState(container);
+
+ // Dispatch to all contained preferences
+ final int preferenceCount = getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ getPreference(i).dispatchRestoreInstanceState(container);
+ }
+ }
+
+}
diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java
new file mode 100644
index 0000000..e2a3157
--- /dev/null
+++ b/core/java/android/preference/PreferenceGroupAdapter.java
@@ -0,0 +1,246 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import android.os.Handler;
+import android.preference.Preference.OnPreferenceChangeInternalListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Adapter;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+
+/**
+ * An adapter that returns the {@link Preference} contained in this group.
+ * In most cases, this adapter should be the base class for any custom
+ * adapters from {@link Preference#getAdapter()}.
+ * <p>
+ * This adapter obeys the
+ * {@link Preference}'s adapter rule (the
+ * {@link Adapter#getView(int, View, ViewGroup)} should be used instead of
+ * {@link Preference#getView(ViewGroup)} if a {@link Preference} has an
+ * adapter via {@link Preference#getAdapter()}).
+ * <p>
+ * This adapter also propagates data change/invalidated notifications upward.
+ * <p>
+ * This adapter does not include this {@link PreferenceGroup} in the returned
+ * adapter, use {@link PreferenceCategoryAdapter} instead.
+ *
+ * @see PreferenceCategoryAdapter
+ */
+class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeInternalListener {
+
+ private static final String TAG = "PreferenceGroupAdapter";
+
+ /**
+ * The group that we are providing data from.
+ */
+ private PreferenceGroup mPreferenceGroup;
+
+ /**
+ * Maps a position into this adapter -> {@link Preference}. These
+ * {@link Preference}s don't have to be direct children of this
+ * {@link PreferenceGroup}, they can be grand children or younger)
+ */
+ private List<Preference> mPreferenceList;
+
+ /**
+ * List of unique Preference and its subclasses' names. This is used to find
+ * out how many types of views this adapter can return. Once the count is
+ * returned, this cannot be modified (since the ListView only checks the
+ * count once--when the adapter is being set). We will not recycle views for
+ * Preference subclasses seen after the count has been returned.
+ */
+ private List<String> mPreferenceClassNames;
+
+ /**
+ * Blocks the mPreferenceClassNames from being changed anymore.
+ */
+ private boolean mHasReturnedViewTypeCount = false;
+
+ private volatile boolean mIsSyncing = false;
+
+ private Handler mHandler = new Handler();
+
+ private Runnable mSyncRunnable = new Runnable() {
+ public void run() {
+ syncMyPreferences();
+ }
+ };
+
+ public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
+ mPreferenceGroup = preferenceGroup;
+ mPreferenceList = new ArrayList<Preference>();
+ mPreferenceClassNames = new ArrayList<String>();
+
+ syncMyPreferences();
+ }
+
+ private void syncMyPreferences() {
+ synchronized(this) {
+ if (mIsSyncing) {
+ return;
+ }
+
+ mIsSyncing = true;
+ }
+
+ List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size());
+ flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);
+ mPreferenceList = newPreferenceList;
+
+ notifyDataSetChanged();
+
+ synchronized(this) {
+ mIsSyncing = false;
+ notifyAll();
+ }
+ }
+
+ private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {
+ // TODO: shouldn't always?
+ group.sortPreferences();
+
+ final int groupSize = group.getPreferenceCount();
+ for (int i = 0; i < groupSize; i++) {
+ final Preference preference = group.getPreference(i);
+
+ preferences.add(preference);
+
+ if (!mHasReturnedViewTypeCount) {
+ addPreferenceClassName(preference);
+ }
+
+ if (preference instanceof PreferenceGroup) {
+ final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference;
+ if (preferenceAsGroup.isOnSameScreenAsChildren()) {
+ flattenPreferenceGroup(preferences, preferenceAsGroup);
+ preference.setOnPreferenceChangeInternalListener(this);
+ }
+ } else {
+ preference.setOnPreferenceChangeInternalListener(this);
+ }
+ }
+ }
+
+ private void addPreferenceClassName(Preference preference) {
+ final String name = preference.getClass().getName();
+ int insertPos = Collections.binarySearch(mPreferenceClassNames, name);
+
+ // Only insert if it doesn't exist (when it is negative).
+ if (insertPos < 0) {
+ // Convert to insert index
+ insertPos = insertPos * -1 - 1;
+ mPreferenceClassNames.add(insertPos, name);
+ }
+ }
+
+ public int getCount() {
+ return mPreferenceList.size();
+ }
+
+ public Preference getItem(int position) {
+ if (position < 0 || position >= getCount()) return null;
+ return mPreferenceList.get(position);
+ }
+
+ public long getItemId(int position) {
+ if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID;
+ return this.getItem(position).getId();
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final Preference preference = this.getItem(position);
+
+ if (preference.hasSpecifiedLayout()) {
+ // If the preference had specified a layout (as opposed to the
+ // default), don't use convert views.
+ convertView = null;
+ } else {
+ // TODO: better way of doing this
+ final String name = preference.getClass().getName();
+ if (Collections.binarySearch(mPreferenceClassNames, name) < 0) {
+ convertView = null;
+ }
+ }
+
+ return preference.getView(convertView, parent);
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ if (position < 0 || position >= getCount()) return true;
+ return this.getItem(position).isSelectable();
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ // There should always be a preference group, and these groups are always
+ // disabled
+ return false;
+ }
+
+ public void onPreferenceChange(Preference preference) {
+ notifyDataSetChanged();
+ }
+
+ public void onPreferenceHierarchyChange(Preference preference) {
+ mHandler.removeCallbacks(mSyncRunnable);
+ mHandler.post(mSyncRunnable);
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ if (!mHasReturnedViewTypeCount) {
+ mHasReturnedViewTypeCount = true;
+ }
+
+ final Preference preference = this.getItem(position);
+ if (preference.hasSpecifiedLayout()) {
+ return IGNORE_ITEM_VIEW_TYPE;
+ }
+
+ final String name = preference.getClass().getName();
+ int viewType = Collections.binarySearch(mPreferenceClassNames, name);
+ if (viewType < 0) {
+ // This is a class that was seen after we returned the count, so
+ // don't recycle it.
+ return IGNORE_ITEM_VIEW_TYPE;
+ } else {
+ return viewType;
+ }
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ if (!mHasReturnedViewTypeCount) {
+ mHasReturnedViewTypeCount = true;
+ }
+
+ return mPreferenceClassNames.size();
+ }
+
+}
diff --git a/core/java/android/preference/PreferenceInflater.java b/core/java/android/preference/PreferenceInflater.java
new file mode 100644
index 0000000..779e746
--- /dev/null
+++ b/core/java/android/preference/PreferenceInflater.java
@@ -0,0 +1,103 @@
+/*
+ * 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 java.io.IOException;
+import java.util.Map;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.app.AliasActivity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * The {@link PreferenceInflater} is used to inflate preference hierarchies from
+ * XML files.
+ * <p>
+ * Do not construct this directly, instead use
+ * {@link Context#getSystemService(String)} with
+ * {@link Context#PREFERENCE_INFLATER_SERVICE}.
+ */
+class PreferenceInflater extends GenericInflater<Preference, PreferenceGroup> {
+ private static final String TAG = "PreferenceInflater";
+ private static final String INTENT_TAG_NAME = "intent";
+
+ private PreferenceManager mPreferenceManager;
+
+ public PreferenceInflater(Context context, PreferenceManager preferenceManager) {
+ super(context);
+ init(preferenceManager);
+ }
+
+ PreferenceInflater(GenericInflater<Preference, PreferenceGroup> original, PreferenceManager preferenceManager, Context newContext) {
+ super(original, newContext);
+ init(preferenceManager);
+ }
+
+ @Override
+ public GenericInflater<Preference, PreferenceGroup> cloneInContext(Context newContext) {
+ return new PreferenceInflater(this, mPreferenceManager, newContext);
+ }
+
+ private void init(PreferenceManager preferenceManager) {
+ mPreferenceManager = preferenceManager;
+ setDefaultPackage("android.preference.");
+ }
+
+ @Override
+ protected boolean onCreateCustomFromTag(XmlPullParser parser, Preference parentPreference,
+ AttributeSet attrs) throws XmlPullParserException {
+ final String tag = parser.getName();
+
+ if (tag.equals(INTENT_TAG_NAME)) {
+ Intent intent = null;
+
+ try {
+ intent = Intent.parseIntent(getContext().getResources(), parser, attrs);
+ } catch (IOException e) {
+ Log.w(TAG, "Could not parse Intent.");
+ Log.w(TAG, e);
+ }
+
+ if (intent != null) {
+ parentPreference.setIntent(intent);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ protected PreferenceGroup onMergeRoots(PreferenceGroup givenRoot, boolean attachToGivenRoot,
+ PreferenceGroup xmlRoot) {
+ // If we were given a Preferences, use it as the root (ignoring the root
+ // Preferences from the XML file).
+ if (givenRoot == null) {
+ xmlRoot.onAttachedToHierarchy(mPreferenceManager);
+ return xmlRoot;
+ } else {
+ return givenRoot;
+ }
+ }
+
+}
diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java
new file mode 100644
index 0000000..9963544
--- /dev/null
+++ b/core/java/android/preference/PreferenceManager.java
@@ -0,0 +1,790 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * The {@link PreferenceManager} is used to help create preference hierarchies
+ * from activities or XML.
+ * <p>
+ * In most cases, clients should use
+ * {@link PreferenceActivity#addPreferencesFromIntent} or
+ * {@link PreferenceActivity#addPreferencesFromResource(int)}.
+ *
+ * @see PreferenceActivity
+ */
+public class PreferenceManager {
+
+ private static final String TAG = "PreferenceManager";
+
+ /**
+ * The Activity meta-data key for its XML preference hierarchy.
+ */
+ public static final String METADATA_KEY_PREFERENCES = "android.preference";
+
+ public static final String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
+
+ /**
+ * @see #getActivity()
+ */
+ private Activity mActivity;
+
+ /**
+ * The context to use. This should always be set.
+ *
+ * @see #mActivity
+ */
+ private Context mContext;
+
+ /**
+ * The counter for unique IDs.
+ */
+ private long mNextId = 0;
+
+ /**
+ * The counter for unique request codes.
+ */
+ private int mNextRequestCode;
+
+ /**
+ * Cached shared preferences.
+ */
+ private SharedPreferences mSharedPreferences;
+
+ /**
+ * If in no-commit mode, the shared editor to give out (which will be
+ * committed when exiting no-commit mode).
+ */
+ private SharedPreferences.Editor mEditor;
+
+ /**
+ * Blocks commits from happening on the shared editor. This is used when
+ * inflating the hierarchy. Do not set this directly, use {@link #setNoCommit(boolean)}
+ */
+ private boolean mNoCommit;
+
+ /**
+ * The SharedPreferences name that will be used for all {@link Preference}s
+ * managed by this instance.
+ */
+ private String mSharedPreferencesName;
+
+ /**
+ * The SharedPreferences mode that will be used for all {@link Preference}s
+ * managed by this instance.
+ */
+ private int mSharedPreferencesMode;
+
+ /**
+ * The {@link PreferenceScreen} at the root of the preference hierarchy.
+ */
+ private PreferenceScreen mPreferenceScreen;
+
+ /**
+ * List of activity result listeners.
+ */
+ private List<OnActivityResultListener> mActivityResultListeners;
+
+ /**
+ * List of activity stop listeners.
+ */
+ private List<OnActivityStopListener> mActivityStopListeners;
+
+ /**
+ * List of activity destroy listeners.
+ */
+ private List<OnActivityDestroyListener> mActivityDestroyListeners;
+
+ /**
+ * List of dialogs that should be dismissed when we receive onNewIntent in
+ * our PreferenceActivity.
+ */
+ private List<DialogInterface> mPreferencesScreens;
+
+ private OnPreferenceTreeClickListener mOnPreferenceTreeClickListener;
+
+ PreferenceManager(Activity activity, int firstRequestCode) {
+ mActivity = activity;
+ mNextRequestCode = firstRequestCode;
+
+ init(activity);
+ }
+
+ /**
+ * This constructor should ONLY be used when getting default values from
+ * an XML preference hierarchy.
+ * <p>
+ * The {@link PreferenceManager#PreferenceManager(Activity)}
+ * should be used ANY time a preference will be displayed, since some preference
+ * types need an Activity for managed queries.
+ */
+ private PreferenceManager(Context context) {
+ init(context);
+ }
+
+ private void init(Context context) {
+ mContext = context;
+
+ setSharedPreferencesName(getDefaultSharedPreferencesName(context));
+ }
+
+ /**
+ * Returns a list of {@link Activity} (indirectly) that match a given
+ * {@link Intent}.
+ *
+ * @param queryIntent The Intent to match.
+ * @return The list of {@link ResolveInfo} that point to the matched
+ * activities.
+ */
+ private List<ResolveInfo> queryIntentActivities(Intent queryIntent) {
+ return mContext.getPackageManager().queryIntentActivities(queryIntent,
+ PackageManager.GET_META_DATA);
+ }
+
+ /**
+ * Inflates a preference hierarchy from the preference hierarchies of
+ * {@link Activity Activities} that match the given {@link Intent}. An
+ * {@link Activity} defines its preference hierarchy with meta-data using
+ * the {@link #METADATA_KEY_PREFERENCES} key.
+ * <p>
+ * If a preference hierarchy is given, the new preference hierarchies will
+ * be merged in.
+ *
+ * @param queryIntent The intent to match activities.
+ * @param rootPreferences Optional existing hierarchy to merge the new
+ * hierarchies into.
+ * @return The root hierarchy (if one was not provided, the new hierarchy's
+ * root).
+ */
+ PreferenceScreen inflateFromIntent(Intent queryIntent, PreferenceScreen rootPreferences) {
+ final List<ResolveInfo> activities = queryIntentActivities(queryIntent);
+ final HashSet<String> inflatedRes = new HashSet<String>();
+
+ for (int i = activities.size() - 1; i >= 0; i--) {
+ final ActivityInfo activityInfo = activities.get(i).activityInfo;
+ final Bundle metaData = activityInfo.metaData;
+
+ if ((metaData == null) || !metaData.containsKey(METADATA_KEY_PREFERENCES)) {
+ continue;
+ }
+
+ // Need to concat the package with res ID since the same res ID
+ // can be re-used across contexts
+ final String uniqueResId = activityInfo.packageName + ":"
+ + activityInfo.metaData.getInt(METADATA_KEY_PREFERENCES);
+
+ if (!inflatedRes.contains(uniqueResId)) {
+ inflatedRes.add(uniqueResId);
+
+ final Context context;
+ try {
+ context = mContext.createPackageContext(activityInfo.packageName, 0);
+ } catch (NameNotFoundException e) {
+ Log.w(TAG, "Could not create context for " + activityInfo.packageName + ": "
+ + Log.getStackTraceString(e));
+ continue;
+ }
+
+ final PreferenceInflater inflater = new PreferenceInflater(context, this);
+ final XmlResourceParser parser = activityInfo.loadXmlMetaData(context
+ .getPackageManager(), METADATA_KEY_PREFERENCES);
+ rootPreferences = (PreferenceScreen) inflater
+ .inflate(parser, rootPreferences, true);
+ parser.close();
+ }
+ }
+
+ rootPreferences.onAttachedToHierarchy(this);
+
+ return rootPreferences;
+ }
+
+ /**
+ * Inflates a preference hierarchy from XML. If a preference hierarchy is
+ * given, the new preference hierarchies will be merged in.
+ *
+ * @param context The context of the resource.
+ * @param resId The resource ID of the XML to inflate.
+ * @param rootPreferences Optional existing hierarchy to merge the new
+ * hierarchies into.
+ * @return The root hierarchy (if one was not provided, the new hierarchy's
+ * root).
+ */
+ PreferenceScreen inflateFromResource(Context context, int resId,
+ PreferenceScreen rootPreferences) {
+ // Block commits
+ setNoCommit(true);
+
+ final PreferenceInflater inflater = new PreferenceInflater(context, this);
+ rootPreferences = (PreferenceScreen) inflater.inflate(resId, rootPreferences, true);
+ rootPreferences.onAttachedToHierarchy(this);
+
+ // Unblock commits
+ setNoCommit(false);
+
+ return rootPreferences;
+ }
+
+ public PreferenceScreen createPreferenceScreen(Context context) {
+ final PreferenceScreen preferenceScreen = new PreferenceScreen(context, null);
+ preferenceScreen.onAttachedToHierarchy(this);
+ return preferenceScreen;
+ }
+
+ /**
+ * Called by a preference to get a unique ID in its hierarchy.
+ *
+ * @return A unique ID.
+ */
+ long getNextId() {
+ synchronized (this) {
+ return mNextId++;
+ }
+ }
+
+ /**
+ * Returns the current name of the SharedPreferences file that preferences managed by
+ * this will use.
+ *
+ * @return The name that can be passed to {@link Context#getSharedPreferences(String, int)}.
+ * @see Context#getSharedPreferences(String, int)
+ */
+ public String getSharedPreferencesName() {
+ return mSharedPreferencesName;
+ }
+
+ /**
+ * Sets the name of the SharedPreferences file that preferences managed by this
+ * will use.
+ *
+ * @param sharedPreferencesName The name of the SharedPreferences file.
+ * @see Context#getSharedPreferences(String, int)
+ */
+ public void setSharedPreferencesName(String sharedPreferencesName) {
+ mSharedPreferencesName = sharedPreferencesName;
+ mSharedPreferences = null;
+ }
+
+ /**
+ * Returns the current mode of the SharedPreferences file that preferences managed by
+ * this will use.
+ *
+ * @return The mode that can be passed to {@link Context#getSharedPreferences(String, int)}.
+ * @see Context#getSharedPreferences(String, int)
+ */
+ public int getSharedPreferencesMode() {
+ return mSharedPreferencesMode;
+ }
+
+ /**
+ * Sets the mode of the SharedPreferences file that preferences managed by this
+ * will use.
+ *
+ * @param sharedPreferencesMode The mode of the SharedPreferences file.
+ * @see Context#getSharedPreferences(String, int)
+ */
+ public void setSharedPreferencesMode(int sharedPreferencesMode) {
+ mSharedPreferencesMode = sharedPreferencesMode;
+ mSharedPreferences = null;
+ }
+
+ /**
+ * Gets a SharedPreferences instance that preferences managed by this will
+ * use.
+ *
+ * @return A SharedPreferences instance pointing to the file that contains
+ * the values of preferences that are managed by this.
+ */
+ public SharedPreferences getSharedPreferences() {
+ if (mSharedPreferences == null) {
+ mSharedPreferences = mContext.getSharedPreferences(mSharedPreferencesName,
+ mSharedPreferencesMode);
+ }
+
+ return mSharedPreferences;
+ }
+
+ /**
+ * Gets a SharedPreferences instance that points to the default file that is
+ * used by the preference framework in the given context.
+ *
+ * @param context The context of the preferences whose values are wanted.
+ * @return A SharedPreferences instance that can be used to retrieve and
+ * listen to values of the preferences.
+ */
+ public static SharedPreferences getDefaultSharedPreferences(Context context) {
+ return context.getSharedPreferences(getDefaultSharedPreferencesName(context),
+ getDefaultSharedPreferencesMode());
+ }
+
+ private static String getDefaultSharedPreferencesName(Context context) {
+ return context.getPackageName() + "_preferences";
+ }
+
+ private static int getDefaultSharedPreferencesMode() {
+ return Context.MODE_PRIVATE;
+ }
+
+ /**
+ * Returns the root of the preference hierarchy managed by this class.
+ *
+ * @return The {@link PreferenceScreen} object that is at the root of the hierarchy.
+ */
+ PreferenceScreen getPreferenceScreen() {
+ return mPreferenceScreen;
+ }
+
+ /**
+ * Sets the root of the preference hierarchy.
+ *
+ * @param preferenceScreen The root {@link PreferenceScreen} of the preference hierarchy.
+ * @return Whether the {@link PreferenceScreen} given is different than the previous.
+ */
+ boolean setPreferences(PreferenceScreen preferenceScreen) {
+ if (preferenceScreen != mPreferenceScreen) {
+ mPreferenceScreen = preferenceScreen;
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * 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)
+ */
+ public Preference findPreference(CharSequence key) {
+ if (mPreferenceScreen == null) {
+ return null;
+ }
+
+ return mPreferenceScreen.findPreference(key);
+ }
+
+ /**
+ * Sets the default values from a preference hierarchy in XML. This should
+ * be called by the application's main activity.
+ * <p>
+ * If {@code readAgain} is false, this will only set the default values if this
+ * method has never been called in the past (or the
+ * {@link #KEY_HAS_SET_DEFAULT_VALUES} in the default value shared
+ * preferences file is false). To attempt to set the default values again
+ * bypassing this check, set {@code readAgain} to true.
+ *
+ * @param context The context of the shared preferences.
+ * @param resId The resource ID of the preference hierarchy XML file.
+ * @param readAgain Whether to re-read the default values.
+ * <p>
+ * Note: this will NOT reset preferences back to their default
+ * values. For that functionality, use
+ * {@link PreferenceManager#getDefaultSharedPreferences(Context)}
+ * and clear it followed by a call to this method with this
+ * parameter set to true.
+ */
+ public static void setDefaultValues(Context context, int resId, boolean readAgain) {
+
+ // Use the default shared preferences name and mode
+ setDefaultValues(context, getDefaultSharedPreferencesName(context),
+ getDefaultSharedPreferencesMode(), resId, readAgain);
+ }
+
+ /**
+ * Similar to {@link #setDefaultValues(Context, int, boolean)} but allows
+ * the client to provide the filename and mode of the shared preferences
+ * file.
+ *
+ * @see #setDefaultValues(Context, int, boolean)
+ * @see #setSharedPreferencesName(String)
+ * @see #setSharedPreferencesMode(int)
+ */
+ public static void setDefaultValues(Context context, String sharedPreferencesName,
+ int sharedPreferencesMode, int resId, boolean readAgain) {
+ final SharedPreferences defaultValueSp = context.getSharedPreferences(
+ KEY_HAS_SET_DEFAULT_VALUES, Context.MODE_PRIVATE);
+
+ if (readAgain || !defaultValueSp.getBoolean(KEY_HAS_SET_DEFAULT_VALUES, false)) {
+ final PreferenceManager pm = new PreferenceManager(context);
+ pm.setSharedPreferencesName(sharedPreferencesName);
+ pm.setSharedPreferencesMode(sharedPreferencesMode);
+ pm.inflateFromResource(context, resId, null);
+
+ defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true).commit();
+ }
+ }
+
+ /**
+ * Returns an editor to use when modifying the shared preferences.
+ * <p>
+ * Do NOT commit unless {@link #shouldCommit()} returns true.
+ *
+ * @return An editor to use to write to shared preferences.
+ * @see #shouldCommit()
+ */
+ SharedPreferences.Editor getEditor() {
+
+ if (mNoCommit) {
+ if (mEditor == null) {
+ mEditor = getSharedPreferences().edit();
+ }
+
+ return mEditor;
+ } else {
+ return getSharedPreferences().edit();
+ }
+ }
+
+ /**
+ * Whether it is the client's responsibility to commit on the
+ * {@link #getEditor()}. This will return false in cases where the writes
+ * should be batched, for example when inflating preferences from XML.
+ *
+ * @return Whether the client should commit.
+ */
+ boolean shouldCommit() {
+ return !mNoCommit;
+ }
+
+ private void setNoCommit(boolean noCommit) {
+ if (!noCommit && mEditor != null) {
+ mEditor.commit();
+ }
+
+ mNoCommit = noCommit;
+ }
+
+ /**
+ * Returns the activity that shows the preferences. This is useful for doing
+ * managed queries, but in most cases the use of {@link #getContext()} is
+ * preferred.
+ * <p>
+ * This will return null if this class was instantiated with a Context
+ * instead of Activity. For example, when setting the default values.
+ *
+ * @return The activity that shows the preferences.
+ * @see #mContext
+ */
+ Activity getActivity() {
+ return mActivity;
+ }
+
+ /**
+ * Returns the context. This is preferred over {@link #getActivity()} when
+ * possible.
+ *
+ * @return The context.
+ */
+ Context getContext() {
+ return mContext;
+ }
+
+ /**
+ * Registers a listener.
+ *
+ * @see OnActivityResultListener
+ */
+ void registerOnActivityResultListener(OnActivityResultListener listener) {
+ synchronized (this) {
+ if (mActivityResultListeners == null) {
+ mActivityResultListeners = new ArrayList<OnActivityResultListener>();
+ }
+
+ if (!mActivityResultListeners.contains(listener)) {
+ mActivityResultListeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Unregisters a listener.
+ *
+ * @see OnActivityResultListener
+ */
+ void unregisterOnActivityResultListener(OnActivityResultListener listener) {
+ synchronized (this) {
+ if (mActivityResultListeners != null) {
+ mActivityResultListeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ * Called by the {@link PreferenceManager} to dispatch a subactivity result.
+ */
+ void dispatchActivityResult(int requestCode, int resultCode, Intent data) {
+ List<OnActivityResultListener> list;
+
+ synchronized (this) {
+ if (mActivityResultListeners == null) return;
+ list = new ArrayList<OnActivityResultListener>(mActivityResultListeners);
+ }
+
+ final int N = list.size();
+ for (int i = 0; i < N; i++) {
+ if (list.get(i).onActivityResult(requestCode, resultCode, data)) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Registers a listener.
+ *
+ * @see OnActivityStopListener
+ */
+ void registerOnActivityStopListener(OnActivityStopListener listener) {
+ synchronized (this) {
+ if (mActivityStopListeners == null) {
+ mActivityStopListeners = new ArrayList<OnActivityStopListener>();
+ }
+
+ if (!mActivityStopListeners.contains(listener)) {
+ mActivityStopListeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Unregisters a listener.
+ *
+ * @see OnActivityStopListener
+ */
+ void unregisterOnActivityStopListener(OnActivityStopListener listener) {
+ synchronized (this) {
+ if (mActivityStopListeners != null) {
+ mActivityStopListeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ * Called by the {@link PreferenceManager} to dispatch the activity stop
+ * event.
+ */
+ void dispatchActivityStop() {
+ List<OnActivityStopListener> list;
+
+ synchronized (this) {
+ if (mActivityStopListeners == null) return;
+ list = new ArrayList<OnActivityStopListener>(mActivityStopListeners);
+ }
+
+ final int N = list.size();
+ for (int i = 0; i < N; i++) {
+ list.get(i).onActivityStop();
+ }
+ }
+
+ /**
+ * Registers a listener.
+ *
+ * @see OnActivityDestroyListener
+ */
+ void registerOnActivityDestroyListener(OnActivityDestroyListener listener) {
+ synchronized (this) {
+ if (mActivityDestroyListeners == null) {
+ mActivityDestroyListeners = new ArrayList<OnActivityDestroyListener>();
+ }
+
+ if (!mActivityDestroyListeners.contains(listener)) {
+ mActivityDestroyListeners.add(listener);
+ }
+ }
+ }
+
+ /**
+ * Unregisters a listener.
+ *
+ * @see OnActivityDestroyListener
+ */
+ void unregisterOnActivityDestroyListener(OnActivityDestroyListener listener) {
+ synchronized (this) {
+ if (mActivityDestroyListeners != null) {
+ mActivityDestroyListeners.remove(listener);
+ }
+ }
+ }
+
+ /**
+ * Called by the {@link PreferenceManager} to dispatch the activity destroy
+ * event.
+ */
+ void dispatchActivityDestroy() {
+ List<OnActivityDestroyListener> list;
+
+ synchronized (this) {
+ if (mActivityDestroyListeners == null) return;
+ list = new ArrayList<OnActivityDestroyListener>(mActivityDestroyListeners);
+ }
+
+ final int N = list.size();
+ for (int i = 0; i < N; i++) {
+ list.get(i).onActivityDestroy();
+ }
+ }
+
+ /**
+ * Returns a request code that is unique for the activity. Each subsequent
+ * call to this method should return another unique request code.
+ *
+ * @return A unique request code that will never be used by anyone other
+ * than the caller of this method.
+ */
+ int getNextRequestCode() {
+ synchronized (this) {
+ return mNextRequestCode++;
+ }
+ }
+
+ void addPreferencesScreen(DialogInterface screen) {
+ synchronized (this) {
+
+ if (mPreferencesScreens == null) {
+ mPreferencesScreens = new ArrayList<DialogInterface>();
+ }
+
+ mPreferencesScreens.add(screen);
+ }
+ }
+
+ void removePreferencesScreen(DialogInterface screen) {
+ synchronized (this) {
+
+ if (mPreferencesScreens == null) {
+ return;
+ }
+
+ mPreferencesScreens.remove(screen);
+ }
+ }
+
+ /**
+ * Called by {@link PreferenceActivity} to dispatch the new Intent event.
+ *
+ * @param intent The new Intent.
+ */
+ void dispatchNewIntent(Intent intent) {
+
+ // Remove any of the previously shown preferences screens
+ ArrayList<DialogInterface> screensToDismiss;
+
+ synchronized (this) {
+
+ if (mPreferencesScreens == null) {
+ return;
+ }
+
+ screensToDismiss = new ArrayList<DialogInterface>(mPreferencesScreens);
+ mPreferencesScreens.clear();
+ }
+
+ for (int i = screensToDismiss.size() - 1; i >= 0; i--) {
+ screensToDismiss.get(i).dismiss();
+ }
+ }
+
+ /**
+ * Sets the callback to be invoked when a {@link Preference} in the
+ * hierarchy rooted at this {@link PreferenceManager} is clicked.
+ *
+ * @param listener The callback to be invoked.
+ */
+ void setOnPreferenceTreeClickListener(OnPreferenceTreeClickListener listener) {
+ mOnPreferenceTreeClickListener = listener;
+ }
+
+ OnPreferenceTreeClickListener getOnPreferenceTreeClickListener() {
+ return mOnPreferenceTreeClickListener;
+ }
+
+ /**
+ * Interface definition for a callback to be invoked when a
+ * {@link Preference} in the hierarchy rooted at this {@link PreferenceScreen} is
+ * clicked.
+ */
+ interface OnPreferenceTreeClickListener {
+ /**
+ * Called when a preference in the tree rooted at this
+ * {@link PreferenceScreen} has been clicked.
+ *
+ * @param preferenceScreen The {@link PreferenceScreen} that the
+ * preference is located in.
+ * @param preference The preference that was clicked.
+ * @return Whether the click was handled.
+ */
+ boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference);
+ }
+
+ /**
+ * Interface definition for a class that will be called when the container's activity
+ * receives an activity result.
+ */
+ public interface OnActivityResultListener {
+
+ /**
+ * See Activity's onActivityResult.
+ *
+ * @return Whether the request code was handled (in which case
+ * subsequent listeners will not be called.
+ */
+ boolean onActivityResult(int requestCode, int resultCode, Intent data);
+ }
+
+ /**
+ * Interface definition for a class that will be called when the container's activity
+ * is stopped.
+ */
+ public interface OnActivityStopListener {
+
+ /**
+ * See Activity's onStop.
+ */
+ void onActivityStop();
+ }
+
+ /**
+ * Interface definition for a class that will be called when the container's activity
+ * is destroyed.
+ */
+ public interface OnActivityDestroyListener {
+
+ /**
+ * See Activity's onDestroy.
+ */
+ void onActivityDestroy();
+ }
+
+}
diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java
new file mode 100644
index 0000000..e4ecb88
--- /dev/null
+++ b/core/java/android/preference/PreferenceScreen.java
@@ -0,0 +1,252 @@
+/*
+ * 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.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Adapter;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+/**
+ * The {@link PreferenceScreen} class represents a top-level {@link Preference} that
+ * is the root of a {@link Preference} hierarchy. A {@link PreferenceActivity}
+ * points to an instance of this class to show the preferences. To instantiate
+ * this class, use {@link PreferenceManager#createPreferenceScreen(Context)}.
+ * <p>
+ * This class can appear in two places:
+ * <li> When a {@link PreferenceActivity} points to this, it is used as the root
+ * and is not shown (only the contained preferences are shown).
+ * <li> When it appears inside another preference hierarchy, it is shown and
+ * serves as the gateway to another screen of preferences (either by showing
+ * another screen of preferences as a {@link Dialog} or via a
+ * {@link Context#startActivity(android.content.Intent)} from the
+ * {@link Preference#getIntent()}). The children of this {@link PreferenceScreen}
+ * are NOT shown in the screen that this {@link PreferenceScreen} is shown in.
+ * Instead, a separate screen will be shown when this preference is clicked.
+ * <p>
+ * <code>
+ &lt;PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:key="first_preferencescreen"&gt;
+ &lt;CheckBoxPreference
+ android:key="wifi enabled"
+ android:title="WiFi" /&gt;
+ &lt;PreferenceScreen
+ android:key="second_preferencescreen"
+ android:title="WiFi settings"&gt;
+ &lt;CheckBoxPreference
+ android:key="prefer wifi"
+ android:title="Prefer WiFi" /&gt;
+ ... other preferences here ...
+ &lt;/PreferenceScreen&gt;
+ &lt;/PreferenceScreen&gt;
+ * </code>
+ * In this example, the "first_preferencescreen" will be used as the root of the
+ * hierarchy and given to a {@link PreferenceActivity}. The first screen will
+ * show preferences "WiFi" (which can be used to quickly enable/disable WiFi)
+ * and "WiFi settings". The "WiFi settings" is the "second_preferencescreen" and when
+ * clicked will show another screen of preferences such as "Prefer WiFi" (and
+ * the other preferences that are children of the "second_preferencescreen" tag).
+ *
+ * @see PreferenceCategory
+ */
+public final class PreferenceScreen extends PreferenceGroup implements AdapterView.OnItemClickListener,
+ DialogInterface.OnDismissListener {
+
+ private ListAdapter mRootAdapter;
+
+ private Dialog mDialog;
+
+ /**
+ * Do NOT use this constructor, use {@link PreferenceManager#createPreferenceScreen(Context)}.
+ * @hide-
+ */
+ public PreferenceScreen(Context context, AttributeSet attrs) {
+ super(context, attrs, com.android.internal.R.attr.preferenceScreenStyle);
+ }
+
+ /**
+ * Returns an adapter that can be attached to a {@link PreferenceActivity}
+ * to show the preferences contained in this {@link PreferenceScreen}.
+ * <p>
+ * This {@link PreferenceScreen} will NOT appear in the returned adapter, instead
+ * it appears in the hierarchy above this {@link PreferenceScreen}.
+ * <p>
+ * This adapter's {@link Adapter#getItem(int)} should always return a
+ * subclass of {@link Preference}.
+ *
+ * @return An adapter that provides the {@link Preference} contained in this
+ * {@link PreferenceScreen}.
+ * @see PreferenceGroupAdapter
+ */
+ public ListAdapter getRootAdapter() {
+ if (mRootAdapter == null) {
+ mRootAdapter = onCreateRootAdapter();
+ }
+
+ return mRootAdapter;
+ }
+
+ /**
+ * Creates the root adapter.
+ *
+ * @return An adapter that contains the preferences contained in this {@link PreferenceScreen}.
+ * @see #getRootAdapter()
+ */
+ protected ListAdapter onCreateRootAdapter() {
+ return new PreferenceGroupAdapter(this);
+ }
+
+ /**
+ * Binds a {@link ListView} to the preferences contained in this {@link PreferenceScreen} via
+ * {@link #getRootAdapter()}. It also handles passing list item clicks to the corresponding
+ * {@link Preference} contained by this {@link PreferenceScreen}.
+ *
+ * @param listView The list view to attach to.
+ */
+ public void bind(ListView listView) {
+ listView.setOnItemClickListener(this);
+ listView.setAdapter(getRootAdapter());
+
+ onAttachedToActivity();
+ }
+
+ @Override
+ protected void onClick() {
+ if (getIntent() != null || getPreferenceCount() == 0) {
+ return;
+ }
+
+ showDialog(null);
+ }
+
+ private void showDialog(Bundle state) {
+ Context context = getContext();
+ ListView listView = new ListView(context);
+ bind(listView);
+
+ Dialog dialog = mDialog = new Dialog(context, com.android.internal.R.style.Theme_NoTitleBar);
+ dialog.setContentView(listView);
+ dialog.setOnDismissListener(this);
+ if (state != null) {
+ dialog.onRestoreInstanceState(state);
+ }
+
+ // Add the screen to the list of preferences screens opened as dialogs
+ getPreferenceManager().addPreferencesScreen(dialog);
+
+ dialog.show();
+ }
+
+ public void onDismiss(DialogInterface dialog) {
+ mDialog = null;
+ getPreferenceManager().removePreferencesScreen(dialog);
+ }
+
+ /**
+ * Used to get a handle to the dialog.
+ * This is useful for cases where we want to manipulate the dialog
+ * as we would with any other activity or view.
+ */
+ public Dialog getDialog() {
+ return mDialog;
+ }
+
+ public void onItemClick(AdapterView parent, View view, int position, long id) {
+ Object item = getRootAdapter().getItem(position);
+ if (!(item instanceof Preference)) return;
+
+ final Preference preference = (Preference) item;
+ preference.performClick(this);
+ }
+
+ @Override
+ protected boolean isOnSameScreenAsChildren() {
+ return false;
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ final Parcelable superState = super.onSaveInstanceState();
+ final Dialog dialog = mDialog;
+ if (dialog == null || !dialog.isShowing()) {
+ return superState;
+ }
+
+ final SavedState myState = new SavedState(superState);
+ myState.isDialogShowing = true;
+ myState.dialogBundle = dialog.onSaveInstanceState();
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (state == null || !state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ if (myState.isDialogShowing) {
+ showDialog(myState.dialogBundle);
+ }
+ }
+
+ private static class SavedState extends BaseSavedState {
+ boolean isDialogShowing;
+ Bundle dialogBundle;
+
+ public SavedState(Parcel source) {
+ super(source);
+ isDialogShowing = source.readInt() == 1;
+ dialogBundle = source.readBundle();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(isDialogShowing ? 1 : 0);
+ dest.writeBundle(dialogBundle);
+ }
+
+ public SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR =
+ new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+}
diff --git a/core/java/android/preference/RingtonePreference.java b/core/java/android/preference/RingtonePreference.java
new file mode 100644
index 0000000..97674ce
--- /dev/null
+++ b/core/java/android/preference/RingtonePreference.java
@@ -0,0 +1,242 @@
+/*
+ * 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.content.Context;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.provider.Settings.System;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * The {@link RingtonePreference} allows the user to choose one from all of the
+ * available ringtones. The chosen ringtone's URI will be persisted as a string.
+ * <p>
+ * If the user chooses the "Default" item, the saved string will be one of
+ * {@link System#DEFAULT_RINGTONE_URI} or
+ * {@link System#DEFAULT_NOTIFICATION_URI}. If the user chooses the "Silent"
+ * item, the saved string will be an empty string.
+ *
+ * @attr ref android.R.styleable#RingtonePreference_ringtoneType
+ * @attr ref android.R.styleable#RingtonePreference_showDefault
+ * @attr ref android.R.styleable#RingtonePreference_showSilent
+ */
+public class RingtonePreference extends Preference implements
+ PreferenceManager.OnActivityResultListener {
+
+ private static final String TAG = "RingtonePreference";
+
+ private int mRingtoneType;
+ private boolean mShowDefault;
+ private boolean mShowSilent;
+
+ private int mRequestCode;
+
+ public RingtonePreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.RingtonePreference, defStyle, 0);
+ mRingtoneType = a.getInt(com.android.internal.R.styleable.RingtonePreference_ringtoneType,
+ RingtoneManager.TYPE_RINGTONE);
+ mShowDefault = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showDefault,
+ true);
+ mShowSilent = a.getBoolean(com.android.internal.R.styleable.RingtonePreference_showSilent,
+ true);
+ a.recycle();
+ }
+
+ public RingtonePreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.ringtonePreferenceStyle);
+ }
+
+ public RingtonePreference(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Returns the sound type(s) that are shown in the picker.
+ *
+ * @return The sound type(s) that are shown in the picker.
+ * @see #setRingtoneType(int)
+ */
+ public int getRingtoneType() {
+ return mRingtoneType;
+ }
+
+ /**
+ * Sets the sound type(s) that are shown in the picker.
+ *
+ * @param type The sound type(s) that are shown in the picker.
+ * @see RingtoneManager#EXTRA_RINGTONE_TYPE
+ */
+ public void setRingtoneType(int type) {
+ mRingtoneType = type;
+ }
+
+ /**
+ * Returns whether to a show an item for the default sound/ringtone.
+ *
+ * @return Whether to show an item for the default sound/ringtone.
+ */
+ public boolean getShowDefault() {
+ return mShowDefault;
+ }
+
+ /**
+ * Sets whether to show an item for the default sound/ringtone. The default
+ * to use will be deduced from the sound type(s) being shown.
+ *
+ * @param showDefault Whether to show the default or not.
+ * @see RingtoneManager#EXTRA_RINGTONE_SHOW_DEFAULT
+ */
+ public void setShowDefault(boolean showDefault) {
+ mShowDefault = showDefault;
+ }
+
+ /**
+ * Returns whether to a show an item for 'Silent'.
+ *
+ * @return Whether to show an item for 'Silent'.
+ */
+ public boolean getShowSilent() {
+ return mShowSilent;
+ }
+
+ /**
+ * Sets whether to show an item for 'Silent'.
+ *
+ * @param showSilent Whether to show 'Silent'.
+ * @see RingtoneManager#EXTRA_RINGTONE_SHOW_SILENT
+ */
+ public void setShowSilent(boolean showSilent) {
+ mShowSilent = showSilent;
+ }
+
+ @Override
+ protected void onClick() {
+ // Launch the ringtone picker
+ Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+ onPrepareRingtonePickerIntent(intent);
+ getPreferenceManager().getActivity().startActivityForResult(intent, mRequestCode);
+ }
+
+ /**
+ * Prepares the intent to launch the ringtone picker. This can be modified
+ * to adjust the parameters of the ringtone picker.
+ *
+ * @param ringtonePickerIntent The ringtone picker intent that can be
+ * modified by putting extras.
+ */
+ protected void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) {
+
+ ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI,
+ onRestoreRingtone());
+
+ ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, mShowDefault);
+ if (mShowDefault) {
+ ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_DEFAULT_URI,
+ RingtoneManager.getDefaultUri(getRingtoneType()));
+ }
+
+ ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, mShowSilent);
+ ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, mRingtoneType);
+ }
+
+ /**
+ * Called when a ringtone is chosen.
+ * <p>
+ * By default, this saves the ringtone URI to the persistent storage as a
+ * string.
+ *
+ * @param ringtoneUri The chosen ringtone's {@link Uri}. Can be null.
+ */
+ protected void onSaveRingtone(Uri ringtoneUri) {
+ persistString(ringtoneUri != null ? ringtoneUri.toString() : "");
+ }
+
+ /**
+ * Called when the chooser is about to be shown and the current ringtone
+ * should be marked. Can return null to not mark any ringtone.
+ * <p>
+ * By default, this restores the previous ringtone URI from the persistent
+ * storage.
+ *
+ * @return The ringtone to be marked as the current ringtone.
+ */
+ protected Uri onRestoreRingtone() {
+ final String uriString = getPersistedString(null);
+ return !TextUtils.isEmpty(uriString) ? Uri.parse(uriString) : null;
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getString(index);
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValueObj) {
+ String defaultValue = (String) defaultValueObj;
+
+ /*
+ * This method is normally to make sure the internal state and UI
+ * matches either the persisted value or the default value. Since we
+ * don't show the current value in the UI (until the dialog is opened)
+ * and we don't keep local state, if we are restoring the persisted
+ * value we don't need to do anything.
+ */
+ if (restorePersistedValue) {
+ return;
+ }
+
+ // If we are setting to the default value, we should persist it.
+ if (!TextUtils.isEmpty(defaultValue)) {
+ onSaveRingtone(Uri.parse(defaultValue));
+ }
+ }
+
+ @Override
+ protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
+ super.onAttachedToHierarchy(preferenceManager);
+
+ preferenceManager.registerOnActivityResultListener(this);
+ mRequestCode = preferenceManager.getNextRequestCode();
+ }
+
+ public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
+
+ if (requestCode == mRequestCode) {
+
+ if (data != null) {
+ Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
+
+ if (callChangeListener(uri != null ? uri.toString() : "")) {
+ onSaveRingtone(uri);
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+}
diff --git a/core/java/android/preference/SeekBarPreference.java b/core/java/android/preference/SeekBarPreference.java
new file mode 100644
index 0000000..658c2a7
--- /dev/null
+++ b/core/java/android/preference/SeekBarPreference.java
@@ -0,0 +1,62 @@
+/*
+ * 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.content.Context;
+import android.graphics.drawable.Drawable;
+import android.preference.DialogPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+
+/**
+ * @hide
+ */
+public class SeekBarPreference extends DialogPreference {
+ private static final String TAG = "SeekBarPreference";
+
+ private Drawable mMyIcon;
+
+ public SeekBarPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ setDialogLayoutResource(com.android.internal.R.layout.seekbar_dialog);
+ setPositiveButtonText(android.R.string.ok);
+ setNegativeButtonText(android.R.string.cancel);
+
+ // Steal the XML dialogIcon attribute's value
+ mMyIcon = getDialogIcon();
+ setDialogIcon(null);
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ final ImageView iconView = (ImageView) view.findViewById(android.R.id.icon);
+ if (mMyIcon != null) {
+ iconView.setImageDrawable(mMyIcon);
+ } else {
+ iconView.setVisibility(View.GONE);
+ }
+ }
+
+ protected static SeekBar getSeekBar(View dialogView) {
+ return (SeekBar) dialogView.findViewById(com.android.internal.R.id.seekbar);
+ }
+}
diff --git a/core/java/android/preference/VolumePreference.java b/core/java/android/preference/VolumePreference.java
new file mode 100644
index 0000000..5a0a089
--- /dev/null
+++ b/core/java/android/preference/VolumePreference.java
@@ -0,0 +1,161 @@
+/*
+ * 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.content.ContentResolver;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.database.ContentObserver;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.media.AudioManager;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.provider.Settings.System;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+/**
+ * @hide
+ */
+public class VolumePreference extends SeekBarPreference implements OnSeekBarChangeListener,
+ Runnable, PreferenceManager.OnActivityStopListener {
+
+ private static final String TAG = "VolumePreference";
+
+ private ContentResolver mContentResolver;
+ private Handler mHandler = new Handler();
+
+ private AudioManager mVolume;
+ private int mStreamType;
+ private int mOriginalStreamVolume;
+ private Ringtone mRingtone;
+
+ private int mLastProgress;
+ private SeekBar mSeekBar;
+
+ private ContentObserver mVolumeObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+
+ if (mSeekBar != null) {
+ mSeekBar.setProgress(System.getInt(mContentResolver,
+ System.VOLUME_SETTINGS[mStreamType], 0));
+ }
+ }
+ };
+
+ public VolumePreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.VolumePreference, 0, 0);
+ mStreamType = a.getInt(android.R.styleable.VolumePreference_streamType, 0);
+ a.recycle();
+
+ mContentResolver = context.getContentResolver();
+
+ mVolume = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ }
+
+ @Override
+ protected void onBindDialogView(View view) {
+ super.onBindDialogView(view);
+
+ final SeekBar seekBar = mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
+ seekBar.setMax(mVolume.getStreamMaxVolume(mStreamType));
+ mOriginalStreamVolume = mVolume.getStreamVolume(mStreamType);
+ seekBar.setProgress(mOriginalStreamVolume);
+ seekBar.setOnSeekBarChangeListener(this);
+
+ mContentResolver.registerContentObserver(System.getUriFor(System.VOLUME_SETTINGS[mStreamType]), false, mVolumeObserver);
+
+ getPreferenceManager().registerOnActivityStopListener(this);
+ mRingtone = RingtoneManager.getRingtone(getContext(), Settings.System.DEFAULT_RINGTONE_URI);
+ }
+
+ @Override
+ protected void onDialogClosed(boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (!positiveResult) {
+ mVolume.setStreamVolume(mStreamType, mOriginalStreamVolume, 0);
+ }
+
+ cleanup();
+ }
+
+
+ public void onActivityStop() {
+ cleanup();
+ }
+
+ /**
+ * Do clean up. This can be called multiple times!
+ */
+ private void cleanup() {
+ stopSample();
+ if (mVolumeObserver != null) {
+ mContentResolver.unregisterContentObserver(mVolumeObserver);
+ }
+ getPreferenceManager().unregisterOnActivityStopListener(this);
+ mSeekBar = null;
+ }
+
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
+ if (!fromTouch) {
+ return;
+ }
+
+ postSetVolume(progress);
+ }
+
+ private void postSetVolume(int progress) {
+ // Do the volume changing separately to give responsive UI
+ mLastProgress = progress;
+ mHandler.removeCallbacks(this);
+ mHandler.post(this);
+ }
+
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ }
+
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ if (mRingtone != null && !mRingtone.isPlaying()) {
+ sample();
+ }
+ }
+
+ public void run() {
+ mVolume.setStreamVolume(mStreamType, mLastProgress, 0);
+ }
+
+ private void sample() {
+ mRingtone.play();
+ }
+
+ private void stopSample() {
+ if (mRingtone != null) {
+ mRingtone.stop();
+ }
+ }
+
+}
diff --git a/core/java/android/preference/package.html b/core/java/android/preference/package.html
new file mode 100644
index 0000000..d24d5bb
--- /dev/null
+++ b/core/java/android/preference/package.html
@@ -0,0 +1,23 @@
+<HTML>
+<BODY>
+Provides classes that manage application preferences and implement the preferences UI.
+Using these ensures that all the preferences within each application are maintained
+in the same manner and the user experience is consistent with that of the system and
+other applications.
+<p>
+The preferences portion of an application
+should be ran as a separate {@link android.app.Activity} that extends
+the {@link android.preference.PreferenceActivity} class. In the PreferenceActivity, a
+{@link android.preference.PreferenceScreen} object should be the root element of the layout.
+The PreferenceScreen contains {@link android.preference.Preference} elements such as a
+{@link android.preference.CheckBoxPreference}, {@link android.preference.EditTextPreference},
+{@link android.preference.ListPreference}, {@link android.preference.PreferenceCategory},
+or {@link android.preference.RingtonePreference}. </p>
+<p>
+All settings made for a given {@link android.preference.Preference} will be automatically saved
+to the application's instance of {@link android.content.SharedPreferences}. Access to the
+SharedPreferences is simple with {@link android.preference.Preference#getSharedPreferences()}.</p>
+<p>
+Note that saved preferences are accessible only to the application that created them.</p>
+</BODY>
+</HTML>