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