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