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