/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ex.chips; import android.net.Uri; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.DisplayNameSources; import androidx.annotation.DrawableRes; import android.text.util.Rfc822Token; import android.text.util.Rfc822Tokenizer; /** * Represents one entry inside recipient auto-complete list. */ public class RecipientEntry { /* package */ static final int INVALID_CONTACT = -1; /** * A GENERATED_CONTACT is one that was created based entirely on * information passed in to the RecipientEntry from an external source * that is not a real contact. */ /* package */ static final int GENERATED_CONTACT = -2; /** Used when {@link #mDestinationType} is invalid and thus shouldn't be used for display. */ public static final int INVALID_DESTINATION_TYPE = -1; public static final int ENTRY_TYPE_PERSON = 0; /** * Entry of this type represents the item in auto-complete that asks user to grant permissions * to the app. This permission model is introduced in M platform. * *
Entries of this type should have {@link #mPermissions} set as well.
*/
public static final int ENTRY_TYPE_PERMISSION_REQUEST = 1;
public static final int ENTRY_TYPE_SIZE = 2;
private final int mEntryType;
/**
* True when this entry is the first entry in a group, which should have a photo and display
* name, while the second or later entries won't.
*/
private boolean mIsFirstLevel;
private final String mDisplayName;
/** Destination for this contact entry. Would be an email address or a phone number. */
private final String mDestination;
/** Type of the destination like {@link Email#TYPE_HOME} */
private final int mDestinationType;
/**
* Label of the destination which will be used when type was {@link Email#TYPE_CUSTOM}.
* Can be null when {@link #mDestinationType} is {@link #INVALID_DESTINATION_TYPE}.
*/
private final String mDestinationLabel;
/** ID for the person */
private final long mContactId;
/** ID for the directory this contact came from, or null
*/
private final Long mDirectoryId;
/** ID for the destination */
private final long mDataId;
private final Uri mPhotoThumbnailUri;
/** Configures showing the icon in the chip */
private final boolean mShouldDisplayIcon;
private boolean mIsValid;
/**
* This can be updated after this object being constructed, when the photo is fetched
* from remote directories.
*/
private byte[] mPhotoBytes;
@DrawableRes private int mIndicatorIconId;
private String mIndicatorText;
/** See {@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} */
private final String mLookupKey;
/** Should be used when type is {@link #ENTRY_TYPE_PERMISSION_REQUEST}. */
private final String[] mPermissions;
/** Whether RecipientEntry is in a replaced chip or not. */
private boolean mInReplacedChip;
protected 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) {
this(entryType, displayName, destination, destinationType,
destinationLabel, contactId, directoryId, dataId, photoThumbnailUri,
true /* shouldDisplayIcon */, isFirstLevel, isValid, lookupKey, permissions);
}
protected 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) {
mEntryType = entryType;
mIsFirstLevel = isFirstLevel;
mDisplayName = displayName;
mDestination = destination;
mDestinationType = destinationType;
mDestinationLabel = destinationLabel;
mContactId = contactId;
mDirectoryId = directoryId;
mDataId = dataId;
mPhotoThumbnailUri = photoThumbnailUri;
mShouldDisplayIcon = shouldDisplayIcon;
mPhotoBytes = null;
mIsValid = isValid;
mLookupKey = lookupKey;
mIndicatorIconId = 0;
mIndicatorText = null;
mPermissions = permissions;
}
protected 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) {
this(entryType, displayName, destination, destinationType, destinationLabel,
contactId, directoryId, dataId, photoThumbnailUri, isFirstLevel, isValid,
lookupKey, null);
}
public boolean isValid() {
return mIsValid;
}
/**
* Determine if this was a RecipientEntry created from recipient info or
* an entry from contacts.
*/
public static boolean isCreatedRecipient(long id) {
return id == RecipientEntry.INVALID_CONTACT || id == RecipientEntry.GENERATED_CONTACT;
}
/**
* Construct a RecipientEntry from just an address that has been entered.
* This address has not been resolved to a contact and therefore does not
* have a contact id or photo.
*/
public static RecipientEntry constructFakeEntry(final String address, final boolean isValid) {
final Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(address);
final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;
return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
INVALID_CONTACT, null, true, isValid, null /* lookupKey */, null /* permissions */);
}
/**
* Construct a RecipientEntry from just a phone number.
*/
public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,
final boolean isValid) {
return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber,
INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
INVALID_CONTACT, null, true, isValid, null /* lookupKey */, null /* permissions */);
}
/**
* Construct a RecipientEntry from just an address that has been entered
* with both an associated display name. This address has not been resolved
* to a contact and therefore does not have a contact id or photo.
*/
public static RecipientEntry constructGeneratedEntry(String display, String address,
boolean isValid) {
return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
null, GENERATED_CONTACT, null /* directoryId */, GENERATED_CONTACT, null, true,
isValid, null /* lookupKey */, null /* permissions */);
}
public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
String destination, int destinationType, String destinationLabel, long contactId,
Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid,
String lookupKey) {
return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
displayName, destination), destination, destinationType, destinationLabel,
contactId, directoryId, dataId, photoThumbnailUri, true, isValid, lookupKey,
null /* permissions */);
}
public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
String destination, int destinationType, String destinationLabel, long contactId,
Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid,
String lookupKey) {
return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
displayName, destination), destination, destinationType, destinationLabel,
contactId, directoryId, dataId, (thumbnailUriAsString != null
? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey,
null /* permissions */);
}
public static RecipientEntry constructSecondLevelEntry(String displayName,
int displayNameSource, String destination, int destinationType,
String destinationLabel, long contactId, Long directoryId, long dataId,
String thumbnailUriAsString, boolean isValid, String lookupKey) {
return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
displayName, destination), destination, destinationType, destinationLabel,
contactId, directoryId, dataId, (thumbnailUriAsString != null
? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey,
null /* permissions */);
}
public static RecipientEntry constructPermissionEntry(String[] permissions) {
return new RecipientEntry(
ENTRY_TYPE_PERMISSION_REQUEST,
"" /* displayName */,
"" /* destination */,
Email.TYPE_CUSTOM,
"" /* destinationLabel */,
INVALID_CONTACT,
null /* directoryId */,
INVALID_CONTACT,
null /* photoThumbnailUri */,
true /* isFirstLevel*/,
false /* isValid */,
null /* lookupKey */,
permissions);
}
/**
* @return the display name for the entry. If the display name source is larger than
* {@link DisplayNameSources#PHONE} we use the contact's display name, but if not,
* i.e. the display name came from an email address or a phone number, we don't use it
* to avoid confusion and just use the destination instead.
*/
private static String pickDisplayName(int displayNameSource, String displayName,
String destination) {
return (displayNameSource > DisplayNameSources.PHONE) ? displayName : destination;
}
public int getEntryType() {
return mEntryType;
}
public String getDisplayName() {
return mDisplayName;
}
public String getDestination() {
return mDestination;
}
public int getDestinationType() {
return mDestinationType;
}
public String getDestinationLabel() {
return mDestinationLabel;
}
public long getContactId() {
return mContactId;
}
public Long getDirectoryId() {
return mDirectoryId;
}
public long getDataId() {
return mDataId;
}
public boolean isFirstLevel() {
return mIsFirstLevel;
}
public Uri getPhotoThumbnailUri() {
return mPhotoThumbnailUri;
}
/** Indicates whether the icon in the chip is displayed or not. */
public boolean shouldDisplayIcon() {
return mShouldDisplayIcon;
}
/** This can be called outside main Looper thread. */
public synchronized void setPhotoBytes(byte[] photoBytes) {
mPhotoBytes = photoBytes;
}
/** This can be called outside main Looper thread. */
public synchronized byte[] getPhotoBytes() {
return mPhotoBytes;
}
/**
* Used together with {@link #ENTRY_TYPE_PERMISSION_REQUEST} and indicates what permissions we
* need to ask user to grant.
*/
public String[] getPermissions() {
return mPermissions;
}
public String getLookupKey() {
return mLookupKey;
}
public boolean isSelectable() {
return mEntryType == ENTRY_TYPE_PERSON || mEntryType == ENTRY_TYPE_PERMISSION_REQUEST;
}
@Override
public String toString() {
return mDisplayName + " <" + mDestination + ">, isValid=" + mIsValid;
}
/**
* Returns if entry represents the same person as this instance. The default implementation
* checks whether the contact ids are the same, and subclasses may opt to override this.
*/
public boolean isSamePerson(final RecipientEntry entry) {
return entry != null && mContactId == entry.mContactId;
}
/**
* Returns the resource ID for the indicator icon, or 0 if no icon should be displayed.
*/
@DrawableRes
public int getIndicatorIconId() {
return mIndicatorIconId;
}
/**
* Sets the indicator icon to the given resource ID. Set to 0 to display no icon.
*/
public void setIndicatorIconId(@DrawableRes int indicatorIconId) {
mIndicatorIconId = indicatorIconId;
}
/**
* Get the indicator text, or null if no text should be displayed.
*/
public String getIndicatorText() {
return mIndicatorText;
}
/**
* Set the indicator text. Set to null for no text to be displayed.
*/
public void setIndicatorText(String indicatorText) {
mIndicatorText = indicatorText;
}
/**
* Get whether this RecipientEntry is in a replaced chip or not. Replaced chip only occurs
* if {@link RecipientEditTextView} uses a replacement chip for the entry.
*/
public boolean getInReplacedChip() {
return mInReplacedChip;
}
/**
* Sets {@link #mInReplacedChip} to {@param inReplacedChip}.
*/
public void setInReplacedChip(boolean inReplacedChip) {
mInReplacedChip = inReplacedChip;
}
}