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