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     /**
    308      * Used by various classes to determine whether or not this contact should be displayed as
    309      * a business rather than a person.
    310      */
    311     public boolean isDisplayNameFromOrganization() {
    312         return DisplayNameSources.ORGANIZATION == mDisplayNameSource;
    313     }
    314 
    315     public long getPhotoId() {
    316         return mPhotoId;
    317     }
    318 
    319     public String getPhotoUri() {
    320         return mPhotoUri;
    321     }
    322 
    323     public String getDisplayName() {
    324         return mDisplayName;
    325     }
    326 
    327     public String getAltDisplayName() {
    328         return mAltDisplayName;
    329     }
    330 
    331     public String getPhoneticName() {
    332         return mPhoneticName;
    333     }
    334 
    335     public boolean getStarred() {
    336         return mStarred;
    337     }
    338 
    339     public Integer getPresence() {
    340         return mPresence;
    341     }
    342 
    343     /**
    344      * This can return non-null invitable account types only if the {@link ContactLoader} was
    345      * configured to load invitable account types in its constructor.
    346      * @return
    347      */
    348     public ImmutableList<AccountType> getInvitableAccountTypes() {
    349         return mInvitableAccountTypes;
    350     }
    351 
    352     public ImmutableList<RawContact> getRawContacts() {
    353         return mRawContacts;
    354     }
    355 
    356     public ImmutableMap<Long, DataStatus> getStatuses() {
    357         return mStatuses;
    358     }
    359 
    360     public long getDirectoryId() {
    361         return mDirectoryId;
    362     }
    363 
    364     public boolean isDirectoryEntry() {
    365         return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
    366                 && mDirectoryId != Directory.LOCAL_INVISIBLE;
    367     }
    368 
    369     /**
    370      * @return true if this is a contact (not group, etc.) with at least one
    371      *         writable raw-contact, and false otherwise.
    372      */
    373     public boolean isWritableContact(final Context context) {
    374         return getFirstWritableRawContactId(context) != -1;
    375     }
    376 
    377     /**
    378      * Return the ID of the first raw-contact in the contact data that belongs to a
    379      * contact-writable account, or -1 if no such entity exists.
    380      */
    381     public long getFirstWritableRawContactId(final Context context) {
    382         // Directory entries are non-writable
    383         if (isDirectoryEntry()) return -1;
    384 
    385         // Iterate through raw-contacts; if we find a writable on, return its ID.
    386         for (RawContact rawContact : getRawContacts()) {
    387             AccountType accountType = rawContact.getAccountType(context);
    388             if (accountType != null && accountType.areContactsWritable()) {
    389                 return rawContact.getId();
    390             }
    391         }
    392         // No writable raw-contact was found.
    393         return -1;
    394     }
    395 
    396     public int getDirectoryExportSupport() {
    397         return mDirectoryExportSupport;
    398     }
    399 
    400     public String getDirectoryDisplayName() {
    401         return mDirectoryDisplayName;
    402     }
    403 
    404     public String getDirectoryType() {
    405         return mDirectoryType;
    406     }
    407 
    408     public String getDirectoryAccountType() {
    409         return mDirectoryAccountType;
    410     }
    411 
    412     public String getDirectoryAccountName() {
    413         return mDirectoryAccountName;
    414     }
    415 
    416     public byte[] getPhotoBinaryData() {
    417         return mPhotoBinaryData;
    418     }
    419 
    420     public ArrayList<ContentValues> getContentValues() {
    421         if (mRawContacts.size() != 1) {
    422             throw new IllegalStateException(
    423                     "Cannot extract content values from an aggregated contact");
    424         }
    425 
    426         RawContact rawContact = mRawContacts.get(0);
    427         ArrayList<ContentValues> result = rawContact.getContentValues();
    428 
    429         // If the photo was loaded using the URI, create an entry for the photo
    430         // binary data.
    431         if (mPhotoId == 0 && mPhotoBinaryData != null) {
    432             ContentValues photo = new ContentValues();
    433             photo.put(Data.MIMETYPE, Photo.CONTENT_ITEM_TYPE);
    434             photo.put(Photo.PHOTO, mPhotoBinaryData);
    435             result.add(photo);
    436         }
    437 
    438         return result;
    439     }
    440 
    441     /**
    442      * This can return non-null group meta-data only if the {@link ContactLoader} was configured to
    443      * load group metadata in its constructor.
    444      * @return
    445      */
    446     public ImmutableList<GroupMetaData> getGroupMetaData() {
    447         return mGroups;
    448     }
    449 
    450     public boolean isSendToVoicemail() {
    451         return mSendToVoicemail;
    452     }
    453 
    454     public String getCustomRingtone() {
    455         return mCustomRingtone;
    456     }
    457 
    458     public boolean isUserProfile() {
    459         return mIsUserProfile;
    460     }
    461 
    462     @Override
    463     public String toString() {
    464         return "{requested=" + mRequestedUri + ",lookupkey=" + mLookupKey +
    465                 ",uri=" + mUri + ",status=" + mStatus + "}";
    466     }
    467 
    468     /* package */ void setRawContacts(ImmutableList<RawContact> rawContacts) {
    469         mRawContacts = rawContacts;
    470     }
    471 
    472     /* package */ void setStatuses(ImmutableMap<Long, DataStatus> statuses) {
    473         mStatuses = statuses;
    474     }
    475 
    476     /* package */ void setInvitableAccountTypes(ImmutableList<AccountType> accountTypes) {
    477         mInvitableAccountTypes = accountTypes;
    478     }
    479 
    480     /* package */ void setGroupMetaData(ImmutableList<GroupMetaData> groups) {
    481         mGroups = groups;
    482     }
    483 }
    484