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