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 androidx.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