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