1 /* 2 * Copyright 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 androidx.appsearch.builtintypes; 18 19 import android.net.Uri; 20 21 import androidx.annotation.IntDef; 22 import androidx.annotation.OptIn; 23 import androidx.annotation.RestrictTo; 24 import androidx.appsearch.annotation.Document; 25 import androidx.appsearch.app.AppSearchSchema.StringPropertyConfig; 26 import androidx.appsearch.app.ExperimentalAppSearchApi; 27 import androidx.core.util.Preconditions; 28 29 import org.jspecify.annotations.NonNull; 30 import org.jspecify.annotations.Nullable; 31 32 import java.lang.annotation.Retention; 33 import java.lang.annotation.RetentionPolicy; 34 import java.util.ArrayList; 35 import java.util.Collections; 36 import java.util.List; 37 38 /** 39 * AppSearch document representing a Person entity modeled after 40 * <a href="http://schema.org/Person">Person</a>. 41 * 42 * <p>The {@link Person} document includes commonly searchable properties such as name, 43 * organization, and notes, as well as contact information such as phone numbers, email 44 * addresses, etc, grouped by their label. The labeled contact information is present in a nested 45 * {@link ContactPoint} document. 46 */ 47 @Document(name = "builtin:Person") 48 public class Person extends Thing { 49 /** Holds type information for additional names for Person. */ 50 public static class AdditionalName { 51 /** @exportToFramework:hide */ 52 @RestrictTo(RestrictTo.Scope.LIBRARY) 53 @IntDef({ 54 TYPE_UNKNOWN, 55 TYPE_NICKNAME, 56 TYPE_PHONETIC_NAME 57 }) 58 @Retention(RetentionPolicy.SOURCE) 59 public @interface NameType { 60 } 61 62 /** The additional name is unknown. */ 63 public static final int TYPE_UNKNOWN = 0; 64 /** The additional name is a nickname. */ 65 public static final int TYPE_NICKNAME = 1; 66 /** The additional name is a phonetic name. */ 67 public static final int TYPE_PHONETIC_NAME = 2; 68 69 @NameType 70 private final int mType; 71 private final String mValue; 72 73 /** 74 * Constructs an {@link AdditionalName}. 75 */ AdditionalName(@ameType int type, @NonNull String value)76 public AdditionalName(@NameType int type, 77 @NonNull String value) { 78 mType = Preconditions.checkArgumentInRange(type, TYPE_UNKNOWN, TYPE_PHONETIC_NAME, 79 "type"); 80 mValue = value; 81 } 82 83 @NameType getType()84 public int getType() { 85 return mType; 86 } 87 getValue()88 public @NonNull String getValue() { 89 return mValue; 90 } 91 92 @Override equals(@ullable Object other)93 public boolean equals(@Nullable Object other) { 94 if (this == other) { 95 return true; 96 } 97 if (!(other instanceof AdditionalName)) { 98 return false; 99 } 100 101 return mType == ((AdditionalName) other).mType && mValue.equals( 102 ((AdditionalName) other).mValue); 103 } 104 105 @Override hashCode()106 public int hashCode() { 107 String str = mType + mValue; 108 return str.hashCode(); 109 } 110 } 111 112 @Document.StringProperty 113 private final String mGivenName; 114 115 @Document.StringProperty 116 private final String mMiddleName; 117 118 @Document.StringProperty 119 private final String mFamilyName; 120 121 @Document.StringProperty 122 final String mExternalUri; 123 124 @Document.StringProperty 125 final String mImageUri; 126 127 @Document.BooleanProperty 128 final boolean mIsImportant; 129 130 @Document.BooleanProperty 131 final boolean mIsBot; 132 133 @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES) 134 private final List<String> mNotes; 135 136 @Document.LongProperty 137 final List<Long> mAdditionalNameTypes; 138 139 @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES) 140 final List<String> mAdditionalNames; 141 142 @Document.StringProperty(indexingType = StringPropertyConfig.INDEXING_TYPE_PREFIXES) 143 private final List<String> mAffiliations; 144 145 @Document.StringProperty 146 private final List<String> mRelations; 147 148 @Document.DocumentProperty(indexNestedProperties = true) 149 private final List<ContactPoint> mContactPoints; 150 151 private final List<AdditionalName> mTypedAdditionalNames; 152 153 @OptIn(markerClass = ExperimentalAppSearchApi.class) Person(@onNull String namespace, @NonNull String id, int documentScore, long creationTimestampMillis, long documentTtlMillis, @Nullable String name, @Nullable List<String> alternateNames, @Nullable String description, @Nullable String image, @Nullable String url, @NonNull List<PotentialAction> potentialActions, @Nullable String givenName, @Nullable String middleName, @Nullable String familyName, @Nullable String externalUri, @Nullable String imageUri, boolean isImportant, boolean isBot, @NonNull List<String> notes, @AdditionalName.NameType @NonNull List<Long> additionalNameTypes, @NonNull List<String> additionalNames, @NonNull List<String> affiliations, @NonNull List<String> relations, @NonNull List<ContactPoint> contactPoints)154 Person(@NonNull String namespace, 155 @NonNull String id, 156 int documentScore, 157 long creationTimestampMillis, 158 long documentTtlMillis, 159 @Nullable String name, 160 @Nullable List<String> alternateNames, 161 @Nullable String description, 162 @Nullable String image, 163 @Nullable String url, 164 @NonNull List<PotentialAction> potentialActions, 165 @Nullable String givenName, 166 @Nullable String middleName, 167 @Nullable String familyName, 168 @Nullable String externalUri, 169 @Nullable String imageUri, 170 boolean isImportant, 171 boolean isBot, 172 @NonNull List<String> notes, 173 @AdditionalName.NameType @NonNull List<Long> additionalNameTypes, 174 @NonNull List<String> additionalNames, 175 @NonNull List<String> affiliations, 176 @NonNull List<String> relations, 177 @NonNull List<ContactPoint> contactPoints) { 178 super(namespace, id, documentScore, creationTimestampMillis, documentTtlMillis, name, 179 alternateNames, description, image, url, potentialActions); 180 mGivenName = givenName; 181 mMiddleName = middleName; 182 mFamilyName = familyName; 183 mExternalUri = externalUri; 184 mImageUri = imageUri; 185 mIsImportant = isImportant; 186 mIsBot = isBot; 187 mNotes = Collections.unmodifiableList(notes); 188 mAdditionalNameTypes = Collections.unmodifiableList(additionalNameTypes); 189 mAdditionalNames = Collections.unmodifiableList(additionalNames); 190 mAffiliations = Collections.unmodifiableList(affiliations); 191 mRelations = Collections.unmodifiableList(relations); 192 mContactPoints = Collections.unmodifiableList(contactPoints); 193 194 // For the additionalNames to to returned in the getter. 195 List<AdditionalName> names = new ArrayList<>(mAdditionalNameTypes.size()); 196 for (int i = 0; i < mAdditionalNameTypes.size(); ++i) { 197 names.add(new AdditionalName(mAdditionalNameTypes.get(i).intValue(), 198 mAdditionalNames.get(i))); 199 } 200 mTypedAdditionalNames = Collections.unmodifiableList(names); 201 } 202 203 /** Returns the given (or first) name for this {@link Person}. */ getGivenName()204 public @Nullable String getGivenName() { 205 return mGivenName; 206 } 207 208 /** 209 * Returns the middle name(s) for this {@link Person}. 210 * 211 * <p>For a Person with multiple middle names, this method returns a flattened and whitespace 212 * separated list. For example, "middle1 middle2 ..." 213 */ getMiddleName()214 public @Nullable String getMiddleName() { 215 return mMiddleName; 216 } 217 218 /** Returns the family (or last) name for this {@link Person}. */ getFamilyName()219 public @Nullable String getFamilyName() { 220 return mFamilyName; 221 } 222 223 /** 224 * Returns an external uri for this {@link Person}. Or {@code null} if no {@link Uri} is 225 * provided. A {@link Uri} can be any of the following: 226 * <ul> 227 * <li>A {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 228 * <li>A {@code mailto:} schema* 229 * <li>A {@code tel:} schema* 230 * </ul> 231 * <p>For mailto: and tel: URI schemes, it is recommended that the path portion 232 * refers to a valid contact in the Contacts Provider. 233 */ getExternalUri()234 public @Nullable Uri getExternalUri() { 235 if (mExternalUri != null) { 236 return Uri.parse(mExternalUri); 237 } 238 return null; 239 } 240 241 /** Returns {@link Uri} of the profile image for this {@link Person}. */ getImageUri()242 public @Nullable Uri getImageUri() { 243 if (mImageUri != null) { 244 return Uri.parse(mImageUri); 245 } 246 return null; 247 } 248 249 /** 250 * Returns whether this {@link Person} is important to the user of this device with 251 * regards to how frequently they interact. 252 */ isImportant()253 public boolean isImportant() { 254 return mIsImportant; 255 } 256 257 /** Returns whether this {@link Person} is a machine rather than a human. */ isBot()258 public boolean isBot() { 259 return mIsBot; 260 } 261 262 /** Returns the notes about this {@link Person}. */ getNotes()263 public @NonNull List<String> getNotes() { 264 return mNotes; 265 } 266 267 /** 268 * Returns a list of additional names for this {@link Person}. 269 * 270 * <p>Additional names can include something like phonetic names, or nicknames. 271 * 272 * <p>Different from {@link #getTypedAdditionalNames()}, the return value doesn't include 273 * type information for the additional names. 274 */ getAdditionalNames()275 public @NonNull List<String> getAdditionalNames() { 276 return mAdditionalNames; 277 } 278 279 /** 280 * Returns a list of {@link AdditionalName} for this {@link Person}. 281 * 282 * <p>Additional names can include something like phonetic names, or nicknames. 283 * 284 * <p>Each {@link AdditionalName} contains type information for the additional name. 285 */ getTypedAdditionalNames()286 public @NonNull List<AdditionalName> getTypedAdditionalNames() { 287 return mTypedAdditionalNames; 288 } 289 290 /** 291 * Returns a list of affiliation for this {@link Person}. Like company, school, etc. 292 * 293 * <p>For a contact with the title "Software Engineer" in a department "Engineering" at a 294 * company "Cloud Company", this can include a flattened value of "Software Engineer, 295 * Engineering, Cloud Company". 296 */ getAffiliations()297 public @NonNull List<String> getAffiliations() { 298 return mAffiliations; 299 } 300 301 /** Returns a list of relations for this {@link Person}, like "Father" or "Mother". */ getRelations()302 public @NonNull List<String> getRelations() { 303 return mRelations; 304 } 305 306 /** 307 * Returns a list of {@link ContactPoint}. 308 * 309 * <p>More information can be found in {@link ContactPoint}. 310 */ getContactPoints()311 public @NonNull List<ContactPoint> getContactPoints() { 312 return mContactPoints; 313 } 314 315 /** Builder class for {@link Person}. */ 316 public static final class Builder extends BuilderImpl<Builder> { 317 /** 318 * Constructor for {@link Person.Builder}. 319 * 320 * @param namespace Namespace for the {@link Person} Document. See 321 * {@link Document.Namespace}. 322 * @param id Unique identifier for the {@link Person} Document. See 323 * {@link Document.Id}. 324 * @param name The searchable full name of this {@link Person}. E.g. "Larry Page", or 325 * "Page, Larry". 326 */ Builder(@onNull String namespace, @NonNull String id, @NonNull String name)327 public Builder(@NonNull String namespace, @NonNull String id, @NonNull String name) { 328 super(namespace, id, name); 329 } 330 331 /** 332 * Constructor for {@link Builder} with all the existing values of an {@link Person}. 333 */ Builder(@onNull Person person)334 public Builder(@NonNull Person person) { 335 super(person); 336 } 337 } 338 339 @SuppressWarnings("unchecked") 340 static class BuilderImpl<T extends BuilderImpl<T>> extends Thing.BuilderImpl<T> { 341 private String mGivenName; 342 private String mMiddleName; 343 private String mFamilyName; 344 private Uri mExternalUri; 345 private Uri mImageUri; 346 boolean mIsImportant; 347 boolean mIsBot; 348 // Make sure the lists are not null. 349 private List<String> mNotes = Collections.emptyList(); 350 @AdditionalName.NameType 351 private List<Long> mAdditionalNameTypes = Collections.emptyList(); 352 private List<String> mAdditionalNames = Collections.emptyList(); 353 private List<String> mAffiliations = Collections.emptyList(); 354 private List<String> mRelations = Collections.emptyList(); 355 private List<ContactPoint> mContactPoints = Collections.emptyList(); 356 BuilderImpl(@onNull String namespace, @NonNull String id, @NonNull String name)357 BuilderImpl(@NonNull String namespace, @NonNull String id, @NonNull String name) { 358 super(namespace, id); 359 mName = Preconditions.checkNotNull(name); 360 } 361 BuilderImpl(@onNull Person person)362 BuilderImpl(@NonNull Person person) { 363 super(new Thing.Builder(person).build()); 364 mDocumentScore = person.getDocumentScore(); 365 mCreationTimestampMillis = 366 person.getCreationTimestampMillis(); 367 mDocumentTtlMillis = person.getDocumentTtlMillis(); 368 mGivenName = person.getGivenName(); 369 mMiddleName = person.getMiddleName(); 370 mFamilyName = person.getFamilyName(); 371 mExternalUri = person.getExternalUri(); 372 mImageUri = person.getImageUri(); 373 mIsImportant = person.isImportant(); 374 mIsBot = person.isBot(); 375 mNotes = person.getNotes(); 376 mAffiliations = person.getAffiliations(); 377 mRelations = person.getRelations(); 378 mContactPoints = person.getContactPoints(); 379 setAdditionalNames(person.getTypedAdditionalNames()); 380 } 381 382 /** Sets the given name of this {@link Person}. */ setGivenName(@onNull String givenName)383 public @NonNull T setGivenName(@NonNull String givenName) { 384 mGivenName = Preconditions.checkNotNull(givenName); 385 return (T) this; 386 } 387 388 /** 389 * Sets the middle name of this {@link Person}. 390 * 391 * <p>For {@link Person} with multiple middle names, they can all be set in this 392 * single string. Each middle name could be separated by a whitespace like "middleName1 393 * middleName2 middleName3". 394 */ setMiddleName(@onNull String middleName)395 public @NonNull T setMiddleName(@NonNull String middleName) { 396 mMiddleName = Preconditions.checkNotNull(middleName); 397 return (T) this; 398 } 399 400 /** Sets the family name of this {@link Person}. */ setFamilyName(@onNull String familyName)401 public @NonNull T setFamilyName(@NonNull String familyName) { 402 mFamilyName = Preconditions.checkNotNull(familyName); 403 return (T) this; 404 } 405 406 /** 407 * Sets an external {@link Uri} for this {@link Person}. Or {@code null} if no 408 * {@link Uri} is provided. A {@link Uri} can be any of the following: 409 * <ul> 410 * <li>A {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}. 411 * <li>A {@code mailto:} schema* 412 * <li>A {@code tel:} schema* 413 * </ul> 414 * <p>For mailto: and tel: URI schemes, it is recommended that the path 415 * portion refers to a valid contact in the Contacts Provider. 416 */ setExternalUri(@onNull Uri externalUri)417 public @NonNull T setExternalUri(@NonNull Uri externalUri) { 418 mExternalUri = Preconditions.checkNotNull(externalUri); 419 return (T) this; 420 } 421 422 /** Sets the {@link Uri} of the profile image for the {@link Person}. */ setImageUri(@onNull Uri imageUri)423 public @NonNull T setImageUri(@NonNull Uri imageUri) { 424 mImageUri = Preconditions.checkNotNull(imageUri); 425 return (T) this; 426 } 427 428 /** Sets whether this {@link Person} is important. */ setImportant(boolean isImportant)429 public @NonNull T setImportant(boolean isImportant) { 430 mIsImportant = isImportant; 431 return (T) this; 432 } 433 434 /** Sets whether this {@link Person} is a bot. */ setBot(boolean isBot)435 public @NonNull T setBot(boolean isBot) { 436 mIsBot = isBot; 437 return (T) this; 438 } 439 440 /** Sets the notes about this {@link Person}. */ setNotes(@onNull List<String> notes)441 public @NonNull T setNotes(@NonNull List<String> notes) { 442 mNotes = Preconditions.checkNotNull(notes); 443 return (T) this; 444 } 445 446 /** 447 * Sets a list of {@link AdditionalName} for that {@link Person}. 448 * 449 * <p>Only types defined in {@link AdditionalName.NameType} are accepted. 450 */ setAdditionalNames(@onNull List<AdditionalName> additionalNames)451 public @NonNull T setAdditionalNames(@NonNull List<AdditionalName> additionalNames) { 452 Preconditions.checkNotNull(additionalNames); 453 int size = additionalNames.size(); 454 mAdditionalNameTypes = new ArrayList<>(size); 455 mAdditionalNames = new ArrayList<>(size); 456 for (int i = 0; i < additionalNames.size(); ++i) { 457 long type = Preconditions.checkArgumentInRange(additionalNames.get(i).getType(), 458 AdditionalName.TYPE_UNKNOWN, 459 AdditionalName.TYPE_PHONETIC_NAME, 460 "type"); 461 mAdditionalNameTypes.add(type); 462 mAdditionalNames.add(additionalNames.get(i).getValue()); 463 } 464 return (T) this; 465 } 466 467 /** 468 * Sets a list of affiliations for this {@link Person}. Like company, school, 469 * etc. 470 */ setAffiliations(@onNull List<String> affiliations)471 public @NonNull T setAffiliations(@NonNull List<String> affiliations) { 472 mAffiliations = Preconditions.checkNotNull(affiliations); 473 return (T) this; 474 } 475 476 /** Sets a list of relations for this {@link Person}, like "Father" or "Mother". */ setRelations(@onNull List<String> relations)477 public @NonNull T setRelations(@NonNull List<String> relations) { 478 mRelations = Preconditions.checkNotNull(relations); 479 return (T) this; 480 } 481 482 /** 483 * Sets a list of {@link ContactPoint} for the {@link Person}. 484 * 485 * <p>More information could be found in {@link ContactPoint}. 486 */ setContactPoints(@onNull List<ContactPoint> contactPoints)487 public @NonNull T setContactPoints(@NonNull List<ContactPoint> contactPoints) { 488 mContactPoints = Preconditions.checkNotNull(contactPoints); 489 return (T) this; 490 } 491 492 /** Builds the {@link Person}. */ 493 @Override build()494 public @NonNull Person build() { 495 Preconditions.checkState(mAdditionalNameTypes.size() == mAdditionalNames.size()); 496 return new Person( 497 /*namespace=*/ mNamespace, 498 /*id=*/ mId, 499 /*documentScore=*/mDocumentScore, 500 /*creationTimestampMillis=*/ mCreationTimestampMillis, 501 /*documentTtlMillis=*/ mDocumentTtlMillis, 502 /*name=*/ mName, 503 /*alternateNames=*/ mAlternateNames, 504 /*description=*/ mDescription, 505 /*image=*/ mImage, 506 /*url=*/ mUrl, 507 /*potentialActions=*/ mPotentialActions, 508 /*givenName=*/ mGivenName, 509 /*middleName=*/ mMiddleName, 510 /*familyName=*/ mFamilyName, 511 /*externalUri=*/ mExternalUri != null 512 ? mExternalUri.toString() : null, 513 /*imageUri=*/ mImageUri != null 514 ? mImageUri.toString() : null, 515 /*isImportant=*/ mIsImportant, 516 /*isBot=*/ mIsBot, 517 /*notes=*/ new ArrayList<>(mNotes), 518 /*additionalNameTypes=*/ new ArrayList<>(mAdditionalNameTypes), 519 /*additionalNames=*/ new ArrayList<>(mAdditionalNames), 520 /*affiliations=*/ new ArrayList<>(mAffiliations), 521 /*relations=*/ new ArrayList<>(mRelations), 522 /*contactPoints=*/ new ArrayList<>(mContactPoints)); 523 } 524 } 525 } 526