PreferenceGroupAdapter.java revision b798689749c64baba81f02e10cf2157c747d6b46
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 } 137 } 138 139 preference.setOnPreferenceChangeInternalListener(this); 140 } 141 } 142 143 private void addPreferenceClassName(Preference preference) { 144 final String name = preference.getClass().getName(); 145 int insertPos = Collections.binarySearch(mPreferenceClassNames, name); 146 147 // Only insert if it doesn't exist (when it is negative). 148 if (insertPos < 0) { 149 // Convert to insert index 150 insertPos = insertPos * -1 - 1; 151 mPreferenceClassNames.add(insertPos, name); 152 } 153 } 154 155 public int getCount() { 156 return mPreferenceList.size(); 157 } 158 159 public Preference getItem(int position) { 160 if (position < 0 || position >= getCount()) return null; 161 return mPreferenceList.get(position); 162 } 163 164 public long getItemId(int position) { 165 if (position < 0 || position >= getCount()) return ListView.INVALID_ROW_ID; 166 return this.getItem(position).getId(); 167 } 168 169 public View getView(int position, View convertView, ViewGroup parent) { 170 final Preference preference = this.getItem(position); 171 172 if (preference.hasSpecifiedLayout()) { 173 // If the preference had specified a layout (as opposed to the 174 // default), don't use convert views. 175 convertView = null; 176 } else { 177 // TODO: better way of doing this 178 final String name = preference.getClass().getName(); 179 if (Collections.binarySearch(mPreferenceClassNames, name) < 0) { 180 convertView = null; 181 } 182 } 183 184 return preference.getView(convertView, parent); 185 } 186 187 @Override 188 public boolean isEnabled(int position) { 189 if (position < 0 || position >= getCount()) return true; 190 return this.getItem(position).isSelectable(); 191 } 192 193 @Override 194 public boolean areAllItemsEnabled() { 195 // There should always be a preference group, and these groups are always 196 // disabled 197 return false; 198 } 199 200 public void onPreferenceChange(Preference preference) { 201 notifyDataSetChanged(); 202 } 203 204 public void onPreferenceHierarchyChange(Preference preference) { 205 mHandler.removeCallbacks(mSyncRunnable); 206 mHandler.post(mSyncRunnable); 207 } 208 209 @Override 210 public boolean hasStableIds() { 211 return true; 212 } 213 214 @Override 215 public int getItemViewType(int position) { 216 if (!mHasReturnedViewTypeCount) { 217 mHasReturnedViewTypeCount = true; 218 } 219 220 final Preference preference = this.getItem(position); 221 if (preference.hasSpecifiedLayout()) { 222 return IGNORE_ITEM_VIEW_TYPE; 223 } 224 225 final String name = preference.getClass().getName(); 226 int viewType = Collections.binarySearch(mPreferenceClassNames, name); 227 if (viewType < 0) { 228 // This is a class that was seen after we returned the count, so 229 // don't recycle it. 230 return IGNORE_ITEM_VIEW_TYPE; 231 } else { 232 return viewType; 233 } 234 } 235 236 @Override 237 public int getViewTypeCount() { 238 if (!mHasReturnedViewTypeCount) { 239 mHasReturnedViewTypeCount = true; 240 } 241 242 return mPreferenceClassNames.size(); 243 } 244 245} 246