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 protected RecipientEntry(int entryType, String displayName, String destination, 99 int destinationType, String destinationLabel, long contactId, Long directoryId, 100 long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid, 101 String lookupKey, String[] permissions) { 102 this(entryType, displayName, destination, destinationType, 103 destinationLabel, contactId, directoryId, dataId, photoThumbnailUri, 104 true /* shouldDisplayIcon */, isFirstLevel, isValid, lookupKey, permissions); 105 } 106 107 protected RecipientEntry(int entryType, String displayName, String destination, 108 int destinationType, String destinationLabel, long contactId, Long directoryId, 109 long dataId, Uri photoThumbnailUri, boolean shouldDisplayIcon, 110 boolean isFirstLevel, boolean isValid, String lookupKey, String[] permissions) { 111 mEntryType = entryType; 112 mIsFirstLevel = isFirstLevel; 113 mDisplayName = displayName; 114 mDestination = destination; 115 mDestinationType = destinationType; 116 mDestinationLabel = destinationLabel; 117 mContactId = contactId; 118 mDirectoryId = directoryId; 119 mDataId = dataId; 120 mPhotoThumbnailUri = photoThumbnailUri; 121 mShouldDisplayIcon = shouldDisplayIcon; 122 mPhotoBytes = null; 123 mIsValid = isValid; 124 mLookupKey = lookupKey; 125 mIndicatorIconId = 0; 126 mIndicatorText = null; 127 mPermissions = permissions; 128 } 129 130 protected RecipientEntry(int entryType, String displayName, String destination, 131 int destinationType, String destinationLabel, long contactId, Long directoryId, 132 long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid, 133 String lookupKey) { 134 this(entryType, displayName, destination, destinationType, destinationLabel, 135 contactId, directoryId, dataId, photoThumbnailUri, isFirstLevel, isValid, 136 lookupKey, null); 137 } 138 139 public boolean isValid() { 140 return mIsValid; 141 } 142 143 /** 144 * Determine if this was a RecipientEntry created from recipient info or 145 * an entry from contacts. 146 */ 147 public static boolean isCreatedRecipient(long id) { 148 return id == RecipientEntry.INVALID_CONTACT || id == RecipientEntry.GENERATED_CONTACT; 149 } 150 151 /** 152 * Construct a RecipientEntry from just an address that has been entered. 153 * This address has not been resolved to a contact and therefore does not 154 * have a contact id or photo. 155 */ 156 public static RecipientEntry constructFakeEntry(final String address, final boolean isValid) { 157 final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address); 158 final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address; 159 160 return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress, 161 INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */, 162 INVALID_CONTACT, null, true, isValid, null /* lookupKey */, null /* permissions */); 163 } 164 165 /** 166 * Construct a RecipientEntry from just a phone number. 167 */ 168 public static RecipientEntry constructFakePhoneEntry(final String phoneNumber, 169 final boolean isValid) { 170 return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber, 171 INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */, 172 INVALID_CONTACT, null, true, isValid, null /* lookupKey */, null /* permissions */); 173 } 174 175 /** 176 * Construct a RecipientEntry from just an address that has been entered 177 * with both an associated display name. This address has not been resolved 178 * to a contact and therefore does not have a contact id or photo. 179 */ 180 public static RecipientEntry constructGeneratedEntry(String display, String address, 181 boolean isValid) { 182 return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE, 183 null, GENERATED_CONTACT, null /* directoryId */, GENERATED_CONTACT, null, true, 184 isValid, null /* lookupKey */, null /* permissions */); 185 } 186 187 public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource, 188 String destination, int destinationType, String destinationLabel, long contactId, 189 Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid, 190 String lookupKey) { 191 return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource, 192 displayName, destination), destination, destinationType, destinationLabel, 193 contactId, directoryId, dataId, photoThumbnailUri, true, isValid, lookupKey, 194 null /* permissions */); 195 } 196 197 public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource, 198 String destination, int destinationType, String destinationLabel, long contactId, 199 Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid, 200 String lookupKey) { 201 return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource, 202 displayName, destination), destination, destinationType, destinationLabel, 203 contactId, directoryId, dataId, (thumbnailUriAsString != null 204 ? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey, 205 null /* permissions */); 206 } 207 208 public static RecipientEntry constructSecondLevelEntry(String displayName, 209 int displayNameSource, String destination, int destinationType, 210 String destinationLabel, long contactId, Long directoryId, long dataId, 211 String thumbnailUriAsString, boolean isValid, String lookupKey) { 212 return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource, 213 displayName, destination), destination, destinationType, destinationLabel, 214 contactId, directoryId, dataId, (thumbnailUriAsString != null 215 ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey, 216 null /* permissions */); 217 } 218 219 public static RecipientEntry constructPermissionEntry(String[] permissions) { 220 return new RecipientEntry( 221 ENTRY_TYPE_PERMISSION_REQUEST, 222 "" /* displayName */, 223 "" /* destination */, 224 Email.TYPE_CUSTOM, 225 "" /* destinationLabel */, 226 INVALID_CONTACT, 227 null /* directoryId */, 228 INVALID_CONTACT, 229 null /* photoThumbnailUri */, 230 true /* isFirstLevel*/, 231 false /* isValid */, 232 null /* lookupKey */, 233 permissions); 234 } 235 236 /** 237 * @return the display name for the entry. If the display name source is larger than 238 * {@link DisplayNameSources#PHONE} we use the contact's display name, but if not, 239 * i.e. the display name came from an email address or a phone number, we don't use it 240 * to avoid confusion and just use the destination instead. 241 */ 242 private static String pickDisplayName(int displayNameSource, String displayName, 243 String destination) { 244 return (displayNameSource > DisplayNameSources.PHONE) ? displayName : destination; 245 } 246 247 public int getEntryType() { 248 return mEntryType; 249 } 250 251 public String getDisplayName() { 252 return mDisplayName; 253 } 254 255 public String getDestination() { 256 return mDestination; 257 } 258 259 public int getDestinationType() { 260 return mDestinationType; 261 } 262 263 public String getDestinationLabel() { 264 return mDestinationLabel; 265 } 266 267 public long getContactId() { 268 return mContactId; 269 } 270 271 public Long getDirectoryId() { 272 return mDirectoryId; 273 } 274 275 public long getDataId() { 276 return mDataId; 277 } 278 279 public boolean isFirstLevel() { 280 return mIsFirstLevel; 281 } 282 283 public Uri getPhotoThumbnailUri() { 284 return mPhotoThumbnailUri; 285 } 286 287 /** Indicates whether the icon in the chip is displayed or not. */ 288 public boolean shouldDisplayIcon() { 289 return mShouldDisplayIcon; 290 } 291 292 /** This can be called outside main Looper thread. */ 293 public synchronized void setPhotoBytes(byte[] photoBytes) { 294 mPhotoBytes = photoBytes; 295 } 296 297 /** This can be called outside main Looper thread. */ 298 public synchronized byte[] getPhotoBytes() { 299 return mPhotoBytes; 300 } 301 302 /** 303 * Used together with {@link #ENTRY_TYPE_PERMISSION_REQUEST} and indicates what permissions we 304 * need to ask user to grant. 305 */ 306 public String[] getPermissions() { 307 return mPermissions; 308 } 309 310 public String getLookupKey() { 311 return mLookupKey; 312 } 313 314 public boolean isSelectable() { 315 return mEntryType == ENTRY_TYPE_PERSON || mEntryType == ENTRY_TYPE_PERMISSION_REQUEST; 316 } 317 318 @Override 319 public String toString() { 320 return mDisplayName + " <" + mDestination + ">, isValid=" + mIsValid; 321 } 322 323 /** 324 * Returns if entry represents the same person as this instance. The default implementation 325 * checks whether the contact ids are the same, and subclasses may opt to override this. 326 */ 327 public boolean isSamePerson(final RecipientEntry entry) { 328 return entry != null && mContactId == entry.mContactId; 329 } 330 331 /** 332 * Returns the resource ID for the indicator icon, or 0 if no icon should be displayed. 333 */ 334 @DrawableRes 335 public int getIndicatorIconId() { 336 return mIndicatorIconId; 337 } 338 339 /** 340 * Sets the indicator icon to the given resource ID. Set to 0 to display no icon. 341 */ 342 public void setIndicatorIconId(@DrawableRes int indicatorIconId) { 343 mIndicatorIconId = indicatorIconId; 344 } 345 346 /** 347 * Get the indicator text, or null if no text should be displayed. 348 */ 349 public String getIndicatorText() { 350 return mIndicatorText; 351 } 352 353 /** 354 * Set the indicator text. Set to null for no text to be displayed. 355 */ 356 public void setIndicatorText(String indicatorText) { 357 mIndicatorText = indicatorText; 358 } 359 } 360