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