PreferenceGroupAdapter.java revision 54b6cfa9a9e5b861a9930af873580d6dc20f773c
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 mPreferenceList = new ArrayList<Preference>(); 92 mPreferenceClassNames = new ArrayList<String>(); 93 94 syncMyPreferences(); 95 } 96 97 private void syncMyPreferences() { 98 synchronized(this) { 99 if (mIsSyncing) { 100 return; 101 } 102 103 mIsSyncing = true; 104 } 105 106 List<Preference> newPreferenceList = new ArrayList<Preference>(mPreferenceList.size()); 107 flattenPreferenceGroup(newPreferenceList, mPreferenceGroup); 108 mPreferenceList = newPreferenceList; 109 110 notifyDataSetChanged(); 111 112 synchronized(this) { 113 mIsSyncing = false; 114 notifyAll(); 115 } 116 } 117 118 private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) { 119 // TODO: shouldn't always? 120 group.sortPreferences(); 121 122 final int groupSize = group.getPreferenceCount(); 123 for (int i = 0; i < groupSize; i++) { 124 final Preference preference = group.getPreference(i); 125 126 preferences.add(preference); 127 128 if (!mHasReturnedViewTypeCount) { 129 addPreferenceClassName(preference); 130 } 131 132 if (preference instanceof PreferenceGroup) { 133 final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference; 134 if (preferenceAsGroup.isOnSameScreenAsChildren()) { 135 flattenPreferenceGroup(preferences, preferenceAsGroup); 136 preference.setOnPreferenceChangeInternalListener(this); 137 } 138 } else { 139 preference.setOnPreferenceChangeInternalListener(this); 140 } 141 } 142 } 143 144 private void addPreferenceClassName(Preference preference) { 145 final String name = preference.getClass().getName(); 146 int insertPos = Collections.binarySearch(mPreferenceClassNames, name); 147 148 // Only insert if it doesn't exist (when it is negative). 149 if (insertPos < 0) { 150 // Convert to insert index 151 insertPos = insertPos * -1 - 1; 152 mPreferenceClassNames.add(insertPos, name); 153 } 154 } 155 156 public int getCount() { 157 return mPreferenceList.size(); 158 } 159 160 public Preference getItem(int position) { 161 if (position < 0 || position >= getCount()) return null; 162 return mPreferenceList.get(position); 163 } 164 165 public long getItemId(int position) { 166 if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID; 167 return this.getItem(position).getId(); 168 } 169 170 public View getView(int position, View convertView, ViewGroup parent) { 171 final Preference preference = this.getItem(position); 172 173 if (preference.hasSpecifiedLayout()) { 174 // If the preference had specified a layout (as opposed to the 175 // default), don't use convert views. 176 convertView = null; 177 } else { 178 // TODO: better way of doing this 179 final String name = preference.getClass().getName(); 180 if (Collections.binarySearch(mPreferenceClassNames, name) < 0) { 181 convertView = null; 182 } 183 } 184 185 return preference.getView(convertView, parent); 186 } 187 188 @Override 189 public boolean isEnabled(int position) { 190 if (position < 0 || position >= getCount()) return true; 191 return this.getItem(position).isSelectable(); 192 } 193 194 @Override 195 public boolean areAllItemsEnabled() { 196 // There should always be a preference group, and these groups are always 197 // disabled 198 return false; 199 } 200 201 public void onPreferenceChange(Preference preference) { 202 notifyDataSetChanged(); 203 } 204 205 public void onPreferenceHierarchyChange(Preference preference) { 206 mHandler.removeCallbacks(mSyncRunnable); 207 mHandler.post(mSyncRunnable); 208 } 209 210 @Override 211 public boolean hasStableIds() { 212 return true; 213 } 214 215 @Override 216 public int getItemViewType(int position) { 217 if (!mHasReturnedViewTypeCount) { 218 mHasReturnedViewTypeCount = true; 219 } 220 221 final Preference preference = this.getItem(position); 222 if (preference.hasSpecifiedLayout()) { 223 return IGNORE_ITEM_VIEW_TYPE; 224 } 225 226 final String name = preference.getClass().getName(); 227 int viewType = Collections.binarySearch(mPreferenceClassNames, name); 228 if (viewType < 0) { 229 // This is a class that was seen after we returned the count, so 230 // don't recycle it. 231 return IGNORE_ITEM_VIEW_TYPE; 232 } else { 233 return viewType; 234 } 235 } 236 237 @Override 238 public int getViewTypeCount() { 239 if (!mHasReturnedViewTypeCount) { 240 mHasReturnedViewTypeCount = true; 241 } 242 243 return mPreferenceClassNames.size(); 244 } 245 246} 247