• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 com.android.server.appsearch.contactsindexer.appsearchtypes;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.appsearch.AppSearchSchema;
23 import android.app.appsearch.GenericDocument;
24 import android.net.Uri;
25 
26 import com.android.appsearch.flags.Flags;
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.util.Preconditions;
29 
30 import java.lang.annotation.Retention;
31 import java.lang.annotation.RetentionPolicy;
32 import java.util.ArrayList;
33 import java.util.List;
34 import java.util.Objects;
35 
36 /**
37  * Represents a Person in AppSearch.
38  *
39  * @hide
40  */
41 public class Person extends GenericDocument {
42     public static final String SCHEMA_TYPE = "builtin:Person";
43 
44     /**
45      * The type of the name stored in additionalNames list. We have two parallel lists to store
46      * different names, like nicknames and phonetic names as searchable field in additionalNames.
47      *
48      * <p>Having this type for each name stored in additionalNames, so clients can distinguish the
49      * type of those names in the search result.
50      *
51      * @hide
52      */
53     @IntDef(
54             value = {
55                 TYPE_UNKNOWN,
56                 TYPE_NICKNAME,
57                 TYPE_PHONETIC_NAME,
58             })
59     @Retention(RetentionPolicy.SOURCE)
60     public @interface NameType {}
61 
62     public static final int TYPE_UNKNOWN = 0;
63     public static final int TYPE_NICKNAME = 1;
64     public static final int TYPE_PHONETIC_NAME = 2;
65 
66     // Properties
67     public static final String PERSON_PROPERTY_NAME = "name";
68     public static final String PERSON_PROPERTY_GIVEN_NAME = "givenName";
69     public static final String PERSON_PROPERTY_MIDDLE_NAME = "middleName";
70     public static final String PERSON_PROPERTY_FAMILY_NAME = "familyName";
71     public static final String PERSON_PROPERTY_EXTERNAL_URI = "externalUri";
72     public static final String PERSON_PROPERTY_ADDITIONAL_NAME_TYPES = "additionalNameTypes";
73     public static final String PERSON_PROPERTY_ADDITIONAL_NAMES = "additionalNames";
74     public static final String PERSON_PROPERTY_IS_IMPORTANT = "isImportant";
75     public static final String PERSON_PROPERTY_IS_BOT = "isBot";
76     public static final String PERSON_PROPERTY_IMAGE_URI = "imageUri";
77     public static final String PERSON_PROPERTY_CONTACT_POINTS = "contactPoints";
78     public static final String PERSON_PROPERTY_AFFILIATIONS = "affiliations";
79     public static final String PERSON_PROPERTY_RELATIONS = "relations";
80     public static final String PERSON_PROPERTY_NOTES = "notes";
81     public static final String PERSON_PROPERTY_FINGERPRINT = "fingerprint";
82 
createSchema(boolean indexFirstMiddleAndLastNames)83     private static AppSearchSchema createSchema(boolean indexFirstMiddleAndLastNames) {
84         AppSearchSchema.Builder builder =
85                 new AppSearchSchema.Builder(SCHEMA_TYPE)
86                         // full display name
87                         .addProperty(
88                                 new AppSearchSchema.StringPropertyConfig.Builder(
89                                                 PERSON_PROPERTY_NAME)
90                                         .setCardinality(
91                                                 AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
92                                         .setIndexingType(
93                                                 AppSearchSchema.StringPropertyConfig
94                                                         .INDEXING_TYPE_PREFIXES)
95                                         .setTokenizerType(
96                                                 AppSearchSchema.StringPropertyConfig
97                                                         .TOKENIZER_TYPE_PLAIN)
98                                         .build());
99 
100         if (indexFirstMiddleAndLastNames) {
101             builder
102                     // given name from CP2
103                     .addProperty(
104                             new AppSearchSchema.StringPropertyConfig.Builder(
105                                             PERSON_PROPERTY_GIVEN_NAME)
106                                     .setCardinality(
107                                             AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
108                                     .setIndexingType(
109                                             AppSearchSchema.StringPropertyConfig
110                                                     .INDEXING_TYPE_PREFIXES)
111                                     .setTokenizerType(
112                                             AppSearchSchema.StringPropertyConfig
113                                                     .TOKENIZER_TYPE_PLAIN)
114                                     .build())
115                     // middle name from CP2
116                     .addProperty(
117                             new AppSearchSchema.StringPropertyConfig.Builder(
118                                             PERSON_PROPERTY_MIDDLE_NAME)
119                                     .setCardinality(
120                                             AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
121                                     .setIndexingType(
122                                             AppSearchSchema.StringPropertyConfig
123                                                     .INDEXING_TYPE_PREFIXES)
124                                     .setTokenizerType(
125                                             AppSearchSchema.StringPropertyConfig
126                                                     .TOKENIZER_TYPE_PLAIN)
127                                     .build())
128                     // family name from CP2
129                     .addProperty(
130                             new AppSearchSchema.StringPropertyConfig.Builder(
131                                             PERSON_PROPERTY_FAMILY_NAME)
132                                     .setCardinality(
133                                             AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
134                                     .setIndexingType(
135                                             AppSearchSchema.StringPropertyConfig
136                                                     .INDEXING_TYPE_PREFIXES)
137                                     .setTokenizerType(
138                                             AppSearchSchema.StringPropertyConfig
139                                                     .TOKENIZER_TYPE_PLAIN)
140                                     .build());
141         } else {
142             builder
143                     // given name from CP2
144                     .addProperty(
145                             new AppSearchSchema.StringPropertyConfig.Builder(
146                                             PERSON_PROPERTY_GIVEN_NAME)
147                                     .setCardinality(
148                                             AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
149                                     .build())
150                     // middle name from CP2
151                     .addProperty(
152                             new AppSearchSchema.StringPropertyConfig.Builder(
153                                             PERSON_PROPERTY_MIDDLE_NAME)
154                                     .setCardinality(
155                                             AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
156                                     .build())
157                     // family name from CP2
158                     .addProperty(
159                             new AppSearchSchema.StringPropertyConfig.Builder(
160                                             PERSON_PROPERTY_FAMILY_NAME)
161                                     .setCardinality(
162                                             AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
163                                     .build());
164         }
165 
166         builder
167                 // lookup uri from CP2
168                 .addProperty(
169                         new AppSearchSchema.StringPropertyConfig.Builder(
170                                         PERSON_PROPERTY_EXTERNAL_URI)
171                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
172                                 .build())
173                 // corresponding name types for the names stored in additional names below.
174                 .addProperty(
175                         new AppSearchSchema.LongPropertyConfig.Builder(
176                                         PERSON_PROPERTY_ADDITIONAL_NAME_TYPES)
177                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
178                                 .build())
179                 // additional names e.g. nick names and phonetic names.
180                 .addProperty(
181                         new AppSearchSchema.StringPropertyConfig.Builder(
182                                         PERSON_PROPERTY_ADDITIONAL_NAMES)
183                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
184                                 .setIndexingType(
185                                         AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
186                                 .setTokenizerType(
187                                         AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
188                                 .build())
189                 // isImportant. It could be used to store isStarred from CP2.
190                 .addProperty(
191                         new AppSearchSchema.BooleanPropertyConfig.Builder(
192                                         PERSON_PROPERTY_IS_IMPORTANT)
193                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
194                                 .build())
195                 // isBot
196                 .addProperty(
197                         new AppSearchSchema.BooleanPropertyConfig.Builder(PERSON_PROPERTY_IS_BOT)
198                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
199                                 .build())
200                 // imageUri
201                 .addProperty(
202                         new AppSearchSchema.StringPropertyConfig.Builder(PERSON_PROPERTY_IMAGE_URI)
203                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
204                                 .build())
205                 // ContactPoint
206                 .addProperty(
207                         new AppSearchSchema.DocumentPropertyConfig.Builder(
208                                         PERSON_PROPERTY_CONTACT_POINTS,
209                                         ContactPoint.SCHEMA.getSchemaType())
210                                 .setShouldIndexNestedProperties(true)
211                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
212                                 .build())
213                 // Affiliations
214                 .addProperty(
215                         new AppSearchSchema.StringPropertyConfig.Builder(
216                                         PERSON_PROPERTY_AFFILIATIONS)
217                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
218                                 .setIndexingType(
219                                         AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
220                                 .setTokenizerType(
221                                         AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
222                                 .build())
223                 // Relations
224                 .addProperty(
225                         new AppSearchSchema.StringPropertyConfig.Builder(PERSON_PROPERTY_RELATIONS)
226                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
227                                 .build())
228                 // Notes
229                 .addProperty(
230                         new AppSearchSchema.StringPropertyConfig.Builder(PERSON_PROPERTY_NOTES)
231                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
232                                 .setIndexingType(
233                                         AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
234                                 .setTokenizerType(
235                                         AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
236                                 .build())
237                 //
238                 // Following fields are internal to ContactsIndexer.
239                 //
240                 // Fingerprint for detecting significant changes
241                 .addProperty(
242                         new AppSearchSchema.StringPropertyConfig.Builder(
243                                         PERSON_PROPERTY_FINGERPRINT)
244                                 .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
245                                 .build());
246         return builder.build();
247     }
248 
249     /** Returns Person schema based on {@link Flags#enableContactsIndexFirstMiddleAndLastNames}. */
getSchema()250     public static AppSearchSchema getSchema() {
251         return createSchema(Flags.enableContactsIndexFirstMiddleAndLastNames());
252     }
253 
254     /** Constructs a {@link Person}. */
255     @VisibleForTesting
Person(@onNull GenericDocument document)256     public Person(@NonNull GenericDocument document) {
257         super(document);
258     }
259 
260     @NonNull
getName()261     public String getName() {
262         return getPropertyString(PERSON_PROPERTY_NAME);
263     }
264 
265     @Nullable
getGivenName()266     public String getGivenName() {
267         return getPropertyString(PERSON_PROPERTY_GIVEN_NAME);
268     }
269 
270     @Nullable
getMiddleName()271     public String getMiddleName() {
272         return getPropertyString(PERSON_PROPERTY_MIDDLE_NAME);
273     }
274 
275     @Nullable
getFamilyName()276     public String getFamilyName() {
277         return getPropertyString(PERSON_PROPERTY_FAMILY_NAME);
278     }
279 
280     @Nullable
getExternalUri()281     public Uri getExternalUri() {
282         String uriStr = getPropertyString(PERSON_PROPERTY_EXTERNAL_URI);
283         if (uriStr == null) {
284             return null;
285         }
286         return Uri.parse(uriStr);
287     }
288 
289     @Nullable
getImageUri()290     public Uri getImageUri() {
291         String uriStr = getPropertyString(PERSON_PROPERTY_IMAGE_URI);
292         if (uriStr == null) {
293             return null;
294         }
295         return Uri.parse(uriStr);
296     }
297 
isImportant()298     public boolean isImportant() {
299         return getPropertyBoolean(PERSON_PROPERTY_IS_IMPORTANT);
300     }
301 
isBot()302     public boolean isBot() {
303         return getPropertyBoolean(PERSON_PROPERTY_IS_BOT);
304     }
305 
306     @NonNull
307     @NameType
getAdditionalNameTypes()308     public long[] getAdditionalNameTypes() {
309         return getPropertyLongArray(PERSON_PROPERTY_ADDITIONAL_NAME_TYPES);
310     }
311 
312     @NonNull
getAdditionalNames()313     public String[] getAdditionalNames() {
314         return getPropertyStringArray(PERSON_PROPERTY_ADDITIONAL_NAMES);
315     }
316 
317     @NonNull
getAffiliations()318     public String[] getAffiliations() {
319         return getPropertyStringArray(PERSON_PROPERTY_AFFILIATIONS);
320     }
321 
322     @NonNull
getRelations()323     public String[] getRelations() {
324         return getPropertyStringArray(PERSON_PROPERTY_RELATIONS);
325     }
326 
327     @Nullable
getNotes()328     public String[] getNotes() {
329         return getPropertyStringArray(PERSON_PROPERTY_NOTES);
330     }
331 
332     // This method is expensive, and is intended to be used in tests only.
333     @NonNull
getContactPoints()334     public ContactPoint[] getContactPoints() {
335         GenericDocument[] docs = getPropertyDocumentArray(PERSON_PROPERTY_CONTACT_POINTS);
336         ContactPoint[] contactPoints = new ContactPoint[docs.length];
337         for (int i = 0; i < contactPoints.length; ++i) {
338             contactPoints[i] = new ContactPoint(docs[i]);
339         }
340         return contactPoints;
341     }
342 
343     /** Gets a byte array for the fingerprint. */
344     @NonNull
getFingerprint()345     public byte[] getFingerprint() {
346         return getPropertyBytes(PERSON_PROPERTY_FINGERPRINT);
347     }
348 
349     /** Builder for {@link Person}. */
350     public static final class Builder extends GenericDocument.Builder<Builder> {
351         @NameType private final List<Long> mAdditionalNameTypes = new ArrayList<>();
352         private final List<String> mAdditionalNames = new ArrayList<>();
353         private final List<String> mAffiliations = new ArrayList<>();
354         private final List<String> mRelations = new ArrayList<>();
355         private final List<String> mNotes = new ArrayList<>();
356         private final List<ContactPoint> mContactPoints = new ArrayList<>();
357 
358         /**
359          * Creates a new {@link ContactPoint.Builder}
360          *
361          * @param namespace The namespace of the Email.
362          * @param id The ID of the Email.
363          * @param name The name of the {@link Person}.
364          */
Builder(@onNull String namespace, @NonNull String id, @NonNull String name)365         public Builder(@NonNull String namespace, @NonNull String id, @NonNull String name) {
366             super(namespace, id, SCHEMA_TYPE);
367             setName(name);
368         }
369 
370         /** Sets the full display name. */
371         @NonNull
setName(@onNull String name)372         private Builder setName(@NonNull String name) {
373             setPropertyString(PERSON_PROPERTY_NAME, name);
374             return this;
375         }
376 
377         @NonNull
setGivenName(@onNull String givenName)378         public Builder setGivenName(@NonNull String givenName) {
379             setPropertyString(PERSON_PROPERTY_GIVEN_NAME, givenName);
380             return this;
381         }
382 
383         @NonNull
setMiddleName(@onNull String middleName)384         public Builder setMiddleName(@NonNull String middleName) {
385             setPropertyString(PERSON_PROPERTY_MIDDLE_NAME, middleName);
386             return this;
387         }
388 
389         @NonNull
setFamilyName(@onNull String familyName)390         public Builder setFamilyName(@NonNull String familyName) {
391             setPropertyString(PERSON_PROPERTY_FAMILY_NAME, familyName);
392             return this;
393         }
394 
395         @NonNull
setExternalUri(@onNull Uri externalUri)396         public Builder setExternalUri(@NonNull Uri externalUri) {
397             setPropertyString(
398                     PERSON_PROPERTY_EXTERNAL_URI, Objects.requireNonNull(externalUri).toString());
399             return this;
400         }
401 
402         @NonNull
setImageUri(@onNull Uri imageUri)403         public Builder setImageUri(@NonNull Uri imageUri) {
404             setPropertyString(
405                     PERSON_PROPERTY_IMAGE_URI, Objects.requireNonNull(imageUri).toString());
406             return this;
407         }
408 
409         @NonNull
setIsImportant(boolean isImportant)410         public Builder setIsImportant(boolean isImportant) {
411             setPropertyBoolean(PERSON_PROPERTY_IS_IMPORTANT, isImportant);
412             return this;
413         }
414 
415         @NonNull
setIsBot(boolean isBot)416         public Builder setIsBot(boolean isBot) {
417             setPropertyBoolean(PERSON_PROPERTY_IS_BOT, isBot);
418             return this;
419         }
420 
421         @NonNull
addAdditionalName(@ameType long nameType, @NonNull String name)422         public Builder addAdditionalName(@NameType long nameType, @NonNull String name) {
423             mAdditionalNameTypes.add(nameType);
424             mAdditionalNames.add(Objects.requireNonNull(name));
425             return this;
426         }
427 
428         /**
429          * Adds an affiliation for the {@link Person}, like a company name as an employee, or a
430          * university name as a student.
431          */
432         @NonNull
addAffiliation(@onNull String affiliation)433         public Builder addAffiliation(@NonNull String affiliation) {
434             mAffiliations.add(Objects.requireNonNull(affiliation));
435             return this;
436         }
437 
438         /** Adds a relation to this {@link Person}. Like "spouse", "father", etc. */
439         @NonNull
addRelation(@onNull String relation)440         public Builder addRelation(@NonNull String relation) {
441             mRelations.add(Objects.requireNonNull(relation));
442             return this;
443         }
444 
445         /** Adds a note about this {@link Person}. */
446         @NonNull
addNote(@onNull String note)447         public Builder addNote(@NonNull String note) {
448             mNotes.add(Objects.requireNonNull(note));
449             return this;
450         }
451 
452         @NonNull
addContactPoint(@onNull ContactPoint contactPoint)453         public Builder addContactPoint(@NonNull ContactPoint contactPoint) {
454             Objects.requireNonNull(contactPoint);
455             mContactPoints.add(contactPoint);
456             return this;
457         }
458 
459         /**
460          * Sets the fingerprint for this {@link Person}
461          *
462          * @param fingerprint byte array for the fingerprint. The size depends on the algorithm
463          *     being used. Right now we are using md5 and generating a 16-byte fingerprint.
464          */
465         @NonNull
setFingerprint(@onNull byte[] fingerprint)466         public Builder setFingerprint(@NonNull byte[] fingerprint) {
467             setPropertyBytes(PERSON_PROPERTY_FINGERPRINT, Objects.requireNonNull(fingerprint));
468             return this;
469         }
470 
471         @NonNull
build()472         public Person build() {
473             Preconditions.checkState(mAdditionalNameTypes.size() == mAdditionalNames.size());
474             long[] primitiveNameTypes = new long[mAdditionalNameTypes.size()];
475             for (int i = 0; i < mAdditionalNameTypes.size(); i++) {
476                 primitiveNameTypes[i] = mAdditionalNameTypes.get(i).longValue();
477             }
478             setPropertyLong(PERSON_PROPERTY_ADDITIONAL_NAME_TYPES, primitiveNameTypes);
479             setPropertyString(
480                     PERSON_PROPERTY_ADDITIONAL_NAMES, mAdditionalNames.toArray(new String[0]));
481             setPropertyString(PERSON_PROPERTY_AFFILIATIONS, mAffiliations.toArray(new String[0]));
482             setPropertyString(PERSON_PROPERTY_RELATIONS, mRelations.toArray(new String[0]));
483             setPropertyString(PERSON_PROPERTY_NOTES, mNotes.toArray(new String[0]));
484             setPropertyDocument(
485                     PERSON_PROPERTY_CONTACT_POINTS, mContactPoints.toArray(new ContactPoint[0]));
486             // TODO(b/203605504) calculate score here.
487             return new Person(super.build());
488         }
489     }
490 }
491