/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.preference; import android.annotation.CallSuper; import android.annotation.DrawableRes; import android.annotation.LayoutRes; import android.annotation.Nullable; import android.annotation.StringRes; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; import android.view.AbsSavedState; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import com.android.internal.util.CharSequences; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * Represents the basic Preference UI building * block 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 element 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. * *
For information about building a settings UI with Preferences, * read the Settings * guide.
** 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 this Preference is clicked. * * @param intent The intent associated with this 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 class name of a fragment to be shown when this Preference is clicked. * * @param fragment The class name of the fragment associated with this Preference. */ public void setFragment(String fragment) { mFragment = fragment; } /** * Return the fragment class name associated with this Preference. * * @return The fragment class name last set via {@link #setFragment} or XML. */ public String getFragment() { return mFragment; } /** * Sets a {@link PreferenceDataStore} to be used by this Preference instead of using * {@link android.content.SharedPreferences}. * *
The data store will remain assigned even if the Preference is moved around the preference * hierarchy. It will also override a data store propagated from the {@link PreferenceManager} * that owns this Preference. * * @param dataStore The {@link PreferenceDataStore} to be used by this Preference. * @see PreferenceManager#setPreferenceDataStore(PreferenceDataStore) */ public void setPreferenceDataStore(PreferenceDataStore dataStore) { mPreferenceDataStore = dataStore; } /** * Returns {@link PreferenceDataStore} used by this Preference. Returns {@code null} if * {@link android.content.SharedPreferences} is used instead. * *
By default preferences always use {@link android.content.SharedPreferences}. To make this * preference to use the {@link PreferenceDataStore} you need to assign your implementation * to the Preference itself via {@link #setPreferenceDataStore(PreferenceDataStore)} or to its * {@link PreferenceManager} via * {@link PreferenceManager#setPreferenceDataStore(PreferenceDataStore)}. * * @return The {@link PreferenceDataStore} used by this Preference or {@code null} if none. */ @Nullable public PreferenceDataStore getPreferenceDataStore() { if (mPreferenceDataStore != null) { return mPreferenceDataStore; } else if (mPreferenceManager != null) { return mPreferenceManager.getPreferenceDataStore(); } return null; } /** * Return the extras Bundle object associated with this preference, creating * a new Bundle if there currently isn't one. You can use this to get and * set individual extra key/value pairs. */ public Bundle getExtras() { if (mExtras == null) { mExtras = new Bundle(); } return mExtras; } /** * Return the extras Bundle object associated with this preference, returning {@code null} if * there is not currently one. */ public Bundle peekExtras() { return mExtras; } /** * 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 Preference objects 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(@LayoutRes int layoutResId) { if (layoutResId != mLayoutResId) { // Layout changed mRecycleEnabled = false; } mLayoutResId = layoutResId; } /** * Gets the layout resource that will be shown as the {@link View} for this Preference. * * @return The layout resource ID. */ @LayoutRes public int getLayoutResource() { return mLayoutResId; } /** * Sets the layout for the controllable widget portion of this Preference. This * is inflated into the main layout. For example, a {@link CheckBoxPreference} * 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(@LayoutRes int widgetLayoutResId) { if (widgetLayoutResId != mWidgetLayoutResId) { // Layout changed mRecycleEnabled = false; } mWidgetLayoutResId = widgetLayoutResId; } /** * Gets the layout resource for the controllable widget portion of this Preference. * * @return The layout resource ID. */ @LayoutRes 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) */ @CallSuper protected View onCreateView(ViewGroup parent) { final LayoutInflater layoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); final View layout = layoutInflater.inflate(mLayoutResId, parent, false); final ViewGroup widgetFrame = (ViewGroup) layout .findViewById(com.android.internal.R.id.widget_frame); if (widgetFrame != null) { if (mWidgetLayoutResId != 0) { layoutInflater.inflate(mWidgetLayoutResId, widgetFrame); } else { widgetFrame.setVisibility(View.GONE); } } return layout; } /** * Binds the created View to the data for this 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) */ @CallSuper protected void onBindView(View view) { final TextView titleView = (TextView) view.findViewById(com.android.internal.R.id.title); if (titleView != null) { final CharSequence title = getTitle(); if (!TextUtils.isEmpty(title)) { titleView.setText(title); titleView.setVisibility(View.VISIBLE); if (mHasSingleLineTitleAttr) { titleView.setSingleLine(mSingleLineTitle); } } else { titleView.setVisibility(View.GONE); } } final TextView summaryView = (TextView) view.findViewById( com.android.internal.R.id.summary); if (summaryView != null) { final CharSequence summary = getSummary(); if (!TextUtils.isEmpty(summary)) { summaryView.setText(summary); summaryView.setVisibility(View.VISIBLE); } else { summaryView.setVisibility(View.GONE); } } final ImageView imageView = (ImageView) view.findViewById(com.android.internal.R.id.icon); if (imageView != null) { if (mIconResId != 0 || mIcon != null) { if (mIcon == null) { mIcon = getContext().getDrawable(mIconResId); } if (mIcon != null) { imageView.setImageDrawable(mIcon); } } if (mIcon != null) { imageView.setVisibility(View.VISIBLE); } else { imageView.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE); } } final View imageFrame = view.findViewById(com.android.internal.R.id.icon_frame); if (imageFrame != null) { if (mIcon != null) { imageFrame.setVisibility(View.VISIBLE); } else { imageFrame.setVisibility(mIconSpaceReserved ? View.INVISIBLE : View.GONE); } } if (mShouldDisableView) { setEnabledStateOnViews(view, isEnabled()); } } /** * 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 Preference with respect to other Preference objects 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 Preference objects * 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 Preference with respect to other Preference objects on the same level. * * @return the order of this Preference * @see #setOrder(int) */ public int getOrder() { return mOrder; } /** * Sets the title for this Preference with a CharSequence. 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 for this Preference */ public void setTitle(CharSequence title) { if (title == null && mTitle != null || title != null && !title.equals(mTitle)) { mTitleRes = 0; mTitle = title; notifyChanged(); } } /** * Sets the title for this Preference with a resource ID. * * @see #setTitle(CharSequence) * @param titleResId the title as a resource ID */ public void setTitle(@StringRes int titleResId) { setTitle(mContext.getString(titleResId)); mTitleRes = titleResId; } /** * Returns the title resource ID of this Preference. If the title did not come from a resource, * {@code 0} is returned. * * @return the title resource * @see #setTitle(int) */ @StringRes public int getTitleRes() { return mTitleRes; } /** * Returns the title of this Preference. * * @return the title * @see #setTitle(CharSequence) */ public CharSequence getTitle() { return mTitle; } /** * Sets the icon for this Preference with a Drawable. This icon will be placed into the ID * {@link android.R.id#icon} within the View created by {@link #onCreateView(ViewGroup)}. * * @param icon the optional icon for this Preference */ public void setIcon(Drawable icon) { if ((icon == null && mIcon != null) || (icon != null && mIcon != icon)) { mIcon = icon; notifyChanged(); } } /** * Sets the icon for this Preference with a resource ID. * * @see #setIcon(Drawable) * @param iconResId the icon as a resource ID */ public void setIcon(@DrawableRes int iconResId) { if (mIconResId != iconResId) { mIconResId = iconResId; setIcon(mContext.getDrawable(iconResId)); } } /** * Returns the icon of this Preference. * * @return the icon * @see #setIcon(Drawable) */ public Drawable getIcon() { if (mIcon == null && mIconResId != 0) { mIcon = getContext().getDrawable(mIconResId); } return mIcon; } /** * Returns the summary of this Preference. * * @return the summary * @see #setSummary(CharSequence) */ public CharSequence getSummary() { return mSummary; } /** * Sets the summary for this Preference with a CharSequence. * * @param summary the summary for the preference */ public void setSummary(CharSequence summary) { if (summary == null && mSummary != null || summary != null && !summary.equals(mSummary)) { mSummary = summary; notifyChanged(); } } /** * Sets the summary for this Preference with a resource ID. * * @see #setSummary(CharSequence) * @param summaryResId the summary as a resource */ public void setSummary(@StringRes int summaryResId) { setSummary(mContext.getString(summaryResId)); } /** * Sets whether this Preference is enabled. If disabled, it will * not handle clicks. * * @param enabled set {@code true} to enable it */ public void setEnabled(boolean enabled) { if (mEnabled != enabled) { mEnabled = enabled; // Enabled state can change dependent preferences' states, so notify notifyDependencyChange(shouldDisableDependents()); notifyChanged(); } } /** * Checks whether this Preference should be enabled in the list. * * @return {@code true} if this Preference is enabled, false otherwise */ public boolean isEnabled() { return mEnabled && mDependencyMet && mParentDependencyMet; } /** * Sets whether this Preference is selectable. * * @param selectable set {@code true} to make it selectable */ public void setSelectable(boolean selectable) { if (mSelectable != selectable) { mSelectable = selectable; notifyChanged(); } } /** * Checks whether this Preference should be selectable in the list. * * @return {@code true} if it is selectable, {@code false} otherwise */ public boolean isSelectable() { return mSelectable; } /** * Sets whether this 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 set {@code true} if this preference should disable its view when * the preference is disabled */ public void setShouldDisableView(boolean shouldDisableView) { mShouldDisableView = shouldDisableView; notifyChanged(); } /** * Checks whether this Preference should disable its view when it's action is disabled. * * @see #setShouldDisableView(boolean) * @return {@code true} if it should disable the view */ public boolean getShouldDisableView() { return mShouldDisableView; } /** * Sets whether this Preference has enabled to have its view recycled when used in the list * view. By default the recycling is enabled. * *
The value can be changed only before this preference is added to the preference hierarchy. * *
If view recycling is not allowed then each time the list view populates this preference * the {@link #getView(View, ViewGroup)} method receives a {@code null} convert view and needs * to recreate the view. Otherwise view gets recycled and only {@link #onBindView(View)} gets * called. * * @param enabled set {@code true} if this preference view should be recycled */ @CallSuper public void setRecycleEnabled(boolean enabled) { mRecycleEnabled = enabled; notifyChanged(); } /** * Checks whether this Preference has enabled to have its view recycled when used in the list * view. * * @see #setRecycleEnabled(boolean) * @return {@code true} if this preference view should be recycled */ public boolean isRecycleEnabled() { return mRecycleEnabled; } /** * Sets whether to constrain the title of this Preference to a single line instead of * letting it wrap onto multiple lines. * * @param singleLineTitle set {@code true} if the title should be constrained to one line */ public void setSingleLineTitle(boolean singleLineTitle) { mHasSingleLineTitleAttr = true; mSingleLineTitle = singleLineTitle; notifyChanged(); } /** * Gets whether the title of this preference is constrained to a single line. * * @see #setSingleLineTitle(boolean) * @return {@code true} if the title of this preference is constrained to a single line */ public boolean isSingleLineTitle() { return mSingleLineTitle; } /** * Sets whether to reserve the space of this Preference icon view when no icon is provided. * * @param iconSpaceReserved set {@code true} if the space for the icon view should be reserved */ public void setIconSpaceReserved(boolean iconSpaceReserved) { mIconSpaceReserved = iconSpaceReserved; notifyChanged(); } /** * Gets whether the space this preference icon view is reserved. * * @see #setIconSpaceReserved(boolean) * @return {@code true} if the space of this preference icon view is reserved */ public boolean isIconSpaceReserved() { return mIconSpaceReserved; } /** * Returns a unique ID for this Preference. This ID should be unique across all * Preference objects 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 this Preference, which is used as a key to the {@link SharedPreferences} or * {@link PreferenceDataStore}. This should be unique for the package. * * @param key The key for the preference. */ public void setKey(String key) { mKey = key; if (mRequiresKey && !hasKey()) { requireKey(); } } /** * Gets the key for this Preference, which is also the key used for storing values into * {@link SharedPreferences} or {@link PreferenceDataStore}. * * @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 persist their preferences. * * @throws IllegalStateException If there is no key assigned. */ void requireKey() { if (mKey == null) { throw new IllegalStateException("Preference does not have a key assigned."); } mRequiresKey = true; } /** * Checks whether this Preference has a valid key. * * @return True if the key exists and is not a blank string, false otherwise. */ public boolean hasKey() { return !TextUtils.isEmpty(mKey); } /** * Checks whether this Preference is persistent. If it is, it stores its value(s) into * the persistent {@link SharedPreferences} storage by default or into * {@link PreferenceDataStore} if assigned. * * @return True if it is persistent. */ public boolean isPersistent() { return mPersistent; } /** * Checks whether, at the given time this method is called, this Preference should store/restore * its value(s) into the {@link SharedPreferences} or into {@link PreferenceDataStore} if * assigned. This, at minimum, checks whether this Preference is persistent and it currently has * a key. Before you save/restore from the storage, check this first. * * @return True if it should persist the value. */ protected boolean shouldPersist() { return mPreferenceManager != null && isPersistent() && hasKey(); } /** * Sets whether this Preference is persistent. When persistent, it stores its value(s) into * the persistent {@link SharedPreferences} storage by default or into * {@link PreferenceDataStore} if assigned. * * @param persistent set {@code true} if it should store its value(s) into the storage. */ 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 this Preference. * @return True if the user value should be set as the preference * value (and persisted). */ protected boolean callChangeListener(Object newValue) { return mOnChangeListener == null || 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; } /** * Returns 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; } /** * Returns 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 A {@link PreferenceScreen} whose hierarchy click * listener should be called in the proper order (between other * processing). May be {@code null}. * @hide */ public 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); } } /** * Allows a Preference to intercept key events without having focus. * For example, SeekBarPreference uses this to intercept +/- to adjust * the progress. * @return True if the Preference handled the key. Returns false by default. * @hide */ public boolean onKey(View v, int keyCode, KeyEvent event) { return false; } /** * Returns the {@link android.content.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 values. * * @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). If * this preference isn't attached to a Preference hierarchy or if * a {@link PreferenceDataStore} has been set, this method returns {@code null}. * @see #getEditor() * @see #setPreferenceDataStore(PreferenceDataStore) */ public SharedPreferences getSharedPreferences() { if (mPreferenceManager == null || getPreferenceDataStore() != 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 SharedPreferences, this is intended behavior to * improve performance. * * @return a {@link SharedPreferences.Editor} where this preference saves its value(s). If * this preference isn't attached to a Preference hierarchy or if * a {@link PreferenceDataStore} has been set, this method returns {@code null}. * @see #shouldCommit() * @see #getSharedPreferences() * @see #setPreferenceDataStore(PreferenceDataStore) */ public SharedPreferences.Editor getEditor() { if (mPreferenceManager == null || getPreferenceDataStore() != 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. * *
If this preference is using {@link PreferenceDataStore} this value is irrelevant. * * @return Whether the Preference should commit its saved value(s). * @see #getEditor() */ public boolean shouldCommit() { if (mPreferenceManager == null) { return false; } return mPreferenceManager.shouldCommit(); } /** * Compares Preference objects based on order (if set), otherwise alphabetically on the titles. * * @param another The Preference to compare to this one. * @return 0 if the same; less than 0 if this Preference sorts ahead of another; * greater than 0 if this Preference sorts after another. */ @Override public int compareTo(Preference another) { if (mOrder != another.mOrder) { // Do order comparison return mOrder - another.mOrder; } else if (mTitle == another.mTitle) { // If titles are null or share same object comparison return 0; } 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 when a 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 object'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 PreferenceManager 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 * 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(); } /** * Assigns a {@link PreferenceGroup} as the parent of this Preference. Set {@code null} to * remove the current parent. * * @param parentGroup Parent preference group of this Preference or {@code null} if none. */ void assignParent(@Nullable PreferenceGroup parentGroup) { mParentGroup = parentGroup; } 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); } } } /** * Finds 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 Preference to find.
* @return The Preference that uses 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 If restorePersistedValue is true, you should restore the
* Preference value from the {@link android.content.SharedPreferences}. If
* restorePersistedValue is false, you should set the Preference
* value to defaultValue that is given (and possibly store to SharedPreferences
* if {@link #shouldPersist()} is true).
*
* In case of using {@link PreferenceDataStore}, the restorePersistedValue is
* always {@code true}. But the default value (if provided) is set.
*
* This may not always be called. One example is if it should not persist
* but there is no default value given.
*
* @param restorePersistedValue True to restore the persisted value;
* false to use the given defaultValue.
* @param defaultValue The default value for this Preference. Only use this
* if restorePersistedValue is false.
*/
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
}
private void tryCommit(SharedPreferences.Editor editor) {
if (mPreferenceManager.shouldCommit()) {
try {
editor.apply();
} catch (AbstractMethodError unused) {
// The app injected its own pre-Gingerbread
// SharedPreferences.Editor implementation without
// an apply method.
editor.commit();
}
}
}
/**
* Attempts to persist a String if this Preference is persistent.
*
* @param value The value to persist.
* @return True if this 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()) {
return false;
}
// Shouldn't store null
if (TextUtils.equals(value, getPersistedString(null))) {
// It's already there, so the same as persisting
return true;
}
PreferenceDataStore dataStore = getPreferenceDataStore();
if (dataStore != null) {
dataStore.putString(mKey, value);
} else {
SharedPreferences.Editor editor = mPreferenceManager.getEditor();
editor.putString(mKey, value);
tryCommit(editor);
}
return true;
}
/**
* Attempts to get a persisted String if this Preference is persistent.
*
* @param defaultReturnValue The default value to return if either this
* Preference is not persistent or this Preference is not present.
* @return The value from the data store or the default return
* value.
* @see #persistString(String)
*/
protected String getPersistedString(String defaultReturnValue) {
if (!shouldPersist()) {
return defaultReturnValue;
}
PreferenceDataStore dataStore = getPreferenceDataStore();
if (dataStore != null) {
return dataStore.getString(mKey, defaultReturnValue);
}
return mPreferenceManager.getSharedPreferences().getString(mKey, defaultReturnValue);
}
/**
* Attempts to persist a set of Strings if this Preference is persistent.
*
* @param values The values to persist.
* @return True if this 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 #getPersistedStringSet(Set)
*/
public boolean persistStringSet(Set
* 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(' ');
}
if (sb.length() > 0) {
// 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 instance of this Preference.
*
* @see #restoreHierarchyState
* @see #onSaveInstanceState
*/
public void saveHierarchyState(Bundle container) {
dispatchSaveInstanceState(container);
}
/**
* Called by {@link #saveHierarchyState} to store the instance for this Preference and its
* children. May be overridden to modify how the save happens for children. For example, some
* Preference objects may want to not store an instance for their children.
*
* @param container The Bundle in which to save the instance of this Preference.
*
* @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 A Parcelable object containing the current dynamic state of this Preference, or
* {@code null} if there is nothing interesting to save. The default implementation
* returns {@code null}.
* @see #onRestoreInstanceState
* @see #saveHierarchyState
*/
protected Parcelable onSaveInstanceState() {
mBaseMethodCalled = true;
return BaseSavedState.EMPTY_STATE;
}
/**
* Restore this Preference hierarchy's previously saved state from the given container.
*
* @param container The Bundle that holds the previously saved state.
*
* @see #saveHierarchyState
* @see #onRestoreInstanceState
*/
public void restoreHierarchyState(Bundle container) {
dispatchRestoreInstanceState(container);
}
/**
* Called by {@link #restoreHierarchyState} to retrieve the saved state for this
* Preference and its children. May be overridden to modify how restoring
* happens to the children of a Preference. For example, some Preference objects may
* not want to save state for their children.
*
* @param container The Bundle that holds the previously saved state.
* @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 {@code null} state.
*
* @param state The saved state that had previously been returned by
* {@link #onSaveInstanceState}.
* @see #onSaveInstanceState
* @see #restoreHierarchyState
*/
protected void onRestoreInstanceState(Parcelable state) {
mBaseMethodCalled = true;
if (state != BaseSavedState.EMPTY_STATE && state != null) {
throw new IllegalArgumentException("Wrong state class -- expecting Preference State");
}
}
/**
* A base class for managing the instance state of a {@link Preference}.
*/
public static class BaseSavedState extends AbsSavedState {
public BaseSavedState(Parcel source) {
super(source);
}
public BaseSavedState(Parcelable superState) {
super(superState);
}
public static final Parcelable.Creator