1 /*
2  * Copyright (C) 2015 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.messaging.ui;
17 
18 import android.content.Context;
19 import android.database.DataSetObserver;
20 import android.view.View;
21 import android.view.ViewGroup;
22 import android.widget.BaseAdapter;
23 
24 /**
25  * A general purpose adapter that composes one or more other adapters.  It
26  * appends them in the order they are added.
27  */
28 public class CompositeAdapter extends BaseAdapter {
29 
30     private static final int INITIAL_CAPACITY = 2;
31 
32     public static class Partition {
33         boolean mShowIfEmpty;
34         boolean mHasHeader;
35         BaseAdapter mAdapter;
36 
Partition(final boolean showIfEmpty, final boolean hasHeader, final BaseAdapter adapter)37         public Partition(final boolean showIfEmpty, final boolean hasHeader,
38                 final BaseAdapter adapter) {
39             this.mShowIfEmpty = showIfEmpty;
40             this.mHasHeader = hasHeader;
41             this.mAdapter = adapter;
42         }
43 
44         /**
45          * True if the directory should be shown even if no contacts are found.
46          */
showIfEmpty()47         public boolean showIfEmpty() {
48             return mShowIfEmpty;
49         }
50 
hasHeader()51         public boolean hasHeader() {
52             return mHasHeader;
53         }
54 
getCount()55         public int getCount() {
56             int count = mAdapter.getCount();
57             if (mHasHeader && (count != 0 || mShowIfEmpty)) {
58                 count++;
59             }
60             return count;
61         }
62 
getAdapter()63         public BaseAdapter getAdapter() {
64             return mAdapter;
65         }
66 
getHeaderView(final View convertView, final ViewGroup parentView)67         public View getHeaderView(final View convertView, final ViewGroup parentView) {
68             return null;
69         }
70 
close()71         public void close() {
72             // do nothing in base class.
73         }
74     }
75 
76     private class Observer extends DataSetObserver {
77         @Override
onChanged()78         public void onChanged() {
79             CompositeAdapter.this.notifyDataSetChanged();
80         }
81 
82         @Override
onInvalidated()83         public void onInvalidated() {
84             CompositeAdapter.this.notifyDataSetInvalidated();
85         }
86     };
87 
88     protected final Context mContext;
89     private Partition[] mPartitions;
90     private int mSize = 0;
91     private int mCount = 0;
92     private boolean mCacheValid = true;
93     private final Observer mObserver;
94 
CompositeAdapter(final Context context)95     public CompositeAdapter(final Context context) {
96         mContext = context;
97         mObserver = new Observer();
98         mPartitions = new Partition[INITIAL_CAPACITY];
99     }
100 
getContext()101     public Context getContext() {
102         return mContext;
103     }
104 
addPartition(final Partition partition)105     public void addPartition(final Partition partition) {
106         if (mSize >= mPartitions.length) {
107             final int newCapacity = mSize + 2;
108             final Partition[] newAdapters = new Partition[newCapacity];
109             System.arraycopy(mPartitions, 0, newAdapters, 0, mSize);
110             mPartitions = newAdapters;
111         }
112         mPartitions[mSize++] = partition;
113         partition.getAdapter().registerDataSetObserver(mObserver);
114         invalidate();
115         notifyDataSetChanged();
116     }
117 
removePartition(final int index)118     public void removePartition(final int index) {
119         final Partition partition = mPartitions[index];
120         partition.close();
121         System.arraycopy(mPartitions, index + 1, mPartitions, index,
122                 mSize - index - 1);
123         mSize--;
124         partition.getAdapter().unregisterDataSetObserver(mObserver);
125         invalidate();
126         notifyDataSetChanged();
127     }
128 
clearPartitions()129     public void clearPartitions() {
130         for (int i = 0; i < mSize; i++) {
131             final Partition partition = mPartitions[i];
132             partition.close();
133             partition.getAdapter().unregisterDataSetObserver(mObserver);
134         }
135         invalidate();
136         notifyDataSetChanged();
137     }
138 
getPartition(final int index)139     public Partition getPartition(final int index) {
140         return mPartitions[index];
141     }
142 
getPartitionAtPosition(final int position)143     public int getPartitionAtPosition(final int position) {
144         ensureCacheValid();
145         int start = 0;
146         for (int i = 0; i < mSize; i++) {
147             final int end = start + mPartitions[i].getCount();
148             if (position >= start && position < end) {
149                 int offset = position - start;
150                 if (mPartitions[i].hasHeader() &&
151                         (mPartitions[i].getCount() > 0 || mPartitions[i].showIfEmpty())) {
152                     offset--;
153                 }
154                 if (offset == -1) {
155                     return -1;
156                 }
157                 return i;
158             }
159             start = end;
160         }
161         return mSize - 1;
162     }
163 
getPartitionCount()164     public int getPartitionCount() {
165         return mSize;
166     }
167 
invalidate()168     public void invalidate() {
169         mCacheValid = false;
170     }
171 
ensureCacheValid()172     private void ensureCacheValid() {
173         if (mCacheValid) {
174             return;
175         }
176         mCount = 0;
177         for (int i = 0; i < mSize; i++) {
178             mCount += mPartitions[i].getCount();
179         }
180     }
181 
182     @Override
getCount()183     public int getCount() {
184         ensureCacheValid();
185         return mCount;
186     }
187 
getCount(final int index)188     public int getCount(final int index) {
189         ensureCacheValid();
190         return mPartitions[index].getCount();
191     }
192 
193     @Override
getItem(final int position)194     public Object getItem(final int position) {
195         ensureCacheValid();
196         int start = 0;
197         for (int i = 0; i < mSize; i++) {
198             final int end = start + mPartitions[i].getCount();
199             if (position >= start && position < end) {
200                 final int offset = position - start;
201                 final Partition partition = mPartitions[i];
202                 if (partition.hasHeader() && offset == 0 &&
203                         (partition.getCount() > 0 || partition.showIfEmpty())) {
204                     // This is the header
205                     return null;
206                 }
207                 return mPartitions[i].getAdapter().getItem(offset);
208             }
209             start = end;
210         }
211 
212         return null;
213     }
214 
215     @Override
getItemId(final int position)216     public long getItemId(final int position) {
217         ensureCacheValid();
218         int start = 0;
219         for (int i = 0; i < mSize; i++) {
220             final int end = start + mPartitions[i].getCount();
221             if (position >= start && position < end) {
222                 final int offset = position - start;
223                 final Partition partition = mPartitions[i];
224                 if (partition.hasHeader() && offset == 0 &&
225                         (partition.getCount() > 0 || partition.showIfEmpty())) {
226                     // Header
227                     return 0;
228                 }
229                 return mPartitions[i].getAdapter().getItemId(offset);
230             }
231             start = end;
232         }
233 
234         return 0;
235     }
236 
237     @Override
isEnabled(int position)238     public boolean isEnabled(int position) {
239         ensureCacheValid();
240         int start = 0;
241         for (int i = 0; i < mSize; i++) {
242             final int end = start + mPartitions[i].getCount();
243             if (position >= start && position < end) {
244                 final int offset = position - start;
245                 final Partition partition = mPartitions[i];
246                 if (partition.hasHeader() && offset == 0 &&
247                         (partition.getCount() > 0 || partition.showIfEmpty())) {
248                     // This is the header
249                     return false;
250                 }
251                 return true;
252             }
253             start = end;
254         }
255         return true;
256     }
257 
258     @Override
getView(final int position, final View convertView, final ViewGroup parentView)259     public View getView(final int position, final View convertView, final ViewGroup parentView) {
260         ensureCacheValid();
261         int start = 0;
262         for (int i = 0; i < mSize; i++) {
263             final Partition partition = mPartitions[i];
264             final int end = start + partition.getCount();
265             if (position >= start && position < end) {
266                 int offset = position - start;
267                 View view;
268                 if (partition.hasHeader() &&
269                         (partition.getCount() > 0 || partition.showIfEmpty())) {
270                     offset = offset - 1;
271                 }
272                 if (offset == -1) {
273                     view = partition.getHeaderView(convertView, parentView);
274                 } else {
275                     view = partition.getAdapter().getView(offset, convertView, parentView);
276                 }
277                 if (view == null) {
278                     throw new NullPointerException("View should not be null, partition: " + i
279                             + " position: " + offset);
280                 }
281                 return view;
282             }
283             start = end;
284         }
285 
286         throw new ArrayIndexOutOfBoundsException(position);
287     }
288 }
289