17b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov/*
27b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * Copyright (C) 2010 The Android Open Source Project
37b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov *
47b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * Licensed under the Apache License, Version 2.0 (the "License");
57b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * you may not use this file except in compliance with the License.
67b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * You may obtain a copy of the License at
77b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov *
87b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov *      http://www.apache.org/licenses/LICENSE-2.0
97b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov *
107b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * Unless required by applicable law or agreed to in writing, software
117b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * distributed under the License is distributed on an "AS IS" BASIS,
127b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * See the License for the specific language governing permissions and
147b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * limitations under the License.
157b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov */
167b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
177b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovpackage com.android.common.widget;
187b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
197b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovimport android.content.Context;
207b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovimport android.database.ContentObserver;
217b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovimport android.database.Cursor;
227b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovimport android.database.DataSetObserver;
237b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovimport android.os.Handler;
247b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovimport android.util.SparseIntArray;
257b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovimport android.view.View;
267b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovimport android.view.ViewGroup;
277b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovimport android.widget.BaseAdapter;
287b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
297b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov/**
307b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * Maintains a list that groups adjacent items sharing the same value of
317b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * a "group-by" field.  The list has three types of elements: stand-alone, group header and group
327b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov * child. Groups are collapsible and collapsed by default.
337b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov */
347b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikovpublic abstract class GroupingListAdapter extends BaseAdapter {
357b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
367b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private static final int GROUP_METADATA_ARRAY_INITIAL_SIZE = 16;
377b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private static final int GROUP_METADATA_ARRAY_INCREMENT = 128;
387b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private static final long GROUP_OFFSET_MASK    = 0x00000000FFFFFFFFL;
397b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private static final long GROUP_SIZE_MASK     = 0x7FFFFFFF00000000L;
407b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private static final long EXPANDED_GROUP_MASK = 0x8000000000000000L;
417b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
427b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public static final int ITEM_TYPE_STANDALONE = 0;
437b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public static final int ITEM_TYPE_GROUP_HEADER = 1;
447b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public static final int ITEM_TYPE_IN_GROUP = 2;
457b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
467b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
477b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Information about a specific list item: is it a group, if so is it expanded.
487b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Otherwise, is it a stand-alone item or a group member.
497b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
507b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected static class PositionMetadata {
517b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        int itemType;
527b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        boolean isExpanded;
537b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        int cursorPosition;
547b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        int childCount;
557b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        private int groupPosition;
567b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        private int listPosition = -1;
577b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
587b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
597b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private Context mContext;
607b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private Cursor mCursor;
617b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
627b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
637b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Count of list items.
647b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
657b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private int mCount;
667b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
677b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private int mRowIdColumnIndex;
687b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
697b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
707b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Count of groups in the list.
717b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
727b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private int mGroupCount;
737b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
747b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
757b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Information about where these groups are located in the list, how large they are
767b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * and whether they are expanded.
777b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
787b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private long[] mGroupMetadata;
797b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
807b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private SparseIntArray mPositionCache = new SparseIntArray();
817b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private int mLastCachedListPosition;
827b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private int mLastCachedCursorPosition;
837b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private int mLastCachedGroup;
847b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
857b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
867b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * A reusable temporary instance of PositionMetadata
877b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
887b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private PositionMetadata mPositionMetadata = new PositionMetadata();
897b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
907b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected ContentObserver mChangeObserver = new ContentObserver(new Handler()) {
917b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
927b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        @Override
937b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        public boolean deliverSelfNotifications() {
947b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return true;
957b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
967b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
977b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        @Override
987b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        public void onChange(boolean selfChange) {
997b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            onContentChanged();
1007b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
1017b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    };
1027b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1037b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected DataSetObserver mDataSetObserver = new DataSetObserver() {
1047b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1057b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        @Override
1067b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        public void onChanged() {
1077b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            notifyDataSetChanged();
1087b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
1097b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1107b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        @Override
1117b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        public void onInvalidated() {
1127b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            notifyDataSetInvalidated();
1137b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
1147b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    };
1157b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1167b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public GroupingListAdapter(Context context) {
1177b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mContext = context;
1187b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        resetCache();
1197b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
1207b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1217b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
1227b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Finds all groups of adjacent items in the cursor and calls {@link #addGroup} for
1237b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * each of them.
1247b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
1257b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected abstract void addGroups(Cursor cursor);
1267b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1277b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected abstract View newStandAloneView(Context context, ViewGroup parent);
1287b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected abstract void bindStandAloneView(View view, Context context, Cursor cursor);
1297b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1307b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected abstract View newGroupView(Context context, ViewGroup parent);
1317b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected abstract void bindGroupView(View view, Context context, Cursor cursor, int groupSize,
1327b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            boolean expanded);
1337b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1347b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected abstract View newChildView(Context context, ViewGroup parent);
1357b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected abstract void bindChildView(View view, Context context, Cursor cursor);
1367b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1377b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
1387b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Cache should be reset whenever the cursor changes or groups are expanded or collapsed.
1397b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
1407b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private void resetCache() {
1417b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mCount = -1;
1427b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mLastCachedListPosition = -1;
1437b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mLastCachedCursorPosition = -1;
1447b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mLastCachedGroup = -1;
1457b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mPositionMetadata.listPosition = -1;
1467b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mPositionCache.clear();
1477b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
1487b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1497b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected void onContentChanged() {
1507b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
1517b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1527b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public void changeCursor(Cursor cursor) {
1537b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (cursor == mCursor) {
1547b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return;
1557b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
1567b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1577b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (mCursor != null) {
1587b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            mCursor.unregisterContentObserver(mChangeObserver);
1597b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            mCursor.unregisterDataSetObserver(mDataSetObserver);
1607b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            mCursor.close();
1617b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
1627b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mCursor = cursor;
1637b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        resetCache();
1647b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        findGroups();
1657b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1667b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (cursor != null) {
1677b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            cursor.registerContentObserver(mChangeObserver);
1687b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            cursor.registerDataSetObserver(mDataSetObserver);
1697b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            mRowIdColumnIndex = cursor.getColumnIndexOrThrow("_id");
1707b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            notifyDataSetChanged();
1717b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        } else {
1727b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            // notify the observers about the lack of a data set
1737b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            notifyDataSetInvalidated();
1747b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
1757b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1767b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
1777b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1787b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public Cursor getCursor() {
1797b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        return mCursor;
1807b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
1817b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1827b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
1837b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Scans over the entire cursor looking for duplicate phone numbers that need
1847b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * to be collapsed.
1857b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
1867b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private void findGroups() {
1877b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mGroupCount = 0;
1887b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mGroupMetadata = new long[GROUP_METADATA_ARRAY_INITIAL_SIZE];
1897b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1907b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (mCursor == null) {
1917b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return;
1927b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
1937b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1947b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        addGroups(mCursor);
1957b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
1967b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
1977b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
1987b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Records information about grouping in the list.  Should be called by the overridden
1997b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * {@link #addGroups} method.
2007b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
2017b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    protected void addGroup(int cursorPosition, int size, boolean expanded) {
2027b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (mGroupCount >= mGroupMetadata.length) {
2037b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            int newSize = idealLongArraySize(
2047b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    mGroupMetadata.length + GROUP_METADATA_ARRAY_INCREMENT);
2057b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            long[] array = new long[newSize];
2067b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            System.arraycopy(mGroupMetadata, 0, array, 0, mGroupCount);
2077b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            mGroupMetadata = array;
2087b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
2097b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2107b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        long metadata = ((long)size << 32) | cursorPosition;
2117b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (expanded) {
2127b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            metadata |= EXPANDED_GROUP_MASK;
2137b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
2147b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mGroupMetadata[mGroupCount++] = metadata;
2157b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
2167b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2177b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    // Copy/paste from ArrayUtils
2187b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private int idealLongArraySize(int need) {
2197b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        return idealByteArraySize(need * 8) / 8;
2207b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
2217b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2227b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    // Copy/paste from ArrayUtils
2237b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    private int idealByteArraySize(int need) {
2247b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        for (int i = 4; i < 32; i++)
2257b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            if (need <= (1 << i) - 12)
2267b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                return (1 << i) - 12;
2277b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2287b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        return need;
2297b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
2307b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2317b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public int getCount() {
2327b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (mCursor == null) {
2337b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return 0;
2347b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
2357b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2367b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (mCount != -1) {
2377b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return mCount;
2387b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
2397b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2407b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        int cursorPosition = 0;
2417b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        int count = 0;
2427b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        for (int i = 0; i < mGroupCount; i++) {
2437b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            long metadata = mGroupMetadata[i];
2447b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            int offset = (int)(metadata & GROUP_OFFSET_MASK);
2457b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            boolean expanded = (metadata & EXPANDED_GROUP_MASK) != 0;
2467b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            int size = (int)((metadata & GROUP_SIZE_MASK) >> 32);
2477b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2487b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            count += (offset - cursorPosition);
2497b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2507b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            if (expanded) {
2517b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                count += size + 1;
2527b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            } else {
2537b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                count++;
2547b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            }
2557b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2567b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            cursorPosition = offset + size;
2577b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
2587b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2597b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mCount = count + mCursor.getCount() - cursorPosition;
2607b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        return mCount;
2617b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
2627b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2637b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
2647b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Figures out whether the item at the specified position represents a
2657b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * stand-alone element, a group or a group child. Also computes the
2667b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * corresponding cursor position.
2677b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
2687b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public void obtainPositionMetadata(PositionMetadata metadata, int position) {
2697b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2707b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        // If the description object already contains requested information, just return
2717b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (metadata.listPosition == position) {
2727b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return;
2737b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
2747b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2757b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        int listPosition = 0;
2767b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        int cursorPosition = 0;
2777b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        int firstGroupToCheck = 0;
2787b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2797b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        // Check cache for the supplied position.  What we are looking for is
2807b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        // the group descriptor immediately preceding the supplied position.
2817b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        // Once we have that, we will be able to tell whether the position
2827b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        // is the header of the group, a member of the group or a standalone item.
2837b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (mLastCachedListPosition != -1) {
2847b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            if (position <= mLastCachedListPosition) {
2857b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2867b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // Have SparceIntArray do a binary search for us.
2877b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                int index = mPositionCache.indexOfKey(position);
2887b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2897b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // If we get back a positive number, the position corresponds to
2907b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // a group header.
2917b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                if (index < 0) {
2927b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2937b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    // We had a cache miss, but we did obtain valuable information anyway.
2947b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    // The negative number will allow us to compute the location of
2957b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    // the group header immediately preceding the supplied position.
2967b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    index = ~index - 1;
2977b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
2987b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    if (index >= mPositionCache.size()) {
2997b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                        index--;
3007b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    }
3017b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                }
3027b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3037b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // A non-negative index gives us the position of the group header
3047b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // corresponding or preceding the position, so we can
3057b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // search for the group information at the supplied position
3067b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // starting with the cached group we just found
3077b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                if (index >= 0) {
3087b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    listPosition = mPositionCache.keyAt(index);
3097b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    firstGroupToCheck = mPositionCache.valueAt(index);
3107b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    long descriptor = mGroupMetadata[firstGroupToCheck];
3117b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    cursorPosition = (int)(descriptor & GROUP_OFFSET_MASK);
3127b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                }
3137b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            } else {
3147b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3157b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // If we haven't examined groups beyond the supplied position,
3167b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // we will start where we left off previously
3177b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                firstGroupToCheck = mLastCachedGroup;
3187b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                listPosition = mLastCachedListPosition;
3197b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                cursorPosition = mLastCachedCursorPosition;
3207b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            }
3217b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
3227b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3237b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        for (int i = firstGroupToCheck; i < mGroupCount; i++) {
3247b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            long group = mGroupMetadata[i];
3257b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            int offset = (int)(group & GROUP_OFFSET_MASK);
3267b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3277b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            // Move pointers to the beginning of the group
3287b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            listPosition += (offset - cursorPosition);
3297b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            cursorPosition = offset;
3307b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3317b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            if (i > mLastCachedGroup) {
3327b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                mPositionCache.append(listPosition, i);
3337b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                mLastCachedListPosition = listPosition;
3347b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                mLastCachedCursorPosition = cursorPosition;
3357b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                mLastCachedGroup = i;
3367b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            }
3377b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3387b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            // Now we have several possibilities:
3397b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            // A) The requested position precedes the group
3407b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            if (position < listPosition) {
3417b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                metadata.itemType = ITEM_TYPE_STANDALONE;
3427b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                metadata.cursorPosition = cursorPosition - (listPosition - position);
3437b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                return;
3447b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            }
3457b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3467b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            boolean expanded = (group & EXPANDED_GROUP_MASK) != 0;
3477b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            int size = (int) ((group & GROUP_SIZE_MASK) >> 32);
3487b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3497b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            // B) The requested position is a group header
3507b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            if (position == listPosition) {
3517b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                metadata.itemType = ITEM_TYPE_GROUP_HEADER;
3527b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                metadata.groupPosition = i;
3537b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                metadata.isExpanded = expanded;
3547b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                metadata.childCount = size;
3557b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                metadata.cursorPosition = offset;
3567b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                return;
3577b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            }
3587b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3597b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            if (expanded) {
3607b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // C) The requested position is an element in the expanded group
3617b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                if (position < listPosition + size + 1) {
3627b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    metadata.itemType = ITEM_TYPE_IN_GROUP;
3637b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    metadata.cursorPosition = cursorPosition + (position - listPosition) - 1;
3647b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    return;
3657b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                }
3667b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3677b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // D) The element is past the expanded group
3687b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                listPosition += size + 1;
3697b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            } else {
3707b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3717b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                // E) The element is past the collapsed group
3727b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                listPosition++;
3737b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            }
3747b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3757b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            // Move cursor past the group
3767b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            cursorPosition += size;
3777b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
3787b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3797b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        // The required item is past the last group
3807b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        metadata.itemType = ITEM_TYPE_STANDALONE;
3817b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        metadata.cursorPosition = cursorPosition + (position - listPosition);
3827b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
3837b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3847b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
3857b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Returns true if the specified position in the list corresponds to a
3867b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * group header.
3877b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
3887b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public boolean isGroupHeader(int position) {
3897b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        obtainPositionMetadata(mPositionMetadata, position);
3907b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        return mPositionMetadata.itemType == ITEM_TYPE_GROUP_HEADER;
3917b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
3927b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
3937b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
3947b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Given a position of a groups header in the list, returns the size of
3957b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * the corresponding group.
3967b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
3977b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public int getGroupSize(int position) {
3987b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        obtainPositionMetadata(mPositionMetadata, position);
3997b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        return mPositionMetadata.childCount;
4007b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
4017b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4027b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    /**
4037b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     * Mark group as expanded if it is collapsed and vice versa.
4047b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov     */
4057b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public void toggleGroup(int position) {
4067b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        obtainPositionMetadata(mPositionMetadata, position);
4077b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (mPositionMetadata.itemType != ITEM_TYPE_GROUP_HEADER) {
4087b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            throw new IllegalArgumentException("Not a group at position " + position);
4097b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
4107b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4117b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4127b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (mPositionMetadata.isExpanded) {
4137b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            mGroupMetadata[mPositionMetadata.groupPosition] &= ~EXPANDED_GROUP_MASK;
4147b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        } else {
4157b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            mGroupMetadata[mPositionMetadata.groupPosition] |= EXPANDED_GROUP_MASK;
4167b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
4177b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        resetCache();
4187b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        notifyDataSetChanged();
4197b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
4207b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4217b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    @Override
4227b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public int getViewTypeCount() {
4237b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        return 3;
4247b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
4257b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4267b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    @Override
4277b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public int getItemViewType(int position) {
4287b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        obtainPositionMetadata(mPositionMetadata, position);
4297b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        return mPositionMetadata.itemType;
4307b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
4317b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4327b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public Object getItem(int position) {
4337b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (mCursor == null) {
4347b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return null;
4357b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
4367b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4377b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        obtainPositionMetadata(mPositionMetadata, position);
4387b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (mCursor.moveToPosition(mPositionMetadata.cursorPosition)) {
4397b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return mCursor;
4407b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        } else {
4417b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return null;
4427b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
4437b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
4447b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4457b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public long getItemId(int position) {
4467b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        Object item = getItem(position);
4477b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (item != null) {
4487b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return mCursor.getLong(mRowIdColumnIndex);
4497b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        } else {
4507b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            return -1;
4517b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
4527b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
4537b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4547b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    public View getView(int position, View convertView, ViewGroup parent) {
4557b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        obtainPositionMetadata(mPositionMetadata, position);
4567b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        View view = convertView;
4577b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        if (view == null) {
4587b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            switch (mPositionMetadata.itemType) {
4597b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                case ITEM_TYPE_STANDALONE:
4607b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    view = newStandAloneView(mContext, parent);
4617b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    break;
4627b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                case ITEM_TYPE_GROUP_HEADER:
4637b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    view = newGroupView(mContext, parent);
4647b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    break;
4657b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                case ITEM_TYPE_IN_GROUP:
4667b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    view = newChildView(mContext, parent);
4677b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                    break;
4687b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            }
4697b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
4707b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4717b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        mCursor.moveToPosition(mPositionMetadata.cursorPosition);
4727b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        switch (mPositionMetadata.itemType) {
4737b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            case ITEM_TYPE_STANDALONE:
4747b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                bindStandAloneView(view, mContext, mCursor);
4757b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                break;
4767b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            case ITEM_TYPE_GROUP_HEADER:
4777b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                bindGroupView(view, mContext, mCursor, mPositionMetadata.childCount,
4787b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                        mPositionMetadata.isExpanded);
4797b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                break;
4807b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov            case ITEM_TYPE_IN_GROUP:
4817b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                bindChildView(view, mContext, mCursor);
4827b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov                break;
4837b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov
4847b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        }
4857b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov        return view;
4867b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov    }
4877b8b23f9c04fa33b79a8949752a90a134a11df8aDmitri Plotnikov}
488