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 package com.android.mail.browse;
19 
20 import android.database.DataSetObserver;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.widget.BaseAdapter;
24 import android.widget.ListAdapter;
25 
26 import java.util.Arrays;
27 import java.util.List;
28 
29 /**
30  * An adapter that combines items from multiple provided adapters into a single list.
31  *
32  * @param <T> the class of each constituent adapter
33  */
34 public class MergedAdapter<T extends ListAdapter> extends BaseAdapter {
35 
36     private List<T> mAdapters;
37     private final DataSetObserver mObserver;
38 
39     public static class LocalAdapterPosition<T extends ListAdapter> {
40         private final T mAdapter;
41         private final int mLocalPosition;
42 
LocalAdapterPosition(T adapter, int offset)43         public LocalAdapterPosition(T adapter, int offset) {
44             mAdapter = adapter;
45             mLocalPosition = offset;
46         }
47 
getAdapter()48         public T getAdapter() {
49             return mAdapter;
50         }
51 
getLocalPosition()52         public int getLocalPosition() {
53             return mLocalPosition;
54         }
55     }
56 
MergedAdapter()57     public MergedAdapter() {
58         mObserver = new DataSetObserver() {
59             @Override
60             public void onChanged() {
61                 notifyDataSetChanged();
62             }
63         };
64     }
65 
setAdapters(T... adapters)66     public void setAdapters(T... adapters) {
67         if (mAdapters != null) {
68             for (T adapter : mAdapters) {
69                 adapter.unregisterDataSetObserver(mObserver);
70             }
71         }
72 
73         mAdapters = Arrays.asList(adapters);
74 
75         for (T adapter : mAdapters) {
76             adapter.registerDataSetObserver(mObserver);
77         }
78     }
79 
getSubAdapterCount()80     public int getSubAdapterCount() {
81         return mAdapters.size();
82     }
83 
getSubAdapter(int index)84     public T getSubAdapter(int index) {
85         return mAdapters.get(index);
86     }
87 
88     @Override
getCount()89     public int getCount() {
90         int count = 0;
91         for (T adapter : mAdapters) {
92             count += adapter.getCount();
93         }
94         return count;
95         // TODO: cache counts until next onChanged
96     }
97 
98     /**
99      * For a given merged position, find the corresponding Adapter and local position within that
100      * Adapter by iterating through Adapters and summing their counts until the merged position is
101      * found.
102      *
103      * @param position a merged (global) position
104      * @return the matching Adapter and local position, or null if not found
105      */
getAdapterOffsetForItem(final int position)106     public LocalAdapterPosition<T> getAdapterOffsetForItem(final int position) {
107         final int adapterCount = mAdapters.size();
108         int i = 0;
109         int count = 0;
110 
111         while (i < adapterCount) {
112             T a = mAdapters.get(i);
113             int newCount = count + a.getCount();
114             if (position < newCount) {
115                 return new LocalAdapterPosition<T>(a, position - count);
116             }
117             count = newCount;
118             i++;
119         }
120         return null;
121     }
122 
123     @Override
getItem(int position)124     public Object getItem(int position) {
125         LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
126         if (result == null) {
127             return null;
128         }
129         return result.mAdapter.getItem(result.mLocalPosition);
130     }
131 
132     @Override
getItemId(int position)133     public long getItemId(int position) {
134         return position;
135     }
136 
137     @Override
getViewTypeCount()138     public int getViewTypeCount() {
139         int count = 0;
140         for (T adapter : mAdapters) {
141             count += adapter.getViewTypeCount();
142         }
143         return count;
144     }
145 
146     @Override
getItemViewType(int position)147     public int getItemViewType(int position) {
148         LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
149         int otherViewTypeCount = 0;
150         for (T adapter : mAdapters) {
151             if (adapter == result.mAdapter) {
152                 break;
153             }
154             otherViewTypeCount += adapter.getViewTypeCount();
155         }
156         int type = result.mAdapter.getItemViewType(result.mLocalPosition);
157         // Headers (negative types) are in a separate global namespace and their values should not
158         // be affected by preceding adapter view types.
159         if (type >= 0) {
160             type += otherViewTypeCount;
161         }
162         return type;
163     }
164 
165     @Override
getView(int position, View convertView, ViewGroup parent)166     public View getView(int position, View convertView, ViewGroup parent) {
167         LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
168         return result.mAdapter.getView(result.mLocalPosition, convertView, parent);
169     }
170 
171     @Override
areAllItemsEnabled()172     public boolean areAllItemsEnabled() {
173         boolean enabled = true;
174         for (T adapter : mAdapters) {
175             enabled &= adapter.areAllItemsEnabled();
176         }
177         return enabled;
178     }
179 
180     @Override
isEnabled(int position)181     public boolean isEnabled(int position) {
182         LocalAdapterPosition<T> result = getAdapterOffsetForItem(position);
183         return result.mAdapter.isEnabled(result.mLocalPosition);
184     }
185 
186 }