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