1 /* 2 * Copyright (C) 2010 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 17 package com.android.contacts; 18 19 import com.android.contacts.model.AccountType; 20 import com.android.contacts.model.AccountTypeManager; 21 import com.android.contacts.model.AccountTypeWithDataSet; 22 import com.android.contacts.util.ContactLoaderUtils; 23 import com.android.contacts.util.DataStatus; 24 import com.android.contacts.util.StreamItemEntry; 25 import com.android.contacts.util.StreamItemPhotoEntry; 26 import com.google.android.collect.Lists; 27 import com.google.common.annotations.VisibleForTesting; 28 import com.google.common.collect.Maps; 29 import com.google.common.collect.Sets; 30 31 import android.content.ContentResolver; 32 import android.content.ContentUris; 33 import android.content.ContentValues; 34 import android.content.Context; 35 import android.content.Entity; 36 import android.content.Entity.NamedContentValues; 37 import android.content.Intent; 38 import android.content.Loader; 39 import android.content.pm.PackageManager; 40 import android.content.pm.PackageManager.NameNotFoundException; 41 import android.content.res.AssetFileDescriptor; 42 import android.content.res.Resources; 43 import android.database.Cursor; 44 import android.net.Uri; 45 import android.os.AsyncTask; 46 import android.provider.ContactsContract; 47 import android.provider.ContactsContract.CommonDataKinds.GroupMembership; 48 import android.provider.ContactsContract.CommonDataKinds.Photo; 49 import android.provider.ContactsContract.Contacts; 50 import android.provider.ContactsContract.Data; 51 import android.provider.ContactsContract.Directory; 52 import android.provider.ContactsContract.DisplayNameSources; 53 import android.provider.ContactsContract.Groups; 54 import android.provider.ContactsContract.RawContacts; 55 import android.provider.ContactsContract.StreamItemPhotos; 56 import android.provider.ContactsContract.StreamItems; 57 import android.text.TextUtils; 58 import android.util.Log; 59 60 import java.io.ByteArrayOutputStream; 61 import java.io.FileInputStream; 62 import java.io.IOException; 63 import java.io.InputStream; 64 import java.util.ArrayList; 65 import java.util.Collections; 66 import java.util.HashMap; 67 import java.util.List; 68 import java.util.Map; 69 import java.util.Set; 70 71 /** 72 * Loads a single Contact and all it constituent RawContacts. 73 */ 74 public class ContactLoader extends Loader<ContactLoader.Result> { 75 private static final String TAG = "ContactLoader"; 76 77 private final Uri mRequestedUri; 78 private Uri mLookupUri; 79 private boolean mLoadGroupMetaData; 80 private boolean mLoadStreamItems; 81 private final boolean mLoadInvitableAccountTypes; 82 private Result mContact; 83 private ForceLoadContentObserver mObserver; 84 private boolean mDestroyed; 85 private final Set<Long> mNotifiedRawContactIds = Sets.newHashSet(); 86 87 public interface Listener { 88 public void onContactLoaded(Result contact); 89 } 90 91 /** 92 * The result of a load operation. Contains all data necessary to display the contact. 93 */ 94 public static final class Result { 95 private enum Status { 96 /** Contact is successfully loaded */ 97 LOADED, 98 /** There was an error loading the contact */ 99 ERROR, 100 /** Contact is not found */ 101 NOT_FOUND, 102 } 103 104 private final Uri mRequestedUri; 105 private final Uri mLookupUri; 106 private final Uri mUri; 107 private final long mDirectoryId; 108 private final String mLookupKey; 109 private final long mId; 110 private final long mNameRawContactId; 111 private final int mDisplayNameSource; 112 private final long mPhotoId; 113 private final String mPhotoUri; 114 private final String mDisplayName; 115 private final String mAltDisplayName; 116 private final String mPhoneticName; 117 private final boolean mStarred; 118 private final Integer mPresence; 119 private final ArrayList<Entity> mEntities; 120 private final ArrayList<StreamItemEntry> mStreamItems; 121 private final HashMap<Long, DataStatus> mStatuses; 122 private final ArrayList<AccountType> mInvitableAccountTypes; 123 124 private String mDirectoryDisplayName; 125 private String mDirectoryType; 126 private String mDirectoryAccountType; 127 private String mDirectoryAccountName; 128 private int mDirectoryExportSupport; 129 130 private ArrayList<GroupMetaData> mGroups; 131 132 private boolean mLoadingPhoto; 133 private byte[] mPhotoBinaryData; 134 private final boolean mSendToVoicemail; 135 private final String mCustomRingtone; 136 private final boolean mIsUserProfile; 137 138 private final Status mStatus; 139 private final Exception mException; 140 141 /** 142 * Constructor for special results, namely "no contact found" and "error". 143 */ 144 private Result(Uri requestedUri, Status status, Exception exception) { 145 if (status == Status.ERROR && exception == null) { 146 throw new IllegalArgumentException("ERROR result must have exception"); 147 } 148 mStatus = status; 149 mException = exception; 150 mRequestedUri = requestedUri; 151 mLookupUri = null; 152 mUri = null; 153 mDirectoryId = -1; 154 mLookupKey = null; 155 mId = -1; 156 mEntities = null; 157 mStreamItems = new ArrayList<StreamItemEntry>(); 158 mStatuses = null; 159 mNameRawContactId = -1; 160 mDisplayNameSource = DisplayNameSources.UNDEFINED; 161 mPhotoId = -1; 162 mPhotoUri = null; 163 mDisplayName = null; 164 mAltDisplayName = null; 165 mPhoneticName = null; 166 mStarred = false; 167 mPresence = null; 168 mInvitableAccountTypes = null; 169 mSendToVoicemail = false; 170 mCustomRingtone = null; 171 mIsUserProfile = false; 172 } 173 174 private static Result forError(Uri requestedUri, Exception exception) { 175 return new Result(requestedUri, Status.ERROR, exception); 176 } 177 178 private static Result forNotFound(Uri requestedUri) { 179 return new Result(requestedUri, Status.NOT_FOUND, null); 180 } 181 182 /** 183 * Constructor to call when contact was found 184 */ 185 private Result(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey, 186 long id, long nameRawContactId, int displayNameSource, long photoId, 187 String photoUri, String displayName, String altDisplayName, String phoneticName, 188 boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone, 189 boolean isUserProfile) { 190 mStatus = Status.LOADED; 191 mException = null; 192 mRequestedUri = requestedUri; 193 mLookupUri = lookupUri; 194 mUri = uri; 195 mDirectoryId = directoryId; 196 mLookupKey = lookupKey; 197 mId = id; 198 mEntities = new ArrayList<Entity>(); 199 mStreamItems = new ArrayList<StreamItemEntry>(); 200 mStatuses = new HashMap<Long, DataStatus>(); 201 mNameRawContactId = nameRawContactId; 202 mDisplayNameSource = displayNameSource; 203 mPhotoId = photoId; 204 mPhotoUri = photoUri; 205 mDisplayName = displayName; 206 mAltDisplayName = altDisplayName; 207 mPhoneticName = phoneticName; 208 mStarred = starred; 209 mPresence = presence; 210 mInvitableAccountTypes = Lists.newArrayList(); 211 mSendToVoicemail = sendToVoicemail; 212 mCustomRingtone = customRingtone; 213 mIsUserProfile = isUserProfile; 214 } 215 216 private Result(Result from) { 217 mStatus = from.mStatus; 218 mException = from.mException; 219 mRequestedUri = from.mRequestedUri; 220 mLookupUri = from.mLookupUri; 221 mUri = from.mUri; 222 mDirectoryId = from.mDirectoryId; 223 mLookupKey = from.mLookupKey; 224 mId = from.mId; 225 mNameRawContactId = from.mNameRawContactId; 226 mDisplayNameSource = from.mDisplayNameSource; 227 mPhotoId = from.mPhotoId; 228 mPhotoUri = from.mPhotoUri; 229 mDisplayName = from.mDisplayName; 230 mAltDisplayName = from.mAltDisplayName; 231 mPhoneticName = from.mPhoneticName; 232 mStarred = from.mStarred; 233 mPresence = from.mPresence; 234 mEntities = from.mEntities; 235 mStreamItems = from.mStreamItems; 236 mStatuses = from.mStatuses; 237 mInvitableAccountTypes = from.mInvitableAccountTypes; 238 239 mDirectoryDisplayName = from.mDirectoryDisplayName; 240 mDirectoryType = from.mDirectoryType; 241 mDirectoryAccountType = from.mDirectoryAccountType; 242 mDirectoryAccountName = from.mDirectoryAccountName; 243 mDirectoryExportSupport = from.mDirectoryExportSupport; 244 245 mGroups = from.mGroups; 246 247 mLoadingPhoto = from.mLoadingPhoto; 248 mPhotoBinaryData = from.mPhotoBinaryData; 249 mSendToVoicemail = from.mSendToVoicemail; 250 mCustomRingtone = from.mCustomRingtone; 251 mIsUserProfile = from.mIsUserProfile; 252 } 253 254 /** 255 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}. 256 */ 257 private void setDirectoryMetaData(String displayName, String directoryType, 258 String accountType, String accountName, int exportSupport) { 259 mDirectoryDisplayName = displayName; 260 mDirectoryType = directoryType; 261 mDirectoryAccountType = accountType; 262 mDirectoryAccountName = accountName; 263 mDirectoryExportSupport = exportSupport; 264 } 265 266 private void setLoadingPhoto(boolean flag) { 267 mLoadingPhoto = flag; 268 } 269 270 private void setPhotoBinaryData(byte[] photoBinaryData) { 271 mPhotoBinaryData = photoBinaryData; 272 } 273 274 /** 275 * Returns the URI for the contact that contains both the lookup key and the ID. This is 276 * the best URI to reference a contact. 277 * For directory contacts, this is the same a the URI as returned by {@link #getUri()} 278 */ 279 public Uri getLookupUri() { 280 return mLookupUri; 281 } 282 283 public String getLookupKey() { 284 return mLookupKey; 285 } 286 287 /** 288 * Returns the contact Uri that was passed to the provider to make the query. This is 289 * the same as the requested Uri, unless the requested Uri doesn't specify a Contact: 290 * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will 291 * always reference the full aggregate contact. 292 */ 293 public Uri getUri() { 294 return mUri; 295 } 296 297 /** 298 * Returns the URI for which this {@link ContactLoader) was initially requested. 299 */ 300 public Uri getRequestedUri() { 301 return mRequestedUri; 302 } 303 304 @VisibleForTesting 305 /*package*/ long getId() { 306 return mId; 307 } 308 309 /** 310 * @return true when an exception happened during loading, in which case 311 * {@link #getException} returns the actual exception object. 312 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If 313 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false}, 314 * and vice versa. 315 */ 316 public boolean isError() { 317 return mStatus == Status.ERROR; 318 } 319 320 public Exception getException() { 321 return mException; 322 } 323 324 /** 325 * @return true when the specified contact is not found. 326 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If 327 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false}, 328 * and vice versa. 329 */ 330 public boolean isNotFound() { 331 return mStatus == Status.NOT_FOUND; 332 } 333 334 /** 335 * @return true if the specified contact is successfully loaded. 336 * i.e. neither {@link #isError()} nor {@link #isNotFound()}. 337 */ 338 public boolean isLoaded() { 339 return mStatus == Status.LOADED; 340 } 341 342 public long getNameRawContactId() { 343 return mNameRawContactId; 344 } 345 346 public int getDisplayNameSource() { 347 return mDisplayNameSource; 348 } 349 350 public long getPhotoId() { 351 return mPhotoId; 352 } 353 354 public String getPhotoUri() { 355 return mPhotoUri; 356 } 357 358 public String getDisplayName() { 359 return mDisplayName; 360 } 361 362 public String getAltDisplayName() { 363 return mAltDisplayName; 364 } 365 366 public String getPhoneticName() { 367 return mPhoneticName; 368 } 369 370 public boolean getStarred() { 371 return mStarred; 372 } 373 374 public Integer getPresence() { 375 return mPresence; 376 } 377 378 public ArrayList<AccountType> getInvitableAccountTypes() { 379 return mInvitableAccountTypes; 380 } 381 382 public ArrayList<Entity> getEntities() { 383 return mEntities; 384 } 385 386 public ArrayList<StreamItemEntry> getStreamItems() { 387 return mStreamItems; 388 } 389 390 public HashMap<Long, DataStatus> getStatuses() { 391 return mStatuses; 392 } 393 394 public long getDirectoryId() { 395 return mDirectoryId; 396 } 397 398 public boolean isDirectoryEntry() { 399 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT 400 && mDirectoryId != Directory.LOCAL_INVISIBLE; 401 } 402 403 public int getDirectoryExportSupport() { 404 return mDirectoryExportSupport; 405 } 406 407 public String getDirectoryDisplayName() { 408 return mDirectoryDisplayName; 409 } 410 411 public String getDirectoryType() { 412 return mDirectoryType; 413 } 414 415 public String getDirectoryAccountType() { 416 return mDirectoryAccountType; 417 } 418 419 public String getDirectoryAccountName() { 420 return mDirectoryAccountName; 421 } 422 423 public boolean isLoadingPhoto() { 424 return mLoadingPhoto; 425 } 426 427 public byte[] getPhotoBinaryData() { 428 return mPhotoBinaryData; 429 } 430 431 public ArrayList<ContentValues> getContentValues() { 432 if (mEntities.size() != 1) { 433 throw new IllegalStateException( 434 "Cannot extract content values from an aggregated contact"); 435 } 436 437 Entity entity = mEntities.get(0); 438 ArrayList<ContentValues> result = new ArrayList<ContentValues>(); 439 ArrayList<NamedContentValues> subValues = entity.getSubValues(); 440 if (subValues != null) { 441 int size = subValues.size(); 442 for (int i = 0; i < size; i++) { 443 NamedContentValues pair = subValues.get(i); 444 if (Data.CONTENT_URI.equals(pair.uri)) { 445 result.add(pair.values); 446 } 447 } 448 } 449 450 // If the photo was loaded using the URI, create an entry for the photo 451 // binary data. 452 if (mPhotoId == 0 && mPhotoBinaryData != null) { 453 ContentValues photo = new ContentValues(); 454 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); 455 photo.put(Photo.PHOTO, mPhotoBinaryData); 456 result.add(photo); 457 } 458 459 return result; 460 } 461 462 private void addGroupMetaData(GroupMetaData group) { 463 if (mGroups == null) { 464 mGroups = new ArrayList<GroupMetaData>(); 465 } 466 mGroups.add(group); 467 } 468 469 public List<GroupMetaData> getGroupMetaData() { 470 return mGroups; 471 } 472 473 public boolean isSendToVoicemail() { 474 return mSendToVoicemail; 475 } 476 477 public String getCustomRingtone() { 478 return mCustomRingtone; 479 } 480 481 public boolean isUserProfile() { 482 return mIsUserProfile; 483 } 484 } 485 486 /** 487 * Projection used for the query that loads all data for the entire contact (except for 488 * social stream items). 489 */ 490 private static class ContactQuery { 491 final static String[] COLUMNS = new String[] { 492 Contacts.NAME_RAW_CONTACT_ID, 493 Contacts.DISPLAY_NAME_SOURCE, 494 Contacts.LOOKUP_KEY, 495 Contacts.DISPLAY_NAME, 496 Contacts.DISPLAY_NAME_ALTERNATIVE, 497 Contacts.PHONETIC_NAME, 498 Contacts.PHOTO_ID, 499 Contacts.STARRED, 500 Contacts.CONTACT_PRESENCE, 501 Contacts.CONTACT_STATUS, 502 Contacts.CONTACT_STATUS_TIMESTAMP, 503 Contacts.CONTACT_STATUS_RES_PACKAGE, 504 Contacts.CONTACT_STATUS_LABEL, 505 Contacts.Entity.CONTACT_ID, 506 Contacts.Entity.RAW_CONTACT_ID, 507 508 RawContacts.ACCOUNT_NAME, 509 RawContacts.ACCOUNT_TYPE, 510 RawContacts.DATA_SET, 511 RawContacts.ACCOUNT_TYPE_AND_DATA_SET, 512 RawContacts.DIRTY, 513 RawContacts.VERSION, 514 RawContacts.SOURCE_ID, 515 RawContacts.SYNC1, 516 RawContacts.SYNC2, 517 RawContacts.SYNC3, 518 RawContacts.SYNC4, 519 RawContacts.DELETED, 520 RawContacts.NAME_VERIFIED, 521 522 Contacts.Entity.DATA_ID, 523 Data.DATA1, 524 Data.DATA2, 525 Data.DATA3, 526 Data.DATA4, 527 Data.DATA5, 528 Data.DATA6, 529 Data.DATA7, 530 Data.DATA8, 531 Data.DATA9, 532 Data.DATA10, 533 Data.DATA11, 534 Data.DATA12, 535 Data.DATA13, 536 Data.DATA14, 537 Data.DATA15, 538 Data.SYNC1, 539 Data.SYNC2, 540 Data.SYNC3, 541 Data.SYNC4, 542 Data.DATA_VERSION, 543 Data.IS_PRIMARY, 544 Data.IS_SUPER_PRIMARY, 545 Data.MIMETYPE, 546 Data.RES_PACKAGE, 547 548 GroupMembership.GROUP_SOURCE_ID, 549 550 Data.PRESENCE, 551 Data.CHAT_CAPABILITY, 552 Data.STATUS, 553 Data.STATUS_RES_PACKAGE, 554 Data.STATUS_ICON, 555 Data.STATUS_LABEL, 556 Data.STATUS_TIMESTAMP, 557 558 Contacts.PHOTO_URI, 559 Contacts.SEND_TO_VOICEMAIL, 560 Contacts.CUSTOM_RINGTONE, 561 Contacts.IS_USER_PROFILE, 562 }; 563 564 public final static int NAME_RAW_CONTACT_ID = 0; 565 public final static int DISPLAY_NAME_SOURCE = 1; 566 public final static int LOOKUP_KEY = 2; 567 public final static int DISPLAY_NAME = 3; 568 public final static int ALT_DISPLAY_NAME = 4; 569 public final static int PHONETIC_NAME = 5; 570 public final static int PHOTO_ID = 6; 571 public final static int STARRED = 7; 572 public final static int CONTACT_PRESENCE = 8; 573 public final static int CONTACT_STATUS = 9; 574 public final static int CONTACT_STATUS_TIMESTAMP = 10; 575 public final static int CONTACT_STATUS_RES_PACKAGE = 11; 576 public final static int CONTACT_STATUS_LABEL = 12; 577 public final static int CONTACT_ID = 13; 578 public final static int RAW_CONTACT_ID = 14; 579 580 public final static int ACCOUNT_NAME = 15; 581 public final static int ACCOUNT_TYPE = 16; 582 public final static int DATA_SET = 17; 583 public final static int ACCOUNT_TYPE_AND_DATA_SET = 18; 584 public final static int DIRTY = 19; 585 public final static int VERSION = 20; 586 public final static int SOURCE_ID = 21; 587 public final static int SYNC1 = 22; 588 public final static int SYNC2 = 23; 589 public final static int SYNC3 = 24; 590 public final static int SYNC4 = 25; 591 public final static int DELETED = 26; 592 public final static int NAME_VERIFIED = 27; 593 594 public final static int DATA_ID = 28; 595 public final static int DATA1 = 29; 596 public final static int DATA2 = 30; 597 public final static int DATA3 = 31; 598 public final static int DATA4 = 32; 599 public final static int DATA5 = 33; 600 public final static int DATA6 = 34; 601 public final static int DATA7 = 35; 602 public final static int DATA8 = 36; 603 public final static int DATA9 = 37; 604 public final static int DATA10 = 38; 605 public final static int DATA11 = 39; 606 public final static int DATA12 = 40; 607 public final static int DATA13 = 41; 608 public final static int DATA14 = 42; 609 public final static int DATA15 = 43; 610 public final static int DATA_SYNC1 = 44; 611 public final static int DATA_SYNC2 = 45; 612 public final static int DATA_SYNC3 = 46; 613 public final static int DATA_SYNC4 = 47; 614 public final static int DATA_VERSION = 48; 615 public final static int IS_PRIMARY = 49; 616 public final static int IS_SUPERPRIMARY = 50; 617 public final static int MIMETYPE = 51; 618 public final static int RES_PACKAGE = 52; 619 620 public final static int GROUP_SOURCE_ID = 53; 621 622 public final static int PRESENCE = 54; 623 public final static int CHAT_CAPABILITY = 55; 624 public final static int STATUS = 56; 625 public final static int STATUS_RES_PACKAGE = 57; 626 public final static int STATUS_ICON = 58; 627 public final static int STATUS_LABEL = 59; 628 public final static int STATUS_TIMESTAMP = 60; 629 630 public final static int PHOTO_URI = 61; 631 public final static int SEND_TO_VOICEMAIL = 62; 632 public final static int CUSTOM_RINGTONE = 63; 633 public final static int IS_USER_PROFILE = 64; 634 } 635 636 /** 637 * Projection used for the query that loads all data for the entire contact. 638 */ 639 private static class DirectoryQuery { 640 final static String[] COLUMNS = new String[] { 641 Directory.DISPLAY_NAME, 642 Directory.PACKAGE_NAME, 643 Directory.TYPE_RESOURCE_ID, 644 Directory.ACCOUNT_TYPE, 645 Directory.ACCOUNT_NAME, 646 Directory.EXPORT_SUPPORT, 647 }; 648 649 public final static int DISPLAY_NAME = 0; 650 public final static int PACKAGE_NAME = 1; 651 public final static int TYPE_RESOURCE_ID = 2; 652 public final static int ACCOUNT_TYPE = 3; 653 public final static int ACCOUNT_NAME = 4; 654 public final static int EXPORT_SUPPORT = 5; 655 } 656 657 private static class GroupQuery { 658 final static String[] COLUMNS = new String[] { 659 Groups.ACCOUNT_NAME, 660 Groups.ACCOUNT_TYPE, 661 Groups.DATA_SET, 662 Groups.ACCOUNT_TYPE_AND_DATA_SET, 663 Groups._ID, 664 Groups.TITLE, 665 Groups.AUTO_ADD, 666 Groups.FAVORITES, 667 }; 668 669 public final static int ACCOUNT_NAME = 0; 670 public final static int ACCOUNT_TYPE = 1; 671 public final static int DATA_SET = 2; 672 public final static int ACCOUNT_TYPE_AND_DATA_SET = 3; 673 public final static int ID = 4; 674 public final static int TITLE = 5; 675 public final static int AUTO_ADD = 6; 676 public final static int FAVORITES = 7; 677 } 678 679 private final class LoadContactTask extends AsyncTask<Void, Void, Result> { 680 681 @Override 682 protected Result doInBackground(Void... args) { 683 try { 684 final ContentResolver resolver = getContext().getContentResolver(); 685 final Uri uriCurrentFormat = ContactLoaderUtils.ensureIsContactUri( 686 resolver, mLookupUri); 687 Result result = loadContactEntity(resolver, uriCurrentFormat); 688 if (!result.isNotFound()) { 689 if (result.isDirectoryEntry()) { 690 loadDirectoryMetaData(result); 691 } else if (mLoadGroupMetaData) { 692 loadGroupMetaData(result); 693 } 694 if (mLoadStreamItems) { 695 loadStreamItems(result); 696 } 697 loadPhotoBinaryData(result); 698 699 // Note ME profile should never have "Add connection" 700 if (mLoadInvitableAccountTypes && !result.isUserProfile()) { 701 loadInvitableAccountTypes(result); 702 } 703 } 704 return result; 705 } catch (Exception e) { 706 Log.e(TAG, "Error loading the contact: " + mLookupUri, e); 707 return Result.forError(mRequestedUri, e); 708 } 709 } 710 711 private Result loadContactEntity(ContentResolver resolver, Uri contactUri) { 712 Uri entityUri = Uri.withAppendedPath(contactUri, Contacts.Entity.CONTENT_DIRECTORY); 713 Cursor cursor = resolver.query(entityUri, ContactQuery.COLUMNS, null, null, 714 Contacts.Entity.RAW_CONTACT_ID); 715 if (cursor == null) { 716 Log.e(TAG, "No cursor returned in loadContactEntity"); 717 return Result.forNotFound(mRequestedUri); 718 } 719 720 try { 721 if (!cursor.moveToFirst()) { 722 cursor.close(); 723 return Result.forNotFound(mRequestedUri); 724 } 725 726 long currentRawContactId = -1; 727 Entity entity = null; 728 Result result = loadContactHeaderData(cursor, contactUri); 729 ArrayList<Entity> entities = result.getEntities(); 730 HashMap<Long, DataStatus> statuses = result.getStatuses(); 731 for (; !cursor.isAfterLast(); cursor.moveToNext()) { 732 long rawContactId = cursor.getLong(ContactQuery.RAW_CONTACT_ID); 733 if (rawContactId != currentRawContactId) { 734 currentRawContactId = rawContactId; 735 entity = new android.content.Entity(loadRawContact(cursor)); 736 entities.add(entity); 737 } 738 if (!cursor.isNull(ContactQuery.DATA_ID)) { 739 ContentValues data = loadData(cursor); 740 entity.addSubValue(ContactsContract.Data.CONTENT_URI, data); 741 742 if (!cursor.isNull(ContactQuery.PRESENCE) 743 || !cursor.isNull(ContactQuery.STATUS)) { 744 final DataStatus status = new DataStatus(cursor); 745 final long dataId = cursor.getLong(ContactQuery.DATA_ID); 746 statuses.put(dataId, status); 747 } 748 } 749 } 750 751 return result; 752 } finally { 753 cursor.close(); 754 } 755 } 756 757 /** 758 * Looks for the photo data item in entities. If found, creates a new Bitmap instance. If 759 * not found, returns null 760 */ 761 private void loadPhotoBinaryData(Result contactData) { 762 763 // If we have a photo URI, try loading that first. 764 String photoUri = contactData.getPhotoUri(); 765 if (photoUri != null) { 766 try { 767 AssetFileDescriptor fd = getContext().getContentResolver() 768 .openAssetFileDescriptor(Uri.parse(photoUri), "r"); 769 byte[] buffer = new byte[16 * 1024]; 770 FileInputStream fis = fd.createInputStream(); 771 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 772 try { 773 int size; 774 while ((size = fis.read(buffer)) != -1) { 775 baos.write(buffer, 0, size); 776 } 777 contactData.setPhotoBinaryData(baos.toByteArray()); 778 } finally { 779 fis.close(); 780 fd.close(); 781 } 782 return; 783 } catch (IOException ioe) { 784 // Just fall back to the case below. 785 } 786 } 787 788 // If we couldn't load from a file, fall back to the data blob. 789 final long photoId = contactData.getPhotoId(); 790 if (photoId <= 0) { 791 // No photo ID 792 return; 793 } 794 795 for (Entity entity : contactData.getEntities()) { 796 for (NamedContentValues subValue : entity.getSubValues()) { 797 final ContentValues entryValues = subValue.values; 798 final long dataId = entryValues.getAsLong(Data._ID); 799 if (dataId == photoId) { 800 final String mimeType = entryValues.getAsString(Data.MIMETYPE); 801 // Correct Data Id but incorrect MimeType? Don't load 802 if (!Photo.CONTENT_ITEM_TYPE.equals(mimeType)) { 803 return; 804 } 805 contactData.setPhotoBinaryData(entryValues.getAsByteArray(Photo.PHOTO)); 806 break; 807 } 808 } 809 } 810 } 811 812 /** 813 * Sets the "invitable" account types to {@link Result#mInvitableAccountTypes}. 814 */ 815 private void loadInvitableAccountTypes(Result contactData) { 816 Map<AccountTypeWithDataSet, AccountType> invitables = 817 AccountTypeManager.getInstance(getContext()).getUsableInvitableAccountTypes(); 818 if (invitables.isEmpty()) { 819 return; 820 } 821 822 HashMap<AccountTypeWithDataSet, AccountType> result = Maps.newHashMap(invitables); 823 824 // Remove the ones that already have a raw contact in the current contact 825 for (Entity entity : contactData.getEntities()) { 826 final ContentValues values = entity.getEntityValues(); 827 final AccountTypeWithDataSet type = AccountTypeWithDataSet.get( 828 values.getAsString(RawContacts.ACCOUNT_TYPE), 829 values.getAsString(RawContacts.DATA_SET)); 830 result.remove(type); 831 } 832 833 // Set to mInvitableAccountTypes 834 contactData.mInvitableAccountTypes.addAll(result.values()); 835 } 836 837 /** 838 * Extracts Contact level columns from the cursor. 839 */ 840 private Result loadContactHeaderData(final Cursor cursor, Uri contactUri) { 841 final String directoryParameter = 842 contactUri.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY); 843 final long directoryId = directoryParameter == null 844 ? Directory.DEFAULT 845 : Long.parseLong(directoryParameter); 846 final long contactId = cursor.getLong(ContactQuery.CONTACT_ID); 847 final String lookupKey = cursor.getString(ContactQuery.LOOKUP_KEY); 848 final long nameRawContactId = cursor.getLong(ContactQuery.NAME_RAW_CONTACT_ID); 849 final int displayNameSource = cursor.getInt(ContactQuery.DISPLAY_NAME_SOURCE); 850 final String displayName = cursor.getString(ContactQuery.DISPLAY_NAME); 851 final String altDisplayName = cursor.getString(ContactQuery.ALT_DISPLAY_NAME); 852 final String phoneticName = cursor.getString(ContactQuery.PHONETIC_NAME); 853 final long photoId = cursor.getLong(ContactQuery.PHOTO_ID); 854 final String photoUri = cursor.getString(ContactQuery.PHOTO_URI); 855 final boolean starred = cursor.getInt(ContactQuery.STARRED) != 0; 856 final Integer presence = cursor.isNull(ContactQuery.CONTACT_PRESENCE) 857 ? null 858 : cursor.getInt(ContactQuery.CONTACT_PRESENCE); 859 final boolean sendToVoicemail = cursor.getInt(ContactQuery.SEND_TO_VOICEMAIL) == 1; 860 final String customRingtone = cursor.getString(ContactQuery.CUSTOM_RINGTONE); 861 final boolean isUserProfile = cursor.getInt(ContactQuery.IS_USER_PROFILE) == 1; 862 863 Uri lookupUri; 864 if (directoryId == Directory.DEFAULT || directoryId == Directory.LOCAL_INVISIBLE) { 865 lookupUri = ContentUris.withAppendedId( 866 Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, lookupKey), contactId); 867 } else { 868 lookupUri = contactUri; 869 } 870 871 return new Result(mRequestedUri, contactUri, lookupUri, directoryId, lookupKey, 872 contactId, nameRawContactId, displayNameSource, photoId, photoUri, displayName, 873 altDisplayName, phoneticName, starred, presence, sendToVoicemail, 874 customRingtone, isUserProfile); 875 } 876 877 /** 878 * Extracts RawContact level columns from the cursor. 879 */ 880 private ContentValues loadRawContact(Cursor cursor) { 881 ContentValues cv = new ContentValues(); 882 883 cv.put(RawContacts._ID, cursor.getLong(ContactQuery.RAW_CONTACT_ID)); 884 885 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_NAME); 886 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE); 887 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SET); 888 cursorColumnToContentValues(cursor, cv, ContactQuery.ACCOUNT_TYPE_AND_DATA_SET); 889 cursorColumnToContentValues(cursor, cv, ContactQuery.DIRTY); 890 cursorColumnToContentValues(cursor, cv, ContactQuery.VERSION); 891 cursorColumnToContentValues(cursor, cv, ContactQuery.SOURCE_ID); 892 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC1); 893 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC2); 894 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC3); 895 cursorColumnToContentValues(cursor, cv, ContactQuery.SYNC4); 896 cursorColumnToContentValues(cursor, cv, ContactQuery.DELETED); 897 cursorColumnToContentValues(cursor, cv, ContactQuery.CONTACT_ID); 898 cursorColumnToContentValues(cursor, cv, ContactQuery.STARRED); 899 cursorColumnToContentValues(cursor, cv, ContactQuery.NAME_VERIFIED); 900 901 return cv; 902 } 903 904 /** 905 * Extracts Data level columns from the cursor. 906 */ 907 private ContentValues loadData(Cursor cursor) { 908 ContentValues cv = new ContentValues(); 909 910 cv.put(Data._ID, cursor.getLong(ContactQuery.DATA_ID)); 911 912 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA1); 913 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA2); 914 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA3); 915 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA4); 916 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA5); 917 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA6); 918 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA7); 919 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA8); 920 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA9); 921 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA10); 922 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA11); 923 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA12); 924 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA13); 925 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA14); 926 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA15); 927 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC1); 928 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC2); 929 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC3); 930 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_SYNC4); 931 cursorColumnToContentValues(cursor, cv, ContactQuery.DATA_VERSION); 932 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_PRIMARY); 933 cursorColumnToContentValues(cursor, cv, ContactQuery.IS_SUPERPRIMARY); 934 cursorColumnToContentValues(cursor, cv, ContactQuery.MIMETYPE); 935 cursorColumnToContentValues(cursor, cv, ContactQuery.RES_PACKAGE); 936 cursorColumnToContentValues(cursor, cv, ContactQuery.GROUP_SOURCE_ID); 937 cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY); 938 939 return cv; 940 } 941 942 private void cursorColumnToContentValues( 943 Cursor cursor, ContentValues values, int index) { 944 switch (cursor.getType(index)) { 945 case Cursor.FIELD_TYPE_NULL: 946 // don't put anything in the content values 947 break; 948 case Cursor.FIELD_TYPE_INTEGER: 949 values.put(ContactQuery.COLUMNS[index], cursor.getLong(index)); 950 break; 951 case Cursor.FIELD_TYPE_STRING: 952 values.put(ContactQuery.COLUMNS[index], cursor.getString(index)); 953 break; 954 case Cursor.FIELD_TYPE_BLOB: 955 values.put(ContactQuery.COLUMNS[index], cursor.getBlob(index)); 956 break; 957 default: 958 throw new IllegalStateException("Invalid or unhandled data type"); 959 } 960 } 961 962 private void loadDirectoryMetaData(Result result) { 963 long directoryId = result.getDirectoryId(); 964 965 Cursor cursor = getContext().getContentResolver().query( 966 ContentUris.withAppendedId(Directory.CONTENT_URI, directoryId), 967 DirectoryQuery.COLUMNS, null, null, null); 968 if (cursor == null) { 969 return; 970 } 971 try { 972 if (cursor.moveToFirst()) { 973 final String displayName = cursor.getString(DirectoryQuery.DISPLAY_NAME); 974 final String packageName = cursor.getString(DirectoryQuery.PACKAGE_NAME); 975 final int typeResourceId = cursor.getInt(DirectoryQuery.TYPE_RESOURCE_ID); 976 final String accountType = cursor.getString(DirectoryQuery.ACCOUNT_TYPE); 977 final String accountName = cursor.getString(DirectoryQuery.ACCOUNT_NAME); 978 final int exportSupport = cursor.getInt(DirectoryQuery.EXPORT_SUPPORT); 979 String directoryType = null; 980 if (!TextUtils.isEmpty(packageName)) { 981 PackageManager pm = getContext().getPackageManager(); 982 try { 983 Resources resources = pm.getResourcesForApplication(packageName); 984 directoryType = resources.getString(typeResourceId); 985 } catch (NameNotFoundException e) { 986 Log.w(TAG, "Contact directory resource not found: " 987 + packageName + "." + typeResourceId); 988 } 989 } 990 991 result.setDirectoryMetaData( 992 displayName, directoryType, accountType, accountName, exportSupport); 993 } 994 } finally { 995 cursor.close(); 996 } 997 } 998 999 /** 1000 * Loads groups meta-data for all groups associated with all constituent raw contacts' 1001 * accounts. 1002 */ 1003 private void loadGroupMetaData(Result result) { 1004 StringBuilder selection = new StringBuilder(); 1005 ArrayList<String> selectionArgs = new ArrayList<String>(); 1006 for (Entity entity : result.mEntities) { 1007 ContentValues values = entity.getEntityValues(); 1008 String accountName = values.getAsString(RawContacts.ACCOUNT_NAME); 1009 String accountType = values.getAsString(RawContacts.ACCOUNT_TYPE); 1010 String dataSet = values.getAsString(RawContacts.DATA_SET); 1011 if (accountName != null && accountType != null) { 1012 if (selection.length() != 0) { 1013 selection.append(" OR "); 1014 } 1015 selection.append( 1016 "(" + Groups.ACCOUNT_NAME + "=? AND " + Groups.ACCOUNT_TYPE + "=?"); 1017 selectionArgs.add(accountName); 1018 selectionArgs.add(accountType); 1019 1020 if (dataSet != null) { 1021 selection.append(" AND " + Groups.DATA_SET + "=?"); 1022 selectionArgs.add(dataSet); 1023 } else { 1024 selection.append(" AND " + Groups.DATA_SET + " IS NULL"); 1025 } 1026 selection.append(")"); 1027 } 1028 } 1029 Cursor cursor = getContext().getContentResolver().query(Groups.CONTENT_URI, 1030 GroupQuery.COLUMNS, selection.toString(), selectionArgs.toArray(new String[0]), 1031 null); 1032 try { 1033 while (cursor.moveToNext()) { 1034 final String accountName = cursor.getString(GroupQuery.ACCOUNT_NAME); 1035 final String accountType = cursor.getString(GroupQuery.ACCOUNT_TYPE); 1036 final String dataSet = cursor.getString(GroupQuery.DATA_SET); 1037 final long groupId = cursor.getLong(GroupQuery.ID); 1038 final String title = cursor.getString(GroupQuery.TITLE); 1039 final boolean defaultGroup = cursor.isNull(GroupQuery.AUTO_ADD) 1040 ? false 1041 : cursor.getInt(GroupQuery.AUTO_ADD) != 0; 1042 final boolean favorites = cursor.isNull(GroupQuery.FAVORITES) 1043 ? false 1044 : cursor.getInt(GroupQuery.FAVORITES) != 0; 1045 1046 result.addGroupMetaData(new GroupMetaData( 1047 accountName, accountType, dataSet, groupId, title, defaultGroup, 1048 favorites)); 1049 } 1050 } finally { 1051 cursor.close(); 1052 } 1053 } 1054 1055 /** 1056 * Loads all stream items and stream item photos belonging to this contact. 1057 */ 1058 private void loadStreamItems(Result result) { 1059 Cursor cursor = getContext().getContentResolver().query( 1060 Contacts.CONTENT_LOOKUP_URI.buildUpon() 1061 .appendPath(result.getLookupKey()) 1062 .appendPath(Contacts.StreamItems.CONTENT_DIRECTORY).build(), 1063 null, null, null, null); 1064 Map<Long, StreamItemEntry> streamItemsById = new HashMap<Long, StreamItemEntry>(); 1065 ArrayList<StreamItemEntry> streamItems = new ArrayList<StreamItemEntry>(); 1066 try { 1067 while (cursor.moveToNext()) { 1068 StreamItemEntry streamItem = new StreamItemEntry(cursor); 1069 streamItemsById.put(streamItem.getId(), streamItem); 1070 streamItems.add(streamItem); 1071 } 1072 } finally { 1073 cursor.close(); 1074 } 1075 1076 // Now retrieve any photo records associated with the stream items. 1077 if (!streamItems.isEmpty()) { 1078 if (result.isUserProfile()) { 1079 // If the stream items we're loading are for the profile, we can't bulk-load the 1080 // stream items with a custom selection. 1081 for (StreamItemEntry entry : streamItems) { 1082 Cursor siCursor = getContext().getContentResolver().query( 1083 Uri.withAppendedPath( 1084 ContentUris.withAppendedId( 1085 StreamItems.CONTENT_URI, entry.getId()), 1086 StreamItems.StreamItemPhotos.CONTENT_DIRECTORY), 1087 null, null, null, null); 1088 try { 1089 while (siCursor.moveToNext()) { 1090 entry.addPhoto(new StreamItemPhotoEntry(siCursor)); 1091 } 1092 } finally { 1093 siCursor.close(); 1094 } 1095 } 1096 } else { 1097 String[] streamItemIdArr = new String[streamItems.size()]; 1098 StringBuilder streamItemPhotoSelection = new StringBuilder(); 1099 streamItemPhotoSelection.append(StreamItemPhotos.STREAM_ITEM_ID + " IN ("); 1100 for (int i = 0; i < streamItems.size(); i++) { 1101 if (i > 0) { 1102 streamItemPhotoSelection.append(","); 1103 } 1104 streamItemPhotoSelection.append("?"); 1105 streamItemIdArr[i] = String.valueOf(streamItems.get(i).getId()); 1106 } 1107 streamItemPhotoSelection.append(")"); 1108 Cursor sipCursor = getContext().getContentResolver().query( 1109 StreamItems.CONTENT_PHOTO_URI, 1110 null, streamItemPhotoSelection.toString(), streamItemIdArr, 1111 StreamItemPhotos.STREAM_ITEM_ID); 1112 try { 1113 while (sipCursor.moveToNext()) { 1114 long streamItemId = sipCursor.getLong( 1115 sipCursor.getColumnIndex(StreamItemPhotos.STREAM_ITEM_ID)); 1116 StreamItemEntry streamItem = streamItemsById.get(streamItemId); 1117 streamItem.addPhoto(new StreamItemPhotoEntry(sipCursor)); 1118 } 1119 } finally { 1120 sipCursor.close(); 1121 } 1122 } 1123 } 1124 1125 // Set the sorted stream items on the result. 1126 Collections.sort(streamItems); 1127 result.mStreamItems.addAll(streamItems); 1128 } 1129 1130 @Override 1131 protected void onPostExecute(Result result) { 1132 unregisterObserver(); 1133 1134 // The creator isn't interested in any further updates 1135 if (mDestroyed || result == null) { 1136 return; 1137 } 1138 1139 mContact = result; 1140 1141 if (result.isLoaded()) { 1142 mLookupUri = result.getLookupUri(); 1143 1144 if (!result.isDirectoryEntry()) { 1145 Log.i(TAG, "Registering content observer for " + mLookupUri); 1146 if (mObserver == null) { 1147 mObserver = new ForceLoadContentObserver(); 1148 } 1149 getContext().getContentResolver().registerContentObserver( 1150 mLookupUri, true, mObserver); 1151 } 1152 1153 if (mContact.getPhotoBinaryData() == null && mContact.getPhotoUri() != null) { 1154 mContact.setLoadingPhoto(true); 1155 new AsyncPhotoLoader().execute(mContact.getPhotoUri()); 1156 } 1157 1158 // inform the source of the data that this contact is being looked at 1159 postViewNotificationToSyncAdapter(); 1160 } 1161 1162 deliverResult(mContact); 1163 } 1164 } 1165 1166 /** 1167 * Posts a message to the contributing sync adapters that have opted-in, notifying them 1168 * that the contact has just been loaded 1169 */ 1170 private void postViewNotificationToSyncAdapter() { 1171 Context context = getContext(); 1172 for (Entity entity : mContact.getEntities()) { 1173 final ContentValues entityValues = entity.getEntityValues(); 1174 final long rawContactId = entityValues.getAsLong(RawContacts.Entity._ID); 1175 if (mNotifiedRawContactIds.contains(rawContactId)) { 1176 continue; // Already notified for this raw contact. 1177 } 1178 mNotifiedRawContactIds.add(rawContactId); 1179 final String type = entityValues.getAsString(RawContacts.ACCOUNT_TYPE); 1180 final String dataSet = entityValues.getAsString(RawContacts.DATA_SET); 1181 final AccountType accountType = AccountTypeManager.getInstance(context).getAccountType( 1182 type, dataSet); 1183 final String serviceName = accountType.getViewContactNotifyServiceClassName(); 1184 final String resPackageName = accountType.resPackageName; 1185 if (!TextUtils.isEmpty(serviceName) && !TextUtils.isEmpty(resPackageName)) { 1186 final Uri uri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId); 1187 final Intent intent = new Intent(); 1188 intent.setClassName(resPackageName, serviceName); 1189 intent.setAction(Intent.ACTION_VIEW); 1190 intent.setDataAndType(uri, RawContacts.CONTENT_ITEM_TYPE); 1191 try { 1192 context.startService(intent); 1193 } catch (Exception e) { 1194 Log.e(TAG, "Error sending message to source-app", e); 1195 } 1196 } 1197 } 1198 } 1199 1200 private class AsyncPhotoLoader extends AsyncTask<String, Void, byte[]> { 1201 1202 private static final int BUFFER_SIZE = 1024*16; 1203 1204 @Override 1205 protected byte[] doInBackground(String... params) { 1206 Uri uri = Uri.parse(params[0]); 1207 byte[] data = null; 1208 try { 1209 InputStream is = getContext().getContentResolver().openInputStream(uri); 1210 if (is != null) { 1211 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 1212 try { 1213 byte[] mBuffer = new byte[BUFFER_SIZE]; 1214 1215 int size; 1216 while ((size = is.read(mBuffer)) != -1) { 1217 baos.write(mBuffer, 0, size); 1218 } 1219 data = baos.toByteArray(); 1220 } finally { 1221 is.close(); 1222 } 1223 } else { 1224 Log.v(TAG, "Cannot load photo " + uri); 1225 } 1226 } catch (IOException e) { 1227 Log.e(TAG, "Cannot load photo " + uri, e); 1228 } 1229 1230 return data; 1231 } 1232 1233 @Override 1234 protected void onPostExecute(byte[] data) { 1235 if (mContact != null) { 1236 mContact = new Result(mContact); 1237 mContact.setPhotoBinaryData(data); 1238 mContact.setLoadingPhoto(false); 1239 deliverResult(mContact); 1240 } 1241 } 1242 } 1243 1244 private void unregisterObserver() { 1245 if (mObserver != null) { 1246 getContext().getContentResolver().unregisterContentObserver(mObserver); 1247 mObserver = null; 1248 } 1249 } 1250 1251 public ContactLoader(Context context, Uri lookupUri) { 1252 this(context, lookupUri, false, false, false); 1253 } 1254 1255 public ContactLoader(Context context, Uri lookupUri, boolean loadGroupMetaData, 1256 boolean loadStreamItems, boolean loadInvitableAccountTypes) { 1257 super(context); 1258 mLookupUri = lookupUri; 1259 mRequestedUri = lookupUri; 1260 mLoadGroupMetaData = loadGroupMetaData; 1261 mLoadStreamItems = loadStreamItems; 1262 mLoadInvitableAccountTypes = loadInvitableAccountTypes; 1263 } 1264 1265 public Uri getLookupUri() { 1266 return mLookupUri; 1267 } 1268 1269 @Override 1270 protected void onStartLoading() { 1271 if (mContact != null) { 1272 deliverResult(mContact); 1273 } 1274 1275 if (takeContentChanged() || mContact == null) { 1276 forceLoad(); 1277 } 1278 } 1279 1280 @Override 1281 protected void onForceLoad() { 1282 final LoadContactTask task = new LoadContactTask(); 1283 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[])null); 1284 } 1285 1286 @Override 1287 protected void onReset() { 1288 unregisterObserver(); 1289 mContact = null; 1290 mDestroyed = true; 1291 } 1292 } 1293