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