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
17package com.android.contacts.common.model;
18
19import android.content.ContentValues;
20import android.net.Uri;
21import android.provider.ContactsContract.CommonDataKinds.Photo;
22import android.provider.ContactsContract.Data;
23import android.provider.ContactsContract.Directory;
24import android.provider.ContactsContract.DisplayNameSources;
25import android.support.annotation.VisibleForTesting;
26import com.android.contacts.common.GroupMetaData;
27import com.android.contacts.common.model.account.AccountType;
28import com.google.common.collect.ImmutableList;
29import java.util.ArrayList;
30
31/**
32 * A Contact represents a single person or logical entity as perceived by the user. The information
33 * about a contact can come from multiple data sources, which are each represented by a RawContact
34 * object. Thus, a Contact is associated with a collection of RawContact objects.
35 *
36 * <p>The aggregation of raw contacts into a single contact is performed automatically, and it is
37 * also possible for users to manually split and join raw contacts into various contacts.
38 *
39 * <p>Only the {@link ContactLoader} class can create a Contact object with various flags to allow
40 * partial loading of contact data. Thus, an instance of this class should be treated as a read-only
41 * object.
42 */
43public class Contact {
44
45  private final Uri mRequestedUri;
46  private final Uri mLookupUri;
47  private final Uri mUri;
48  private final long mDirectoryId;
49  private final String mLookupKey;
50  private final long mId;
51  private final long mNameRawContactId;
52  private final int mDisplayNameSource;
53  private final long mPhotoId;
54  private final String mPhotoUri;
55  private final String mDisplayName;
56  private final String mAltDisplayName;
57  private final String mPhoneticName;
58  private final boolean mStarred;
59  private final Integer mPresence;
60  private final boolean mSendToVoicemail;
61  private final String mCustomRingtone;
62  private final boolean mIsUserProfile;
63  private final Contact.Status mStatus;
64  private final Exception mException;
65  private ImmutableList<RawContact> mRawContacts;
66  private ImmutableList<AccountType> mInvitableAccountTypes;
67  private String mDirectoryDisplayName;
68  private String mDirectoryType;
69  private String mDirectoryAccountType;
70  private String mDirectoryAccountName;
71  private int mDirectoryExportSupport;
72  private ImmutableList<GroupMetaData> mGroups;
73  private byte[] mPhotoBinaryData;
74  /**
75   * Small version of the contact photo loaded from a blob instead of from a file. If a large
76   * contact photo is not available yet, then this has the same value as mPhotoBinaryData.
77   */
78  private byte[] mThumbnailPhotoBinaryData;
79
80  /** Constructor for special results, namely "no contact found" and "error". */
81  private Contact(Uri requestedUri, Contact.Status status, Exception exception) {
82    if (status == Status.ERROR && exception == null) {
83      throw new IllegalArgumentException("ERROR result must have exception");
84    }
85    mStatus = status;
86    mException = exception;
87    mRequestedUri = requestedUri;
88    mLookupUri = null;
89    mUri = null;
90    mDirectoryId = -1;
91    mLookupKey = null;
92    mId = -1;
93    mRawContacts = null;
94    mNameRawContactId = -1;
95    mDisplayNameSource = DisplayNameSources.UNDEFINED;
96    mPhotoId = -1;
97    mPhotoUri = null;
98    mDisplayName = null;
99    mAltDisplayName = null;
100    mPhoneticName = null;
101    mStarred = false;
102    mPresence = null;
103    mInvitableAccountTypes = null;
104    mSendToVoicemail = false;
105    mCustomRingtone = null;
106    mIsUserProfile = false;
107  }
108
109  /** Constructor to call when contact was found */
110  public Contact(
111      Uri requestedUri,
112      Uri uri,
113      Uri lookupUri,
114      long directoryId,
115      String lookupKey,
116      long id,
117      long nameRawContactId,
118      int displayNameSource,
119      long photoId,
120      String photoUri,
121      String displayName,
122      String altDisplayName,
123      String phoneticName,
124      boolean starred,
125      Integer presence,
126      boolean sendToVoicemail,
127      String customRingtone,
128      boolean isUserProfile) {
129    mStatus = Status.LOADED;
130    mException = null;
131    mRequestedUri = requestedUri;
132    mLookupUri = lookupUri;
133    mUri = uri;
134    mDirectoryId = directoryId;
135    mLookupKey = lookupKey;
136    mId = id;
137    mRawContacts = null;
138    mNameRawContactId = nameRawContactId;
139    mDisplayNameSource = displayNameSource;
140    mPhotoId = photoId;
141    mPhotoUri = photoUri;
142    mDisplayName = displayName;
143    mAltDisplayName = altDisplayName;
144    mPhoneticName = phoneticName;
145    mStarred = starred;
146    mPresence = presence;
147    mInvitableAccountTypes = null;
148    mSendToVoicemail = sendToVoicemail;
149    mCustomRingtone = customRingtone;
150    mIsUserProfile = isUserProfile;
151  }
152
153  public Contact(Uri requestedUri, Contact from) {
154    mRequestedUri = requestedUri;
155
156    mStatus = from.mStatus;
157    mException = from.mException;
158    mLookupUri = from.mLookupUri;
159    mUri = from.mUri;
160    mDirectoryId = from.mDirectoryId;
161    mLookupKey = from.mLookupKey;
162    mId = from.mId;
163    mNameRawContactId = from.mNameRawContactId;
164    mDisplayNameSource = from.mDisplayNameSource;
165    mPhotoId = from.mPhotoId;
166    mPhotoUri = from.mPhotoUri;
167    mDisplayName = from.mDisplayName;
168    mAltDisplayName = from.mAltDisplayName;
169    mPhoneticName = from.mPhoneticName;
170    mStarred = from.mStarred;
171    mPresence = from.mPresence;
172    mRawContacts = from.mRawContacts;
173    mInvitableAccountTypes = from.mInvitableAccountTypes;
174
175    mDirectoryDisplayName = from.mDirectoryDisplayName;
176    mDirectoryType = from.mDirectoryType;
177    mDirectoryAccountType = from.mDirectoryAccountType;
178    mDirectoryAccountName = from.mDirectoryAccountName;
179    mDirectoryExportSupport = from.mDirectoryExportSupport;
180
181    mGroups = from.mGroups;
182
183    mPhotoBinaryData = from.mPhotoBinaryData;
184    mSendToVoicemail = from.mSendToVoicemail;
185    mCustomRingtone = from.mCustomRingtone;
186    mIsUserProfile = from.mIsUserProfile;
187  }
188
189  public static Contact forError(Uri requestedUri, Exception exception) {
190    return new Contact(requestedUri, Status.ERROR, exception);
191  }
192
193  public static Contact forNotFound(Uri requestedUri) {
194    return new Contact(requestedUri, Status.NOT_FOUND, null);
195  }
196
197  /** @param exportSupport See {@link Directory#EXPORT_SUPPORT}. */
198  public void setDirectoryMetaData(
199      String displayName,
200      String directoryType,
201      String accountType,
202      String accountName,
203      int exportSupport) {
204    mDirectoryDisplayName = displayName;
205    mDirectoryType = directoryType;
206    mDirectoryAccountType = accountType;
207    mDirectoryAccountName = accountName;
208    mDirectoryExportSupport = exportSupport;
209  }
210
211  /**
212   * Returns the URI for the contact that contains both the lookup key and the ID. This is the best
213   * URI to reference a contact. For directory contacts, this is the same a the URI as returned by
214   * {@link #getUri()}
215   */
216  public Uri getLookupUri() {
217    return mLookupUri;
218  }
219
220  public String getLookupKey() {
221    return mLookupKey;
222  }
223
224  /**
225   * Returns the contact Uri that was passed to the provider to make the query. This is the same as
226   * the requested Uri, unless the requested Uri doesn't specify a Contact: If it either references
227   * a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will always reference the full
228   * aggregate contact.
229   */
230  public Uri getUri() {
231    return mUri;
232  }
233
234  /** Returns the contact ID. */
235  @VisibleForTesting
236  public long getId() {
237    return mId;
238  }
239
240  /**
241   * @return true when an exception happened during loading, in which case {@link #getException}
242   *     returns the actual exception object.
243   */
244  public boolean isError() {
245    return mStatus == Status.ERROR;
246  }
247
248  public Exception getException() {
249    return mException;
250  }
251
252  /** @return true if the specified contact is successfully loaded. */
253  public boolean isLoaded() {
254    return mStatus == Status.LOADED;
255  }
256
257  public long getNameRawContactId() {
258    return mNameRawContactId;
259  }
260
261  public int getDisplayNameSource() {
262    return mDisplayNameSource;
263  }
264
265  public long getPhotoId() {
266    return mPhotoId;
267  }
268
269  public String getPhotoUri() {
270    return mPhotoUri;
271  }
272
273  public String getDisplayName() {
274    return mDisplayName;
275  }
276
277  public boolean getStarred() {
278    return mStarred;
279  }
280
281  public Integer getPresence() {
282    return mPresence;
283  }
284
285  /**
286   * This can return non-null invitable account types only if the {@link ContactLoader} was
287   * configured to load invitable account types in its constructor.
288   */
289  public ImmutableList<AccountType> getInvitableAccountTypes() {
290    return mInvitableAccountTypes;
291  }
292
293  /* package */ void setInvitableAccountTypes(ImmutableList<AccountType> accountTypes) {
294    mInvitableAccountTypes = accountTypes;
295  }
296
297  public ImmutableList<RawContact> getRawContacts() {
298    return mRawContacts;
299  }
300
301  /* package */ void setRawContacts(ImmutableList<RawContact> rawContacts) {
302    mRawContacts = rawContacts;
303  }
304
305  public long getDirectoryId() {
306    return mDirectoryId;
307  }
308
309  public boolean isDirectoryEntry() {
310    return mDirectoryId != -1
311        && mDirectoryId != Directory.DEFAULT
312        && mDirectoryId != Directory.LOCAL_INVISIBLE;
313  }
314
315  /* package */ void setPhotoBinaryData(byte[] photoBinaryData) {
316    mPhotoBinaryData = photoBinaryData;
317  }
318
319  public byte[] getThumbnailPhotoBinaryData() {
320    return mThumbnailPhotoBinaryData;
321  }
322
323  /* package */ void setThumbnailPhotoBinaryData(byte[] photoBinaryData) {
324    mThumbnailPhotoBinaryData = photoBinaryData;
325  }
326
327  public ArrayList<ContentValues> getContentValues() {
328    if (mRawContacts.size() != 1) {
329      throw new IllegalStateException("Cannot extract content values from an aggregated contact");
330    }
331
332    RawContact rawContact = mRawContacts.get(0);
333    ArrayList<ContentValues> result = rawContact.getContentValues();
334
335    // If the photo was loaded using the URI, create an entry for the photo
336    // binary data.
337    if (mPhotoId == 0 && mPhotoBinaryData != null) {
338      ContentValues photo = new ContentValues();
339      photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
340      photo.put(Photo.PHOTO, mPhotoBinaryData);
341      result.add(photo);
342    }
343
344    return result;
345  }
346
347  /**
348   * This can return non-null group meta-data only if the {@link ContactLoader} was configured to
349   * load group metadata in its constructor.
350   */
351  public ImmutableList<GroupMetaData> getGroupMetaData() {
352    return mGroups;
353  }
354
355  /* package */ void setGroupMetaData(ImmutableList<GroupMetaData> groups) {
356    mGroups = groups;
357  }
358
359  public boolean isUserProfile() {
360    return mIsUserProfile;
361  }
362
363  @Override
364  public String toString() {
365    return "{requested="
366        + mRequestedUri
367        + ",lookupkey="
368        + mLookupKey
369        + ",uri="
370        + mUri
371        + ",status="
372        + mStatus
373        + "}";
374  }
375
376  private enum Status {
377    /** Contact is successfully loaded */
378    LOADED,
379    /** There was an error loading the contact */
380    ERROR,
381    /** Contact is not found */
382    NOT_FOUND,
383  }
384}
385