1/*
2 * Copyright (C) 2011 Google Inc.
3 * Licensed to The Android Open Source Project.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18
19package com.android.mailcommon;
20
21import android.database.DataSetObserver;
22import android.view.View;
23import android.view.ViewGroup;
24import android.widget.BaseAdapter;
25import android.widget.ListAdapter;
26import android.widget.SpinnerAdapter;
27
28import java.util.Arrays;
29import java.util.List;
30
31/**
32 * An adapter that combines items from multiple provided adapters into a single list.
33 *
34 * @param <T> the class of each constituent adapter
35 */
36public class MergedAdapter<T extends MergedAdapter.ListSpinnerAdapter> extends BaseAdapter {
37
38    private List<T> mAdapters;
39    private final DataSetObserver mObserver;
40
41    /**
42     * A Mergeable adapter must implement both ListAdapter and SpinnerAdapter to be useful in lists
43     * and spinners.
44     */
45    public interface ListSpinnerAdapter extends ListAdapter, SpinnerAdapter {
46    }
47
48    public static class LocalAdapterPosition<T extends ListSpinnerAdapter> {
49        public final T mAdapter;
50        public final int mLocalPosition;
51
52        public LocalAdapterPosition(T adapter, int offset) {
53            mAdapter = adapter;
54            mLocalPosition = offset;
55        }
56    }
57
58    public MergedAdapter() {
59        mObserver = new DataSetObserver() {
60            @Override
61            public void onChanged() {
62                notifyDataSetChanged();
63            }
64        };
65    }
66
67    public void setAdapters(T... adapters) {
68        if (mAdapters != null) {
69            for (T adapter : mAdapters) {
70                adapter.unregisterDataSetObserver(mObserver);
71            }
72        }
73
74        mAdapters = Arrays.asList(adapters);
75
76        for (T adapter : mAdapters) {
77            adapter.registerDataSetObserver(mObserver);
78        }
79    }
80
81    public int getSubAdapterCount() {
82        return mAdapters.size();
83    }
84
85    public T getSubAdapter(int index) {
86        return mAdapters.get(index);
87    }
88
89    @Override
90    public int getCount() {
91        int count = 0;
92        for (T adapter : mAdapters) {
93            count += adapter.getCount();
94        }
95        return count;
96        // TODO: cache counts until next onChanged
97    }
98
99    /**
100     * For a given merged position, find the corresponding Adapter and local position within that
101     * Adapter by iterating through Adapters and summing their counts until the merged position is
102     * found.
103     *
104     * @param position a merged (global) position
105     * @return the matching Adapter and local position, or null if not found
106     */
107    public LocalAdapterPosition<T> getAdapterOffsetForItem(final int position) {
108        final int adapterCount = mAdapters.size();
109        int i = 0;
110        int count = 0;
111
112        while (i < adapterCount) {
113            T a = mAdapters.get(i);
114            int newCount = count + a.getCount();
115            if (position < newCount) {
116                return new LocalAdapterPosition<T>(a, position - count);
117            }
118            count = newCount;
119            i++;
120        }
121        return null;
122    }
123
124    @Override
125    public Object getItem(int position) {
126        LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
127        if (result == null) {
128            return null;
129        }
130        return result.mAdapter.getItem(result.mLocalPosition);
131    }
132
133    @Override
134    public long getItemId(int position) {
135        return position;
136    }
137
138    @Override
139    public int getViewTypeCount() {
140        int count = 0;
141        for (T adapter : mAdapters) {
142            count += adapter.getViewTypeCount();
143        }
144        return count;
145    }
146
147    @Override
148    public int getItemViewType(int position) {
149        LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
150        int otherViewTypeCount = 0;
151        for (T adapter : mAdapters) {
152            if (adapter == result.mAdapter) {
153                break;
154            }
155            otherViewTypeCount += adapter.getViewTypeCount();
156        }
157        int type = result.mAdapter.getItemViewType(result.mLocalPosition);
158        // Headers (negative types) are in a separate global namespace and their values should not
159        // be affected by preceding adapter view types.
160        if (type >= 0) {
161            type += otherViewTypeCount;
162        }
163        return type;
164    }
165
166    @Override
167    public View getView(int position, View convertView, ViewGroup parent) {
168        LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
169        return result.mAdapter.getView(result.mLocalPosition, convertView, parent);
170    }
171
172    @Override
173    public View getDropDownView(int position, View convertView, ViewGroup parent) {
174        LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
175        return result.mAdapter.getDropDownView(result.mLocalPosition, convertView, parent);
176    }
177
178    @Override
179    public boolean areAllItemsEnabled() {
180        boolean enabled = true;
181        for (T adapter : mAdapters) {
182            enabled &= adapter.areAllItemsEnabled();
183        }
184        return enabled;
185    }
186
187    @Override
188    public boolean isEnabled(int position) {
189        LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
190        return result.mAdapter.isEnabled(result.mLocalPosition);
191    }
192
193}