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