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.group.GroupMetaData;
     28 import com.android.contacts.model.account.AccountType;
     29 import com.android.contacts.model.account.SimAccountType;
     30 import com.android.contacts.util.DataStatus;
     31 
     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 ImmutableMap<Long,DataStatus> mStatuses;
     77 
     78     private String mDirectoryDisplayName;
     79     private String mDirectoryType;
     80     private String mDirectoryAccountType;
     81     private String mDirectoryAccountName;
     82     private int mDirectoryExportSupport;
     83 
     84     private ImmutableList<GroupMetaData> mGroups;
     85 
     86     private byte[] mPhotoBinaryData;
     87     /**
     88      * Small version of the contact photo loaded from a blob instead of from a file. If a large
     89      * contact photo is not available yet, then this has the same value as mPhotoBinaryData.
     90      */
     91     private byte[] mThumbnailPhotoBinaryData;
     92     private final boolean mSendToVoicemail;
     93     private final String mCustomRingtone;
     94     private final boolean mIsUserProfile;
     95 
     96     private final Contact.Status mStatus;
     97     private final Exception mException;
     98 
     99     /**
    100      * Constructor for special results, namely "no contact found" and "error".
    101      */
    102     private Contact(Uri requestedUri, Contact.Status status, Exception exception) {
    103         if (status == Status.ERROR && exception == null) {
    104             throw new IllegalArgumentException("ERROR result must have exception");
    105         }
    106         mStatus = status;
    107         mException = exception;
    108         mRequestedUri = requestedUri;
    109         mLookupUri = null;
    110         mUri = null;
    111         mDirectoryId = -1;
    112         mLookupKey = null;
    113         mId = -1;
    114         mRawContacts = null;
    115         mStatuses = null;
    116         mNameRawContactId = -1;
    117         mDisplayNameSource = DisplayNameSources.UNDEFINED;
    118         mPhotoId = -1;
    119         mPhotoUri = null;
    120         mDisplayName = null;
    121         mAltDisplayName = null;
    122         mPhoneticName = null;
    123         mStarred = false;
    124         mPresence = null;
    125         mSendToVoicemail = false;
    126         mCustomRingtone = null;
    127         mIsUserProfile = false;
    128     }
    129 
    130     public static Contact forError(Uri requestedUri, Exception exception) {
    131         return new Contact(requestedUri, Status.ERROR, exception);
    132     }
    133 
    134     public static Contact forNotFound(Uri requestedUri) {
    135         return new Contact(requestedUri, Status.NOT_FOUND, null);
    136     }
    137 
    138     /**
    139      * Constructor to call when contact was found
    140      */
    141     public Contact(Uri requestedUri, Uri uri, Uri lookupUri, long directoryId, String lookupKey,
    142             long id, long nameRawContactId, int displayNameSource, long photoId,
    143             String photoUri, String displayName, String altDisplayName, String phoneticName,
    144             boolean starred, Integer presence, boolean sendToVoicemail, String customRingtone,
    145             boolean isUserProfile) {
    146         mStatus = Status.LOADED;
    147         mException = null;
    148         mRequestedUri = requestedUri;
    149         mLookupUri = lookupUri;
    150         mUri = uri;
    151         mDirectoryId = directoryId;
    152         mLookupKey = lookupKey;
    153         mId = id;
    154         mRawContacts = 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         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         mStatuses = from.mStatuses;
    191 
    192         mDirectoryDisplayName = from.mDirectoryDisplayName;
    193         mDirectoryType = from.mDirectoryType;
    194         mDirectoryAccountType = from.mDirectoryAccountType;
    195         mDirectoryAccountName = from.mDirectoryAccountName;
    196         mDirectoryExportSupport = from.mDirectoryExportSupport;
    197 
    198         mGroups = from.mGroups;
    199 
    200         mPhotoBinaryData = from.mPhotoBinaryData;
    201         mSendToVoicemail = from.mSendToVoicemail;
    202         mCustomRingtone = from.mCustomRingtone;
    203         mIsUserProfile = from.mIsUserProfile;
    204     }
    205 
    206     /**
    207      * @param exportSupport See {@link Directory#EXPORT_SUPPORT}.
    208      */
    209     public void setDirectoryMetaData(String displayName, String directoryType,
    210             String accountType, String accountName, int exportSupport) {
    211         mDirectoryDisplayName = displayName;
    212         mDirectoryType = directoryType;
    213         mDirectoryAccountType = accountType;
    214         mDirectoryAccountName = accountName;
    215         mDirectoryExportSupport = exportSupport;
    216     }
    217 
    218     /* package */ void setPhotoBinaryData(byte[] photoBinaryData) {
    219         mPhotoBinaryData = photoBinaryData;
    220     }
    221 
    222     /* package */ void setThumbnailPhotoBinaryData(byte[] photoBinaryData) {
    223         mThumbnailPhotoBinaryData = photoBinaryData;
    224     }
    225 
    226     /**
    227      * Returns the URI for the contact that contains both the lookup key and the ID. This is
    228      * the best URI to reference a contact.
    229      * For directory contacts, this is the same a the URI as returned by {@link #getUri()}
    230      */
    231     public Uri getLookupUri() {
    232         return mLookupUri;
    233     }
    234 
    235     public String getLookupKey() {
    236         return mLookupKey;
    237     }
    238 
    239     /**
    240      * Returns the contact Uri that was passed to the provider to make the query. This is
    241      * the same as the requested Uri, unless the requested Uri doesn't specify a Contact:
    242      * If it either references a Raw-Contact or a Person (a pre-Eclair style Uri), this Uri will
    243      * always reference the full aggregate contact.
    244      */
    245     public Uri getUri() {
    246         return mUri;
    247     }
    248 
    249     /**
    250      * Returns the URI for which this {@link ContactLoader) was initially requested.
    251      */
    252     public Uri getRequestedUri() {
    253         return mRequestedUri;
    254     }
    255 
    256     /**
    257      * Instantiate a new RawContactDeltaList for this contact.
    258      */
    259     public RawContactDeltaList createRawContactDeltaList() {
    260         return RawContactDeltaList.fromIterator(getRawContacts().iterator());
    261     }
    262 
    263     /**
    264      * Returns the contact ID.
    265      */
    266     @VisibleForTesting
    267     public long getId() {
    268         return mId;
    269     }
    270 
    271     /**
    272      * @return true when an exception happened during loading, in which case
    273      *     {@link #getException} returns the actual exception object.
    274      *     Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
    275      *     {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
    276      *     and vice versa.
    277      */
    278     public boolean isError() {
    279         return mStatus == Status.ERROR;
    280     }
    281 
    282     public Exception getException() {
    283         return mException;
    284     }
    285 
    286     /**
    287      * @return true when the specified contact is not found.
    288      *     Note {@link #isNotFound()} and {@link #isError()} are mutually exclusive; If
    289      *     {@link #isError()} is {@code true}, {@link #isNotFound()} is always {@code false},
    290      *     and vice versa.
    291      */
    292     public boolean isNotFound() {
    293         return mStatus == Status.NOT_FOUND;
    294     }
    295 
    296     /**
    297      * @return true if the specified contact is successfully loaded.
    298      *     i.e. neither {@link #isError()} nor {@link #isNotFound()}.
    299      */
    300     public boolean isLoaded() {
    301         return mStatus == Status.LOADED;
    302     }
    303 
    304     public long getNameRawContactId() {
    305         return mNameRawContactId;
    306     }
    307 
    308     public int getDisplayNameSource() {
    309         return mDisplayNameSource;
    310     }
    311 
    312     /**
    313      * Used by various classes to determine whether or not this contact should be displayed as
    314      * a business rather than a person.
    315      */
    316     public boolean isDisplayNameFromOrganization() {
    317         return DisplayNameSources.ORGANIZATION == mDisplayNameSource;
    318     }
    319 
    320     public long getPhotoId() {
    321         return mPhotoId;
    322     }
    323 
    324     public String getPhotoUri() {
    325         return mPhotoUri;
    326     }
    327 
    328     public String getDisplayName() {
    329         return mDisplayName;
    330     }
    331 
    332     public String getAltDisplayName() {
    333         return mAltDisplayName;
    334     }
    335 
    336     public String getPhoneticName() {
    337         return mPhoneticName;
    338     }
    339 
    340     public boolean getStarred() {
    341         return mStarred;
    342     }
    343 
    344     public Integer getPresence() {
    345         return mPresence;
    346     }
    347 
    348     public ImmutableList<RawContact> getRawContacts() {
    349         return mRawContacts;
    350     }
    351 
    352     public ImmutableMap<Long, DataStatus> getStatuses() {
    353         return mStatuses;
    354     }
    355 
    356     public long getDirectoryId() {
    357         return mDirectoryId;
    358     }
    359 
    360     public boolean isDirectoryEntry() {
    361         return mDirectoryId != -1 && mDirectoryId != Directory.DEFAULT
    362                 && mDirectoryId != Directory.LOCAL_INVISIBLE;
    363     }
    364 
    365     /**
    366      * @return true if this is a contact (not group, etc.) with at least one
    367      *         writable raw-contact, and false otherwise.
    368      */
    369     public boolean isWritableContact(final Context context) {
    370         return getFirstWritableRawContactId(context) != -1;
    371     }
    372 
    373     /**
    374      * Return the ID of the first raw-contact in the contact data that belongs to a
    375      * contact-writable account, or -1 if no such entity exists.
    376      */
    377     public long getFirstWritableRawContactId(final Context context) {
    378         // Directory entries are non-writable
    379         if (isDirectoryEntry()) return -1;
    380 
    381         // Iterate through raw-contacts; if we find a writable on, return its ID.
    382         for (RawContact rawContact : getRawContacts()) {
    383             AccountType accountType = rawContact.getAccountType(context);
    384             if (accountType != null && accountType.areContactsWritable()) {
    385                 return rawContact.getId();
    386             }
    387         }
    388         // No writable raw-contact was found.
    389         return -1;
    390     }
    391 
    392     public int getDirectoryExportSupport() {
    393         return mDirectoryExportSupport;
    394     }
    395 
    396     public String getDirectoryDisplayName() {
    397         return mDirectoryDisplayName;
    398     }
    399 
    400     public String getDirectoryType() {
    401         return mDirectoryType;
    402     }
    403 
    404     public String getDirectoryAccountType() {
    405         return mDirectoryAccountType;
    406     }
    407 
    408     public String getDirectoryAccountName() {
    409         return mDirectoryAccountName;
    410     }
    411 
    412     public byte[] getPhotoBinaryData() {
    413         return mPhotoBinaryData;
    414     }
    415 
    416     public byte[] getThumbnailPhotoBinaryData() {
    417         return mThumbnailPhotoBinaryData;
    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     public boolean isMultipleRawContacts() {
    463         return mRawContacts.size() > 1;
    464     }
    465 
    466     /**
    467      * @return true if all the raw contacts are from SIM accounts, and false otherwise.
    468      */
    469     public boolean areAllRawContactsSimAccounts(final Context context) {
    470         if (getRawContacts() == null) return false;
    471 
    472         for (RawContact rawContact : getRawContacts()) {
    473             final AccountType accountType = rawContact.getAccountType(context);
    474             if (!(accountType instanceof SimAccountType)) return false;
    475         }
    476         return true;
    477     }
    478 
    479     @Override
    480     public String toString() {
    481         return "{requested=" + mRequestedUri + ",lookupkey=" + mLookupKey +
    482                 ",uri=" + mUri + ",status=" + mStatus + "}";
    483     }
    484 
    485     /* package */ void setRawContacts(ImmutableList<RawContact> rawContacts) {
    486         mRawContacts = rawContacts;
    487     }
    488 
    489     /* package */ void setStatuses(ImmutableMap<Long, DataStatus> statuses) {
    490         mStatuses = statuses;
    491     }
    492 
    493     /* package */ void setGroupMetaData(ImmutableList<GroupMetaData> groups) {
    494         mGroups = groups;
    495     }
    496 }
    497