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.support.annotation.DrawableRes;
23import android.text.util.Rfc822Token;
24import android.text.util.Rfc822Tokenizer;
25
26/**
27 * Represents one entry inside recipient auto-complete list.
28 */
29public class RecipientEntry {
30    /* package */ static final int INVALID_CONTACT = -1;
31    /**
32     * A GENERATED_CONTACT is one that was created based entirely on
33     * information passed in to the RecipientEntry from an external source
34     * that is not a real contact.
35     */
36    /* package */ static final int GENERATED_CONTACT = -2;
37
38    /** Used when {@link #mDestinationType} is invalid and thus shouldn't be used for display. */
39    public static final int INVALID_DESTINATION_TYPE = -1;
40
41    public static final int ENTRY_TYPE_PERSON = 0;
42
43    /**
44     * Entry of this type represents the item in auto-complete that asks user to grant permissions
45     * to the app. This permission model is introduced in M platform.
46     *
47     * <p>Entries of this type should have {@link #mPermissions} set as well.
48     */
49    public static final int ENTRY_TYPE_PERMISSION_REQUEST = 1;
50
51    public static final int ENTRY_TYPE_SIZE = 2;
52
53    private final int mEntryType;
54
55    /**
56     * True when this entry is the first entry in a group, which should have a photo and display
57     * name, while the second or later entries won't.
58     */
59    private boolean mIsFirstLevel;
60    private final String mDisplayName;
61
62    /** Destination for this contact entry. Would be an email address or a phone number. */
63    private final String mDestination;
64    /** Type of the destination like {@link Email#TYPE_HOME} */
65    private final int mDestinationType;
66    /**
67     * Label of the destination which will be used when type was {@link Email#TYPE_CUSTOM}.
68     * Can be null when {@link #mDestinationType} is {@link #INVALID_DESTINATION_TYPE}.
69     */
70    private final String mDestinationLabel;
71    /** ID for the person */
72    private final long mContactId;
73    /** ID for the directory this contact came from, or <code>null</code> */
74    private final Long mDirectoryId;
75    /** ID for the destination */
76    private final long mDataId;
77
78    private final Uri mPhotoThumbnailUri;
79    /** Configures showing the icon in the chip */
80    private final boolean mShouldDisplayIcon;
81
82    private boolean mIsValid;
83    /**
84     * This can be updated after this object being constructed, when the photo is fetched
85     * from remote directories.
86     */
87    private byte[] mPhotoBytes;
88
89    @DrawableRes private int mIndicatorIconId;
90    private String mIndicatorText;
91
92    /** See {@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} */
93    private final String mLookupKey;
94
95    /** Should be used when type is {@link #ENTRY_TYPE_PERMISSION_REQUEST}. */
96    private final String[] mPermissions;
97
98    /** Whether RecipientEntry is in a replaced chip or not. */
99    private boolean mInReplacedChip;
100
101    protected RecipientEntry(int entryType, String displayName, String destination,
102        int destinationType, String destinationLabel, long contactId, Long directoryId,
103        long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
104        String lookupKey, String[] permissions) {
105        this(entryType, displayName, destination, destinationType,
106            destinationLabel, contactId, directoryId, dataId, photoThumbnailUri,
107            true /* shouldDisplayIcon */, isFirstLevel, isValid, lookupKey, permissions);
108    }
109
110    protected RecipientEntry(int entryType, String displayName, String destination,
111            int destinationType, String destinationLabel, long contactId, Long directoryId,
112            long dataId, Uri photoThumbnailUri, boolean shouldDisplayIcon,
113            boolean isFirstLevel, boolean isValid, String lookupKey, String[] permissions) {
114        mEntryType = entryType;
115        mIsFirstLevel = isFirstLevel;
116        mDisplayName = displayName;
117        mDestination = destination;
118        mDestinationType = destinationType;
119        mDestinationLabel = destinationLabel;
120        mContactId = contactId;
121        mDirectoryId = directoryId;
122        mDataId = dataId;
123        mPhotoThumbnailUri = photoThumbnailUri;
124        mShouldDisplayIcon = shouldDisplayIcon;
125        mPhotoBytes = null;
126        mIsValid = isValid;
127        mLookupKey = lookupKey;
128        mIndicatorIconId = 0;
129        mIndicatorText = null;
130        mPermissions = permissions;
131    }
132
133    protected RecipientEntry(int entryType, String displayName, String destination,
134            int destinationType, String destinationLabel, long contactId, Long directoryId,
135            long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
136            String lookupKey) {
137        this(entryType, displayName, destination, destinationType, destinationLabel,
138                contactId, directoryId, dataId, photoThumbnailUri, isFirstLevel, isValid,
139                lookupKey, null);
140    }
141
142    public boolean isValid() {
143        return mIsValid;
144    }
145
146    /**
147     * Determine if this was a RecipientEntry created from recipient info or
148     * an entry from contacts.
149     */
150    public static boolean isCreatedRecipient(long id) {
151        return id == RecipientEntry.INVALID_CONTACT || id == RecipientEntry.GENERATED_CONTACT;
152    }
153
154    /**
155     * Construct a RecipientEntry from just an address that has been entered.
156     * This address has not been resolved to a contact and therefore does not
157     * have a contact id or photo.
158     */
159    public static RecipientEntry constructFakeEntry(final String address, final boolean isValid) {
160        final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
161        final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;
162
163        return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
164                INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
165                INVALID_CONTACT, null, true, isValid, null /* lookupKey */, null /* permissions */);
166    }
167
168    /**
169     * Construct a RecipientEntry from just a phone number.
170     */
171    public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,
172            final boolean isValid) {
173        return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber,
174                INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
175                INVALID_CONTACT, null, true, isValid, null /* lookupKey */, null /* permissions */);
176    }
177
178    /**
179     * Construct a RecipientEntry from just an address that has been entered
180     * with both an associated display name. This address has not been resolved
181     * to a contact and therefore does not have a contact id or photo.
182     */
183    public static RecipientEntry constructGeneratedEntry(String display, String address,
184            boolean isValid) {
185        return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
186                null, GENERATED_CONTACT, null /* directoryId */, GENERATED_CONTACT, null, true,
187                isValid, null /* lookupKey */, null /* permissions */);
188    }
189
190    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
191            String destination, int destinationType, String destinationLabel, long contactId,
192            Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid,
193            String lookupKey) {
194        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
195                displayName, destination), destination, destinationType, destinationLabel,
196                contactId, directoryId, dataId, photoThumbnailUri, true, isValid, lookupKey,
197                null /* permissions */);
198    }
199
200    public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
201            String destination, int destinationType, String destinationLabel, long contactId,
202            Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid,
203            String lookupKey) {
204        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
205                displayName, destination), destination, destinationType, destinationLabel,
206                contactId, directoryId, dataId, (thumbnailUriAsString != null
207                ? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey,
208                null /* permissions */);
209    }
210
211    public static RecipientEntry constructSecondLevelEntry(String displayName,
212            int displayNameSource, String destination, int destinationType,
213            String destinationLabel, long contactId, Long directoryId, long dataId,
214            String thumbnailUriAsString, boolean isValid, String lookupKey) {
215        return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
216                displayName, destination), destination, destinationType, destinationLabel,
217                contactId, directoryId, dataId, (thumbnailUriAsString != null
218                ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey,
219                null /* permissions */);
220    }
221
222    public static RecipientEntry constructPermissionEntry(String[] permissions) {
223        return new RecipientEntry(
224                ENTRY_TYPE_PERMISSION_REQUEST,
225                "" /* displayName */,
226                "" /* destination */,
227                Email.TYPE_CUSTOM,
228                "" /* destinationLabel */,
229                INVALID_CONTACT,
230                null /* directoryId */,
231                INVALID_CONTACT,
232                null /* photoThumbnailUri */,
233                true /* isFirstLevel*/,
234                false /* isValid */,
235                null /* lookupKey */,
236                permissions);
237    }
238
239    /**
240     * @return the display name for the entry.  If the display name source is larger than
241     * {@link DisplayNameSources#PHONE} we use the contact's display name, but if not,
242     * i.e. the display name came from an email address or a phone number, we don't use it
243     * to avoid confusion and just use the destination instead.
244     */
245    private static String pickDisplayName(int displayNameSource, String displayName,
246            String destination) {
247        return (displayNameSource > DisplayNameSources.PHONE) ? displayName : destination;
248    }
249
250    public int getEntryType() {
251        return mEntryType;
252    }
253
254    public String getDisplayName() {
255        return mDisplayName;
256    }
257
258    public String getDestination() {
259        return mDestination;
260    }
261
262    public int getDestinationType() {
263        return mDestinationType;
264    }
265
266    public String getDestinationLabel() {
267        return mDestinationLabel;
268    }
269
270    public long getContactId() {
271        return mContactId;
272    }
273
274    public Long getDirectoryId() {
275        return mDirectoryId;
276    }
277
278    public long getDataId() {
279        return mDataId;
280    }
281
282    public boolean isFirstLevel() {
283        return mIsFirstLevel;
284    }
285
286    public Uri getPhotoThumbnailUri() {
287        return mPhotoThumbnailUri;
288    }
289
290    /** Indicates whether the icon in the chip is displayed or not. */
291    public boolean shouldDisplayIcon() {
292        return mShouldDisplayIcon;
293    }
294
295    /** This can be called outside main Looper thread. */
296    public synchronized void setPhotoBytes(byte[] photoBytes) {
297        mPhotoBytes = photoBytes;
298    }
299
300    /** This can be called outside main Looper thread. */
301    public synchronized byte[] getPhotoBytes() {
302        return mPhotoBytes;
303    }
304
305    /**
306     * Used together with {@link #ENTRY_TYPE_PERMISSION_REQUEST} and indicates what permissions we
307     * need to ask user to grant.
308     */
309    public String[] getPermissions() {
310        return mPermissions;
311    }
312
313    public String getLookupKey() {
314        return mLookupKey;
315    }
316
317    public boolean isSelectable() {
318        return mEntryType == ENTRY_TYPE_PERSON || mEntryType == ENTRY_TYPE_PERMISSION_REQUEST;
319    }
320
321    @Override
322    public String toString() {
323        return mDisplayName + " <" + mDestination + ">, isValid=" + mIsValid;
324    }
325
326    /**
327     * Returns if entry represents the same person as this instance. The default implementation
328     * checks whether the contact ids are the same, and subclasses may opt to override this.
329     */
330    public boolean isSamePerson(final RecipientEntry entry) {
331        return entry != null && mContactId == entry.mContactId;
332    }
333
334    /**
335     * Returns the resource ID for the indicator icon, or 0 if no icon should be displayed.
336     */
337    @DrawableRes
338    public int getIndicatorIconId() {
339        return mIndicatorIconId;
340    }
341
342    /**
343     * Sets the indicator icon to the given resource ID.  Set to 0 to display no icon.
344     */
345    public void setIndicatorIconId(@DrawableRes int indicatorIconId) {
346        mIndicatorIconId = indicatorIconId;
347    }
348
349    /**
350     * Get the indicator text, or null if no text should be displayed.
351     */
352    public String getIndicatorText() {
353        return mIndicatorText;
354    }
355
356    /**
357     * Set the indicator text.  Set to null for no text to be displayed.
358     */
359    public void setIndicatorText(String indicatorText) {
360        mIndicatorText = indicatorText;
361    }
362
363    /**
364     * Get whether this RecipientEntry is in a replaced chip or not. Replaced chip only occurs
365     * if {@link RecipientEditTextView} uses a replacement chip for the entry.
366     */
367    public boolean getInReplacedChip() {
368        return mInReplacedChip;
369    }
370
371    /**
372     * Sets {@link #mInReplacedChip} to {@param inReplacedChip}.
373     */
374    public void setInReplacedChip(boolean inReplacedChip) {
375        mInReplacedChip = inReplacedChip;
376    }
377}
378