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