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