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