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