/* * 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. *

* 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. *

* 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, 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 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 defStyle; * 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. *

* 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. *

* 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}. *

* 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. *

* This is a good place to grab references to custom Views in the layout and * set properties on them. *

* 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. *

* 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()}. *

* 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. *

* 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. *

* {@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. *

* 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(); } 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 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. *

* 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. *

* 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. *

* 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. *

* 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 CREATOR = new Parcelable.Creator() { public BaseSavedState createFromParcel(Parcel in) { return new BaseSavedState(in); } public BaseSavedState[] newArray(int size) { return new BaseSavedState[size]; } }; } }