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  */
16 package com.android.contacts.widget;
17 
18 import android.database.DataSetObserver;
19 import android.view.View;
20 import android.view.ViewGroup;
21 import android.widget.BaseAdapter;
22 import android.widget.ListAdapter;
23 
24 import 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  */
31 public 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 
CompositeListAdapter()59     public CompositeListAdapter() {
60         this(INITIAL_CAPACITY);
61     }
62 
CompositeListAdapter(int initialCapacity)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
addAdapter(ListAdapter adapter)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 
notifyDataChanged()102     protected void notifyDataChanged() {
103         if (getCount() > 0) {
104             notifyDataSetChanged();
105         } else {
106             notifyDataSetInvalidated();
107         }
108     }
109 
invalidate()110     protected void invalidate() {
111         mCacheValid = false;
112     }
113 
ensureCacheValid()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 
getCount()134     public int getCount() {
135         ensureCacheValid();
136         return mCount;
137     }
138 
getItem(int position)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 
getItemId(int position)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
getViewTypeCount()168     public int getViewTypeCount() {
169         ensureCacheValid();
170         return mViewTypeCount;
171     }
172 
173     @Override
getItemViewType(int position)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 
getView(int position, View convertView, ViewGroup parent)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
areAllItemsEnabled()205     public boolean areAllItemsEnabled() {
206         ensureCacheValid();
207         return mAllItemsEnabled;
208     }
209 
210     @Override
isEnabled(int position)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