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