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 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 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 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 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 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 */ 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 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 196 public int getCount() { 197 return mPreferenceList.size(); 198 } 199 200 public Preference getItem(int position) { 201 if (position < 0 || position >= getCount()) return null; 202 return mPreferenceList.get(position); 203 } 204 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 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 225 public boolean isEnabled(int position) { 226 if (position < 0 || position >= getCount()) return true; 227 return this.getItem(position).isSelectable(); 228 } 229 230 @Override 231 public boolean areAllItemsEnabled() { 232 // There should always be a preference group, and these groups are always 233 // disabled 234 return false; 235 } 236 237 public void onPreferenceChange(Preference preference) { 238 notifyDataSetChanged(); 239 } 240 241 public void onPreferenceHierarchyChange(Preference preference) { 242 mHandler.removeCallbacks(mSyncRunnable); 243 mHandler.post(mSyncRunnable); 244 } 245 246 @Override 247 public boolean hasStableIds() { 248 return true; 249 } 250 251 @Override 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 275 public int getViewTypeCount() { 276 if (!mHasReturnedViewTypeCount) { 277 mHasReturnedViewTypeCount = true; 278 } 279 280 return Math.max(1, mPreferenceLayouts.size()); 281 } 282 283} 284