/* * 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; } }