1 /*
2  * Copyright 2022 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 androidx.appsearch.builtintypes;
18 
19 import android.net.Uri;
20 
21 import androidx.annotation.IntDef;
22 import androidx.annotation.OptIn;
23 import androidx.annotation.RestrictTo;
24 import androidx.appsearch.annotation.Document;
25 import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig;
26 import androidx.appsearch.app.ExperimentalAppSearchApi;
27 import androidx.core.util.Preconditions;
28 
29 import org.jspecify.annotations.NonNull;
30 import org.jspecify.annotations.Nullable;
31 
32 import java.lang.annotation.Retention;
33 import java.lang.annotation.RetentionPolicy;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.List;
37 
38 /**
39  * AppSearch document representing a Person entity modeled after
40  * <a href="http://schema.org/Person">Person</a>.
41  *
42  * <p>The {@link Person} document includes commonly searchable properties such as name,
43  * organization, and notes, as well as contact information such as phone numbers, email
44  * addresses, etc, grouped by their label. The labeled contact information is present in a nested
45  * {@link ContactPoint} document.
46  */
47 @Document(name = "builtin:Person")
48 public class Person extends Thing {
49     /** Holds type information for additional names for Person. */
50     public static class AdditionalName {
51         /** @exportToFramework:hide */
52         @RestrictTo(RestrictTo.Scope.LIBRARY)
53         @IntDef({
54                 TYPE_UNKNOWN,
55                 TYPE_NICKNAME,
56                 TYPE_PHONETIC_NAME
57         })
58         @Retention(RetentionPolicy.SOURCE)
59         public @interface NameType {
60         }
61 
62         /** The additional name is unknown. */
63         public static final int TYPE_UNKNOWN = 0;
64         /** The additional name is a nickname. */
65         public static final int TYPE_NICKNAME = 1;
66         /** The additional name is a phonetic name. */
67         public static final int TYPE_PHONETIC_NAME = 2;
68 
69         @NameType
70         private final int mType;
71         private final String mValue;
72 
73         /**
74          * Constructs an {@link AdditionalName}.
75          */
AdditionalName(@ameType int type, @NonNull String value)76         public AdditionalName(@NameType int type,
77                 @NonNull String value) {
78             mType = Preconditions.checkArgumentInRange(type, TYPE_UNKNOWN, TYPE_PHONETIC_NAME,
79                     "type");
80             mValue = value;
81         }
82 
83         @NameType
getType()84         public int getType() {
85             return mType;
86         }
87 
getValue()88         public @NonNull String getValue() {
89             return mValue;
90         }
91 
92         @Override
equals(@ullable Object other)93         public boolean equals(@Nullable Object other) {
94             if (this == other) {
95                 return true;
96             }
97             if (!(other instanceof AdditionalName)) {
98                 return false;
99             }
100 
101             return mType == ((AdditionalName) other).mType && mValue.equals(
102                     ((AdditionalName) other).mValue);
103         }
104 
105         @Override
hashCode()106         public int hashCode() {
107             String str = mType + mValue;
108             return str.hashCode();
109         }
110     }
111 
112     @Document.StringProperty
113     private final String mGivenName;
114 
115     @Document.StringProperty
116     private final String mMiddleName;
117 
118     @Document.StringProperty
119     private final String mFamilyName;
120 
121     @Document.StringProperty
122     final String mExternalUri;
123 
124     @Document.StringProperty
125     final String mImageUri;
126 
127     @Document.BooleanProperty
128     final boolean mIsImportant;
129 
130     @Document.BooleanProperty
131     final boolean mIsBot;
132 
133     @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES)
134     private final List<String> mNotes;
135 
136     @Document.LongProperty
137     final List<Long> mAdditionalNameTypes;
138 
139     @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES)
140     final List<String> mAdditionalNames;
141 
142     @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES)
143     private final List<String> mAffiliations;
144 
145     @Document.StringProperty
146     private final List<String> mRelations;
147 
148     @Document.DocumentProperty(indexNestedProperties = true)
149     private final List<ContactPoint> mContactPoints;
150 
151     private final List<AdditionalName> mTypedAdditionalNames;
152 
153     @OptIn(markerClass = ExperimentalAppSearchApi.class)
Person(@onNull String namespace, @NonNull String id, int documentScore, long creationTimestampMillis, long documentTtlMillis, @Nullable String name, @Nullable List<String> alternateNames, @Nullable String description, @Nullable String image, @Nullable String url, @NonNull List<PotentialAction> potentialActions, @Nullable String givenName, @Nullable String middleName, @Nullable String familyName, @Nullable String externalUri, @Nullable String imageUri, boolean isImportant, boolean isBot, @NonNull List<String> notes, @AdditionalName.NameType @NonNull List<Long> additionalNameTypes, @NonNull List<String> additionalNames, @NonNull List<String> affiliations, @NonNull List<String> relations, @NonNull List<ContactPoint> contactPoints)154     Person(@NonNull String namespace,
155             @NonNull String id,
156             int documentScore,
157             long creationTimestampMillis,
158             long documentTtlMillis,
159             @Nullable String name,
160             @Nullable List<String> alternateNames,
161             @Nullable String description,
162             @Nullable String image,
163             @Nullable String url,
164             @NonNull List<PotentialAction> potentialActions,
165             @Nullable String givenName,
166             @Nullable String middleName,
167             @Nullable String familyName,
168             @Nullable String externalUri,
169             @Nullable String imageUri,
170             boolean isImportant,
171             boolean isBot,
172             @NonNull List<String> notes,
173             @AdditionalName.NameType @NonNull List<Long> additionalNameTypes,
174             @NonNull List<String> additionalNames,
175             @NonNull List<String> affiliations,
176             @NonNull List<String> relations,
177             @NonNull List<ContactPoint> contactPoints) {
178         super(namespace, id, documentScore, creationTimestampMillis, documentTtlMillis, name,
179                 alternateNames, description, image, url, potentialActions);
180         mGivenName = givenName;
181         mMiddleName = middleName;
182         mFamilyName = familyName;
183         mExternalUri = externalUri;
184         mImageUri = imageUri;
185         mIsImportant = isImportant;
186         mIsBot = isBot;
187         mNotes = Collections.unmodifiableList(notes);
188         mAdditionalNameTypes = Collections.unmodifiableList(additionalNameTypes);
189         mAdditionalNames = Collections.unmodifiableList(additionalNames);
190         mAffiliations = Collections.unmodifiableList(affiliations);
191         mRelations = Collections.unmodifiableList(relations);
192         mContactPoints = Collections.unmodifiableList(contactPoints);
193 
194         // For the additionalNames to to returned in the getter.
195         List<AdditionalName> names = new ArrayList<>(mAdditionalNameTypes.size());
196         for (int i = 0; i < mAdditionalNameTypes.size(); ++i) {
197             names.add(new AdditionalName(mAdditionalNameTypes.get(i).intValue(),
198                     mAdditionalNames.get(i)));
199         }
200         mTypedAdditionalNames = Collections.unmodifiableList(names);
201     }
202 
203     /** Returns the given (or first) name for this {@link Person}. */
getGivenName()204     public @Nullable String getGivenName() {
205         return mGivenName;
206     }
207 
208     /**
209      * Returns the middle name(s) for this {@link Person}.
210      *
211      * <p>For a Person with multiple middle names, this method returns a flattened and whitespace
212      * separated list. For example, "middle1 middle2 ..."
213      */
getMiddleName()214     public @Nullable String getMiddleName() {
215         return mMiddleName;
216     }
217 
218     /** Returns the family (or last) name for this {@link Person}. */
getFamilyName()219     public @Nullable String getFamilyName() {
220         return mFamilyName;
221     }
222 
223     /**
224      * Returns an external uri for this {@link Person}. Or {@code null} if no {@link Uri} is
225      * provided. A {@link Uri} can be any of the following:
226      * <ul>
227      * <li>A {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
228      * <li>A {@code mailto:} schema*
229      * <li>A {@code tel:} schema*
230      * </ul>
231      * <p>For mailto: and tel: URI schemes, it is recommended that the path portion
232      * refers to a valid contact in the Contacts Provider.
233      */
getExternalUri()234     public @Nullable Uri getExternalUri() {
235         if (mExternalUri != null) {
236             return Uri.parse(mExternalUri);
237         }
238         return null;
239     }
240 
241     /** Returns {@link Uri} of the profile image for this {@link Person}. */
getImageUri()242     public @Nullable Uri getImageUri() {
243         if (mImageUri != null) {
244             return Uri.parse(mImageUri);
245         }
246         return null;
247     }
248 
249     /**
250      * Returns whether this {@link Person} is important to the user of this device with
251      * regards to how frequently they interact.
252      */
isImportant()253     public boolean isImportant() {
254         return mIsImportant;
255     }
256 
257     /** Returns whether this {@link Person} is a machine rather than a human. */
isBot()258     public boolean isBot() {
259         return mIsBot;
260     }
261 
262     /** Returns the notes about this {@link Person}. */
getNotes()263     public @NonNull List<String> getNotes() {
264         return mNotes;
265     }
266 
267     /**
268      * Returns a list of additional names for this {@link Person}.
269      *
270      * <p>Additional names can include something like phonetic names, or nicknames.
271      *
272      * <p>Different from {@link #getTypedAdditionalNames()}, the return value doesn't include
273      * type information for the additional names.
274      */
getAdditionalNames()275     public @NonNull List<String> getAdditionalNames() {
276         return mAdditionalNames;
277     }
278 
279     /**
280      * Returns a list of {@link AdditionalName} for this {@link Person}.
281      *
282      * <p>Additional names can include something like phonetic names, or nicknames.
283      *
284      * <p>Each {@link AdditionalName} contains type information for the additional name.
285      */
getTypedAdditionalNames()286     public @NonNull List<AdditionalName> getTypedAdditionalNames() {
287         return mTypedAdditionalNames;
288     }
289 
290     /**
291      * Returns a list of affiliation for this {@link Person}. Like company, school, etc.
292      *
293      * <p>For a contact with the title "Software Engineer" in a department "Engineering" at a
294      * company "Cloud Company", this can include a flattened value of "Software Engineer,
295      * Engineering, Cloud Company".
296      */
getAffiliations()297     public @NonNull List<String> getAffiliations() {
298         return mAffiliations;
299     }
300 
301     /** Returns a list of relations for this {@link Person}, like "Father" or "Mother". */
getRelations()302     public @NonNull List<String> getRelations() {
303         return mRelations;
304     }
305 
306     /**
307      * Returns a list of {@link ContactPoint}.
308      *
309      * <p>More information can be found in {@link ContactPoint}.
310      */
getContactPoints()311     public @NonNull List<ContactPoint> getContactPoints() {
312         return mContactPoints;
313     }
314 
315     /** Builder class for {@link Person}. */
316     public static final class Builder extends BuilderImpl<Builder> {
317         /**
318          * Constructor for {@link Person.Builder}.
319          *
320          * @param namespace Namespace for the {@link Person} Document. See
321          *                  {@link Document.Namespace}.
322          * @param id        Unique identifier for the {@link Person} Document. See
323          *                  {@link Document.Id}.
324          * @param name      The searchable full name of this {@link Person}. E.g. "Larry Page", or
325          *                  "Page, Larry".
326          */
Builder(@onNull String namespace, @NonNull String id, @NonNull String name)327         public Builder(@NonNull String namespace, @NonNull String id, @NonNull String name) {
328             super(namespace, id, name);
329         }
330 
331         /**
332          * Constructor for {@link Builder} with all the existing values of an {@link Person}.
333          */
Builder(@onNull Person person)334         public Builder(@NonNull Person person) {
335             super(person);
336         }
337     }
338 
339     @SuppressWarnings("unchecked")
340     static class BuilderImpl<T extends BuilderImpl<T>> extends Thing.BuilderImpl<T> {
341         private String mGivenName;
342         private String mMiddleName;
343         private String mFamilyName;
344         private Uri mExternalUri;
345         private Uri mImageUri;
346         boolean mIsImportant;
347         boolean mIsBot;
348         // Make sure the lists are not null.
349         private List<String> mNotes = Collections.emptyList();
350         @AdditionalName.NameType
351         private List<Long> mAdditionalNameTypes = Collections.emptyList();
352         private List<String> mAdditionalNames = Collections.emptyList();
353         private List<String> mAffiliations = Collections.emptyList();
354         private List<String> mRelations = Collections.emptyList();
355         private List<ContactPoint> mContactPoints = Collections.emptyList();
356 
BuilderImpl(@onNull String namespace, @NonNull String id, @NonNull String name)357         BuilderImpl(@NonNull String namespace, @NonNull String id, @NonNull String name) {
358             super(namespace, id);
359             mName = Preconditions.checkNotNull(name);
360         }
361 
BuilderImpl(@onNull Person person)362         BuilderImpl(@NonNull Person person) {
363             super(new Thing.Builder(person).build());
364             mDocumentScore = person.getDocumentScore();
365             mCreationTimestampMillis =
366                     person.getCreationTimestampMillis();
367             mDocumentTtlMillis = person.getDocumentTtlMillis();
368             mGivenName = person.getGivenName();
369             mMiddleName = person.getMiddleName();
370             mFamilyName = person.getFamilyName();
371             mExternalUri = person.getExternalUri();
372             mImageUri = person.getImageUri();
373             mIsImportant = person.isImportant();
374             mIsBot = person.isBot();
375             mNotes = person.getNotes();
376             mAffiliations = person.getAffiliations();
377             mRelations = person.getRelations();
378             mContactPoints = person.getContactPoints();
379             setAdditionalNames(person.getTypedAdditionalNames());
380         }
381 
382         /** Sets the given name of this {@link Person}. */
setGivenName(@onNull String givenName)383         public @NonNull T setGivenName(@NonNull String givenName) {
384             mGivenName = Preconditions.checkNotNull(givenName);
385             return (T) this;
386         }
387 
388         /**
389          * Sets the middle name of this {@link Person}.
390          *
391          * <p>For {@link Person} with multiple middle names, they can all be set in this
392          * single string. Each middle name could be separated by a whitespace like "middleName1
393          * middleName2 middleName3".
394          */
setMiddleName(@onNull String middleName)395         public @NonNull T setMiddleName(@NonNull String middleName) {
396             mMiddleName = Preconditions.checkNotNull(middleName);
397             return (T) this;
398         }
399 
400         /** Sets the family name of this {@link Person}. */
setFamilyName(@onNull String familyName)401         public @NonNull T setFamilyName(@NonNull String familyName) {
402             mFamilyName = Preconditions.checkNotNull(familyName);
403             return (T) this;
404         }
405 
406         /**
407          * Sets an external {@link Uri} for this {@link Person}. Or {@code null} if no
408          * {@link Uri} is provided. A {@link Uri} can be any of the following:
409          * <ul>
410          * <li>A {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}.
411          * <li>A {@code mailto:} schema*
412          * <li>A {@code tel:} schema*
413          * </ul>
414          * <p>For mailto: and tel: URI schemes, it is recommended that the path
415          * portion refers to a valid contact in the Contacts Provider.
416          */
setExternalUri(@onNull Uri externalUri)417         public @NonNull T setExternalUri(@NonNull Uri externalUri) {
418             mExternalUri = Preconditions.checkNotNull(externalUri);
419             return (T) this;
420         }
421 
422         /** Sets the {@link Uri} of the profile image for the {@link Person}. */
setImageUri(@onNull Uri imageUri)423         public @NonNull T setImageUri(@NonNull Uri imageUri) {
424             mImageUri = Preconditions.checkNotNull(imageUri);
425             return (T) this;
426         }
427 
428         /** Sets whether this {@link Person} is important. */
setImportant(boolean isImportant)429         public @NonNull T setImportant(boolean isImportant) {
430             mIsImportant = isImportant;
431             return (T) this;
432         }
433 
434         /** Sets whether this {@link Person} is a bot. */
setBot(boolean isBot)435         public @NonNull T setBot(boolean isBot) {
436             mIsBot = isBot;
437             return (T) this;
438         }
439 
440         /** Sets the notes about this {@link Person}. */
setNotes(@onNull List<String> notes)441         public @NonNull T setNotes(@NonNull List<String> notes) {
442             mNotes = Preconditions.checkNotNull(notes);
443             return (T) this;
444         }
445 
446         /**
447          * Sets a list of {@link AdditionalName} for that {@link Person}.
448          *
449          * <p>Only types defined in {@link AdditionalName.NameType} are accepted.
450          */
setAdditionalNames(@onNull List<AdditionalName> additionalNames)451         public @NonNull T setAdditionalNames(@NonNull List<AdditionalName> additionalNames) {
452             Preconditions.checkNotNull(additionalNames);
453             int size = additionalNames.size();
454             mAdditionalNameTypes = new ArrayList<>(size);
455             mAdditionalNames = new ArrayList<>(size);
456             for (int i = 0; i < additionalNames.size(); ++i) {
457                 long type = Preconditions.checkArgumentInRange(additionalNames.get(i).getType(),
458                         AdditionalName.TYPE_UNKNOWN,
459                         AdditionalName.TYPE_PHONETIC_NAME,
460                         "type");
461                 mAdditionalNameTypes.add(type);
462                 mAdditionalNames.add(additionalNames.get(i).getValue());
463             }
464             return (T) this;
465         }
466 
467         /**
468          * Sets a list of affiliations for this {@link Person}. Like company, school,
469          * etc.
470          */
setAffiliations(@onNull List<String> affiliations)471         public @NonNull T setAffiliations(@NonNull List<String> affiliations) {
472             mAffiliations = Preconditions.checkNotNull(affiliations);
473             return (T) this;
474         }
475 
476         /** Sets a list of relations for this {@link Person}, like "Father" or "Mother". */
setRelations(@onNull List<String> relations)477         public @NonNull T setRelations(@NonNull List<String> relations) {
478             mRelations = Preconditions.checkNotNull(relations);
479             return (T) this;
480         }
481 
482         /**
483          * Sets a list of {@link ContactPoint} for the {@link Person}.
484          *
485          * <p>More information could be found in {@link ContactPoint}.
486          */
setContactPoints(@onNull List<ContactPoint> contactPoints)487         public @NonNull T setContactPoints(@NonNull List<ContactPoint> contactPoints) {
488             mContactPoints = Preconditions.checkNotNull(contactPoints);
489             return (T) this;
490         }
491 
492         /** Builds the {@link Person}. */
493         @Override
build()494         public @NonNull Person build() {
495             Preconditions.checkState(mAdditionalNameTypes.size() == mAdditionalNames.size());
496             return new Person(
497                     /*namespace=*/ mNamespace,
498                     /*id=*/ mId,
499                     /*documentScore=*/mDocumentScore,
500                     /*creationTimestampMillis=*/ mCreationTimestampMillis,
501                     /*documentTtlMillis=*/ mDocumentTtlMillis,
502                     /*name=*/ mName,
503                     /*alternateNames=*/ mAlternateNames,
504                     /*description=*/ mDescription,
505                     /*image=*/ mImage,
506                     /*url=*/ mUrl,
507                     /*potentialActions=*/ mPotentialActions,
508                     /*givenName=*/ mGivenName,
509                     /*middleName=*/ mMiddleName,
510                     /*familyName=*/ mFamilyName,
511                     /*externalUri=*/ mExternalUri != null
512                     ? mExternalUri.toString() : null,
513                     /*imageUri=*/ mImageUri != null
514                     ? mImageUri.toString() : null,
515                     /*isImportant=*/ mIsImportant,
516                     /*isBot=*/ mIsBot,
517                     /*notes=*/ new ArrayList<>(mNotes),
518                     /*additionalNameTypes=*/ new ArrayList<>(mAdditionalNameTypes),
519                     /*additionalNames=*/ new ArrayList<>(mAdditionalNames),
520                     /*affiliations=*/ new ArrayList<>(mAffiliations),
521                     /*relations=*/ new ArrayList<>(mRelations),
522                     /*contactPoints=*/ new ArrayList<>(mContactPoints));
523         }
524     }
525 }
526