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 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 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)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 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)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 isValid()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 */ isCreatedRecipient(long id)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 */ constructFakeEntry(final String address, final boolean isValid)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 */ constructFakePhoneEntry(final String phoneNumber, final boolean isValid)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 */ constructGeneratedEntry(String display, String address, boolean isValid)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 constructTopLevelEntry(String displayName, int displayNameSource, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid, String lookupKey)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 constructTopLevelEntry(String displayName, int displayNameSource, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid, String lookupKey)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 constructSecondLevelEntry(String displayName, int displayNameSource, String destination, int destinationType, String destinationLabel, long contactId, Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid, String lookupKey)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 constructPermissionEntry(String[] permissions)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 */ pickDisplayName(int displayNameSource, String displayName, String destination)230 private static String pickDisplayName(int displayNameSource, String displayName, 231 String destination) { 232 return (displayNameSource > DisplayNameSources.PHONE) ? displayName : destination; 233 } 234 getEntryType()235 public int getEntryType() { 236 return mEntryType; 237 } 238 getDisplayName()239 public String getDisplayName() { 240 return mDisplayName; 241 } 242 getDestination()243 public String getDestination() { 244 return mDestination; 245 } 246 getDestinationType()247 public int getDestinationType() { 248 return mDestinationType; 249 } 250 getDestinationLabel()251 public String getDestinationLabel() { 252 return mDestinationLabel; 253 } 254 getContactId()255 public long getContactId() { 256 return mContactId; 257 } 258 getDirectoryId()259 public Long getDirectoryId() { 260 return mDirectoryId; 261 } 262 getDataId()263 public long getDataId() { 264 return mDataId; 265 } 266 isFirstLevel()267 public boolean isFirstLevel() { 268 return mIsFirstLevel; 269 } 270 getPhotoThumbnailUri()271 public Uri getPhotoThumbnailUri() { 272 return mPhotoThumbnailUri; 273 } 274 275 /** This can be called outside main Looper thread. */ setPhotoBytes(byte[] photoBytes)276 public synchronized void setPhotoBytes(byte[] photoBytes) { 277 mPhotoBytes = photoBytes; 278 } 279 280 /** This can be called outside main Looper thread. */ getPhotoBytes()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 */ getPermissions()289 public String[] getPermissions() { 290 return mPermissions; 291 } 292 getLookupKey()293 public String getLookupKey() { 294 return mLookupKey; 295 } 296 isSelectable()297 public boolean isSelectable() { 298 return mEntryType == ENTRY_TYPE_PERSON || mEntryType == ENTRY_TYPE_PERMISSION_REQUEST; 299 } 300 301 @Override toString()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 */ isSamePerson(final RecipientEntry entry)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 getIndicatorIconId()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 */ setIndicatorIconId(@rawableRes int indicatorIconId)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 */ getIndicatorText()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 */ setIndicatorText(String indicatorText)339 public void setIndicatorText(String indicatorText) { 340 mIndicatorText = indicatorText; 341 } 342 } 343