PreferenceGroupAdapter.java revision da996f390e17e16f2dfa60e972e7ebc4f868f37e
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        // If this group gets or loses any children, let us know
92        mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
93
94        mPreferenceList = new ArrayList<Preference>();
95        mPreferenceClassNames = new ArrayList<String>();
96
97        syncMyPreferences();
98    }
99
100    private void syncMyPreferences() {
101        synchronized(this) {
102            if (mIsSyncing) {
103                return;
104            }
105
106            mIsSyncing = true;
107        }
108
109        List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size());
110        flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);
111        mPreferenceList = newPreferenceList;
112
113        notifyDataSetChanged();
114
115        synchronized(this) {
116            mIsSyncing = false;
117            notifyAll();
118        }
119    }
120
121    private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {
122        // TODO: shouldn't always?
123        group.sortPreferences();
124
125        final int groupSize = group.getPreferenceCount();
126        for (int i = 0; i < groupSize; i++) {
127            final Preference preference = group.getPreference(i);
128
129            preferences.add(preference);
130
131            if (!mHasReturnedViewTypeCount) {
132                addPreferenceClassName(preference);
133            }
134
135            if (preference instanceof PreferenceGroup) {
136                final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference;
137                if (preferenceAsGroup.isOnSameScreenAsChildren()) {
138                    flattenPreferenceGroup(preferences, preferenceAsGroup);
139                }
140            }
141
142            preference.setOnPreferenceChangeInternalListener(this);
143        }
144    }
145
146    private void addPreferenceClassName(Preference preference) {
147        final String name = preference.getClass().getName();
148        int insertPos = Collections.binarySearch(mPreferenceClassNames, name);
149
150        // Only insert if it doesn't exist (when it is negative).
151        if (insertPos < 0) {
152            // Convert to insert index
153            insertPos = insertPos * -1 - 1;
154            mPreferenceClassNames.add(insertPos, name);
155        }
156    }
157
158    public int getCount() {
159        return mPreferenceList.size();
160    }
161
162    public Preference getItem(int position) {
163        if (position < 0 || position >= getCount()) return null;
164        return mPreferenceList.get(position);
165    }
166
167    public long getItemId(int position) {
168        if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID;
169        return this.getItem(position).getId();
170    }
171
172    public View getView(int position, View convertView, ViewGroup parent) {
173        final Preference preference = this.getItem(position);
174
175        if (preference.hasSpecifiedLayout()) {
176            // If the preference had specified a layout (as opposed to the
177            // default), don't use convert views.
178            convertView = null;
179        } else {
180            // TODO: better way of doing this
181            final String name = preference.getClass().getName();
182            if (Collections.binarySearch(mPreferenceClassNames, name) < 0) {
183                convertView = null;
184            }
185        }
186
187        return preference.getView(convertView, parent);
188    }
189
190    @Override
191    public boolean isEnabled(int position) {
192        if (position < 0 || position >= getCount()) return true;
193        return this.getItem(position).isSelectable();
194    }
195
196    @Override
197    public boolean areAllItemsEnabled() {
198        // There should always be a preference group, and these groups are always
199        // disabled
200        return false;
201    }
202
203    public void onPreferenceChange(Preference preference) {
204        notifyDataSetChanged();
205    }
206
207    public void onPreferenceHierarchyChange(Preference preference) {
208        mHandler.removeCallbacks(mSyncRunnable);
209        mHandler.post(mSyncRunnable);
210    }
211
212    @Override
213    public boolean hasStableIds() {
214        return true;
215    }
216
217    @Override
218    public int getItemViewType(int position) {
219        if (!mHasReturnedViewTypeCount) {
220            mHasReturnedViewTypeCount = true;
221        }
222
223        final Preference preference = this.getItem(position);
224        if (preference.hasSpecifiedLayout()) {
225            return IGNORE_ITEM_VIEW_TYPE;
226        }
227
228        final String name = preference.getClass().getName();
229        int viewType = Collections.binarySearch(mPreferenceClassNames, name);
230        if (viewType < 0) {
231            // This is a class that was seen after we returned the count, so
232            // don't recycle it.
233            return IGNORE_ITEM_VIEW_TYPE;
234        } else {
235            return viewType;
236        }
237    }
238
239    @Override
240    public int getViewTypeCount() {
241        if (!mHasReturnedViewTypeCount) {
242            mHasReturnedViewTypeCount = true;
243        }
244
245        return mPreferenceClassNames.size();
246    }
247
248}
249