diff options
Diffstat (limited to 'core/java/android/preference/Preference.java')
-rw-r--r-- | core/java/android/preference/Preference.java | 1577 |
1 files changed, 1577 insertions, 0 deletions
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]; + } + }; + } + +} |