Home | History | Annotate | Download | only in model
      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 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     /**
    349      * This can return non-null stream items only if the {@link ContactLoader} was
    350      * configured to load stream items in its constructor.
    351      * @return
    352      */
    353     public ImmutableList<StreamItemEntry> getStreamItems() {
    354         return ImmutableList.of();
    355     }
    356 
    357     public ImmutableMap<Long, DataStatus> getStatuses() {
    358         return mStatuses;
    359     }
    360 
    361     public long getDirectoryId() {
    362         return mDirectoryId;
    363     }
    364 
    365     public boolean isDirectoryEntry() {
    366         return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
    367                 && mDirectoryId != Directory.LOCAL_INVISIBLE;
    368     }
    369 
    370     /**
    371      * @return true if this is a contact (not group, etc.) with at least one
    372      *         writable raw-contact, and false otherwise.
    373      */
    374     public boolean isWritableContact(final Context context) {
    375         return getFirstWritableRawContactId(context) != -1;
    376     }
    377 
    378     /**
    379      * Return the ID of the first raw-contact in the contact data that belongs to a
    380      * contact-writable account, or -1 if no such entity exists.
    381      */
    382     public long getFirstWritableRawContactId(final Context context) {
    383         // Directory entries are non-writable
    384         if (isDirectoryEntry()) return -1;
    385 
    386         // Iterate through raw-contacts; if we find a writable on, return its ID.
    387         for (RawContact rawContact : getRawContacts()) {
    388             AccountType accountType = rawContact.getAccountType(context);
    389             if (accountType != null && accountType.areContactsWritable()) {
    390                 return rawContact.getId();
    391             }
    392         }
    393         // No writable raw-contact was found.
    394         return -1;
    395     }
    396 
    397     public int getDirectoryExportSupport() {
    398         return mDirectoryExportSupport;
    399     }
    400 
    401     public String getDirectoryDisplayName() {
    402         return mDirectoryDisplayName;
    403     }
    404 
    405     public String getDirectoryType() {
    406         return mDirectoryType;
    407     }
    408 
    409     public String getDirectoryAccountType() {
    410         return mDirectoryAccountType;
    411     }
    412 
    413     public String getDirectoryAccountName() {
    414         return mDirectoryAccountName;
    415     }
    416 
    417     public byte[] getPhotoBinaryData() {
    418         return mPhotoBinaryData;
    419     }
    420 
    421     public ArrayList<ContentValues> getContentValues() {
    422         if (mRawContacts.size() != 1) {
    423             throw new IllegalStateException(
    424                     "Cannot extract content values from an aggregated contact");
    425         }
    426 
    427         RawContact rawContact = mRawContacts.get(0);
    428         ArrayList<ContentValues> result = rawContact.getContentValues();
    429 
    430         // If the photo was loaded using the URI, create an entry for the photo
    431         // binary data.
    432         if (mPhotoId == 0 && mPhotoBinaryData != null) {
    433             ContentValues photo = new ContentValues();
    434             photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
    435             photo.put(Photo.PHOTO, mPhotoBinaryData);
    436             result.add(photo);
    437         }
    438 
    439         return result;
    440     }
    441 
    442     /**
    443      * This can return non-null group meta-data only if the {@link ContactLoader} was configured to
    444      * load group metadata in its constructor.
    445      * @return
    446      */
    447     public ImmutableList<GroupMetaData> getGroupMetaData() {
    448         return mGroups;
    449     }
    450 
    451     public boolean isSendToVoicemail() {
    452         return mSendToVoicemail;
    453     }
    454 
    455     public String getCustomRingtone() {
    456         return mCustomRingtone;
    457     }
    458 
    459     public boolean isUserProfile() {
    460         return mIsUserProfile;
    461     }
    462 
    463     @Override
    464     public String toString() {
    465         return "{requested=" + mRequestedUri + ",lookupkey=" + mLookupKey +
    466                 ",uri=" + mUri + ",status=" + mStatus + "}";
    467     }
    468 
    469     /* package */ void setRawContacts(ImmutableList<RawContact> rawContacts) {
    470         mRawContacts = rawContacts;
    471     }
    472 
    473     /* package */ void setStatuses(ImmutableMap<Long, DataStatus> statuses) {
    474         mStatuses = statuses;
    475     }
    476 
    477     /* package */ void setInvitableAccountTypes(ImmutableList<AccountType> accountTypes) {
    478         mInvitableAccountTypes = accountTypes;
    479     }
    480 
    481     /* package */ void setGroupMetaData(ImmutableList<GroupMetaData> groups) {
    482         mGroups = groups;
    483     }
    484 }
    485