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