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 */
16package com.android.messaging.ui;
17
18import android.content.Context;
19import android.database.DataSetObserver;
20import android.view.View;
21import android.view.ViewGroup;
22import 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 */
28public 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
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         */
47        public boolean showIfEmpty() {
48            return mShowIfEmpty;
49        }
50
51        public boolean hasHeader() {
52            return mHasHeader;
53        }
54
55        public int getCount() {
56            int count = mAdapter.getCount();
57            if (mHasHeader && (count != 0 || mShowIfEmpty)) {
58                count++;
59            }
60            return count;
61        }
62
63        public BaseAdapter getAdapter() {
64            return mAdapter;
65        }
66
67        public View getHeaderView(final View convertView, final ViewGroup parentView) {
68            return null;
69        }
70
71        public void close() {
72            // do nothing in base class.
73        }
74    }
75
76    private class Observer extends DataSetObserver {
77        @Override
78        public void onChanged() {
79            CompositeAdapter.this.notifyDataSetChanged();
80        }
81
82        @Override
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
95    public CompositeAdapter(final Context context) {
96        mContext = context;
97        mObserver = new Observer();
98        mPartitions = new Partition[INITIAL_CAPACITY];
99    }
100
101    public Context getContext() {
102        return mContext;
103    }
104
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
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
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
139    public Partition getPartition(final int index) {
140        return mPartitions[index];
141    }
142
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
164    public int getPartitionCount() {
165        return mSize;
166    }
167
168    public void invalidate() {
169        mCacheValid = false;
170    }
171
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
183    public int getCount() {
184        ensureCacheValid();
185        return mCount;
186    }
187
188    public int getCount(final int index) {
189        ensureCacheValid();
190        return mPartitions[index].getCount();
191    }
192
193    @Override
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
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
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
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