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 package com.android.contacts.list; 18 19 import com.android.contacts.R; 20 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.database.DataSetObserver; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.widget.BaseAdapter; 27 import android.widget.FrameLayout; 28 import android.widget.SectionIndexer; 29 30 /** 31 * An adapter that combines items from {@link ContactTileAdapter} and 32 * {@link ContactEntryListAdapter} into a single list. In between those two results, 33 * an account filter header will be inserted. 34 */ 35 public class PhoneFavoriteMergedAdapter extends BaseAdapter implements SectionIndexer { 36 37 private class CustomDataSetObserver extends DataSetObserver { 38 @Override 39 public void onChanged() { 40 notifyDataSetChanged(); 41 } 42 } 43 44 private final ContactTileAdapter mContactTileAdapter; 45 private final ContactEntryListAdapter mContactEntryListAdapter; 46 private final View mAccountFilterHeaderContainer; 47 private final View mLoadingView; 48 49 private final int mItemPaddingLeft; 50 private final int mItemPaddingRight; 51 52 // Make frequent header consistent with account filter header. 53 private final int mFrequentHeaderPaddingTop; 54 55 private final DataSetObserver mObserver; 56 57 public PhoneFavoriteMergedAdapter(Context context, 58 ContactTileAdapter contactTileAdapter, 59 View accountFilterHeaderContainer, 60 ContactEntryListAdapter contactEntryListAdapter, 61 View loadingView) { 62 Resources resources = context.getResources(); 63 mItemPaddingLeft = resources.getDimensionPixelSize(R.dimen.detail_item_side_margin); 64 mItemPaddingRight = resources.getDimensionPixelSize(R.dimen.list_visible_scrollbar_padding); 65 mFrequentHeaderPaddingTop = resources.getDimensionPixelSize( 66 R.dimen.contact_browser_list_top_margin); 67 mContactTileAdapter = contactTileAdapter; 68 mContactEntryListAdapter = contactEntryListAdapter; 69 70 mAccountFilterHeaderContainer = accountFilterHeaderContainer; 71 72 mObserver = new CustomDataSetObserver(); 73 mContactTileAdapter.registerDataSetObserver(mObserver); 74 mContactEntryListAdapter.registerDataSetObserver(mObserver); 75 76 mLoadingView = loadingView; 77 } 78 79 @Override 80 public boolean isEmpty() { 81 // Cannot use the super's method here because we add extra rows in getCount() to account 82 // for headers 83 return mContactTileAdapter.getCount() + mContactEntryListAdapter.getCount() == 0; 84 } 85 86 @Override 87 public int getCount() { 88 final int contactTileAdapterCount = mContactTileAdapter.getCount(); 89 final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); 90 if (mContactEntryListAdapter.isLoading()) { 91 // Hide "all" contacts during its being loaded. Instead show "loading" view. 92 // 93 // "+2" for mAccountFilterHeaderContainer and mLoadingView 94 return contactTileAdapterCount + 2; 95 } else { 96 // "+1" for mAccountFilterHeaderContainer 97 return contactTileAdapterCount + contactEntryListAdapterCount + 1; 98 } 99 } 100 101 @Override 102 public Object getItem(int position) { 103 final int contactTileAdapterCount = mContactTileAdapter.getCount(); 104 final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); 105 if (position < contactTileAdapterCount) { // For "tile" and "frequent" sections 106 return mContactTileAdapter.getItem(position); 107 } else if (position == contactTileAdapterCount) { // For "all" section's account header 108 return mAccountFilterHeaderContainer; 109 } else { // For "all" section 110 if (mContactEntryListAdapter.isLoading()) { // "All" section is being loaded. 111 return mLoadingView; 112 } else { 113 // "-1" for mAccountFilterHeaderContainer 114 final int localPosition = position - contactTileAdapterCount - 1; 115 return mContactTileAdapter.getItem(localPosition); 116 } 117 } 118 } 119 120 @Override 121 public long getItemId(int position) { 122 return position; 123 } 124 125 @Override 126 public int getViewTypeCount() { 127 // "+2" for mAccountFilterHeaderContainer and mLoadingView 128 return (mContactTileAdapter.getViewTypeCount() 129 + mContactEntryListAdapter.getViewTypeCount() 130 + 2); 131 } 132 133 @Override 134 public int getItemViewType(int position) { 135 final int contactTileAdapterCount = mContactTileAdapter.getCount(); 136 final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); 137 // There should be four kinds of types that are usually used, and one more exceptional 138 // type (IGNORE_ITEM_VIEW_TYPE), which sometimes comes from mContactTileAdapter. 139 // 140 // The four ordinary view types have the index equal to or more than 0, and less than 141 // mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount() + 2. 142 // (See also this class's getViewTypeCount()) 143 // 144 // We have those values for: 145 // - The view types mContactTileAdapter originally has 146 // - The view types mContactEntryListAdapter originally has 147 // - mAccountFilterHeaderContainer ("all" section's account header), and 148 // - mLoadingView 149 // 150 // Those types should not be mixed, so we have a different range for each kinds of types: 151 // - Types for mContactTileAdapter ("tile" and "frequent" sections) 152 // They should have the index, >=0 and <mContactTileAdapter.getViewTypeCount() 153 // 154 // - Types for mContactEntryListAdapter ("all" sections) 155 // They should have the index, >=mContactTileAdapter.getViewTypeCount() and 156 // <(mContactTileAdapter.getViewTypeCount() + mContactEntryListAdapter.getViewTypeCount()) 157 // 158 // - Type for "all" section's account header 159 // It should have the exact index 160 // mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount() 161 // 162 // - Type for "loading" view used during "all" section is being loaded. 163 // It should have the exact index 164 // mContactTileAdapter.getViewTypeCount()+ mContactEntryListAdapter.getViewTypeCount() + 1 165 // 166 // As an exception, IGNORE_ITEM_VIEW_TYPE (-1) will be remained as is, which will be used 167 // by framework's Adapter implementation and thus should be left as is. 168 if (position < contactTileAdapterCount) { // For "tile" and "frequent" sections 169 return mContactTileAdapter.getItemViewType(position); 170 } else if (position == contactTileAdapterCount) { // For "all" section's account header 171 return mContactTileAdapter.getViewTypeCount() 172 + mContactEntryListAdapter.getViewTypeCount(); 173 } else { // For "all" section 174 if (mContactEntryListAdapter.isLoading()) { // "All" section is being loaded. 175 return mContactTileAdapter.getViewTypeCount() 176 + mContactEntryListAdapter.getViewTypeCount() + 1; 177 } else { 178 // "-1" for mAccountFilterHeaderContainer 179 final int localPosition = position - contactTileAdapterCount - 1; 180 final int type = mContactEntryListAdapter.getItemViewType(localPosition); 181 // IGNORE_ITEM_VIEW_TYPE must be handled differently. 182 return (type < 0) ? type : type + mContactTileAdapter.getViewTypeCount(); 183 } 184 } 185 } 186 187 @Override 188 public View getView(int position, View convertView, ViewGroup parent) { 189 final int contactTileAdapterCount = mContactTileAdapter.getCount(); 190 final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); 191 192 // Obtain a View relevant for that position, and adjust its horizontal padding. Each 193 // View has different implementation, so we use different way to control those padding. 194 if (position < contactTileAdapterCount) { // For "tile" and "frequent" sections 195 final View view = mContactTileAdapter.getView(position, convertView, parent); 196 final int frequentHeaderPosition = mContactTileAdapter.getFrequentHeaderPosition(); 197 if (position < frequentHeaderPosition) { // "starred" contacts 198 // No padding adjustment. 199 } else if (position == frequentHeaderPosition) { 200 view.setPadding(mItemPaddingLeft, mFrequentHeaderPaddingTop, 201 mItemPaddingRight, view.getPaddingBottom()); 202 } else { 203 // Views for "frequent" contacts use FrameLayout's margins instead of padding. 204 final FrameLayout frameLayout = (FrameLayout) view; 205 final View child = frameLayout.getChildAt(0); 206 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( 207 FrameLayout.LayoutParams.WRAP_CONTENT, 208 FrameLayout.LayoutParams.WRAP_CONTENT); 209 params.setMargins(mItemPaddingLeft, 0, mItemPaddingRight, 0); 210 child.setLayoutParams(params); 211 } 212 return view; 213 } else if (position == contactTileAdapterCount) { // For "all" section's account header 214 mAccountFilterHeaderContainer.setPadding(mItemPaddingLeft, 215 mAccountFilterHeaderContainer.getPaddingTop(), 216 mItemPaddingRight, 217 mAccountFilterHeaderContainer.getPaddingBottom()); 218 return mAccountFilterHeaderContainer; 219 } else { // For "all" section 220 if (mContactEntryListAdapter.isLoading()) { // "All" section is being loaded. 221 mLoadingView.setPadding(mItemPaddingLeft, 222 mLoadingView.getPaddingTop(), 223 mItemPaddingRight, 224 mLoadingView.getPaddingBottom()); 225 return mLoadingView; 226 } else { 227 // "-1" for mAccountFilterHeaderContainer 228 final int localPosition = position - contactTileAdapterCount - 1; 229 final ContactListItemView itemView = (ContactListItemView) 230 mContactEntryListAdapter.getView(localPosition, convertView, null); 231 itemView.setPadding(mItemPaddingLeft, itemView.getPaddingTop(), 232 mItemPaddingRight, itemView.getPaddingBottom()); 233 itemView.setSelectionBoundsHorizontalMargin(mItemPaddingLeft, mItemPaddingRight); 234 return itemView; 235 } 236 } 237 } 238 239 @Override 240 public boolean areAllItemsEnabled() { 241 // If "all" section is being loaded we'll show mLoadingView, which is not enabled. 242 // Otherwise check the all the other components in the ListView and return appropriate 243 // result. 244 return !mContactEntryListAdapter.isLoading() 245 && (mContactTileAdapter.areAllItemsEnabled() 246 && mAccountFilterHeaderContainer.isEnabled() 247 && mContactEntryListAdapter.areAllItemsEnabled()); 248 } 249 250 @Override 251 public boolean isEnabled(int position) { 252 final int contactTileAdapterCount = mContactTileAdapter.getCount(); 253 final int contactEntryListAdapterCount = mContactEntryListAdapter.getCount(); 254 if (position < contactTileAdapterCount) { // For "tile" and "frequent" sections 255 return mContactTileAdapter.isEnabled(position); 256 } else if (position == contactTileAdapterCount) { // For "all" section's account header 257 // This will be handled by View's onClick event instead of ListView's onItemClick event. 258 return false; 259 } else { // For "all" section 260 if (mContactEntryListAdapter.isLoading()) { // "All" section is being loaded. 261 return false; 262 } else { 263 // "-1" for mAccountFilterHeaderContainer 264 final int localPosition = position - contactTileAdapterCount - 1; 265 return mContactEntryListAdapter.isEnabled(localPosition); 266 } 267 } 268 } 269 270 @Override 271 public int getPositionForSection(int sectionIndex) { 272 final int contactTileAdapterCount = mContactTileAdapter.getCount(); 273 final int localPosition = mContactEntryListAdapter.getPositionForSection(sectionIndex); 274 return contactTileAdapterCount + 1 + localPosition; 275 } 276 277 @Override 278 public int getSectionForPosition(int position) { 279 final int contactTileAdapterCount = mContactTileAdapter.getCount(); 280 if (position <= contactTileAdapterCount) { 281 return 0; 282 } else { 283 // "-1" for mAccountFilterHeaderContainer 284 final int localPosition = position - contactTileAdapterCount - 1; 285 return mContactEntryListAdapter.getSectionForPosition(localPosition); 286 } 287 } 288 289 @Override 290 public Object[] getSections() { 291 return mContactEntryListAdapter.getSections(); 292 } 293 294 public boolean shouldShowFirstScroller(int firstVisibleItem) { 295 final int contactTileAdapterCount = mContactTileAdapter.getCount(); 296 return firstVisibleItem > contactTileAdapterCount; 297 } 298 }