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