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
17package com.android.ex.chips;
18
19import android.net.Uri;
20import android.provider.ContactsContract.CommonDataKinds.Email;
21import android.provider.ContactsContract.DisplayNameSources;
22import android.text.util.Rfc822Token;
23import android.text.util.Rfc822Tokenizer;
24
25/**
26 * Represents one entry inside recipient auto-complete list.
27 */
28public 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
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
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     */
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     */
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     */
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     */
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     */
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
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
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
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
190    public int getEntryType() {
191        return mEntryType;
192    }
193
194    public String getDisplayName() {
195        return mDisplayName;
196    }
197
198    public String getDestination() {
199        return mDestination;
200    }
201
202    public int getDestinationType() {
203        return mDestinationType;
204    }
205
206    public String getDestinationLabel() {
207        return mDestinationLabel;
208    }
209
210    public long getContactId() {
211        return mContactId;
212    }
213
214    public Long getDirectoryId() {
215        return mDirectoryId;
216    }
217
218    public long getDataId() {
219        return mDataId;
220    }
221
222    public boolean isFirstLevel() {
223        return mIsFirstLevel;
224    }
225
226    public Uri getPhotoThumbnailUri() {
227        return mPhotoThumbnailUri;
228    }
229
230    /** This can be called outside main Looper thread. */
231    public synchronized void setPhotoBytes(byte[] photoBytes) {
232        mPhotoBytes = photoBytes;
233    }
234
235    /** This can be called outside main Looper thread. */
236    public synchronized byte[] getPhotoBytes() {
237        return mPhotoBytes;
238    }
239
240    public boolean isSeparator() {
241        return mIsDivider;
242    }
243
244    public boolean isSelectable() {
245        return mEntryType == ENTRY_TYPE_PERSON;
246    }
247
248    public String getLookupKey() {
249        return mLookupKey;
250    }
251
252    @Override
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     */
261    public boolean isSamePerson(final RecipientEntry entry) {
262        return entry != null && mContactId == entry.mContactId;
263    }
264}