1 /*
2  * Copyright (C) 2008 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.widget.cts.util;
18 
19 import java.util.ArrayList;
20 import java.util.List;
21 
22 import android.view.Gravity;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.widget.AbsListView;
26 import android.widget.BaseExpandableListAdapter;
27 import android.widget.ExpandableListAdapter;
28 import android.widget.ExpandableListView;
29 import android.widget.ListView;
30 import android.widget.TextView;
31 
32 /**
33  * Utility base class for creating various Expandable List scenarios.
34  * <p>
35  * WARNING: A lot of the features are mixed between ListView's expected position
36  * (flat list position) and an ExpandableListView's expected position.  You must add/change
37  * features as you need them.
38  *
39  * @see ListScenario
40  */
41 public abstract class ExpandableListScenario extends ListScenario {
42     protected ExpandableListAdapter mAdapter;
43     protected List<MyGroup> mGroups;
44 
45     @Override
createListView()46     protected ListView createListView() {
47         return new ExpandableListView(this);
48     }
49 
50     @Override
createParams()51     protected Params createParams() {
52         return new ExpandableParams();
53     }
54 
55     @Override
setAdapter(ListView listView)56     protected void setAdapter(ListView listView) {
57         mAdapter = createAdapter();
58         if (mAdapter != null) {
59             ((ExpandableListView) listView).setAdapter(mAdapter);
60         }
61     }
62 
createAdapter()63     protected ExpandableListAdapter createAdapter() {
64         return new MyAdapter();
65     }
66 
67     @Override
readAndValidateParams(Params params)68     protected void readAndValidateParams(Params params) {
69         ExpandableParams expandableParams = (ExpandableParams) params;
70 
71         int[] numChildren = expandableParams.mNumChildren;
72 
73         mGroups = new ArrayList<MyGroup>(numChildren.length);
74         for (int i = 0; i < numChildren.length; i++) {
75             mGroups.add(new MyGroup(numChildren[i]));
76         }
77 
78         expandableParams.superSetNumItems();
79 
80         super.readAndValidateParams(params);
81     }
82 
83     /**
84      * Get the ExpandableListView widget.
85      * @return The main widget.
86      */
getExpandableListView()87     public ExpandableListView getExpandableListView() {
88         return (ExpandableListView) super.getListView();
89     }
90 
91     public static class ExpandableParams extends Params {
92         private int[] mNumChildren;
93 
94         /**
95          * Sets the number of children per group.
96          *
97          * @param numChildren The number of children per group.
98          */
setNumChildren(int[] numChildren)99         public ExpandableParams setNumChildren(int[] numChildren) {
100             mNumChildren = numChildren;
101             return this;
102         }
103 
104         /**
105          * Sets the number of items on the superclass based on the number of
106          * groups and children per group.
107          */
superSetNumItems()108         private ExpandableParams superSetNumItems() {
109             int numItems = 0;
110 
111             if (mNumChildren != null) {
112                 for (int i = mNumChildren.length - 1; i >= 0; i--) {
113                     numItems += mNumChildren[i];
114                 }
115             }
116 
117             super.setNumItems(numItems);
118 
119             return this;
120         }
121 
122         @Override
setNumItems(int numItems)123         public Params setNumItems(int numItems) {
124             throw new IllegalStateException("Use setNumGroups and setNumChildren instead.");
125         }
126 
127         @Override
setFadingEdgeScreenSizeFactor(double fadingEdgeScreenSizeFactor)128         public ExpandableParams setFadingEdgeScreenSizeFactor(double fadingEdgeScreenSizeFactor) {
129             return (ExpandableParams) super.setFadingEdgeScreenSizeFactor(fadingEdgeScreenSizeFactor);
130         }
131 
132         @Override
setItemScreenSizeFactor(double itemScreenSizeFactor)133         public ExpandableParams setItemScreenSizeFactor(double itemScreenSizeFactor) {
134             return (ExpandableParams) super.setItemScreenSizeFactor(itemScreenSizeFactor);
135         }
136 
137         @Override
setItemsFocusable(boolean itemsFocusable)138         public ExpandableParams setItemsFocusable(boolean itemsFocusable) {
139             return (ExpandableParams) super.setItemsFocusable(itemsFocusable);
140         }
141 
142         @Override
setMustFillScreen(boolean fillScreen)143         public ExpandableParams setMustFillScreen(boolean fillScreen) {
144             return (ExpandableParams) super.setMustFillScreen(fillScreen);
145         }
146 
147         @Override
setPositionScreenSizeFactorOverride(int position, double itemScreenSizeFactor)148         public ExpandableParams setPositionScreenSizeFactorOverride(int position, double itemScreenSizeFactor) {
149             return (ExpandableParams) super.setPositionScreenSizeFactorOverride(position, itemScreenSizeFactor);
150         }
151 
152         @Override
setPositionUnselectable(int position)153         public ExpandableParams setPositionUnselectable(int position) {
154             return (ExpandableParams) super.setPositionUnselectable(position);
155         }
156 
157         @Override
setStackFromBottom(boolean stackFromBottom)158         public ExpandableParams setStackFromBottom(boolean stackFromBottom) {
159             return (ExpandableParams) super.setStackFromBottom(stackFromBottom);
160         }
161 
162         @Override
setStartingSelectionPosition(int startingSelectionPosition)163         public ExpandableParams setStartingSelectionPosition(int startingSelectionPosition) {
164             return (ExpandableParams) super.setStartingSelectionPosition(startingSelectionPosition);
165         }
166 
167         @Override
setConnectAdapter(boolean connectAdapter)168         public ExpandableParams setConnectAdapter(boolean connectAdapter) {
169             return (ExpandableParams) super.setConnectAdapter(connectAdapter);
170         }
171     }
172 
173     /**
174      * Gets a string for the value of some item.
175      * @param packedPosition The position of the item.
176      * @return The string.
177      */
getValueAtPosition(long packedPosition)178     public final String getValueAtPosition(long packedPosition) {
179         final int type = ExpandableListView.getPackedPositionType(packedPosition);
180 
181         if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
182             return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
183                     .children.get(ExpandableListView.getPackedPositionChild(packedPosition))
184                     .name;
185         } else if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
186             return mGroups.get(ExpandableListView.getPackedPositionGroup(packedPosition))
187                     .name;
188         } else {
189             throw new IllegalStateException("packedPosition is not a valid position.");
190         }
191     }
192 
193     /**
194      * Whether a particular position is out of bounds.
195      *
196      * @param packedPosition The packed position.
197      * @return Whether it's out of bounds.
198      */
isOutOfBounds(long packedPosition)199     private boolean isOutOfBounds(long packedPosition) {
200         final int type = ExpandableListView.getPackedPositionType(packedPosition);
201 
202         if (type == ExpandableListView.PACKED_POSITION_TYPE_NULL) {
203             throw new IllegalStateException("packedPosition is not a valid position.");
204         }
205 
206         final int group = ExpandableListView.getPackedPositionGroup(packedPosition);
207         if (group >= mGroups.size() || group < 0) {
208             return true;
209         }
210 
211         if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
212             final int child = ExpandableListView.getPackedPositionChild(packedPosition);
213             if (child >= mGroups.get(group).children.size() || child < 0) {
214                 return true;
215             }
216         }
217 
218         return false;
219     }
220 
221     /**
222      * Gets a view for the packed position, possibly reusing the convertView.
223      *
224      * @param packedPosition The position to get a view for.
225      * @param convertView Optional view to convert.
226      * @param parent The future parent.
227      * @return A view.
228      */
getView(long packedPosition, View convertView, ViewGroup parent)229     private View getView(long packedPosition, View convertView, ViewGroup parent) {
230         if (isOutOfBounds(packedPosition)) {
231             throw new IllegalStateException("position out of range for adapter!");
232         }
233 
234         final ExpandableListView elv = getExpandableListView();
235         final int flPos = elv.getFlatListPosition(packedPosition);
236 
237         if (convertView != null) {
238             ((TextView) convertView).setText(getValueAtPosition(packedPosition));
239             convertView.setId(flPos);
240             return convertView;
241         }
242 
243         int desiredHeight = getHeightForPosition(flPos);
244         return createView(packedPosition, flPos, parent, desiredHeight);
245     }
246 
247     /**
248      * Create a view for a group or child position.
249      *
250      * @param packedPosition The packed position (has type, group pos, and optionally child pos).
251      * @param flPos The flat list position (the position that the ListView goes by).
252      * @param parent The parent view.
253      * @param desiredHeight The desired height.
254      * @return A view.
255      */
createView(long packedPosition, int flPos, ViewGroup parent, int desiredHeight)256     protected View createView(long packedPosition, int flPos, ViewGroup parent, int desiredHeight) {
257         TextView result = new TextView(parent.getContext());
258         result.setHeight(desiredHeight);
259         result.setText(getValueAtPosition(packedPosition));
260         final ViewGroup.LayoutParams lp = new AbsListView.LayoutParams(
261                 ViewGroup.LayoutParams.MATCH_PARENT,
262                 ViewGroup.LayoutParams.WRAP_CONTENT);
263         result.setLayoutParams(lp);
264         result.setGravity(Gravity.CENTER_VERTICAL);
265         result.setPadding(36, 0, 0, 0);
266         result.setId(flPos);
267         return result;
268     }
269 
270     /**
271      * Returns a group index containing either the number of children or at
272      * least one child.
273      *
274      * @param numChildren The group must have this amount, or -1 if using
275      *            atLeastOneChild.
276      * @param atLeastOneChild The group must have at least one child, or false
277      *            if using numChildren.
278      * @return A group index with the requirements.
279      */
findGroupWithNumChildren(int numChildren, boolean atLeastOneChild)280     public int findGroupWithNumChildren(int numChildren, boolean atLeastOneChild) {
281         if (mAdapter == null) {
282             return -1;
283         }
284 
285         final ExpandableListAdapter adapter = mAdapter;
286 
287         for (int i = adapter.getGroupCount() - 1; i >= 0; i--) {
288             final int curNumChildren = adapter.getChildrenCount(i);
289 
290             if (numChildren == curNumChildren || atLeastOneChild && curNumChildren > 0) {
291                 return i;
292             }
293         }
294 
295         return -1;
296     }
297 
getGroups()298     public List<MyGroup> getGroups() {
299         return mGroups;
300     }
301 
302     /**
303      * Simple expandable list adapter.
304      */
305     protected class MyAdapter extends BaseExpandableListAdapter {
getChild(int groupPosition, int childPosition)306         public Object getChild(int groupPosition, int childPosition) {
307             return getValueAtPosition(ExpandableListView.getPackedPositionForChild(groupPosition,
308                     childPosition));
309         }
310 
getChildId(int groupPosition, int childPosition)311         public long getChildId(int groupPosition, int childPosition) {
312             return mGroups.get(groupPosition).children.get(childPosition).id;
313         }
314 
getChildrenCount(int groupPosition)315         public int getChildrenCount(int groupPosition) {
316             return mGroups.get(groupPosition).children.size();
317         }
318 
getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent)319         public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
320                 View convertView, ViewGroup parent) {
321             return getView(ExpandableListView.getPackedPositionForChild(groupPosition,
322                     childPosition), convertView, parent);
323         }
324 
getGroup(int groupPosition)325         public Object getGroup(int groupPosition) {
326             return getValueAtPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
327         }
328 
getGroupCount()329         public int getGroupCount() {
330             return mGroups.size();
331         }
332 
getGroupId(int groupPosition)333         public long getGroupId(int groupPosition) {
334             return mGroups.get(groupPosition).id;
335         }
336 
getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent)337         public View getGroupView(int groupPosition, boolean isExpanded, View convertView,
338                 ViewGroup parent) {
339             return getView(ExpandableListView.getPackedPositionForGroup(groupPosition),
340                     convertView, parent);
341         }
342 
isChildSelectable(int groupPosition, int childPosition)343         public boolean isChildSelectable(int groupPosition, int childPosition) {
344             return true;
345         }
346 
hasStableIds()347         public boolean hasStableIds() {
348             return true;
349         }
350 
351     }
352 
353     public static class MyGroup {
354         private static long sNextId = 1000;
355 
356         String name;
357         long id = sNextId++;
358         List<MyChild> children;
359 
MyGroup(int numChildren)360         public MyGroup(int numChildren) {
361             name = "Group " + id;
362             children = new ArrayList<MyChild>(numChildren);
363             for (int i = 0; i < numChildren; i++) {
364                 children.add(new MyChild());
365             }
366         }
367     }
368 
369     public static class MyChild {
370         private static long mNextId = 2000;
371 
372         String name;
373         long id = mNextId++;
374 
MyChild()375         public MyChild() {
376             name = "Child " + id;
377         }
378     }
379 
380     @Override
init(Params params)381     protected final void init(Params params) {
382         init((ExpandableParams) params);
383     }
384 
385     /**
386      * @see ListScenario#init
387      */
init(ExpandableParams params)388     protected abstract void init(ExpandableParams params);
389 }
390