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