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