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