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    /* package */ 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 destination */
65    private final long mDataId;
66    private final boolean mIsDivider;
67
68    private final Uri mPhotoThumbnailUri;
69
70    private boolean mIsValid;
71    /**
72     * This can be updated after this object being constructed, when the photo is fetched
73     * from remote directories.
74     */
75    private byte[] mPhotoBytes;
76
77    private RecipientEntry(int entryType, String displayName, String destination,
78            int destinationType, String destinationLabel, long contactId, long dataId,
79            Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid) {
80        mEntryType = entryType;
81        mIsFirstLevel = isFirstLevel;
82        mDisplayName = displayName;
83        mDestination = destination;
84        mDestinationType = destinationType;
85        mDestinationLabel = destinationLabel;
86        mContactId = contactId;
87        mDataId = dataId;
88        mPhotoThumbnailUri = photoThumbnailUri;
89        mPhotoBytes = null;
90        mIsDivider = false;
91        mIsValid = isValid;
92    }
93
94    public boolean isValid() {
95        return mIsValid;
96    }
97
98    /**
99     * Determine if this was a RecipientEntry created from recipient info or
100     * an entry from contacts.
101     */
102    public static boolean isCreatedRecipient(long id) {
103        return id == RecipientEntry.INVALID_CONTACT || id == RecipientEntry.GENERATED_CONTACT;
104    }
105
106    /**
107     * Construct a RecipientEntry from just an address that has been entered.
108     * This address has not been resolved to a contact and therefore does not
109     * have a contact id or photo.
110     */
111    public static RecipientEntry constructFakeEntry(final String address, final boolean isValid) {
112        final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
113        final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;
114
115        return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
116                INVALID_DESTINATION_TYPE, null,
117                INVALID_CONTACT, INVALID_CONTACT, null, true, isValid);
118    }
119
120    /**
121     * Construct a RecipientEntry from just a phone number.
122     */
123    public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,
124            final boolean isValid) {
125        return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber,
126                INVALID_DESTINATION_TYPE, null,
127                INVALID_CONTACT, INVALID_CONTACT, null, true, isValid);
128    }
129
130    /**
131     * @return the display name for the entry.  If the display name source is larger than
132     * {@link DisplayNameSources#PHONE} we use the contact's display name, but if not,
133     * i.e. the display name came from an email address or a phone number, we don't use it
134     * to avoid confusion and just use the destination instead.
135     */
136    private static String pickDisplayName(int displayNameSource, String displayName,
137            String destination) {
138        return (displayNameSource > DisplayNameSources.PHONE) ? displayName : destination;
139    }
140
141    /**
142     * Construct a RecipientEntry from just an address that has been entered
143     * with both an associated display name. This address has not been resolved
144     * to a contact and therefore does not have a contact id or photo.
145     */
146    public static RecipientEntry constructGeneratedEntry(String display, String address,
147            boolean isValid) {
148        return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
149                null, GENERATED_CONTACT, GENERATED_CONTACT, null, true, isValid);
150    }
151
152    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
153            String destination, int destinationType, String destinationLabel, long contactId,
154            long dataId, Uri photoThumbnailUri, boolean isValid) {
155        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
156                displayName, destination), destination, destinationType, destinationLabel,
157                contactId, dataId, photoThumbnailUri, true, isValid);
158    }
159
160    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
161            String destination, int destinationType, String destinationLabel, long contactId,
162            long dataId, String thumbnailUriAsString, boolean isValid) {
163        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
164                displayName, destination), destination, destinationType, destinationLabel,
165                contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
166                        : null), true, isValid);
167    }
168
169    public static RecipientEntry constructSecondLevelEntry(String displayName,
170            int displayNameSource, String destination, int destinationType,
171            String destinationLabel, long contactId, long dataId, String thumbnailUriAsString,
172            boolean isValid) {
173        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
174                displayName, destination), destination, destinationType, destinationLabel,
175                contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
176                        : null), false, isValid);
177    }
178
179    public int getEntryType() {
180        return mEntryType;
181    }
182
183    public String getDisplayName() {
184        return mDisplayName;
185    }
186
187    public String getDestination() {
188        return mDestination;
189    }
190
191    public int getDestinationType() {
192        return mDestinationType;
193    }
194
195    public String getDestinationLabel() {
196        return mDestinationLabel;
197    }
198
199    public long getContactId() {
200        return mContactId;
201    }
202
203    public long getDataId() {
204        return mDataId;
205    }
206
207    public boolean isFirstLevel() {
208        return mIsFirstLevel;
209    }
210
211    public Uri getPhotoThumbnailUri() {
212        return mPhotoThumbnailUri;
213    }
214
215    /** This can be called outside main Looper thread. */
216    public synchronized void setPhotoBytes(byte[] photoBytes) {
217        mPhotoBytes = photoBytes;
218    }
219
220    /** This can be called outside main Looper thread. */
221    public synchronized byte[] getPhotoBytes() {
222        return mPhotoBytes;
223    }
224
225    public boolean isSeparator() {
226        return mIsDivider;
227    }
228
229    public boolean isSelectable() {
230        return mEntryType == ENTRY_TYPE_PERSON;
231    }
232
233    @Override
234    public String toString() {
235        return mDisplayName + " <" + mDestination + ">, isValid=" + mIsValid;
236    }
237}