ContactFragment.java revision f6ffbae39b62c54c0a96914beaacfca213658a4f
1/* 2 * Copyright (C) 2010 Google Inc. 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 17package com.android.loaderapp.fragments; 18 19import com.android.internal.widget.ContactHeaderWidget; 20import com.android.loaderapp.R; 21import com.android.loaderapp.model.Collapser; 22import com.android.loaderapp.model.ContactLoader; 23import com.android.loaderapp.model.ContactsSource; 24import com.android.loaderapp.model.Sources; 25import com.android.loaderapp.model.TypePrecedence; 26import com.android.loaderapp.model.Collapser.Collapsible; 27import com.android.loaderapp.model.ContactLoader.ContactData; 28import com.android.loaderapp.model.ContactsSource.DataKind; 29import com.android.loaderapp.util.Constants; 30import com.android.loaderapp.util.ContactPresenceIconUtil; 31import com.android.loaderapp.util.ContactsUtils; 32import com.android.loaderapp.util.DataStatus; 33import com.google.android.collect.Lists; 34import com.google.android.collect.Maps; 35 36import android.app.LoaderManagingFragment; 37import android.content.ActivityNotFoundException; 38import android.content.ContentUris; 39import android.content.ContentValues; 40import android.content.Context; 41import android.content.Entity; 42import android.content.Intent; 43import android.content.Loader; 44import android.content.Entity.NamedContentValues; 45import android.content.res.Resources; 46import android.graphics.drawable.Drawable; 47import android.net.ParseException; 48import android.net.Uri; 49import android.net.WebAddress; 50import android.os.Bundle; 51import android.provider.ContactsContract.CommonDataKinds; 52import android.provider.ContactsContract.Contacts; 53import android.provider.ContactsContract.Data; 54import android.provider.ContactsContract.DisplayNameSources; 55import android.provider.ContactsContract.RawContacts; 56import android.provider.ContactsContract.StatusUpdates; 57import android.provider.ContactsContract.CommonDataKinds.Email; 58import android.provider.ContactsContract.CommonDataKinds.Im; 59import android.provider.ContactsContract.CommonDataKinds.Nickname; 60import android.provider.ContactsContract.CommonDataKinds.Note; 61import android.provider.ContactsContract.CommonDataKinds.Organization; 62import android.provider.ContactsContract.CommonDataKinds.Phone; 63import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; 64import android.provider.ContactsContract.CommonDataKinds.Website; 65import android.telephony.PhoneNumberUtils; 66import android.text.TextUtils; 67import android.util.Log; 68import android.view.LayoutInflater; 69import android.view.View; 70import android.view.ViewGroup; 71import android.view.View.OnClickListener; 72import android.widget.AdapterView; 73import android.widget.ImageView; 74import android.widget.ListView; 75import android.widget.TextView; 76import android.widget.AdapterView.OnItemClickListener; 77 78import java.util.ArrayList; 79import java.util.HashMap; 80 81public class ContactFragment extends LoaderManagingFragment<ContactData> 82 implements OnClickListener, OnItemClickListener { 83 private static final String TAG = "ContactCoupler"; 84 85 static final String ARG_URI = "uri"; 86 static final int LOADER_DETAILS = 1; 87 88 Uri mUri; 89 90 private static final boolean SHOW_SEPARATORS = false; 91 92 protected Uri mLookupUri; 93 private ViewAdapter mAdapter; 94 private int mNumPhoneNumbers = 0; 95 private Controller mController; 96 97 /** 98 * A list of distinct contact IDs included in the current contact. 99 */ 100 private ArrayList<Long> mRawContactIds = new ArrayList<Long>(); 101 102 /* package */ ArrayList<ViewEntry> mPhoneEntries = new ArrayList<ViewEntry>(); 103 /* package */ ArrayList<ViewEntry> mSmsEntries = new ArrayList<ViewEntry>(); 104 /* package */ ArrayList<ViewEntry> mEmailEntries = new ArrayList<ViewEntry>(); 105 /* package */ ArrayList<ViewEntry> mPostalEntries = new ArrayList<ViewEntry>(); 106 /* package */ ArrayList<ViewEntry> mImEntries = new ArrayList<ViewEntry>(); 107 /* package */ ArrayList<ViewEntry> mNicknameEntries = new ArrayList<ViewEntry>(); 108 /* package */ ArrayList<ViewEntry> mOrganizationEntries = new ArrayList<ViewEntry>(); 109 /* package */ ArrayList<ViewEntry> mGroupEntries = new ArrayList<ViewEntry>(); 110 /* package */ ArrayList<ViewEntry> mOtherEntries = new ArrayList<ViewEntry>(); 111 /* package */ ArrayList<ArrayList<ViewEntry>> mSections = new ArrayList<ArrayList<ViewEntry>>(); 112 113 protected ContactHeaderWidget mContactHeaderWidget; 114 115 protected LayoutInflater mInflater; 116 117 protected int mReadOnlySourcesCnt; 118 protected int mWritableSourcesCnt; 119 protected boolean mAllRestricted; 120 121 protected Uri mPrimaryPhoneUri = null; 122 123 protected ArrayList<Long> mWritableRawContactIds = new ArrayList<Long>(); 124 125 private long mNameRawContactId = -1; 126 private int mDisplayNameSource = DisplayNameSources.UNDEFINED; 127 128 private ArrayList<Entity> mEntities = Lists.newArrayList(); 129 private HashMap<Long, DataStatus> mStatuses = Maps.newHashMap(); 130 131 /** 132 * The view shown if the detail list is empty. 133 * We set this to the list view when first bind the adapter, so that it won't be shown while 134 * we're loading data. 135 */ 136 private View mEmptyView; 137 138 private ListView mListView; 139 private boolean mShowSmsLinksForAllPhones; 140 141 public ContactFragment() { 142 } 143 144 public ContactFragment(Uri uri, ContactFragment.Controller controller) { 145 mUri = uri; 146 mController = controller; 147 } 148 149 @Override 150 public void onCreate(Bundle savedState) { 151 super.onCreate(savedState); 152 } 153 154 @Override 155 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) { 156 View view = inflater.inflate(R.layout.contact_details, container, false); 157 158 mInflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 159 160 mContactHeaderWidget = (ContactHeaderWidget) view.findViewById(R.id.contact_header_widget); 161 mContactHeaderWidget.showStar(true); 162 mContactHeaderWidget.setExcludeMimes(new String[] { Contacts.CONTENT_ITEM_TYPE }); 163 164 mListView = (ListView) view.findViewById(android.R.id.list); 165 mListView.setScrollBarStyle(ListView.SCROLLBARS_OUTSIDE_OVERLAY); 166 mListView.setOnItemClickListener(this); 167 // Don't set it to mListView yet. We do so later when we bind the adapter. 168 mEmptyView = view.findViewById(android.R.id.empty); 169 170 // Build the list of sections. The order they're added to mSections dictates the 171 // order they are displayed in the list. 172 mSections.add(mPhoneEntries); 173 mSections.add(mSmsEntries); 174 mSections.add(mEmailEntries); 175 mSections.add(mImEntries); 176 mSections.add(mPostalEntries); 177 mSections.add(mNicknameEntries); 178 mSections.add(mOrganizationEntries); 179 mSections.add(mGroupEntries); 180 mSections.add(mOtherEntries); 181 182 //TODO Read this value from a preference 183 mShowSmsLinksForAllPhones = true; 184 185 return view; 186 } 187 188 @Override 189 public void onInitializeLoaders() { 190 if (mUri != null) { 191 loadContact(mUri); 192 } 193 } 194 195 @Override 196 protected Loader onCreateLoader(int id, Bundle args) { 197 switch (id) { 198 case LOADER_DETAILS: { 199 Uri uri = args.getParcelable(ARG_URI); 200 return new ContactLoader(getActivity(), uri); 201 } 202 } 203 return null; 204 } 205 206 @Override 207 public void onLoadFinished(Loader<ContactData> loader, ContactData data) { 208 switch (loader.getId()) { 209 case LOADER_DETAILS: { 210 setData(data); 211 break; 212 } 213 } 214 } 215 216 public void loadContact(Uri uri) { 217 mUri = uri; 218 Bundle args = new Bundle(); 219 args.putParcelable(ARG_URI, uri); 220 startLoading(LOADER_DETAILS, args); 221 } 222 223 public void setData(ContactData data) { 224 mEntities = data.entities; 225 mStatuses = data.statuses; 226 227 mNameRawContactId = data.nameRawContactId; 228 mDisplayNameSource = data.displayNameSource; 229 230 mContactHeaderWidget.bindFromContactLookupUri(data.uri); 231 bindData(); 232 } 233 234 public interface Controller { 235 public void onPrimaryAction(ViewEntry entry); 236 public void onSecondaryAction(ViewEntry entry); 237 } 238 239 public static final class DefaultController implements Controller { 240 private Context mContext; 241 242 public DefaultController(Context context) { 243 mContext = context; 244 } 245 246 public void onPrimaryAction(ViewEntry entry) { 247 Intent intent = entry.intent; 248 if (intent != null) { 249 try { 250 mContext.startActivity(intent); 251 } catch (ActivityNotFoundException e) { 252 Log.e(TAG, "No activity found for intent: " + intent); 253 } 254 } 255 } 256 257 public void onSecondaryAction(ViewEntry entry) { 258 Intent intent = entry.secondaryIntent; 259 if (intent != null) { 260 try { 261 mContext.startActivity(intent); 262 } catch (ActivityNotFoundException e) { 263 Log.e(TAG, "No activity found for intent: " + intent); 264 } 265 } 266 } 267 } 268 269 public void setController(Controller controller) { 270 mController = controller; 271 } 272 273 public void onItemClick(AdapterView parent, View v, int position, long id) { 274 if (mController != null) { 275 ViewEntry entry = ViewAdapter.getEntry(mSections, position, SHOW_SEPARATORS); 276 if (entry != null) { 277 mController.onPrimaryAction(entry); 278 } 279 } 280 } 281 282 public void onClick(View v) { 283 if (mController != null) { 284 mController.onSecondaryAction((ViewEntry) v.getTag()); 285 } 286 } 287 288 private void bindData() { 289 290 // Build up the contact entries 291 buildEntries(); 292 293 // Collapse similar data items in select sections. 294 Collapser.collapseList(mPhoneEntries); 295 Collapser.collapseList(mSmsEntries); 296 Collapser.collapseList(mEmailEntries); 297 Collapser.collapseList(mPostalEntries); 298 Collapser.collapseList(mImEntries); 299 300 if (mAdapter == null) { 301 mAdapter = new ViewAdapter(getActivity(), mSections); 302 mListView.setAdapter(mAdapter); 303 } else { 304 mAdapter.setSections(mSections, SHOW_SEPARATORS); 305 } 306 mListView.setEmptyView(mEmptyView); 307 } 308 309 /** 310 * Build up the entries to display on the screen. 311 * 312 * @param personCursor the URI for the contact being displayed 313 */ 314 private final void buildEntries() { 315 // Clear out the old entries 316 final int numSections = mSections.size(); 317 for (int i = 0; i < numSections; i++) { 318 mSections.get(i).clear(); 319 } 320 321 mRawContactIds.clear(); 322 323 mReadOnlySourcesCnt = 0; 324 mWritableSourcesCnt = 0; 325 mAllRestricted = true; 326 mPrimaryPhoneUri = null; 327 328 mWritableRawContactIds.clear(); 329 330 if (mEntities == null || mStatuses == null) { 331 return; 332 } 333 334 final Context context = getActivity(); 335 final Sources sources = Sources.getInstance(context); 336 337 // Build up method entries 338 for (Entity entity: mEntities) { 339 final ContentValues entValues = entity.getEntityValues(); 340 final String accountType = entValues.getAsString(RawContacts.ACCOUNT_TYPE); 341 final long rawContactId = entValues.getAsLong(RawContacts._ID); 342 343 // Mark when this contact has any unrestricted components 344 final boolean isRestricted = entValues.getAsInteger(RawContacts.IS_RESTRICTED) != 0; 345 if (!isRestricted) mAllRestricted = false; 346 347 if (!mRawContactIds.contains(rawContactId)) { 348 mRawContactIds.add(rawContactId); 349 } 350 ContactsSource contactsSource = sources.getInflatedSource(accountType, 351 ContactsSource.LEVEL_SUMMARY); 352 if (contactsSource != null && contactsSource.readOnly) { 353 mReadOnlySourcesCnt += 1; 354 } else { 355 mWritableSourcesCnt += 1; 356 mWritableRawContactIds.add(rawContactId); 357 } 358 359 360 for (NamedContentValues subValue : entity.getSubValues()) { 361 final ContentValues entryValues = subValue.values; 362 entryValues.put(Data.RAW_CONTACT_ID, rawContactId); 363 364 final long dataId = entryValues.getAsLong(Data._ID); 365 final String mimeType = entryValues.getAsString(Data.MIMETYPE); 366 if (mimeType == null) continue; 367 368 final DataKind kind = sources.getKindOrFallback(accountType, mimeType, 369 context, ContactsSource.LEVEL_MIMETYPES); 370 if (kind == null) continue; 371 372 final ViewEntry entry = ViewEntry.fromValues(context, mimeType, kind, 373 rawContactId, dataId, entryValues); 374 375 final boolean hasData = !TextUtils.isEmpty(entry.data); 376 final boolean isSuperPrimary = entryValues.getAsInteger( 377 Data.IS_SUPER_PRIMARY) != 0; 378 379 if (Phone.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) { 380 // Build phone entries 381 mNumPhoneNumbers++; 382 383 entry.intent = new Intent(Intent.ACTION_CALL_PRIVILEGED, 384 Uri.fromParts(Constants.SCHEME_TEL, entry.data, null)); 385 entry.secondaryIntent = new Intent(Intent.ACTION_SENDTO, 386 Uri.fromParts(Constants.SCHEME_SMSTO, entry.data, null)); 387 388 // Remember super-primary phone 389 if (isSuperPrimary) mPrimaryPhoneUri = entry.uri; 390 391 entry.isPrimary = isSuperPrimary; 392 mPhoneEntries.add(entry); 393 394 if (entry.type == CommonDataKinds.Phone.TYPE_MOBILE 395 || mShowSmsLinksForAllPhones) { 396 // Add an SMS entry 397 if (kind.iconAltRes > 0) { 398 entry.secondaryActionIcon = kind.iconAltRes; 399 } 400 } 401 } else if (Email.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) { 402 // Build email entries 403 entry.intent = new Intent(Intent.ACTION_SENDTO, 404 Uri.fromParts(Constants.SCHEME_MAILTO, entry.data, null)); 405 entry.isPrimary = isSuperPrimary; 406 mEmailEntries.add(entry); 407 408 // When Email rows have status, create additional Im row 409 final DataStatus status = mStatuses.get(entry.id); 410 if (status != null) { 411 final String imMime = Im.CONTENT_ITEM_TYPE; 412 final DataKind imKind = sources.getKindOrFallback(accountType, 413 imMime, context, ContactsSource.LEVEL_MIMETYPES); 414 final ViewEntry imEntry = ViewEntry.fromValues(context, 415 imMime, imKind, rawContactId, dataId, entryValues); 416 imEntry.intent = ContactsUtils.buildImIntent(entryValues); 417 imEntry.applyStatus(status, false); 418 mImEntries.add(imEntry); 419 } 420 } else if (StructuredPostal.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) { 421 // Build postal entries 422 entry.maxLines = 4; 423 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri); 424 mPostalEntries.add(entry); 425 } else if (Im.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) { 426 // Build IM entries 427 entry.intent = ContactsUtils.buildImIntent(entryValues); 428 if (TextUtils.isEmpty(entry.label)) { 429 entry.label = context.getString(R.string.chat).toLowerCase(); 430 } 431 432 // Apply presence and status details when available 433 final DataStatus status = mStatuses.get(entry.id); 434 if (status != null) { 435 entry.applyStatus(status, false); 436 } 437 mImEntries.add(entry); 438 } else if (Organization.CONTENT_ITEM_TYPE.equals(mimeType) && 439 (hasData || !TextUtils.isEmpty(entry.label))) { 440 // Build organization entries 441 final boolean isNameRawContact = (mNameRawContactId == rawContactId); 442 443 final boolean duplicatesTitle = 444 isNameRawContact 445 && mDisplayNameSource == DisplayNameSources.ORGANIZATION 446 && (!hasData || TextUtils.isEmpty(entry.label)); 447 448 if (!duplicatesTitle) { 449 entry.uri = null; 450 451 if (TextUtils.isEmpty(entry.label)) { 452 entry.label = entry.data; 453 entry.data = ""; 454 } 455 456 mOrganizationEntries.add(entry); 457 } 458 } else if (Nickname.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) { 459 // Build nickname entries 460 final boolean isNameRawContact = (mNameRawContactId == rawContactId); 461 462 final boolean duplicatesTitle = 463 isNameRawContact 464 && mDisplayNameSource == DisplayNameSources.NICKNAME; 465 466 if (!duplicatesTitle) { 467 entry.uri = null; 468 mNicknameEntries.add(entry); 469 } 470 } else if (Note.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) { 471 // Build note entries 472 entry.uri = null; 473 entry.maxLines = 100; 474 mOtherEntries.add(entry); 475 } else if (Website.CONTENT_ITEM_TYPE.equals(mimeType) && hasData) { 476 // Build note entries 477 entry.uri = null; 478 entry.maxLines = 10; 479 try { 480 WebAddress webAddress = new WebAddress(entry.data); 481 entry.intent = new Intent(Intent.ACTION_VIEW, 482 Uri.parse(webAddress.toString())); 483 } catch (ParseException e) { 484 Log.e(TAG, "Couldn't parse website: " + entry.data); 485 } 486 mOtherEntries.add(entry); 487 } else { 488 // Handle showing custom rows 489 entry.intent = new Intent(Intent.ACTION_VIEW, entry.uri); 490 491 // Use social summary when requested by external source 492 final DataStatus status = mStatuses.get(entry.id); 493 final boolean hasSocial = kind.actionBodySocial && status != null; 494 if (hasSocial) { 495 entry.applyStatus(status, true); 496 } 497 498 if (hasSocial || hasData) { 499 mOtherEntries.add(entry); 500 } 501 } 502 } 503 } 504 } 505 506 static String buildActionString(DataKind kind, ContentValues values, boolean lowerCase, 507 Context context) { 508 if (kind.actionHeader == null) { 509 return null; 510 } 511 CharSequence actionHeader = kind.actionHeader.inflateUsing(context, values); 512 if (actionHeader == null) { 513 return null; 514 } 515 return lowerCase ? actionHeader.toString().toLowerCase() : actionHeader.toString(); 516 } 517 518 static String buildDataString(DataKind kind, ContentValues values, Context context) { 519 if (kind.actionBody == null) { 520 return null; 521 } 522 CharSequence actionBody = kind.actionBody.inflateUsing(context, values); 523 return actionBody == null ? null : actionBody.toString(); 524 } 525 526 /** 527 * A basic structure with the data for a contact entry in the list. 528 */ 529 public static class ViewEntry extends ContactEntryAdapter.Entry implements Collapsible<ViewEntry> { 530 public Context context = null; 531 public String resPackageName = null; 532 public int actionIcon = -1; 533 public boolean isPrimary = false; 534 public int secondaryActionIcon = -1; 535 public Intent intent; 536 public Intent secondaryIntent = null; 537 public int maxLabelLines = 1; 538 public ArrayList<Long> ids = new ArrayList<Long>(); 539 public int collapseCount = 0; 540 541 public int presence = -1; 542 543 public CharSequence footerLine = null; 544 545 private ViewEntry() { 546 } 547 548 /** 549 * Build new {@link ViewEntry} and populate from the given values. 550 */ 551 public static ViewEntry fromValues(Context context, String mimeType, DataKind kind, 552 long rawContactId, long dataId, ContentValues values) { 553 final ViewEntry entry = new ViewEntry(); 554 entry.context = context; 555 entry.contactId = rawContactId; 556 entry.id = dataId; 557 entry.uri = ContentUris.withAppendedId(Data.CONTENT_URI, entry.id); 558 entry.mimetype = mimeType; 559 entry.label = buildActionString(kind, values, false, context); 560 entry.data = buildDataString(kind, values, context); 561 562 if (kind.typeColumn != null && values.containsKey(kind.typeColumn)) { 563 entry.type = values.getAsInteger(kind.typeColumn); 564 } 565 if (kind.iconRes > 0) { 566 entry.resPackageName = kind.resPackageName; 567 entry.actionIcon = kind.iconRes; 568 } 569 570 return entry; 571 } 572 573 /** 574 * Apply given {@link DataStatus} values over this {@link ViewEntry} 575 * 576 * @param fillData When true, the given status replaces {@link #data} 577 * and {@link #footerLine}. Otherwise only {@link #presence} 578 * is updated. 579 */ 580 public ViewEntry applyStatus(DataStatus status, boolean fillData) { 581 presence = status.getPresence(); 582 if (fillData && status.isValid()) { 583 this.data = status.getStatus().toString(); 584 this.footerLine = status.getTimestampLabel(context); 585 } 586 587 return this; 588 } 589 590 public boolean collapseWith(ViewEntry entry) { 591 // assert equal collapse keys 592 if (!shouldCollapseWith(entry)) { 593 return false; 594 } 595 596 // Choose the label associated with the highest type precedence. 597 if (TypePrecedence.getTypePrecedence(mimetype, type) 598 > TypePrecedence.getTypePrecedence(entry.mimetype, entry.type)) { 599 type = entry.type; 600 label = entry.label; 601 } 602 603 // Choose the max of the maxLines and maxLabelLines values. 604 maxLines = Math.max(maxLines, entry.maxLines); 605 maxLabelLines = Math.max(maxLabelLines, entry.maxLabelLines); 606 607 // Choose the presence with the highest precedence. 608 if (StatusUpdates.getPresencePrecedence(presence) 609 < StatusUpdates.getPresencePrecedence(entry.presence)) { 610 presence = entry.presence; 611 } 612 613 // If any of the collapsed entries are primary make the whole thing primary. 614 isPrimary = entry.isPrimary ? true : isPrimary; 615 616 // uri, and contactdId, shouldn't make a difference. Just keep the original. 617 618 // Keep track of all the ids that have been collapsed with this one. 619 ids.add(entry.id); 620 collapseCount++; 621 return true; 622 } 623 624 public boolean shouldCollapseWith(ViewEntry entry) { 625 if (entry == null) { 626 return false; 627 } 628 629 if (!ContactsUtils.areDataEqual(context, mimetype, data, entry.mimetype, entry.data)) { 630 return false; 631 } 632 633 if (!TextUtils.equals(mimetype, entry.mimetype) 634 || !ContactsUtils.areIntentActionEqual(intent, entry.intent) 635 || !ContactsUtils.areIntentActionEqual(secondaryIntent, entry.secondaryIntent) 636 || actionIcon != entry.actionIcon) { 637 return false; 638 } 639 640 return true; 641 } 642 } 643 644 /** Cache of the children views of a row */ 645 static class ViewCache { 646 public TextView label; 647 public TextView data; 648 public TextView footer; 649 public ImageView actionIcon; 650 public ImageView presenceIcon; 651 public ImageView primaryIcon; 652 public ImageView secondaryActionButton; 653 public View secondaryActionDivider; 654 655 // Need to keep track of this too 656 ViewEntry entry; 657 } 658 659 private final class ViewAdapter extends ContactEntryAdapter<ViewEntry> { 660 ViewAdapter(Context context, ArrayList<ArrayList<ViewEntry>> sections) { 661 super(context, sections, SHOW_SEPARATORS); 662 } 663 664 @Override 665 public View getView(int position, View convertView, ViewGroup parent) { 666 ViewEntry entry = getEntry(mSections, position, false); 667 View v; 668 669 ViewCache views; 670 671 // Check to see if we can reuse convertView 672 if (convertView != null) { 673 v = convertView; 674 views = (ViewCache) v.getTag(); 675 } else { 676 // Create a new view if needed 677 v = mInflater.inflate(R.layout.list_item_text_icons, parent, false); 678 679 // Cache the children 680 views = new ViewCache(); 681 views.label = (TextView) v.findViewById(android.R.id.text1); 682 views.data = (TextView) v.findViewById(android.R.id.text2); 683 views.footer = (TextView) v.findViewById(R.id.footer); 684 views.actionIcon = (ImageView) v.findViewById(R.id.action_icon); 685 views.primaryIcon = (ImageView) v.findViewById(R.id.primary_icon); 686 views.presenceIcon = (ImageView) v.findViewById(R.id.presence_icon); 687 views.secondaryActionButton = (ImageView) v.findViewById( 688 R.id.secondary_action_button); 689 views.secondaryActionButton.setOnClickListener(ContactFragment.this); 690 views.secondaryActionDivider = v.findViewById(R.id.divider); 691 v.setTag(views); 692 } 693 694 // Update the entry in the view cache 695 views.entry = entry; 696 697 // Bind the data to the view 698 bindView(v, entry); 699 return v; 700 } 701 702 @Override 703 protected View newView(int position, ViewGroup parent) { 704 // getView() handles this 705 throw new UnsupportedOperationException(); 706 } 707 708 @Override 709 protected void bindView(View view, ViewEntry entry) { 710 final Resources resources = mContext.getResources(); 711 ViewCache views = (ViewCache) view.getTag(); 712 713 // Set the label 714 TextView label = views.label; 715 setMaxLines(label, entry.maxLabelLines); 716 label.setText(entry.label); 717 718 // Set the data 719 TextView data = views.data; 720 if (data != null) { 721 if (entry.mimetype.equals(Phone.CONTENT_ITEM_TYPE) 722 || entry.mimetype.equals(Constants.MIME_SMS_ADDRESS)) { 723 data.setText(PhoneNumberUtils.formatNumber(entry.data)); 724 } else { 725 data.setText(entry.data); 726 } 727 setMaxLines(data, entry.maxLines); 728 } 729 730 // Set the footer 731 if (!TextUtils.isEmpty(entry.footerLine)) { 732 views.footer.setText(entry.footerLine); 733 views.footer.setVisibility(View.VISIBLE); 734 } else { 735 views.footer.setVisibility(View.GONE); 736 } 737 738 // Set the primary icon 739 views.primaryIcon.setVisibility(entry.isPrimary ? View.VISIBLE : View.GONE); 740 741 // Set the action icon 742 ImageView action = views.actionIcon; 743 if (entry.actionIcon != -1) { 744 Drawable actionIcon; 745 if (entry.resPackageName != null) { 746 // Load external resources through PackageManager 747 actionIcon = mContext.getPackageManager().getDrawable(entry.resPackageName, 748 entry.actionIcon, null); 749 } else { 750 actionIcon = resources.getDrawable(entry.actionIcon); 751 } 752 action.setImageDrawable(actionIcon); 753 action.setVisibility(View.VISIBLE); 754 } else { 755 // Things should still line up as if there was an icon, so make it invisible 756 action.setVisibility(View.INVISIBLE); 757 } 758 759 // Set the presence icon 760 Drawable presenceIcon = ContactPresenceIconUtil.getPresenceIcon( 761 mContext, entry.presence); 762 ImageView presenceIconView = views.presenceIcon; 763 if (presenceIcon != null) { 764 presenceIconView.setImageDrawable(presenceIcon); 765 presenceIconView.setVisibility(View.VISIBLE); 766 } else { 767 presenceIconView.setVisibility(View.GONE); 768 } 769 770 // Set the secondary action button 771 ImageView secondaryActionView = views.secondaryActionButton; 772 Drawable secondaryActionIcon = null; 773 if (entry.secondaryActionIcon != -1) { 774 secondaryActionIcon = resources.getDrawable(entry.secondaryActionIcon); 775 } 776 if (entry.secondaryIntent != null && secondaryActionIcon != null) { 777 secondaryActionView.setImageDrawable(secondaryActionIcon); 778 secondaryActionView.setTag(entry); 779 secondaryActionView.setVisibility(View.VISIBLE); 780 views.secondaryActionDivider.setVisibility(View.VISIBLE); 781 } else { 782 secondaryActionView.setVisibility(View.GONE); 783 views.secondaryActionDivider.setVisibility(View.GONE); 784 } 785 } 786 787 private void setMaxLines(TextView textView, int maxLines) { 788 if (maxLines == 1) { 789 textView.setSingleLine(true); 790 textView.setEllipsize(TextUtils.TruncateAt.END); 791 } else { 792 textView.setSingleLine(false); 793 textView.setMaxLines(maxLines); 794 textView.setEllipsize(null); 795 } 796 } 797 } 798} 799