PreferenceGroupAdapter.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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;
22
23import android.os.Handler;
24import android.preference.Preference.OnPreferenceChangeInternalListener;
25import android.view.View;
26import android.view.ViewGroup;
27import android.widget.Adapter;
28import android.widget.BaseAdapter;
29import android.widget.ListView;
30
31/**
32 * An adapter that returns the {@link Preference} contained in this group.
33 * In most cases, this adapter should be the base class for any custom
34 * adapters from {@link Preference#getAdapter()}.
35 * <p>
36 * This adapter obeys the
37 * {@link Preference}'s adapter rule (the
38 * {@link Adapter#getView(int, View, ViewGroup)} should be used instead of
39 * {@link Preference#getView(ViewGroup)} if a {@link Preference} has an
40 * adapter via {@link Preference#getAdapter()}).
41 * <p>
42 * This adapter also propagates data change/invalidated notifications upward.
43 * <p>
44 * This adapter does not include this {@link PreferenceGroup} in the returned
45 * adapter, use {@link PreferenceCategoryAdapter} instead.
46 *
47 * @see PreferenceCategoryAdapter
48 */
49class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeInternalListener {
50
51    private static final String TAG = "PreferenceGroupAdapter";
52
53    /**
54     * The group that we are providing data from.
55     */
56    private PreferenceGroup mPreferenceGroup;
57
58    /**
59     * Maps a position into this adapter -> {@link Preference}. These
60     * {@link Preference}s don't have to be direct children of this
61     * {@link PreferenceGroup}, they can be grand children or younger)
62     */
63    private List<Preference> mPreferenceList;
64
65    /**
66     * List of unique Preference and its subclasses' names. This is used to find
67     * out how many types of views this adapter can return. Once the count is
68     * returned, this cannot be modified (since the ListView only checks the
69     * count once--when the adapter is being set). We will not recycle views for
70     * Preference subclasses seen after the count has been returned.
71     */
72    private List<String> mPreferenceClassNames;
73
74    /**
75     * Blocks the mPreferenceClassNames from being changed anymore.
76     */
77    private boolean mHasReturnedViewTypeCount = false;
78
79    private volatile boolean mIsSyncing = false;
80
81    private Handler mHandler = new Handler();
82
83    private Runnable mSyncRunnable = new Runnable() {
84        public void run() {
85            syncMyPreferences();
86        }
87    };
88
89    public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
90        mPreferenceGroup = preferenceGroup;
91        mPreferenceList = new ArrayList<Preference>();
92        mPreferenceClassNames = new ArrayList<String>();
93
94        syncMyPreferences();
95    }
96
97    private void syncMyPreferences() {
98        synchronized(this) {
99            if (mIsSyncing) {
100                return;
101            }
102
103            mIsSyncing = true;
104        }
105
106        List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size());
107        flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);
108        mPreferenceList = newPreferenceList;
109
110        notifyDataSetChanged();
111
112        synchronized(this) {
113            mIsSyncing = false;
114            notifyAll();
115        }
116    }
117
118    private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {
119        // TODO: shouldn't always?
120        group.sortPreferences();
121
122        final int groupSize = group.getPreferenceCount();
123        for (int i = 0; i < groupSize; i++) {
124            final Preference preference = group.getPreference(i);
125
126            preferences.add(preference);
127
128            if (!mHasReturnedViewTypeCount) {
129                addPreferenceClassName(preference);
130            }
131
132            if (preference instanceof PreferenceGroup) {
133                final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference;
134                if (preferenceAsGroup.isOnSameScreenAsChildren()) {
135                    flattenPreferenceGroup(preferences, preferenceAsGroup);
136                    preference.setOnPreferenceChangeInternalListener(this);
137                }
138            } else {
139                preference.setOnPreferenceChangeInternalListener(this);
140            }
141        }
142    }
143
144    private void addPreferenceClassName(Preference preference) {
145        final String name = preference.getClass().getName();
146        int insertPos = Collections.binarySearch(mPreferenceClassNames, name);
147
148        // Only insert if it doesn't exist (when it is negative).
149        if (insertPos < 0) {
150            // Convert to insert index
151            insertPos = insertPos * -1 - 1;
152            mPreferenceClassNames.add(insertPos, name);
153        }
154    }
155
156    public int getCount() {
157        return mPreferenceList.size();
158    }
159
160    public Preference getItem(int position) {
161        if (position < 0 || position >= getCount()) return null;
162        return mPreferenceList.get(position);
163    }
164
165    public long getItemId(int position) {
166        if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID;
167        return this.getItem(position).getId();
168    }
169
170    public View getView(int position, View convertView, ViewGroup parent) {
171        final Preference preference = this.getItem(position);
172
173        if (preference.hasSpecifiedLayout()) {
174            // If the preference had specified a layout (as opposed to the
175            // default), don't use convert views.
176            convertView = null;
177        } else {
178            // TODO: better way of doing this
179            final String name = preference.getClass().getName();
180            if (Collections.binarySearch(mPreferenceClassNames, name) < 0) {
181                convertView = null;
182            }
183        }
184
185        return preference.getView(convertView, parent);
186    }
187
188    @Override
189    public boolean isEnabled(int position) {
190        if (position < 0 || position >= getCount()) return true;
191        return this.getItem(position).isSelectable();
192    }
193
194    @Override
195    public boolean areAllItemsEnabled() {
196        // There should always be a preference group, and these groups are always
197        // disabled
198        return false;
199    }
200
201    public void onPreferenceChange(Preference preference) {
202        notifyDataSetChanged();
203    }
204
205    public void onPreferenceHierarchyChange(Preference preference) {
206        mHandler.removeCallbacks(mSyncRunnable);
207        mHandler.post(mSyncRunnable);
208    }
209
210    @Override
211    public boolean hasStableIds() {
212        return true;
213    }
214
215    @Override
216    public int getItemViewType(int position) {
217        if (!mHasReturnedViewTypeCount) {
218            mHasReturnedViewTypeCount = true;
219        }
220
221        final Preference preference = this.getItem(position);
222        if (preference.hasSpecifiedLayout()) {
223            return IGNORE_ITEM_VIEW_TYPE;
224        }
225
226        final String name = preference.getClass().getName();
227        int viewType = Collections.binarySearch(mPreferenceClassNames, name);
228        if (viewType < 0) {
229            // This is a class that was seen after we returned the count, so
230            // don't recycle it.
231            return IGNORE_ITEM_VIEW_TYPE;
232        } else {
233            return viewType;
234        }
235    }
236
237    @Override
238    public int getViewTypeCount() {
239        if (!mHasReturnedViewTypeCount) {
240            mHasReturnedViewTypeCount = true;
241        }
242
243        return mPreferenceClassNames.size();
244    }
245
246}
247