• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.preference;
18 
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22 
23 import android.os.Handler;
24 import android.preference.Preference.OnPreferenceChangeInternalListener;
25 import android.view.View;
26 import android.view.ViewGroup;
27 import android.widget.Adapter;
28 import android.widget.BaseAdapter;
29 import 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  */
49 class 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 ArrayList<PreferenceLayout> mPreferenceLayouts;
73 
74     private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
75 
76     /**
77      * Blocks the mPreferenceClassNames from being changed anymore.
78      */
79     private boolean mHasReturnedViewTypeCount = false;
80 
81     private volatile boolean mIsSyncing = false;
82 
83     private Handler mHandler = new Handler();
84 
85     private Runnable mSyncRunnable = new Runnable() {
86         public void run() {
87             syncMyPreferences();
88         }
89     };
90 
91     private static class PreferenceLayout implements Comparable<PreferenceLayout> {
92         private int resId;
93         private int widgetResId;
94         private String name;
95 
compareTo(PreferenceLayout other)96         public int compareTo(PreferenceLayout other) {
97             int compareNames = name.compareTo(other.name);
98             if (compareNames == 0) {
99                 if (resId == other.resId) {
100                     if (widgetResId == other.widgetResId) {
101                         return 0;
102                     } else {
103                         return widgetResId - other.widgetResId;
104                     }
105                 } else {
106                     return resId - other.resId;
107                 }
108             } else {
109                 return compareNames;
110             }
111         }
112     }
113 
PreferenceGroupAdapter(PreferenceGroup preferenceGroup)114     public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) {
115         mPreferenceGroup = preferenceGroup;
116         // If this group gets or loses any children, let us know
117         mPreferenceGroup.setOnPreferenceChangeInternalListener(this);
118 
119         mPreferenceList = new ArrayList<Preference>();
120         mPreferenceLayouts = new ArrayList<PreferenceLayout>();
121 
122         syncMyPreferences();
123     }
124 
syncMyPreferences()125     private void syncMyPreferences() {
126         synchronized(this) {
127             if (mIsSyncing) {
128                 return;
129             }
130 
131             mIsSyncing = true;
132         }
133 
134         List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size());
135         flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);
136         mPreferenceList = newPreferenceList;
137 
138         notifyDataSetChanged();
139 
140         synchronized(this) {
141             mIsSyncing = false;
142             notifyAll();
143         }
144     }
145 
flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group)146     private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {
147         // TODO: shouldn't always?
148         group.sortPreferences();
149 
150         final int groupSize = group.getPreferenceCount();
151         for (int i = 0; i < groupSize; i++) {
152             final Preference preference = group.getPreference(i);
153 
154             preferences.add(preference);
155 
156             if (!mHasReturnedViewTypeCount && !preference.hasSpecifiedLayout()) {
157                 addPreferenceClassName(preference);
158             }
159 
160             if (preference instanceof PreferenceGroup) {
161                 final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference;
162                 if (preferenceAsGroup.isOnSameScreenAsChildren()) {
163                     flattenPreferenceGroup(preferences, preferenceAsGroup);
164                 }
165             }
166 
167             preference.setOnPreferenceChangeInternalListener(this);
168         }
169     }
170 
171     /**
172      * Creates a string that includes the preference name, layout id and widget layout id.
173      * If a particular preference type uses 2 different resources, they will be treated as
174      * different view types.
175      */
createPreferenceLayout(Preference preference, PreferenceLayout in)176     private PreferenceLayout createPreferenceLayout(Preference preference, PreferenceLayout in) {
177         PreferenceLayout pl = in != null? in : new PreferenceLayout();
178         pl.name = preference.getClass().getName();
179         pl.resId = preference.getLayoutResource();
180         pl.widgetResId = preference.getWidgetLayoutResource();
181         return pl;
182     }
183 
addPreferenceClassName(Preference preference)184     private void addPreferenceClassName(Preference preference) {
185         final PreferenceLayout pl = createPreferenceLayout(preference, null);
186         int insertPos = Collections.binarySearch(mPreferenceLayouts, pl);
187 
188         // Only insert if it doesn't exist (when it is negative).
189         if (insertPos < 0) {
190             // Convert to insert index
191             insertPos = insertPos * -1 - 1;
192             mPreferenceLayouts.add(insertPos, pl);
193         }
194     }
195 
getCount()196     public int getCount() {
197         return mPreferenceList.size();
198     }
199 
getItem(int position)200     public Preference getItem(int position) {
201         if (position < 0 || position >= getCount()) return null;
202         return mPreferenceList.get(position);
203     }
204 
getItemId(int position)205     public long getItemId(int position) {
206         if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID;
207         return this.getItem(position).getId();
208     }
209 
getView(int position, View convertView, ViewGroup parent)210     public View getView(int position, View convertView, ViewGroup parent) {
211         final Preference preference = this.getItem(position);
212         // Build a PreferenceLayout to compare with known ones that are cacheable.
213         mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
214 
215         // If it's not one of the cached ones, set the convertView to null so that
216         // the layout gets re-created by the Preference.
217         if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0) {
218             convertView = null;
219         }
220 
221         return preference.getView(convertView, parent);
222     }
223 
224     @Override
isEnabled(int position)225     public boolean isEnabled(int position) {
226         if (position < 0 || position >= getCount()) return true;
227         return this.getItem(position).isSelectable();
228     }
229 
230     @Override
areAllItemsEnabled()231     public boolean areAllItemsEnabled() {
232         // There should always be a preference group, and these groups are always
233         // disabled
234         return false;
235     }
236 
onPreferenceChange(Preference preference)237     public void onPreferenceChange(Preference preference) {
238         notifyDataSetChanged();
239     }
240 
onPreferenceHierarchyChange(Preference preference)241     public void onPreferenceHierarchyChange(Preference preference) {
242         mHandler.removeCallbacks(mSyncRunnable);
243         mHandler.post(mSyncRunnable);
244     }
245 
246     @Override
hasStableIds()247     public boolean hasStableIds() {
248         return true;
249     }
250 
251     @Override
getItemViewType(int position)252     public int getItemViewType(int position) {
253         if (!mHasReturnedViewTypeCount) {
254             mHasReturnedViewTypeCount = true;
255         }
256 
257         final Preference preference = this.getItem(position);
258         if (preference.hasSpecifiedLayout()) {
259             return IGNORE_ITEM_VIEW_TYPE;
260         }
261 
262         mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout);
263 
264         int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout);
265         if (viewType < 0) {
266             // This is a class that was seen after we returned the count, so
267             // don't recycle it.
268             return IGNORE_ITEM_VIEW_TYPE;
269         } else {
270             return viewType;
271         }
272     }
273 
274     @Override
getViewTypeCount()275     public int getViewTypeCount() {
276         if (!mHasReturnedViewTypeCount) {
277             mHasReturnedViewTypeCount = true;
278         }
279 
280         return Math.max(1, mPreferenceLayouts.size());
281     }
282 
283 }
284