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