/* * Copyright 2022 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 androidx.appsearch.builtintypes; import android.net.Uri; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RestrictTo; import androidx.appsearch.annotation.Document; import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig; import androidx.core.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * AppSearch document representing a Person entity modeled after * Person. * *

The {@link Person} document includes commonly searchable properties such as name, * organization, and notes, as well as contact information such as phone numbers, email * addresses, etc, grouped by their label. The labeled contact information is present in a nested * {@link ContactPoint} document. */ @Document(name = "builtin:Person") public class Person extends Thing { /** Holds type information for additional names for Person. */ public static class AdditionalName { /** @hide */ @RestrictTo(RestrictTo.Scope.LIBRARY) @IntDef({ TYPE_UNKNOWN, TYPE_NICKNAME, TYPE_PHONETIC_NAME }) @Retention(RetentionPolicy.SOURCE) public @interface NameType { } /** The additional name is unknown. */ public static final int TYPE_UNKNOWN = 0; /** The additional name is a nickname. */ public static final int TYPE_NICKNAME = 1; /** The additional name is a phonetic name. */ public static final int TYPE_PHONETIC_NAME = 2; @NameType private final int mType; private final String mValue; /** * Constructs an {@link AdditionalName}. */ public AdditionalName(@NameType int type, @NonNull String value) { mType = Preconditions.checkArgumentInRange(type, TYPE_UNKNOWN, TYPE_PHONETIC_NAME, "type"); mValue = value; } @NameType public int getType() { return mType; } @NonNull public String getValue() { return mValue; } @Override public boolean equals(@Nullable Object other) { if (this == other) { return true; } if (!(other instanceof AdditionalName)) { return false; } return mType == ((AdditionalName) other).mType && mValue.equals( ((AdditionalName) other).mValue); } @Override public int hashCode() { String str = mType + mValue; return str.hashCode(); } } @Document.StringProperty private final String mGivenName; @Document.StringProperty private final String mMiddleName; @Document.StringProperty private final String mFamilyName; @Document.StringProperty final String mExternalUri; @Document.StringProperty final String mImageUri; @Document.BooleanProperty final boolean mIsImportant; @Document.BooleanProperty final boolean mIsBot; @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES) private final List mNotes; @Document.LongProperty final List mAdditionalNameTypes; @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES) final List mAdditionalNames; @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES) private final List mAffiliations; @Document.StringProperty private final List mRelations; @Document.DocumentProperty(indexNestedProperties = true) private final List mContactPoints; private final List mTypedAdditionalNames; Person(@NonNull String namespace, @NonNull String id, int documentScore, long creationTimestampMillis, long documentTtlMillis, @Nullable String name, @Nullable List alternateNames, @Nullable String description, @Nullable String image, @Nullable String url, @Nullable String givenName, @Nullable String middleName, @Nullable String familyName, @Nullable String externalUri, @Nullable String imageUri, boolean isImportant, boolean isBot, @NonNull List notes, @NonNull @AdditionalName.NameType List additionalNameTypes, @NonNull List additionalNames, @NonNull List affiliations, @NonNull List relations, @NonNull List contactPoints) { super(namespace, id, documentScore, creationTimestampMillis, documentTtlMillis, name, alternateNames, description, image, url); mGivenName = givenName; mMiddleName = middleName; mFamilyName = familyName; mExternalUri = externalUri; mImageUri = imageUri; mIsImportant = isImportant; mIsBot = isBot; mNotes = Collections.unmodifiableList(notes); mAdditionalNameTypes = Collections.unmodifiableList(additionalNameTypes); mAdditionalNames = Collections.unmodifiableList(additionalNames); mAffiliations = Collections.unmodifiableList(affiliations); mRelations = Collections.unmodifiableList(relations); mContactPoints = Collections.unmodifiableList(contactPoints); // For the additionalNames to to returned in the getter. List names = new ArrayList<>(mAdditionalNameTypes.size()); for (int i = 0; i < mAdditionalNameTypes.size(); ++i) { names.add(new AdditionalName(mAdditionalNameTypes.get(i).intValue(), mAdditionalNames.get(i))); } mTypedAdditionalNames = Collections.unmodifiableList(names); } /** Returns the given (or first) name for this {@link Person}. */ @Nullable public String getGivenName() { return mGivenName; } /** * Returns the middle name(s) for this {@link Person}. * *

For a Person with multiple middle names, this method returns a flattened and whitespace * separated list. For example, "middle1 middle2 ..." */ @Nullable public String getMiddleName() { return mMiddleName; } /** Returns the family (or last) name for this {@link Person}. */ @Nullable public String getFamilyName() { return mFamilyName; } /** * Returns an external uri for this {@link Person}. Or {@code null} if no {@link Uri} is * provided. A {@link Uri} can be any of the following: * *

  • A {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. *
  • A {@code mailto:} schema* *
  • A {@code tel:} schema* * *

    For mailto: and tel: URI schemes, it is recommended that the path portion * refers to a valid contact in the Contacts Provider. */ @Nullable public Uri getExternalUri() { if (mExternalUri != null) { return Uri.parse(mExternalUri); } return null; } /** Returns {@link Uri} of the profile image for this {@link Person}. */ @Nullable public Uri getImageUri() { if (mImageUri != null) { return Uri.parse(mImageUri); } return null; } /** * Returns whether this {@link Person} is important to the user of this device with * regards to how frequently they interact. */ public boolean isImportant() { return mIsImportant; } /** Returns whether this {@link Person} is a machine rather than a human. */ public boolean isBot() { return mIsBot; } /** Returns the notes about this {@link Person}. */ @NonNull public List getNotes() { return mNotes; } /** * Returns a list of additional names for this {@link Person}. * *

    Additional names can include something like phonetic names, or nicknames. * *

    Different from {@link #getTypedAdditionalNames()}, the return value doesn't include * type information for the additional names. */ @NonNull public List getAdditionalNames() { return mAdditionalNames; } /** * Returns a list of {@link AdditionalName} for this {@link Person}. * *

    Additional names can include something like phonetic names, or nicknames. * *

    Each {@link AdditionalName} contains type information for the additional name. */ @NonNull public List getTypedAdditionalNames() { return mTypedAdditionalNames; } /** * Returns a list of affiliation for this {@link Person}. Like company, school, etc. * *

    For a contact with the title "Software Engineer" in a department "Engineering" at a * company "Cloud Company", this can include a flattened value of "Software Engineer, * Engineering, Cloud Company". */ @NonNull public List getAffiliations() { return mAffiliations; } /** Returns a list of relations for this {@link Person}, like "Father" or "Mother". */ @NonNull public List getRelations() { return mRelations; } /** * Returns a list of {@link ContactPoint}. * *

    More information can be found in {@link ContactPoint}. */ @NonNull public List getContactPoints() { return mContactPoints; } /** Builder class for {@link Person}. */ public static final class Builder extends BuilderImpl { /** * Constructor for {@link Person.Builder}. * * @param namespace Namespace for the {@link Person} Document. See * {@link Document.Namespace}. * @param id Unique identifier for the {@link Person} Document. See * {@link Document.Id}. * @param name The searchable full name of this {@link Person}. E.g. "Larry Page", or * "Page, Larry". */ public Builder(@NonNull String namespace, @NonNull String id, @NonNull String name) { super(namespace, id, name); } /** * Constructor for {@link Builder} with all the existing values of an {@link Person}. */ public Builder(@NonNull Person person) { super(person); } } @SuppressWarnings("unchecked") static class BuilderImpl> extends Thing.BuilderImpl { private String mGivenName; private String mMiddleName; private String mFamilyName; private Uri mExternalUri; private Uri mImageUri; boolean mIsImportant; boolean mIsBot; // Make sure the lists are not null. private List mNotes = Collections.emptyList(); @AdditionalName.NameType private List mAdditionalNameTypes = Collections.emptyList(); private List mAdditionalNames = Collections.emptyList(); private List mAffiliations = Collections.emptyList(); private List mRelations = Collections.emptyList(); private List mContactPoints = Collections.emptyList(); BuilderImpl(@NonNull String namespace, @NonNull String id, @NonNull String name) { super(namespace, id); mName = Preconditions.checkNotNull(name); } BuilderImpl(@NonNull Person person) { super(new Thing.Builder(person).build()); mDocumentScore = person.getDocumentScore(); mCreationTimestampMillis = person.getCreationTimestampMillis(); mDocumentTtlMillis = person.getDocumentTtlMillis(); mGivenName = person.getGivenName(); mMiddleName = person.getMiddleName(); mFamilyName = person.getFamilyName(); mExternalUri = person.getExternalUri(); mImageUri = person.getImageUri(); mIsImportant = person.isImportant(); mIsBot = person.isBot(); mNotes = person.getNotes(); mAffiliations = person.getAffiliations(); mRelations = person.getRelations(); mContactPoints = person.getContactPoints(); setAdditionalNames(person.getTypedAdditionalNames()); } /** Sets the given name of this {@link Person}. */ @NonNull public T setGivenName(@NonNull String givenName) { mGivenName = Preconditions.checkNotNull(givenName); return (T) this; } /** * Sets the middle name of this {@link Person}. * *

    For {@link Person} with multiple middle names, they can all be set in this * single string. Each middle name could be separated by a whitespace like "middleName1 * middleName2 middleName3". */ @NonNull public T setMiddleName(@NonNull String middleName) { mMiddleName = Preconditions.checkNotNull(middleName); return (T) this; } /** Sets the family name of this {@link Person}. */ @NonNull public T setFamilyName(@NonNull String familyName) { mFamilyName = Preconditions.checkNotNull(familyName); return (T) this; } /** * Sets an external {@link Uri} for this {@link Person}. Or {@code null} if no * {@link Uri} is provided. A {@link Uri} can be any of the following: * *

  • A {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. *
  • A {@code mailto:} schema* *
  • A {@code tel:} schema* * *

    For mailto: and tel: URI schemes, it is recommended that the path * portion refers to a valid contact in the Contacts Provider. */ @NonNull public T setExternalUri(@NonNull Uri externalUri) { mExternalUri = Preconditions.checkNotNull(externalUri); return (T) this; } /** Sets the {@link Uri} of the profile image for the {@link Person}. */ @NonNull public T setImageUri(@NonNull Uri imageUri) { mImageUri = Preconditions.checkNotNull(imageUri); return (T) this; } /** Sets whether this {@link Person} is important. */ @NonNull public T setImportant(boolean isImportant) { mIsImportant = isImportant; return (T) this; } /** Sets whether this {@link Person} is a bot. */ @NonNull public T setBot(boolean isBot) { mIsBot = isBot; return (T) this; } /** Sets the notes about this {@link Person}. */ @NonNull public T setNotes(@NonNull List notes) { mNotes = Preconditions.checkNotNull(notes); return (T) this; } /** * Sets a list of {@link AdditionalName} for that {@link Person}. * *

    Only types defined in {@link AdditionalName.NameType} are accepted. */ @NonNull public T setAdditionalNames(@NonNull List additionalNames) { Preconditions.checkNotNull(additionalNames); int size = additionalNames.size(); mAdditionalNameTypes = new ArrayList<>(size); mAdditionalNames = new ArrayList<>(size); for (int i = 0; i < additionalNames.size(); ++i) { long type = Preconditions.checkArgumentInRange(additionalNames.get(i).getType(), AdditionalName.TYPE_UNKNOWN, AdditionalName.TYPE_PHONETIC_NAME, "type"); mAdditionalNameTypes.add(type); mAdditionalNames.add(additionalNames.get(i).getValue()); } return (T) this; } /** * Sets a list of affiliations for this {@link Person}. Like company, school, * etc. */ @NonNull public T setAffiliations(@NonNull List affiliations) { mAffiliations = Preconditions.checkNotNull(affiliations); return (T) this; } /** Sets a list of relations for this {@link Person}, like "Father" or "Mother". */ @NonNull public T setRelations(@NonNull List relations) { mRelations = Preconditions.checkNotNull(relations); return (T) this; } /** * Sets a list of {@link ContactPoint} for the {@link Person}. * *

    More information could be found in {@link ContactPoint}. */ @NonNull public T setContactPoints(@NonNull List contactPoints) { mContactPoints = Preconditions.checkNotNull(contactPoints); return (T) this; } /** Builds the {@link Person}. */ @NonNull @Override public Person build() { Preconditions.checkState(mAdditionalNameTypes.size() == mAdditionalNames.size()); return new Person( /*namespace=*/ mNamespace, /*id=*/ mId, /*documentScore=*/mDocumentScore, /*creationTimestampMillis=*/ mCreationTimestampMillis, /*documentTtlMillis=*/ mDocumentTtlMillis, /*name=*/ mName, /*alternateNames=*/ mAlternateNames, /*description=*/ mDescription, /*image=*/ mImage, /*url=*/ mUrl, /*givenName=*/ mGivenName, /*middleName=*/ mMiddleName, /*familyName=*/ mFamilyName, /*externalUri=*/ mExternalUri != null ? mExternalUri.toString() : null, /*imageUri=*/ mImageUri != null ? mImageUri.toString() : null, /*isImportant=*/ mIsImportant, /*isBot=*/ mIsBot, /*notes=*/ new ArrayList<>(mNotes), /*additionalNameTypes=*/ new ArrayList<>(mAdditionalNameTypes), /*additionalNames=*/ new ArrayList<>(mAdditionalNames), /*affiliations=*/ new ArrayList<>(mAffiliations), /*relations=*/ new ArrayList<>(mRelations), /*contactPoints=*/ new ArrayList<>(mContactPoints)); } } }