CompositeListAdapter.java revision 6bb8718b04da174f3642fbdcefea0fee6086db3d
1/*
2 * Copyright (C) 2010 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 */
16package com.android.contacts.widget;
17
18import android.database.DataSetObserver;
19import android.view.View;
20import android.view.ViewGroup;
21import android.widget.BaseAdapter;
22import android.widget.ListAdapter;
23
24import com.google.common.annotations.VisibleForTesting;
25
26/**
27 * A general purpose adapter that is composed of multiple sub-adapters. It just
28 * appends them in the order they are added. It listens to changes from all
29 * sub-adapters and propagates them to its own listeners.
30 */
31public class CompositeListAdapter extends BaseAdapter {
32
33    private static final int INITIAL_CAPACITY = 2;
34
35    private ListAdapter[] mAdapters;
36    private int[] mCounts;
37    private int[] mViewTypeCounts;
38    private int mSize = 0;
39    private int mCount = 0;
40    private int mViewTypeCount = 0;
41    private boolean mAllItemsEnabled = true;
42    private boolean mCacheValid = true;
43
44    private DataSetObserver mDataSetObserver = new DataSetObserver() {
45
46        @Override
47        public void onChanged() {
48            invalidate();
49            notifyDataChanged();
50        }
51
52        @Override
53        public void onInvalidated() {
54            invalidate();
55            notifyDataChanged();
56        }
57    };
58
59    public CompositeListAdapter() {
60        this(INITIAL_CAPACITY);
61    }
62
63    public CompositeListAdapter(int initialCapacity) {
64        mAdapters = new ListAdapter[INITIAL_CAPACITY];
65        mCounts = new int[INITIAL_CAPACITY];
66        mViewTypeCounts = new int[INITIAL_CAPACITY];
67    }
68
69    @VisibleForTesting
70    /*package*/ void addAdapter(ListAdapter adapter) {
71        if (mSize >= mAdapters.length) {
72            int newCapacity = mSize + 2;
73            ListAdapter[] newAdapters = new ListAdapter[newCapacity];
74            System.arraycopy(mAdapters, 0, newAdapters, 0, mSize);
75            mAdapters = newAdapters;
76
77            int[] newCounts = new int[newCapacity];
78            System.arraycopy(mCounts, 0, newCounts, 0, mSize);
79            mCounts = newCounts;
80
81            int[] newViewTypeCounts = new int[newCapacity];
82            System.arraycopy(mViewTypeCounts, 0, newViewTypeCounts, 0, mSize);
83            mViewTypeCounts = newViewTypeCounts;
84        }
85
86        adapter.registerDataSetObserver(mDataSetObserver);
87
88        int count = adapter.getCount();
89        int viewTypeCount = adapter.getViewTypeCount();
90
91        mAdapters[mSize] = adapter;
92        mCounts[mSize] = count;
93        mCount += count;
94        mAllItemsEnabled &= adapter.areAllItemsEnabled();
95        mViewTypeCounts[mSize] = viewTypeCount;
96        mViewTypeCount += viewTypeCount;
97        mSize++;
98
99        notifyDataChanged();
100    }
101
102    protected void notifyDataChanged() {
103        if (getCount() > 0) {
104            notifyDataSetChanged();
105        } else {
106            notifyDataSetInvalidated();
107        }
108    }
109
110    protected void invalidate() {
111        mCacheValid = false;
112    }
113
114    protected void ensureCacheValid() {
115        if (mCacheValid) {
116            return;
117        }
118
119        mCount = 0;
120        mAllItemsEnabled = true;
121        mViewTypeCount = 0;
122        for (int i = 0; i < mSize; i++) {
123            int count = mAdapters[i].getCount();
124            int viewTypeCount = mAdapters[i].getViewTypeCount();
125            mCounts[i] = count;
126            mCount += count;
127            mAllItemsEnabled &= mAdapters[i].areAllItemsEnabled();
128            mViewTypeCount += viewTypeCount;
129        }
130
131        mCacheValid = true;
132    }
133
134    public int getCount() {
135        ensureCacheValid();
136        return mCount;
137    }
138
139    public Object getItem(int position) {
140        ensureCacheValid();
141        int start = 0;
142        for (int i = 0; i < mCounts.length; i++) {
143            int end = start + mCounts[i];
144            if (position >= start && position < end) {
145                return mAdapters[i].getItem(position - start);
146            }
147            start = end;
148        }
149
150        throw new ArrayIndexOutOfBoundsException(position);
151    }
152
153    public long getItemId(int position) {
154        ensureCacheValid();
155        int start = 0;
156        for (int i = 0; i < mCounts.length; i++) {
157            int end = start + mCounts[i];
158            if (position >= start && position < end) {
159                return mAdapters[i].getItemId(position - start);
160            }
161            start = end;
162        }
163
164        throw new ArrayIndexOutOfBoundsException(position);
165    }
166
167    @Override
168    public int getViewTypeCount() {
169        ensureCacheValid();
170        return mViewTypeCount;
171    }
172
173    @Override
174    public int getItemViewType(int position) {
175        ensureCacheValid();
176        int start = 0;
177        int viewTypeOffset = 0;
178        for (int i = 0; i < mCounts.length; i++) {
179            int end = start + mCounts[i];
180            if (position >= start && position < end) {
181                return viewTypeOffset + mAdapters[i].getItemViewType(position - start);
182            }
183            viewTypeOffset += mViewTypeCounts[i];
184            start = end;
185        }
186
187        throw new ArrayIndexOutOfBoundsException(position);
188    }
189
190    public View getView(int position, View convertView, ViewGroup parent) {
191        ensureCacheValid();
192        int start = 0;
193        for (int i = 0; i < mCounts.length; i++) {
194            int end = start + mCounts[i];
195            if (position >= start && position < end) {
196                return mAdapters[i].getView(position - start, convertView, parent);
197            }
198            start = end;
199        }
200
201        throw new ArrayIndexOutOfBoundsException(position);
202    }
203
204    @Override
205    public boolean areAllItemsEnabled() {
206        ensureCacheValid();
207        return mAllItemsEnabled;
208    }
209
210    @Override
211    public boolean isEnabled(int position) {
212        ensureCacheValid();
213        int start = 0;
214        for (int i = 0; i < mCounts.length; i++) {
215            int end = start + mCounts[i];
216            if (position >= start && position < end) {
217                return mAdapters[i].areAllItemsEnabled()
218                        || mAdapters[i].isEnabled(position - start);
219            }
220            start = end;
221        }
222
223        throw new ArrayIndexOutOfBoundsException(position);
224    }
225}
226