The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
Fabrice Di Meglio | 35d7b89 | 2014-04-15 19:15:20 -0700 | [diff] [blame] | 23 | import android.graphics.drawable.Drawable; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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; |
Fabrice Di Meglio | 5f9f6c39 | 2014-06-19 12:03:43 -0700 | [diff] [blame] | 30 | import android.widget.FrameLayout; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 |
Fabrice Di Meglio | 53c2cf7 | 2014-04-07 19:12:41 -0700 | [diff] [blame] | 50 | * |
| 51 | * @hide |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 52 | */ |
Fabrice Di Meglio | 53c2cf7 | 2014-04-07 19:12:41 -0700 | [diff] [blame] | 53 | public class PreferenceGroupAdapter extends BaseAdapter |
| 54 | implements OnPreferenceChangeInternalListener { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | */ |
Amith Yamasani | a98129b | 2009-10-05 15:31:47 -0700 | [diff] [blame] | 77 | private ArrayList<PreferenceLayout> mPreferenceLayouts; |
| 78 | |
| 79 | private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout(); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
Fabrice Di Meglio | 35d7b89 | 2014-04-15 19:15:20 -0700 | [diff] [blame] | 96 | private int mHighlightedPosition = -1; |
| 97 | private Drawable mHighlightedDrawable; |
Fabrice Di Meglio | 53c2cf7 | 2014-04-07 19:12:41 -0700 | [diff] [blame] | 98 | |
Fabrice Di Meglio | 5f9f6c39 | 2014-06-19 12:03:43 -0700 | [diff] [blame] | 99 | private static ViewGroup.LayoutParams sWrapperLayoutParams = new ViewGroup.LayoutParams( |
| 100 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); |
| 101 | |
Amith Yamasani | a98129b | 2009-10-05 15:31:47 -0700 | [diff] [blame] | 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 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 125 | public PreferenceGroupAdapter(PreferenceGroup preferenceGroup) { |
| 126 | mPreferenceGroup = preferenceGroup; |
| 127 | // If this group gets or loses any children, let us know |
| 128 | mPreferenceGroup.setOnPreferenceChangeInternalListener(this); |
Amith Yamasani | a98129b | 2009-10-05 15:31:47 -0700 | [diff] [blame] | 129 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 130 | mPreferenceList = new ArrayList<Preference>(); |
Amith Yamasani | a98129b | 2009-10-05 15:31:47 -0700 | [diff] [blame] | 131 | mPreferenceLayouts = new ArrayList<PreferenceLayout>(); |
| 132 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 133 | syncMyPreferences(); |
| 134 | } |
| 135 | |
| 136 | private void syncMyPreferences() { |
| 137 | synchronized(this) { |
| 138 | if (mIsSyncing) { |
| 139 | return; |
| 140 | } |
Amith Yamasani | a98129b | 2009-10-05 15:31:47 -0700 | [diff] [blame] | 141 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
Alan Viverette | 791b37e | 2013-08-20 17:34:19 -0700 | [diff] [blame] | 167 | if (!mHasReturnedViewTypeCount && preference.canRecycleLayout()) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
Amith Yamasani | a98129b | 2009-10-05 15:31:47 -0700 | [diff] [blame] | 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 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 195 | private void addPreferenceClassName(Preference preference) { |
Amith Yamasani | a98129b | 2009-10-05 15:31:47 -0700 | [diff] [blame] | 196 | final PreferenceLayout pl = createPreferenceLayout(preference, null); |
| 197 | int insertPos = Collections.binarySearch(mPreferenceLayouts, pl); |
| 198 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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; |
Amith Yamasani | a98129b | 2009-10-05 15:31:47 -0700 | [diff] [blame] | 203 | mPreferenceLayouts.add(insertPos, pl); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
Fabrice Di Meglio | 35d7b89 | 2014-04-15 19:15:20 -0700 | [diff] [blame] | 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; |
Fabrice Di Meglio | 53c2cf7 | 2014-04-07 19:12:41 -0700 | [diff] [blame] | 233 | } |
| 234 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 235 | public View getView(int position, View convertView, ViewGroup parent) { |
| 236 | final Preference preference = this.getItem(position); |
Amith Yamasani | a98129b | 2009-10-05 15:31:47 -0700 | [diff] [blame] | 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. |
Fabrice Di Meglio | 5f9f6c39 | 2014-06-19 12:03:43 -0700 | [diff] [blame] | 242 | if (Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout) < 0 || |
| 243 | (getItemViewType(position) == getHighlightItemViewType())) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 244 | convertView = null; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 245 | } |
Fabrice Di Meglio | 53c2cf7 | 2014-04-07 19:12:41 -0700 | [diff] [blame] | 246 | View result = preference.getView(convertView, parent); |
Fabrice Di Meglio | 35d7b89 | 2014-04-15 19:15:20 -0700 | [diff] [blame] | 247 | if (position == mHighlightedPosition && mHighlightedDrawable != null) { |
Fabrice Di Meglio | 5f9f6c39 | 2014-06-19 12:03:43 -0700 | [diff] [blame] | 248 | ViewGroup wrapper = new FrameLayout(parent.getContext()); |
| 249 | wrapper.setLayoutParams(sWrapperLayoutParams); |
| 250 | wrapper.setBackgroundDrawable(mHighlightedDrawable); |
| 251 | wrapper.addView(result); |
| 252 | result = wrapper; |
Fabrice Di Meglio | 35d7b89 | 2014-04-15 19:15:20 -0700 | [diff] [blame] | 253 | } |
Fabrice Di Meglio | 53c2cf7 | 2014-04-07 19:12:41 -0700 | [diff] [blame] | 254 | return result; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
Fabrice Di Meglio | 5f9f6c39 | 2014-06-19 12:03:43 -0700 | [diff] [blame] | 284 | private int getHighlightItemViewType() { |
| 285 | return getViewTypeCount() - 1; |
| 286 | } |
| 287 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 288 | @Override |
| 289 | public int getItemViewType(int position) { |
Fabrice Di Meglio | 5f9f6c39 | 2014-06-19 12:03:43 -0700 | [diff] [blame] | 290 | if (position == mHighlightedPosition) { |
| 291 | return getHighlightItemViewType(); |
| 292 | } |
| 293 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 294 | if (!mHasReturnedViewTypeCount) { |
| 295 | mHasReturnedViewTypeCount = true; |
| 296 | } |
| 297 | |
| 298 | final Preference preference = this.getItem(position); |
Alan Viverette | 791b37e | 2013-08-20 17:34:19 -0700 | [diff] [blame] | 299 | if (!preference.canRecycleLayout()) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 300 | return IGNORE_ITEM_VIEW_TYPE; |
| 301 | } |
| 302 | |
Amith Yamasani | a98129b | 2009-10-05 15:31:47 -0700 | [diff] [blame] | 303 | mTempPreferenceLayout = createPreferenceLayout(preference, mTempPreferenceLayout); |
| 304 | |
| 305 | int viewType = Collections.binarySearch(mPreferenceLayouts, mTempPreferenceLayout); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 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 | |
Fabrice Di Meglio | 5f9f6c39 | 2014-06-19 12:03:43 -0700 | [diff] [blame] | 321 | return Math.max(1, mPreferenceLayouts.size()) + 1; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 322 | } |
| 323 | |
| 324 | } |