1 /* 2 * Copyright (C) 2012 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.model; 18 19 import android.content.ContentValues; 20 import android.content.Context; 21 import android.net.Uri; 22 import android.provider.ContactsContract.CommonDataKinds.Photo; 23 import android.provider.ContactsContract.Data; 24 import android.provider.ContactsContract.Directory; 25 import android.provider.ContactsContract.DisplayNameSources; 26 27 import com.android.contacts.group.GroupMetaData; 28 import com.android.contacts.model.account.AccountType; 29 import com.android.contacts.model.account.SimAccountType; 30 import com.android.contacts.util.DataStatus; 31 32 import com.google.common.annotations.VisibleForTesting; 33 import com.google.common.collect.ImmutableList; 34 import com.google.common.collect.ImmutableMap; 35 36 import java.util.ArrayList; 37 38 /** 39 * A Contact represents a single person or logical entity as perceived by the user. The information 40 * about a contact can come from multiple data sources, which are each represented by a RawContact 41 * object. Thus, a Contact is associated with a collection of RawContact objects. 42 * 43 * The aggregation of raw contacts into a single contact is performed automatically, and it is 44 * also possible for users to manually split and join raw contacts into various contacts. 45 * 46 * Only the {@link ContactLoader} class can create a Contact object with various flags to allow 47 * partial loading of contact data. Thus, an instance of this class should be treated as 48 * a read-only object. 49 */ 50 public class Contact { 51 private enum Status { 52 /** Contact is successfully loaded */ 53 LOADED, 54 /** There was an error loading the contact */ 55 ERROR, 56 /** Contact is not found */ 57 NOT_FOUND, 58 } 59 60 private final Uri mRequestedUri; 61 private final Uri mLookupUri; 62 private final Uri mUri; 63 private final long mDirectoryId; 64 private final String mLookupKey; 65 private final long mId; 66 private final long mNameRawContactId; 67 private final int mDisplayNameSource; 68 private final long mPhotoId; 69 private final String mPhotoUri; 70 private final String mDisplayName; 71 private final String mAltDisplayName; 72 private final String mPhoneticName; 73 private final boolean mStarred; 74 private final Integer mPresence; 75 private ImmutableList<RawContact> mRawContacts; 76 private ImmutableMap<Long,DataStatus> mStatuses; 77 78 private String mDirectoryDisplayName; 79 private String mDirectoryType; 80 private String mDirectoryAccountType; 81 private String mDirectoryAccountName; 82 private int mDirectoryExportSupport; 83 84 private ImmutableList<GroupMetaData> mGroups; 85 86 private byte[] mPhotoBinaryData; 87 /** 88 * Small version of the contact photo loaded from a blob instead of from a file. If a large 89 * contact photo is not available yet, then this has the same value as mPhotoBinaryData. 90 */ 91 private byte[] mThumbnailPhotoBinaryData; 92 private final boolean mSendToVoicemail; 93 private final String mCustomRingtone; 94 private final boolean mIsUserProfile; 95 96 private final Contact.Status mStatus; 97 private final Exception mException; 98 99 /** 100 * Constructor for special results, namely "no contact found" and "error". 101 */ Contact(Uri requestedUri, Contact.Status status, Exception exception)102 private Contact(Uri requestedUri, Contact.Status status, Exception exception) { 103 if (status == Status.ERROR && exception == null) { 104 throw new IllegalArgumentException("ERROR result must have exception"); 105 } 106 mStatus = status; 107 mException = exception; 108 mRequestedUri = requestedUri; 109 mLookupUri = null; 110 mUri = null; 111 mDirectoryId = -1; 112 mLookupKey = null; 113 mId = -1; 114 mRawContacts = null; 115 mStatuses = null; 116 mNameRawContactId = -1; 117 mDisplayNameSource = DisplayNameSources.UNDEFINED; 118 mPhotoId = -1; 119 mPhotoUri = null; 120 mDisplayName = null; 121 mAltDisplayName = null; 122 mPhoneticName = null; 123 mStarred = false; 124 mPresence = null; 125 mSendToVoicemail = false; 126 mCustomRingtone = null; 127 mIsUserProfile = false; 128 } 129 forError(Uri requestedUri, Exception exception)130 public static Contact forError(Uri requestedUri, Exception exception) { 131 return new Contact(requestedUri, Status.ERROR, exception); 132 } 133 forNotFound(Uri requestedUri)134 public static Contact forNotFound(Uri requestedUri) { 135 return new Contact(requestedUri, Status.NOT_FOUND, null); 136 } 137 138 /** 139 * Constructor to call when contact was found 140 */ Contact(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey, long id, long nameRawContactId, int displayNameSource, long photoId, String photoUri, String displayName, String altDisplayName, String phoneticName, boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone, boolean isUserProfile)141 public Contact(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey, 142 long id, long nameRawContactId, int displayNameSource, long photoId, 143 String photoUri, String displayName, String altDisplayName, String phoneticName, 144 boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone, 145 boolean isUserProfile) { 146 mStatus = Status.LOADED; 147 mException = null; 148 mRequestedUri = requestedUri; 149 mLookupUri = lookupUri; 150 mUri = uri; 151 mDirectoryId = directoryId; 152 mLookupKey = lookupKey; 153 mId = id; 154 mRawContacts = null; 155 mStatuses = null; 156 mNameRawContactId = nameRawContactId; 157 mDisplayNameSource = displayNameSource; 158 mPhotoId = photoId; 159 mPhotoUri = photoUri; 160 mDisplayName = displayName; 161 mAltDisplayName = altDisplayName; 162 mPhoneticName = phoneticName; 163 mStarred = starred; 164 mPresence = presence; 165 mSendToVoicemail = sendToVoicemail; 166 mCustomRingtone = customRingtone; 167 mIsUserProfile = isUserProfile; 168 } 169 Contact(Uri requestedUri, Contact from)170 public Contact(Uri requestedUri, Contact from) { 171 mRequestedUri = requestedUri; 172 173 mStatus = from.mStatus; 174 mException = from.mException; 175 mLookupUri = from.mLookupUri; 176 mUri = from.mUri; 177 mDirectoryId = from.mDirectoryId; 178 mLookupKey = from.mLookupKey; 179 mId = from.mId; 180 mNameRawContactId = from.mNameRawContactId; 181 mDisplayNameSource = from.mDisplayNameSource; 182 mPhotoId = from.mPhotoId; 183 mPhotoUri = from.mPhotoUri; 184 mDisplayName = from.mDisplayName; 185 mAltDisplayName = from.mAltDisplayName; 186 mPhoneticName = from.mPhoneticName; 187 mStarred = from.mStarred; 188 mPresence = from.mPresence; 189 mRawContacts = from.mRawContacts; 190 mStatuses = from.mStatuses; 191 192 mDirectoryDisplayName = from.mDirectoryDisplayName; 193 mDirectoryType = from.mDirectoryType; 194 mDirectoryAccountType = from.mDirectoryAccountType; 195 mDirectoryAccountName = from.mDirectoryAccountName; 196 mDirectoryExportSupport = from.mDirectoryExportSupport; 197 198 mGroups = from.mGroups; 199 200 mPhotoBinaryData = from.mPhotoBinaryData; 201 mSendToVoicemail = from.mSendToVoicemail; 202 mCustomRingtone = from.mCustomRingtone; 203 mIsUserProfile = from.mIsUserProfile; 204 } 205 206 /** 207 * @param exportSupport See {@link Directory#EXPORT_SUPPORT}. 208 */ setDirectoryMetaData(String displayName, String directoryType, String accountType, String accountName, int exportSupport)209 public void setDirectoryMetaData(String displayName, String directoryType, 210 String accountType, String accountName, int exportSupport) { 211 mDirectoryDisplayName = displayName; 212 mDirectoryType = directoryType; 213 mDirectoryAccountType = accountType; 214 mDirectoryAccountName = accountName; 215 mDirectoryExportSupport = exportSupport; 216 } 217 setPhotoBinaryData(byte[] photoBinaryData)218 /* package */ void setPhotoBinaryData(byte[] photoBinaryData) { 219 mPhotoBinaryData = photoBinaryData; 220 } 221 setThumbnailPhotoBinaryData(byte[] photoBinaryData)222 /* package */ void setThumbnailPhotoBinaryData(byte[] photoBinaryData) { 223 mThumbnailPhotoBinaryData = photoBinaryData; 224 } 225 226 /** 227 * Returns the URI for the contact that contains both the lookup key and the ID. This is 228 * the best URI to reference a contact. 229 * For directory contacts, this is the same a the URI as returned by {@link #getUri()} 230 */ getLookupUri()231 public Uri getLookupUri() { 232 return mLookupUri; 233 } 234 getLookupKey()235 public String getLookupKey() { 236 return mLookupKey; 237 } 238 239 /** 240 * Returns the contact Uri that was passed to the provider to make the query. This is 241 * the same as the requested Uri, unless the requested Uri doesn't specify a Contact: 242 * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will 243 * always reference the full aggregate contact. 244 */ getUri()245 public Uri getUri() { 246 return mUri; 247 } 248 249 /** 250 * Returns the URI for which this {@link ContactLoader) was initially requested. 251 */ getRequestedUri()252 public Uri getRequestedUri() { 253 return mRequestedUri; 254 } 255 256 /** 257 * Instantiate a new RawContactDeltaList for this contact. 258 */ createRawContactDeltaList()259 public RawContactDeltaList createRawContactDeltaList() { 260 return RawContactDeltaList.fromIterator(getRawContacts().iterator()); 261 } 262 263 /** 264 * Returns the contact ID. 265 */ 266 @VisibleForTesting getId()267 public long getId() { 268 return mId; 269 } 270 271 /** 272 * @return true when an exception happened during loading, in which case 273 * {@link #getException} returns the actual exception object. 274 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If 275 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false}, 276 * and vice versa. 277 */ isError()278 public boolean isError() { 279 return mStatus == Status.ERROR; 280 } 281 getException()282 public Exception getException() { 283 return mException; 284 } 285 286 /** 287 * @return true when the specified contact is not found. 288 * Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If 289 * {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false}, 290 * and vice versa. 291 */ isNotFound()292 public boolean isNotFound() { 293 return mStatus == Status.NOT_FOUND; 294 } 295 296 /** 297 * @return true if the specified contact is successfully loaded. 298 * i.e. neither {@link #isError()} nor {@link #isNotFound()}. 299 */ isLoaded()300 public boolean isLoaded() { 301 return mStatus == Status.LOADED; 302 } 303 getNameRawContactId()304 public long getNameRawContactId() { 305 return mNameRawContactId; 306 } 307 getDisplayNameSource()308 public int getDisplayNameSource() { 309 return mDisplayNameSource; 310 } 311 312 /** 313 * Used by various classes to determine whether or not this contact should be displayed as 314 * a business rather than a person. 315 */ isDisplayNameFromOrganization()316 public boolean isDisplayNameFromOrganization() { 317 return DisplayNameSources.ORGANIZATION == mDisplayNameSource; 318 } 319 getPhotoId()320 public long getPhotoId() { 321 return mPhotoId; 322 } 323 getPhotoUri()324 public String getPhotoUri() { 325 return mPhotoUri; 326 } 327 getDisplayName()328 public String getDisplayName() { 329 return mDisplayName; 330 } 331 getAltDisplayName()332 public String getAltDisplayName() { 333 return mAltDisplayName; 334 } 335 getPhoneticName()336 public String getPhoneticName() { 337 return mPhoneticName; 338 } 339 getStarred()340 public boolean getStarred() { 341 return mStarred; 342 } 343 getPresence()344 public Integer getPresence() { 345 return mPresence; 346 } 347 getRawContacts()348 public ImmutableList<RawContact> getRawContacts() { 349 return mRawContacts; 350 } 351 getStatuses()352 public ImmutableMap<Long, DataStatus> getStatuses() { 353 return mStatuses; 354 } 355 getDirectoryId()356 public long getDirectoryId() { 357 return mDirectoryId; 358 } 359 isDirectoryEntry()360 public boolean isDirectoryEntry() { 361 return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT 362 && mDirectoryId != Directory.LOCAL_INVISIBLE; 363 } 364 365 /** 366 * @return true if this is a contact (not group, etc.) with at least one 367 * writable raw-contact, and false otherwise. 368 */ isWritableContact(final Context context)369 public boolean isWritableContact(final Context context) { 370 return getFirstWritableRawContactId(context) != -1; 371 } 372 373 /** 374 * Return the ID of the first raw-contact in the contact data that belongs to a 375 * contact-writable account, or -1 if no such entity exists. 376 */ getFirstWritableRawContactId(final Context context)377 public long getFirstWritableRawContactId(final Context context) { 378 // Directory entries are non-writable 379 if (isDirectoryEntry()) return -1; 380 381 // Iterate through raw-contacts; if we find a writable on, return its ID. 382 for (RawContact rawContact : getRawContacts()) { 383 AccountType accountType = rawContact.getAccountType(context); 384 if (accountType != null && accountType.areContactsWritable()) { 385 return rawContact.getId(); 386 } 387 } 388 // No writable raw-contact was found. 389 return -1; 390 } 391 getDirectoryExportSupport()392 public int getDirectoryExportSupport() { 393 return mDirectoryExportSupport; 394 } 395 getDirectoryDisplayName()396 public String getDirectoryDisplayName() { 397 return mDirectoryDisplayName; 398 } 399 getDirectoryType()400 public String getDirectoryType() { 401 return mDirectoryType; 402 } 403 getDirectoryAccountType()404 public String getDirectoryAccountType() { 405 return mDirectoryAccountType; 406 } 407 getDirectoryAccountName()408 public String getDirectoryAccountName() { 409 return mDirectoryAccountName; 410 } 411 getPhotoBinaryData()412 public byte[] getPhotoBinaryData() { 413 return mPhotoBinaryData; 414 } 415 getThumbnailPhotoBinaryData()416 public byte[] getThumbnailPhotoBinaryData() { 417 return mThumbnailPhotoBinaryData; 418 } 419 getContentValues()420 public ArrayList<ContentValues> getContentValues() { 421 if (mRawContacts.size() != 1) { 422 throw new IllegalStateException( 423 "Cannot extract content values from an aggregated contact"); 424 } 425 426 RawContact rawContact = mRawContacts.get(0); 427 ArrayList<ContentValues> result = rawContact.getContentValues(); 428 429 // If the photo was loaded using the URI, create an entry for the photo 430 // binary data. 431 if (mPhotoId == 0 && mPhotoBinaryData != null) { 432 ContentValues photo = new ContentValues(); 433 photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE); 434 photo.put(Photo.PHOTO, mPhotoBinaryData); 435 result.add(photo); 436 } 437 438 return result; 439 } 440 441 /** 442 * This can return non-null group meta-data only if the {@link ContactLoader} was configured to 443 * load group metadata in its constructor. 444 * @return 445 */ getGroupMetaData()446 public ImmutableList<GroupMetaData> getGroupMetaData() { 447 return mGroups; 448 } 449 isSendToVoicemail()450 public boolean isSendToVoicemail() { 451 return mSendToVoicemail; 452 } 453 getCustomRingtone()454 public String getCustomRingtone() { 455 return mCustomRingtone; 456 } 457 isUserProfile()458 public boolean isUserProfile() { 459 return mIsUserProfile; 460 } 461 isMultipleRawContacts()462 public boolean isMultipleRawContacts() { 463 return mRawContacts.size() > 1; 464 } 465 466 /** 467 * @return true if all the raw contacts are from SIM accounts, and false otherwise. 468 */ areAllRawContactsSimAccounts(final Context context)469 public boolean areAllRawContactsSimAccounts(final Context context) { 470 if (getRawContacts() == null) return false; 471 472 for (RawContact rawContact : getRawContacts()) { 473 final AccountType accountType = rawContact.getAccountType(context); 474 if (!(accountType instanceof SimAccountType)) return false; 475 } 476 return true; 477 } 478 479 @Override toString()480 public String toString() { 481 return "{requested=" + mRequestedUri + ",lookupkey=" + mLookupKey + 482 ",uri=" + mUri + ",status=" + mStatus + "}"; 483 } 484 setRawContacts(ImmutableList<RawContact> rawContacts)485 /* package */ void setRawContacts(ImmutableList<RawContact> rawContacts) { 486 mRawContacts = rawContacts; 487 } 488 setStatuses(ImmutableMap<Long, DataStatus> statuses)489 /* package */ void setStatuses(ImmutableMap<Long, DataStatus> statuses) { 490 mStatuses = statuses; 491 } 492 setGroupMetaData(ImmutableList<GroupMetaData> groups)493 /* package */ void setGroupMetaData(ImmutableList<GroupMetaData> groups) { 494 mGroups = groups; 495 } 496 } 497