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