Home | History | Annotate | Download | only in list
      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 
    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 
    133     public void setPhotoLoader(ContactPhotoManager photoLoader) {
    134         mPhotoManager = photoLoader;
    135     }
    136 
    137     public void setColumnCount(int columnCount) {
    138         mColumnCount = columnCount;
    139     }
    140 
    141     public void setDisplayType(DisplayType displayType) {
    142         mDisplayType = displayType;
    143     }
    144 
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    297     public int getNumFrequents() {
    298         return mNumFrequents;
    299     }
    300 
    301     @Override
    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      */
    333     protected int getRowCount(int entryCount) {
    334         return entryCount == 0 ? 0 : ((entryCount - 1) / mColumnCount) + 1;
    335     }
    336 
    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
    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
    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
    394     public boolean areAllItemsEnabled() {
    395         return (mDisplayType != DisplayType.STREQUENT &&
    396                 mDisplayType != DisplayType.STREQUENT_PHONE_ONLY);
    397     }
    398 
    399     @Override
    400     public boolean isEnabled(int position) {
    401         return position != getRowCount(mDividerPosition);
    402     }
    403 
    404     @Override
    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      */
    429     public View getDivider() {
    430         return MoreContactUtils.createHeaderView(mContext,
    431                 mDisplayType == DisplayType.STREQUENT_PHONE_ONLY ?
    432                         R.string.favoritesFrequentCalled : R.string.favoritesFrequentContacted);
    433     }
    434 
    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
    450     public int getViewTypeCount() {
    451         return ViewTypes.COUNT;
    452     }
    453 
    454     @Override
    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      */
    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 
    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          */
    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