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.common.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.common.GroupMetaData;
     28 import com.android.contacts.common.model.account.AccountType;
     29 import com.android.contacts.common.util.DataStatus;
     30 
     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     /**
     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;
     93     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 
    226     /* package */ void setThumbnailPhotoBinaryData(byte[] photoBinaryData) {
    227         mThumbnailPhotoBinaryData = photoBinaryData;
    228     }
    229 
    230     /**
    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
    271     /* package */ long getId() {
    272         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 
    316     /**
    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 
    324     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 
    429     public byte[] getThumbnailPhotoBinaryData() {
    430         return mThumbnailPhotoBinaryData;
    431     }
    432 
    433     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 
    475     @Override
    476     public String toString() {
    477         return "{requested=" + mRequestedUri + ",lookupkey=" + mLookupKey +
    478                 ",uri=" + mUri + ",status=" + mStatus + "}";
    479     }
    480 
    481     /* package */ void setRawContacts(ImmutableList<RawContact> rawContacts) {
    482         mRawContacts = rawContacts;
    483     }
    484 
    485     /* package */ void setStatuses(ImmutableMap<Long, DataStatus> statuses) {
    486         mStatuses = statuses;
    487     }
    488 
    489     /* package */ void setInvitableAccountTypes(ImmutableList<AccountType> accountTypes) {
    490         mInvitableAccountTypes = accountTypes;
    491     }
    492 
    493     /* package */ void setGroupMetaData(ImmutableList<GroupMetaData> groups) {
    494         mGroups = groups;
    495     }
    496 }
    497