• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package android.support.v7.preference;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.os.Bundle;
22 import android.support.v4.content.res.TypedArrayUtils;
23 import android.text.TextUtils;
24 import android.util.AttributeSet;
25 
26 import java.util.ArrayList;
27 import java.util.Collections;
28 import java.util.List;
29 
30 /**
31  * A container for multiple
32  * {@link Preference} objects. It is a base class for  Preference objects that are
33  * parents, such as {@link PreferenceCategory} and {@link PreferenceScreen}.
34  *
35  * <div class="special reference">
36  * <h3>Developer Guides</h3>
37  * <p>For information about building a settings UI with Preferences,
38  * read the <a href="{@docRoot}guide/topics/ui/settings.html">Settings</a>
39  * guide.</p>
40  * </div>
41  *
42  * @attr ref android.R.styleable#PreferenceGroup_orderingFromXml
43  */
44 public abstract class PreferenceGroup extends Preference {
45     /**
46      * The container for child {@link Preference}s. This is sorted based on the
47      * ordering, please use {@link #addPreference(Preference)} instead of adding
48      * to this directly.
49      */
50     private List<Preference> mPreferenceList;
51 
52     private boolean mOrderingAsAdded = true;
53 
54     private int mCurrentPreferenceOrder = 0;
55 
56     private boolean mAttachedToHierarchy = false;
57 
PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)58     public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
59         super(context, attrs, defStyleAttr, defStyleRes);
60 
61         mPreferenceList = new ArrayList<>();
62 
63         final TypedArray a = context.obtainStyledAttributes(
64                 attrs, R.styleable.PreferenceGroup, defStyleAttr, defStyleRes);
65 
66         mOrderingAsAdded =
67                 TypedArrayUtils.getBoolean(a, R.styleable.PreferenceGroup_orderingFromXml,
68                         R.styleable.PreferenceGroup_orderingFromXml, true);
69 
70         a.recycle();
71     }
72 
PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr)73     public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr) {
74         this(context, attrs, defStyleAttr, 0);
75     }
76 
PreferenceGroup(Context context, AttributeSet attrs)77     public PreferenceGroup(Context context, AttributeSet attrs) {
78         this(context, attrs, 0);
79     }
80 
81     /**
82      * Whether to order the {@link Preference} children of this group as they
83      * are added. If this is false, the ordering will follow each Preference
84      * order and default to alphabetic for those without an order.
85      * <p>
86      * If this is called after preferences are added, they will not be
87      * re-ordered in the order they were added, hence call this method early on.
88      *
89      * @param orderingAsAdded Whether to order according to the order added.
90      * @see Preference#setOrder(int)
91      */
setOrderingAsAdded(boolean orderingAsAdded)92     public void setOrderingAsAdded(boolean orderingAsAdded) {
93         mOrderingAsAdded = orderingAsAdded;
94     }
95 
96     /**
97      * Whether this group is ordering preferences in the order they are added.
98      *
99      * @return Whether this group orders based on the order the children are added.
100      * @see #setOrderingAsAdded(boolean)
101      */
isOrderingAsAdded()102     public boolean isOrderingAsAdded() {
103         return mOrderingAsAdded;
104     }
105 
106     /**
107      * Called by the inflater to add an item to this group.
108      */
addItemFromInflater(Preference preference)109     public void addItemFromInflater(Preference preference) {
110         addPreference(preference);
111     }
112 
113     /**
114      * Returns the number of children {@link Preference}s.
115      * @return The number of preference children in this group.
116      */
getPreferenceCount()117     public int getPreferenceCount() {
118         return mPreferenceList.size();
119     }
120 
121     /**
122      * Returns the {@link Preference} at a particular index.
123      *
124      * @param index The index of the {@link Preference} to retrieve.
125      * @return The {@link Preference}.
126      */
getPreference(int index)127     public Preference getPreference(int index) {
128         return mPreferenceList.get(index);
129     }
130 
131     /**
132      * Adds a {@link Preference} at the correct position based on the
133      * preference's order.
134      *
135      * @param preference The preference to add.
136      * @return Whether the preference is now in this group.
137      */
addPreference(Preference preference)138     public boolean addPreference(Preference preference) {
139         if (mPreferenceList.contains(preference)) {
140             // Exists
141             return true;
142         }
143 
144         if (preference.getOrder() == DEFAULT_ORDER) {
145             if (mOrderingAsAdded) {
146                 preference.setOrder(mCurrentPreferenceOrder++);
147             }
148 
149             if (preference instanceof PreferenceGroup) {
150                 // TODO: fix (method is called tail recursively when inflating,
151                 // so we won't end up properly passing this flag down to children
152                 ((PreferenceGroup)preference).setOrderingAsAdded(mOrderingAsAdded);
153             }
154         }
155 
156         int insertionIndex = Collections.binarySearch(mPreferenceList, preference);
157         if (insertionIndex < 0) {
158             insertionIndex = insertionIndex * -1 - 1;
159         }
160 
161         if (!onPrepareAddPreference(preference)) {
162             return false;
163         }
164 
165         synchronized(this) {
166             mPreferenceList.add(insertionIndex, preference);
167         }
168 
169         preference.onAttachedToHierarchy(getPreferenceManager());
170 
171         if (mAttachedToHierarchy) {
172             preference.onAttached();
173         }
174 
175         notifyHierarchyChanged();
176 
177         return true;
178     }
179 
180     /**
181      * Removes a {@link Preference} from this group.
182      *
183      * @param preference The preference to remove.
184      * @return Whether the preference was found and removed.
185      */
removePreference(Preference preference)186     public boolean removePreference(Preference preference) {
187         final boolean returnValue = removePreferenceInt(preference);
188         notifyHierarchyChanged();
189         return returnValue;
190     }
191 
removePreferenceInt(Preference preference)192     private boolean removePreferenceInt(Preference preference) {
193         synchronized(this) {
194             preference.onPrepareForRemoval();
195             return mPreferenceList.remove(preference);
196         }
197     }
198 
199     /**
200      * Removes all {@link Preference Preferences} from this group.
201      */
removeAll()202     public void removeAll() {
203         synchronized(this) {
204             List<Preference> preferenceList = mPreferenceList;
205             for (int i = preferenceList.size() - 1; i >= 0; i--) {
206                 removePreferenceInt(preferenceList.get(0));
207             }
208         }
209         notifyHierarchyChanged();
210     }
211 
212     /**
213      * Prepares a {@link Preference} to be added to the group.
214      *
215      * @param preference The preference to add.
216      * @return Whether to allow adding the preference (true), or not (false).
217      */
onPrepareAddPreference(Preference preference)218     protected boolean onPrepareAddPreference(Preference preference) {
219         preference.onParentChanged(this, shouldDisableDependents());
220         return true;
221     }
222 
223     /**
224      * Finds a {@link Preference} based on its key. If two {@link Preference}
225      * share the same key (not recommended), the first to appear will be
226      * returned (to retrieve the other preference with the same key, call this
227      * method on the first preference). If this preference has the key, it will
228      * not be returned.
229      * <p>
230      * This will recursively search for the preference into children that are
231      * also {@link PreferenceGroup PreferenceGroups}.
232      *
233      * @param key The key of the preference to retrieve.
234      * @return The {@link Preference} with the key, or null.
235      */
findPreference(CharSequence key)236     public Preference findPreference(CharSequence key) {
237         if (TextUtils.equals(getKey(), key)) {
238             return this;
239         }
240         final int preferenceCount = getPreferenceCount();
241         for (int i = 0; i < preferenceCount; i++) {
242             final Preference preference = getPreference(i);
243             final String curKey = preference.getKey();
244 
245             if (curKey != null && curKey.equals(key)) {
246                 return preference;
247             }
248 
249             if (preference instanceof PreferenceGroup) {
250                 final Preference returnedPreference = ((PreferenceGroup)preference)
251                         .findPreference(key);
252                 if (returnedPreference != null) {
253                     return returnedPreference;
254                 }
255             }
256         }
257 
258         return null;
259     }
260 
261     /**
262      * Whether this preference group should be shown on the same screen as its
263      * contained preferences.
264      *
265      * @return True if the contained preferences should be shown on the same
266      *         screen as this preference.
267      */
isOnSameScreenAsChildren()268     protected boolean isOnSameScreenAsChildren() {
269         return true;
270     }
271 
272     @Override
onAttached()273     public void onAttached() {
274         super.onAttached();
275 
276         // Mark as attached so if a preference is later added to this group, we
277         // can tell it we are already attached
278         mAttachedToHierarchy = true;
279 
280         // Dispatch to all contained preferences
281         final int preferenceCount = getPreferenceCount();
282         for (int i = 0; i < preferenceCount; i++) {
283             getPreference(i).onAttached();
284         }
285     }
286 
287     @Override
onPrepareForRemoval()288     protected void onPrepareForRemoval() {
289         super.onPrepareForRemoval();
290 
291         // We won't be attached to the activity anymore
292         mAttachedToHierarchy = false;
293     }
294 
295     @Override
notifyDependencyChange(boolean disableDependents)296     public void notifyDependencyChange(boolean disableDependents) {
297         super.notifyDependencyChange(disableDependents);
298 
299         // Child preferences have an implicit dependency on their containing
300         // group. Dispatch dependency change to all contained preferences.
301         final int preferenceCount = getPreferenceCount();
302         for (int i = 0; i < preferenceCount; i++) {
303             getPreference(i).onParentChanged(this, disableDependents);
304         }
305     }
306 
sortPreferences()307     void sortPreferences() {
308         synchronized (this) {
309             Collections.sort(mPreferenceList);
310         }
311     }
312 
313     @Override
dispatchSaveInstanceState(Bundle container)314     protected void dispatchSaveInstanceState(Bundle container) {
315         super.dispatchSaveInstanceState(container);
316 
317         // Dispatch to all contained preferences
318         final int preferenceCount = getPreferenceCount();
319         for (int i = 0; i < preferenceCount; i++) {
320             getPreference(i).dispatchSaveInstanceState(container);
321         }
322     }
323 
324     @Override
dispatchRestoreInstanceState(Bundle container)325     protected void dispatchRestoreInstanceState(Bundle container) {
326         super.dispatchRestoreInstanceState(container);
327 
328         // Dispatch to all contained preferences
329         final int preferenceCount = getPreferenceCount();
330         for (int i = 0; i < preferenceCount; i++) {
331             getPreference(i).dispatchRestoreInstanceState(container);
332         }
333     }
334 
335 }
336