• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 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.contacts.common.list;
17 
18 import android.content.ContentUris;
19 import android.content.Context;
20 import android.content.res.Resources;
21 import android.database.Cursor;
22 import android.graphics.drawable.Drawable;
23 import android.net.Uri;
24 import android.provider.ContactsContract.CommonDataKinds.Phone;
25 import android.provider.ContactsContract.Contacts;
26 import android.view.View;
27 import android.view.ViewGroup;
28 import android.widget.BaseAdapter;
29 import android.widget.FrameLayout;
30 
31 import com.android.contacts.common.ContactPhotoManager;
32 import com.android.contacts.common.ContactPresenceIconUtil;
33 import com.android.contacts.common.ContactStatusUtil;
34 import com.android.contacts.common.ContactTileLoaderFactory;
35 import com.android.contacts.common.MoreContactUtils;
36 import com.android.contacts.common.R;
37 
38 import java.util.ArrayList;
39 
40 /**
41  * Arranges contacts favorites according to provided {@link DisplayType}.
42  * Also allows for a configurable number of columns and {@link DisplayType}
43  */
44 public class ContactTileAdapter extends BaseAdapter {
45     private static final String TAG = ContactTileAdapter.class.getSimpleName();
46 
47     private DisplayType mDisplayType;
48     private ContactTileView.Listener mListener;
49     private Context mContext;
50     private Resources mResources;
51     protected Cursor mContactCursor = null;
52     private ContactPhotoManager mPhotoManager;
53     protected int mNumFrequents;
54 
55     /**
56      * Index of the first NON starred contact in the {@link Cursor}
57      * Only valid when {@link DisplayType#STREQUENT} is true
58      */
59     private int mDividerPosition;
60     protected int mColumnCount;
61     private int mStarredIndex;
62 
63     protected int mIdIndex;
64     protected int mLookupIndex;
65     protected int mPhotoUriIndex;
66     protected int mNameIndex;
67     protected int mPresenceIndex;
68     protected int mStatusIndex;
69 
70     /**
71      * Only valid when {@link DisplayType#STREQUENT_PHONE_ONLY} is true
72      */
73     private int mPhoneNumberIndex;
74     private int mPhoneNumberTypeIndex;
75     private int mPhoneNumberLabelIndex;
76 
77     private boolean mIsQuickContactEnabled = false;
78     private final int mPaddingInPixels;
79 
80     /**
81      * Configures the adapter to filter and display contacts using different view types.
82      * TODO: Create Uris to support getting Starred_only and Frequent_only cursors.
83      */
84     public enum DisplayType {
85         /**
86          * Displays a mixed view type of starred and frequent contacts
87          */
88         STREQUENT,
89 
90         /**
91          * Displays a mixed view type of starred and frequent contacts based on phone data.
92          * Also includes secondary touch target.
93          */
94         STREQUENT_PHONE_ONLY,
95 
96         /**
97          * Display only starred contacts
98          */
99         STARRED_ONLY,
100 
101         /**
102          * Display only most frequently contacted
103          */
104         FREQUENT_ONLY,
105 
106         /**
107          * Display all contacts from a group in the cursor
108          * Use {@link com.android.contacts.GroupMemberLoader}
109          * when passing {@link Cursor} into loadFromCusor method.
110          *
111          * Group member logic has been moved into GroupMemberTileAdapter.  This constant is still
112          * needed by calling classes.
113          */
114         GROUP_MEMBERS
115     }
116 
ContactTileAdapter(Context context, ContactTileView.Listener listener, int numCols, DisplayType displayType)117     public ContactTileAdapter(Context context, ContactTileView.Listener listener, int numCols,
118             DisplayType displayType) {
119         mListener = listener;
120         mContext = context;
121         mResources = context.getResources();
122         mColumnCount = (displayType == DisplayType.FREQUENT_ONLY ? 1 : numCols);
123         mDisplayType = displayType;
124         mNumFrequents = 0;
125 
126         // Converting padding in dips to padding in pixels
127         mPaddingInPixels = mContext.getResources()
128                 .getDimensionPixelSize(R.dimen.contact_tile_divider_padding);
129 
130         bindColumnIndices();
131     }
132 
setPhotoLoader(ContactPhotoManager photoLoader)133     public void setPhotoLoader(ContactPhotoManager photoLoader) {
134         mPhotoManager = photoLoader;
135     }
136 
setColumnCount(int columnCount)137     public void setColumnCount(int columnCount) {
138         mColumnCount = columnCount;
139     }
140 
setDisplayType(DisplayType displayType)141     public void setDisplayType(DisplayType displayType) {
142         mDisplayType = displayType;
143     }
144 
enableQuickContact(boolean enableQuickContact)145     public void enableQuickContact(boolean enableQuickContact) {
146         mIsQuickContactEnabled = enableQuickContact;
147     }
148 
149     /**
150      * Sets the column indices for expected {@link Cursor}
151      * based on {@link DisplayType}.
152      */
bindColumnIndices()153     protected void bindColumnIndices() {
154         mIdIndex = ContactTileLoaderFactory.CONTACT_ID;
155         mLookupIndex = ContactTileLoaderFactory.LOOKUP_KEY;
156         mPhotoUriIndex = ContactTileLoaderFactory.PHOTO_URI;
157         mNameIndex = ContactTileLoaderFactory.DISPLAY_NAME;
158         mStarredIndex = ContactTileLoaderFactory.STARRED;
159         mPresenceIndex = ContactTileLoaderFactory.CONTACT_PRESENCE;
160         mStatusIndex = ContactTileLoaderFactory.CONTACT_STATUS;
161 
162         mPhoneNumberIndex = ContactTileLoaderFactory.PHONE_NUMBER;
163         mPhoneNumberTypeIndex = ContactTileLoaderFactory.PHONE_NUMBER_TYPE;
164         mPhoneNumberLabelIndex = ContactTileLoaderFactory.PHONE_NUMBER_LABEL;
165     }
166 
167     /**
168      * Gets the number of frequents from the passed in cursor.
169      *
170      * This methods is needed so the GroupMemberTileAdapter can override this.
171      *
172      * @param cursor The cursor to get number of frequents from.
173      */
saveNumFrequentsFromCursor(Cursor cursor)174     protected void saveNumFrequentsFromCursor(Cursor cursor) {
175 
176         // count the number of frequents
177         switch (mDisplayType) {
178             case STARRED_ONLY:
179                 mNumFrequents = 0;
180                 break;
181             case STREQUENT:
182             case STREQUENT_PHONE_ONLY:
183                 mNumFrequents = cursor.getCount() - mDividerPosition;
184                 break;
185             case FREQUENT_ONLY:
186                 mNumFrequents = cursor.getCount();
187                 break;
188             default:
189                 throw new IllegalArgumentException("Unrecognized DisplayType " + mDisplayType);
190         }
191     }
192 
193     /**
194      * Creates {@link ContactTileView}s for each item in {@link Cursor}.
195      *
196      * Else use {@link ContactTileLoaderFactory}
197      */
setContactCursor(Cursor cursor)198     public void setContactCursor(Cursor cursor) {
199         mContactCursor = cursor;
200         mDividerPosition = getDividerPosition(cursor);
201 
202         saveNumFrequentsFromCursor(cursor);
203 
204         // cause a refresh of any views that rely on this data
205         notifyDataSetChanged();
206     }
207 
208     /**
209      * Iterates over the {@link Cursor}
210      * Returns position of the first NON Starred Contact
211      * Returns -1 if {@link DisplayType#STARRED_ONLY}
212      * Returns 0 if {@link DisplayType#FREQUENT_ONLY}
213      */
getDividerPosition(Cursor cursor)214     protected int getDividerPosition(Cursor cursor) {
215         if (cursor == null || cursor.isClosed()) {
216             throw new IllegalStateException("Unable to access cursor");
217         }
218 
219         switch (mDisplayType) {
220             case STREQUENT:
221             case STREQUENT_PHONE_ONLY:
222                 cursor.moveToPosition(-1);
223                 while (cursor.moveToNext()) {
224                     if (cursor.getInt(mStarredIndex) == 0) {
225                         return cursor.getPosition();
226                     }
227                 }
228                 break;
229             case STARRED_ONLY:
230                 // There is no divider
231                 return -1;
232             case FREQUENT_ONLY:
233                 // Divider is first
234                 return 0;
235             default:
236                 throw new IllegalStateException("Unrecognized DisplayType " + mDisplayType);
237         }
238 
239         // There are not NON Starred contacts in cursor
240         // Set divider positon to end
241         return cursor.getCount();
242     }
243 
createContactEntryFromCursor(Cursor cursor, int position)244     protected ContactEntry createContactEntryFromCursor(Cursor cursor, int position) {
245         // If the loader was canceled we will be given a null cursor.
246         // In that case, show an empty list of contacts.
247         if (cursor == null || cursor.isClosed() || cursor.getCount() <= position) return null;
248 
249         cursor.moveToPosition(position);
250         long id = cursor.getLong(mIdIndex);
251         String photoUri = cursor.getString(mPhotoUriIndex);
252         String lookupKey = cursor.getString(mLookupIndex);
253 
254         ContactEntry contact = new ContactEntry();
255         String name = cursor.getString(mNameIndex);
256         contact.name = (name != null) ? name : mResources.getString(R.string.missing_name);
257         contact.status = cursor.getString(mStatusIndex);
258         contact.photoUri = (photoUri != null ? Uri.parse(photoUri) : null);
259         contact.lookupKey = ContentUris.withAppendedId(
260                 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), id);
261 
262         // Set phone number and label
263         if (mDisplayType == DisplayType.STREQUENT_PHONE_ONLY) {
264             int phoneNumberType = cursor.getInt(mPhoneNumberTypeIndex);
265             String phoneNumberCustomLabel = cursor.getString(mPhoneNumberLabelIndex);
266             contact.phoneLabel = (String) Phone.getTypeLabel(mResources, phoneNumberType,
267                     phoneNumberCustomLabel);
268             contact.phoneNumber = cursor.getString(mPhoneNumberIndex);
269         } else {
270             // Set presence icon and status message
271             Drawable icon = null;
272             int presence = 0;
273             if (!cursor.isNull(mPresenceIndex)) {
274                 presence = cursor.getInt(mPresenceIndex);
275                 icon = ContactPresenceIconUtil.getPresenceIcon(mContext, presence);
276             }
277             contact.presenceIcon = icon;
278 
279             String statusMessage = null;
280             if (mStatusIndex != 0 && !cursor.isNull(mStatusIndex)) {
281                 statusMessage = cursor.getString(mStatusIndex);
282             }
283             // If there is no status message from the contact, but there was a presence value,
284             // then use the default status message string
285             if (statusMessage == null && presence != 0) {
286                 statusMessage = ContactStatusUtil.getStatusString(mContext, presence);
287             }
288             contact.status = statusMessage;
289         }
290 
291         return contact;
292     }
293 
294     /**
295      * Returns the number of frequents that will be displayed in the list.
296      */
getNumFrequents()297     public int getNumFrequents() {
298         return mNumFrequents;
299     }
300 
301     @Override
getCount()302     public int getCount() {
303         if (mContactCursor == null || mContactCursor.isClosed()) {
304             return 0;
305         }
306 
307         switch (mDisplayType) {
308             case STARRED_ONLY:
309                 return getRowCount(mContactCursor.getCount());
310             case STREQUENT:
311             case STREQUENT_PHONE_ONLY:
312                 // Takes numbers of rows the Starred Contacts Occupy
313                 int starredRowCount = getRowCount(mDividerPosition);
314 
315                 // Compute the frequent row count which is 1 plus the number of frequents
316                 // (to account for the divider) or 0 if there are no frequents.
317                 int frequentRowCount = mNumFrequents == 0 ? 0 : mNumFrequents + 1;
318 
319                 // Return the number of starred plus frequent rows
320                 return starredRowCount + frequentRowCount;
321             case FREQUENT_ONLY:
322                 // Number of frequent contacts
323                 return mContactCursor.getCount();
324             default:
325                 throw new IllegalArgumentException("Unrecognized DisplayType " + mDisplayType);
326         }
327     }
328 
329     /**
330      * Returns the number of rows required to show the provided number of entries
331      * with the current number of columns.
332      */
getRowCount(int entryCount)333     protected int getRowCount(int entryCount) {
334         return entryCount == 0 ? 0 : ((entryCount - 1) / mColumnCount) + 1;
335     }
336 
getColumnCount()337     public int getColumnCount() {
338         return mColumnCount;
339     }
340 
341     /**
342      * Returns an ArrayList of the {@link ContactEntry}s that are to appear
343      * on the row for the given position.
344      */
345     @Override
getItem(int position)346     public ArrayList<ContactEntry> getItem(int position) {
347         ArrayList<ContactEntry> resultList = new ArrayList<ContactEntry>(mColumnCount);
348         int contactIndex = position * mColumnCount;
349 
350         switch (mDisplayType) {
351             case FREQUENT_ONLY:
352                 resultList.add(createContactEntryFromCursor(mContactCursor, position));
353                 break;
354             case STARRED_ONLY:
355                 for (int columnCounter = 0; columnCounter < mColumnCount; columnCounter++) {
356                     resultList.add(createContactEntryFromCursor(mContactCursor, contactIndex));
357                     contactIndex++;
358                 }
359                 break;
360             case STREQUENT:
361             case STREQUENT_PHONE_ONLY:
362                 if (position < getRowCount(mDividerPosition)) {
363                     for (int columnCounter = 0; columnCounter < mColumnCount &&
364                             contactIndex != mDividerPosition; columnCounter++) {
365                         resultList.add(createContactEntryFromCursor(mContactCursor, contactIndex));
366                         contactIndex++;
367                     }
368                 } else {
369                     /*
370                      * Current position minus how many rows are before the divider and
371                      * Minus 1 for the divider itself provides the relative index of the frequent
372                      * contact being displayed. Then add the dividerPostion to give the offset
373                      * into the contacts cursor to get the absoulte index.
374                      */
375                     contactIndex = position - getRowCount(mDividerPosition) - 1 + mDividerPosition;
376                     resultList.add(createContactEntryFromCursor(mContactCursor, contactIndex));
377                 }
378                 break;
379             default:
380                 throw new IllegalStateException("Unrecognized DisplayType " + mDisplayType);
381         }
382         return resultList;
383     }
384 
385     @Override
getItemId(int position)386     public long getItemId(int position) {
387         // As we show several selectable items for each ListView row,
388         // we can not determine a stable id. But as we don't rely on ListView's selection,
389         // this should not be a problem.
390         return position;
391     }
392 
393     @Override
areAllItemsEnabled()394     public boolean areAllItemsEnabled() {
395         return (mDisplayType != DisplayType.STREQUENT &&
396                 mDisplayType != DisplayType.STREQUENT_PHONE_ONLY);
397     }
398 
399     @Override
isEnabled(int position)400     public boolean isEnabled(int position) {
401         return position != getRowCount(mDividerPosition);
402     }
403 
404     @Override
getView(int position, View convertView, ViewGroup parent)405     public View getView(int position, View convertView, ViewGroup parent) {
406         int itemViewType = getItemViewType(position);
407 
408         if (itemViewType == ViewTypes.DIVIDER) {
409             // Checking For Divider First so not to cast convertView
410             return convertView == null ? getDivider() : convertView;
411         }
412 
413         ContactTileRow contactTileRowView = (ContactTileRow) convertView;
414         ArrayList<ContactEntry> contactList = getItem(position);
415 
416         if (contactTileRowView == null) {
417             // Creating new row if needed
418             contactTileRowView = new ContactTileRow(mContext, itemViewType);
419         }
420 
421         contactTileRowView.configureRow(contactList, position == getCount() - 1);
422         return contactTileRowView;
423     }
424 
425     /**
426      * Divider uses a list_seperator.xml along with text to denote
427      * the most frequently contacted contacts.
428      */
getDivider()429     public View getDivider() {
430         return MoreContactUtils.createHeaderView(mContext,
431                 mDisplayType == DisplayType.STREQUENT_PHONE_ONLY ?
432                         R.string.favoritesFrequentCalled : R.string.favoritesFrequentContacted);
433     }
434 
getLayoutResourceId(int viewType)435     private int getLayoutResourceId(int viewType) {
436         switch (viewType) {
437             case ViewTypes.STARRED:
438                 return mIsQuickContactEnabled ?
439                         R.layout.contact_tile_starred_quick_contact : R.layout.contact_tile_starred;
440             case ViewTypes.FREQUENT:
441                 return mDisplayType == DisplayType.STREQUENT_PHONE_ONLY ?
442                         R.layout.contact_tile_frequent_phone : R.layout.contact_tile_frequent;
443             case ViewTypes.STARRED_PHONE:
444                 return R.layout.contact_tile_phone_starred;
445             default:
446                 throw new IllegalArgumentException("Unrecognized viewType " + viewType);
447         }
448     }
449     @Override
getViewTypeCount()450     public int getViewTypeCount() {
451         return ViewTypes.COUNT;
452     }
453 
454     @Override
getItemViewType(int position)455     public int getItemViewType(int position) {
456         /*
457          * Returns view type based on {@link DisplayType}.
458          * {@link DisplayType#STARRED_ONLY} and {@link DisplayType#GROUP_MEMBERS}
459          * are {@link ViewTypes#STARRED}.
460          * {@link DisplayType#FREQUENT_ONLY} is {@link ViewTypes#FREQUENT}.
461          * {@link DisplayType#STREQUENT} mixes both {@link ViewTypes}
462          * and also adds in {@link ViewTypes#DIVIDER}.
463          */
464         switch (mDisplayType) {
465             case STREQUENT:
466                 if (position < getRowCount(mDividerPosition)) {
467                     return ViewTypes.STARRED;
468                 } else if (position == getRowCount(mDividerPosition)) {
469                     return ViewTypes.DIVIDER;
470                 } else {
471                     return ViewTypes.FREQUENT;
472                 }
473             case STREQUENT_PHONE_ONLY:
474                 if (position < getRowCount(mDividerPosition)) {
475                     return ViewTypes.STARRED_PHONE;
476                  } else if (position == getRowCount(mDividerPosition)) {
477                     return ViewTypes.DIVIDER;
478                 } else {
479                     return ViewTypes.FREQUENT;
480                 }
481             case STARRED_ONLY:
482                 return ViewTypes.STARRED;
483             case FREQUENT_ONLY:
484                 return ViewTypes.FREQUENT;
485             default:
486                 throw new IllegalStateException("Unrecognized DisplayType " + mDisplayType);
487         }
488     }
489 
490     /**
491      * Returns the "frequent header" position. Only available when STREQUENT or
492      * STREQUENT_PHONE_ONLY is used for its display type.
493      */
getFrequentHeaderPosition()494     public int getFrequentHeaderPosition() {
495         return getRowCount(mDividerPosition);
496     }
497 
498     /**
499      * Acts as a row item composed of {@link ContactTileView}
500      *
501      * TODO: FREQUENT doesn't really need it.  Just let {@link #getView} return
502      */
503     private class ContactTileRow extends FrameLayout {
504         private int mItemViewType;
505         private int mLayoutResId;
506 
ContactTileRow(Context context, int itemViewType)507         public ContactTileRow(Context context, int itemViewType) {
508             super(context);
509             mItemViewType = itemViewType;
510             mLayoutResId = getLayoutResourceId(mItemViewType);
511 
512             // Remove row (but not children) from accessibility node tree.
513             setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
514         }
515 
516         /**
517          * Configures the row to add {@link ContactEntry}s information to the views
518          */
configureRow(ArrayList<ContactEntry> list, boolean isLastRow)519         public void configureRow(ArrayList<ContactEntry> list, boolean isLastRow) {
520             int columnCount = mItemViewType == ViewTypes.FREQUENT ? 1 : mColumnCount;
521 
522             // Adding tiles to row and filling in contact information
523             for (int columnCounter = 0; columnCounter < columnCount; columnCounter++) {
524                 ContactEntry entry =
525                         columnCounter < list.size() ? list.get(columnCounter) : null;
526                 addTileFromEntry(entry, columnCounter, isLastRow);
527             }
528         }
529 
530         private void addTileFromEntry(ContactEntry entry, int childIndex, boolean isLastRow) {
531             final ContactTileView contactTile;
532 
533             if (getChildCount() <= childIndex) {
534                 contactTile = (ContactTileView) inflate(mContext, mLayoutResId, null);
535                 // Note: the layoutparam set here is only actually used for FREQUENT.
536                 // We override onMeasure() for STARRED and we don't care the layout param there.
537                 Resources resources = mContext.getResources();
538                 FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(
539                         ViewGroup.LayoutParams.WRAP_CONTENT,
540                         ViewGroup.LayoutParams.WRAP_CONTENT);
541                 params.setMargins(
542                         resources.getDimensionPixelSize(R.dimen.detail_item_side_margin),
543                         0,
544                         resources.getDimensionPixelSize(R.dimen.detail_item_side_margin),
545                         0);
546                 contactTile.setLayoutParams(params);
547                 contactTile.setPhotoManager(mPhotoManager);
548                 contactTile.setListener(mListener);
549                 addView(contactTile);
550             } else {
551                 contactTile = (ContactTileView) getChildAt(childIndex);
552             }
553             contactTile.loadFromContact(entry);
554 
555             switch (mItemViewType) {
556                 case ViewTypes.STARRED_PHONE:
557                 case ViewTypes.STARRED:
558                     // Setting divider visibilities
559                     contactTile.setPaddingRelative(0, 0,
560                             childIndex >= mColumnCount - 1 ? 0 : mPaddingInPixels,
561                             isLastRow ? 0 : mPaddingInPixels);
562                     break;
563                 case ViewTypes.FREQUENT:
564                     contactTile.setHorizontalDividerVisibility(
565                             isLastRow ? View.GONE : View.VISIBLE);
566                     break;
567                 default:
568                     break;
569             }
570         }
571 
572         @Override
573         protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
574             switch (mItemViewType) {
575                 case ViewTypes.STARRED_PHONE:
576                 case ViewTypes.STARRED:
577                     onLayoutForTiles();
578                     return;
579                 default:
580                     super.onLayout(changed, left, top, right, bottom);
581                     return;
582             }
583         }
584 
585         private void onLayoutForTiles() {
586             final int count = getChildCount();
587 
588             // Just line up children horizontally.
589             int childLeft = 0;
590             for (int i = 0; i < count; i++) {
591                 final View child = getChildAt(i);
592 
593                 // Note MeasuredWidth includes the padding.
594                 final int childWidth = child.getMeasuredWidth();
595                 child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
596                 childLeft += childWidth;
597             }
598         }
599 
600         @Override
601         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
602             switch (mItemViewType) {
603                 case ViewTypes.STARRED_PHONE:
604                 case ViewTypes.STARRED:
605                     onMeasureForTiles(widthMeasureSpec);
606                     return;
607                 default:
608                     super.onMeasure(widthMeasureSpec, heightMeasureSpec);
609                     return;
610             }
611         }
612 
613         private void onMeasureForTiles(int widthMeasureSpec) {
614             final int width = MeasureSpec.getSize(widthMeasureSpec);
615 
616             final int childCount = getChildCount();
617             if (childCount == 0) {
618                 // Just in case...
619                 setMeasuredDimension(width, 0);
620                 return;
621             }
622 
623             // 1. Calculate image size.
624             //      = ([total width] - [total padding]) / [child count]
625             //
626             // 2. Set it to width/height of each children.
627             //    If we have a remainder, some tiles will have 1 pixel larger width than its height.
628             //
629             // 3. Set the dimensions of itself.
630             //    Let width = given width.
631             //    Let height = image size + bottom paddding.
632 
633             final int totalPaddingsInPixels = (mColumnCount - 1) * mPaddingInPixels;
634 
635             // Preferred width / height for images (excluding the padding).
636             // The actual width may be 1 pixel larger than this if we have a remainder.
637             final int imageSize = (width - totalPaddingsInPixels) / mColumnCount;
638             final int remainder = width - (imageSize * mColumnCount) - totalPaddingsInPixels;
639 
640             for (int i = 0; i < childCount; i++) {
641                 final View child = getChildAt(i);
642                 final int childWidth = imageSize + child.getPaddingRight()
643                         // Compensate for the remainder
644                         + (i < remainder ? 1 : 0);
645                 final int childHeight = imageSize + child.getPaddingBottom();
646                 child.measure(
647                         MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
648                         MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)
649                         );
650             }
651             setMeasuredDimension(width, imageSize + getChildAt(0).getPaddingBottom());
652         }
653     }
654 
655     /**
656      * Class to hold contact information
657      */
658     public static class ContactEntry {
659         public String name;
660         public String status;
661         public String phoneLabel;
662         public String phoneNumber;
663         public Uri photoUri;
664         public Uri lookupKey;
665         public Drawable presenceIcon;
666     }
667 
668     protected static class ViewTypes {
669         public static final int COUNT = 4;
670         public static final int STARRED = 0;
671         public static final int DIVIDER = 1;
672         public static final int FREQUENT = 2;
673         public static final int STARRED_PHONE = 3;
674     }
675 }
676