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