/* * Copyright (C) 2015 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.support.v7.preference; import android.content.Context; import android.content.res.TypedArray; import android.os.Bundle; import android.support.v4.content.res.TypedArrayUtils; import android.text.TextUtils; import android.util.AttributeSet; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A container for multiple * {@link Preference} objects. It is a base class for Preference objects that are * parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}. * * <div class="special reference"> * <h3>Developer Guides</h3> * <p>For information about building a settings UI with Preferences, * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a> * guide.</p> * </div> * * @attr ref android.R.styleable#PreferenceGroup_orderingFromXml */ public abstract class PreferenceGroup extends Preference { /** * The container for child {@link Preference}s. This is sorted based on the * ordering, please use {@link #addPreference(Preference)} instead of adding * to this directly. */ private List<Preference> mPreferenceList; private boolean mOrderingAsAdded = true; private int mCurrentPreferenceOrder = 0; private boolean mAttachedToHierarchy = false; public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); mPreferenceList = new ArrayList<>(); final TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.PreferenceGroup, defStyleAttr, defStyleRes); mOrderingAsAdded = TypedArrayUtils.getBoolean(a, R.styleable.PreferenceGroup_orderingFromXml, R.styleable.PreferenceGroup_orderingFromXml, true); a.recycle(); } public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public PreferenceGroup(Context context, AttributeSet attrs) { this(context, attrs, 0); } /** * Whether to order the {@link Preference} children of this group as they * are added. If this is false, the ordering will follow each Preference * order and default to alphabetic for those without an order. * <p> * If this is called after preferences are added, they will not be * re-ordered in the order they were added, hence call this method early on. * * @param orderingAsAdded Whether to order according to the order added. * @see Preference#setOrder(int) */ public void setOrderingAsAdded(boolean orderingAsAdded) { mOrderingAsAdded = orderingAsAdded; } /** * Whether this group is ordering preferences in the order they are added. * * @return Whether this group orders based on the order the children are added. * @see #setOrderingAsAdded(boolean) */ public boolean isOrderingAsAdded() { return mOrderingAsAdded; } /** * Called by the inflater to add an item to this group. */ public void addItemFromInflater(Preference preference) { addPreference(preference); } /** * Returns the number of children {@link Preference}s. * @return The number of preference children in this group. */ public int getPreferenceCount() { return mPreferenceList.size(); } /** * Returns the {@link Preference} at a particular index. * * @param index The index of the {@link Preference} to retrieve. * @return The {@link Preference}. */ public Preference getPreference(int index) { return mPreferenceList.get(index); } /** * Adds a {@link Preference} at the correct position based on the * preference's order. * * @param preference The preference to add. * @return Whether the preference is now in this group. */ public boolean addPreference(Preference preference) { if (mPreferenceList.contains(preference)) { // Exists return true; } if (preference.getOrder() == DEFAULT_ORDER) { if (mOrderingAsAdded) { preference.setOrder(mCurrentPreferenceOrder++); } if (preference instanceof PreferenceGroup) { // TODO: fix (method is called tail recursively when inflating, // so we won't end up properly passing this flag down to children ((PreferenceGroup)preference).setOrderingAsAdded(mOrderingAsAdded); } } int insertionIndex = Collections.binarySearch(mPreferenceList, preference); if (insertionIndex < 0) { insertionIndex = insertionIndex * -1 - 1; } if (!onPrepareAddPreference(preference)) { return false; } synchronized(this) { mPreferenceList.add(insertionIndex, preference); } preference.onAttachedToHierarchy(getPreferenceManager()); if (mAttachedToHierarchy) { preference.onAttached(); } notifyHierarchyChanged(); return true; } /** * Removes a {@link Preference} from this group. * * @param preference The preference to remove. * @return Whether the preference was found and removed. */ public boolean removePreference(Preference preference) { final boolean returnValue = removePreferenceInt(preference); notifyHierarchyChanged(); return returnValue; } private boolean removePreferenceInt(Preference preference) { synchronized(this) { preference.onPrepareForRemoval(); return mPreferenceList.remove(preference); } } /** * Removes all {@link Preference Preferences} from this group. */ public void removeAll() { synchronized(this) { List<Preference> preferenceList = mPreferenceList; for (int i = preferenceList.size() - 1; i >= 0; i--) { removePreferenceInt(preferenceList.get(0)); } } notifyHierarchyChanged(); } /** * Prepares a {@link Preference} to be added to the group. * * @param preference The preference to add. * @return Whether to allow adding the preference (true), or not (false). */ protected boolean onPrepareAddPreference(Preference preference) { preference.onParentChanged(this, shouldDisableDependents()); return true; } /** * Finds a {@link Preference} based on its key. If two {@link Preference} * share the same key (not recommended), the first to appear will be * returned (to retrieve the other preference with the same key, call this * method on the first preference). If this preference has the key, it will * not be returned. * <p> * This will recursively search for the preference into children that are * also {@link PreferenceGroup PreferenceGroups}. * * @param key The key of the preference to retrieve. * @return The {@link Preference} with the key, or null. */ public Preference findPreference(CharSequence key) { if (TextUtils.equals(getKey(), key)) { return this; } final int preferenceCount = getPreferenceCount(); for (int i = 0; i < preferenceCount; i++) { final Preference preference = getPreference(i); final String curKey = preference.getKey(); if (curKey != null && curKey.equals(key)) { return preference; } if (preference instanceof PreferenceGroup) { final Preference returnedPreference = ((PreferenceGroup)preference) .findPreference(key); if (returnedPreference != null) { return returnedPreference; } } } return null; } /** * Whether this preference group should be shown on the same screen as its * contained preferences. * * @return True if the contained preferences should be shown on the same * screen as this preference. */ protected boolean isOnSameScreenAsChildren() { return true; } @Override public void onAttached() { super.onAttached(); // Mark as attached so if a preference is later added to this group, we // can tell it we are already attached mAttachedToHierarchy = true; // Dispatch to all contained preferences final int preferenceCount = getPreferenceCount(); for (int i = 0; i < preferenceCount; i++) { getPreference(i).onAttached(); } } @Override protected void onPrepareForRemoval() { super.onPrepareForRemoval(); // We won't be attached to the activity anymore mAttachedToHierarchy = false; } @Override public void notifyDependencyChange(boolean disableDependents) { super.notifyDependencyChange(disableDependents); // Child preferences have an implicit dependency on their containing // group. Dispatch dependency change to all contained preferences. final int preferenceCount = getPreferenceCount(); for (int i = 0; i < preferenceCount; i++) { getPreference(i).onParentChanged(this, disableDependents); } } void sortPreferences() { synchronized (this) { Collections.sort(mPreferenceList); } } @Override protected void dispatchSaveInstanceState(Bundle container) { super.dispatchSaveInstanceState(container); // Dispatch to all contained preferences final int preferenceCount = getPreferenceCount(); for (int i = 0; i < preferenceCount; i++) { getPreference(i).dispatchSaveInstanceState(container); } } @Override protected void dispatchRestoreInstanceState(Bundle container) { super.dispatchRestoreInstanceState(container); // Dispatch to all contained preferences final int preferenceCount = getPreferenceCount(); for (int i = 0; i < preferenceCount; i++) { getPreference(i).dispatchRestoreInstanceState(container); } } }