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