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