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 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 /*** 250 * Returns Person schema based on current value of flag 251 * 'contacts_index_first_middle_and_last_names'. If the flag value changes after the initial 252 * schema fetch, the schema returned will be different than the original schema that was set 253 * for the Person corpus. 254 */ getSchema(ContactsIndexerConfig config)255 public static AppSearchSchema getSchema(ContactsIndexerConfig config) { 256 return createSchema(config.shouldIndexFirstMiddleAndLastNames()); 257 } 258 259 /** Constructs a {@link Person}. */ 260 @VisibleForTesting Person(@onNull GenericDocument document)261 public Person(@NonNull GenericDocument document) { 262 super(document); 263 } 264 265 @NonNull getName()266 public String getName() { 267 return getPropertyString(PERSON_PROPERTY_NAME); 268 } 269 270 @Nullable getGivenName()271 public String getGivenName() { 272 return getPropertyString(PERSON_PROPERTY_GIVEN_NAME); 273 } 274 275 @Nullable getMiddleName()276 public String getMiddleName() { 277 return getPropertyString(PERSON_PROPERTY_MIDDLE_NAME); 278 } 279 280 @Nullable getFamilyName()281 public String getFamilyName() { 282 return getPropertyString(PERSON_PROPERTY_FAMILY_NAME); 283 } 284 285 @Nullable getExternalUri()286 public Uri getExternalUri() { 287 String uriStr = getPropertyString(PERSON_PROPERTY_EXTERNAL_URI); 288 if (uriStr == null) { 289 return null; 290 } 291 return Uri.parse(uriStr); 292 } 293 294 @Nullable getImageUri()295 public Uri getImageUri() { 296 String uriStr = getPropertyString(PERSON_PROPERTY_IMAGE_URI); 297 if (uriStr == null) { 298 return null; 299 } 300 return Uri.parse(uriStr); 301 } 302 isImportant()303 public boolean isImportant() { 304 return getPropertyBoolean(PERSON_PROPERTY_IS_IMPORTANT); 305 } 306 isBot()307 public boolean isBot() { 308 return getPropertyBoolean(PERSON_PROPERTY_IS_BOT); 309 } 310 311 @NonNull 312 @NameType getAdditionalNameTypes()313 public long[] getAdditionalNameTypes() { 314 return getPropertyLongArray(PERSON_PROPERTY_ADDITIONAL_NAME_TYPES); 315 } 316 317 @NonNull getAdditionalNames()318 public String[] getAdditionalNames() { 319 return getPropertyStringArray(PERSON_PROPERTY_ADDITIONAL_NAMES); 320 } 321 322 @NonNull getAffiliations()323 public String[] getAffiliations() { 324 return getPropertyStringArray(PERSON_PROPERTY_AFFILIATIONS); 325 } 326 327 @NonNull getRelations()328 public String[] getRelations() { 329 return getPropertyStringArray(PERSON_PROPERTY_RELATIONS); 330 } 331 332 @Nullable getNotes()333 public String[] getNotes() { 334 return getPropertyStringArray(PERSON_PROPERTY_NOTES); 335 } 336 337 // This method is expensive, and is intended to be used in tests only. 338 @NonNull getContactPoints()339 public ContactPoint[] getContactPoints() { 340 GenericDocument[] docs = getPropertyDocumentArray(PERSON_PROPERTY_CONTACT_POINTS); 341 ContactPoint[] contactPoints = new ContactPoint[docs.length]; 342 for (int i = 0; i < contactPoints.length; ++i) { 343 contactPoints[i] = new ContactPoint(docs[i]); 344 } 345 return contactPoints; 346 } 347 348 /** Gets a byte array for the fingerprint. */ 349 @NonNull getFingerprint()350 public byte[] getFingerprint() { 351 return getPropertyBytes(PERSON_PROPERTY_FINGERPRINT); 352 } 353 354 /** Builder for {@link Person}. */ 355 public static final class Builder extends GenericDocument.Builder<Builder> { 356 @NameType private final List<Long> mAdditionalNameTypes = new ArrayList<>(); 357 private final List<String> mAdditionalNames = new ArrayList<>(); 358 private final List<String> mAffiliations = new ArrayList<>(); 359 private final List<String> mRelations = new ArrayList<>(); 360 private final List<String> mNotes = new ArrayList<>(); 361 private final List<ContactPoint> mContactPoints = new ArrayList<>(); 362 363 /** 364 * Creates a new {@link ContactPoint.Builder} 365 * 366 * @param namespace The namespace of the Email. 367 * @param id The ID of the Email. 368 * @param name The name of the {@link Person}. 369 */ Builder(@onNull String namespace, @NonNull String id, @NonNull String name)370 public Builder(@NonNull String namespace, @NonNull String id, @NonNull String name) { 371 super(namespace, id, SCHEMA_TYPE); 372 setName(name); 373 } 374 375 /** Sets the full display name. */ 376 @NonNull setName(@onNull String name)377 private Builder setName(@NonNull String name) { 378 setPropertyString(PERSON_PROPERTY_NAME, name); 379 return this; 380 } 381 382 @NonNull setGivenName(@onNull String givenName)383 public Builder setGivenName(@NonNull String givenName) { 384 setPropertyString(PERSON_PROPERTY_GIVEN_NAME, givenName); 385 return this; 386 } 387 388 @NonNull setMiddleName(@onNull String middleName)389 public Builder setMiddleName(@NonNull String middleName) { 390 setPropertyString(PERSON_PROPERTY_MIDDLE_NAME, middleName); 391 return this; 392 } 393 394 @NonNull setFamilyName(@onNull String familyName)395 public Builder setFamilyName(@NonNull String familyName) { 396 setPropertyString(PERSON_PROPERTY_FAMILY_NAME, familyName); 397 return this; 398 } 399 400 @NonNull setExternalUri(@onNull Uri externalUri)401 public Builder setExternalUri(@NonNull Uri externalUri) { 402 setPropertyString( 403 PERSON_PROPERTY_EXTERNAL_URI, Objects.requireNonNull(externalUri).toString()); 404 return this; 405 } 406 407 @NonNull setImageUri(@onNull Uri imageUri)408 public Builder setImageUri(@NonNull Uri imageUri) { 409 setPropertyString( 410 PERSON_PROPERTY_IMAGE_URI, Objects.requireNonNull(imageUri).toString()); 411 return this; 412 } 413 414 @NonNull setIsImportant(boolean isImportant)415 public Builder setIsImportant(boolean isImportant) { 416 setPropertyBoolean(PERSON_PROPERTY_IS_IMPORTANT, isImportant); 417 return this; 418 } 419 420 @NonNull setIsBot(boolean isBot)421 public Builder setIsBot(boolean isBot) { 422 setPropertyBoolean(PERSON_PROPERTY_IS_BOT, isBot); 423 return this; 424 } 425 426 @NonNull addAdditionalName(@ameType long nameType, @NonNull String name)427 public Builder addAdditionalName(@NameType long nameType, @NonNull String name) { 428 mAdditionalNameTypes.add(nameType); 429 mAdditionalNames.add(Objects.requireNonNull(name)); 430 return this; 431 } 432 433 /** 434 * Adds an affiliation for the {@link Person}, like a company name as an employee, or a 435 * university name as a student. 436 */ 437 @NonNull addAffiliation(@onNull String affiliation)438 public Builder addAffiliation(@NonNull String affiliation) { 439 mAffiliations.add(Objects.requireNonNull(affiliation)); 440 return this; 441 } 442 443 /** Adds a relation to this {@link Person}. Like "spouse", "father", etc. */ 444 @NonNull addRelation(@onNull String relation)445 public Builder addRelation(@NonNull String relation) { 446 mRelations.add(Objects.requireNonNull(relation)); 447 return this; 448 } 449 450 /** Adds a note about this {@link Person}. */ 451 @NonNull addNote(@onNull String note)452 public Builder addNote(@NonNull String note) { 453 mNotes.add(Objects.requireNonNull(note)); 454 return this; 455 } 456 457 @NonNull addContactPoint(@onNull ContactPoint contactPoint)458 public Builder addContactPoint(@NonNull ContactPoint contactPoint) { 459 Objects.requireNonNull(contactPoint); 460 mContactPoints.add(contactPoint); 461 return this; 462 } 463 464 /** 465 * Sets the fingerprint for this {@link Person} 466 * 467 * @param fingerprint byte array for the fingerprint. The size depends on the algorithm 468 * being used. Right now we are using md5 and generating a 16-byte fingerprint. 469 */ 470 @NonNull setFingerprint(@onNull byte[] fingerprint)471 public Builder setFingerprint(@NonNull byte[] fingerprint) { 472 setPropertyBytes(PERSON_PROPERTY_FINGERPRINT, Objects.requireNonNull(fingerprint)); 473 return this; 474 } 475 476 @NonNull build()477 public Person build() { 478 Preconditions.checkState(mAdditionalNameTypes.size() == mAdditionalNames.size()); 479 long[] primitiveNameTypes = new long[mAdditionalNameTypes.size()]; 480 for (int i = 0; i < mAdditionalNameTypes.size(); i++) { 481 primitiveNameTypes[i] = mAdditionalNameTypes.get(i).longValue(); 482 } 483 setPropertyLong(PERSON_PROPERTY_ADDITIONAL_NAME_TYPES, primitiveNameTypes); 484 setPropertyString( 485 PERSON_PROPERTY_ADDITIONAL_NAMES, mAdditionalNames.toArray(new String[0])); 486 setPropertyString(PERSON_PROPERTY_AFFILIATIONS, mAffiliations.toArray(new String[0])); 487 setPropertyString(PERSON_PROPERTY_RELATIONS, mRelations.toArray(new String[0])); 488 setPropertyString(PERSON_PROPERTY_NOTES, mNotes.toArray(new String[0])); 489 setPropertyDocument( 490 PERSON_PROPERTY_CONTACT_POINTS, mContactPoints.toArray(new ContactPoint[0])); 491 // TODO(b/203605504) calculate score here. 492 return new Person(super.build()); 493 } 494 } 495 } 496