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