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