/* * Copyright 2018 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 androidx.preference; import static androidx.annotation.RestrictTo.Scope.LIBRARY; import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX; import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; import androidx.collection.SimpleArrayMap; import androidx.core.content.res.TypedArrayUtils; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A container for multiple {@link Preference}s. It is a base class for preference * objects that are parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}. * *
For information about building a settings screen using the AndroidX Preference library, see * Settings.
*If this is called after preferences are added, they will not be re-ordered in the * order they were added, hence call this method early on. * * @param orderingAsAdded Whether to order according to the order added * @see Preference#setOrder(int) */ public void setOrderingAsAdded(boolean orderingAsAdded) { mOrderingAsAdded = orderingAsAdded; } /** * Whether this group is ordering preferences in the order they are added. * * @return Whether this group orders based on the order the children are added * @see #setOrderingAsAdded(boolean) */ public boolean isOrderingAsAdded() { return mOrderingAsAdded; } /** * Sets the maximal number of children that are shown when the preference group is launched * where the rest of the children will be hidden. If some children are hidden an expand * button will be provided to show all the hidden children. Any child in any level of the * hierarchy that is also a preference group (e.g. preference category) will not be counted * towards the limit. But instead the children of such group will be counted. By default, all * children will be shown, so the default value of this attribute is equal to Integer.MAX_VALUE. * *
Note: The group should have a key defined if an expandable preference is present to * correctly persist state. * * @param expandedCount The number of children that is initially shown * {@link androidx.preference.R.attr#initialExpandedChildrenCount} */ public void setInitialExpandedChildrenCount(int expandedCount) { if (expandedCount != Integer.MAX_VALUE && !hasKey()) { Log.e(TAG, getClass().getSimpleName() + " should have a key defined if it contains an expandable preference"); } mInitialExpandedChildrenCount = expandedCount; } /** * Gets the maximal number of children that are initially shown. * * @return The maximal number of children that are initially shown * {@link androidx.preference.R.attr#initialExpandedChildrenCount} */ public int getInitialExpandedChildrenCount() { return mInitialExpandedChildrenCount; } /** * Called by the inflater to add an item to this group. */ public void addItemFromInflater(Preference preference) { addPreference(preference); } /** * Returns the number of children {@link Preference}s. * * @return The number of preference children in this group */ public int getPreferenceCount() { return mPreferences.size(); } /** * Returns the {@link Preference} at a particular index. * * @param index The index of the {@link Preference} to retrieve * @return The {@link Preference} */ public Preference getPreference(int index) { return mPreferences.get(index); } /** * Adds a {@link Preference} at the correct position based on the preference's order. * * @param preference The preference to add * @return Whether the preference is now in this group */ public boolean addPreference(Preference preference) { if (mPreferences.contains(preference)) { return true; } if (preference.getKey() != null) { PreferenceGroup root = this; while (root.getParent() != null) { root = root.getParent(); } final String key = preference.getKey(); if (root.findPreference(key) != null) { Log.e(TAG, "Found duplicated key: \"" + key + "\". This can cause unintended behaviour," + " please use unique keys for every preference."); } } if (preference.getOrder() == DEFAULT_ORDER) { if (mOrderingAsAdded) { preference.setOrder(mCurrentPreferenceOrder++); } if (preference instanceof PreferenceGroup) { // TODO: fix (method is called tail recursively when inflating, // so we won't end up properly passing this flag down to children ((PreferenceGroup) preference).setOrderingAsAdded(mOrderingAsAdded); } } int insertionIndex = Collections.binarySearch(mPreferences, preference); if (insertionIndex < 0) { insertionIndex = insertionIndex * -1 - 1; } if (!onPrepareAddPreference(preference)) { return false; } synchronized (this) { mPreferences.add(insertionIndex, preference); } final PreferenceManager preferenceManager = getPreferenceManager(); final String key = preference.getKey(); final long id; if (key != null && mIdRecycleCache.containsKey(key)) { id = mIdRecycleCache.get(key); mIdRecycleCache.remove(key); } else { id = preferenceManager.getNextId(); } preference.onAttachedToHierarchy(preferenceManager, id); preference.assignParent(this); if (mAttachedToHierarchy) { preference.onAttached(); } notifyHierarchyChanged(); return true; } /** * Removes a {@link Preference} from this group. * *
Note: This action is not recursive, and will only remove a preference if it exists in
* this group, ignoring preferences found in nested groups. Use
* {@link #removePreferenceRecursively(CharSequence)} to recursively find and remove a
* preference.
*
* @param preference The preference to remove
* @return Whether the preference was found and removed
* @see #removePreferenceRecursively(CharSequence)
*/
public boolean removePreference(Preference preference) {
final boolean returnValue = removePreferenceInt(preference);
notifyHierarchyChanged();
return returnValue;
}
/**
* Recursively finds and removes a {@link Preference} from this group or a nested group lower
* down in the hierarchy. If two {@link Preference}s share the same key (not recommended),
* the first to appear will be removed.
*
* @param key The key of the preference to remove
* @return Whether the preference was found and removed
* @see #findPreference(CharSequence)
*/
public boolean removePreferenceRecursively(@NonNull CharSequence key) {
final Preference preference = findPreference(key);
if (preference == null) {
return false;
}
return preference.getParent().removePreference(preference);
}
private boolean removePreferenceInt(Preference preference) {
synchronized (this) {
preference.onPrepareForRemoval();
if (preference.getParent() == this) {
preference.assignParent(null);
}
boolean success = mPreferences.remove(preference);
if (success) {
// If this preference, or another preference with the same key, gets re-added
// immediately, we want it to have the same id so that it can be correctly tracked
// in the adapter by RecyclerView, to make it appear as if it has only been
// seamlessly updated. If the preference is not re-added by the time the handler
// runs, we take that as a signal that the preference will not be re-added soon
// in which case it does not need to retain the same id.
// If two (or more) preferences have the same (or null) key and both are removed
// and then re-added, only one id will be recycled and the second (and later)
// preferences will receive a newly generated id. This use pattern of the preference
// API is strongly discouraged.
final String key = preference.getKey();
if (key != null) {
mIdRecycleCache.put(key, preference.getId());
mHandler.removeCallbacks(mClearRecycleCacheRunnable);
mHandler.post(mClearRecycleCacheRunnable);
}
if (mAttachedToHierarchy) {
preference.onDetached();
}
}
return success;
}
}
/**
* Removes all {@link Preference}s from this group.
*/
public void removeAll() {
synchronized (this) {
List This will recursively search for the {@link Preference} in any children that are also
* {@link PreferenceGroup}s.
*
* @param key The key of the {@link Preference} to retrieve
* @return The {@link Preference} with the key, or {@code null}
*/
@SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
@Nullable
public