1 /*
2  * Copyright (C) 2011 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.ex.chips;
18 
19 import android.net.Uri;
20 import android.provider.ContactsContract.CommonDataKinds.Email;
21 import android.provider.ContactsContract.DisplayNameSources;
22 import android.text.util.Rfc822Token;
23 import android.text.util.Rfc822Tokenizer;
24 
25 /**
26  * Represents one entry inside recipient auto-complete list.
27  */
28 public class RecipientEntry {
29     /* package */ static final int INVALID_CONTACT = -1;
30     /**
31      * A GENERATED_CONTACT is one that was created based entirely on
32      * information passed in to the RecipientEntry from an external source
33      * that is not a real contact.
34      */
35     /* package */ static final int GENERATED_CONTACT = -2;
36 
37     /** Used when {@link #mDestinationType} is invalid and thus shouldn't be used for display. */
38     public static final int INVALID_DESTINATION_TYPE = -1;
39 
40     public static final int ENTRY_TYPE_PERSON = 0;
41 
42     public static final int ENTRY_TYPE_SIZE = 1;
43 
44     private final int mEntryType;
45 
46     /**
47      * True when this entry is the first entry in a group, which should have a photo and display
48      * name, while the second or later entries won't.
49      */
50     private boolean mIsFirstLevel;
51     private final String mDisplayName;
52 
53     /** Destination for this contact entry. Would be an email address or a phone number. */
54     private final String mDestination;
55     /** Type of the destination like {@link Email#TYPE_HOME} */
56     private final int mDestinationType;
57     /**
58      * Label of the destination which will be used when type was {@link Email#TYPE_CUSTOM}.
59      * Can be null when {@link #mDestinationType} is {@link #INVALID_DESTINATION_TYPE}.
60      */
61     private final String mDestinationLabel;
62     /** ID for the person */
63     private final long mContactId;
64     /** ID for the directory this contact came from, or <code>null</code> */
65     private final Long mDirectoryId;
66     /** ID for the destination */
67     private final long mDataId;
68     private final boolean mIsDivider;
69 
70     private final Uri mPhotoThumbnailUri;
71 
72     private boolean mIsValid;
73     /**
74      * This can be updated after this object being constructed, when the photo is fetched
75      * from remote directories.
76      */
77     private byte[] mPhotoBytes;
78 
79     /** See {@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} */
80     private final String mLookupKey;
81 
RecipientEntry(int entryType, String displayName, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid, String lookupKey)82     protected RecipientEntry(int entryType, String displayName, String destination,
83             int destinationType, String destinationLabel, long contactId, Long directoryId,
84             long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
85             String lookupKey) {
86         mEntryType = entryType;
87         mIsFirstLevel = isFirstLevel;
88         mDisplayName = displayName;
89         mDestination = destination;
90         mDestinationType = destinationType;
91         mDestinationLabel = destinationLabel;
92         mContactId = contactId;
93         mDirectoryId = directoryId;
94         mDataId = dataId;
95         mPhotoThumbnailUri = photoThumbnailUri;
96         mPhotoBytes = null;
97         mIsDivider = false;
98         mIsValid = isValid;
99         mLookupKey = lookupKey;
100     }
101 
isValid()102     public boolean isValid() {
103         return mIsValid;
104     }
105 
106     /**
107      * Determine if this was a RecipientEntry created from recipient info or
108      * an entry from contacts.
109      */
isCreatedRecipient(long id)110     public static boolean isCreatedRecipient(long id) {
111         return id == RecipientEntry.INVALID_CONTACT || id == RecipientEntry.GENERATED_CONTACT;
112     }
113 
114     /**
115      * Construct a RecipientEntry from just an address that has been entered.
116      * This address has not been resolved to a contact and therefore does not
117      * have a contact id or photo.
118      */
constructFakeEntry(final String address, final boolean isValid)119     public static RecipientEntry constructFakeEntry(final String address, final boolean isValid) {
120         final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
121         final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;
122 
123         return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
124                 INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
125                 INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
126     }
127 
128     /**
129      * Construct a RecipientEntry from just a phone number.
130      */
constructFakePhoneEntry(final String phoneNumber, final boolean isValid)131     public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,
132             final boolean isValid) {
133         return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber,
134                 INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
135                 INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
136     }
137 
138     /**
139      * @return the display name for the entry.  If the display name source is larger than
140      * {@link DisplayNameSources#PHONE} we use the contact's display name, but if not,
141      * i.e. the display name came from an email address or a phone number, we don't use it
142      * to avoid confusion and just use the destination instead.
143      */
pickDisplayName(int displayNameSource, String displayName, String destination)144     private static String pickDisplayName(int displayNameSource, String displayName,
145             String destination) {
146         return (displayNameSource > DisplayNameSources.PHONE) ? displayName : destination;
147     }
148 
149     /**
150      * Construct a RecipientEntry from just an address that has been entered
151      * with both an associated display name. This address has not been resolved
152      * to a contact and therefore does not have a contact id or photo.
153      */
constructGeneratedEntry(String display, String address, boolean isValid)154     public static RecipientEntry constructGeneratedEntry(String display, String address,
155             boolean isValid) {
156         return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
157                 null, GENERATED_CONTACT, null /* directoryId */, GENERATED_CONTACT, null, true,
158                 isValid, null /* lookupKey */);
159     }
160 
constructTopLevelEntry(String displayName, int displayNameSource, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid, String lookupKey)161     public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
162             String destination, int destinationType, String destinationLabel, long contactId,
163             Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid,
164             String lookupKey) {
165         return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
166                 displayName, destination), destination, destinationType, destinationLabel,
167                 contactId, directoryId, dataId, photoThumbnailUri, true, isValid, lookupKey);
168     }
169 
constructTopLevelEntry(String displayName, int displayNameSource, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid, String lookupKey)170     public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
171             String destination, int destinationType, String destinationLabel, long contactId,
172             Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid,
173             String lookupKey) {
174         return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
175                 displayName, destination), destination, destinationType, destinationLabel,
176                 contactId, directoryId, dataId, (thumbnailUriAsString != null
177                 ? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey);
178     }
179 
constructSecondLevelEntry(String displayName, int displayNameSource, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid, String lookupKey)180     public static RecipientEntry constructSecondLevelEntry(String displayName,
181             int displayNameSource, String destination, int destinationType,
182             String destinationLabel, long contactId, Long directoryId, long dataId,
183             String thumbnailUriAsString, boolean isValid, String lookupKey) {
184         return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
185                 displayName, destination), destination, destinationType, destinationLabel,
186                 contactId, directoryId, dataId, (thumbnailUriAsString != null
187                 ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey);
188     }
189 
getEntryType()190     public int getEntryType() {
191         return mEntryType;
192     }
193 
getDisplayName()194     public String getDisplayName() {
195         return mDisplayName;
196     }
197 
getDestination()198     public String getDestination() {
199         return mDestination;
200     }
201 
getDestinationType()202     public int getDestinationType() {
203         return mDestinationType;
204     }
205 
getDestinationLabel()206     public String getDestinationLabel() {
207         return mDestinationLabel;
208     }
209 
getContactId()210     public long getContactId() {
211         return mContactId;
212     }
213 
getDirectoryId()214     public Long getDirectoryId() {
215         return mDirectoryId;
216     }
217 
getDataId()218     public long getDataId() {
219         return mDataId;
220     }
221 
isFirstLevel()222     public boolean isFirstLevel() {
223         return mIsFirstLevel;
224     }
225 
getPhotoThumbnailUri()226     public Uri getPhotoThumbnailUri() {
227         return mPhotoThumbnailUri;
228     }
229 
230     /** This can be called outside main Looper thread. */
setPhotoBytes(byte[] photoBytes)231     public synchronized void setPhotoBytes(byte[] photoBytes) {
232         mPhotoBytes = photoBytes;
233     }
234 
235     /** This can be called outside main Looper thread. */
getPhotoBytes()236     public synchronized byte[] getPhotoBytes() {
237         return mPhotoBytes;
238     }
239 
isSeparator()240     public boolean isSeparator() {
241         return mIsDivider;
242     }
243 
isSelectable()244     public boolean isSelectable() {
245         return mEntryType == ENTRY_TYPE_PERSON;
246     }
247 
getLookupKey()248     public String getLookupKey() {
249         return mLookupKey;
250     }
251 
252     @Override
toString()253     public String toString() {
254         return mDisplayName + " <" + mDestination + ">, isValid=" + mIsValid;
255     }
256 
257     /**
258      * Returns if entry represents the same person as this instance. The default implementation
259      * checks whether the contact ids are the same, and subclasses may opt to override this.
260      */
isSamePerson(final RecipientEntry entry)261     public boolean isSamePerson(final RecipientEntry entry) {
262         return entry != null && mContactId == entry.mContactId;
263     }
264 }