PreferenceGroupAdapter.java revision 8a86d734e53dcbafd86c5cc28679323798ecd8a5
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.graphics.drawable.Drawable;
24import android.os.Handler;
25import android.preference.Preference.OnPreferenceChangeInternalListener;
26import android.view.View;
27import android.view.ViewGroup;
28import android.widget.Adapter;
29import android.widget.BaseAdapter;
30import android.widget.ListView;
31
32/**
33 * An adapter that returns the {@link Preference} contained in this group.
34 * In most cases, this adapter should be the base class for any custom
35 * adapters from {@link Preference#getAdapter()}.
36 * <p>
37 * This adapter obeys the
38 * {@link Preference}'s adapter rule (the
39 * {@link Adapter#getView(int, View, ViewGroup)} should be used instead of
40 * {@link Preference#getView(ViewGroup)} if a {@link Preference} has an
41 * adapter via {@link Preference#getAdapter()}).
42 * <p>
43 * This adapter also propagates data change/invalidated notifications upward.
44 * <p>
45 * This adapter does not include this {@link PreferenceGroup} in the returned
46 * adapter, use {@link PreferenceCategoryAdapter} instead.
47 *
48 * @see PreferenceCategoryAdapter
49 *
50 * @hide
51 */
52public class PreferenceGroupAdapter extends BaseAdapter
53        implements OnPreferenceChangeInternalListener {
54
55    private static final String TAG = "PreferenceGroupAdapter";
56
57    /**
58     * The group that we are providing data from.
59     */
60    private PreferenceGroup mPreferenceGroup;
61
62    /**
63     * Maps a position into this adapter -> {@link Preference}. These
64     * {@link Preference}s don't have to be direct children of this
65     * {@link PreferenceGroup}, they can be grand children or younger)
66     */
67    private List<Preference> mPreferenceList;
68
69    /**
70     * List of unique Preference and its subclasses' names. This is used to find
71     * out how many types of views this adapter can return. Once the count is
72     * returned, this cannot be modified (since the ListView only checks the
73     * count once--when the adapter is being set). We will not recycle views for
74     * Preference subclasses seen after the count has been returned.
75     */
76    private ArrayList<PreferenceLayout> mPreferenceLayouts;
77
78    private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
79
80    /**
81     * Blocks the mPreferenceClassNames from being changed anymore.
82     */
83    private boolean mHasReturnedViewTypeCount = false;
84
85    private volatile boolean mIsSyncing = false;
86
87    private Handler mHandler = new Handler();
88
89    private Runnable mSyncRunnable = new Runnable() {
90        public void run() {
91            syncMyPreferences();
92        }
93    };
94
95    private int mHighlightedPosition = -1;
96    private Drawable mHighlightedDrawable;
97
98    private static class PreferenceLayout implements Comparable<PreferenceLayout> {
99        private int resId;
100        private int widgetResId;
101        private String name;
102
103        public int compareTo(PreferenceLayout other) {
104            int compareNames = name.compareTo(other.name);
105            if (compareNames == 0) {
106                if (resId == other.resId) {
107                    if (widgetResId == other.widgetResId) {
108                        return 0;
109                    } else {
110                        return widgetResId - other.widgetResId;
111                    }
112                } else {
113                    return resId - other.resId;
114                }
115            } else {
116                return compareNames;
117            }
118        }
119    }
120
121    public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
122        mPreferenceGroup = preferenceGroup;
123        // If this group gets or loses any children, let us know
124        mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
125
126        mPreferenceList = new ArrayList<Preference>();
127        mPreferenceLayouts = new ArrayList<PreferenceLayout>();
128
129        syncMyPreferences();
130    }
131
132    private void syncMyPreferences() {
133        synchronized(this) {
134            if (mIsSyncing) {
135                return;
136            }
137
138            mIsSyncing = true;
139        }
140
141        List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size());
142        flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);
143        mPreferenceList = newPreferenceList;
144
145        notifyDataSetChanged();
146
147        synchronized(this) {
148            mIsSyncing = false;
149            notifyAll();
150        }
151    }
152
153    private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {
154        // TODO: shouldn't always?
155        group.sortPreferences();
156
157        final int groupSize = group.getPreferenceCount();
158        for (int i = 0; i < groupSize; i++) {
159            final Preference preference = group.getPreference(i);
160
161            preferences.add(preference);
162
163            if (!mHasReturnedViewTypeCount && preference.canRecycleLayout()) {
164                addPreferenceClassName(preference);
165            }
166
167            if (preference instanceof PreferenceGroup) {
168                final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference;
169                if (preferenceAsGroup.isOnSameScreenAsChildren()) {
170                    flattenPreferenceGroup(preferences, preferenceAsGroup);
171                }
172            }
173
174            preference.setOnPreferenceChangeInternalListener(this);
175        }
176    }
177
178    /**
179     * Creates a string that includes the preference name, layout id and widget layout id.
180     * If a particular preference type uses 2 different resources, they will be treated as
181     * different view types.
182     */
183    private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {
184        PreferenceLayout pl = in != null? in : new PreferenceLayout();
185        pl.name = preference.getClass().getName();
186        pl.resId = preference.getLayoutResource();
187        pl.widgetResId = preference.getWidgetLayoutResource();
188        return pl;
189    }
190
191    private void addPreferenceClassName(Preference preference) {
192        final PreferenceLayout pl = createPreferenceLayout(preference, null);
193        int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);
194
195        // Only insert if it doesn't exist (when it is negative).
196        if (insertPos < 0) {
197            // Convert to insert index
198            insertPos = insertPos * -1 - 1;
199            mPreferenceLayouts.add(insertPos, pl);
200        }
201    }
202
203    public int getCount() {
204        return mPreferenceList.size();
205    }
206
207    public Preference getItem(int position) {
208        if (position < 0 || position >= getCount()) return null;
209        return mPreferenceList.get(position);
210    }
211
212    public long getItemId(int position) {
213        if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID;
214        return this.getItem(position).getId();
215    }
216
217    /**
218     * @hide
219     */
220    public void setHighlighted(int position) {
221        mHighlightedPosition = position;
222    }
223
224    /**
225     * @hide
226     */
227    public void setHighlightedDrawable(Drawable drawable) {
228        mHighlightedDrawable = drawable;
229    }
230
231    public View getView(int position, View convertView, ViewGroup parent) {
232        final Preference preference = this.getItem(position);
233        // Build a PreferenceLayout to compare with known ones that are cacheable.
234        mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
235
236        // If it's not one of the cached ones, set the convertView to null so that
237        // the layout gets re-created by the Preference.
238        if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
239            convertView = null;
240        }
241        View result = preference.getView(convertView, parent);
242        if (position == mHighlightedPosition && mHighlightedDrawable != null) {
243            result.setBackgroundDrawable(mHighlightedDrawable);
244        }
245        result.setTag(preference.getKey());
246        return result;
247    }
248
249    @Override
250    public boolean isEnabled(int position) {
251        if (position < 0 || position >= getCount()) return true;
252        return this.getItem(position).isSelectable();
253    }
254
255    @Override
256    public boolean areAllItemsEnabled() {
257        // There should always be a preference group, and these groups are always
258        // disabled
259        return false;
260    }
261
262    public void onPreferenceChange(Preference preference) {
263        notifyDataSetChanged();
264    }
265
266    public void onPreferenceHierarchyChange(Preference preference) {
267        mHandler.removeCallbacks(mSyncRunnable);
268        mHandler.post(mSyncRunnable);
269    }
270
271    @Override
272    public boolean hasStableIds() {
273        return true;
274    }
275
276    @Override
277    public int getItemViewType(int position) {
278        if (!mHasReturnedViewTypeCount) {
279            mHasReturnedViewTypeCount = true;
280        }
281
282        final Preference preference = this.getItem(position);
283        if (!preference.canRecycleLayout()) {
284            return IGNORE_ITEM_VIEW_TYPE;
285        }
286
287        mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
288
289        int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout);
290        if (viewType < 0) {
291            // This is a class that was seen after we returned the count, so
292            // don't recycle it.
293            return IGNORE_ITEM_VIEW_TYPE;
294        } else {
295            return viewType;
296        }
297    }
298
299    @Override
300    public int getViewTypeCount() {
301        if (!mHasReturnedViewTypeCount) {
302            mHasReturnedViewTypeCount = true;
303        }
304
305        return Math.max(1, mPreferenceLayouts.size());
306    }
307
308}
309