• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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