• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.support.annotation.DrawableRes;
23  import android.text.util.Rfc822Token;
24  import android.text.util.Rfc822Tokenizer;
25  
26  /**
27   * Represents one entry inside recipient auto-complete list.
28   */
29  public 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  
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, String[] permissions)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  
RecipientEntry(int entryType, String displayName, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, Uri photoThumbnailUri, boolean shouldDisplayIcon, boolean isFirstLevel, boolean isValid, String lookupKey, String[] permissions)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  
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)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  
isValid()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       */
isCreatedRecipient(long id)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       */
constructFakeEntry(final String address, final boolean isValid)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       */
constructFakePhoneEntry(final String phoneNumber, final boolean isValid)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       */
constructGeneratedEntry(String display, String address, boolean isValid)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  
constructTopLevelEntry(String displayName, int displayNameSource, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid, String lookupKey)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  
constructTopLevelEntry(String displayName, int displayNameSource, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid, String lookupKey)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  
constructSecondLevelEntry(String displayName, int displayNameSource, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid, String lookupKey)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  
constructPermissionEntry(String[] permissions)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       */
pickDisplayName(int displayNameSource, String displayName, String destination)245      private static String pickDisplayName(int displayNameSource, String displayName,
246              String destination) {
247          return (displayNameSource > DisplayNameSources.PHONE) ? displayName : destination;
248      }
249  
getEntryType()250      public int getEntryType() {
251          return mEntryType;
252      }
253  
getDisplayName()254      public String getDisplayName() {
255          return mDisplayName;
256      }
257  
getDestination()258      public String getDestination() {
259          return mDestination;
260      }
261  
getDestinationType()262      public int getDestinationType() {
263          return mDestinationType;
264      }
265  
getDestinationLabel()266      public String getDestinationLabel() {
267          return mDestinationLabel;
268      }
269  
getContactId()270      public long getContactId() {
271          return mContactId;
272      }
273  
getDirectoryId()274      public Long getDirectoryId() {
275          return mDirectoryId;
276      }
277  
getDataId()278      public long getDataId() {
279          return mDataId;
280      }
281  
isFirstLevel()282      public boolean isFirstLevel() {
283          return mIsFirstLevel;
284      }
285  
getPhotoThumbnailUri()286      public Uri getPhotoThumbnailUri() {
287          return mPhotoThumbnailUri;
288      }
289  
290      /** Indicates whether the icon in the chip is displayed or not. */
shouldDisplayIcon()291      public boolean shouldDisplayIcon() {
292          return mShouldDisplayIcon;
293      }
294  
295      /** This can be called outside main Looper thread. */
setPhotoBytes(byte[] photoBytes)296      public synchronized void setPhotoBytes(byte[] photoBytes) {
297          mPhotoBytes = photoBytes;
298      }
299  
300      /** This can be called outside main Looper thread. */
getPhotoBytes()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       */
getPermissions()309      public String[] getPermissions() {
310          return mPermissions;
311      }
312  
getLookupKey()313      public String getLookupKey() {
314          return mLookupKey;
315      }
316  
isSelectable()317      public boolean isSelectable() {
318          return mEntryType == ENTRY_TYPE_PERSON || mEntryType == ENTRY_TYPE_PERMISSION_REQUEST;
319      }
320  
321      @Override
toString()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       */
isSamePerson(final RecipientEntry entry)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
getIndicatorIconId()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       */
setIndicatorIconId(@rawableRes int indicatorIconId)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       */
getIndicatorText()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       */
setIndicatorText(String indicatorText)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       */
getInReplacedChip()367      public boolean getInReplacedChip() {
368          return mInReplacedChip;
369      }
370  
371      /**
372       * Sets {@link #mInReplacedChip} to {@param inReplacedChip}.
373       */
setInReplacedChip(boolean inReplacedChip)374      public void setInReplacedChip(boolean inReplacedChip) {
375          mInReplacedChip = inReplacedChip;
376      }
377  }
378