1 /* 2 * Copyright 2020 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.external.localstorage; 18 19 import static android.app.appsearch.AppSearchResult.RESULT_INVALID_ARGUMENT; 20 import static android.app.appsearch.AppSearchResult.RESULT_NOT_FOUND; 21 import static android.app.appsearch.AppSearchResult.RESULT_OUT_OF_SPACE; 22 import static android.app.appsearch.testutil.AppSearchTestUtils.calculateDigest; 23 import static android.app.appsearch.testutil.AppSearchTestUtils.createMockVisibilityChecker; 24 import static android.app.appsearch.testutil.AppSearchTestUtils.generateRandomBytes; 25 26 import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.addPrefixToDocument; 27 import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.createPrefix; 28 import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.removePrefixesFromDocument; 29 import static com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore.BLOB_ANDROID_V_OVERLAY_DATABASE_NAME; 30 import static com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore.BLOB_VISIBILITY_DATABASE_NAME; 31 import static com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore.DOCUMENT_ANDROID_V_OVERLAY_DATABASE_NAME; 32 import static com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore.DOCUMENT_VISIBILITY_DATABASE_NAME; 33 import static com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore.VISIBILITY_PACKAGE_NAME; 34 35 import static com.google.common.truth.Truth.assertThat; 36 37 import static org.junit.Assert.assertThrows; 38 39 import android.annotation.NonNull; 40 import android.app.appsearch.AppSearchBlobHandle; 41 import android.app.appsearch.AppSearchResult; 42 import android.app.appsearch.AppSearchSchema; 43 import android.app.appsearch.Features; 44 import android.app.appsearch.GenericDocument; 45 import android.app.appsearch.GetSchemaResponse; 46 import android.app.appsearch.InternalSetSchemaResponse; 47 import android.app.appsearch.InternalVisibilityConfig; 48 import android.app.appsearch.JoinSpec; 49 import android.app.appsearch.PackageIdentifier; 50 import android.app.appsearch.SchemaVisibilityConfig; 51 import android.app.appsearch.SearchResult; 52 import android.app.appsearch.SearchResultPage; 53 import android.app.appsearch.SearchSpec; 54 import android.app.appsearch.SearchSuggestionResult; 55 import android.app.appsearch.SearchSuggestionSpec; 56 import android.app.appsearch.SetSchemaResponse; 57 import android.app.appsearch.StorageInfo; 58 import android.app.appsearch.exceptions.AppSearchException; 59 import android.app.appsearch.observer.DocumentChangeInfo; 60 import android.app.appsearch.observer.ObserverSpec; 61 import android.app.appsearch.observer.SchemaChangeInfo; 62 import android.app.appsearch.testutil.AppSearchTestUtils; 63 import android.app.appsearch.testutil.TestObserverCallback; 64 import android.content.Context; 65 import android.os.ParcelFileDescriptor; 66 import android.platform.test.annotations.RequiresFlagsDisabled; 67 import android.platform.test.annotations.RequiresFlagsEnabled; 68 import android.util.ArrayMap; 69 import android.util.ArraySet; 70 71 import androidx.test.core.app.ApplicationProvider; 72 import androidx.test.filters.FlakyTest; 73 74 import com.android.appsearch.flags.Flags; 75 import com.android.server.appsearch.appsearch.proto.AndroidVOverlayProto; 76 import com.android.server.appsearch.appsearch.proto.PackageIdentifierProto; 77 import com.android.server.appsearch.appsearch.proto.VisibilityConfigProto; 78 import com.android.server.appsearch.external.localstorage.stats.InitializeStats; 79 import com.android.server.appsearch.external.localstorage.stats.OptimizeStats; 80 import com.android.server.appsearch.external.localstorage.util.PrefixUtil; 81 import com.android.server.appsearch.external.localstorage.visibilitystore.CallerAccess; 82 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityChecker; 83 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore; 84 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityToDocumentConverter; 85 import com.android.server.appsearch.icing.proto.DebugInfoProto; 86 import com.android.server.appsearch.icing.proto.DebugInfoVerbosity; 87 import com.android.server.appsearch.icing.proto.DocumentProto; 88 import com.android.server.appsearch.icing.proto.GetOptimizeInfoResultProto; 89 import com.android.server.appsearch.icing.proto.PersistType; 90 import com.android.server.appsearch.icing.proto.PropertyConfigProto; 91 import com.android.server.appsearch.icing.proto.PropertyProto; 92 import com.android.server.appsearch.icing.proto.PutResultProto; 93 import com.android.server.appsearch.icing.proto.SchemaProto; 94 import com.android.server.appsearch.icing.proto.SchemaTypeConfigProto; 95 import com.android.server.appsearch.icing.proto.StatusProto; 96 import com.android.server.appsearch.icing.proto.StorageInfoProto; 97 import com.android.server.appsearch.icing.proto.StringIndexingConfig; 98 import com.android.server.appsearch.icing.proto.TermMatchType; 99 import com.android.server.appsearch.protobuf.ByteString; 100 101 import com.google.common.collect.ImmutableList; 102 import com.google.common.collect.ImmutableMap; 103 import com.google.common.collect.ImmutableSet; 104 import com.google.common.util.concurrent.MoreExecutors; 105 106 import org.junit.After; 107 import org.junit.Before; 108 import org.junit.Ignore; 109 import org.junit.Rule; 110 import org.junit.Test; 111 import org.junit.rules.RuleChain; 112 import org.junit.rules.TemporaryFolder; 113 114 import java.io.File; 115 import java.io.IOException; 116 import java.io.InputStream; 117 import java.io.OutputStream; 118 import java.util.ArrayList; 119 import java.util.Collections; 120 import java.util.List; 121 import java.util.Map; 122 import java.util.Set; 123 124 @SuppressWarnings("GuardedBy") 125 public class AppSearchImplTest { 126 /** 127 * Always trigger optimize in this class. OptimizeStrategy will be tested in its own test class. 128 */ 129 private static final OptimizeStrategy ALWAYS_OPTIMIZE = optimizeInfo -> true; 130 131 @Rule public final RuleChain mRuleChain = AppSearchTestUtils.createCommonTestRules(); 132 133 @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder(); 134 private File mAppSearchDir; 135 136 private final Context mContext = ApplicationProvider.getApplicationContext(); 137 138 // The caller access for this package 139 private final CallerAccess mSelfCallerAccess = new CallerAccess(mContext.getPackageName()); 140 141 private AppSearchImpl mAppSearchImpl; 142 private AppSearchConfig mUnlimitedConfig = 143 new AppSearchConfigImpl( 144 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()); 145 146 @Before setUp()147 public void setUp() throws Exception { 148 mAppSearchDir = mTemporaryFolder.newFolder(); 149 mAppSearchImpl = 150 AppSearchImpl.create( 151 mAppSearchDir, 152 mUnlimitedConfig, 153 /* initStatsBuilder= */ null, 154 /* visibilityChecker= */ null, 155 /* revocableFileDescriptorStore= */ null, 156 ALWAYS_OPTIMIZE); 157 } 158 159 @After tearDown()160 public void tearDown() { 161 mAppSearchImpl.close(); 162 } 163 164 /** 165 * Ensure that we can rewrite an incoming schema type by adding the database as a prefix. While 166 * also keeping any other existing schema types that may already be part of Icing's persisted 167 * schema. 168 */ 169 @Test testRewriteSchema_addType()170 public void testRewriteSchema_addType() throws Exception { 171 SchemaProto.Builder existingSchemaBuilder = 172 SchemaProto.newBuilder() 173 .addTypes( 174 SchemaTypeConfigProto.newBuilder() 175 .setSchemaType("package$existingDatabase/Foo") 176 .build()); 177 178 // Create a copy so we can modify it. 179 List<SchemaTypeConfigProto> existingTypes = 180 new ArrayList<>(existingSchemaBuilder.getTypesList()); 181 SchemaTypeConfigProto schemaTypeConfigProto1 = 182 SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build(); 183 SchemaTypeConfigProto schemaTypeConfigProto2 = 184 SchemaTypeConfigProto.newBuilder() 185 .setSchemaType("TestType") 186 .addProperties( 187 PropertyConfigProto.newBuilder() 188 .setPropertyName("subject") 189 .setDataType(PropertyConfigProto.DataType.Code.STRING) 190 .setCardinality( 191 PropertyConfigProto.Cardinality.Code.OPTIONAL) 192 .setStringIndexingConfig( 193 StringIndexingConfig.newBuilder() 194 .setTokenizerType( 195 StringIndexingConfig.TokenizerType 196 .Code.PLAIN) 197 .setTermMatchType(TermMatchType.Code.PREFIX) 198 .build()) 199 .build()) 200 .addProperties( 201 PropertyConfigProto.newBuilder() 202 .setPropertyName("link") 203 .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT) 204 .setCardinality( 205 PropertyConfigProto.Cardinality.Code.OPTIONAL) 206 .setSchemaType("RefType") 207 .build()) 208 .build(); 209 SchemaTypeConfigProto schemaTypeConfigProto3 = 210 SchemaTypeConfigProto.newBuilder() 211 .setSchemaType("RefType") 212 .addParentTypes("Foo") 213 .build(); 214 SchemaProto newSchema = 215 SchemaProto.newBuilder() 216 .addTypes(schemaTypeConfigProto1) 217 .addTypes(schemaTypeConfigProto2) 218 .addTypes(schemaTypeConfigProto3) 219 .build(); 220 221 AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults = 222 AppSearchImpl.rewriteSchema( 223 createPrefix("package", "newDatabase"), existingSchemaBuilder, newSchema); 224 225 // We rewrote all the new types that were added. And nothing was removed. 226 assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet()) 227 .containsExactly( 228 "package$newDatabase/Foo", 229 "package$newDatabase/TestType", 230 "package$newDatabase/RefType"); 231 assertThat( 232 rewrittenSchemaResults 233 .mRewrittenPrefixedTypes 234 .get("package$newDatabase/Foo") 235 .getSchemaType()) 236 .isEqualTo("package$newDatabase/Foo"); 237 assertThat( 238 rewrittenSchemaResults 239 .mRewrittenPrefixedTypes 240 .get("package$newDatabase/TestType") 241 .getSchemaType()) 242 .isEqualTo("package$newDatabase/TestType"); 243 assertThat( 244 rewrittenSchemaResults 245 .mRewrittenPrefixedTypes 246 .get("package$newDatabase/RefType") 247 .getSchemaType()) 248 .isEqualTo("package$newDatabase/RefType"); 249 assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes).isEmpty(); 250 251 SchemaProto expectedSchema = 252 SchemaProto.newBuilder() 253 .addTypes( 254 SchemaTypeConfigProto.newBuilder() 255 .setSchemaType("package$newDatabase/Foo") 256 .build()) 257 .addTypes( 258 SchemaTypeConfigProto.newBuilder() 259 .setSchemaType("package$newDatabase/TestType") 260 .addProperties( 261 PropertyConfigProto.newBuilder() 262 .setPropertyName("subject") 263 .setDataType( 264 PropertyConfigProto.DataType.Code 265 .STRING) 266 .setCardinality( 267 PropertyConfigProto.Cardinality.Code 268 .OPTIONAL) 269 .setStringIndexingConfig( 270 StringIndexingConfig.newBuilder() 271 .setTokenizerType( 272 StringIndexingConfig 273 .TokenizerType 274 .Code.PLAIN) 275 .setTermMatchType( 276 TermMatchType.Code 277 .PREFIX) 278 .build()) 279 .build()) 280 .addProperties( 281 PropertyConfigProto.newBuilder() 282 .setPropertyName("link") 283 .setDataType( 284 PropertyConfigProto.DataType.Code 285 .DOCUMENT) 286 .setCardinality( 287 PropertyConfigProto.Cardinality.Code 288 .OPTIONAL) 289 .setSchemaType( 290 "package$newDatabase/RefType") 291 .build()) 292 .build()) 293 .addTypes( 294 SchemaTypeConfigProto.newBuilder() 295 .setSchemaType("package$newDatabase/RefType") 296 .addParentTypes("package$newDatabase/Foo") 297 .build()) 298 .build(); 299 300 existingTypes.addAll(expectedSchema.getTypesList()); 301 assertThat(existingSchemaBuilder.getTypesList()).containsExactlyElementsIn(existingTypes); 302 } 303 304 /** 305 * Ensure that we track all types that were rewritten in the input schema. Even if they were not 306 * technically "added" to the existing schema. 307 */ 308 @Test testRewriteSchema_rewriteType()309 public void testRewriteSchema_rewriteType() throws Exception { 310 SchemaProto.Builder existingSchemaBuilder = 311 SchemaProto.newBuilder() 312 .addTypes( 313 SchemaTypeConfigProto.newBuilder() 314 .setSchemaType("package$existingDatabase/Foo") 315 .build()); 316 317 SchemaProto newSchema = 318 SchemaProto.newBuilder() 319 .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Foo").build()) 320 .build(); 321 322 AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults = 323 AppSearchImpl.rewriteSchema( 324 createPrefix("package", "existingDatabase"), 325 existingSchemaBuilder, 326 newSchema); 327 328 // Nothing was removed, but the method did rewrite the type name. 329 assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet()) 330 .containsExactly("package$existingDatabase/Foo"); 331 assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes).isEmpty(); 332 333 // Same schema since nothing was added. 334 SchemaProto expectedSchema = existingSchemaBuilder.build(); 335 assertThat(existingSchemaBuilder.getTypesList()) 336 .containsExactlyElementsIn(expectedSchema.getTypesList()); 337 } 338 339 /** 340 * Ensure that we track which types from the existing schema are deleted when a new schema is 341 * set. 342 */ 343 @Test testRewriteSchema_deleteType()344 public void testRewriteSchema_deleteType() throws Exception { 345 SchemaProto.Builder existingSchemaBuilder = 346 SchemaProto.newBuilder() 347 .addTypes( 348 SchemaTypeConfigProto.newBuilder() 349 .setSchemaType("package$existingDatabase/Foo") 350 .build()); 351 352 SchemaProto newSchema = 353 SchemaProto.newBuilder() 354 .addTypes(SchemaTypeConfigProto.newBuilder().setSchemaType("Bar").build()) 355 .build(); 356 357 AppSearchImpl.RewrittenSchemaResults rewrittenSchemaResults = 358 AppSearchImpl.rewriteSchema( 359 createPrefix("package", "existingDatabase"), 360 existingSchemaBuilder, 361 newSchema); 362 363 // Bar type was rewritten, but Foo ended up being deleted since it wasn't included in the 364 // new schema. 365 assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes) 366 .containsKey("package$existingDatabase/Bar"); 367 assertThat(rewrittenSchemaResults.mRewrittenPrefixedTypes.keySet().size()).isEqualTo(1); 368 assertThat(rewrittenSchemaResults.mDeletedPrefixedTypes) 369 .containsExactly("package$existingDatabase/Foo"); 370 371 // Same schema since nothing was added. 372 SchemaProto expectedSchema = 373 SchemaProto.newBuilder() 374 .addTypes( 375 SchemaTypeConfigProto.newBuilder() 376 .setSchemaType("package$existingDatabase/Bar") 377 .build()) 378 .build(); 379 380 assertThat(existingSchemaBuilder.getTypesList()) 381 .containsExactlyElementsIn(expectedSchema.getTypesList()); 382 } 383 384 @Test testAddDocumentTypePrefix()385 public void testAddDocumentTypePrefix() { 386 DocumentProto insideDocument = 387 DocumentProto.newBuilder() 388 .setUri("inside-id") 389 .setSchema("type") 390 .setNamespace("namespace") 391 .build(); 392 DocumentProto documentProto = 393 DocumentProto.newBuilder() 394 .setUri("id") 395 .setSchema("type") 396 .setNamespace("namespace") 397 .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument)) 398 .build(); 399 400 DocumentProto expectedInsideDocument = 401 DocumentProto.newBuilder() 402 .setUri("inside-id") 403 .setSchema("package$databaseName/type") 404 .setNamespace("package$databaseName/namespace") 405 .build(); 406 DocumentProto expectedDocumentProto = 407 DocumentProto.newBuilder() 408 .setUri("id") 409 .setSchema("package$databaseName/type") 410 .setNamespace("package$databaseName/namespace") 411 .addProperties( 412 PropertyProto.newBuilder() 413 .addDocumentValues(expectedInsideDocument)) 414 .build(); 415 416 DocumentProto.Builder actualDocument = documentProto.toBuilder(); 417 addPrefixToDocument(actualDocument, createPrefix("package", "databaseName")); 418 assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto); 419 } 420 421 @Test testRemoveDocumentTypePrefixes()422 public void testRemoveDocumentTypePrefixes() throws Exception { 423 DocumentProto insideDocument = 424 DocumentProto.newBuilder() 425 .setUri("inside-id") 426 .setSchema("package$databaseName/type") 427 .setNamespace("package$databaseName/namespace") 428 .build(); 429 DocumentProto documentProto = 430 DocumentProto.newBuilder() 431 .setUri("id") 432 .setSchema("package$databaseName/type") 433 .setNamespace("package$databaseName/namespace") 434 .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument)) 435 .build(); 436 437 DocumentProto expectedInsideDocument = 438 DocumentProto.newBuilder() 439 .setUri("inside-id") 440 .setSchema("type") 441 .setNamespace("namespace") 442 .build(); 443 444 DocumentProto expectedDocumentProto = 445 DocumentProto.newBuilder() 446 .setUri("id") 447 .setSchema("type") 448 .setNamespace("namespace") 449 .addProperties( 450 PropertyProto.newBuilder() 451 .addDocumentValues(expectedInsideDocument)) 452 .build(); 453 454 DocumentProto.Builder actualDocument = documentProto.toBuilder(); 455 assertThat(removePrefixesFromDocument(actualDocument)).isEqualTo("package$databaseName/"); 456 assertThat(actualDocument.build()).isEqualTo(expectedDocumentProto); 457 } 458 459 @Test testRemoveDatabasesFromDocumentThrowsException()460 public void testRemoveDatabasesFromDocumentThrowsException() { 461 // Set two different database names in the document, which should never happen 462 DocumentProto documentProto = 463 DocumentProto.newBuilder() 464 .setUri("id") 465 .setSchema("prefix1/type") 466 .setNamespace("prefix2/namespace") 467 .build(); 468 469 DocumentProto.Builder actualDocument = documentProto.toBuilder(); 470 AppSearchException e = 471 assertThrows( 472 AppSearchException.class, () -> removePrefixesFromDocument(actualDocument)); 473 assertThat(e).hasMessageThat().contains("Found unexpected multiple prefix names"); 474 } 475 476 @Test testNestedRemoveDatabasesFromDocumentThrowsException()477 public void testNestedRemoveDatabasesFromDocumentThrowsException() { 478 // Set two different database names in the outer and inner document, which should never 479 // happen. 480 DocumentProto insideDocument = 481 DocumentProto.newBuilder() 482 .setUri("inside-id") 483 .setSchema("prefix1/type") 484 .setNamespace("prefix1/namespace") 485 .build(); 486 DocumentProto documentProto = 487 DocumentProto.newBuilder() 488 .setUri("id") 489 .setSchema("prefix2/type") 490 .setNamespace("prefix2/namespace") 491 .addProperties(PropertyProto.newBuilder().addDocumentValues(insideDocument)) 492 .build(); 493 494 DocumentProto.Builder actualDocument = documentProto.toBuilder(); 495 AppSearchException e = 496 assertThrows( 497 AppSearchException.class, () -> removePrefixesFromDocument(actualDocument)); 498 assertThat(e).hasMessageThat().contains("Found unexpected multiple prefix names"); 499 } 500 501 @Test testTriggerCheckOptimizeByMutationSize()502 public void testTriggerCheckOptimizeByMutationSize() throws Exception { 503 // Insert schema 504 List<AppSearchSchema> schemas = 505 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 506 InternalSetSchemaResponse internalSetSchemaResponse = 507 mAppSearchImpl.setSchema( 508 "package", 509 "database", 510 schemas, 511 /* visibilityConfigs= */ Collections.emptyList(), 512 /* forceOverride= */ false, 513 /* version= */ 0, 514 /* setSchemaStatsBuilder= */ null); 515 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 516 517 // Insert a document and then remove it to generate garbage. 518 GenericDocument document = new GenericDocument.Builder<>("namespace", "id", "type").build(); 519 mAppSearchImpl.putDocument( 520 "package", 521 "database", 522 document, 523 /* sendChangeNotifications= */ false, 524 /* logger= */ null); 525 mAppSearchImpl.remove( 526 "package", "database", "namespace", "id", /* removeStatsBuilder= */ null); 527 528 // Verify there is garbage documents. 529 GetOptimizeInfoResultProto optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked(); 530 assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(1); 531 532 // Increase mutation counter and stop before reach the threshold 533 mAppSearchImpl.checkForOptimize( 534 AppSearchImpl.CHECK_OPTIMIZE_INTERVAL - 1, /* builder= */ null); 535 536 // Verify the optimize() isn't triggered. 537 optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked(); 538 assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(1); 539 540 // Increase the counter and reach the threshold, optimize() should be triggered. 541 OptimizeStats.Builder builder = new OptimizeStats.Builder(); 542 mAppSearchImpl.checkForOptimize(/* mutateBatchSize= */ 1, builder); 543 544 // Verify optimize() is triggered. 545 optimizeInfo = mAppSearchImpl.getOptimizeInfoResultLocked(); 546 assertThat(optimizeInfo.getOptimizableDocs()).isEqualTo(0); 547 assertThat(optimizeInfo.getEstimatedOptimizableBytes()).isEqualTo(0); 548 549 // Verify the stats have been set. 550 OptimizeStats oStats = builder.build(); 551 assertThat(oStats.getOriginalDocumentCount()).isEqualTo(1); 552 assertThat(oStats.getDeletedDocumentCount()).isEqualTo(1); 553 } 554 555 @Test testReset()556 public void testReset() throws Exception { 557 // Insert schema 558 List<AppSearchSchema> schemas = 559 ImmutableList.of( 560 new AppSearchSchema.Builder("Type1").build(), 561 new AppSearchSchema.Builder("Type2").build()); 562 InternalSetSchemaResponse internalSetSchemaResponse = 563 mAppSearchImpl.setSchema( 564 mContext.getPackageName(), 565 "database1", 566 schemas, 567 /* visibilityConfigs= */ Collections.emptyList(), 568 /* forceOverride= */ false, 569 /* version= */ 0, 570 /* setSchemaStatsBuilder= */ null); 571 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 572 573 // Insert a valid doc 574 GenericDocument validDoc = 575 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(); 576 mAppSearchImpl.putDocument( 577 mContext.getPackageName(), 578 "database1", 579 validDoc, 580 /* sendChangeNotifications= */ false, 581 /* logger= */ null); 582 583 // Query it via global query. We use the same code again later so this is to make sure we 584 // have our global query configured right. 585 SearchResultPage results = 586 mAppSearchImpl.globalQuery( 587 /* queryExpression= */ "", 588 new SearchSpec.Builder().addFilterSchemas("Type1").build(), 589 mSelfCallerAccess, 590 /* logger= */ null); 591 assertThat(results.getResults()).hasSize(1); 592 assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc); 593 594 // Create a doc with a malformed namespace 595 DocumentProto invalidDoc = 596 DocumentProto.newBuilder() 597 .setNamespace("invalidNamespace") 598 .setUri("id2") 599 .setSchema(mContext.getPackageName() + "$database1/Type1") 600 .build(); 601 AppSearchException e = 602 assertThrows( 603 AppSearchException.class, 604 () -> PrefixUtil.getPrefix(invalidDoc.getNamespace())); 605 assertThat(e) 606 .hasMessageThat() 607 .isEqualTo( 608 "The prefixed value \"invalidNamespace\" doesn't contain a valid database" 609 + " name"); 610 611 // Insert the invalid doc with an invalid namespace right into icing 612 PutResultProto putResultProto = mAppSearchImpl.mIcingSearchEngineLocked.put(invalidDoc); 613 assertThat(putResultProto.getStatus().getCode()).isEqualTo(StatusProto.Code.OK); 614 615 // Initialize AppSearchImpl. This should cause a reset. 616 InitializeStats.Builder initStatsBuilder = new InitializeStats.Builder(); 617 mAppSearchImpl.close(); 618 mAppSearchImpl = 619 AppSearchImpl.create( 620 mAppSearchDir, 621 new AppSearchConfigImpl( 622 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 623 initStatsBuilder, 624 /* visibilityChecker= */ null, 625 /* revocableFileDescriptorStore= */ null, 626 ALWAYS_OPTIMIZE); 627 628 // Check recovery state 629 InitializeStats initStats = initStatsBuilder.build(); 630 assertThat(initStats).isNotNull(); 631 assertThat(initStats.getStatusCode()).isEqualTo(AppSearchResult.RESULT_INTERNAL_ERROR); 632 assertThat(initStats.hasDeSync()).isFalse(); 633 assertThat(initStats.getDocumentStoreRecoveryCause()) 634 .isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE); 635 assertThat(initStats.getIndexRestorationCause()) 636 .isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE); 637 assertThat(initStats.getSchemaStoreRecoveryCause()) 638 .isEqualTo(InitializeStats.RECOVERY_CAUSE_NONE); 639 assertThat(initStats.getDocumentStoreDataStatus()) 640 .isEqualTo(InitializeStats.DOCUMENT_STORE_DATA_STATUS_NO_DATA_LOSS); 641 assertThat(initStats.hasReset()).isTrue(); 642 assertThat(initStats.getResetStatusCode()).isEqualTo(AppSearchResult.RESULT_OK); 643 644 // Make sure all our data is gone 645 assertThat( 646 mAppSearchImpl 647 .getSchema( 648 /* packageName= */ mContext.getPackageName(), 649 /* databaseName= */ "database1", 650 /* callerAccess= */ mSelfCallerAccess) 651 .getSchemas()) 652 .isEmpty(); 653 results = 654 mAppSearchImpl.globalQuery( 655 /* queryExpression= */ "", 656 new SearchSpec.Builder().addFilterSchemas("Type1").build(), 657 mSelfCallerAccess, 658 /* logger= */ null); 659 assertThat(results.getResults()).isEmpty(); 660 661 // Make sure the index can now be used successfully 662 internalSetSchemaResponse = 663 mAppSearchImpl.setSchema( 664 mContext.getPackageName(), 665 "database1", 666 Collections.singletonList(new AppSearchSchema.Builder("Type1").build()), 667 /* visibilityConfigs= */ Collections.emptyList(), 668 /* forceOverride= */ false, 669 /* version= */ 0, 670 /* setSchemaStatsBuilder= */ null); 671 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 672 673 // Insert a valid doc 674 mAppSearchImpl.putDocument( 675 mContext.getPackageName(), 676 "database1", 677 validDoc, 678 /* sendChangeNotifications= */ false, 679 /* logger= */ null); 680 681 // Query it via global query. 682 results = 683 mAppSearchImpl.globalQuery( 684 /* queryExpression= */ "", 685 new SearchSpec.Builder().addFilterSchemas("Type1").build(), 686 mSelfCallerAccess, 687 /* logger= */ null); 688 assertThat(results.getResults()).hasSize(1); 689 assertThat(results.getResults().get(0).getGenericDocument()).isEqualTo(validDoc); 690 } 691 692 @Test testQueryEmptyDatabase()693 public void testQueryEmptyDatabase() throws Exception { 694 SearchSpec searchSpec = 695 new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build(); 696 SearchResultPage searchResultPage = 697 mAppSearchImpl.query( 698 "package", "EmptyDatabase", "", searchSpec, /* logger= */ null); 699 assertThat(searchResultPage.getResults()).isEmpty(); 700 } 701 702 /** 703 * TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term 704 * test until we have official support for multiple-apps indexing at once. 705 */ 706 @Test testQueryWithMultiplePackages_noPackageFilters()707 public void testQueryWithMultiplePackages_noPackageFilters() throws Exception { 708 // Insert package1 schema 709 List<AppSearchSchema> schema1 = 710 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 711 InternalSetSchemaResponse internalSetSchemaResponse = 712 mAppSearchImpl.setSchema( 713 "package1", 714 "database1", 715 schema1, 716 /* visibilityConfigs= */ Collections.emptyList(), 717 /* forceOverride= */ false, 718 /* version= */ 0, 719 /* setSchemaStatsBuilder= */ null); 720 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 721 722 // Insert package2 schema 723 List<AppSearchSchema> schema2 = 724 ImmutableList.of(new AppSearchSchema.Builder("schema2").build()); 725 internalSetSchemaResponse = 726 mAppSearchImpl.setSchema( 727 "package2", 728 "database2", 729 schema2, 730 /* visibilityConfigs= */ Collections.emptyList(), 731 /* forceOverride= */ false, 732 /* version= */ 0, 733 /* setSchemaStatsBuilder= */ null); 734 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 735 736 // Insert package1 document 737 GenericDocument document = 738 new GenericDocument.Builder<>("namespace", "id", "schema1").build(); 739 mAppSearchImpl.putDocument( 740 "package1", 741 "database1", 742 document, 743 /* sendChangeNotifications= */ false, 744 /* logger= */ null); 745 746 // No query filters specified, package2 shouldn't be able to query for package1's documents. 747 SearchSpec searchSpec = 748 new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build(); 749 SearchResultPage searchResultPage = 750 mAppSearchImpl.query("package2", "database2", "", searchSpec, /* logger= */ null); 751 assertThat(searchResultPage.getResults()).isEmpty(); 752 753 // Insert package2 document 754 document = new GenericDocument.Builder<>("namespace", "id", "schema2").build(); 755 mAppSearchImpl.putDocument( 756 "package2", 757 "database2", 758 document, 759 /* sendChangeNotifications= */ false, 760 /* logger= */ null); 761 762 // No query filters specified. package2 should only get its own documents back. 763 searchResultPage = 764 mAppSearchImpl.query("package2", "database2", "", searchSpec, /* logger= */ null); 765 assertThat(searchResultPage.getResults()).hasSize(1); 766 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document); 767 } 768 769 /** 770 * TODO(b/169883602): This should be an integration test at the cts-level. This is a short-term 771 * test until we have official support for multiple-apps indexing at once. 772 */ 773 @Test testQueryWithMultiplePackages_withPackageFilters()774 public void testQueryWithMultiplePackages_withPackageFilters() throws Exception { 775 // Insert package1 schema 776 List<AppSearchSchema> schema1 = 777 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 778 InternalSetSchemaResponse internalSetSchemaResponse = 779 mAppSearchImpl.setSchema( 780 "package1", 781 "database1", 782 schema1, 783 /* visibilityConfigs= */ Collections.emptyList(), 784 /* forceOverride= */ false, 785 /* version= */ 0, 786 /* setSchemaStatsBuilder= */ null); 787 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 788 789 // Insert package2 schema 790 List<AppSearchSchema> schema2 = 791 ImmutableList.of(new AppSearchSchema.Builder("schema2").build()); 792 internalSetSchemaResponse = 793 mAppSearchImpl.setSchema( 794 "package2", 795 "database2", 796 schema2, 797 /* visibilityConfigs= */ Collections.emptyList(), 798 /* forceOverride= */ false, 799 /* version= */ 0, 800 /* setSchemaStatsBuilder= */ null); 801 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 802 803 // Insert package1 document 804 GenericDocument document = 805 new GenericDocument.Builder<>("namespace", "id", "schema1").build(); 806 mAppSearchImpl.putDocument( 807 "package1", 808 "database1", 809 document, 810 /* sendChangeNotifications= */ false, 811 /* logger= */ null); 812 813 // "package1" filter specified, but package2 shouldn't be able to query for package1's 814 // documents. 815 SearchSpec searchSpec = 816 new SearchSpec.Builder() 817 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 818 .addFilterPackageNames("package1") 819 .build(); 820 SearchResultPage searchResultPage = 821 mAppSearchImpl.query("package2", "database2", "", searchSpec, /* logger= */ null); 822 assertThat(searchResultPage.getResults()).isEmpty(); 823 824 // Insert package2 document 825 document = new GenericDocument.Builder<>("namespace", "id", "schema2").build(); 826 mAppSearchImpl.putDocument( 827 "package2", 828 "database2", 829 document, 830 /* sendChangeNotifications= */ false, 831 /* logger= */ null); 832 833 // "package2" filter specified, package2 should only get its own documents back. 834 searchSpec = 835 new SearchSpec.Builder() 836 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 837 .addFilterPackageNames("package2") 838 .build(); 839 searchResultPage = 840 mAppSearchImpl.query("package2", "database2", "", searchSpec, /* logger= */ null); 841 assertThat(searchResultPage.getResults()).hasSize(1); 842 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document); 843 } 844 845 @Test testGlobalQuery_emptyPackage()846 public void testGlobalQuery_emptyPackage() throws Exception { 847 SearchSpec searchSpec = 848 new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build(); 849 SearchResultPage searchResultPage = 850 mAppSearchImpl.globalQuery( 851 /* queryExpression= */ "", 852 searchSpec, 853 new CallerAccess(/* callingPackageName= */ ""), 854 /* logger= */ null); 855 assertThat(searchResultPage.getResults()).isEmpty(); 856 } 857 858 @Test testGlobalQuery_withJoin_packageFilter()859 public void testGlobalQuery_withJoin_packageFilter() throws Exception { 860 // Create a new mAppSearchImpl with a mock Visibility Checker 861 mAppSearchImpl.close(); 862 File tempFolder = mTemporaryFolder.newFolder(); 863 // We need to share across packages 864 VisibilityChecker mockVisibilityChecker = createMockVisibilityChecker(true); 865 mAppSearchImpl = 866 AppSearchImpl.create( 867 tempFolder, 868 new AppSearchConfigImpl( 869 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 870 /* initStatsBuilder= */ null, 871 mockVisibilityChecker, 872 /* revocableFileDescriptorStore= */ null, 873 ALWAYS_OPTIMIZE); 874 875 // Insert package1 schema 876 List<AppSearchSchema> personSchema = 877 ImmutableList.of(new AppSearchSchema.Builder("personSchema").build()); 878 InternalSetSchemaResponse internalSetSchemaResponse = 879 mAppSearchImpl.setSchema( 880 "package1", 881 "database1", 882 personSchema, 883 /* visibilityConfigs= */ Collections.emptyList(), 884 /* forceOverride= */ false, 885 /* version= */ 0, 886 /* setSchemaStatsBuilder= */ null); 887 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 888 889 AppSearchSchema.StringPropertyConfig personField = 890 new AppSearchSchema.StringPropertyConfig.Builder("personId") 891 .setJoinableValueType( 892 AppSearchSchema.StringPropertyConfig 893 .JOINABLE_VALUE_TYPE_QUALIFIED_ID) 894 .build(); 895 // Insert package2 schema 896 List<AppSearchSchema> callSchema = 897 ImmutableList.of( 898 new AppSearchSchema.Builder("callSchema").addProperty(personField).build()); 899 internalSetSchemaResponse = 900 mAppSearchImpl.setSchema( 901 "package2", 902 "database2", 903 callSchema, 904 /* visibilityConfigs= */ Collections.emptyList(), 905 /* forceOverride= */ true, 906 /* version= */ 0, 907 /* setSchemaStatsBuilder= */ null); 908 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 909 910 List<AppSearchSchema> textSchema = 911 ImmutableList.of( 912 new AppSearchSchema.Builder("textSchema").addProperty(personField).build()); 913 internalSetSchemaResponse = 914 mAppSearchImpl.setSchema( 915 "package3", 916 "database3", 917 textSchema, 918 /* visibilityConfigs= */ Collections.emptyList(), 919 /* forceOverride= */ true, 920 /* version= */ 0, 921 /* setSchemaStatsBuilder= */ null); 922 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 923 924 // Insert package1 document 925 GenericDocument person = 926 new GenericDocument.Builder<>("namespace", "id", "personSchema").build(); 927 mAppSearchImpl.putDocument( 928 "package1", 929 "database1", 930 person, 931 /* sendChangeNotifications= */ false, 932 /* logger= */ null); 933 934 // Insert package2 document 935 GenericDocument call = 936 new GenericDocument.Builder<>("namespace", "id", "callSchema") 937 .setPropertyString("personId", "package1$database1/namespace#id") 938 .build(); 939 mAppSearchImpl.putDocument( 940 "package2", 941 "database2", 942 call, 943 /* sendChangeNotifications= */ false, 944 /* logger= */ null); 945 946 // Insert package3 document 947 GenericDocument text = 948 new GenericDocument.Builder<>("namespace", "id", "textSchema") 949 .setPropertyString("personId", "package1$database1/namespace#id") 950 .build(); 951 mAppSearchImpl.putDocument( 952 "package3", 953 "database3", 954 text, 955 /* sendChangeNotifications= */ false, 956 /* logger= */ null); 957 958 // Filter on parent spec only 959 SearchSpec nested = 960 new SearchSpec.Builder() 961 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 962 .setOrder(SearchSpec.ORDER_ASCENDING) 963 .build(); 964 JoinSpec join = new JoinSpec.Builder("personId").setNestedSearch("", nested).build(); 965 966 SearchSpec searchSpec = 967 new SearchSpec.Builder() 968 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 969 .addFilterPackageNames("package1") 970 .setJoinSpec(join) 971 .build(); 972 SearchResultPage searchResultPage = 973 mAppSearchImpl.globalQuery( 974 "", searchSpec, new CallerAccess("package1"), /* logger= */ null); 975 assertThat(searchResultPage.getResults()).hasSize(1); 976 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(person); 977 SearchResult result = searchResultPage.getResults().get(0); 978 assertThat(result.getJoinedResults()).hasSize(2); 979 980 // Filter on neither 981 searchSpec = 982 new SearchSpec.Builder() 983 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 984 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 985 .setOrder(SearchSpec.ORDER_ASCENDING) 986 .setJoinSpec(join) 987 .build(); 988 searchResultPage = 989 mAppSearchImpl.globalQuery( 990 "", searchSpec, new CallerAccess("package1"), /* logger= */ null); 991 assertThat(searchResultPage.getResults()).hasSize(3); 992 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(person); 993 assertThat(searchResultPage.getResults().get(1).getGenericDocument()).isEqualTo(call); 994 assertThat(searchResultPage.getResults().get(2).getGenericDocument()).isEqualTo(text); 995 result = searchResultPage.getResults().get(0); 996 assertThat(result.getJoinedResults()).hasSize(2); 997 998 // Filter on child spec only 999 nested = new SearchSpec.Builder().addFilterPackageNames("package2").build(); 1000 join = new JoinSpec.Builder("personId").setNestedSearch("", nested).build(); 1001 1002 searchSpec = 1003 new SearchSpec.Builder() 1004 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1005 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 1006 .setOrder(SearchSpec.ORDER_ASCENDING) 1007 .setJoinSpec(join) 1008 .build(); 1009 searchResultPage = 1010 mAppSearchImpl.globalQuery( 1011 "", searchSpec, new CallerAccess("package1"), /* logger= */ null); 1012 assertThat(searchResultPage.getResults()).hasSize(3); 1013 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(person); 1014 assertThat(searchResultPage.getResults().get(1).getGenericDocument()).isEqualTo(call); 1015 assertThat(searchResultPage.getResults().get(2).getGenericDocument()).isEqualTo(text); 1016 result = searchResultPage.getResults().get(0); 1017 assertThat(result.getJoinedResults()).hasSize(1); 1018 assertThat(result.getJoinedResults().get(0).getGenericDocument()).isEqualTo(call); 1019 1020 // Filter on both 1021 searchSpec = 1022 new SearchSpec.Builder() 1023 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1024 .addFilterPackageNames("package1") 1025 .setJoinSpec(join) 1026 .build(); 1027 searchResultPage = 1028 mAppSearchImpl.globalQuery( 1029 "", searchSpec, new CallerAccess("package1"), /* logger= */ null); 1030 assertThat(searchResultPage.getResults()).hasSize(1); 1031 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(person); 1032 result = searchResultPage.getResults().get(0); 1033 assertThat(result.getJoinedResults()).hasSize(1); 1034 assertThat(result.getJoinedResults().get(0).getGenericDocument()).isEqualTo(call); 1035 } 1036 1037 @Test testQueryInvalidPackages_withJoin()1038 public void testQueryInvalidPackages_withJoin() throws Exception { 1039 // Make sure that local queries with joinspecs including package filters don't access 1040 // other packages. 1041 1042 // Create a new mAppSearchImpl with a mock Visibility Checker 1043 mAppSearchImpl.close(); 1044 File tempFolder = mTemporaryFolder.newFolder(); 1045 // We need to share across packages 1046 VisibilityChecker mockVisibilityChecker = createMockVisibilityChecker(true); 1047 mAppSearchImpl = 1048 AppSearchImpl.create( 1049 tempFolder, 1050 new AppSearchConfigImpl( 1051 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 1052 /* initStatsBuilder= */ null, 1053 mockVisibilityChecker, 1054 /* revocableFileDescriptorStore= */ null, 1055 ALWAYS_OPTIMIZE); 1056 1057 AppSearchSchema.StringPropertyConfig personField = 1058 new AppSearchSchema.StringPropertyConfig.Builder("personId") 1059 .setJoinableValueType( 1060 AppSearchSchema.StringPropertyConfig 1061 .JOINABLE_VALUE_TYPE_QUALIFIED_ID) 1062 .build(); 1063 1064 // Insert package1 schema 1065 List<AppSearchSchema> personAndCallSchema = 1066 ImmutableList.of( 1067 new AppSearchSchema.Builder("personSchema").build(), 1068 new AppSearchSchema.Builder("callSchema").addProperty(personField).build()); 1069 InternalSetSchemaResponse internalSetSchemaResponse = 1070 mAppSearchImpl.setSchema( 1071 "package1", 1072 "database1", 1073 personAndCallSchema, 1074 /* visibilityConfigs= */ Collections.emptyList(), 1075 /* forceOverride= */ false, 1076 /* version= */ 0, 1077 /* setSchemaStatsBuilder= */ null); 1078 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 1079 1080 // Insert package2 schema 1081 List<AppSearchSchema> callSchema = 1082 ImmutableList.of( 1083 new AppSearchSchema.Builder("callSchema").addProperty(personField).build()); 1084 internalSetSchemaResponse = 1085 mAppSearchImpl.setSchema( 1086 "package2", 1087 "database2", 1088 callSchema, 1089 /* visibilityConfigs= */ Collections.emptyList(), 1090 /* forceOverride= */ true, 1091 /* version= */ 0, 1092 /* setSchemaStatsBuilder= */ null); 1093 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 1094 1095 // Insert package1 document 1096 GenericDocument person = 1097 new GenericDocument.Builder<>("namespace", "person", "personSchema").build(); 1098 mAppSearchImpl.putDocument( 1099 "package1", 1100 "database1", 1101 person, 1102 /* sendChangeNotifications= */ false, 1103 /* logger= */ null); 1104 1105 GenericDocument call1 = 1106 new GenericDocument.Builder<>("namespace", "id1", "callSchema") 1107 .setPropertyString("personId", "package1$database1/namespace#person") 1108 .build(); 1109 GenericDocument call2 = 1110 new GenericDocument.Builder<>("namespace", "id2", "callSchema") 1111 .setPropertyString("personId", "package1$database1/namespace#person") 1112 .build(); 1113 1114 // Insert package1 action document 1115 mAppSearchImpl.putDocument( 1116 "package1", 1117 "database1", 1118 call1, 1119 /* sendChangeNotifications= */ false, 1120 /* logger= */ null); 1121 1122 // Insert package2 action document 1123 mAppSearchImpl.putDocument( 1124 "package2", 1125 "database2", 1126 call2, 1127 /* sendChangeNotifications= */ false, 1128 /* logger= */ null); 1129 1130 // Invalid parent spec filter 1131 SearchSpec searchSpec = 1132 new SearchSpec.Builder() 1133 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1134 .addFilterPackageNames("package1", "package2") 1135 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 1136 .setOrder(SearchSpec.ORDER_ASCENDING) 1137 .build(); 1138 SearchResultPage searchResultPage = 1139 mAppSearchImpl.query("package1", "database1", "", searchSpec, /* logger= */ null); 1140 // Only package1 documents should be returned 1141 assertThat(searchResultPage.getResults()).hasSize(2); 1142 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(person); 1143 assertThat(searchResultPage.getResults().get(1).getGenericDocument()).isEqualTo(call1); 1144 1145 // Valid parent spec filter with invalid child spec filter 1146 SearchSpec nested = 1147 new SearchSpec.Builder() 1148 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 1149 .addFilterPackageNames("package1", "package2") 1150 .setOrder(SearchSpec.ORDER_ASCENDING) 1151 .build(); 1152 JoinSpec join = new JoinSpec.Builder("personId").setNestedSearch("", nested).build(); 1153 searchSpec = 1154 new SearchSpec.Builder() 1155 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1156 .addFilterPackageNames("package1") 1157 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 1158 .setOrder(SearchSpec.ORDER_ASCENDING) 1159 .setJoinSpec(join) 1160 .build(); 1161 searchResultPage = 1162 mAppSearchImpl.query("package1", "database1", "", searchSpec, /* logger= */ null); 1163 1164 // Only package1 documents should be returned, for both the outer and nested searches 1165 assertThat(searchResultPage.getResults()).hasSize(2); 1166 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(person); 1167 assertThat(searchResultPage.getResults().get(1).getGenericDocument()).isEqualTo(call1); 1168 SearchResult result = searchResultPage.getResults().get(0); 1169 assertThat(result.getJoinedResults()).hasSize(1); 1170 assertThat(result.getJoinedResults().get(0).getGenericDocument()).isEqualTo(call1); 1171 1172 // Valid parent spec, but child spec package filters only contain other packages 1173 nested = 1174 new SearchSpec.Builder() 1175 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 1176 .addFilterPackageNames("package2", "package3") 1177 .setOrder(SearchSpec.ORDER_ASCENDING) 1178 .build(); 1179 join = new JoinSpec.Builder("personId").setNestedSearch("", nested).build(); 1180 searchSpec = 1181 new SearchSpec.Builder() 1182 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1183 .addFilterPackageNames("package1") 1184 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 1185 .setOrder(SearchSpec.ORDER_ASCENDING) 1186 .setJoinSpec(join) 1187 .build(); 1188 searchResultPage = 1189 mAppSearchImpl.query("package1", "database1", "", searchSpec, /* logger= */ null); 1190 1191 // Package1 documents should be returned, but no packages should be joined 1192 assertThat(searchResultPage.getResults()).hasSize(2); 1193 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(person); 1194 assertThat(searchResultPage.getResults().get(1).getGenericDocument()).isEqualTo(call1); 1195 result = searchResultPage.getResults().get(0); 1196 assertThat(result.getJoinedResults()).isEmpty(); 1197 1198 // Valid parent spec, empty child spec package filters 1199 nested = 1200 new SearchSpec.Builder() 1201 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 1202 .setOrder(SearchSpec.ORDER_ASCENDING) 1203 .build(); 1204 join = new JoinSpec.Builder("personId").setNestedSearch("", nested).build(); 1205 searchSpec = 1206 new SearchSpec.Builder() 1207 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1208 .addFilterPackageNames("package1") 1209 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 1210 .setOrder(SearchSpec.ORDER_ASCENDING) 1211 .setJoinSpec(join) 1212 .build(); 1213 searchResultPage = 1214 mAppSearchImpl.query("package1", "database1", "", searchSpec, /* logger= */ null); 1215 1216 // Only package1 documents should be returned, for both the outer and nested searches 1217 assertThat(searchResultPage.getResults()).hasSize(2); 1218 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(person); 1219 assertThat(searchResultPage.getResults().get(1).getGenericDocument()).isEqualTo(call1); 1220 result = searchResultPage.getResults().get(0); 1221 assertThat(result.getJoinedResults()).hasSize(1); 1222 assertThat(result.getJoinedResults().get(0).getGenericDocument()).isEqualTo(call1); 1223 1224 // Valid parent spec filter with valid child spec filter 1225 nested = new SearchSpec.Builder().build(); 1226 join = new JoinSpec.Builder("personId").setNestedSearch("", nested).build(); 1227 searchSpec = 1228 new SearchSpec.Builder() 1229 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1230 .addFilterPackageNames("package1") 1231 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_CREATION_TIMESTAMP) 1232 .setOrder(SearchSpec.ORDER_ASCENDING) 1233 .setJoinSpec(join) 1234 .build(); 1235 searchResultPage = 1236 mAppSearchImpl.query("package1", "database1", "", searchSpec, /* logger= */ null); 1237 // Should work as expected 1238 assertThat(searchResultPage.getResults()).hasSize(2); 1239 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(person); 1240 assertThat(searchResultPage.getResults().get(1).getGenericDocument()).isEqualTo(call1); 1241 result = searchResultPage.getResults().get(0); 1242 assertThat(result.getJoinedResults()).hasSize(1); 1243 assertThat(result.getJoinedResults().get(0).getGenericDocument()).isEqualTo(call1); 1244 } 1245 1246 @Test testSearchSuggestion()1247 public void testSearchSuggestion() throws Exception { 1248 // Insert schema 1249 List<AppSearchSchema> schemas = 1250 Collections.singletonList( 1251 new AppSearchSchema.Builder("type") 1252 .addProperty( 1253 new AppSearchSchema.StringPropertyConfig.Builder("body") 1254 .setIndexingType( 1255 AppSearchSchema.StringPropertyConfig 1256 .INDEXING_TYPE_PREFIXES) 1257 .setTokenizerType( 1258 AppSearchSchema.StringPropertyConfig 1259 .TOKENIZER_TYPE_PLAIN) 1260 .build()) 1261 .build()); 1262 mAppSearchImpl.setSchema( 1263 "package", 1264 "database", 1265 schemas, 1266 /* visibilityConfigs= */ Collections.emptyList(), 1267 /* forceOverride= */ false, 1268 /* version= */ 0, 1269 /* setSchemaStatsBuilder= */ null); 1270 1271 // Insert three documents. 1272 GenericDocument doc1 = 1273 new GenericDocument.Builder<>("namespace", "id1", "type") 1274 .setPropertyString("body", "termOne") 1275 .build(); 1276 GenericDocument doc2 = 1277 new GenericDocument.Builder<>("namespace", "id2", "type") 1278 .setPropertyString("body", "termOne termTwo") 1279 .build(); 1280 GenericDocument doc3 = 1281 new GenericDocument.Builder<>("namespace", "id3", "type") 1282 .setPropertyString("body", "termOne termTwo termThree") 1283 .build(); 1284 mAppSearchImpl.putDocument( 1285 "package", 1286 "database", 1287 doc1, 1288 /* sendChangeNotifications= */ false, 1289 /* logger= */ null); 1290 mAppSearchImpl.putDocument( 1291 "package", 1292 "database", 1293 doc2, 1294 /* sendChangeNotifications= */ false, 1295 /* logger= */ null); 1296 mAppSearchImpl.putDocument( 1297 "package", 1298 "database", 1299 doc3, 1300 /* sendChangeNotifications= */ false, 1301 /* logger= */ null); 1302 1303 List<SearchSuggestionResult> suggestions = 1304 mAppSearchImpl.searchSuggestion( 1305 "package", 1306 "database", 1307 /* suggestionQueryExpression= */ "t", 1308 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10).build()); 1309 assertThat(suggestions).hasSize(3); 1310 assertThat(suggestions.get(0).getSuggestedResult()).isEqualTo("termone"); 1311 assertThat(suggestions.get(1).getSuggestedResult()).isEqualTo("termtwo"); 1312 assertThat(suggestions.get(2).getSuggestedResult()).isEqualTo("termthree"); 1313 1314 // Set total result count to be 2. 1315 suggestions = 1316 mAppSearchImpl.searchSuggestion( 1317 "package", 1318 "database", 1319 /* suggestionQueryExpression= */ "t", 1320 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 2).build()); 1321 assertThat(suggestions).hasSize(2); 1322 assertThat(suggestions.get(0).getSuggestedResult()).isEqualTo("termone"); 1323 assertThat(suggestions.get(1).getSuggestedResult()).isEqualTo("termtwo"); 1324 } 1325 1326 @Test testSearchSuggestion_removeDocument()1327 public void testSearchSuggestion_removeDocument() throws Exception { 1328 // Insert schema 1329 List<AppSearchSchema> schemas = 1330 Collections.singletonList( 1331 new AppSearchSchema.Builder("type") 1332 .addProperty( 1333 new AppSearchSchema.StringPropertyConfig.Builder("body") 1334 .setIndexingType( 1335 AppSearchSchema.StringPropertyConfig 1336 .INDEXING_TYPE_PREFIXES) 1337 .setTokenizerType( 1338 AppSearchSchema.StringPropertyConfig 1339 .TOKENIZER_TYPE_PLAIN) 1340 .build()) 1341 .build()); 1342 mAppSearchImpl.setSchema( 1343 "package", 1344 "database", 1345 schemas, 1346 /* visibilityConfigs= */ Collections.emptyList(), 1347 /* forceOverride= */ false, 1348 /* version= */ 0, 1349 /* setSchemaStatsBuilder= */ null); 1350 1351 // Insert a document. 1352 GenericDocument doc1 = 1353 new GenericDocument.Builder<>("namespace", "id1", "type") 1354 .setPropertyString("body", "termOne") 1355 .build(); 1356 mAppSearchImpl.putDocument( 1357 "package", 1358 "database", 1359 doc1, 1360 /* sendChangeNotifications= */ false, 1361 /* logger= */ null); 1362 1363 List<SearchSuggestionResult> suggestions = 1364 mAppSearchImpl.searchSuggestion( 1365 "package", 1366 "database", 1367 /* suggestionQueryExpression= */ "t", 1368 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10).build()); 1369 assertThat(suggestions).hasSize(1); 1370 assertThat(suggestions.get(0).getSuggestedResult()).isEqualTo("termone"); 1371 1372 // Remove the document. 1373 mAppSearchImpl.remove( 1374 "package", "database", "namespace", "id1", /* removeStatsBuilder= */ null); 1375 1376 // Now we cannot find any suggestion 1377 suggestions = 1378 mAppSearchImpl.searchSuggestion( 1379 "package", 1380 "database", 1381 /* suggestionQueryExpression= */ "t", 1382 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10).build()); 1383 assertThat(suggestions).isEmpty(); 1384 } 1385 1386 @Test testSearchSuggestion_replaceDocument()1387 public void testSearchSuggestion_replaceDocument() throws Exception { 1388 // Insert schema 1389 List<AppSearchSchema> schemas = 1390 Collections.singletonList( 1391 new AppSearchSchema.Builder("type") 1392 .addProperty( 1393 new AppSearchSchema.StringPropertyConfig.Builder("body") 1394 .setIndexingType( 1395 AppSearchSchema.StringPropertyConfig 1396 .INDEXING_TYPE_PREFIXES) 1397 .setTokenizerType( 1398 AppSearchSchema.StringPropertyConfig 1399 .TOKENIZER_TYPE_PLAIN) 1400 .build()) 1401 .build()); 1402 mAppSearchImpl.setSchema( 1403 "package", 1404 "database", 1405 schemas, 1406 /* visibilityConfigs= */ Collections.emptyList(), 1407 /* forceOverride= */ false, 1408 /* version= */ 0, 1409 /* setSchemaStatsBuilder= */ null); 1410 1411 // Insert a document. 1412 GenericDocument doc1 = 1413 new GenericDocument.Builder<>("namespace", "id1", "type") 1414 .setPropertyString("body", "tart two three") 1415 .build(); 1416 mAppSearchImpl.putDocument( 1417 "package", 1418 "database", 1419 doc1, 1420 /* sendChangeNotifications= */ false, 1421 /* logger= */ null); 1422 SearchSuggestionResult tartResult = 1423 new SearchSuggestionResult.Builder().setSuggestedResult("tart").build(); 1424 SearchSuggestionResult twoResult = 1425 new SearchSuggestionResult.Builder().setSuggestedResult("two").build(); 1426 SearchSuggestionResult threeResult = 1427 new SearchSuggestionResult.Builder().setSuggestedResult("three").build(); 1428 SearchSuggestionResult twistResult = 1429 new SearchSuggestionResult.Builder().setSuggestedResult("twist").build(); 1430 List<SearchSuggestionResult> suggestions = 1431 mAppSearchImpl.searchSuggestion( 1432 "package", 1433 "database", 1434 /* suggestionQueryExpression= */ "t", 1435 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10).build()); 1436 assertThat(suggestions).containsExactly(tartResult, twoResult, threeResult); 1437 1438 // replace the document with two terms. 1439 GenericDocument replaceDocument = 1440 new GenericDocument.Builder<>("namespace", "id1", "type") 1441 .setPropertyString("body", "twist three") 1442 .build(); 1443 mAppSearchImpl.putDocument( 1444 "package", 1445 "database", 1446 replaceDocument, 1447 /* sendChangeNotifications= */ false, 1448 /* logger= */ null); 1449 1450 // Now we cannot find any suggestion 1451 suggestions = 1452 mAppSearchImpl.searchSuggestion( 1453 "package", 1454 "database", 1455 /* suggestionQueryExpression= */ "t", 1456 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10).build()); 1457 assertThat(suggestions).containsExactly(twistResult, threeResult); 1458 } 1459 1460 @Test testSearchSuggestion_namespaceFilter()1461 public void testSearchSuggestion_namespaceFilter() throws Exception { 1462 // Insert schema 1463 List<AppSearchSchema> schemas = 1464 Collections.singletonList( 1465 new AppSearchSchema.Builder("type") 1466 .addProperty( 1467 new AppSearchSchema.StringPropertyConfig.Builder("body") 1468 .setIndexingType( 1469 AppSearchSchema.StringPropertyConfig 1470 .INDEXING_TYPE_PREFIXES) 1471 .setTokenizerType( 1472 AppSearchSchema.StringPropertyConfig 1473 .TOKENIZER_TYPE_PLAIN) 1474 .build()) 1475 .build()); 1476 mAppSearchImpl.setSchema( 1477 "package", 1478 "database", 1479 schemas, 1480 /* visibilityConfigs= */ Collections.emptyList(), 1481 /* forceOverride= */ false, 1482 /* version= */ 0, 1483 /* setSchemaStatsBuilder= */ null); 1484 1485 // Insert three documents. 1486 GenericDocument doc1 = 1487 new GenericDocument.Builder<>("namespace1", "id1", "type") 1488 .setPropertyString("body", "term1") 1489 .build(); 1490 GenericDocument doc2 = 1491 new GenericDocument.Builder<>("namespace2", "id2", "type") 1492 .setPropertyString("body", "term1 term2") 1493 .build(); 1494 GenericDocument doc3 = 1495 new GenericDocument.Builder<>("namespace3", "id3", "type") 1496 .setPropertyString("body", "term1 term2 term3") 1497 .build(); 1498 1499 mAppSearchImpl.putDocument( 1500 "package", 1501 "database", 1502 doc1, 1503 /* sendChangeNotifications= */ false, 1504 /* logger= */ null); 1505 mAppSearchImpl.putDocument( 1506 "package", 1507 "database", 1508 doc2, 1509 /* sendChangeNotifications= */ false, 1510 /* logger= */ null); 1511 mAppSearchImpl.putDocument( 1512 "package", 1513 "database", 1514 doc3, 1515 /* sendChangeNotifications= */ false, 1516 /* logger= */ null); 1517 1518 List<SearchSuggestionResult> suggestions = 1519 mAppSearchImpl.searchSuggestion( 1520 "package", 1521 "database", 1522 /* suggestionQueryExpression= */ "t", 1523 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10) 1524 .addFilterNamespaces("namespace1") 1525 .build()); 1526 assertThat(suggestions).hasSize(1); 1527 assertThat(suggestions.get(0).getSuggestedResult()).isEqualTo("term1"); 1528 1529 suggestions = 1530 mAppSearchImpl.searchSuggestion( 1531 "package", 1532 "database", 1533 /* suggestionQueryExpression= */ "t", 1534 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10) 1535 .addFilterNamespaces("namespace1", "namespace2") 1536 .build()); 1537 assertThat(suggestions).hasSize(2); 1538 assertThat(suggestions.get(0).getSuggestedResult()).isEqualTo("term1"); 1539 assertThat(suggestions.get(1).getSuggestedResult()).isEqualTo("term2"); 1540 } 1541 1542 @Ignore("b/273733335") 1543 @Test testSearchSuggestion_invalidPrefix()1544 public void testSearchSuggestion_invalidPrefix() throws Exception { 1545 // Insert schema just put something in the AppSearch to make it searchable. 1546 List<AppSearchSchema> schemas = 1547 Collections.singletonList( 1548 new AppSearchSchema.Builder("type") 1549 .addProperty( 1550 new AppSearchSchema.StringPropertyConfig.Builder("body") 1551 .setIndexingType( 1552 AppSearchSchema.StringPropertyConfig 1553 .INDEXING_TYPE_PREFIXES) 1554 .setTokenizerType( 1555 AppSearchSchema.StringPropertyConfig 1556 .TOKENIZER_TYPE_PLAIN) 1557 .build()) 1558 .build()); 1559 mAppSearchImpl.setSchema( 1560 "package", 1561 "database", 1562 schemas, 1563 /* visibilityConfigs= */ Collections.emptyList(), 1564 /* forceOverride= */ false, 1565 /* version= */ 0, 1566 /* setSchemaStatsBuilder= */ null); 1567 GenericDocument doc = 1568 new GenericDocument.Builder<>("namespace1", "id1", "type") 1569 .setPropertyString("body", "term1") 1570 .build(); 1571 mAppSearchImpl.putDocument( 1572 "package", 1573 "database", 1574 doc, 1575 /* sendChangeNotifications= */ false, 1576 /* logger= */ null); 1577 1578 List<SearchSuggestionResult> suggestions = 1579 mAppSearchImpl.searchSuggestion( 1580 "package", 1581 "database", 1582 /* suggestionQueryExpression= */ "t:", 1583 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10).build()); 1584 assertThat(suggestions).isEmpty(); 1585 suggestions = 1586 mAppSearchImpl.searchSuggestion( 1587 "package", 1588 "database", 1589 /* suggestionQueryExpression= */ "t-", 1590 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10).build()); 1591 assertThat(suggestions).isEmpty(); 1592 suggestions = 1593 mAppSearchImpl.searchSuggestion( 1594 "package", 1595 "database", 1596 /* suggestionQueryExpression= */ "t ", 1597 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10).build()); 1598 assertThat(suggestions).isEmpty(); 1599 suggestions = 1600 mAppSearchImpl.searchSuggestion( 1601 "package", 1602 "database", 1603 /* suggestionQueryExpression= */ "{t}", 1604 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10).build()); 1605 assertThat(suggestions).isEmpty(); 1606 suggestions = 1607 mAppSearchImpl.searchSuggestion( 1608 "package", 1609 "database", 1610 /* suggestionQueryExpression= */ "(t)", 1611 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10).build()); 1612 assertThat(suggestions).isEmpty(); 1613 } 1614 1615 @Test testSearchSuggestion_emptyPrefix()1616 public void testSearchSuggestion_emptyPrefix() throws Exception { 1617 // Insert schema just put something in the AppSearch to make it searchable. 1618 List<AppSearchSchema> schemas = 1619 Collections.singletonList( 1620 new AppSearchSchema.Builder("type") 1621 .addProperty( 1622 new AppSearchSchema.StringPropertyConfig.Builder("body") 1623 .setIndexingType( 1624 AppSearchSchema.StringPropertyConfig 1625 .INDEXING_TYPE_PREFIXES) 1626 .setTokenizerType( 1627 AppSearchSchema.StringPropertyConfig 1628 .TOKENIZER_TYPE_PLAIN) 1629 .build()) 1630 .build()); 1631 mAppSearchImpl.setSchema( 1632 "package", 1633 "database", 1634 schemas, 1635 /* visibilityConfigs= */ Collections.emptyList(), 1636 /* forceOverride= */ false, 1637 /* version= */ 0, 1638 /* setSchemaStatsBuilder= */ null); 1639 GenericDocument doc = 1640 new GenericDocument.Builder<>("namespace1", "id1", "type") 1641 .setPropertyString("body", "term1") 1642 .build(); 1643 mAppSearchImpl.putDocument( 1644 "package", 1645 "database", 1646 doc, 1647 /* sendChangeNotifications= */ false, 1648 /* logger= */ null); 1649 1650 AppSearchException e = 1651 assertThrows( 1652 AppSearchException.class, 1653 () -> 1654 mAppSearchImpl.searchSuggestion( 1655 "package", 1656 "database", 1657 /* suggestionQueryExpression= */ "", 1658 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10) 1659 .addFilterNamespaces("namespace1") 1660 .build())); 1661 assertThat(e.getResultCode()).isEqualTo(RESULT_INVALID_ARGUMENT); 1662 assertThat(e).hasMessageThat().contains("suggestionQueryExpression cannot be empty."); 1663 } 1664 1665 @Test testGetNextPageToken_query()1666 public void testGetNextPageToken_query() throws Exception { 1667 // Insert package1 schema 1668 List<AppSearchSchema> schema1 = 1669 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 1670 InternalSetSchemaResponse internalSetSchemaResponse = 1671 mAppSearchImpl.setSchema( 1672 "package1", 1673 "database1", 1674 schema1, 1675 /* visibilityConfigs= */ Collections.emptyList(), 1676 /* forceOverride= */ false, 1677 /* version= */ 0, 1678 /* setSchemaStatsBuilder= */ null); 1679 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 1680 1681 // Insert two package1 documents 1682 GenericDocument document1 = 1683 new GenericDocument.Builder<>("namespace", "id1", "schema1").build(); 1684 GenericDocument document2 = 1685 new GenericDocument.Builder<>("namespace", "id2", "schema1").build(); 1686 mAppSearchImpl.putDocument( 1687 "package1", 1688 "database1", 1689 document1, 1690 /* sendChangeNotifications= */ false, 1691 /* logger= */ null); 1692 mAppSearchImpl.putDocument( 1693 "package1", 1694 "database1", 1695 document2, 1696 /* sendChangeNotifications= */ false, 1697 /* logger= */ null); 1698 1699 // Query for only 1 result per page 1700 SearchSpec searchSpec = 1701 new SearchSpec.Builder() 1702 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1703 .setResultCountPerPage(1) 1704 .build(); 1705 SearchResultPage searchResultPage = 1706 mAppSearchImpl.query("package1", "database1", "", searchSpec, /* logger= */ null); 1707 1708 // Document2 will come first because it was inserted last and default return order is 1709 // most recent. 1710 assertThat(searchResultPage.getResults()).hasSize(1); 1711 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); 1712 1713 long nextPageToken = searchResultPage.getNextPageToken(); 1714 searchResultPage = 1715 mAppSearchImpl.getNextPage("package1", nextPageToken, /* statsBuilder= */ null); 1716 assertThat(searchResultPage.getResults()).hasSize(1); 1717 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); 1718 } 1719 1720 @Test testGetNextPageWithDifferentPackage_query()1721 public void testGetNextPageWithDifferentPackage_query() throws Exception { 1722 // Insert package1 schema 1723 List<AppSearchSchema> schema1 = 1724 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 1725 InternalSetSchemaResponse internalSetSchemaResponse = 1726 mAppSearchImpl.setSchema( 1727 "package1", 1728 "database1", 1729 schema1, 1730 /* visibilityConfigs= */ Collections.emptyList(), 1731 /* forceOverride= */ false, 1732 /* version= */ 0, 1733 /* setSchemaStatsBuilder= */ null); 1734 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 1735 1736 // Insert two package1 documents 1737 GenericDocument document1 = 1738 new GenericDocument.Builder<>("namespace", "id1", "schema1").build(); 1739 GenericDocument document2 = 1740 new GenericDocument.Builder<>("namespace", "id2", "schema1").build(); 1741 mAppSearchImpl.putDocument( 1742 "package1", 1743 "database1", 1744 document1, 1745 /* sendChangeNotifications= */ false, 1746 /* logger= */ null); 1747 mAppSearchImpl.putDocument( 1748 "package1", 1749 "database1", 1750 document2, 1751 /* sendChangeNotifications= */ false, 1752 /* logger= */ null); 1753 1754 // Query for only 1 result per page 1755 SearchSpec searchSpec = 1756 new SearchSpec.Builder() 1757 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1758 .setResultCountPerPage(1) 1759 .build(); 1760 SearchResultPage searchResultPage = 1761 mAppSearchImpl.query("package1", "database1", "", searchSpec, /* logger= */ null); 1762 1763 // Document2 will come first because it was inserted last and default return order is 1764 // most recent. 1765 assertThat(searchResultPage.getResults()).hasSize(1); 1766 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); 1767 1768 long nextPageToken = searchResultPage.getNextPageToken(); 1769 1770 // Try getting next page with the wrong package, package2 1771 AppSearchException e = 1772 assertThrows( 1773 AppSearchException.class, 1774 () -> 1775 mAppSearchImpl.getNextPage( 1776 "package2", nextPageToken, /* statsBuilder= */ null)); 1777 assertThat(e) 1778 .hasMessageThat() 1779 .contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken); 1780 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR); 1781 1782 // Can continue getting next page for package1 1783 searchResultPage = 1784 mAppSearchImpl.getNextPage("package1", nextPageToken, /* statsBuilder= */ null); 1785 assertThat(searchResultPage.getResults()).hasSize(1); 1786 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); 1787 } 1788 1789 @Test testGetNextPageToken_globalQuery()1790 public void testGetNextPageToken_globalQuery() throws Exception { 1791 // Insert package1 schema 1792 List<AppSearchSchema> schema1 = 1793 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 1794 InternalSetSchemaResponse internalSetSchemaResponse = 1795 mAppSearchImpl.setSchema( 1796 "package1", 1797 "database1", 1798 schema1, 1799 /* visibilityConfigs= */ Collections.emptyList(), 1800 /* forceOverride= */ false, 1801 /* version= */ 0, 1802 /* setSchemaStatsBuilder= */ null); 1803 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 1804 1805 // Insert two package1 documents 1806 GenericDocument document1 = 1807 new GenericDocument.Builder<>("namespace", "id1", "schema1").build(); 1808 GenericDocument document2 = 1809 new GenericDocument.Builder<>("namespace", "id2", "schema1").build(); 1810 mAppSearchImpl.putDocument( 1811 "package1", 1812 "database1", 1813 document1, 1814 /* sendChangeNotifications= */ false, 1815 /* logger= */ null); 1816 mAppSearchImpl.putDocument( 1817 "package1", 1818 "database1", 1819 document2, 1820 /* sendChangeNotifications= */ false, 1821 /* logger= */ null); 1822 1823 // Query for only 1 result per page 1824 SearchSpec searchSpec = 1825 new SearchSpec.Builder() 1826 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1827 .setResultCountPerPage(1) 1828 .build(); 1829 SearchResultPage searchResultPage = 1830 mAppSearchImpl.globalQuery( 1831 /* queryExpression= */ "", 1832 searchSpec, 1833 new CallerAccess(/* callingPackageName= */ "package1"), 1834 /* logger= */ null); 1835 1836 // Document2 will come first because it was inserted last and default return order is 1837 // most recent. 1838 assertThat(searchResultPage.getResults()).hasSize(1); 1839 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); 1840 1841 long nextPageToken = searchResultPage.getNextPageToken(); 1842 searchResultPage = 1843 mAppSearchImpl.getNextPage("package1", nextPageToken, /* statsBuilder= */ null); 1844 assertThat(searchResultPage.getResults()).hasSize(1); 1845 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); 1846 } 1847 1848 @Test testGetNextPageWithDifferentPackage_globalQuery()1849 public void testGetNextPageWithDifferentPackage_globalQuery() throws Exception { 1850 // Insert package1 schema 1851 List<AppSearchSchema> schema1 = 1852 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 1853 InternalSetSchemaResponse internalSetSchemaResponse = 1854 mAppSearchImpl.setSchema( 1855 "package1", 1856 "database1", 1857 schema1, 1858 /* visibilityConfigs= */ Collections.emptyList(), 1859 /* forceOverride= */ false, 1860 /* version= */ 0, 1861 /* setSchemaStatsBuilder= */ null); 1862 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 1863 1864 // Insert two package1 documents 1865 GenericDocument document1 = 1866 new GenericDocument.Builder<>("namespace", "id1", "schema1").build(); 1867 GenericDocument document2 = 1868 new GenericDocument.Builder<>("namespace", "id2", "schema1").build(); 1869 mAppSearchImpl.putDocument( 1870 "package1", 1871 "database1", 1872 document1, 1873 /* sendChangeNotifications= */ false, 1874 /* logger= */ null); 1875 mAppSearchImpl.putDocument( 1876 "package1", 1877 "database1", 1878 document2, 1879 /* sendChangeNotifications= */ false, 1880 /* logger= */ null); 1881 1882 // Query for only 1 result per page 1883 SearchSpec searchSpec = 1884 new SearchSpec.Builder() 1885 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1886 .setResultCountPerPage(1) 1887 .build(); 1888 SearchResultPage searchResultPage = 1889 mAppSearchImpl.globalQuery( 1890 /* queryExpression= */ "", 1891 searchSpec, 1892 new CallerAccess(/* callingPackageName= */ "package1"), 1893 /* logger= */ null); 1894 1895 // Document2 will come first because it was inserted last and default return order is 1896 // most recent. 1897 assertThat(searchResultPage.getResults()).hasSize(1); 1898 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); 1899 1900 long nextPageToken = searchResultPage.getNextPageToken(); 1901 1902 // Try getting next page with the wrong package, package2 1903 AppSearchException e = 1904 assertThrows( 1905 AppSearchException.class, 1906 () -> 1907 mAppSearchImpl.getNextPage( 1908 "package2", nextPageToken, /* statsBuilder= */ null)); 1909 assertThat(e) 1910 .hasMessageThat() 1911 .contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken); 1912 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR); 1913 1914 // Can continue getting next page for package1 1915 searchResultPage = 1916 mAppSearchImpl.getNextPage("package1", nextPageToken, /* statsBuilder= */ null); 1917 assertThat(searchResultPage.getResults()).hasSize(1); 1918 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); 1919 } 1920 1921 @Test testInvalidateNextPageToken_query()1922 public void testInvalidateNextPageToken_query() throws Exception { 1923 // Insert package1 schema 1924 List<AppSearchSchema> schema1 = 1925 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 1926 InternalSetSchemaResponse internalSetSchemaResponse = 1927 mAppSearchImpl.setSchema( 1928 "package1", 1929 "database1", 1930 schema1, 1931 /* visibilityConfigs= */ Collections.emptyList(), 1932 /* forceOverride= */ false, 1933 /* version= */ 0, 1934 /* setSchemaStatsBuilder= */ null); 1935 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 1936 1937 // Insert two package1 documents 1938 GenericDocument document1 = 1939 new GenericDocument.Builder<>("namespace", "id1", "schema1").build(); 1940 GenericDocument document2 = 1941 new GenericDocument.Builder<>("namespace", "id2", "schema1").build(); 1942 mAppSearchImpl.putDocument( 1943 "package1", 1944 "database1", 1945 document1, 1946 /* sendChangeNotifications= */ false, 1947 /* logger= */ null); 1948 mAppSearchImpl.putDocument( 1949 "package1", 1950 "database1", 1951 document2, 1952 /* sendChangeNotifications= */ false, 1953 /* logger= */ null); 1954 1955 // Query for only 1 result per page 1956 SearchSpec searchSpec = 1957 new SearchSpec.Builder() 1958 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 1959 .setResultCountPerPage(1) 1960 .build(); 1961 SearchResultPage searchResultPage = 1962 mAppSearchImpl.query("package1", "database1", "", searchSpec, /* logger= */ null); 1963 1964 // Document2 will come first because it was inserted last and default return order is 1965 // most recent. 1966 assertThat(searchResultPage.getResults()).hasSize(1); 1967 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); 1968 1969 long nextPageToken = searchResultPage.getNextPageToken(); 1970 1971 // Invalidate the token 1972 mAppSearchImpl.invalidateNextPageToken("package1", nextPageToken); 1973 1974 // Can't get next page because we invalidated the token. 1975 AppSearchException e = 1976 assertThrows( 1977 AppSearchException.class, 1978 () -> 1979 mAppSearchImpl.getNextPage( 1980 "package1", nextPageToken, /* statsBuilder= */ null)); 1981 assertThat(e) 1982 .hasMessageThat() 1983 .contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken); 1984 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR); 1985 } 1986 1987 @Test testInvalidateNextPageToken_zeroNextPageToken()1988 public void testInvalidateNextPageToken_zeroNextPageToken() throws Exception { 1989 // Insert package1 schema 1990 List<AppSearchSchema> schema1 = 1991 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 1992 InternalSetSchemaResponse internalSetSchemaResponse = 1993 mAppSearchImpl.setSchema( 1994 "package1", 1995 "database1", 1996 schema1, 1997 /* visibilityConfigs= */ Collections.emptyList(), 1998 /* forceOverride= */ false, 1999 /* version= */ 0, 2000 /* setSchemaStatsBuilder= */ null); 2001 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2002 2003 // Insert one package1 documents 2004 GenericDocument document1 = 2005 new GenericDocument.Builder<>("namespace", "id1", "schema1").build(); 2006 mAppSearchImpl.putDocument( 2007 "package1", 2008 "database1", 2009 document1, 2010 /* sendChangeNotifications= */ false, 2011 /* logger= */ null); 2012 2013 // Query for 2 results per page, so all the results can fit in one page. 2014 SearchSpec searchSpec = 2015 new SearchSpec.Builder() 2016 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 2017 .setResultCountPerPage( 2018 2) // make sure all the results can be returned in one page. 2019 .build(); 2020 SearchResultPage searchResultPage = 2021 mAppSearchImpl.query("package1", "database1", "", searchSpec, /* logger= */ null); 2022 2023 // We only have one document indexed 2024 assertThat(searchResultPage.getResults()).hasSize(1); 2025 2026 // nextPageToken should be 0 since there is no more results 2027 long nextPageToken = searchResultPage.getNextPageToken(); 2028 assertThat(nextPageToken).isEqualTo(0); 2029 2030 // Invalidate the token, no exception should be thrown 2031 mAppSearchImpl.invalidateNextPageToken("package1", nextPageToken); 2032 } 2033 2034 @Test testInvalidateNextPageTokenWithDifferentPackage_query()2035 public void testInvalidateNextPageTokenWithDifferentPackage_query() throws Exception { 2036 // Insert package1 schema 2037 List<AppSearchSchema> schema1 = 2038 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 2039 InternalSetSchemaResponse internalSetSchemaResponse = 2040 mAppSearchImpl.setSchema( 2041 "package1", 2042 "database1", 2043 schema1, 2044 /* visibilityConfigs= */ Collections.emptyList(), 2045 /* forceOverride= */ false, 2046 /* version= */ 0, 2047 /* setSchemaStatsBuilder= */ null); 2048 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2049 2050 // Insert two package1 documents 2051 GenericDocument document1 = 2052 new GenericDocument.Builder<>("namespace", "id1", "schema1").build(); 2053 GenericDocument document2 = 2054 new GenericDocument.Builder<>("namespace", "id2", "schema1").build(); 2055 mAppSearchImpl.putDocument( 2056 "package1", 2057 "database1", 2058 document1, 2059 /* sendChangeNotifications= */ false, 2060 /* logger= */ null); 2061 mAppSearchImpl.putDocument( 2062 "package1", 2063 "database1", 2064 document2, 2065 /* sendChangeNotifications= */ false, 2066 /* logger= */ null); 2067 2068 // Query for only 1 result per page 2069 SearchSpec searchSpec = 2070 new SearchSpec.Builder() 2071 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 2072 .setResultCountPerPage(1) 2073 .build(); 2074 SearchResultPage searchResultPage = 2075 mAppSearchImpl.query("package1", "database1", "", searchSpec, /* logger= */ null); 2076 2077 // Document2 will come first because it was inserted last and default return order is 2078 // most recent. 2079 assertThat(searchResultPage.getResults()).hasSize(1); 2080 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); 2081 2082 long nextPageToken = searchResultPage.getNextPageToken(); 2083 2084 // Try getting next page with the wrong package, package2 2085 AppSearchException e = 2086 assertThrows( 2087 AppSearchException.class, 2088 () -> mAppSearchImpl.invalidateNextPageToken("package2", nextPageToken)); 2089 assertThat(e) 2090 .hasMessageThat() 2091 .contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken); 2092 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR); 2093 2094 // Can continue getting next page for package1 2095 searchResultPage = 2096 mAppSearchImpl.getNextPage("package1", nextPageToken, /* statsBuilder= */ null); 2097 assertThat(searchResultPage.getResults()).hasSize(1); 2098 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); 2099 } 2100 2101 @Test testInvalidateNextPageToken_globalQuery()2102 public void testInvalidateNextPageToken_globalQuery() throws Exception { 2103 // Insert package1 schema 2104 List<AppSearchSchema> schema1 = 2105 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 2106 InternalSetSchemaResponse internalSetSchemaResponse = 2107 mAppSearchImpl.setSchema( 2108 "package1", 2109 "database1", 2110 schema1, 2111 /* visibilityConfigs= */ Collections.emptyList(), 2112 /* forceOverride= */ false, 2113 /* version= */ 0, 2114 /* setSchemaStatsBuilder= */ null); 2115 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2116 2117 // Insert two package1 documents 2118 GenericDocument document1 = 2119 new GenericDocument.Builder<>("namespace", "id1", "schema1").build(); 2120 GenericDocument document2 = 2121 new GenericDocument.Builder<>("namespace", "id2", "schema1").build(); 2122 mAppSearchImpl.putDocument( 2123 "package1", 2124 "database1", 2125 document1, 2126 /* sendChangeNotifications= */ false, 2127 /* logger= */ null); 2128 mAppSearchImpl.putDocument( 2129 "package1", 2130 "database1", 2131 document2, 2132 /* sendChangeNotifications= */ false, 2133 /* logger= */ null); 2134 2135 // Query for only 1 result per page 2136 SearchSpec searchSpec = 2137 new SearchSpec.Builder() 2138 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 2139 .setResultCountPerPage(1) 2140 .build(); 2141 SearchResultPage searchResultPage = 2142 mAppSearchImpl.globalQuery( 2143 /* queryExpression= */ "", 2144 searchSpec, 2145 new CallerAccess(/* callingPackageName= */ "package1"), 2146 /* logger= */ null); 2147 2148 // Document2 will come first because it was inserted last and default return order is 2149 // most recent. 2150 assertThat(searchResultPage.getResults()).hasSize(1); 2151 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); 2152 2153 long nextPageToken = searchResultPage.getNextPageToken(); 2154 2155 // Invalidate the token 2156 mAppSearchImpl.invalidateNextPageToken("package1", nextPageToken); 2157 2158 // Can't get next page because we invalidated the token. 2159 AppSearchException e = 2160 assertThrows( 2161 AppSearchException.class, 2162 () -> 2163 mAppSearchImpl.getNextPage( 2164 "package1", nextPageToken, /* statsBuilder= */ null)); 2165 assertThat(e) 2166 .hasMessageThat() 2167 .contains("Package \"package1\" cannot use nextPageToken: " + nextPageToken); 2168 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR); 2169 } 2170 2171 @Test testInvalidateNextPageTokenWithDifferentPackage_globalQuery()2172 public void testInvalidateNextPageTokenWithDifferentPackage_globalQuery() throws Exception { 2173 // Insert package1 schema 2174 List<AppSearchSchema> schema1 = 2175 ImmutableList.of(new AppSearchSchema.Builder("schema1").build()); 2176 InternalSetSchemaResponse internalSetSchemaResponse = 2177 mAppSearchImpl.setSchema( 2178 "package1", 2179 "database1", 2180 schema1, 2181 /* visibilityConfigs= */ Collections.emptyList(), 2182 /* forceOverride= */ false, 2183 /* version= */ 0, 2184 /* setSchemaStatsBuilder= */ null); 2185 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2186 2187 // Insert two package1 documents 2188 GenericDocument document1 = 2189 new GenericDocument.Builder<>("namespace", "id1", "schema1").build(); 2190 GenericDocument document2 = 2191 new GenericDocument.Builder<>("namespace", "id2", "schema1").build(); 2192 mAppSearchImpl.putDocument( 2193 "package1", 2194 "database1", 2195 document1, 2196 /* sendChangeNotifications= */ false, 2197 /* logger= */ null); 2198 mAppSearchImpl.putDocument( 2199 "package1", 2200 "database1", 2201 document2, 2202 /* sendChangeNotifications= */ false, 2203 /* logger= */ null); 2204 2205 // Query for only 1 result per page 2206 SearchSpec searchSpec = 2207 new SearchSpec.Builder() 2208 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 2209 .setResultCountPerPage(1) 2210 .build(); 2211 SearchResultPage searchResultPage = 2212 mAppSearchImpl.globalQuery( 2213 /* queryExpression= */ "", 2214 searchSpec, 2215 new CallerAccess(/* callingPackageName= */ "package1"), 2216 /* logger= */ null); 2217 2218 // Document2 will come first because it was inserted last and default return order is 2219 // most recent. 2220 assertThat(searchResultPage.getResults()).hasSize(1); 2221 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document2); 2222 2223 long nextPageToken = searchResultPage.getNextPageToken(); 2224 2225 // Try getting next page with the wrong package, package2 2226 AppSearchException e = 2227 assertThrows( 2228 AppSearchException.class, 2229 () -> mAppSearchImpl.invalidateNextPageToken("package2", nextPageToken)); 2230 assertThat(e) 2231 .hasMessageThat() 2232 .contains("Package \"package2\" cannot use nextPageToken: " + nextPageToken); 2233 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_SECURITY_ERROR); 2234 2235 // Can continue getting next page for package1 2236 searchResultPage = 2237 mAppSearchImpl.getNextPage("package1", nextPageToken, /* statsBuilder= */ null); 2238 assertThat(searchResultPage.getResults()).hasSize(1); 2239 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document1); 2240 } 2241 2242 @Test testRemoveEmptyDatabase_noExceptionThrown()2243 public void testRemoveEmptyDatabase_noExceptionThrown() throws Exception { 2244 SearchSpec searchSpec = 2245 new SearchSpec.Builder() 2246 .addFilterSchemas("FakeType") 2247 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 2248 .build(); 2249 mAppSearchImpl.removeByQuery( 2250 "package", "EmptyDatabase", "", searchSpec, /* statsBuilder= */ null); 2251 2252 searchSpec = 2253 new SearchSpec.Builder() 2254 .addFilterNamespaces("FakeNamespace") 2255 .setTermMatch(TermMatchType.Code.PREFIX_VALUE) 2256 .build(); 2257 mAppSearchImpl.removeByQuery( 2258 "package", "EmptyDatabase", "", searchSpec, /* statsBuilder= */ null); 2259 2260 searchSpec = new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build(); 2261 mAppSearchImpl.removeByQuery( 2262 "package", "EmptyDatabase", "", searchSpec, /* statsBuilder= */ null); 2263 } 2264 2265 @Test testSetSchema()2266 public void testSetSchema() throws Exception { 2267 List<SchemaTypeConfigProto> existingSchemas = 2268 mAppSearchImpl.getSchemaProtoLocked().getTypesList(); 2269 2270 List<AppSearchSchema> schemas = 2271 Collections.singletonList(new AppSearchSchema.Builder("Email").build()); 2272 // Set schema Email to AppSearch database1 2273 InternalSetSchemaResponse internalSetSchemaResponse = 2274 mAppSearchImpl.setSchema( 2275 "package", 2276 "database1", 2277 schemas, 2278 /* visibilityConfigs= */ Collections.emptyList(), 2279 /* forceOverride= */ false, 2280 /* version= */ 0, 2281 /* setSchemaStatsBuilder= */ null); 2282 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2283 2284 // Create expected schemaType proto. 2285 SchemaProto expectedProto = 2286 SchemaProto.newBuilder() 2287 .addTypes( 2288 SchemaTypeConfigProto.newBuilder() 2289 .setSchemaType("package$database1/Email") 2290 .setDescription("") 2291 .setVersion(0)) 2292 .build(); 2293 2294 List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>(); 2295 expectedTypes.addAll(existingSchemas); 2296 expectedTypes.addAll(expectedProto.getTypesList()); 2297 assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList()) 2298 .containsExactlyElementsIn(expectedTypes); 2299 } 2300 2301 @Test testSetSchema_incompatible()2302 public void testSetSchema_incompatible() throws Exception { 2303 List<AppSearchSchema> oldSchemas = new ArrayList<>(); 2304 oldSchemas.add( 2305 new AppSearchSchema.Builder("Email") 2306 .addProperty( 2307 new AppSearchSchema.StringPropertyConfig.Builder("foo") 2308 .setCardinality( 2309 AppSearchSchema.PropertyConfig.CARDINALITY_REQUIRED) 2310 .setTokenizerType( 2311 AppSearchSchema.StringPropertyConfig 2312 .TOKENIZER_TYPE_PLAIN) 2313 .setIndexingType( 2314 AppSearchSchema.StringPropertyConfig 2315 .INDEXING_TYPE_PREFIXES) 2316 .build()) 2317 .build()); 2318 oldSchemas.add(new AppSearchSchema.Builder("Text").build()); 2319 // Set schema Email to AppSearch database1 2320 InternalSetSchemaResponse internalSetSchemaResponse = 2321 mAppSearchImpl.setSchema( 2322 "package", 2323 "database1", 2324 oldSchemas, 2325 /* visibilityConfigs= */ Collections.emptyList(), 2326 /* forceOverride= */ false, 2327 /* version= */ 0, 2328 /* setSchemaStatsBuilder= */ null); 2329 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2330 2331 // Create incompatible schema 2332 List<AppSearchSchema> newSchemas = 2333 Collections.singletonList(new AppSearchSchema.Builder("Email").build()); 2334 2335 // set email incompatible and delete text 2336 internalSetSchemaResponse = 2337 mAppSearchImpl.setSchema( 2338 "package", 2339 "database1", 2340 newSchemas, 2341 /* visibilityConfigs= */ Collections.emptyList(), 2342 /* forceOverride= */ true, 2343 /* version= */ 0, 2344 /* setSchemaStatsBuilder= */ null); 2345 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2346 SetSchemaResponse setSchemaResponse = internalSetSchemaResponse.getSetSchemaResponse(); 2347 2348 assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Text"); 2349 assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Email"); 2350 } 2351 2352 @Test testRemoveSchema()2353 public void testRemoveSchema() throws Exception { 2354 List<SchemaTypeConfigProto> existingSchemas = 2355 mAppSearchImpl.getSchemaProtoLocked().getTypesList(); 2356 2357 List<AppSearchSchema> schemas = 2358 ImmutableList.of( 2359 new AppSearchSchema.Builder("Email").build(), 2360 new AppSearchSchema.Builder("Document").build()); 2361 // Set schema Email and Document to AppSearch database1 2362 InternalSetSchemaResponse internalSetSchemaResponse = 2363 mAppSearchImpl.setSchema( 2364 "package", 2365 "database1", 2366 schemas, 2367 /* visibilityConfigs= */ Collections.emptyList(), 2368 /* forceOverride= */ false, 2369 /* version= */ 0, 2370 /* setSchemaStatsBuilder= */ null); 2371 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2372 2373 // Create expected schemaType proto. 2374 SchemaProto expectedProto = 2375 SchemaProto.newBuilder() 2376 .addTypes( 2377 SchemaTypeConfigProto.newBuilder() 2378 .setSchemaType("package$database1/Email") 2379 .setDescription("") 2380 .setVersion(0)) 2381 .addTypes( 2382 SchemaTypeConfigProto.newBuilder() 2383 .setSchemaType("package$database1/Document") 2384 .setDescription("") 2385 .setVersion(0)) 2386 .build(); 2387 2388 // Check both schema Email and Document saved correctly. 2389 List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>(); 2390 expectedTypes.addAll(existingSchemas); 2391 expectedTypes.addAll(expectedProto.getTypesList()); 2392 assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList()) 2393 .containsExactlyElementsIn(expectedTypes); 2394 2395 final List<AppSearchSchema> finalSchemas = 2396 Collections.singletonList(new AppSearchSchema.Builder("Email").build()); 2397 internalSetSchemaResponse = 2398 mAppSearchImpl.setSchema( 2399 "package", 2400 "database1", 2401 finalSchemas, 2402 /* visibilityConfigs= */ Collections.emptyList(), 2403 /* forceOverride= */ false, 2404 /* version= */ 0, 2405 /* setSchemaStatsBuilder= */ null); 2406 // We are fail to set this call since forceOverride is false. 2407 assertThat(internalSetSchemaResponse.isSuccess()).isFalse(); 2408 SetSchemaResponse setSchemaResponse = internalSetSchemaResponse.getSetSchemaResponse(); 2409 // Check the incompatible reason is we are trying to delete Document type. 2410 assertThat(setSchemaResponse.getDeletedTypes()).containsExactly("Document"); 2411 2412 // ForceOverride to delete. 2413 internalSetSchemaResponse = 2414 mAppSearchImpl.setSchema( 2415 "package", 2416 "database1", 2417 finalSchemas, 2418 /* visibilityConfigs= */ Collections.emptyList(), 2419 /* forceOverride= */ true, 2420 /* version= */ 0, 2421 /* setSchemaStatsBuilder= */ null); 2422 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2423 2424 // Check Document schema is removed. 2425 expectedProto = 2426 SchemaProto.newBuilder() 2427 .addTypes( 2428 SchemaTypeConfigProto.newBuilder() 2429 .setSchemaType("package$database1/Email") 2430 .setDescription("") 2431 .setVersion(0)) 2432 .build(); 2433 2434 expectedTypes = new ArrayList<>(); 2435 expectedTypes.addAll(existingSchemas); 2436 expectedTypes.addAll(expectedProto.getTypesList()); 2437 assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList()) 2438 .containsExactlyElementsIn(expectedTypes); 2439 } 2440 2441 @Test testRemoveSchema_differentDataBase()2442 public void testRemoveSchema_differentDataBase() throws Exception { 2443 List<SchemaTypeConfigProto> existingSchemas = 2444 mAppSearchImpl.getSchemaProtoLocked().getTypesList(); 2445 2446 // Create schemas 2447 List<AppSearchSchema> schemas = 2448 ImmutableList.of( 2449 new AppSearchSchema.Builder("Email").build(), 2450 new AppSearchSchema.Builder("Document").build()); 2451 2452 // Set schema Email and Document to AppSearch database1 and 2 2453 InternalSetSchemaResponse internalSetSchemaResponse = 2454 mAppSearchImpl.setSchema( 2455 "package", 2456 "database1", 2457 schemas, 2458 /* visibilityConfigs= */ Collections.emptyList(), 2459 /* forceOverride= */ false, 2460 /* version= */ 0, 2461 /* setSchemaStatsBuilder= */ null); 2462 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2463 internalSetSchemaResponse = 2464 mAppSearchImpl.setSchema( 2465 "package", 2466 "database2", 2467 schemas, 2468 /* visibilityConfigs= */ Collections.emptyList(), 2469 /* forceOverride= */ false, 2470 /* version= */ 0, 2471 /* setSchemaStatsBuilder= */ null); 2472 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2473 2474 // Create expected schemaType proto. 2475 SchemaProto expectedProto = 2476 SchemaProto.newBuilder() 2477 .addTypes( 2478 SchemaTypeConfigProto.newBuilder() 2479 .setSchemaType("package$database1/Email") 2480 .setDescription("") 2481 .setVersion(0)) 2482 .addTypes( 2483 SchemaTypeConfigProto.newBuilder() 2484 .setSchemaType("package$database1/Document") 2485 .setDescription("") 2486 .setVersion(0)) 2487 .addTypes( 2488 SchemaTypeConfigProto.newBuilder() 2489 .setSchemaType("package$database2/Email") 2490 .setDescription("") 2491 .setVersion(0)) 2492 .addTypes( 2493 SchemaTypeConfigProto.newBuilder() 2494 .setSchemaType("package$database2/Document") 2495 .setDescription("") 2496 .setVersion(0)) 2497 .build(); 2498 2499 // Check Email and Document is saved in database 1 and 2 correctly. 2500 List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>(); 2501 expectedTypes.addAll(existingSchemas); 2502 expectedTypes.addAll(expectedProto.getTypesList()); 2503 assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList()) 2504 .containsExactlyElementsIn(expectedTypes); 2505 2506 // Save only Email to database1 this time. 2507 schemas = Collections.singletonList(new AppSearchSchema.Builder("Email").build()); 2508 internalSetSchemaResponse = 2509 mAppSearchImpl.setSchema( 2510 "package", 2511 "database1", 2512 schemas, 2513 /* visibilityConfigs= */ Collections.emptyList(), 2514 /* forceOverride= */ true, 2515 /* version= */ 0, 2516 /* setSchemaStatsBuilder= */ null); 2517 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2518 2519 // Create expected schemaType list, database 1 should only contain Email but database 2 2520 // remains in same. 2521 expectedProto = 2522 SchemaProto.newBuilder() 2523 .addTypes( 2524 SchemaTypeConfigProto.newBuilder() 2525 .setSchemaType("package$database1/Email") 2526 .setDescription("") 2527 .setVersion(0)) 2528 .addTypes( 2529 SchemaTypeConfigProto.newBuilder() 2530 .setSchemaType("package$database2/Email") 2531 .setDescription("") 2532 .setVersion(0)) 2533 .addTypes( 2534 SchemaTypeConfigProto.newBuilder() 2535 .setSchemaType("package$database2/Document") 2536 .setDescription("") 2537 .setVersion(0)) 2538 .build(); 2539 2540 // Check nothing changed in database2. 2541 expectedTypes = new ArrayList<>(); 2542 expectedTypes.addAll(existingSchemas); 2543 expectedTypes.addAll(expectedProto.getTypesList()); 2544 assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList()) 2545 .containsExactlyElementsIn(expectedTypes); 2546 } 2547 2548 @Test 2549 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testWriteAndReadBlob()2550 public void testWriteAndReadBlob() throws Exception { 2551 mAppSearchImpl = 2552 AppSearchImpl.create( 2553 mAppSearchDir, 2554 new AppSearchConfigImpl( 2555 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 2556 /* initStatsBuilder= */ null, 2557 /* visibilityChecker= */ null, 2558 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 2559 ALWAYS_OPTIMIZE); 2560 byte[] data = generateRandomBytes(20 * 1024); // 20 KiB 2561 byte[] digest = calculateDigest(data); 2562 AppSearchBlobHandle handle = 2563 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "ns"); 2564 try (ParcelFileDescriptor writePfd = 2565 mAppSearchImpl.openWriteBlob("package", "db1", handle); 2566 OutputStream outputStream = 2567 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 2568 outputStream.write(data); 2569 outputStream.flush(); 2570 } 2571 2572 // commit the change and read the blob. 2573 mAppSearchImpl.commitBlob("package", "db1", handle); 2574 byte[] readBytes = new byte[20 * 1024]; 2575 try (ParcelFileDescriptor readPfd = mAppSearchImpl.openReadBlob("package", "db1", handle); 2576 InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readPfd)) { 2577 inputStream.read(readBytes); 2578 } 2579 assertThat(readBytes).isEqualTo(data); 2580 } 2581 2582 @Test 2583 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testRemovePendingBlob()2584 public void testRemovePendingBlob() throws Exception { 2585 mAppSearchImpl = 2586 AppSearchImpl.create( 2587 mAppSearchDir, 2588 new AppSearchConfigImpl( 2589 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 2590 /* initStatsBuilder= */ null, 2591 /* visibilityChecker= */ null, 2592 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 2593 ALWAYS_OPTIMIZE); 2594 byte[] data = generateRandomBytes(20 * 1024); // 20 KiB 2595 byte[] digest = calculateDigest(data); 2596 AppSearchBlobHandle handle = 2597 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "ns"); 2598 try (ParcelFileDescriptor writePfd = 2599 mAppSearchImpl.openWriteBlob("package", "db1", handle); 2600 OutputStream outputStream = 2601 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 2602 outputStream.write(data); 2603 outputStream.flush(); 2604 } 2605 2606 mAppSearchImpl.commitBlob("package", "db1", handle); 2607 2608 // Remove the committed blob 2609 mAppSearchImpl.removeBlob("package", "db1", handle); 2610 2611 // Read will get NOT_FOUND 2612 AppSearchException e = 2613 assertThrows( 2614 AppSearchException.class, 2615 () -> mAppSearchImpl.openReadBlob("package", "db1", handle)); 2616 assertThat(e.getResultCode()).isEqualTo(RESULT_NOT_FOUND); 2617 assertThat(e.getMessage()).contains("Cannot find the blob for handle"); 2618 } 2619 2620 @Test 2621 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testRemoveCommittedBlob()2622 public void testRemoveCommittedBlob() throws Exception { 2623 mAppSearchImpl = 2624 AppSearchImpl.create( 2625 mAppSearchDir, 2626 new AppSearchConfigImpl( 2627 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 2628 /* initStatsBuilder= */ null, 2629 /* visibilityChecker= */ null, 2630 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 2631 ALWAYS_OPTIMIZE); 2632 byte[] data = generateRandomBytes(20 * 1024); // 20 KiB 2633 byte[] digest = calculateDigest(data); 2634 AppSearchBlobHandle handle = 2635 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "ns"); 2636 try (ParcelFileDescriptor writePfd = 2637 mAppSearchImpl.openWriteBlob("package", "db1", handle); 2638 OutputStream outputStream = 2639 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 2640 outputStream.write(data); 2641 outputStream.flush(); 2642 } 2643 2644 // Remove the blob 2645 mAppSearchImpl.removeBlob("package", "db1", handle); 2646 2647 // Commit will get NOT_FOUND 2648 AppSearchException e = 2649 assertThrows( 2650 AppSearchException.class, 2651 () -> mAppSearchImpl.commitBlob("package", "db1", handle)); 2652 assertThat(e.getResultCode()).isEqualTo(RESULT_NOT_FOUND); 2653 assertThat(e.getMessage()).contains("Cannot find the blob for handle"); 2654 } 2655 2656 @Test 2657 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testRemoveAndReWriteBlob()2658 public void testRemoveAndReWriteBlob() throws Exception { 2659 mAppSearchImpl = 2660 AppSearchImpl.create( 2661 mAppSearchDir, 2662 new AppSearchConfigImpl( 2663 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 2664 /* initStatsBuilder= */ null, 2665 /* visibilityChecker= */ null, 2666 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 2667 ALWAYS_OPTIMIZE); 2668 byte[] data = generateRandomBytes(20 * 1024); // 20 KiB 2669 byte[] wrongData = generateRandomBytes(10 * 1024); // 10 KiB 2670 byte[] digest = calculateDigest(data); 2671 AppSearchBlobHandle handle = 2672 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "ns"); 2673 try (ParcelFileDescriptor writePfd = 2674 mAppSearchImpl.openWriteBlob("package", "db1", handle); 2675 OutputStream outputStream = 2676 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 2677 // write wrong data 2678 outputStream.write(wrongData); 2679 outputStream.flush(); 2680 } 2681 2682 // Remove the blob 2683 mAppSearchImpl.removeBlob("package", "db1", handle); 2684 2685 // reopen and rewrite 2686 try (ParcelFileDescriptor writePfd = 2687 mAppSearchImpl.openWriteBlob("package", "db1", handle); 2688 OutputStream outputStream = 2689 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 2690 outputStream.write(data); 2691 outputStream.flush(); 2692 } 2693 2694 // commit the change and read the blob. 2695 mAppSearchImpl.commitBlob("package", "db1", handle); 2696 byte[] readBytes = new byte[20 * 1024]; 2697 try (ParcelFileDescriptor readPfd = mAppSearchImpl.openReadBlob("package", "db1", handle); 2698 InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readPfd)) { 2699 inputStream.read(readBytes); 2700 } 2701 assertThat(readBytes).isEqualTo(data); 2702 } 2703 2704 @Test 2705 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testOpenReadForWrite_notAllowed()2706 public void testOpenReadForWrite_notAllowed() throws Exception { 2707 mAppSearchImpl = 2708 AppSearchImpl.create( 2709 mAppSearchDir, 2710 new AppSearchConfigImpl( 2711 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 2712 /* initStatsBuilder= */ null, 2713 /* visibilityChecker= */ null, 2714 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 2715 ALWAYS_OPTIMIZE); 2716 byte[] data = generateRandomBytes(20); // 20 Bytes 2717 byte[] digest = calculateDigest(data); 2718 AppSearchBlobHandle handle = 2719 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "ns"); 2720 try (ParcelFileDescriptor writePfd = 2721 mAppSearchImpl.openWriteBlob("package", "db1", handle); 2722 OutputStream outputStream = 2723 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 2724 outputStream.write(data); 2725 outputStream.flush(); 2726 } 2727 2728 // commit the change and read the blob. 2729 mAppSearchImpl.commitBlob("package", "db1", handle); 2730 2731 // Open output stream on read-only pfd. 2732 assertThrows( 2733 IOException.class, 2734 () -> { 2735 try (ParcelFileDescriptor readPfd = 2736 mAppSearchImpl.openReadBlob("package", "db1", handle); 2737 OutputStream outputStream = 2738 new ParcelFileDescriptor.AutoCloseOutputStream(readPfd)) { 2739 outputStream.write(data); 2740 } 2741 }); 2742 } 2743 2744 @Test 2745 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testOpenWriteForRead_allowed()2746 public void testOpenWriteForRead_allowed() throws Exception { 2747 mAppSearchImpl = 2748 AppSearchImpl.create( 2749 mAppSearchDir, 2750 new AppSearchConfigImpl( 2751 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 2752 /* initStatsBuilder= */ null, 2753 /* visibilityChecker= */ null, 2754 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 2755 ALWAYS_OPTIMIZE); 2756 byte[] data = generateRandomBytes(20); // 20 Bytes 2757 byte[] digest = calculateDigest(data); 2758 AppSearchBlobHandle handle = 2759 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "ns"); 2760 // openWriteBlob returns read and write fd. 2761 try (ParcelFileDescriptor writePfd = 2762 mAppSearchImpl.openWriteBlob("package", "db1", handle); 2763 InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(writePfd)) { 2764 inputStream.read(new byte[10]); 2765 } 2766 } 2767 2768 @Test 2769 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testOptimizeBlob()2770 public void testOptimizeBlob() throws Exception { 2771 // Create a new AppSearchImpl with lower orphan blob time to live. 2772 mAppSearchImpl.close(); 2773 File tempFolder = mTemporaryFolder.newFolder(); 2774 mAppSearchImpl = 2775 AppSearchImpl.create( 2776 tempFolder, 2777 new AppSearchConfigImpl( 2778 new UnlimitedLimitConfig(), 2779 new LocalStorageIcingOptionsConfig() { 2780 @Override 2781 public long getOrphanBlobTimeToLiveMs() { 2782 // 0 will make it non-expire 2783 return 1L; 2784 } 2785 }), 2786 /* initStatsBuilder= */ null, 2787 /* visibilityChecker= */ null, 2788 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 2789 ALWAYS_OPTIMIZE); 2790 2791 // Write the blob and commit it. 2792 byte[] data = generateRandomBytes(20); // 20 Bytes 2793 byte[] digest = calculateDigest(data); 2794 AppSearchBlobHandle handle = 2795 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "namespace"); 2796 ParcelFileDescriptor writePfd = mAppSearchImpl.openWriteBlob("package", "db1", handle); 2797 try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 2798 outputStream.write(data); 2799 outputStream.flush(); 2800 } 2801 writePfd.close(); 2802 mAppSearchImpl.commitBlob("package", "db1", handle); 2803 2804 mAppSearchImpl.persistToDisk(PersistType.Code.FULL); 2805 2806 // Optimize remove the expired orphan blob. 2807 mAppSearchImpl.optimize(/* builder= */ null); 2808 AppSearchException e = 2809 assertThrows( 2810 AppSearchException.class, 2811 () -> { 2812 mAppSearchImpl.openReadBlob("package", "db1", handle); 2813 }); 2814 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 2815 assertThat(e.getMessage()).contains("Cannot find the blob for handle"); 2816 } 2817 2818 @Test 2819 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testOptimizeBlobWithDocument()2820 public void testOptimizeBlobWithDocument() throws Exception { 2821 // Create a new AppSearchImpl with lower orphan blob time to live. 2822 mAppSearchImpl.close(); 2823 File tempFolder = mTemporaryFolder.newFolder(); 2824 mAppSearchImpl = 2825 AppSearchImpl.create( 2826 tempFolder, 2827 new AppSearchConfigImpl( 2828 new UnlimitedLimitConfig(), 2829 new LocalStorageIcingOptionsConfig() { 2830 @Override 2831 public long getOrphanBlobTimeToLiveMs() { 2832 // 0 will make it non-expire 2833 return 1L; 2834 } 2835 }), 2836 /* initStatsBuilder= */ null, 2837 /* visibilityChecker= */ null, 2838 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 2839 ALWAYS_OPTIMIZE); 2840 2841 // Write the blob and commit it. 2842 byte[] data = generateRandomBytes(20); // 20 Bytes 2843 byte[] digest = calculateDigest(data); 2844 AppSearchBlobHandle handle = 2845 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "namespace"); 2846 ParcelFileDescriptor writePfd = mAppSearchImpl.openWriteBlob("package", "db1", handle); 2847 try (OutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 2848 outputStream.write(data); 2849 outputStream.flush(); 2850 } 2851 writePfd.close(); 2852 mAppSearchImpl.commitBlob("package", "db1", handle); 2853 2854 // Put a document link that blob handle. 2855 AppSearchSchema schema = 2856 new AppSearchSchema.Builder("Type") 2857 .addProperty( 2858 new AppSearchSchema.BlobHandlePropertyConfig.Builder("blob") 2859 .setCardinality( 2860 AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) 2861 .build()) 2862 .build(); 2863 InternalSetSchemaResponse internalSetSchemaResponse = 2864 mAppSearchImpl.setSchema( 2865 "package", 2866 "db1", 2867 ImmutableList.of(schema), 2868 /* visibilityConfigs= */ Collections.emptyList(), 2869 /* forceOverride= */ true, 2870 /* version= */ 0, 2871 /* setSchemaStatsBuilder= */ null); 2872 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 2873 GenericDocument document = 2874 new GenericDocument.Builder<>("namespace", "id", "Type") 2875 .setPropertyBlobHandle("blob", handle) 2876 .build(); 2877 mAppSearchImpl.putDocument( 2878 "package", 2879 "db1", 2880 document, 2881 /* sendChangeNotifications= */ false, 2882 /* logger= */ null); 2883 2884 mAppSearchImpl.persistToDisk(PersistType.Code.FULL); 2885 2886 // Optimize won't remove the blob since it has reference document. 2887 mAppSearchImpl.optimize(/* builder= */ null); 2888 byte[] readBytes = new byte[20]; 2889 try (ParcelFileDescriptor readPfd = mAppSearchImpl.openReadBlob("package", "db1", handle); 2890 InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readPfd)) { 2891 inputStream.read(readBytes); 2892 } 2893 assertThat(readBytes).isEqualTo(data); 2894 2895 mAppSearchImpl.remove("package", "db1", "namespace", "id", /* statsBuilder= */ null); 2896 2897 // The blob is orphan now and optimize will remove it. 2898 mAppSearchImpl.optimize(/* builder= */ null); 2899 AppSearchException e = 2900 assertThrows( 2901 AppSearchException.class, 2902 () -> { 2903 mAppSearchImpl.openReadBlob("package", "db1", handle); 2904 }); 2905 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 2906 assertThat(e.getMessage()).contains("Cannot find the blob for handle"); 2907 } 2908 2909 @Test 2910 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testRevokeFileDescriptor()2911 public void testRevokeFileDescriptor() throws Exception { 2912 mAppSearchImpl = 2913 AppSearchImpl.create( 2914 mAppSearchDir, 2915 new AppSearchConfigImpl( 2916 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 2917 /* initStatsBuilder= */ null, 2918 /* visibilityChecker= */ null, 2919 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 2920 ALWAYS_OPTIMIZE); 2921 byte[] data = generateRandomBytes(20 * 1024); // 20 KiB 2922 byte[] digest = calculateDigest(data); 2923 AppSearchBlobHandle handle = 2924 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "ns"); 2925 try (ParcelFileDescriptor writePfd = 2926 mAppSearchImpl.openWriteBlob("package", "db1", handle)) { 2927 // Clear package data and all file descriptor to that package will be revoked. 2928 mAppSearchImpl.clearPackageData("package"); 2929 2930 assertThrows( 2931 IOException.class, 2932 () -> { 2933 try (OutputStream outputStream = 2934 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 2935 outputStream.write(data); 2936 } 2937 }); 2938 } 2939 2940 // reopen file descriptor could work. 2941 try (ParcelFileDescriptor writePfd2 = 2942 mAppSearchImpl.openWriteBlob("package", "db1", handle)) { 2943 try (OutputStream outputStream = 2944 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd2)) { 2945 outputStream.write(data); 2946 } 2947 // close the AppSearchImpl will revoke all sent fds. 2948 mAppSearchImpl.close(); 2949 assertThrows( 2950 IOException.class, 2951 () -> { 2952 try (OutputStream outputStream = 2953 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd2)) { 2954 outputStream.write(data); 2955 } 2956 }); 2957 } 2958 } 2959 2960 // Verify the blob handle won't sent request to Icing. So no need to enable 2961 // FLAG_ENABLE_BLOB_STORE. 2962 @Test testInvalidBlobHandle()2963 public void testInvalidBlobHandle() throws Exception { 2964 mAppSearchImpl = 2965 AppSearchImpl.create( 2966 mAppSearchDir, 2967 new AppSearchConfigImpl( 2968 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 2969 /* initStatsBuilder= */ null, 2970 /* visibilityChecker= */ null, 2971 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 2972 ALWAYS_OPTIMIZE); 2973 byte[] data = generateRandomBytes(20 * 1024); // 20 KiB 2974 byte[] digest = calculateDigest(data); 2975 AppSearchBlobHandle handle = 2976 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "ns"); 2977 2978 AppSearchException e = 2979 assertThrows( 2980 AppSearchException.class, 2981 () -> mAppSearchImpl.openWriteBlob("wrongPackageName", "db1", handle)); 2982 assertThat(e.getResultCode()).isEqualTo(RESULT_INVALID_ARGUMENT); 2983 assertThat(e.getMessage()) 2984 .contains( 2985 "Blob package doesn't match calling package, " 2986 + "calling package: wrongPackageName, blob package: package"); 2987 2988 e = 2989 assertThrows( 2990 AppSearchException.class, 2991 () -> mAppSearchImpl.openWriteBlob("package", "wrongDb", handle)); 2992 assertThat(e.getResultCode()).isEqualTo(RESULT_INVALID_ARGUMENT); 2993 assertThat(e.getMessage()) 2994 .contains( 2995 "Blob database doesn't match calling database, " 2996 + "calling database: wrongDb, blob database: db1"); 2997 } 2998 2999 @Test 3000 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testSetBlobVisibility()3001 public void testSetBlobVisibility() throws Exception { 3002 mAppSearchImpl = 3003 AppSearchImpl.create( 3004 mAppSearchDir, 3005 new AppSearchConfigImpl( 3006 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 3007 /* initStatsBuilder= */ null, 3008 /* visibilityChecker= */ null, 3009 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 3010 ALWAYS_OPTIMIZE); 3011 3012 SchemaVisibilityConfig visibleToConfig = 3013 new SchemaVisibilityConfig.Builder() 3014 .addAllowedPackage(new PackageIdentifier("pkgBar", new byte[32])) 3015 .addRequiredPermissions(ImmutableSet.of(1, 2)) 3016 .setPubliclyVisibleTargetPackage( 3017 new PackageIdentifier("pkgFoo", new byte[32])) 3018 .build(); 3019 InternalVisibilityConfig config = 3020 new InternalVisibilityConfig.Builder("namespace") 3021 .setNotDisplayedBySystem(false) 3022 .addVisibleToConfig(visibleToConfig) 3023 .build(); 3024 3025 String prefix = PrefixUtil.createPrefix("package", "db1"); 3026 mAppSearchImpl.setBlobNamespaceVisibility("package", "db1", ImmutableList.of(config)); 3027 3028 // Expect the config will be added prefix. 3029 InternalVisibilityConfig expectedConfig = 3030 new InternalVisibilityConfig.Builder(prefix + "namespace") 3031 .setNotDisplayedBySystem(false) 3032 .addVisibleToConfig(visibleToConfig) 3033 .build(); 3034 assertThat(mAppSearchImpl.mBlobVisibilityStoreLocked.getVisibility(prefix + "namespace")) 3035 .isEqualTo(expectedConfig); 3036 3037 // Verify the InternalVisibilityConfig is saved to AppSearchImpl. 3038 GenericDocument visibilityDocument = 3039 mAppSearchImpl.getDocument( 3040 VISIBILITY_PACKAGE_NAME, 3041 BLOB_VISIBILITY_DATABASE_NAME, 3042 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 3043 /* id= */ prefix + "namespace", 3044 /* typePropertyPaths= */ Collections.emptyMap()); 3045 GenericDocument overLayVisibilityDocument = 3046 mAppSearchImpl.getDocument( 3047 VISIBILITY_PACKAGE_NAME, 3048 BLOB_ANDROID_V_OVERLAY_DATABASE_NAME, 3049 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 3050 /* id= */ prefix + "namespace", 3051 /* typePropertyPaths= */ Collections.emptyMap()); 3052 3053 InternalVisibilityConfig outputConfig = 3054 VisibilityToDocumentConverter.createInternalVisibilityConfig( 3055 visibilityDocument, overLayVisibilityDocument); 3056 3057 assertThat(outputConfig).isEqualTo(expectedConfig); 3058 } 3059 3060 @Test 3061 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_BLOB_STORE) testSetBlobVisibility_notSupported()3062 public void testSetBlobVisibility_notSupported() throws Exception { 3063 mAppSearchImpl = 3064 AppSearchImpl.create( 3065 mAppSearchDir, 3066 new AppSearchConfigImpl( 3067 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 3068 /* initStatsBuilder= */ null, 3069 /* visibilityChecker= */ null, 3070 /* revocableFileDescriptorStore= */ null, 3071 ALWAYS_OPTIMIZE); 3072 3073 SchemaVisibilityConfig visibleToConfig = 3074 new SchemaVisibilityConfig.Builder() 3075 .addAllowedPackage(new PackageIdentifier("pkgBar", new byte[32])) 3076 .addRequiredPermissions(ImmutableSet.of(1, 2)) 3077 .setPubliclyVisibleTargetPackage( 3078 new PackageIdentifier("pkgFoo", new byte[32])) 3079 .build(); 3080 InternalVisibilityConfig config = 3081 new InternalVisibilityConfig.Builder("namespace") 3082 .setNotDisplayedBySystem(false) 3083 .addVisibleToConfig(visibleToConfig) 3084 .build(); 3085 3086 UnsupportedOperationException exception = 3087 assertThrows( 3088 UnsupportedOperationException.class, 3089 () -> 3090 mAppSearchImpl.setBlobNamespaceVisibility( 3091 "package", "db1", ImmutableList.of(config))); 3092 assertThat(exception) 3093 .hasMessageThat() 3094 .contains( 3095 Features.BLOB_STORAGE 3096 + " is not available on this AppSearch implementation."); 3097 } 3098 3099 @Test 3100 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testRemoveBlobVisibility()3101 public void testRemoveBlobVisibility() throws Exception { 3102 mAppSearchImpl = 3103 AppSearchImpl.create( 3104 mAppSearchDir, 3105 new AppSearchConfigImpl( 3106 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 3107 /* initStatsBuilder= */ null, 3108 /* visibilityChecker= */ null, 3109 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 3110 ALWAYS_OPTIMIZE); 3111 3112 SchemaVisibilityConfig visibleToConfig1 = 3113 new SchemaVisibilityConfig.Builder() 3114 .addAllowedPackage(new PackageIdentifier("pkgBar1", new byte[32])) 3115 .addRequiredPermissions(ImmutableSet.of(1, 2)) 3116 .setPubliclyVisibleTargetPackage( 3117 new PackageIdentifier("pkgFoo1", new byte[32])) 3118 .build(); 3119 InternalVisibilityConfig config1 = 3120 new InternalVisibilityConfig.Builder("namespace1") 3121 .setNotDisplayedBySystem(false) 3122 .addVisibleToConfig(visibleToConfig1) 3123 .build(); 3124 SchemaVisibilityConfig visibleToConfig2 = 3125 new SchemaVisibilityConfig.Builder() 3126 .addAllowedPackage(new PackageIdentifier("pkgBar2", new byte[32])) 3127 .addRequiredPermissions(ImmutableSet.of(3, 4)) 3128 .setPubliclyVisibleTargetPackage( 3129 new PackageIdentifier("pkgFoo2", new byte[32])) 3130 .build(); 3131 InternalVisibilityConfig config2 = 3132 new InternalVisibilityConfig.Builder("namespace2") 3133 .setNotDisplayedBySystem(false) 3134 .addVisibleToConfig(visibleToConfig2) 3135 .build(); 3136 3137 String prefix = PrefixUtil.createPrefix("package", "db1"); 3138 mAppSearchImpl.setBlobNamespaceVisibility( 3139 "package", "db1", ImmutableList.of(config1, config2)); 3140 3141 // Expect the config will be added prefix. 3142 InternalVisibilityConfig expectedConfig1 = 3143 new InternalVisibilityConfig.Builder(prefix + "namespace1") 3144 .setNotDisplayedBySystem(false) 3145 .addVisibleToConfig(visibleToConfig1) 3146 .build(); 3147 assertThat(mAppSearchImpl.mBlobVisibilityStoreLocked.getVisibility(prefix + "namespace1")) 3148 .isEqualTo(expectedConfig1); 3149 3150 InternalVisibilityConfig expectedConfig2 = 3151 new InternalVisibilityConfig.Builder(prefix + "namespace2") 3152 .setNotDisplayedBySystem(false) 3153 .addVisibleToConfig(visibleToConfig2) 3154 .build(); 3155 assertThat(mAppSearchImpl.mBlobVisibilityStoreLocked.getVisibility(prefix + "namespace2")) 3156 .isEqualTo(expectedConfig2); 3157 3158 // Verify the InternalVisibilityConfig is saved to AppSearchImpl. 3159 GenericDocument visibilityDocument1 = 3160 mAppSearchImpl.getDocument( 3161 VISIBILITY_PACKAGE_NAME, 3162 BLOB_VISIBILITY_DATABASE_NAME, 3163 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 3164 /* id= */ prefix + "namespace1", 3165 /* typePropertyPaths= */ Collections.emptyMap()); 3166 GenericDocument overLayVisibilityDocument1 = 3167 mAppSearchImpl.getDocument( 3168 VISIBILITY_PACKAGE_NAME, 3169 BLOB_ANDROID_V_OVERLAY_DATABASE_NAME, 3170 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 3171 /* id= */ prefix + "namespace1", 3172 /* typePropertyPaths= */ Collections.emptyMap()); 3173 InternalVisibilityConfig outputConfig1 = 3174 VisibilityToDocumentConverter.createInternalVisibilityConfig( 3175 visibilityDocument1, overLayVisibilityDocument1); 3176 assertThat(outputConfig1).isEqualTo(expectedConfig1); 3177 3178 GenericDocument visibilityDocument2 = 3179 mAppSearchImpl.getDocument( 3180 VISIBILITY_PACKAGE_NAME, 3181 BLOB_VISIBILITY_DATABASE_NAME, 3182 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 3183 /* id= */ prefix + "namespace2", 3184 /* typePropertyPaths= */ Collections.emptyMap()); 3185 GenericDocument overLayVisibilityDocument2 = 3186 mAppSearchImpl.getDocument( 3187 VISIBILITY_PACKAGE_NAME, 3188 BLOB_ANDROID_V_OVERLAY_DATABASE_NAME, 3189 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 3190 /* id= */ prefix + "namespace2", 3191 /* typePropertyPaths= */ Collections.emptyMap()); 3192 InternalVisibilityConfig outputConfig2 = 3193 VisibilityToDocumentConverter.createInternalVisibilityConfig( 3194 visibilityDocument2, overLayVisibilityDocument2); 3195 assertThat(outputConfig2).isEqualTo(expectedConfig2); 3196 3197 // remove config1 by only set config2 to db 3198 mAppSearchImpl.setBlobNamespaceVisibility( 3199 "package", "db1", /* visibilityConfigs= */ ImmutableList.of(config2)); 3200 3201 // Check config 1 is removed from VisibilityStore 3202 assertThat(mAppSearchImpl.mBlobVisibilityStoreLocked.getVisibility(prefix + "namespace1")) 3203 .isNull(); 3204 AppSearchException e = 3205 assertThrows( 3206 AppSearchException.class, 3207 () -> 3208 mAppSearchImpl.getDocument( 3209 VISIBILITY_PACKAGE_NAME, 3210 BLOB_VISIBILITY_DATABASE_NAME, 3211 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 3212 /* id= */ prefix + "namespace1", 3213 /* typePropertyPaths= */ Collections.emptyMap())); 3214 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 3215 assertThat(e.getMessage()) 3216 .isEqualTo("Document (VS#Pkg$VSBlob#Db/, package$db1/namespace1) not found."); 3217 e = 3218 assertThrows( 3219 AppSearchException.class, 3220 () -> 3221 mAppSearchImpl.getDocument( 3222 VISIBILITY_PACKAGE_NAME, 3223 BLOB_ANDROID_V_OVERLAY_DATABASE_NAME, 3224 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 3225 /* id= */ prefix + "namespace1", 3226 /* typePropertyPaths= */ Collections.emptyMap())); 3227 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 3228 assertThat(e.getMessage()) 3229 .isEqualTo( 3230 "Document (VS#Pkg$VSBlob#AndroidVDb/androidVOverlay, " 3231 + "package$db1/namespace1) not found."); 3232 3233 // Config2 remains. 3234 assertThat(mAppSearchImpl.mBlobVisibilityStoreLocked.getVisibility(prefix + "namespace2")) 3235 .isEqualTo(expectedConfig2); 3236 visibilityDocument2 = 3237 mAppSearchImpl.getDocument( 3238 VISIBILITY_PACKAGE_NAME, 3239 BLOB_VISIBILITY_DATABASE_NAME, 3240 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 3241 /* id= */ prefix + "namespace2", 3242 /* typePropertyPaths= */ Collections.emptyMap()); 3243 overLayVisibilityDocument2 = 3244 mAppSearchImpl.getDocument( 3245 VISIBILITY_PACKAGE_NAME, 3246 BLOB_ANDROID_V_OVERLAY_DATABASE_NAME, 3247 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 3248 /* id= */ prefix + "namespace2", 3249 /* typePropertyPaths= */ Collections.emptyMap()); 3250 outputConfig2 = 3251 VisibilityToDocumentConverter.createInternalVisibilityConfig( 3252 visibilityDocument2, overLayVisibilityDocument2); 3253 3254 assertThat(outputConfig2).isEqualTo(expectedConfig2); 3255 } 3256 3257 @Test 3258 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_BLOB_STORE) testGlobalReadBlob_notSupported()3259 public void testGlobalReadBlob_notSupported() throws Exception { 3260 String visiblePrefix = PrefixUtil.createPrefix("package", "db1"); 3261 VisibilityChecker mockVisibilityChecker = 3262 createMockVisibilityChecker(ImmutableSet.of(visiblePrefix + "visibleNamespace")); 3263 mAppSearchImpl = 3264 AppSearchImpl.create( 3265 mAppSearchDir, 3266 new AppSearchConfigImpl( 3267 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 3268 /* initStatsBuilder= */ null, 3269 mockVisibilityChecker, 3270 /* revocableFileDescriptorStore= */ null, 3271 ALWAYS_OPTIMIZE); 3272 3273 byte[] data = generateRandomBytes(20 * 1024); // 20 KiB 3274 byte[] digest = calculateDigest(data); 3275 AppSearchBlobHandle handle = 3276 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "ns"); 3277 // nonVisibleHandle is not visible to the caller. 3278 UnsupportedOperationException exception = 3279 assertThrows( 3280 UnsupportedOperationException.class, 3281 () -> mAppSearchImpl.globalOpenReadBlob(handle, mSelfCallerAccess)); 3282 assertThat(exception) 3283 .hasMessageThat() 3284 .contains( 3285 Features.BLOB_STORAGE 3286 + " is not available on this AppSearch implementation."); 3287 } 3288 3289 @Test 3290 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testGlobalReadBlob()3291 public void testGlobalReadBlob() throws Exception { 3292 String visiblePrefix = PrefixUtil.createPrefix("package", "db1"); 3293 VisibilityChecker mockVisibilityChecker = 3294 createMockVisibilityChecker(ImmutableSet.of(visiblePrefix + "visibleNamespace")); 3295 mAppSearchImpl = 3296 AppSearchImpl.create( 3297 mAppSearchDir, 3298 new AppSearchConfigImpl( 3299 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 3300 /* initStatsBuilder= */ null, 3301 mockVisibilityChecker, 3302 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 3303 ALWAYS_OPTIMIZE); 3304 3305 // Set mock visibility setting. 3306 InternalVisibilityConfig config = 3307 new InternalVisibilityConfig.Builder("visibleNamespace").build(); 3308 mAppSearchImpl.setBlobNamespaceVisibility("package", "db1", ImmutableList.of(config)); 3309 3310 byte[] data = generateRandomBytes(20 * 1024); // 20 KiB 3311 byte[] digest = calculateDigest(data); 3312 AppSearchBlobHandle visibleHandle = 3313 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "visibleNamespace"); 3314 try (ParcelFileDescriptor writePfd = 3315 mAppSearchImpl.openWriteBlob("package", "db1", visibleHandle); 3316 OutputStream outputStream = 3317 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 3318 outputStream.write(data); 3319 outputStream.flush(); 3320 } 3321 mAppSearchImpl.commitBlob("package", "db1", visibleHandle); 3322 3323 AppSearchBlobHandle nonVisibleHandle = 3324 AppSearchBlobHandle.createWithSha256( 3325 digest, "package", "db1", "nonVisibleNamespace"); 3326 try (ParcelFileDescriptor writePfd = 3327 mAppSearchImpl.openWriteBlob("package", "db1", nonVisibleHandle); 3328 OutputStream outputStream = 3329 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 3330 outputStream.write(data); 3331 outputStream.flush(); 3332 } 3333 mAppSearchImpl.commitBlob("package", "db1", nonVisibleHandle); 3334 3335 // visibleHandle is visible to the caller. 3336 byte[] readBytes = new byte[20 * 1024]; 3337 try (ParcelFileDescriptor readPfd = 3338 mAppSearchImpl.globalOpenReadBlob(visibleHandle, mSelfCallerAccess); 3339 InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readPfd)) { 3340 inputStream.read(readBytes); 3341 } 3342 assertThat(readBytes).isEqualTo(data); 3343 3344 // nonVisibleHandle is not visible to the caller. 3345 AppSearchException e = 3346 assertThrows( 3347 AppSearchException.class, 3348 () -> 3349 mAppSearchImpl.globalOpenReadBlob( 3350 nonVisibleHandle, mSelfCallerAccess)); 3351 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 3352 assertThat(e.getMessage()).contains("Cannot find the blob for handle"); 3353 } 3354 3355 @Test 3356 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testGlobalReadBlob_sameErrorMessage()3357 public void testGlobalReadBlob_sameErrorMessage() throws Exception { 3358 String visiblePrefix = PrefixUtil.createPrefix("package", "db1"); 3359 VisibilityChecker mockVisibilityChecker = 3360 createMockVisibilityChecker(ImmutableSet.of(visiblePrefix + "visibleNamespace")); 3361 mAppSearchImpl = 3362 AppSearchImpl.create( 3363 mAppSearchDir, 3364 new AppSearchConfigImpl( 3365 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 3366 /* initStatsBuilder= */ null, 3367 mockVisibilityChecker, 3368 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 3369 ALWAYS_OPTIMIZE); 3370 3371 // Set mock visibility setting. 3372 InternalVisibilityConfig config = 3373 new InternalVisibilityConfig.Builder("visibleNamespace").build(); 3374 mAppSearchImpl.setBlobNamespaceVisibility("package", "db1", ImmutableList.of(config)); 3375 3376 byte[] data = generateRandomBytes(20 * 1024); // 20 KiB 3377 byte[] digest = calculateDigest(data); 3378 AppSearchBlobHandle visibleHandle = 3379 AppSearchBlobHandle.createWithSha256(digest, "package", "db1", "visibleNamespace"); 3380 try (ParcelFileDescriptor writePfd = 3381 mAppSearchImpl.openWriteBlob("package", "db1", visibleHandle); 3382 OutputStream outputStream = 3383 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 3384 outputStream.write(data); 3385 outputStream.flush(); 3386 } 3387 mAppSearchImpl.commitBlob("package", "db1", visibleHandle); 3388 3389 AppSearchBlobHandle nonVisibleHandle = 3390 AppSearchBlobHandle.createWithSha256( 3391 digest, "package", "db1", "nonVisibleNamespace"); 3392 try (ParcelFileDescriptor writePfd = 3393 mAppSearchImpl.openWriteBlob("package", "db1", nonVisibleHandle); 3394 OutputStream outputStream = 3395 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 3396 outputStream.write(data); 3397 outputStream.flush(); 3398 } 3399 mAppSearchImpl.commitBlob("package", "db1", nonVisibleHandle); 3400 3401 // visibleHandle is visible to the caller. 3402 byte[] readBytes = new byte[20 * 1024]; 3403 try (ParcelFileDescriptor readPfd = 3404 mAppSearchImpl.globalOpenReadBlob(visibleHandle, mSelfCallerAccess); 3405 InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(readPfd)) { 3406 inputStream.read(readBytes); 3407 } 3408 assertThat(readBytes).isEqualTo(data); 3409 3410 // nonVisibleHandle is not visible to the caller. 3411 AppSearchException exception1 = 3412 assertThrows( 3413 AppSearchException.class, 3414 () -> 3415 mAppSearchImpl.globalOpenReadBlob( 3416 nonVisibleHandle, mSelfCallerAccess)); 3417 assertThat(exception1.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 3418 assertThat(exception1.getMessage()).contains("Cannot find the blob for handle:"); 3419 assertThat(exception1.getCause()).isNull(); 3420 3421 // Remove visibleHandle and verify the error code and message should be same between not 3422 // found and inaccessible. 3423 mAppSearchImpl.removeBlob("package", "db1", visibleHandle); 3424 AppSearchException exception2 = 3425 assertThrows( 3426 AppSearchException.class, 3427 () -> mAppSearchImpl.globalOpenReadBlob(visibleHandle, mSelfCallerAccess)); 3428 assertThat(exception2.getCause()).isNull(); 3429 assertThat(exception2.getResultCode()).isEqualTo(exception1.getResultCode()); 3430 assertThat(exception2.getMessage()).isEqualTo(exception1.getMessage()); 3431 } 3432 3433 @Test testClearPackageData()3434 public void testClearPackageData() throws Exception { 3435 List<SchemaTypeConfigProto> existingSchemas = 3436 mAppSearchImpl.getSchemaProtoLocked().getTypesList(); 3437 Map<String, Set<String>> existingDatabases = mAppSearchImpl.getPackageToDatabases(); 3438 3439 // Insert package schema 3440 List<AppSearchSchema> schema = 3441 ImmutableList.of(new AppSearchSchema.Builder("schema").build()); 3442 InternalSetSchemaResponse internalSetSchemaResponse = 3443 mAppSearchImpl.setSchema( 3444 "package", 3445 "database", 3446 schema, 3447 /* visibilityConfigs= */ Collections.emptyList(), 3448 /* forceOverride= */ false, 3449 /* version= */ 0, 3450 /* setSchemaStatsBuilder= */ null); 3451 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3452 3453 // Insert package document 3454 GenericDocument document = 3455 new GenericDocument.Builder<>("namespace", "id", "schema").build(); 3456 mAppSearchImpl.putDocument( 3457 "package", 3458 "database", 3459 document, 3460 /* sendChangeNotifications= */ false, 3461 /* logger= */ null); 3462 3463 // Verify the document is indexed. 3464 SearchSpec searchSpec = 3465 new SearchSpec.Builder().setTermMatch(TermMatchType.Code.PREFIX_VALUE).build(); 3466 SearchResultPage searchResultPage = 3467 mAppSearchImpl.query( 3468 "package", 3469 "database", 3470 /* queryExpression= */ "", 3471 searchSpec, 3472 /* logger= */ null); 3473 assertThat(searchResultPage.getResults()).hasSize(1); 3474 assertThat(searchResultPage.getResults().get(0).getGenericDocument()).isEqualTo(document); 3475 3476 // Remove the package 3477 mAppSearchImpl.clearPackageData("package"); 3478 3479 // Verify the document is cleared. 3480 searchResultPage = 3481 mAppSearchImpl.query( 3482 "package2", 3483 "database2", 3484 /* queryExpression= */ "", 3485 searchSpec, 3486 /* logger= */ null); 3487 assertThat(searchResultPage.getResults()).isEmpty(); 3488 3489 // Verify the schema is cleared. 3490 assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList()) 3491 .containsExactlyElementsIn(existingSchemas); 3492 assertThat(mAppSearchImpl.getPackageToDatabases()) 3493 .containsExactlyEntriesIn(existingDatabases); 3494 } 3495 3496 @Test testPrunePackageData()3497 public void testPrunePackageData() throws AppSearchException { 3498 List<SchemaTypeConfigProto> existingSchemas = 3499 mAppSearchImpl.getSchemaProtoLocked().getTypesList(); 3500 Map<String, Set<String>> existingDatabases = mAppSearchImpl.getPackageToDatabases(); 3501 3502 Set<String> existingPackages = new ArraySet<>(existingSchemas.size()); 3503 for (int i = 0; i < existingSchemas.size(); i++) { 3504 existingPackages.add(PrefixUtil.getPackageName(existingSchemas.get(i).getSchemaType())); 3505 } 3506 3507 // Create VisibilityConfig 3508 InternalVisibilityConfig visibilityConfig = 3509 new InternalVisibilityConfig.Builder("schema") 3510 .setNotDisplayedBySystem(true) 3511 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 3512 .build(); 3513 3514 // Insert schema for package A and B. 3515 List<AppSearchSchema> schema = 3516 ImmutableList.of(new AppSearchSchema.Builder("schema").build()); 3517 InternalSetSchemaResponse internalSetSchemaResponse = 3518 mAppSearchImpl.setSchema( 3519 "packageA", 3520 "database", 3521 schema, 3522 /* visibilityConfigs= */ ImmutableList.of(visibilityConfig), 3523 /* forceOverride= */ false, 3524 /* version= */ 0, 3525 /* setSchemaStatsBuilder= */ null); 3526 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3527 internalSetSchemaResponse = 3528 mAppSearchImpl.setSchema( 3529 "packageB", 3530 "database", 3531 schema, 3532 /* visibilityConfigs= */ ImmutableList.of(visibilityConfig), 3533 /* forceOverride= */ false, 3534 /* version= */ 0, 3535 /* setSchemaStatsBuilder= */ null); 3536 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3537 3538 // Verify these two packages are stored in AppSearch. 3539 SchemaProto expectedProto = 3540 SchemaProto.newBuilder() 3541 .addTypes( 3542 SchemaTypeConfigProto.newBuilder() 3543 .setSchemaType("packageA$database/schema") 3544 .setDescription("") 3545 .setVersion(0)) 3546 .addTypes( 3547 SchemaTypeConfigProto.newBuilder() 3548 .setSchemaType("packageB$database/schema") 3549 .setDescription("") 3550 .setVersion(0)) 3551 .build(); 3552 List<SchemaTypeConfigProto> expectedTypes = new ArrayList<>(); 3553 expectedTypes.addAll(existingSchemas); 3554 expectedTypes.addAll(expectedProto.getTypesList()); 3555 assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList()) 3556 .containsExactlyElementsIn(expectedTypes); 3557 3558 // Verify these two visibility documents are stored in AppSearch. 3559 InternalVisibilityConfig expectedVisibilityConfigA = 3560 new InternalVisibilityConfig.Builder("packageA$database/schema") 3561 .setNotDisplayedBySystem(true) 3562 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 3563 .build(); 3564 InternalVisibilityConfig expectedVisibilityConfigB = 3565 new InternalVisibilityConfig.Builder("packageB$database/schema") 3566 .setNotDisplayedBySystem(true) 3567 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 3568 .build(); 3569 assertThat( 3570 mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility( 3571 "packageA$database/schema")) 3572 .isEqualTo(expectedVisibilityConfigA); 3573 assertThat( 3574 mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility( 3575 "packageB$database/schema")) 3576 .isEqualTo(expectedVisibilityConfigB); 3577 3578 // Prune packages 3579 mAppSearchImpl.prunePackageData(existingPackages); 3580 3581 // Verify the schema is same as beginning. 3582 assertThat(mAppSearchImpl.getSchemaProtoLocked().getTypesList()) 3583 .containsExactlyElementsIn(existingSchemas); 3584 assertThat(mAppSearchImpl.getPackageToDatabases()) 3585 .containsExactlyEntriesIn(existingDatabases); 3586 3587 // Verify the VisibilitySetting is removed. 3588 assertThat( 3589 mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility( 3590 "packageA$database/schema")) 3591 .isNull(); 3592 assertThat( 3593 mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility( 3594 "packageB$database/schema")) 3595 .isNull(); 3596 } 3597 3598 @Test testGetPackageToDatabases()3599 public void testGetPackageToDatabases() throws Exception { 3600 Map<String, Set<String>> existingMapping = mAppSearchImpl.getPackageToDatabases(); 3601 Map<String, Set<String>> expectedMapping = new ArrayMap<>(); 3602 expectedMapping.putAll(existingMapping); 3603 3604 // Has database1 3605 expectedMapping.put("package1", ImmutableSet.of("database1")); 3606 InternalSetSchemaResponse internalSetSchemaResponse = 3607 mAppSearchImpl.setSchema( 3608 "package1", 3609 "database1", 3610 Collections.singletonList(new AppSearchSchema.Builder("schema").build()), 3611 /* visibilityConfigs= */ Collections.emptyList(), 3612 /* forceOverride= */ false, 3613 /* version= */ 0, 3614 /* setSchemaStatsBuilder= */ null); 3615 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3616 assertThat(mAppSearchImpl.getPackageToDatabases()) 3617 .containsExactlyEntriesIn(expectedMapping); 3618 3619 // Has both databases 3620 expectedMapping.put("package1", ImmutableSet.of("database1", "database2")); 3621 internalSetSchemaResponse = 3622 mAppSearchImpl.setSchema( 3623 "package1", 3624 "database2", 3625 Collections.singletonList(new AppSearchSchema.Builder("schema").build()), 3626 /* visibilityConfigs= */ Collections.emptyList(), 3627 /* forceOverride= */ false, 3628 /* version= */ 0, 3629 /* setSchemaStatsBuilder= */ null); 3630 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3631 assertThat(mAppSearchImpl.getPackageToDatabases()) 3632 .containsExactlyEntriesIn(expectedMapping); 3633 3634 // Has both packages 3635 expectedMapping.put("package2", ImmutableSet.of("database1")); 3636 internalSetSchemaResponse = 3637 mAppSearchImpl.setSchema( 3638 "package2", 3639 "database1", 3640 Collections.singletonList(new AppSearchSchema.Builder("schema").build()), 3641 /* visibilityConfigs= */ Collections.emptyList(), 3642 /* forceOverride= */ false, 3643 /* version= */ 0, 3644 /* setSchemaStatsBuilder= */ null); 3645 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3646 assertThat(mAppSearchImpl.getPackageToDatabases()) 3647 .containsExactlyEntriesIn(expectedMapping); 3648 } 3649 3650 @Test 3651 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_BLOB_STORE) testGetAllPrefixedSchemaTypes()3652 public void testGetAllPrefixedSchemaTypes() throws Exception { 3653 // Insert schema 3654 List<AppSearchSchema> schemas1 = 3655 Collections.singletonList(new AppSearchSchema.Builder("type1").build()); 3656 List<AppSearchSchema> schemas2 = 3657 Collections.singletonList(new AppSearchSchema.Builder("type2").build()); 3658 List<AppSearchSchema> schemas3 = 3659 Collections.singletonList(new AppSearchSchema.Builder("type3").build()); 3660 InternalSetSchemaResponse internalSetSchemaResponse = 3661 mAppSearchImpl.setSchema( 3662 "package1", 3663 "database1", 3664 schemas1, 3665 /* visibilityConfigs= */ Collections.emptyList(), 3666 /* forceOverride= */ false, 3667 /* version= */ 0, 3668 /* setSchemaStatsBuilder= */ null); 3669 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3670 internalSetSchemaResponse = 3671 mAppSearchImpl.setSchema( 3672 "package1", 3673 "database2", 3674 schemas2, 3675 /* visibilityConfigs= */ Collections.emptyList(), 3676 /* forceOverride= */ false, 3677 /* version= */ 0, 3678 /* setSchemaStatsBuilder= */ null); 3679 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3680 internalSetSchemaResponse = 3681 mAppSearchImpl.setSchema( 3682 "package2", 3683 "database1", 3684 schemas3, 3685 /* visibilityConfigs= */ Collections.emptyList(), 3686 /* forceOverride= */ false, 3687 /* version= */ 0, 3688 /* setSchemaStatsBuilder= */ null); 3689 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3690 assertThat(mAppSearchImpl.getAllPrefixedSchemaTypes()) 3691 .containsExactly( 3692 "package1$database1/type1", 3693 "package1$database2/type2", 3694 "package2$database1/type3", 3695 "VS#Pkg$VS#Db/VisibilityType", // plus the stored Visibility schema 3696 "VS#Pkg$VS#Db/VisibilityPermissionType", 3697 "VS#Pkg$VS#AndroidVDb/AndroidVOverlayType"); 3698 } 3699 3700 @Test 3701 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testGetAllPrefixedSchemaTypes_enableBlobStore()3702 public void testGetAllPrefixedSchemaTypes_enableBlobStore() throws Exception { 3703 mAppSearchImpl = 3704 AppSearchImpl.create( 3705 mAppSearchDir, 3706 new AppSearchConfigImpl( 3707 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 3708 /* initStatsBuilder= */ null, 3709 /* visibilityChecker= */ null, 3710 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 3711 ALWAYS_OPTIMIZE); 3712 // Insert schema 3713 List<AppSearchSchema> schemas1 = 3714 Collections.singletonList(new AppSearchSchema.Builder("type1").build()); 3715 List<AppSearchSchema> schemas2 = 3716 Collections.singletonList(new AppSearchSchema.Builder("type2").build()); 3717 List<AppSearchSchema> schemas3 = 3718 Collections.singletonList(new AppSearchSchema.Builder("type3").build()); 3719 InternalSetSchemaResponse internalSetSchemaResponse = 3720 mAppSearchImpl.setSchema( 3721 "package1", 3722 "database1", 3723 schemas1, 3724 /* visibilityConfigs= */ Collections.emptyList(), 3725 /* forceOverride= */ false, 3726 /* version= */ 0, 3727 /* setSchemaStatsBuilder= */ null); 3728 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3729 internalSetSchemaResponse = 3730 mAppSearchImpl.setSchema( 3731 "package1", 3732 "database2", 3733 schemas2, 3734 /* visibilityConfigs= */ Collections.emptyList(), 3735 /* forceOverride= */ false, 3736 /* version= */ 0, 3737 /* setSchemaStatsBuilder= */ null); 3738 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3739 internalSetSchemaResponse = 3740 mAppSearchImpl.setSchema( 3741 "package2", 3742 "database1", 3743 schemas3, 3744 /* visibilityConfigs= */ Collections.emptyList(), 3745 /* forceOverride= */ false, 3746 /* version= */ 0, 3747 /* setSchemaStatsBuilder= */ null); 3748 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3749 assertThat(mAppSearchImpl.getAllPrefixedSchemaTypes()) 3750 .containsExactly( 3751 "package1$database1/type1", 3752 "package1$database2/type2", 3753 "package2$database1/type3", 3754 "VS#Pkg$VS#Db/VisibilityType", // plus the stored Visibility schema 3755 "VS#Pkg$VS#Db/VisibilityPermissionType", 3756 "VS#Pkg$VS#AndroidVDb/AndroidVOverlayType", 3757 "VS#Pkg$VSBlob#Db/VisibilityType", 3758 "VS#Pkg$VSBlob#Db/VisibilityPermissionType", 3759 "VS#Pkg$VSBlob#AndroidVDb/AndroidVOverlayType"); 3760 } 3761 3762 @FlakyTest(bugId = 204186664) 3763 @Test testReportUsage()3764 public void testReportUsage() throws Exception { 3765 // Insert schema 3766 List<AppSearchSchema> schemas = 3767 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 3768 InternalSetSchemaResponse internalSetSchemaResponse = 3769 mAppSearchImpl.setSchema( 3770 "package", 3771 "database", 3772 schemas, 3773 /* visibilityConfigs= */ Collections.emptyList(), 3774 /* forceOverride= */ false, 3775 /* version= */ 0, 3776 /* setSchemaStatsBuilder= */ null); 3777 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3778 3779 // Insert two docs 3780 GenericDocument document1 = 3781 new GenericDocument.Builder<>("namespace", "id1", "type").build(); 3782 GenericDocument document2 = 3783 new GenericDocument.Builder<>("namespace", "id2", "type").build(); 3784 mAppSearchImpl.putDocument( 3785 "package", 3786 "database", 3787 document1, 3788 /* sendChangeNotifications= */ false, 3789 /* logger= */ null); 3790 mAppSearchImpl.putDocument( 3791 "package", 3792 "database", 3793 document2, 3794 /* sendChangeNotifications= */ false, 3795 /* logger= */ null); 3796 3797 // Report some usages. id1 has 2 app and 1 system usage, id2 has 1 app and 2 system usage. 3798 mAppSearchImpl.reportUsage( 3799 "package", 3800 "database", 3801 "namespace", 3802 "id1", 3803 /* usageTimestampMillis= */ 10, 3804 /* systemUsage= */ false); 3805 mAppSearchImpl.reportUsage( 3806 "package", 3807 "database", 3808 "namespace", 3809 "id1", 3810 /* usageTimestampMillis= */ 20, 3811 /* systemUsage= */ false); 3812 mAppSearchImpl.reportUsage( 3813 "package", 3814 "database", 3815 "namespace", 3816 "id1", 3817 /* usageTimestampMillis= */ 1000, 3818 /* systemUsage= */ true); 3819 3820 mAppSearchImpl.reportUsage( 3821 "package", 3822 "database", 3823 "namespace", 3824 "id2", 3825 /* usageTimestampMillis= */ 100, 3826 /* systemUsage= */ false); 3827 mAppSearchImpl.reportUsage( 3828 "package", 3829 "database", 3830 "namespace", 3831 "id2", 3832 /* usageTimestampMillis= */ 200, 3833 /* systemUsage= */ true); 3834 mAppSearchImpl.reportUsage( 3835 "package", 3836 "database", 3837 "namespace", 3838 "id2", 3839 /* usageTimestampMillis= */ 150, 3840 /* systemUsage= */ true); 3841 3842 // Sort by app usage count: id1 should win 3843 List<SearchResult> page = 3844 mAppSearchImpl 3845 .query( 3846 "package", 3847 "database", 3848 "", 3849 new SearchSpec.Builder() 3850 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 3851 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT) 3852 .build(), 3853 /* logger= */ null) 3854 .getResults(); 3855 assertThat(page).hasSize(2); 3856 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id1"); 3857 assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id2"); 3858 3859 // Sort by app usage timestamp: id2 should win 3860 page = 3861 mAppSearchImpl 3862 .query( 3863 "package", 3864 "database", 3865 "", 3866 new SearchSpec.Builder() 3867 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 3868 .setRankingStrategy( 3869 SearchSpec 3870 .RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP) 3871 .build(), 3872 /* logger= */ null) 3873 .getResults(); 3874 assertThat(page).hasSize(2); 3875 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id2"); 3876 assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id1"); 3877 3878 // Sort by system usage count: id2 should win 3879 page = 3880 mAppSearchImpl 3881 .query( 3882 "package", 3883 "database", 3884 "", 3885 new SearchSpec.Builder() 3886 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 3887 .setRankingStrategy( 3888 SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT) 3889 .build(), 3890 /* logger= */ null) 3891 .getResults(); 3892 assertThat(page).hasSize(2); 3893 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id2"); 3894 assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id1"); 3895 3896 // Sort by system usage timestamp: id1 should win 3897 page = 3898 mAppSearchImpl 3899 .query( 3900 "package", 3901 "database", 3902 "", 3903 new SearchSpec.Builder() 3904 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 3905 .setRankingStrategy( 3906 SearchSpec 3907 .RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP) 3908 .build(), 3909 /* logger= */ null) 3910 .getResults(); 3911 assertThat(page).hasSize(2); 3912 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id1"); 3913 assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id2"); 3914 } 3915 3916 @Test testGetStorageInfoForPackage_nonexistentPackage()3917 public void testGetStorageInfoForPackage_nonexistentPackage() throws Exception { 3918 // "package2" doesn't exist yet, so it shouldn't have any storage size 3919 StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("nonexistent.package"); 3920 assertThat(storageInfo.getSizeBytes()).isEqualTo(0); 3921 assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); 3922 assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); 3923 } 3924 3925 @Test testGetStorageInfoForPackage_withoutDocument()3926 public void testGetStorageInfoForPackage_withoutDocument() throws Exception { 3927 // Insert schema for "package1" 3928 List<AppSearchSchema> schemas = 3929 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 3930 InternalSetSchemaResponse internalSetSchemaResponse = 3931 mAppSearchImpl.setSchema( 3932 "package1", 3933 "database", 3934 schemas, 3935 /* visibilityConfigs= */ Collections.emptyList(), 3936 /* forceOverride= */ false, 3937 /* version= */ 0, 3938 /* setSchemaStatsBuilder= */ null); 3939 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3940 3941 // Since "package1" doesn't have a document, it get any space attributed to it. 3942 StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1"); 3943 assertThat(storageInfo.getSizeBytes()).isEqualTo(0); 3944 assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); 3945 assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); 3946 } 3947 3948 @Test testGetStorageInfoForPackage_proportionalToDocuments()3949 public void testGetStorageInfoForPackage_proportionalToDocuments() throws Exception { 3950 List<AppSearchSchema> schemas = 3951 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 3952 3953 // Insert schema for "package1" 3954 InternalSetSchemaResponse internalSetSchemaResponse = 3955 mAppSearchImpl.setSchema( 3956 "package1", 3957 "database", 3958 schemas, 3959 /* visibilityConfigs= */ Collections.emptyList(), 3960 /* forceOverride= */ false, 3961 /* version= */ 0, 3962 /* setSchemaStatsBuilder= */ null); 3963 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3964 3965 // Insert document for "package1" 3966 GenericDocument document = 3967 new GenericDocument.Builder<>("namespace", "id1", "type").build(); 3968 mAppSearchImpl.putDocument( 3969 "package1", 3970 "database", 3971 document, 3972 /* sendChangeNotifications= */ false, 3973 /* logger= */ null); 3974 3975 // Insert schema for "package2" 3976 internalSetSchemaResponse = 3977 mAppSearchImpl.setSchema( 3978 "package2", 3979 "database", 3980 schemas, 3981 /* visibilityConfigs= */ Collections.emptyList(), 3982 /* forceOverride= */ false, 3983 /* version= */ 0, 3984 /* setSchemaStatsBuilder= */ null); 3985 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 3986 3987 // Insert two documents for "package2" 3988 document = new GenericDocument.Builder<>("namespace", "id1", "type").build(); 3989 mAppSearchImpl.putDocument( 3990 "package2", 3991 "database", 3992 document, 3993 /* sendChangeNotifications= */ false, 3994 /* logger= */ null); 3995 document = new GenericDocument.Builder<>("namespace", "id2", "type").build(); 3996 mAppSearchImpl.putDocument( 3997 "package2", 3998 "database", 3999 document, 4000 /* sendChangeNotifications= */ false, 4001 /* logger= */ null); 4002 4003 StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForPackage("package1"); 4004 long size1 = storageInfo.getSizeBytes(); 4005 assertThat(size1).isGreaterThan(0); 4006 assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(1); 4007 assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); 4008 4009 storageInfo = mAppSearchImpl.getStorageInfoForPackage("package2"); 4010 long size2 = storageInfo.getSizeBytes(); 4011 assertThat(size2).isGreaterThan(0); 4012 assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(2); 4013 assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); 4014 4015 // Size is proportional to number of documents. Since "package2" has twice as many 4016 // documents as "package1", its size is twice as much too. 4017 assertThat(size2).isAtLeast(2 * size1); 4018 } 4019 4020 @Test testGetStorageInfoForDatabase_nonexistentPackage()4021 public void testGetStorageInfoForDatabase_nonexistentPackage() throws Exception { 4022 // "package2" doesn't exist yet, so it shouldn't have any storage size 4023 StorageInfo storageInfo = 4024 mAppSearchImpl.getStorageInfoForDatabase( 4025 "nonexistent.package", "nonexistentDatabase"); 4026 assertThat(storageInfo.getSizeBytes()).isEqualTo(0); 4027 assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); 4028 assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); 4029 } 4030 4031 @Test testGetStorageInfoForDatabase_nonexistentDatabase()4032 public void testGetStorageInfoForDatabase_nonexistentDatabase() throws Exception { 4033 // Insert schema for "package1" 4034 List<AppSearchSchema> schemas = 4035 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4036 InternalSetSchemaResponse internalSetSchemaResponse = 4037 mAppSearchImpl.setSchema( 4038 "package1", 4039 "database", 4040 schemas, 4041 /* visibilityConfigs= */ Collections.emptyList(), 4042 /* forceOverride= */ false, 4043 /* version= */ 0, 4044 /* setSchemaStatsBuilder= */ null); 4045 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4046 4047 // "package2" doesn't exist yet, so it shouldn't have any storage size 4048 StorageInfo storageInfo = 4049 mAppSearchImpl.getStorageInfoForDatabase("package1", "nonexistentDatabase"); 4050 assertThat(storageInfo.getSizeBytes()).isEqualTo(0); 4051 assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); 4052 assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); 4053 } 4054 4055 @Test testGetStorageInfoForDatabase_withoutDocument()4056 public void testGetStorageInfoForDatabase_withoutDocument() throws Exception { 4057 // Insert schema for "package1" 4058 List<AppSearchSchema> schemas = 4059 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4060 InternalSetSchemaResponse internalSetSchemaResponse = 4061 mAppSearchImpl.setSchema( 4062 "package1", 4063 "database1", 4064 schemas, 4065 /* visibilityConfigs= */ Collections.emptyList(), 4066 /* forceOverride= */ false, 4067 /* version= */ 0, 4068 /* setSchemaStatsBuilder= */ null); 4069 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4070 4071 // Since "package1", "database1" doesn't have a document, it get any space attributed to it. 4072 StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1"); 4073 assertThat(storageInfo.getSizeBytes()).isEqualTo(0); 4074 assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(0); 4075 assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(0); 4076 } 4077 4078 @Test testGetStorageInfoForDatabase_proportionalToDocuments()4079 public void testGetStorageInfoForDatabase_proportionalToDocuments() throws Exception { 4080 // Insert schema for "package1", "database1" and "database2" 4081 List<AppSearchSchema> schemas = 4082 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4083 InternalSetSchemaResponse internalSetSchemaResponse = 4084 mAppSearchImpl.setSchema( 4085 "package1", 4086 "database1", 4087 schemas, 4088 /* visibilityConfigs= */ Collections.emptyList(), 4089 /* forceOverride= */ false, 4090 /* version= */ 0, 4091 /* setSchemaStatsBuilder= */ null); 4092 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4093 internalSetSchemaResponse = 4094 mAppSearchImpl.setSchema( 4095 "package1", 4096 "database2", 4097 schemas, 4098 /* visibilityConfigs= */ Collections.emptyList(), 4099 /* forceOverride= */ false, 4100 /* version= */ 0, 4101 /* setSchemaStatsBuilder= */ null); 4102 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4103 4104 // Add a document for "package1", "database1" 4105 GenericDocument document = 4106 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 4107 mAppSearchImpl.putDocument( 4108 "package1", 4109 "database1", 4110 document, 4111 /* sendChangeNotifications= */ false, 4112 /* logger= */ null); 4113 4114 // Add two documents for "package1", "database2" 4115 document = new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 4116 mAppSearchImpl.putDocument( 4117 "package1", 4118 "database2", 4119 document, 4120 /* sendChangeNotifications= */ false, 4121 /* logger= */ null); 4122 document = new GenericDocument.Builder<>("namespace1", "id2", "type").build(); 4123 mAppSearchImpl.putDocument( 4124 "package1", 4125 "database2", 4126 document, 4127 /* sendChangeNotifications= */ false, 4128 /* logger= */ null); 4129 4130 StorageInfo storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database1"); 4131 long size1 = storageInfo.getSizeBytes(); 4132 assertThat(size1).isGreaterThan(0); 4133 assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(1); 4134 assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); 4135 4136 storageInfo = mAppSearchImpl.getStorageInfoForDatabase("package1", "database2"); 4137 long size2 = storageInfo.getSizeBytes(); 4138 assertThat(size2).isGreaterThan(0); 4139 assertThat(storageInfo.getAliveDocumentsCount()).isEqualTo(2); 4140 assertThat(storageInfo.getAliveNamespacesCount()).isEqualTo(1); 4141 4142 // Size is proportional to number of documents. Since "database2" has twice as many 4143 // documents as "database1", its size is twice as much too. 4144 assertThat(size2).isAtLeast(2 * size1); 4145 } 4146 4147 @Test 4148 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testGetStorageInfoForPackage_withBlob()4149 public void testGetStorageInfoForPackage_withBlob() throws Exception { 4150 mAppSearchImpl = 4151 AppSearchImpl.create( 4152 mAppSearchDir, 4153 new AppSearchConfigImpl( 4154 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 4155 /* initStatsBuilder= */ null, 4156 /* visibilityChecker= */ null, 4157 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 4158 ALWAYS_OPTIMIZE); 4159 4160 byte[] data1 = generateRandomBytes(5 * 1024); // 5 KiB 4161 byte[] digest1 = calculateDigest(data1); 4162 AppSearchBlobHandle handle1 = 4163 AppSearchBlobHandle.createWithSha256(digest1, "package1", "db1", "ns"); 4164 ParcelFileDescriptor writePfd1 = mAppSearchImpl.openWriteBlob("package1", "db1", handle1); 4165 try (OutputStream outputStream = 4166 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd1)) { 4167 outputStream.write(data1); 4168 outputStream.flush(); 4169 } 4170 4171 byte[] data2 = generateRandomBytes(10 * 1024); // 10 KiB 4172 byte[] digest2 = calculateDigest(data2); 4173 AppSearchBlobHandle handle2 = 4174 AppSearchBlobHandle.createWithSha256(digest2, "package1", "db1", "ns"); 4175 ParcelFileDescriptor writePfd2 = mAppSearchImpl.openWriteBlob("package1", "db1", handle2); 4176 try (OutputStream outputStream = 4177 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd2)) { 4178 outputStream.write(data2); 4179 outputStream.flush(); 4180 } 4181 4182 byte[] data3 = generateRandomBytes(20 * 1024); // 20 KiB 4183 byte[] digest3 = calculateDigest(data3); 4184 AppSearchBlobHandle handle3 = 4185 AppSearchBlobHandle.createWithSha256(digest3, "package2", "db1", "ns"); 4186 ParcelFileDescriptor writePfd3 = mAppSearchImpl.openWriteBlob("package2", "db1", handle3); 4187 try (OutputStream outputStream = 4188 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd3)) { 4189 outputStream.write(data3); 4190 outputStream.flush(); 4191 } 4192 4193 StorageInfo storageInfo1 = mAppSearchImpl.getStorageInfoForPackage("package1"); 4194 assertThat(storageInfo1.getBlobSizeBytes()).isEqualTo(15 * 1024); 4195 assertThat(storageInfo1.getBlobCount()).isEqualTo(2); 4196 StorageInfo storageInfo2 = mAppSearchImpl.getStorageInfoForPackage("package2"); 4197 assertThat(storageInfo2.getBlobSizeBytes()).isEqualTo(20 * 1024); 4198 assertThat(storageInfo2.getBlobCount()).isEqualTo(1); 4199 } 4200 4201 @Test 4202 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testGetStorageInfoForDatabase_withBlob()4203 public void testGetStorageInfoForDatabase_withBlob() throws Exception { 4204 mAppSearchImpl = 4205 AppSearchImpl.create( 4206 mAppSearchDir, 4207 new AppSearchConfigImpl( 4208 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 4209 /* initStatsBuilder= */ null, 4210 /* visibilityChecker= */ null, 4211 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 4212 ALWAYS_OPTIMIZE); 4213 byte[] data1 = generateRandomBytes(5 * 1024); // 5 KiB 4214 byte[] digest1 = calculateDigest(data1); 4215 AppSearchBlobHandle handle1 = 4216 AppSearchBlobHandle.createWithSha256(digest1, "package", "db1", "ns"); 4217 ParcelFileDescriptor writePfd1 = mAppSearchImpl.openWriteBlob("package", "db1", handle1); 4218 try (OutputStream outputStream = 4219 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd1)) { 4220 outputStream.write(data1); 4221 outputStream.flush(); 4222 } 4223 4224 byte[] data2 = generateRandomBytes(10 * 1024); // 10 KiB 4225 byte[] digest2 = calculateDigest(data2); 4226 AppSearchBlobHandle handle2 = 4227 AppSearchBlobHandle.createWithSha256(digest2, "package", "db1", "ns"); 4228 ParcelFileDescriptor writePfd2 = mAppSearchImpl.openWriteBlob("package", "db1", handle2); 4229 try (OutputStream outputStream = 4230 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd2)) { 4231 outputStream.write(data2); 4232 outputStream.flush(); 4233 } 4234 4235 byte[] data3 = generateRandomBytes(20 * 1024); // 20 KiB 4236 byte[] digest3 = calculateDigest(data3); 4237 AppSearchBlobHandle handle3 = 4238 AppSearchBlobHandle.createWithSha256(digest3, "package", "db2", "ns"); 4239 ParcelFileDescriptor writePfd3 = mAppSearchImpl.openWriteBlob("package", "db2", handle3); 4240 try (OutputStream outputStream = 4241 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd3)) { 4242 outputStream.write(data3); 4243 outputStream.flush(); 4244 } 4245 4246 StorageInfo storageInfo1 = mAppSearchImpl.getStorageInfoForDatabase("package", "db1"); 4247 assertThat(storageInfo1.getBlobSizeBytes()).isEqualTo(15 * 1024); 4248 assertThat(storageInfo1.getBlobCount()).isEqualTo(2); 4249 StorageInfo storageInfo2 = mAppSearchImpl.getStorageInfoForDatabase("package", "db2"); 4250 assertThat(storageInfo2.getBlobSizeBytes()).isEqualTo(20 * 1024); 4251 assertThat(storageInfo2.getBlobCount()).isEqualTo(1); 4252 } 4253 4254 @Test testThrowsExceptionIfClosed()4255 public void testThrowsExceptionIfClosed() throws Exception { 4256 // Initial check that we could do something at first. 4257 List<AppSearchSchema> schemas = 4258 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4259 InternalSetSchemaResponse internalSetSchemaResponse = 4260 mAppSearchImpl.setSchema( 4261 "package", 4262 "database", 4263 schemas, 4264 /* visibilityConfigs= */ Collections.emptyList(), 4265 /* forceOverride= */ false, 4266 /* version= */ 0, 4267 /* setSchemaStatsBuilder= */ null); 4268 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4269 4270 mAppSearchImpl.close(); 4271 4272 // Check all our public APIs 4273 assertThrows( 4274 IllegalStateException.class, 4275 () -> 4276 mAppSearchImpl.setSchema( 4277 "package", 4278 "database", 4279 schemas, 4280 /* visibilityConfigs= */ Collections.emptyList(), 4281 /* forceOverride= */ false, 4282 /* version= */ 0, 4283 /* setSchemaStatsBuilder= */ null)); 4284 4285 assertThrows( 4286 IllegalStateException.class, 4287 () -> 4288 mAppSearchImpl.getSchema( 4289 /* packageName= */ "package", 4290 /* databaseName= */ "database", 4291 /* callerAccess= */ mSelfCallerAccess)); 4292 4293 assertThrows( 4294 IllegalStateException.class, 4295 () -> 4296 mAppSearchImpl.putDocument( 4297 "package", 4298 "database", 4299 new GenericDocument.Builder<>("namespace", "id", "type").build(), 4300 /* sendChangeNotifications= */ false, 4301 /* logger= */ null)); 4302 4303 assertThrows( 4304 IllegalStateException.class, 4305 () -> 4306 mAppSearchImpl.getDocument( 4307 "package", "database", "namespace", "id", Collections.emptyMap())); 4308 4309 assertThrows( 4310 IllegalStateException.class, 4311 () -> 4312 mAppSearchImpl.query( 4313 "package", 4314 "database", 4315 "query", 4316 new SearchSpec.Builder().build(), 4317 /* logger= */ null)); 4318 4319 assertThrows( 4320 IllegalStateException.class, 4321 () -> 4322 mAppSearchImpl.globalQuery( 4323 "query", 4324 new SearchSpec.Builder().build(), 4325 mSelfCallerAccess, 4326 /* logger= */ null)); 4327 4328 assertThrows( 4329 IllegalStateException.class, 4330 () -> 4331 mAppSearchImpl.getNextPage( 4332 "package", /* nextPageToken= */ 1L, /* statsBuilder= */ null)); 4333 4334 assertThrows( 4335 IllegalStateException.class, 4336 () -> mAppSearchImpl.invalidateNextPageToken("package", /* nextPageToken= */ 1L)); 4337 4338 assertThrows( 4339 IllegalStateException.class, 4340 () -> 4341 mAppSearchImpl.reportUsage( 4342 "package", 4343 "database", 4344 "namespace", 4345 "id", 4346 /* usageTimestampMillis= */ 1000L, 4347 /* systemUsage= */ false)); 4348 4349 assertThrows( 4350 IllegalStateException.class, 4351 () -> 4352 mAppSearchImpl.remove( 4353 "package", 4354 "database", 4355 "namespace", 4356 "id", 4357 /* removeStatsBuilder= */ null)); 4358 4359 assertThrows( 4360 IllegalStateException.class, 4361 () -> 4362 mAppSearchImpl.removeByQuery( 4363 "package", 4364 "database", 4365 "query", 4366 new SearchSpec.Builder().build(), 4367 /* removeStatsBuilder= */ null)); 4368 4369 assertThrows( 4370 IllegalStateException.class, 4371 () -> mAppSearchImpl.getStorageInfoForPackage("package")); 4372 4373 assertThrows( 4374 IllegalStateException.class, 4375 () -> mAppSearchImpl.getStorageInfoForDatabase("package", "database")); 4376 4377 assertThrows( 4378 IllegalStateException.class, 4379 () -> mAppSearchImpl.persistToDisk(PersistType.Code.FULL)); 4380 } 4381 4382 @Test testPutPersistsWithLiteFlush()4383 public void testPutPersistsWithLiteFlush() throws Exception { 4384 List<AppSearchSchema> schemas = 4385 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4386 InternalSetSchemaResponse internalSetSchemaResponse = 4387 mAppSearchImpl.setSchema( 4388 "package", 4389 "database", 4390 schemas, 4391 /* visibilityConfigs= */ Collections.emptyList(), 4392 /* forceOverride= */ false, 4393 /* version= */ 0, 4394 /* setSchemaStatsBuilder= */ null); 4395 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4396 4397 // Add a document and persist it. 4398 GenericDocument document = 4399 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 4400 mAppSearchImpl.putDocument( 4401 "package", 4402 "database", 4403 document, 4404 /* sendChangeNotifications= */ false, 4405 /* logger= */ null); 4406 mAppSearchImpl.persistToDisk(PersistType.Code.LITE); 4407 4408 GenericDocument getResult = 4409 mAppSearchImpl.getDocument( 4410 "package", "database", "namespace1", "id1", Collections.emptyMap()); 4411 assertThat(getResult).isEqualTo(document); 4412 4413 // That document should be visible even from another instance. 4414 AppSearchImpl appSearchImpl2 = 4415 AppSearchImpl.create( 4416 mAppSearchDir, 4417 new AppSearchConfigImpl( 4418 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 4419 /* initStatsBuilder= */ null, 4420 /* visibilityChecker= */ null, 4421 /* revocableFileDescriptorStore= */ null, 4422 ALWAYS_OPTIMIZE); 4423 getResult = 4424 appSearchImpl2.getDocument( 4425 "package", "database", "namespace1", "id1", Collections.emptyMap()); 4426 assertThat(getResult).isEqualTo(document); 4427 appSearchImpl2.close(); 4428 } 4429 4430 @Test testDeletePersistsWithLiteFlush()4431 public void testDeletePersistsWithLiteFlush() throws Exception { 4432 List<AppSearchSchema> schemas = 4433 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4434 InternalSetSchemaResponse internalSetSchemaResponse = 4435 mAppSearchImpl.setSchema( 4436 "package", 4437 "database", 4438 schemas, 4439 /* visibilityConfigs= */ Collections.emptyList(), 4440 /* forceOverride= */ false, 4441 /* version= */ 0, 4442 /* setSchemaStatsBuilder= */ null); 4443 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4444 4445 // Add two documents and persist them. 4446 GenericDocument document1 = 4447 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 4448 mAppSearchImpl.putDocument( 4449 "package", 4450 "database", 4451 document1, 4452 /* sendChangeNotifications= */ false, 4453 /* logger= */ null); 4454 GenericDocument document2 = 4455 new GenericDocument.Builder<>("namespace1", "id2", "type").build(); 4456 mAppSearchImpl.putDocument( 4457 "package", 4458 "database", 4459 document2, 4460 /* sendChangeNotifications= */ false, 4461 /* logger= */ null); 4462 mAppSearchImpl.persistToDisk(PersistType.Code.LITE); 4463 4464 GenericDocument getResult = 4465 mAppSearchImpl.getDocument( 4466 "package", "database", "namespace1", "id1", Collections.emptyMap()); 4467 assertThat(getResult).isEqualTo(document1); 4468 getResult = 4469 mAppSearchImpl.getDocument( 4470 "package", "database", "namespace1", "id2", Collections.emptyMap()); 4471 assertThat(getResult).isEqualTo(document2); 4472 4473 // Delete the first document 4474 mAppSearchImpl.remove("package", "database", "namespace1", "id1", /* statsBuilder= */ null); 4475 mAppSearchImpl.persistToDisk(PersistType.Code.LITE); 4476 assertThrows( 4477 AppSearchException.class, 4478 () -> 4479 mAppSearchImpl.getDocument( 4480 "package", 4481 "database", 4482 "namespace1", 4483 "id1", 4484 Collections.emptyMap())); 4485 getResult = 4486 mAppSearchImpl.getDocument( 4487 "package", "database", "namespace1", "id2", Collections.emptyMap()); 4488 assertThat(getResult).isEqualTo(document2); 4489 4490 // Only the second document should be retrievable from another instance. 4491 AppSearchImpl appSearchImpl2 = 4492 AppSearchImpl.create( 4493 mAppSearchDir, 4494 new AppSearchConfigImpl( 4495 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 4496 /* initStatsBuilder= */ null, 4497 /* visibilityChecker= */ null, 4498 /* revocableFileDescriptorStore= */ null, 4499 ALWAYS_OPTIMIZE); 4500 assertThrows( 4501 AppSearchException.class, 4502 () -> 4503 appSearchImpl2.getDocument( 4504 "package", 4505 "database", 4506 "namespace1", 4507 "id1", 4508 Collections.emptyMap())); 4509 getResult = 4510 appSearchImpl2.getDocument( 4511 "package", "database", "namespace1", "id2", Collections.emptyMap()); 4512 assertThat(getResult).isEqualTo(document2); 4513 appSearchImpl2.close(); 4514 } 4515 4516 @Test testDeleteByQueryPersistsWithLiteFlush()4517 public void testDeleteByQueryPersistsWithLiteFlush() throws Exception { 4518 List<AppSearchSchema> schemas = 4519 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4520 InternalSetSchemaResponse internalSetSchemaResponse = 4521 mAppSearchImpl.setSchema( 4522 "package", 4523 "database", 4524 schemas, 4525 /* visibilityConfigs= */ Collections.emptyList(), 4526 /* forceOverride= */ false, 4527 /* version= */ 0, 4528 /* setSchemaStatsBuilder= */ null); 4529 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4530 4531 // Add two documents and persist them. 4532 GenericDocument document1 = 4533 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 4534 mAppSearchImpl.putDocument( 4535 "package", 4536 "database", 4537 document1, 4538 /* sendChangeNotifications= */ false, 4539 /* logger= */ null); 4540 GenericDocument document2 = 4541 new GenericDocument.Builder<>("namespace2", "id2", "type").build(); 4542 mAppSearchImpl.putDocument( 4543 "package", 4544 "database", 4545 document2, 4546 /* sendChangeNotifications= */ false, 4547 /* logger= */ null); 4548 mAppSearchImpl.persistToDisk(PersistType.Code.LITE); 4549 4550 GenericDocument getResult = 4551 mAppSearchImpl.getDocument( 4552 "package", "database", "namespace1", "id1", Collections.emptyMap()); 4553 assertThat(getResult).isEqualTo(document1); 4554 getResult = 4555 mAppSearchImpl.getDocument( 4556 "package", "database", "namespace2", "id2", Collections.emptyMap()); 4557 assertThat(getResult).isEqualTo(document2); 4558 4559 // Delete the first document 4560 mAppSearchImpl.removeByQuery( 4561 "package", 4562 "database", 4563 "", 4564 new SearchSpec.Builder() 4565 .addFilterNamespaces("namespace1") 4566 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 4567 .build(), 4568 /* statsBuilder= */ null); 4569 mAppSearchImpl.persistToDisk(PersistType.Code.LITE); 4570 assertThrows( 4571 AppSearchException.class, 4572 () -> 4573 mAppSearchImpl.getDocument( 4574 "package", 4575 "database", 4576 "namespace1", 4577 "id1", 4578 Collections.emptyMap())); 4579 getResult = 4580 mAppSearchImpl.getDocument( 4581 "package", "database", "namespace2", "id2", Collections.emptyMap()); 4582 assertThat(getResult).isEqualTo(document2); 4583 4584 // Only the second document should be retrievable from another instance. 4585 AppSearchImpl appSearchImpl2 = 4586 AppSearchImpl.create( 4587 mAppSearchDir, 4588 new AppSearchConfigImpl( 4589 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 4590 /* initStatsBuilder= */ null, 4591 /* visibilityChecker= */ null, 4592 /* revocableFileDescriptorStore= */ null, 4593 ALWAYS_OPTIMIZE); 4594 assertThrows( 4595 AppSearchException.class, 4596 () -> 4597 appSearchImpl2.getDocument( 4598 "package", 4599 "database", 4600 "namespace1", 4601 "id1", 4602 Collections.emptyMap())); 4603 getResult = 4604 appSearchImpl2.getDocument( 4605 "package", "database", "namespace2", "id2", Collections.emptyMap()); 4606 assertThat(getResult).isEqualTo(document2); 4607 appSearchImpl2.close(); 4608 } 4609 4610 @Test 4611 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_BLOB_STORE) testGetIcingSearchEngineStorageInfo()4612 public void testGetIcingSearchEngineStorageInfo() throws Exception { 4613 List<AppSearchSchema> schemas = 4614 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4615 InternalSetSchemaResponse internalSetSchemaResponse = 4616 mAppSearchImpl.setSchema( 4617 "package", 4618 "database", 4619 schemas, 4620 /* visibilityConfigs= */ Collections.emptyList(), 4621 /* forceOverride= */ false, 4622 /* version= */ 0, 4623 /* setSchemaStatsBuilder= */ null); 4624 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4625 4626 // Add two documents 4627 GenericDocument document1 = 4628 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 4629 mAppSearchImpl.putDocument( 4630 "package", 4631 "database", 4632 document1, 4633 /* sendChangeNotifications= */ false, 4634 /* logger= */ null); 4635 GenericDocument document2 = 4636 new GenericDocument.Builder<>("namespace1", "id2", "type").build(); 4637 mAppSearchImpl.putDocument( 4638 "package", 4639 "database", 4640 document2, 4641 /* sendChangeNotifications= */ false, 4642 /* logger= */ null); 4643 4644 StorageInfoProto storageInfo = mAppSearchImpl.getRawStorageInfoProto(); 4645 4646 // Simple checks to verify if we can get correct StorageInfoProto from IcingSearchEngine 4647 // No need to cover all the fields 4648 assertThat(storageInfo.getTotalStorageSize()).isGreaterThan(0); 4649 assertThat(storageInfo.getDocumentStorageInfo().getNumAliveDocuments()).isEqualTo(2); 4650 assertThat(storageInfo.getSchemaStoreStorageInfo().getNumSchemaTypes()) 4651 .isEqualTo(4); // +2 for VisibilitySchema, +1 for VisibilityOverlay 4652 } 4653 4654 @Test 4655 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testGetIcingSearchEngineStorageInfo_enableBlobStore()4656 public void testGetIcingSearchEngineStorageInfo_enableBlobStore() throws Exception { 4657 mAppSearchImpl = 4658 AppSearchImpl.create( 4659 mAppSearchDir, 4660 new AppSearchConfigImpl( 4661 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 4662 /* initStatsBuilder= */ null, 4663 /* visibilityChecker= */ null, 4664 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 4665 ALWAYS_OPTIMIZE); 4666 List<AppSearchSchema> schemas = 4667 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4668 InternalSetSchemaResponse internalSetSchemaResponse = 4669 mAppSearchImpl.setSchema( 4670 "package", 4671 "database", 4672 schemas, 4673 /* visibilityConfigs= */ Collections.emptyList(), 4674 /* forceOverride= */ false, 4675 /* version= */ 0, 4676 /* setSchemaStatsBuilder= */ null); 4677 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4678 4679 // Add two documents 4680 GenericDocument document1 = 4681 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 4682 mAppSearchImpl.putDocument( 4683 "package", 4684 "database", 4685 document1, 4686 /* sendChangeNotifications= */ false, 4687 /* logger= */ null); 4688 GenericDocument document2 = 4689 new GenericDocument.Builder<>("namespace1", "id2", "type").build(); 4690 mAppSearchImpl.putDocument( 4691 "package", 4692 "database", 4693 document2, 4694 /* sendChangeNotifications= */ false, 4695 /* logger= */ null); 4696 4697 StorageInfoProto storageInfo = mAppSearchImpl.getRawStorageInfoProto(); 4698 4699 // Simple checks to verify if we can get correct StorageInfoProto from IcingSearchEngine 4700 // No need to cover all the fields 4701 assertThat(storageInfo.getTotalStorageSize()).isGreaterThan(0); 4702 assertThat(storageInfo.getDocumentStorageInfo().getNumAliveDocuments()).isEqualTo(2); 4703 // +2 (document and blob db) * (2 for VisibilitySchema +1 for VisibilityOverlay) 4704 assertThat(storageInfo.getSchemaStoreStorageInfo().getNumSchemaTypes()).isEqualTo(7); 4705 } 4706 4707 @Test 4708 @RequiresFlagsDisabled(Flags.FLAG_ENABLE_BLOB_STORE) testGetIcingSearchEngineDebugInfo()4709 public void testGetIcingSearchEngineDebugInfo() throws Exception { 4710 List<AppSearchSchema> schemas = 4711 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4712 InternalSetSchemaResponse internalSetSchemaResponse = 4713 mAppSearchImpl.setSchema( 4714 "package", 4715 "database", 4716 schemas, 4717 /* visibilityConfigs= */ Collections.emptyList(), 4718 /* forceOverride= */ false, 4719 /* version= */ 0, 4720 /* setSchemaStatsBuilder= */ null); 4721 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4722 4723 // Add two documents 4724 GenericDocument document1 = 4725 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 4726 mAppSearchImpl.putDocument( 4727 "package", 4728 "database", 4729 document1, 4730 /* sendChangeNotifications= */ false, 4731 /* logger= */ null); 4732 GenericDocument document2 = 4733 new GenericDocument.Builder<>("namespace1", "id2", "type").build(); 4734 mAppSearchImpl.putDocument( 4735 "package", 4736 "database", 4737 document2, 4738 /* sendChangeNotifications= */ false, 4739 /* logger= */ null); 4740 4741 DebugInfoProto debugInfo = 4742 mAppSearchImpl.getRawDebugInfoProto(DebugInfoVerbosity.Code.DETAILED); 4743 4744 // Simple checks to verify if we can get correct DebugInfoProto from IcingSearchEngine 4745 // No need to cover all the fields 4746 assertThat(debugInfo.getDocumentInfo().getCorpusInfoList()).hasSize(1); 4747 assertThat(debugInfo.getDocumentInfo().getDocumentStorageInfo().getNumAliveDocuments()) 4748 .isEqualTo(2); 4749 assertThat(debugInfo.getSchemaInfo().getSchema().getTypesList()) 4750 .hasSize(4); // +2 for VisibilitySchema, +1 for VisibilityOverlay 4751 } 4752 4753 @Test 4754 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testGetIcingSearchEngineDebugInfo_enableBlobStore()4755 public void testGetIcingSearchEngineDebugInfo_enableBlobStore() throws Exception { 4756 mAppSearchImpl = 4757 AppSearchImpl.create( 4758 mAppSearchDir, 4759 new AppSearchConfigImpl( 4760 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 4761 /* initStatsBuilder= */ null, 4762 /* visibilityChecker= */ null, 4763 new JetpackRevocableFileDescriptorStore(mUnlimitedConfig), 4764 ALWAYS_OPTIMIZE); 4765 List<AppSearchSchema> schemas = 4766 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4767 InternalSetSchemaResponse internalSetSchemaResponse = 4768 mAppSearchImpl.setSchema( 4769 "package", 4770 "database", 4771 schemas, 4772 /* visibilityConfigs= */ Collections.emptyList(), 4773 /* forceOverride= */ false, 4774 /* version= */ 0, 4775 /* setSchemaStatsBuilder= */ null); 4776 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4777 4778 // Add two documents 4779 GenericDocument document1 = 4780 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 4781 mAppSearchImpl.putDocument( 4782 "package", 4783 "database", 4784 document1, 4785 /* sendChangeNotifications= */ false, 4786 /* logger= */ null); 4787 GenericDocument document2 = 4788 new GenericDocument.Builder<>("namespace1", "id2", "type").build(); 4789 mAppSearchImpl.putDocument( 4790 "package", 4791 "database", 4792 document2, 4793 /* sendChangeNotifications= */ false, 4794 /* logger= */ null); 4795 4796 DebugInfoProto debugInfo = 4797 mAppSearchImpl.getRawDebugInfoProto(DebugInfoVerbosity.Code.DETAILED); 4798 4799 // Simple checks to verify if we can get correct DebugInfoProto from IcingSearchEngine 4800 // No need to cover all the fields 4801 assertThat(debugInfo.getDocumentInfo().getCorpusInfoList()).hasSize(1); 4802 assertThat(debugInfo.getDocumentInfo().getDocumentStorageInfo().getNumAliveDocuments()) 4803 .isEqualTo(2); 4804 // +2 (document and blob db) * (2 for VisibilitySchema +1 for VisibilityOverlay) 4805 assertThat(debugInfo.getSchemaInfo().getSchema().getTypesList()).hasSize(7); 4806 } 4807 4808 @Test testLimitConfig_DocumentSize()4809 public void testLimitConfig_DocumentSize() throws Exception { 4810 // Create a new mAppSearchImpl with a lower limit 4811 mAppSearchImpl.close(); 4812 mAppSearchImpl = 4813 AppSearchImpl.create( 4814 mTemporaryFolder.newFolder(), 4815 new AppSearchConfigImpl( 4816 new LimitConfig() { 4817 @Override 4818 public int getMaxDocumentSizeBytes() { 4819 return 80; 4820 } 4821 4822 @Override 4823 public int getPerPackageDocumentCountLimit() { 4824 return 1; 4825 } 4826 4827 @Override 4828 public int getDocumentCountLimitStartThreshold() { 4829 return 0; 4830 } 4831 4832 @Override 4833 public int getMaxSuggestionCount() { 4834 return Integer.MAX_VALUE; 4835 } 4836 4837 @Override 4838 public int getMaxOpenBlobCount() { 4839 return Integer.MAX_VALUE; 4840 } 4841 }, 4842 new LocalStorageIcingOptionsConfig()), 4843 /* initStatsBuilder= */ null, 4844 /* visibilityChecker= */ null, 4845 /* revocableFileDescriptorStore= */ null, 4846 ALWAYS_OPTIMIZE); 4847 4848 // Insert schema 4849 List<AppSearchSchema> schemas = 4850 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4851 InternalSetSchemaResponse internalSetSchemaResponse = 4852 mAppSearchImpl.setSchema( 4853 "package", 4854 "database", 4855 schemas, 4856 /* visibilityConfigs= */ Collections.emptyList(), 4857 /* forceOverride= */ false, 4858 /* version= */ 0, 4859 /* setSchemaStatsBuilder= */ null); 4860 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4861 4862 // Insert a document which is too large 4863 GenericDocument document = 4864 new GenericDocument.Builder<>( 4865 "this_namespace_is_long_to_make_the_doc_big", "id", "type") 4866 .build(); 4867 AppSearchException e = 4868 assertThrows( 4869 AppSearchException.class, 4870 () -> 4871 mAppSearchImpl.putDocument( 4872 "package", 4873 "database", 4874 document, 4875 /* sendChangeNotifications= */ false, 4876 /* logger= */ null)); 4877 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 4878 assertThat(e) 4879 .hasMessageThat() 4880 .contains( 4881 "Document \"id\" for package \"package\" serialized to 99 bytes, which" 4882 + " exceeds limit of 80 bytes"); 4883 4884 // Make sure this failure didn't increase our document count. We should still be able to 4885 // index 1 document. 4886 GenericDocument document2 = 4887 new GenericDocument.Builder<>("namespace", "id2", "type").build(); 4888 mAppSearchImpl.putDocument( 4889 "package", 4890 "database", 4891 document2, 4892 /* sendChangeNotifications= */ false, 4893 /* logger= */ null); 4894 4895 // Now we should get a failure 4896 GenericDocument document3 = 4897 new GenericDocument.Builder<>("namespace", "id3", "type").build(); 4898 e = 4899 assertThrows( 4900 AppSearchException.class, 4901 () -> 4902 mAppSearchImpl.putDocument( 4903 "package", 4904 "database", 4905 document3, 4906 /* sendChangeNotifications= */ false, 4907 /* logger= */ null)); 4908 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 4909 assertThat(e) 4910 .hasMessageThat() 4911 .contains("Package \"package\" exceeded limit of 1 documents"); 4912 } 4913 4914 @Test testLimitConfig_Init()4915 public void testLimitConfig_Init() throws Exception { 4916 // Create a new mAppSearchImpl with a lower limit 4917 mAppSearchImpl.close(); 4918 File tempFolder = mTemporaryFolder.newFolder(); 4919 mAppSearchImpl = 4920 AppSearchImpl.create( 4921 tempFolder, 4922 new AppSearchConfigImpl( 4923 new LimitConfig() { 4924 @Override 4925 public int getMaxDocumentSizeBytes() { 4926 return 80; 4927 } 4928 4929 @Override 4930 public int getPerPackageDocumentCountLimit() { 4931 return 1; 4932 } 4933 4934 @Override 4935 public int getDocumentCountLimitStartThreshold() { 4936 return 0; 4937 } 4938 4939 @Override 4940 public int getMaxSuggestionCount() { 4941 return Integer.MAX_VALUE; 4942 } 4943 4944 @Override 4945 public int getMaxOpenBlobCount() { 4946 return Integer.MAX_VALUE; 4947 } 4948 }, 4949 new LocalStorageIcingOptionsConfig()), 4950 /* initStatsBuilder= */ null, 4951 /* visibilityChecker= */ null, 4952 /* revocableFileDescriptorStore= */ null, 4953 ALWAYS_OPTIMIZE); 4954 4955 // Insert schema 4956 List<AppSearchSchema> schemas = 4957 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 4958 InternalSetSchemaResponse internalSetSchemaResponse = 4959 mAppSearchImpl.setSchema( 4960 "package", 4961 "database", 4962 schemas, 4963 /* visibilityConfigs= */ Collections.emptyList(), 4964 /* forceOverride= */ false, 4965 /* version= */ 0, 4966 /* setSchemaStatsBuilder= */ null); 4967 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 4968 4969 // Index a document 4970 mAppSearchImpl.putDocument( 4971 "package", 4972 "database", 4973 new GenericDocument.Builder<>("namespace", "id1", "type").build(), 4974 /* sendChangeNotifications= */ false, 4975 /* logger= */ null); 4976 4977 // Now we should get a failure 4978 GenericDocument document2 = 4979 new GenericDocument.Builder<>("namespace", "id2", "type").build(); 4980 AppSearchException e = 4981 assertThrows( 4982 AppSearchException.class, 4983 () -> 4984 mAppSearchImpl.putDocument( 4985 "package", 4986 "database", 4987 document2, 4988 /* sendChangeNotifications= */ false, 4989 /* logger= */ null)); 4990 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 4991 assertThat(e) 4992 .hasMessageThat() 4993 .contains("Package \"package\" exceeded limit of 1 documents"); 4994 4995 // Close and reinitialize AppSearchImpl 4996 mAppSearchImpl.close(); 4997 mAppSearchImpl = 4998 AppSearchImpl.create( 4999 tempFolder, 5000 new AppSearchConfigImpl( 5001 new LimitConfig() { 5002 @Override 5003 public int getMaxDocumentSizeBytes() { 5004 return 80; 5005 } 5006 5007 @Override 5008 public int getPerPackageDocumentCountLimit() { 5009 return 1; 5010 } 5011 5012 @Override 5013 public int getDocumentCountLimitStartThreshold() { 5014 return 0; 5015 } 5016 5017 @Override 5018 public int getMaxSuggestionCount() { 5019 return Integer.MAX_VALUE; 5020 } 5021 5022 @Override 5023 public int getMaxOpenBlobCount() { 5024 return Integer.MAX_VALUE; 5025 } 5026 }, 5027 new LocalStorageIcingOptionsConfig()), 5028 /* initStatsBuilder= */ null, 5029 /* visibilityChecker= */ null, 5030 /* revocableFileDescriptorStore= */ null, 5031 ALWAYS_OPTIMIZE); 5032 5033 // Make sure the limit is maintained 5034 e = 5035 assertThrows( 5036 AppSearchException.class, 5037 () -> 5038 mAppSearchImpl.putDocument( 5039 "package", 5040 "database", 5041 document2, 5042 /* sendChangeNotifications= */ false, 5043 /* logger= */ null)); 5044 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5045 assertThat(e) 5046 .hasMessageThat() 5047 .contains("Package \"package\" exceeded limit of 1 documents"); 5048 } 5049 5050 @Test testLimitConfig_Remove()5051 public void testLimitConfig_Remove() throws Exception { 5052 // Create a new mAppSearchImpl with a lower limit 5053 mAppSearchImpl.close(); 5054 mAppSearchImpl = 5055 AppSearchImpl.create( 5056 mTemporaryFolder.newFolder(), 5057 new AppSearchConfigImpl( 5058 new LimitConfig() { 5059 @Override 5060 public int getMaxDocumentSizeBytes() { 5061 return Integer.MAX_VALUE; 5062 } 5063 5064 @Override 5065 public int getPerPackageDocumentCountLimit() { 5066 return 3; 5067 } 5068 5069 @Override 5070 public int getDocumentCountLimitStartThreshold() { 5071 return 0; 5072 } 5073 5074 @Override 5075 public int getMaxSuggestionCount() { 5076 return Integer.MAX_VALUE; 5077 } 5078 5079 @Override 5080 public int getMaxOpenBlobCount() { 5081 return Integer.MAX_VALUE; 5082 } 5083 }, 5084 new LocalStorageIcingOptionsConfig()), 5085 /* initStatsBuilder= */ null, 5086 /* visibilityChecker= */ null, 5087 /* revocableFileDescriptorStore= */ null, 5088 ALWAYS_OPTIMIZE); 5089 5090 // Insert schema 5091 List<AppSearchSchema> schemas = 5092 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 5093 InternalSetSchemaResponse internalSetSchemaResponse = 5094 mAppSearchImpl.setSchema( 5095 "package", 5096 "database", 5097 schemas, 5098 /* visibilityConfigs= */ Collections.emptyList(), 5099 /* forceOverride= */ false, 5100 /* version= */ 0, 5101 /* setSchemaStatsBuilder= */ null); 5102 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 5103 5104 // Index 3 documents 5105 mAppSearchImpl.putDocument( 5106 "package", 5107 "database", 5108 new GenericDocument.Builder<>("namespace", "id1", "type").build(), 5109 /* sendChangeNotifications= */ false, 5110 /* logger= */ null); 5111 mAppSearchImpl.putDocument( 5112 "package", 5113 "database", 5114 new GenericDocument.Builder<>("namespace", "id2", "type").build(), 5115 /* sendChangeNotifications= */ false, 5116 /* logger= */ null); 5117 mAppSearchImpl.putDocument( 5118 "package", 5119 "database", 5120 new GenericDocument.Builder<>("namespace", "id3", "type").build(), 5121 /* sendChangeNotifications= */ false, 5122 /* logger= */ null); 5123 5124 // Now we should get a failure 5125 GenericDocument document4 = 5126 new GenericDocument.Builder<>("namespace", "id4", "type").build(); 5127 AppSearchException e = 5128 assertThrows( 5129 AppSearchException.class, 5130 () -> 5131 mAppSearchImpl.putDocument( 5132 "package", 5133 "database", 5134 document4, 5135 /* sendChangeNotifications= */ false, 5136 /* logger= */ null)); 5137 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5138 assertThat(e) 5139 .hasMessageThat() 5140 .contains("Package \"package\" exceeded limit of 3 documents"); 5141 5142 // Remove a document that doesn't exist 5143 assertThrows( 5144 AppSearchException.class, 5145 () -> 5146 mAppSearchImpl.remove( 5147 "package", 5148 "database", 5149 "namespace", 5150 "id4", 5151 /* removeStatsBuilder= */ null)); 5152 5153 // Should still fail 5154 e = 5155 assertThrows( 5156 AppSearchException.class, 5157 () -> 5158 mAppSearchImpl.putDocument( 5159 "package", 5160 "database", 5161 document4, 5162 /* sendChangeNotifications= */ false, 5163 /* logger= */ null)); 5164 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5165 assertThat(e) 5166 .hasMessageThat() 5167 .contains("Package \"package\" exceeded limit of 3 documents"); 5168 5169 // Remove a document that does exist 5170 mAppSearchImpl.remove( 5171 "package", "database", "namespace", "id2", /* removeStatsBuilder= */ null); 5172 5173 // Now doc4 should work 5174 mAppSearchImpl.putDocument( 5175 "package", 5176 "database", 5177 document4, 5178 /* sendChangeNotifications= */ false, 5179 /* logger= */ null); 5180 5181 // The next one should fail again 5182 e = 5183 assertThrows( 5184 AppSearchException.class, 5185 () -> 5186 mAppSearchImpl.putDocument( 5187 "package", 5188 "database", 5189 new GenericDocument.Builder<>("namespace", "id5", "type") 5190 .build(), 5191 /* sendChangeNotifications= */ false, 5192 /* logger= */ null)); 5193 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5194 assertThat(e) 5195 .hasMessageThat() 5196 .contains("Package \"package\" exceeded limit of 3 documents"); 5197 } 5198 5199 @Test testLimitConfig_DifferentPackages()5200 public void testLimitConfig_DifferentPackages() throws Exception { 5201 // Create a new mAppSearchImpl with a lower limit 5202 mAppSearchImpl.close(); 5203 File tempFolder = mTemporaryFolder.newFolder(); 5204 mAppSearchImpl = 5205 AppSearchImpl.create( 5206 tempFolder, 5207 new AppSearchConfigImpl( 5208 new LimitConfig() { 5209 @Override 5210 public int getMaxDocumentSizeBytes() { 5211 return Integer.MAX_VALUE; 5212 } 5213 5214 @Override 5215 public int getPerPackageDocumentCountLimit() { 5216 return 2; 5217 } 5218 5219 @Override 5220 public int getDocumentCountLimitStartThreshold() { 5221 return 0; 5222 } 5223 5224 @Override 5225 public int getMaxSuggestionCount() { 5226 return Integer.MAX_VALUE; 5227 } 5228 5229 @Override 5230 public int getMaxOpenBlobCount() { 5231 return Integer.MAX_VALUE; 5232 } 5233 }, 5234 new LocalStorageIcingOptionsConfig()), 5235 /* initStatsBuilder= */ null, 5236 /* visibilityChecker= */ null, 5237 /* revocableFileDescriptorStore= */ null, 5238 ALWAYS_OPTIMIZE); 5239 5240 // Insert schema 5241 List<AppSearchSchema> schemas = 5242 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 5243 InternalSetSchemaResponse internalSetSchemaResponse = 5244 mAppSearchImpl.setSchema( 5245 "package1", 5246 "database1", 5247 schemas, 5248 /* visibilityConfigs= */ Collections.emptyList(), 5249 /* forceOverride= */ false, 5250 /* version= */ 0, 5251 /* setSchemaStatsBuilder= */ null); 5252 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 5253 internalSetSchemaResponse = 5254 mAppSearchImpl.setSchema( 5255 "package1", 5256 "database2", 5257 schemas, 5258 /* visibilityConfigs= */ Collections.emptyList(), 5259 /* forceOverride= */ false, 5260 /* version= */ 0, 5261 /* setSchemaStatsBuilder= */ null); 5262 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 5263 internalSetSchemaResponse = 5264 mAppSearchImpl.setSchema( 5265 "package2", 5266 "database1", 5267 schemas, 5268 /* visibilityConfigs= */ Collections.emptyList(), 5269 /* forceOverride= */ false, 5270 /* version= */ 0, 5271 /* setSchemaStatsBuilder= */ null); 5272 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 5273 internalSetSchemaResponse = 5274 mAppSearchImpl.setSchema( 5275 "package2", 5276 "database2", 5277 schemas, 5278 /* visibilityConfigs= */ Collections.emptyList(), 5279 /* forceOverride= */ false, 5280 /* version= */ 0, 5281 /* setSchemaStatsBuilder= */ null); 5282 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 5283 5284 // Index documents in package1/database1 5285 mAppSearchImpl.putDocument( 5286 "package1", 5287 "database1", 5288 new GenericDocument.Builder<>("namespace", "id1", "type").build(), 5289 /* sendChangeNotifications= */ false, 5290 /* logger= */ null); 5291 mAppSearchImpl.putDocument( 5292 "package1", 5293 "database2", 5294 new GenericDocument.Builder<>("namespace", "id2", "type").build(), 5295 /* sendChangeNotifications= */ false, 5296 /* logger= */ null); 5297 5298 // Indexing a third doc into package1 should fail (here we use database3) 5299 AppSearchException e = 5300 assertThrows( 5301 AppSearchException.class, 5302 () -> 5303 mAppSearchImpl.putDocument( 5304 "package1", 5305 "database3", 5306 new GenericDocument.Builder<>("namespace", "id3", "type") 5307 .build(), 5308 /* sendChangeNotifications= */ false, 5309 /* logger= */ null)); 5310 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5311 assertThat(e) 5312 .hasMessageThat() 5313 .contains("Package \"package1\" exceeded limit of 2 documents"); 5314 5315 // Indexing a doc into package2 should succeed 5316 mAppSearchImpl.putDocument( 5317 "package2", 5318 "database1", 5319 new GenericDocument.Builder<>("namespace", "id1", "type").build(), 5320 /* sendChangeNotifications= */ false, 5321 /* logger= */ null); 5322 5323 // Reinitialize to make sure packages are parsed correctly on init 5324 mAppSearchImpl.close(); 5325 mAppSearchImpl = 5326 AppSearchImpl.create( 5327 tempFolder, 5328 new AppSearchConfigImpl( 5329 new LimitConfig() { 5330 @Override 5331 public int getMaxDocumentSizeBytes() { 5332 return Integer.MAX_VALUE; 5333 } 5334 5335 @Override 5336 public int getPerPackageDocumentCountLimit() { 5337 return 2; 5338 } 5339 5340 @Override 5341 public int getDocumentCountLimitStartThreshold() { 5342 return 0; 5343 } 5344 5345 @Override 5346 public int getMaxSuggestionCount() { 5347 return Integer.MAX_VALUE; 5348 } 5349 5350 @Override 5351 public int getMaxOpenBlobCount() { 5352 return Integer.MAX_VALUE; 5353 } 5354 }, 5355 new LocalStorageIcingOptionsConfig()), 5356 /* initStatsBuilder= */ null, 5357 /* visibilityChecker= */ null, 5358 /* revocableFileDescriptorStore= */ null, 5359 ALWAYS_OPTIMIZE); 5360 5361 // package1 should still be out of space 5362 e = 5363 assertThrows( 5364 AppSearchException.class, 5365 () -> 5366 mAppSearchImpl.putDocument( 5367 "package1", 5368 "database4", 5369 new GenericDocument.Builder<>("namespace", "id4", "type") 5370 .build(), 5371 /* sendChangeNotifications= */ false, 5372 /* logger= */ null)); 5373 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5374 assertThat(e) 5375 .hasMessageThat() 5376 .contains("Package \"package1\" exceeded limit of 2 documents"); 5377 5378 // package2 has room for one more 5379 mAppSearchImpl.putDocument( 5380 "package2", 5381 "database2", 5382 new GenericDocument.Builder<>("namespace", "id2", "type").build(), 5383 /* sendChangeNotifications= */ false, 5384 /* logger= */ null); 5385 5386 // now package2 really is out of space 5387 e = 5388 assertThrows( 5389 AppSearchException.class, 5390 () -> 5391 mAppSearchImpl.putDocument( 5392 "package2", 5393 "database3", 5394 new GenericDocument.Builder<>("namespace", "id3", "type") 5395 .build(), 5396 /* sendChangeNotifications= */ false, 5397 /* logger= */ null)); 5398 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5399 assertThat(e) 5400 .hasMessageThat() 5401 .contains("Package \"package2\" exceeded limit of 2 documents"); 5402 } 5403 5404 @Test testLimitConfig_RemoveByQuery()5405 public void testLimitConfig_RemoveByQuery() throws Exception { 5406 // Create a new mAppSearchImpl with a lower limit 5407 mAppSearchImpl.close(); 5408 mAppSearchImpl = 5409 AppSearchImpl.create( 5410 mTemporaryFolder.newFolder(), 5411 new AppSearchConfigImpl( 5412 new LimitConfig() { 5413 @Override 5414 public int getMaxDocumentSizeBytes() { 5415 return Integer.MAX_VALUE; 5416 } 5417 5418 @Override 5419 public int getPerPackageDocumentCountLimit() { 5420 return 3; 5421 } 5422 5423 @Override 5424 public int getDocumentCountLimitStartThreshold() { 5425 return 0; 5426 } 5427 5428 @Override 5429 public int getMaxSuggestionCount() { 5430 return Integer.MAX_VALUE; 5431 } 5432 5433 @Override 5434 public int getMaxOpenBlobCount() { 5435 return Integer.MAX_VALUE; 5436 } 5437 }, 5438 new LocalStorageIcingOptionsConfig()), 5439 /* initStatsBuilder= */ null, 5440 /* visibilityChecker= */ null, 5441 /* revocableFileDescriptorStore= */ null, 5442 ALWAYS_OPTIMIZE); 5443 5444 // Insert schema 5445 List<AppSearchSchema> schemas = 5446 Collections.singletonList( 5447 new AppSearchSchema.Builder("type") 5448 .addProperty( 5449 new AppSearchSchema.StringPropertyConfig.Builder("body") 5450 .setIndexingType( 5451 AppSearchSchema.StringPropertyConfig 5452 .INDEXING_TYPE_PREFIXES) 5453 .setTokenizerType( 5454 AppSearchSchema.StringPropertyConfig 5455 .TOKENIZER_TYPE_PLAIN) 5456 .build()) 5457 .build()); 5458 InternalSetSchemaResponse internalSetSchemaResponse = 5459 mAppSearchImpl.setSchema( 5460 "package", 5461 "database", 5462 schemas, 5463 /* visibilityConfigs= */ Collections.emptyList(), 5464 /* forceOverride= */ false, 5465 /* version= */ 0, 5466 /* setSchemaStatsBuilder= */ null); 5467 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 5468 5469 // Index 3 documents 5470 mAppSearchImpl.putDocument( 5471 "package", 5472 "database", 5473 new GenericDocument.Builder<>("namespace", "id1", "type") 5474 .setPropertyString("body", "tablet") 5475 .build(), 5476 /* sendChangeNotifications= */ false, 5477 /* logger= */ null); 5478 mAppSearchImpl.putDocument( 5479 "package", 5480 "database", 5481 new GenericDocument.Builder<>("namespace", "id2", "type") 5482 .setPropertyString("body", "tabby") 5483 .build(), 5484 /* sendChangeNotifications= */ false, 5485 /* logger= */ null); 5486 mAppSearchImpl.putDocument( 5487 "package", 5488 "database", 5489 new GenericDocument.Builder<>("namespace", "id3", "type") 5490 .setPropertyString("body", "grabby") 5491 .build(), 5492 /* sendChangeNotifications= */ false, 5493 /* logger= */ null); 5494 5495 // Now we should get a failure 5496 GenericDocument document4 = 5497 new GenericDocument.Builder<>("namespace", "id4", "type").build(); 5498 AppSearchException e = 5499 assertThrows( 5500 AppSearchException.class, 5501 () -> 5502 mAppSearchImpl.putDocument( 5503 "package", 5504 "database", 5505 document4, 5506 /* sendChangeNotifications= */ false, 5507 /* logger= */ null)); 5508 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5509 assertThat(e) 5510 .hasMessageThat() 5511 .contains("Package \"package\" exceeded limit of 3 documents"); 5512 5513 // Run removebyquery, deleting nothing 5514 mAppSearchImpl.removeByQuery( 5515 "package", 5516 "database", 5517 "nothing", 5518 new SearchSpec.Builder().build(), 5519 /* removeStatsBuilder= */ null); 5520 5521 // Should still fail 5522 e = 5523 assertThrows( 5524 AppSearchException.class, 5525 () -> 5526 mAppSearchImpl.putDocument( 5527 "package", 5528 "database", 5529 document4, 5530 /* sendChangeNotifications= */ false, 5531 /* logger= */ null)); 5532 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5533 assertThat(e) 5534 .hasMessageThat() 5535 .contains("Package \"package\" exceeded limit of 3 documents"); 5536 5537 // Remove "tab*" 5538 mAppSearchImpl.removeByQuery( 5539 "package", 5540 "database", 5541 "tab", 5542 new SearchSpec.Builder().build(), 5543 /* removeStatsBuilder= */ null); 5544 5545 // Now doc4 and doc5 should work 5546 mAppSearchImpl.putDocument( 5547 "package", 5548 "database", 5549 document4, 5550 /* sendChangeNotifications= */ false, 5551 /* logger= */ null); 5552 mAppSearchImpl.putDocument( 5553 "package", 5554 "database", 5555 new GenericDocument.Builder<>("namespace", "id5", "type").build(), 5556 /* sendChangeNotifications= */ false, 5557 /* logger= */ null); 5558 5559 // We only deleted 2 docs so the next one should fail again 5560 e = 5561 assertThrows( 5562 AppSearchException.class, 5563 () -> 5564 mAppSearchImpl.putDocument( 5565 "package", 5566 "database", 5567 new GenericDocument.Builder<>("namespace", "id6", "type") 5568 .build(), 5569 /* sendChangeNotifications= */ false, 5570 /* logger= */ null)); 5571 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5572 assertThat(e) 5573 .hasMessageThat() 5574 .contains("Package \"package\" exceeded limit of 3 documents"); 5575 } 5576 5577 @Test testRemoveByQuery_withJoinSpec_throwsException()5578 public void testRemoveByQuery_withJoinSpec_throwsException() { 5579 Exception e = 5580 assertThrows( 5581 IllegalArgumentException.class, 5582 () -> 5583 mAppSearchImpl.removeByQuery( 5584 /* packageName= */ "", 5585 /* databaseName= */ "", 5586 /* queryExpression= */ "", 5587 new SearchSpec.Builder() 5588 .setJoinSpec( 5589 new JoinSpec.Builder("childProp").build()) 5590 .build(), 5591 null)); 5592 assertThat(e.getMessage()) 5593 .isEqualTo("JoinSpec not allowed in removeByQuery, but JoinSpec was provided"); 5594 } 5595 5596 @Test testLimitConfig_Replace()5597 public void testLimitConfig_Replace() throws Exception { 5598 // Create a new mAppSearchImpl with a lower limit 5599 mAppSearchImpl.close(); 5600 mAppSearchImpl = 5601 AppSearchImpl.create( 5602 mTemporaryFolder.newFolder(), 5603 new AppSearchConfigImpl( 5604 new LimitConfig() { 5605 @Override 5606 public int getMaxDocumentSizeBytes() { 5607 return Integer.MAX_VALUE; 5608 } 5609 5610 @Override 5611 public int getPerPackageDocumentCountLimit() { 5612 return 2; 5613 } 5614 5615 @Override 5616 public int getDocumentCountLimitStartThreshold() { 5617 return 0; 5618 } 5619 5620 @Override 5621 public int getMaxSuggestionCount() { 5622 return Integer.MAX_VALUE; 5623 } 5624 5625 @Override 5626 public int getMaxOpenBlobCount() { 5627 return Integer.MAX_VALUE; 5628 } 5629 }, 5630 new LocalStorageIcingOptionsConfig()), 5631 /* initStatsBuilder= */ null, 5632 /* visibilityChecker= */ null, 5633 /* revocableFileDescriptorStore= */ null, 5634 ALWAYS_OPTIMIZE); 5635 5636 // Insert schema 5637 List<AppSearchSchema> schemas = 5638 Collections.singletonList( 5639 new AppSearchSchema.Builder("type") 5640 .addProperty( 5641 new AppSearchSchema.StringPropertyConfig.Builder("body") 5642 .build()) 5643 .build()); 5644 InternalSetSchemaResponse internalSetSchemaResponse = 5645 mAppSearchImpl.setSchema( 5646 "package", 5647 "database", 5648 schemas, 5649 /* visibilityConfigs= */ Collections.emptyList(), 5650 /* forceOverride= */ false, 5651 /* version= */ 0, 5652 /* setSchemaStatsBuilder= */ null); 5653 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 5654 5655 // Index a document 5656 mAppSearchImpl.putDocument( 5657 "package", 5658 "database", 5659 new GenericDocument.Builder<>("namespace", "id1", "type") 5660 .setPropertyString("body", "id1.orig") 5661 .build(), 5662 /* sendChangeNotifications= */ false, 5663 /* logger= */ null); 5664 // Replace it with another doc 5665 mAppSearchImpl.putDocument( 5666 "package", 5667 "database", 5668 new GenericDocument.Builder<>("namespace", "id1", "type") 5669 .setPropertyString("body", "id1.new") 5670 .build(), 5671 /* sendChangeNotifications= */ false, 5672 /* logger= */ null); 5673 5674 // Index id2. This should pass but only because we check for replacements. 5675 mAppSearchImpl.putDocument( 5676 "package", 5677 "database", 5678 new GenericDocument.Builder<>("namespace", "id2", "type").build(), 5679 /* sendChangeNotifications= */ false, 5680 /* logger= */ null); 5681 5682 // Now we should get a failure on id3 5683 GenericDocument document3 = 5684 new GenericDocument.Builder<>("namespace", "id3", "type").build(); 5685 AppSearchException e = 5686 assertThrows( 5687 AppSearchException.class, 5688 () -> 5689 mAppSearchImpl.putDocument( 5690 "package", 5691 "database", 5692 document3, 5693 /* sendChangeNotifications= */ false, 5694 /* logger= */ null)); 5695 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5696 assertThat(e) 5697 .hasMessageThat() 5698 .contains("Package \"package\" exceeded limit of 2 documents"); 5699 } 5700 5701 @Test testLimitConfig_ReplaceReinit()5702 public void testLimitConfig_ReplaceReinit() throws Exception { 5703 // Create a new mAppSearchImpl with a lower limit 5704 mAppSearchImpl.close(); 5705 File tempFolder = mTemporaryFolder.newFolder(); 5706 mAppSearchImpl = 5707 AppSearchImpl.create( 5708 tempFolder, 5709 new AppSearchConfigImpl( 5710 new LimitConfig() { 5711 @Override 5712 public int getMaxDocumentSizeBytes() { 5713 return Integer.MAX_VALUE; 5714 } 5715 5716 @Override 5717 public int getPerPackageDocumentCountLimit() { 5718 return 2; 5719 } 5720 5721 @Override 5722 public int getDocumentCountLimitStartThreshold() { 5723 return 0; 5724 } 5725 5726 @Override 5727 public int getMaxSuggestionCount() { 5728 return Integer.MAX_VALUE; 5729 } 5730 5731 @Override 5732 public int getMaxOpenBlobCount() { 5733 return Integer.MAX_VALUE; 5734 } 5735 }, 5736 new LocalStorageIcingOptionsConfig()), 5737 /* initStatsBuilder= */ null, 5738 /* visibilityChecker= */ null, 5739 /* revocableFileDescriptorStore= */ null, 5740 ALWAYS_OPTIMIZE); 5741 5742 // Insert schema 5743 List<AppSearchSchema> schemas = 5744 Collections.singletonList( 5745 new AppSearchSchema.Builder("type") 5746 .addProperty( 5747 new AppSearchSchema.StringPropertyConfig.Builder("body") 5748 .build()) 5749 .build()); 5750 InternalSetSchemaResponse internalSetSchemaResponse = 5751 mAppSearchImpl.setSchema( 5752 "package", 5753 "database", 5754 schemas, 5755 /* visibilityConfigs= */ Collections.emptyList(), 5756 /* forceOverride= */ false, 5757 /* version= */ 0, 5758 /* setSchemaStatsBuilder= */ null); 5759 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 5760 5761 // Index a document 5762 mAppSearchImpl.putDocument( 5763 "package", 5764 "database", 5765 new GenericDocument.Builder<>("namespace", "id1", "type") 5766 .setPropertyString("body", "id1.orig") 5767 .build(), 5768 /* sendChangeNotifications= */ false, 5769 /* logger= */ null); 5770 // Replace it with another doc 5771 mAppSearchImpl.putDocument( 5772 "package", 5773 "database", 5774 new GenericDocument.Builder<>("namespace", "id1", "type") 5775 .setPropertyString("body", "id1.new") 5776 .build(), 5777 /* sendChangeNotifications= */ false, 5778 /* logger= */ null); 5779 5780 // Reinitialize to make sure replacements are correctly accounted for by init 5781 mAppSearchImpl.close(); 5782 mAppSearchImpl = 5783 AppSearchImpl.create( 5784 tempFolder, 5785 new AppSearchConfigImpl( 5786 new LimitConfig() { 5787 @Override 5788 public int getMaxDocumentSizeBytes() { 5789 return Integer.MAX_VALUE; 5790 } 5791 5792 @Override 5793 public int getPerPackageDocumentCountLimit() { 5794 return 2; 5795 } 5796 5797 @Override 5798 public int getDocumentCountLimitStartThreshold() { 5799 return 0; 5800 } 5801 5802 @Override 5803 public int getMaxSuggestionCount() { 5804 return Integer.MAX_VALUE; 5805 } 5806 5807 @Override 5808 public int getMaxOpenBlobCount() { 5809 return Integer.MAX_VALUE; 5810 } 5811 }, 5812 new LocalStorageIcingOptionsConfig()), 5813 /* initStatsBuilder= */ null, 5814 /* visibilityChecker= */ null, 5815 /* revocableFileDescriptorStore= */ null, 5816 ALWAYS_OPTIMIZE); 5817 5818 // Index id2. This should pass but only because we check for replacements. 5819 mAppSearchImpl.putDocument( 5820 "package", 5821 "database", 5822 new GenericDocument.Builder<>("namespace", "id2", "type").build(), 5823 /* sendChangeNotifications= */ false, 5824 /* logger= */ null); 5825 5826 // Now we should get a failure on id3 5827 GenericDocument document3 = 5828 new GenericDocument.Builder<>("namespace", "id3", "type").build(); 5829 AppSearchException e = 5830 assertThrows( 5831 AppSearchException.class, 5832 () -> 5833 mAppSearchImpl.putDocument( 5834 "package", 5835 "database", 5836 document3, 5837 /* sendChangeNotifications= */ false, 5838 /* logger= */ null)); 5839 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 5840 assertThat(e) 5841 .hasMessageThat() 5842 .contains("Package \"package\" exceeded limit of 2 documents"); 5843 } 5844 5845 @Test testLimitConfig_suggestion()5846 public void testLimitConfig_suggestion() throws Exception { 5847 mAppSearchImpl.close(); 5848 File tempFolder = mTemporaryFolder.newFolder(); 5849 mAppSearchImpl = 5850 AppSearchImpl.create( 5851 tempFolder, 5852 new AppSearchConfigImpl( 5853 new LimitConfig() { 5854 @Override 5855 public int getMaxDocumentSizeBytes() { 5856 return Integer.MAX_VALUE; 5857 } 5858 5859 @Override 5860 public int getPerPackageDocumentCountLimit() { 5861 return Integer.MAX_VALUE; 5862 } 5863 5864 @Override 5865 public int getDocumentCountLimitStartThreshold() { 5866 return 0; 5867 } 5868 5869 @Override 5870 public int getMaxSuggestionCount() { 5871 return 2; 5872 } 5873 5874 @Override 5875 public int getMaxOpenBlobCount() { 5876 return Integer.MAX_VALUE; 5877 } 5878 }, 5879 new LocalStorageIcingOptionsConfig()), 5880 /* initStatsBuilder= */ null, 5881 /* visibilityChecker= */ null, 5882 /* revocableFileDescriptorStore= */ null, 5883 ALWAYS_OPTIMIZE); 5884 5885 AppSearchException e = 5886 assertThrows( 5887 AppSearchException.class, 5888 () -> 5889 mAppSearchImpl.searchSuggestion( 5890 "package", 5891 "database", 5892 /* suggestionQueryExpression= */ "t", 5893 new SearchSuggestionSpec.Builder(/* totalResultCount= */ 10) 5894 .build())); 5895 assertThat(e.getResultCode()).isEqualTo(RESULT_INVALID_ARGUMENT); 5896 assertThat(e) 5897 .hasMessageThat() 5898 .contains("Trying to get 10 suggestion results, which exceeds limit of 2"); 5899 } 5900 5901 @Test testLimitConfig_belowLimitStartThreshold_limitHasNoEffect()5902 public void testLimitConfig_belowLimitStartThreshold_limitHasNoEffect() throws Exception { 5903 // Create a new mAppSearchImpl with a low limit, but a higher limit start threshold. 5904 mAppSearchImpl.close(); 5905 File tempFolder = mTemporaryFolder.newFolder(); 5906 mAppSearchImpl = 5907 AppSearchImpl.create( 5908 tempFolder, 5909 new AppSearchConfigImpl( 5910 new LimitConfig() { 5911 @Override 5912 public int getMaxDocumentSizeBytes() { 5913 return Integer.MAX_VALUE; 5914 } 5915 5916 @Override 5917 public int getPerPackageDocumentCountLimit() { 5918 return 1; 5919 } 5920 5921 @Override 5922 public int getDocumentCountLimitStartThreshold() { 5923 return 3; 5924 } 5925 5926 @Override 5927 public int getMaxSuggestionCount() { 5928 return Integer.MAX_VALUE; 5929 } 5930 5931 @Override 5932 public int getMaxOpenBlobCount() { 5933 return Integer.MAX_VALUE; 5934 } 5935 }, 5936 new LocalStorageIcingOptionsConfig()), 5937 /* initStatsBuilder= */ null, 5938 /* visibilityChecker= */ null, 5939 /* revocableFileDescriptorStore= */ null, 5940 ALWAYS_OPTIMIZE); 5941 5942 // Insert schema 5943 List<AppSearchSchema> schemas = 5944 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 5945 InternalSetSchemaResponse internalSetSchemaResponse = 5946 mAppSearchImpl.setSchema( 5947 "package", 5948 "database", 5949 schemas, 5950 /* visibilityConfigs= */ Collections.emptyList(), 5951 /* forceOverride= */ false, 5952 /* version= */ 0, 5953 /* setSchemaStatsBuilder= */ null); 5954 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 5955 5956 // Index a document 5957 mAppSearchImpl.putDocument( 5958 "package", 5959 "database", 5960 new GenericDocument.Builder<>("namespace", "id1", "type").build(), 5961 /* sendChangeNotifications= */ false, 5962 /* logger= */ null); 5963 5964 // We should still be able to index another document even though we are over the 5965 // getPerPackageDocumentCountLimit threshold. 5966 GenericDocument document2 = 5967 new GenericDocument.Builder<>("namespace", "id2", "type").build(); 5968 mAppSearchImpl.putDocument( 5969 "package", 5970 "database", 5971 document2, 5972 /* sendChangeNotifications= */ false, 5973 /* logger= */ null); 5974 } 5975 5976 @Test testLimitConfig_aboveLimitStartThreshold_limitTakesEffect()5977 public void testLimitConfig_aboveLimitStartThreshold_limitTakesEffect() throws Exception { 5978 // Create a new mAppSearchImpl with a low limit, but a higher limit start threshold. 5979 mAppSearchImpl.close(); 5980 File tempFolder = mTemporaryFolder.newFolder(); 5981 mAppSearchImpl = 5982 AppSearchImpl.create( 5983 tempFolder, 5984 new AppSearchConfigImpl( 5985 new LimitConfig() { 5986 @Override 5987 public int getMaxDocumentSizeBytes() { 5988 return Integer.MAX_VALUE; 5989 } 5990 5991 @Override 5992 public int getPerPackageDocumentCountLimit() { 5993 return 1; 5994 } 5995 5996 @Override 5997 public int getDocumentCountLimitStartThreshold() { 5998 return 3; 5999 } 6000 6001 @Override 6002 public int getMaxSuggestionCount() { 6003 return Integer.MAX_VALUE; 6004 } 6005 6006 @Override 6007 public int getMaxOpenBlobCount() { 6008 return Integer.MAX_VALUE; 6009 } 6010 }, 6011 new LocalStorageIcingOptionsConfig()), 6012 /* initStatsBuilder= */ null, 6013 /* visibilityChecker= */ null, 6014 /* revocableFileDescriptorStore= */ null, 6015 ALWAYS_OPTIMIZE); 6016 6017 // Insert schemas for thress packages 6018 List<AppSearchSchema> schemas = 6019 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 6020 InternalSetSchemaResponse internalSetSchemaResponse = 6021 mAppSearchImpl.setSchema( 6022 "package", 6023 "database", 6024 schemas, 6025 /* visibilityConfigs= */ Collections.emptyList(), 6026 /* forceOverride= */ false, 6027 /* version= */ 0, 6028 /* setSchemaStatsBuilder= */ null); 6029 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6030 internalSetSchemaResponse = 6031 mAppSearchImpl.setSchema( 6032 "package2", 6033 "database", 6034 schemas, 6035 /* visibilityConfigs= */ Collections.emptyList(), 6036 /* forceOverride= */ false, 6037 /* version= */ 0, 6038 /* setSchemaStatsBuilder= */ null); 6039 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6040 internalSetSchemaResponse = 6041 mAppSearchImpl.setSchema( 6042 "package3", 6043 "database", 6044 schemas, 6045 /* visibilityConfigs= */ Collections.emptyList(), 6046 /* forceOverride= */ false, 6047 /* version= */ 0, 6048 /* setSchemaStatsBuilder= */ null); 6049 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6050 6051 // Index a document 6052 mAppSearchImpl.putDocument( 6053 "package", 6054 "database", 6055 new GenericDocument.Builder<>("namespace", "id1", "type").build(), 6056 /* sendChangeNotifications= */ false, 6057 /* logger= */ null); 6058 6059 // We should still be able to index another document even though we are over the 6060 // getPerPackageDocumentCountLimit threshold. 6061 GenericDocument document2 = 6062 new GenericDocument.Builder<>("namespace", "id2", "type").build(); 6063 mAppSearchImpl.putDocument( 6064 "package", 6065 "database", 6066 document2, 6067 /* sendChangeNotifications= */ false, 6068 /* logger= */ null); 6069 6070 // Index a document in another package. We will now be at the limit start threshold. 6071 GenericDocument document3 = 6072 new GenericDocument.Builder<>("namespace", "id3", "type").build(); 6073 mAppSearchImpl.putDocument( 6074 "package2", 6075 "database", 6076 document3, 6077 /* sendChangeNotifications= */ false, 6078 /* logger= */ null); 6079 6080 // Both packages are at the maxPerPackageDocumentLimitCount and the limit is in force. 6081 // Neither should be able to add another document. 6082 GenericDocument document4 = 6083 new GenericDocument.Builder<>("namespace", "id4", "type").build(); 6084 AppSearchException e = 6085 assertThrows( 6086 AppSearchException.class, 6087 () -> 6088 mAppSearchImpl.putDocument( 6089 "package", 6090 "database", 6091 document4, 6092 /* sendChangeNotifications= */ false, 6093 /* logger= */ null)); 6094 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 6095 assertThat(e) 6096 .hasMessageThat() 6097 .contains("Package \"package\" exceeded limit of 1 documents"); 6098 6099 e = 6100 assertThrows( 6101 AppSearchException.class, 6102 () -> 6103 mAppSearchImpl.putDocument( 6104 "package2", 6105 "database", 6106 document4, 6107 /* sendChangeNotifications= */ false, 6108 /* logger= */ null)); 6109 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 6110 assertThat(e) 6111 .hasMessageThat() 6112 .contains("Package \"package2\" exceeded limit of 1 documents"); 6113 6114 // A new package should still be able to add a document however. 6115 mAppSearchImpl.putDocument( 6116 "package3", 6117 "database", 6118 document4, 6119 /* sendChangeNotifications= */ false, 6120 /* logger= */ null); 6121 } 6122 6123 @Test testLimitConfig_replacement_doesntTriggerLimitStartThreshold()6124 public void testLimitConfig_replacement_doesntTriggerLimitStartThreshold() throws Exception { 6125 // Create a new mAppSearchImpl with a low limit, but a higher limit start threshold. 6126 mAppSearchImpl.close(); 6127 File tempFolder = mTemporaryFolder.newFolder(); 6128 mAppSearchImpl = 6129 AppSearchImpl.create( 6130 tempFolder, 6131 new AppSearchConfigImpl( 6132 new LimitConfig() { 6133 @Override 6134 public int getMaxDocumentSizeBytes() { 6135 return Integer.MAX_VALUE; 6136 } 6137 6138 @Override 6139 public int getPerPackageDocumentCountLimit() { 6140 return 1; 6141 } 6142 6143 @Override 6144 public int getDocumentCountLimitStartThreshold() { 6145 return 3; 6146 } 6147 6148 @Override 6149 public int getMaxSuggestionCount() { 6150 return Integer.MAX_VALUE; 6151 } 6152 6153 @Override 6154 public int getMaxOpenBlobCount() { 6155 return Integer.MAX_VALUE; 6156 } 6157 }, 6158 new LocalStorageIcingOptionsConfig()), 6159 /* initStatsBuilder= */ null, 6160 /* visibilityChecker= */ null, 6161 /* revocableFileDescriptorStore= */ null, 6162 ALWAYS_OPTIMIZE); 6163 6164 // Insert schema 6165 List<AppSearchSchema> schemas = 6166 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 6167 InternalSetSchemaResponse internalSetSchemaResponse = 6168 mAppSearchImpl.setSchema( 6169 "package", 6170 "database", 6171 schemas, 6172 /* visibilityConfigs= */ Collections.emptyList(), 6173 /* forceOverride= */ false, 6174 /* version= */ 0, 6175 /* setSchemaStatsBuilder= */ null); 6176 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6177 6178 // Index two documents 6179 mAppSearchImpl.putDocument( 6180 "package", 6181 "database", 6182 new GenericDocument.Builder<>("namespace", "id1", "type").build(), 6183 /* sendChangeNotifications= */ false, 6184 /* logger= */ null); 6185 6186 GenericDocument document2 = 6187 new GenericDocument.Builder<>("namespace", "id2", "type").build(); 6188 mAppSearchImpl.putDocument( 6189 "package", 6190 "database", 6191 document2, 6192 /* sendChangeNotifications= */ false, 6193 /* logger= */ null); 6194 6195 // Now Index a replacement. This should not trigger the DocumentCountLimitStartThreshold 6196 // because the total number of living documents should still be two. 6197 mAppSearchImpl.putDocument( 6198 "package", 6199 "database", 6200 document2, 6201 /* sendChangeNotifications= */ false, 6202 /* logger= */ null); 6203 6204 // We should be able to index one more document before triggering the limit. 6205 GenericDocument document3 = 6206 new GenericDocument.Builder<>("namespace", "id3", "type").build(); 6207 mAppSearchImpl.putDocument( 6208 "package", 6209 "database", 6210 document3, 6211 /* sendChangeNotifications= */ false, 6212 /* logger= */ null); 6213 } 6214 6215 @Test testLimitConfig_remove_deactivatesDocumentCountLimit()6216 public void testLimitConfig_remove_deactivatesDocumentCountLimit() throws Exception { 6217 // Create a new mAppSearchImpl with a low limit, but a higher limit start threshold. 6218 mAppSearchImpl.close(); 6219 File tempFolder = mTemporaryFolder.newFolder(); 6220 mAppSearchImpl = 6221 AppSearchImpl.create( 6222 tempFolder, 6223 new AppSearchConfigImpl( 6224 new LimitConfig() { 6225 @Override 6226 public int getMaxDocumentSizeBytes() { 6227 return Integer.MAX_VALUE; 6228 } 6229 6230 @Override 6231 public int getPerPackageDocumentCountLimit() { 6232 return 1; 6233 } 6234 6235 @Override 6236 public int getDocumentCountLimitStartThreshold() { 6237 return 3; 6238 } 6239 6240 @Override 6241 public int getMaxSuggestionCount() { 6242 return Integer.MAX_VALUE; 6243 } 6244 6245 @Override 6246 public int getMaxOpenBlobCount() { 6247 return Integer.MAX_VALUE; 6248 } 6249 }, 6250 new LocalStorageIcingOptionsConfig()), 6251 /* initStatsBuilder= */ null, 6252 /* visibilityChecker= */ null, 6253 /* revocableFileDescriptorStore= */ null, 6254 ALWAYS_OPTIMIZE); 6255 6256 // Insert schema 6257 List<AppSearchSchema> schemas = 6258 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 6259 InternalSetSchemaResponse internalSetSchemaResponse = 6260 mAppSearchImpl.setSchema( 6261 "package", 6262 "database", 6263 schemas, 6264 /* visibilityConfigs= */ Collections.emptyList(), 6265 /* forceOverride= */ false, 6266 /* version= */ 0, 6267 /* setSchemaStatsBuilder= */ null); 6268 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6269 internalSetSchemaResponse = 6270 mAppSearchImpl.setSchema( 6271 "package2", 6272 "database", 6273 schemas, 6274 /* visibilityConfigs= */ Collections.emptyList(), 6275 /* forceOverride= */ false, 6276 /* version= */ 0, 6277 /* setSchemaStatsBuilder= */ null); 6278 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6279 6280 // Index three documents in "package" and one in "package2". This will mean four total 6281 // documents in the system which will exceed the limit start threshold of three. The limit 6282 // will be in force and neither package will be able to documents. 6283 mAppSearchImpl.putDocument( 6284 "package", 6285 "database", 6286 new GenericDocument.Builder<>("namespace", "id1", "type").build(), 6287 /* sendChangeNotifications= */ false, 6288 /* logger= */ null); 6289 6290 GenericDocument document2 = 6291 new GenericDocument.Builder<>("namespace", "id2", "type").build(); 6292 mAppSearchImpl.putDocument( 6293 "package", 6294 "database", 6295 document2, 6296 /* sendChangeNotifications= */ false, 6297 /* logger= */ null); 6298 6299 GenericDocument document3 = 6300 new GenericDocument.Builder<>("namespace", "id3", "type").build(); 6301 mAppSearchImpl.putDocument( 6302 "package", 6303 "database", 6304 document3, 6305 /* sendChangeNotifications= */ false, 6306 /* logger= */ null); 6307 6308 GenericDocument document4 = 6309 new GenericDocument.Builder<>("namespace", "id4", "type").build(); 6310 mAppSearchImpl.putDocument( 6311 "package2", 6312 "database", 6313 document4, 6314 /* sendChangeNotifications= */ false, 6315 /* logger= */ null); 6316 6317 // The limit is in force. We should be unable to index another document. Even after we 6318 // delete one document, the system is still over the limit start threshold. 6319 GenericDocument document5 = 6320 new GenericDocument.Builder<>("namespace", "id5", "type").build(); 6321 AppSearchException e = 6322 assertThrows( 6323 AppSearchException.class, 6324 () -> 6325 mAppSearchImpl.putDocument( 6326 "package", 6327 "database", 6328 document5, 6329 /* sendChangeNotifications= */ false, 6330 /* logger= */ null)); 6331 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 6332 assertThat(e) 6333 .hasMessageThat() 6334 .contains("Package \"package\" exceeded limit of 1 documents"); 6335 6336 mAppSearchImpl.remove( 6337 "package", "database", "namespace", "id2", /* removeStatsBuilder= */ null); 6338 e = 6339 assertThrows( 6340 AppSearchException.class, 6341 () -> 6342 mAppSearchImpl.putDocument( 6343 "package", 6344 "database", 6345 document5, 6346 /* sendChangeNotifications= */ false, 6347 /* logger= */ null)); 6348 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 6349 assertThat(e) 6350 .hasMessageThat() 6351 .contains("Package \"package\" exceeded limit of 1 documents"); 6352 6353 // Removing another document will bring the system below the limit start threshold. Now, 6354 // adding another document can succeed. 6355 mAppSearchImpl.remove( 6356 "package", "database", "namespace", "id3", /* removeStatsBuilder= */ null); 6357 mAppSearchImpl.putDocument( 6358 "package", 6359 "database", 6360 document5, 6361 /* sendChangeNotifications= */ false, 6362 /* logger= */ null); 6363 } 6364 6365 @Test testLimitConfig_removeByQuery_deactivatesDocumentCountLimit()6366 public void testLimitConfig_removeByQuery_deactivatesDocumentCountLimit() throws Exception { 6367 // Create a new mAppSearchImpl with a low limit, but a higher limit start threshold. 6368 mAppSearchImpl.close(); 6369 File tempFolder = mTemporaryFolder.newFolder(); 6370 mAppSearchImpl = 6371 AppSearchImpl.create( 6372 tempFolder, 6373 new AppSearchConfigImpl( 6374 new LimitConfig() { 6375 @Override 6376 public int getMaxDocumentSizeBytes() { 6377 return Integer.MAX_VALUE; 6378 } 6379 6380 @Override 6381 public int getPerPackageDocumentCountLimit() { 6382 return 1; 6383 } 6384 6385 @Override 6386 public int getDocumentCountLimitStartThreshold() { 6387 return 3; 6388 } 6389 6390 @Override 6391 public int getMaxSuggestionCount() { 6392 return Integer.MAX_VALUE; 6393 } 6394 6395 @Override 6396 public int getMaxOpenBlobCount() { 6397 return Integer.MAX_VALUE; 6398 } 6399 }, 6400 new LocalStorageIcingOptionsConfig()), 6401 /* initStatsBuilder= */ null, 6402 /* visibilityChecker= */ null, 6403 /* revocableFileDescriptorStore= */ null, 6404 ALWAYS_OPTIMIZE); 6405 6406 // Insert schema 6407 AppSearchSchema schema = 6408 new AppSearchSchema.Builder("type") 6409 .addProperty( 6410 new AppSearchSchema.StringPropertyConfig.Builder("number") 6411 .setIndexingType( 6412 AppSearchSchema.StringPropertyConfig 6413 .INDEXING_TYPE_PREFIXES) 6414 .setTokenizerType( 6415 AppSearchSchema.StringPropertyConfig 6416 .TOKENIZER_TYPE_PLAIN) 6417 .build()) 6418 .addProperty( 6419 new AppSearchSchema.StringPropertyConfig.Builder("evenOdd") 6420 .setIndexingType( 6421 AppSearchSchema.StringPropertyConfig 6422 .INDEXING_TYPE_PREFIXES) 6423 .setTokenizerType( 6424 AppSearchSchema.StringPropertyConfig 6425 .TOKENIZER_TYPE_PLAIN) 6426 .build()) 6427 .build(); 6428 List<AppSearchSchema> schemas = Collections.singletonList(schema); 6429 6430 InternalSetSchemaResponse internalSetSchemaResponse = 6431 mAppSearchImpl.setSchema( 6432 "package", 6433 "database", 6434 schemas, 6435 /* visibilityConfigs= */ Collections.emptyList(), 6436 /* forceOverride= */ false, 6437 /* version= */ 0, 6438 /* setSchemaStatsBuilder= */ null); 6439 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6440 internalSetSchemaResponse = 6441 mAppSearchImpl.setSchema( 6442 "package2", 6443 "database", 6444 schemas, 6445 /* visibilityConfigs= */ Collections.emptyList(), 6446 /* forceOverride= */ false, 6447 /* version= */ 0, 6448 /* setSchemaStatsBuilder= */ null); 6449 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6450 6451 // Index three documents in "package" and one in "package2". This will mean four total 6452 // documents in the system which will exceed the limit start threshold of three. The limit 6453 // will be in force and neither package will be able to documents. 6454 GenericDocument document1 = 6455 new GenericDocument.Builder<>("namespace", "id1", "type") 6456 .setPropertyString("number", "first") 6457 .setPropertyString("evenOdd", "odd") 6458 .build(); 6459 mAppSearchImpl.putDocument( 6460 "package", 6461 "database", 6462 document1, 6463 /* sendChangeNotifications= */ false, 6464 /* logger= */ null); 6465 6466 GenericDocument document2 = 6467 new GenericDocument.Builder<>("namespace", "id2", "type") 6468 .setPropertyString("number", "second") 6469 .setPropertyString("evenOdd", "even") 6470 .build(); 6471 mAppSearchImpl.putDocument( 6472 "package", 6473 "database", 6474 document2, 6475 /* sendChangeNotifications= */ false, 6476 /* logger= */ null); 6477 6478 GenericDocument document3 = 6479 new GenericDocument.Builder<>("namespace", "id3", "type") 6480 .setPropertyString("number", "third") 6481 .setPropertyString("evenOdd", "odd") 6482 .build(); 6483 mAppSearchImpl.putDocument( 6484 "package", 6485 "database", 6486 document3, 6487 /* sendChangeNotifications= */ false, 6488 /* logger= */ null); 6489 6490 GenericDocument document4 = 6491 new GenericDocument.Builder<>("namespace", "id4", "type") 6492 .setPropertyString("number", "fourth") 6493 .setPropertyString("evenOdd", "even") 6494 .build(); 6495 mAppSearchImpl.putDocument( 6496 "package2", 6497 "database", 6498 document4, 6499 /* sendChangeNotifications= */ false, 6500 /* logger= */ null); 6501 6502 // The limit is in force. We should be unable to index another document. 6503 GenericDocument document5 = 6504 new GenericDocument.Builder<>("namespace", "id5", "type") 6505 .setPropertyString("number", "five") 6506 .setPropertyString("evenOdd", "odd") 6507 .build(); 6508 AppSearchException e = 6509 assertThrows( 6510 AppSearchException.class, 6511 () -> 6512 mAppSearchImpl.putDocument( 6513 "package", 6514 "database", 6515 document5, 6516 /* sendChangeNotifications= */ false, 6517 /* logger= */ null)); 6518 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_OUT_OF_SPACE); 6519 assertThat(e) 6520 .hasMessageThat() 6521 .contains("Package \"package\" exceeded limit of 1 documents"); 6522 6523 // Remove two documents by query. Now we should be under the limit and be able to add 6524 // another document. 6525 mAppSearchImpl.removeByQuery( 6526 "package", 6527 "database", 6528 "evenOdd:odd", 6529 new SearchSpec.Builder().build(), 6530 /* removeStatsBuilder= */ null); 6531 mAppSearchImpl.putDocument( 6532 "package", 6533 "database", 6534 document5, 6535 /* sendChangeNotifications= */ false, 6536 /* logger= */ null); 6537 } 6538 6539 @Test 6540 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testLimitConfig_activeWriteFds()6541 public void testLimitConfig_activeWriteFds() throws Exception { 6542 mAppSearchImpl.close(); 6543 File tempFolder = mTemporaryFolder.newFolder(); 6544 AppSearchConfig config = 6545 new AppSearchConfigImpl( 6546 new LimitConfig() { 6547 @Override 6548 public int getMaxDocumentSizeBytes() { 6549 return Integer.MAX_VALUE; 6550 } 6551 6552 @Override 6553 public int getPerPackageDocumentCountLimit() { 6554 return Integer.MAX_VALUE; 6555 } 6556 6557 @Override 6558 public int getDocumentCountLimitStartThreshold() { 6559 return Integer.MAX_VALUE; 6560 } 6561 6562 @Override 6563 public int getMaxSuggestionCount() { 6564 return Integer.MAX_VALUE; 6565 } 6566 6567 @Override 6568 public int getMaxOpenBlobCount() { 6569 return 2; 6570 } 6571 }, 6572 new LocalStorageIcingOptionsConfig()); 6573 mAppSearchImpl = 6574 AppSearchImpl.create( 6575 tempFolder, 6576 config, 6577 /* initStatsBuilder= */ null, 6578 /* visibilityChecker= */ null, 6579 new JetpackRevocableFileDescriptorStore(config), 6580 ALWAYS_OPTIMIZE); 6581 // We could open only 2 fds per package. 6582 byte[] data1 = generateRandomBytes(20 * 1024); // 20 KiB 6583 byte[] digest1 = calculateDigest(data1); 6584 AppSearchBlobHandle handle1 = 6585 AppSearchBlobHandle.createWithSha256(digest1, "package", "db1", "ns"); 6586 ParcelFileDescriptor writer1 = mAppSearchImpl.openWriteBlob("package", "db1", handle1); 6587 6588 byte[] data2 = generateRandomBytes(20 * 1024); // 20 KiB 6589 byte[] digest2 = calculateDigest(data2); 6590 AppSearchBlobHandle handle2 = 6591 AppSearchBlobHandle.createWithSha256(digest2, "package", "db1", "ns"); 6592 ParcelFileDescriptor writer2 = mAppSearchImpl.openWriteBlob("package", "db1", handle2); 6593 6594 // Open 3rd fd will fail. 6595 byte[] data3 = generateRandomBytes(20 * 1024); // 20 KiB 6596 byte[] digest3 = calculateDigest(data3); 6597 AppSearchBlobHandle handle3 = 6598 AppSearchBlobHandle.createWithSha256(digest3, "package", "db1", "ns"); 6599 AppSearchException e = 6600 assertThrows( 6601 AppSearchException.class, 6602 () -> mAppSearchImpl.openWriteBlob("package", "db1", handle3)); 6603 assertThat(e.getResultCode()).isEqualTo(RESULT_OUT_OF_SPACE); 6604 assertThat(e) 6605 .hasMessageThat() 6606 .contains( 6607 "Package \"package\" exceeded limit of 2 opened file descriptors. " 6608 + "Some file descriptors must be closed to open additional ones."); 6609 } 6610 6611 @Test 6612 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testLimitConfig_activeReadFds()6613 public void testLimitConfig_activeReadFds() throws Exception { 6614 mAppSearchImpl.close(); 6615 File tempFolder = mTemporaryFolder.newFolder(); 6616 AppSearchConfig config = 6617 new AppSearchConfigImpl( 6618 new LimitConfig() { 6619 @Override 6620 public int getMaxDocumentSizeBytes() { 6621 return Integer.MAX_VALUE; 6622 } 6623 6624 @Override 6625 public int getPerPackageDocumentCountLimit() { 6626 return Integer.MAX_VALUE; 6627 } 6628 6629 @Override 6630 public int getDocumentCountLimitStartThreshold() { 6631 return Integer.MAX_VALUE; 6632 } 6633 6634 @Override 6635 public int getMaxSuggestionCount() { 6636 return Integer.MAX_VALUE; 6637 } 6638 6639 @Override 6640 public int getMaxOpenBlobCount() { 6641 return 2; 6642 } 6643 }, 6644 new LocalStorageIcingOptionsConfig()); 6645 mAppSearchImpl = 6646 AppSearchImpl.create( 6647 tempFolder, 6648 config, 6649 /* initStatsBuilder= */ null, 6650 /* visibilityChecker= */ null, 6651 new JetpackRevocableFileDescriptorStore(config), 6652 ALWAYS_OPTIMIZE); 6653 6654 // Write and commit one blob 6655 byte[] data = generateRandomBytes(20 * 1024); // 20 KiB 6656 byte[] digest = calculateDigest(data); 6657 AppSearchBlobHandle handle = 6658 AppSearchBlobHandle.createWithSha256( 6659 digest, mContext.getPackageName(), "db1", "ns"); 6660 try (ParcelFileDescriptor writePfd = 6661 mAppSearchImpl.openWriteBlob(mContext.getPackageName(), "db1", handle); 6662 OutputStream outputStream = 6663 new ParcelFileDescriptor.AutoCloseOutputStream(writePfd)) { 6664 outputStream.write(data); 6665 outputStream.flush(); 6666 } 6667 mAppSearchImpl.commitBlob(mContext.getPackageName(), "db1", handle); 6668 6669 ParcelFileDescriptor reader1 = 6670 mAppSearchImpl.openReadBlob(mContext.getPackageName(), "db1", handle); 6671 ParcelFileDescriptor reader2 = 6672 mAppSearchImpl.openReadBlob(mContext.getPackageName(), "db1", handle); 6673 // Open 3rd fd will fail. 6674 AppSearchException e = 6675 assertThrows( 6676 AppSearchException.class, 6677 () -> 6678 mAppSearchImpl.openReadBlob( 6679 mContext.getPackageName(), "db1", handle)); 6680 assertThat(e.getResultCode()).isEqualTo(RESULT_OUT_OF_SPACE); 6681 assertThat(e) 6682 .hasMessageThat() 6683 .contains( 6684 "Package \"" 6685 + mContext.getPackageName() 6686 + "\" exceeded limit of 2 opened file descriptors. Some file" 6687 + " descriptors must be closed to open additional ones."); 6688 6689 // Close 1st fd and open 3rd fd will success 6690 reader1.close(); 6691 ParcelFileDescriptor reader3 = 6692 mAppSearchImpl.openReadBlob(mContext.getPackageName(), "db1", handle); 6693 6694 // GlobalOpenRead will share same limit. 6695 e = 6696 assertThrows( 6697 AppSearchException.class, 6698 () -> mAppSearchImpl.globalOpenReadBlob(handle, mSelfCallerAccess)); 6699 assertThat(e.getResultCode()).isEqualTo(RESULT_OUT_OF_SPACE); 6700 assertThat(e) 6701 .hasMessageThat() 6702 .contains( 6703 "Package \"" 6704 + mContext.getPackageName() 6705 + "\" exceeded limit of 2 opened file descriptors. Some file" 6706 + " descriptors must be closed to open additional ones."); 6707 // Close 2st fd and global open fd will success 6708 reader2.close(); 6709 ParcelFileDescriptor reader4 = mAppSearchImpl.globalOpenReadBlob(handle, mSelfCallerAccess); 6710 6711 // Keep opening will fail 6712 e = 6713 assertThrows( 6714 AppSearchException.class, 6715 () -> 6716 mAppSearchImpl.openReadBlob( 6717 mContext.getPackageName(), "db1", handle)); 6718 assertThat(e.getResultCode()).isEqualTo(RESULT_OUT_OF_SPACE); 6719 assertThat(e) 6720 .hasMessageThat() 6721 .contains( 6722 "Package \"" 6723 + mContext.getPackageName() 6724 + "\" exceeded limit of 2 opened file descriptors. Some file" 6725 + " descriptors must be closed to open additional ones."); 6726 e = 6727 assertThrows( 6728 AppSearchException.class, 6729 () -> mAppSearchImpl.globalOpenReadBlob(handle, mSelfCallerAccess)); 6730 assertThat(e.getResultCode()).isEqualTo(RESULT_OUT_OF_SPACE); 6731 assertThat(e) 6732 .hasMessageThat() 6733 .contains( 6734 "Package \"" 6735 + mContext.getPackageName() 6736 + "\" exceeded limit of 2 opened file descriptors. Some file" 6737 + " descriptors must be closed to open additional ones."); 6738 6739 reader3.close(); 6740 reader4.close(); 6741 } 6742 6743 /** 6744 * Ensure that it is okay to register the same observer for multiple packages and that removing 6745 * the observer for one package doesn't remove it for the other. 6746 */ 6747 @Test testRemoveObserver_onlyAffectsOnePackage()6748 public void testRemoveObserver_onlyAffectsOnePackage() throws Exception { 6749 final String fakePackage = "com.android.appsearch.fake.package"; 6750 6751 InternalSetSchemaResponse internalSetSchemaResponse = 6752 mAppSearchImpl.setSchema( 6753 mContext.getPackageName(), 6754 "database1", 6755 /* schemas= */ ImmutableList.of( 6756 new AppSearchSchema.Builder("Type1").build()), 6757 /* visibilityConfigs= */ Collections.emptyList(), 6758 /* forceOverride= */ false, 6759 /* version= */ 0, 6760 /* setSchemaStatsBuilder= */ null); 6761 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6762 6763 // Register an observer twice, on different packages. 6764 TestObserverCallback observer = new TestObserverCallback(); 6765 mAppSearchImpl.registerObserverCallback( 6766 /* listeningPackageAccess= */ mSelfCallerAccess, 6767 /* targetPackageName= */ mContext.getPackageName(), 6768 new ObserverSpec.Builder().build(), 6769 MoreExecutors.directExecutor(), 6770 observer); 6771 mAppSearchImpl.registerObserverCallback( 6772 /* listeningPackageAccess= */ mSelfCallerAccess, 6773 /* targetPackageName= */ fakePackage, 6774 new ObserverSpec.Builder().build(), 6775 MoreExecutors.directExecutor(), 6776 observer); 6777 6778 // Insert a valid doc 6779 GenericDocument validDoc = 6780 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(); 6781 assertThat(observer.getSchemaChanges()).isEmpty(); 6782 assertThat(observer.getDocumentChanges()).isEmpty(); 6783 mAppSearchImpl.putDocument( 6784 mContext.getPackageName(), 6785 "database1", 6786 validDoc, 6787 /* sendChangeNotifications= */ true, 6788 /* logger= */ null); 6789 6790 // Dispatch notifications and empty the observers 6791 mAppSearchImpl.dispatchAndClearChangeNotifications(); 6792 observer.clear(); 6793 6794 // Remove the observer from the fake package 6795 mAppSearchImpl.unregisterObserverCallback(fakePackage, observer); 6796 6797 // Index a second document 6798 GenericDocument doc2 = new GenericDocument.Builder<>("namespace1", "id2", "Type1").build(); 6799 mAppSearchImpl.putDocument( 6800 mContext.getPackageName(), 6801 "database1", 6802 doc2, 6803 /* sendChangeNotifications= */ true, 6804 /* logger= */ null); 6805 6806 // Observer should still have received this data from its registration on 6807 // context.getPackageName(), as we only removed the copy from fakePackage. 6808 mAppSearchImpl.dispatchAndClearChangeNotifications(); 6809 assertThat(observer.getSchemaChanges()).isEmpty(); 6810 assertThat(observer.getDocumentChanges()) 6811 .containsExactly( 6812 new DocumentChangeInfo( 6813 mContext.getPackageName(), 6814 "database1", 6815 "namespace1", 6816 "Type1", 6817 /* changedDocumentIds= */ ImmutableSet.of("id2"))); 6818 } 6819 6820 @Test testGetGlobalDocumentThrowsExceptionWhenNotVisible()6821 public void testGetGlobalDocumentThrowsExceptionWhenNotVisible() throws Exception { 6822 List<AppSearchSchema> schemas = 6823 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 6824 6825 // Create a new mAppSearchImpl with a mock Visibility Checker 6826 mAppSearchImpl.close(); 6827 File tempFolder = mTemporaryFolder.newFolder(); 6828 VisibilityChecker mockVisibilityChecker = createMockVisibilityChecker(false); 6829 mAppSearchImpl = 6830 AppSearchImpl.create( 6831 tempFolder, 6832 new AppSearchConfigImpl( 6833 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 6834 /* initStatsBuilder= */ null, 6835 mockVisibilityChecker, 6836 /* revocableFileDescriptorStore= */ null, 6837 ALWAYS_OPTIMIZE); 6838 6839 InternalSetSchemaResponse internalSetSchemaResponse = 6840 mAppSearchImpl.setSchema( 6841 "package", 6842 "database", 6843 schemas, 6844 /* visibilityConfigs= */ Collections.emptyList(), 6845 /* forceOverride= */ false, 6846 /* version= */ 0, 6847 /* setSchemaStatsBuilder= */ null); 6848 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6849 6850 // Add a document and persist it. 6851 GenericDocument document = 6852 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 6853 mAppSearchImpl.putDocument( 6854 "package", 6855 "database", 6856 document, 6857 /* sendChangeNotifications= */ false, 6858 /* logger= */ null); 6859 mAppSearchImpl.persistToDisk(PersistType.Code.LITE); 6860 6861 AppSearchException e = 6862 assertThrows( 6863 AppSearchException.class, 6864 () -> 6865 mAppSearchImpl.globalGetDocument( 6866 "package", 6867 "database", 6868 "namespace1", 6869 "id1", 6870 /* typePropertyPaths= */ Collections.emptyMap(), 6871 /* callerAccess= */ mSelfCallerAccess)); 6872 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 6873 assertThat(e.getMessage()).isEqualTo("Document (namespace1, id1) not found."); 6874 } 6875 6876 @Test testGetGlobalDocument()6877 public void testGetGlobalDocument() throws Exception { 6878 List<AppSearchSchema> schemas = 6879 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 6880 6881 // Create a new mAppSearchImpl with a mock Visibility Checker 6882 mAppSearchImpl.close(); 6883 File tempFolder = mTemporaryFolder.newFolder(); 6884 VisibilityChecker mockVisibilityChecker = createMockVisibilityChecker(true); 6885 mAppSearchImpl = 6886 AppSearchImpl.create( 6887 tempFolder, 6888 new AppSearchConfigImpl( 6889 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 6890 /* initStatsBuilder= */ null, 6891 mockVisibilityChecker, 6892 /* revocableFileDescriptorStore= */ null, 6893 ALWAYS_OPTIMIZE); 6894 6895 InternalSetSchemaResponse internalSetSchemaResponse = 6896 mAppSearchImpl.setSchema( 6897 "package", 6898 "database", 6899 schemas, 6900 /* visibilityConfigs= */ Collections.emptyList(), 6901 /* forceOverride= */ false, 6902 /* version= */ 0, 6903 /* setSchemaStatsBuilder= */ null); 6904 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6905 6906 // Add a document and persist it. 6907 GenericDocument document = 6908 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 6909 mAppSearchImpl.putDocument( 6910 "package", 6911 "database", 6912 document, 6913 /* sendChangeNotifications= */ false, 6914 /* logger= */ null); 6915 mAppSearchImpl.persistToDisk(PersistType.Code.LITE); 6916 6917 GenericDocument getResult = 6918 mAppSearchImpl.globalGetDocument( 6919 "package", 6920 "database", 6921 "namespace1", 6922 "id1", 6923 /* typePropertyPaths= */ Collections.emptyMap(), 6924 /* callerAccess= */ mSelfCallerAccess); 6925 assertThat(getResult).isEqualTo(document); 6926 } 6927 6928 @Test getGlobalDocumentTest_notFound()6929 public void getGlobalDocumentTest_notFound() throws Exception { 6930 List<AppSearchSchema> schemas = 6931 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 6932 6933 // Create a new mAppSearchImpl with a mock Visibility Checker 6934 mAppSearchImpl.close(); 6935 File tempFolder = mTemporaryFolder.newFolder(); 6936 VisibilityChecker mockVisibilityChecker = createMockVisibilityChecker(true); 6937 mAppSearchImpl = 6938 AppSearchImpl.create( 6939 tempFolder, 6940 new AppSearchConfigImpl( 6941 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 6942 /* initStatsBuilder= */ null, 6943 mockVisibilityChecker, 6944 /* revocableFileDescriptorStore= */ null, 6945 ALWAYS_OPTIMIZE); 6946 6947 InternalSetSchemaResponse internalSetSchemaResponse = 6948 mAppSearchImpl.setSchema( 6949 "package", 6950 "database", 6951 schemas, 6952 /* visibilityConfigs= */ Collections.emptyList(), 6953 /* forceOverride= */ false, 6954 /* version= */ 0, 6955 /* setSchemaStatsBuilder= */ null); 6956 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 6957 6958 // Add a document and persist it. 6959 GenericDocument document = 6960 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 6961 mAppSearchImpl.putDocument( 6962 "package", 6963 "database", 6964 document, 6965 /* sendChangeNotifications= */ false, 6966 /* logger= */ null); 6967 mAppSearchImpl.persistToDisk(PersistType.Code.LITE); 6968 6969 AppSearchException e = 6970 assertThrows( 6971 AppSearchException.class, 6972 () -> 6973 mAppSearchImpl.globalGetDocument( 6974 "package", 6975 "database", 6976 "namespace1", 6977 "id2", 6978 /* typePropertyPaths= */ Collections.emptyMap(), 6979 /* callerAccess= */ mSelfCallerAccess)); 6980 assertThat(e.getResultCode()).isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 6981 assertThat(e.getMessage()).isEqualTo("Document (namespace1, id2) not found."); 6982 } 6983 6984 @Test getGlobalDocumentNoAccessNoFileHasSameException()6985 public void getGlobalDocumentNoAccessNoFileHasSameException() throws Exception { 6986 List<AppSearchSchema> schemas = 6987 Collections.singletonList(new AppSearchSchema.Builder("type").build()); 6988 // Create a new mAppSearchImpl with a mock Visibility Checker 6989 mAppSearchImpl.close(); 6990 File tempFolder = mTemporaryFolder.newFolder(); 6991 VisibilityChecker mockVisibilityChecker = 6992 new VisibilityChecker() { 6993 @Override 6994 public boolean isSchemaSearchableByCaller( 6995 @NonNull CallerAccess callerAccess, 6996 @NonNull String packageName, 6997 @NonNull String prefixedSchema, 6998 @NonNull VisibilityStore visibilityStore) { 6999 return callerAccess.getCallingPackageName().equals("visiblePackage"); 7000 } 7001 7002 @Override 7003 public boolean doesCallerHaveSystemAccess(@NonNull String callerPackageName) { 7004 return false; 7005 } 7006 }; 7007 7008 mAppSearchImpl = 7009 AppSearchImpl.create( 7010 tempFolder, 7011 new AppSearchConfigImpl( 7012 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 7013 /* initStatsBuilder= */ null, 7014 mockVisibilityChecker, 7015 /* revocableFileDescriptorStore= */ null, 7016 ALWAYS_OPTIMIZE); 7017 7018 InternalSetSchemaResponse internalSetSchemaResponse = 7019 mAppSearchImpl.setSchema( 7020 "package", 7021 "database", 7022 schemas, 7023 /* visibilityConfigs= */ Collections.emptyList(), 7024 /* forceOverride= */ false, 7025 /* version= */ 0, 7026 /* setSchemaStatsBuilder= */ null); 7027 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7028 7029 // Add a document and persist it. 7030 GenericDocument document = 7031 new GenericDocument.Builder<>("namespace1", "id1", "type").build(); 7032 mAppSearchImpl.putDocument( 7033 "package", 7034 "database", 7035 document, 7036 /* sendChangeNotifications= */ false, 7037 /* logger= */ null); 7038 mAppSearchImpl.persistToDisk(PersistType.Code.LITE); 7039 7040 AppSearchException unauthorizedException = 7041 assertThrows( 7042 AppSearchException.class, 7043 () -> 7044 mAppSearchImpl.globalGetDocument( 7045 "package", 7046 "database", 7047 "namespace1", 7048 "id1", 7049 /* typePropertyPaths= */ Collections.emptyMap(), 7050 new CallerAccess( 7051 /* callingPackageName= */ "invisiblePackage"))); 7052 7053 mAppSearchImpl.remove( 7054 "package", "database", "namespace1", "id1", /* removeStatsBuilder= */ null); 7055 7056 AppSearchException noDocException = 7057 assertThrows( 7058 AppSearchException.class, 7059 () -> 7060 mAppSearchImpl.globalGetDocument( 7061 "package", 7062 "database", 7063 "namespace1", 7064 "id1", 7065 /* typePropertyPaths= */ Collections.emptyMap(), 7066 new CallerAccess( 7067 /* callingPackageName= */ "visiblePackage"))); 7068 7069 assertThat(noDocException.getResultCode()).isEqualTo(unauthorizedException.getResultCode()); 7070 assertThat(noDocException.getMessage()).isEqualTo(unauthorizedException.getMessage()); 7071 } 7072 7073 @Test testSetVisibility()7074 public void testSetVisibility() throws Exception { 7075 InternalVisibilityConfig visibilityConfig = 7076 new InternalVisibilityConfig.Builder("Email") 7077 .setNotDisplayedBySystem(true) 7078 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 7079 .build(); 7080 List<AppSearchSchema> schemas = 7081 Collections.singletonList(new AppSearchSchema.Builder("Email").build()); 7082 7083 // Set schema Email to AppSearch database1 with a visibility document 7084 InternalSetSchemaResponse internalSetSchemaResponse = 7085 mAppSearchImpl.setSchema( 7086 "package", 7087 "database1", 7088 schemas, 7089 /* visibilityConfigs= */ ImmutableList.of(visibilityConfig), 7090 /* forceOverride= */ false, 7091 /* version= */ 0, 7092 /* setSchemaStatsBuilder= */ null); 7093 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7094 String prefix = PrefixUtil.createPrefix("package", "database1"); 7095 7096 // assert the visibility document is saved. 7097 InternalVisibilityConfig expectedDocument = 7098 new InternalVisibilityConfig.Builder(prefix + "Email") 7099 .setNotDisplayedBySystem(true) 7100 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 7101 .build(); 7102 assertThat(mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility(prefix + "Email")) 7103 .isEqualTo(expectedDocument); 7104 // Verify the InternalVisibilityConfig is saved to AppSearchImpl. 7105 InternalVisibilityConfig actualDocument = 7106 VisibilityToDocumentConverter.createInternalVisibilityConfig( 7107 mAppSearchImpl.getDocument( 7108 VISIBILITY_PACKAGE_NAME, 7109 DOCUMENT_VISIBILITY_DATABASE_NAME, 7110 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 7111 /* id= */ prefix + "Email", 7112 /* typePropertyPaths= */ Collections.emptyMap()), 7113 /* androidVOverlayDocument= */ null); 7114 assertThat(actualDocument).isEqualTo(expectedDocument); 7115 } 7116 7117 @Test testSetVisibility_existingVisibilitySettingRetains()7118 public void testSetVisibility_existingVisibilitySettingRetains() throws Exception { 7119 // Create Visibility Document for Email1 7120 InternalVisibilityConfig visibilityConfig1 = 7121 new InternalVisibilityConfig.Builder("Email1") 7122 .setNotDisplayedBySystem(true) 7123 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 7124 .build(); 7125 List<AppSearchSchema> schemas1 = 7126 Collections.singletonList(new AppSearchSchema.Builder("Email1").build()); 7127 7128 // Set schema Email1 to package1 with a visibility document 7129 InternalSetSchemaResponse internalSetSchemaResponse = 7130 mAppSearchImpl.setSchema( 7131 "package1", 7132 "database", 7133 schemas1, 7134 /* visibilityConfigs= */ ImmutableList.of(visibilityConfig1), 7135 /* forceOverride= */ false, 7136 /* version= */ 0, 7137 /* setSchemaStatsBuilder= */ null); 7138 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7139 String prefix1 = PrefixUtil.createPrefix("package1", "database"); 7140 7141 // assert the visibility document is saved. 7142 InternalVisibilityConfig expectedDocument1 = 7143 new InternalVisibilityConfig.Builder(prefix1 + "Email1") 7144 .setNotDisplayedBySystem(true) 7145 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 7146 .build(); 7147 assertThat(mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility(prefix1 + "Email1")) 7148 .isEqualTo(expectedDocument1); 7149 // Verify the InternalVisibilityConfig is saved to AppSearchImpl. 7150 InternalVisibilityConfig actualDocument1 = 7151 VisibilityToDocumentConverter.createInternalVisibilityConfig( 7152 mAppSearchImpl.getDocument( 7153 VISIBILITY_PACKAGE_NAME, 7154 DOCUMENT_VISIBILITY_DATABASE_NAME, 7155 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 7156 /* id= */ prefix1 + "Email1", 7157 /* typePropertyPaths= */ Collections.emptyMap()), 7158 /* androidVOverlayDocument= */ null); 7159 7160 assertThat(actualDocument1).isEqualTo(expectedDocument1); 7161 7162 // Create Visibility Document for Email2 7163 InternalVisibilityConfig visibilityConfig2 = 7164 new InternalVisibilityConfig.Builder("Email2") 7165 .setNotDisplayedBySystem(false) 7166 .addVisibleToPackage(new PackageIdentifier("pkgFoo", new byte[32])) 7167 .build(); 7168 List<AppSearchSchema> schemas2 = 7169 Collections.singletonList(new AppSearchSchema.Builder("Email2").build()); 7170 7171 // Set schema Email2 to package1 with a visibility document 7172 internalSetSchemaResponse = 7173 mAppSearchImpl.setSchema( 7174 "package2", 7175 "database", 7176 schemas2, 7177 /* visibilityConfigs= */ ImmutableList.of(visibilityConfig2), 7178 /* forceOverride= */ false, 7179 /* version= */ 0, 7180 /* setSchemaStatsBuilder= */ null); 7181 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7182 String prefix2 = PrefixUtil.createPrefix("package2", "database"); 7183 7184 // assert the visibility document is saved. 7185 InternalVisibilityConfig expectedDocument2 = 7186 new InternalVisibilityConfig.Builder(prefix2 + "Email2") 7187 .setNotDisplayedBySystem(false) 7188 .addVisibleToPackage(new PackageIdentifier("pkgFoo", new byte[32])) 7189 .build(); 7190 assertThat(mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility(prefix2 + "Email2")) 7191 .isEqualTo(expectedDocument2); 7192 // Verify the InternalVisibilityConfig is saved to AppSearchImpl. 7193 InternalVisibilityConfig actualDocument2 = 7194 VisibilityToDocumentConverter.createInternalVisibilityConfig( 7195 mAppSearchImpl.getDocument( 7196 VISIBILITY_PACKAGE_NAME, 7197 DOCUMENT_VISIBILITY_DATABASE_NAME, 7198 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 7199 /* id= */ prefix2 + "Email2", 7200 /* typePropertyPaths= */ Collections.emptyMap()), 7201 /* androidVOverlayDocument= */ null); 7202 assertThat(actualDocument2).isEqualTo(expectedDocument2); 7203 7204 // Check the existing visibility document retains. 7205 assertThat(mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility(prefix1 + "Email1")) 7206 .isEqualTo(expectedDocument1); 7207 // Verify the VisibilityDocument is saved to AppSearchImpl. 7208 actualDocument1 = 7209 VisibilityToDocumentConverter.createInternalVisibilityConfig( 7210 mAppSearchImpl.getDocument( 7211 VISIBILITY_PACKAGE_NAME, 7212 DOCUMENT_VISIBILITY_DATABASE_NAME, 7213 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 7214 /* id= */ prefix1 + "Email1", 7215 /* typePropertyPaths= */ Collections.emptyMap()), 7216 /* androidVOverlayDocument= */ null); 7217 assertThat(actualDocument1).isEqualTo(expectedDocument1); 7218 } 7219 7220 @Test testSetVisibility_removeVisibilitySettings()7221 public void testSetVisibility_removeVisibilitySettings() throws Exception { 7222 // Create a non-all-default visibility document 7223 InternalVisibilityConfig visibilityConfig = 7224 new InternalVisibilityConfig.Builder("Email") 7225 .setNotDisplayedBySystem(true) 7226 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 7227 .build(); 7228 7229 List<AppSearchSchema> schemas = 7230 Collections.singletonList(new AppSearchSchema.Builder("Email").build()); 7231 7232 // Set schema Email and its visibility document to AppSearch database1 7233 InternalSetSchemaResponse internalSetSchemaResponse = 7234 mAppSearchImpl.setSchema( 7235 "package", 7236 "database1", 7237 schemas, 7238 /* visibilityConfigs= */ ImmutableList.of(visibilityConfig), 7239 /* forceOverride= */ false, 7240 /* version= */ 0, 7241 /* setSchemaStatsBuilder= */ null); 7242 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7243 String prefix = PrefixUtil.createPrefix("package", "database1"); 7244 InternalVisibilityConfig expectedDocument = 7245 new InternalVisibilityConfig.Builder(prefix + "Email") 7246 .setNotDisplayedBySystem(true) 7247 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 7248 .build(); 7249 assertThat(mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility(prefix + "Email")) 7250 .isEqualTo(expectedDocument); 7251 InternalVisibilityConfig actualDocument = 7252 VisibilityToDocumentConverter.createInternalVisibilityConfig( 7253 mAppSearchImpl.getDocument( 7254 VISIBILITY_PACKAGE_NAME, 7255 DOCUMENT_VISIBILITY_DATABASE_NAME, 7256 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 7257 /* id= */ prefix + "Email", 7258 /* typePropertyPaths= */ Collections.emptyMap()), 7259 /* androidVOverlayDocument= */ null); 7260 assertThat(actualDocument).isEqualTo(expectedDocument); 7261 7262 // Set schema Email and its all-default visibility document to AppSearch database1 7263 internalSetSchemaResponse = 7264 mAppSearchImpl.setSchema( 7265 "package", 7266 "database1", 7267 schemas, 7268 /* visibilityConfigs= */ ImmutableList.of(), 7269 /* forceOverride= */ false, 7270 /* version= */ 0, 7271 /* setSchemaStatsBuilder= */ null); 7272 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7273 // All-default visibility document won't be saved in AppSearch. 7274 assertThat(mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility(prefix + "Email")) 7275 .isNull(); 7276 // Verify the InternalVisibilityConfig is removed from AppSearchImpl. 7277 AppSearchException e = 7278 assertThrows( 7279 AppSearchException.class, 7280 () -> 7281 mAppSearchImpl.getDocument( 7282 VISIBILITY_PACKAGE_NAME, 7283 DOCUMENT_VISIBILITY_DATABASE_NAME, 7284 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 7285 /* id= */ prefix + "Email", 7286 /* typePropertyPaths= */ Collections.emptyMap())); 7287 assertThat(e) 7288 .hasMessageThat() 7289 .contains("Document (VS#Pkg$VS#Db/, package$database1/Email) not found."); 7290 } 7291 7292 @Test testRemoveVisibility_noRemainingSettings()7293 public void testRemoveVisibility_noRemainingSettings() throws Exception { 7294 // Create a non-all-default visibility document 7295 InternalVisibilityConfig visibilityConfig = 7296 new InternalVisibilityConfig.Builder("Email") 7297 .setNotDisplayedBySystem(true) 7298 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 7299 .build(); 7300 7301 List<AppSearchSchema> schemas = 7302 Collections.singletonList(new AppSearchSchema.Builder("Email").build()); 7303 7304 // Set schema Email and its visibility document to AppSearch database1 7305 mAppSearchImpl.setSchema( 7306 "package", 7307 "database1", 7308 schemas, 7309 /* visibilityConfigs= */ ImmutableList.of(visibilityConfig), 7310 /* forceOverride= */ false, 7311 /* version= */ 0, 7312 /* setSchemaStatsBuilder= */ null); 7313 String prefix = PrefixUtil.createPrefix("package", "database1"); 7314 InternalVisibilityConfig expectedDocument = 7315 new InternalVisibilityConfig.Builder(prefix + "Email") 7316 .setNotDisplayedBySystem(true) 7317 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 7318 .build(); 7319 assertThat(mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility(prefix + "Email")) 7320 .isEqualTo(expectedDocument); 7321 // Verify the InternalVisibilityConfig is saved to AppSearchImpl. 7322 InternalVisibilityConfig actualDocument = 7323 VisibilityToDocumentConverter.createInternalVisibilityConfig( 7324 mAppSearchImpl.getDocument( 7325 VISIBILITY_PACKAGE_NAME, 7326 DOCUMENT_VISIBILITY_DATABASE_NAME, 7327 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 7328 /* id= */ prefix + "Email", 7329 /* typePropertyPaths= */ Collections.emptyMap()), 7330 /* androidVOverlayDocument= */ null); 7331 assertThat(actualDocument).isEqualTo(expectedDocument); 7332 7333 // remove the schema and visibility setting from AppSearch 7334 mAppSearchImpl.setSchema( 7335 "package", 7336 "database1", 7337 /* schemas= */ new ArrayList<>(), 7338 /* visibilityConfigs= */ ImmutableList.of(), 7339 /* forceOverride= */ false, 7340 /* version= */ 0, 7341 /* setSchemaStatsBuilder= */ null); 7342 7343 // add the schema back with an all default visibility setting. 7344 mAppSearchImpl.setSchema( 7345 "package", 7346 "database1", 7347 schemas, 7348 /* visibilityConfigs= */ ImmutableList.of(), 7349 /* forceOverride= */ false, 7350 /* version= */ 0, 7351 /* setSchemaStatsBuilder= */ null); 7352 // All-default visibility document won't be saved in AppSearch. 7353 assertThat(mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility(prefix + "Email")) 7354 .isNull(); 7355 // Verify there is no visibility setting for the schema. 7356 AppSearchException e = 7357 assertThrows( 7358 AppSearchException.class, 7359 () -> 7360 mAppSearchImpl.getDocument( 7361 VISIBILITY_PACKAGE_NAME, 7362 DOCUMENT_VISIBILITY_DATABASE_NAME, 7363 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 7364 /* id= */ prefix + "Email", 7365 /* typePropertyPaths= */ Collections.emptyMap())); 7366 assertThat(e) 7367 .hasMessageThat() 7368 .contains("Document (VS#Pkg$VS#Db/, package$database1/Email) not found."); 7369 } 7370 7371 @Test testCloseAndReopen_visibilityInfoRetains()7372 public void testCloseAndReopen_visibilityInfoRetains() throws Exception { 7373 // set Schema and visibility to AppSearch 7374 InternalVisibilityConfig visibilityConfig = 7375 new InternalVisibilityConfig.Builder("Email") 7376 .setNotDisplayedBySystem(true) 7377 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 7378 .build(); 7379 List<AppSearchSchema> schemas = 7380 Collections.singletonList(new AppSearchSchema.Builder("Email").build()); 7381 InternalSetSchemaResponse internalSetSchemaResponse = 7382 mAppSearchImpl.setSchema( 7383 "packageName", 7384 "databaseName", 7385 schemas, 7386 ImmutableList.of(visibilityConfig), 7387 /* forceOverride= */ true, 7388 /* version= */ 0, 7389 /* setSchemaStatsBuilder= */ null); 7390 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7391 7392 // close and re-open AppSearchImpl, the visibility document retains 7393 mAppSearchImpl.close(); 7394 mAppSearchImpl = 7395 AppSearchImpl.create( 7396 mAppSearchDir, 7397 new AppSearchConfigImpl( 7398 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 7399 /* initStatsBuilder= */ null, 7400 /* visibilityChecker= */ null, 7401 /* revocableFileDescriptorStore= */ null, 7402 ALWAYS_OPTIMIZE); 7403 7404 String prefix = PrefixUtil.createPrefix("packageName", "databaseName"); 7405 InternalVisibilityConfig expectedDocument = 7406 new InternalVisibilityConfig.Builder(prefix + "Email") 7407 .setNotDisplayedBySystem(true) 7408 .addVisibleToPackage(new PackageIdentifier("pkgBar", new byte[32])) 7409 .build(); 7410 7411 assertThat(mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility(prefix + "Email")) 7412 .isEqualTo(expectedDocument); 7413 // Verify the InternalVisibilityConfig is saved to AppSearchImpl. 7414 InternalVisibilityConfig actualDocument = 7415 VisibilityToDocumentConverter.createInternalVisibilityConfig( 7416 mAppSearchImpl.getDocument( 7417 VISIBILITY_PACKAGE_NAME, 7418 DOCUMENT_VISIBILITY_DATABASE_NAME, 7419 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 7420 /* id= */ prefix + "Email", 7421 /* typePropertyPaths= */ Collections.emptyMap()), 7422 /* androidVOverlayDocument= */ null); 7423 assertThat(actualDocument).isEqualTo(expectedDocument); 7424 7425 // remove schema and visibility document 7426 internalSetSchemaResponse = 7427 mAppSearchImpl.setSchema( 7428 "packageName", 7429 "databaseName", 7430 ImmutableList.of(), 7431 ImmutableList.of(), 7432 /* forceOverride= */ true, 7433 /* version= */ 0, 7434 /* setSchemaStatsBuilder= */ null); 7435 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7436 7437 // close and re-open AppSearchImpl, the visibility document removed 7438 mAppSearchImpl.close(); 7439 mAppSearchImpl = 7440 AppSearchImpl.create( 7441 mAppSearchDir, 7442 new AppSearchConfigImpl( 7443 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 7444 /* initStatsBuilder= */ null, 7445 /* visibilityChecker= */ null, 7446 /* revocableFileDescriptorStore= */ null, 7447 ALWAYS_OPTIMIZE); 7448 7449 assertThat(mAppSearchImpl.mDocumentVisibilityStoreLocked.getVisibility(prefix + "Email")) 7450 .isNull(); 7451 // Verify the InternalVisibilityConfig is removed from AppSearchImpl. 7452 AppSearchException e = 7453 assertThrows( 7454 AppSearchException.class, 7455 () -> 7456 mAppSearchImpl.getDocument( 7457 VISIBILITY_PACKAGE_NAME, 7458 DOCUMENT_VISIBILITY_DATABASE_NAME, 7459 VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE, 7460 /* id= */ prefix + "Email", 7461 /* typePropertyPaths= */ Collections.emptyMap())); 7462 assertThat(e) 7463 .hasMessageThat() 7464 .contains("Document (VS#Pkg$VS#Db/, packageName$databaseName/Email) not found."); 7465 } 7466 7467 @Test testGetSchema_global()7468 public void testGetSchema_global() throws Exception { 7469 List<AppSearchSchema> schemas = 7470 Collections.singletonList(new AppSearchSchema.Builder("Type").build()); 7471 7472 // Create a new mAppSearchImpl with a mock Visibility Checker 7473 mAppSearchImpl.close(); 7474 File tempFolder = mTemporaryFolder.newFolder(); 7475 VisibilityChecker mockVisibilityChecker = createMockVisibilityChecker(true); 7476 mAppSearchImpl = 7477 AppSearchImpl.create( 7478 tempFolder, 7479 new AppSearchConfigImpl( 7480 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 7481 /* initStatsBuilder= */ null, 7482 mockVisibilityChecker, 7483 /* revocableFileDescriptorStore= */ null, 7484 ALWAYS_OPTIMIZE); 7485 7486 // Add a schema type that is not displayed by the system 7487 InternalSetSchemaResponse internalSetSchemaResponse = 7488 mAppSearchImpl.setSchema( 7489 "package", 7490 "database", 7491 schemas, 7492 /* visibilityConfigs= */ ImmutableList.of( 7493 new InternalVisibilityConfig.Builder("Type") 7494 .setNotDisplayedBySystem(true) 7495 .build()), 7496 /* forceOverride= */ false, 7497 /* version= */ 0, 7498 /* setSchemaStatsBuilder= */ null); 7499 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7500 7501 // Get this schema as another package 7502 GetSchemaResponse getResponse = 7503 mAppSearchImpl.getSchema( 7504 "package", 7505 "database", 7506 new CallerAccess( 7507 /* callingPackageName= */ "com.android.appsearch.fake.package")); 7508 assertThat(getResponse.getSchemas()).containsExactlyElementsIn(schemas); 7509 assertThat(getResponse.getSchemaTypesNotDisplayedBySystem()).containsExactly("Type"); 7510 } 7511 7512 @Test testGetSchema_nonExistentApp()7513 public void testGetSchema_nonExistentApp() throws Exception { 7514 // Add a schema. The test loses meaning if the schema is completely empty. 7515 InternalSetSchemaResponse internalSetSchemaResponse = 7516 mAppSearchImpl.setSchema( 7517 "package", 7518 "database", 7519 Collections.singletonList(new AppSearchSchema.Builder("Type").build()), 7520 /* visibilityConfigs= */ ImmutableList.of(), 7521 /* forceOverride= */ false, 7522 /* version= */ 0, 7523 /* setSchemaStatsBuilder= */ null); 7524 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7525 7526 // Try to get the schema of a nonexistent package. 7527 GetSchemaResponse getResponse = 7528 mAppSearchImpl.getSchema( 7529 "com.android.appsearch.fake.package", 7530 "database", 7531 new CallerAccess(/* callingPackageName= */ "package")); 7532 assertThat(getResponse.getSchemas()).isEmpty(); 7533 assertThat(getResponse.getSchemaTypesNotDisplayedBySystem()).isEmpty(); 7534 } 7535 7536 @Test testGetSchema_noAccess()7537 public void testGetSchema_noAccess() throws Exception { 7538 List<AppSearchSchema> schemas = 7539 Collections.singletonList(new AppSearchSchema.Builder("Type").build()); 7540 // Add a schema type 7541 InternalSetSchemaResponse internalSetSchemaResponse = 7542 mAppSearchImpl.setSchema( 7543 "package", 7544 "database", 7545 schemas, 7546 /* visibilityConfigs= */ ImmutableList.of(), 7547 /* forceOverride= */ false, 7548 /* version= */ 1, 7549 /* setSchemaStatsBuilder= */ null); 7550 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7551 GetSchemaResponse getResponse = 7552 mAppSearchImpl.getSchema( 7553 "package", 7554 "database", 7555 new CallerAccess( 7556 /* callingPackageName= */ "com.android.appsearch.fake.package")); 7557 assertThat(getResponse.getSchemas()).isEmpty(); 7558 assertThat(getResponse.getSchemaTypesNotDisplayedBySystem()).isEmpty(); 7559 assertThat(getResponse.getVersion()).isEqualTo(0); 7560 7561 // Make sure the test is hooked up right by calling getSchema with the same parameters but 7562 // from the same package 7563 getResponse = 7564 mAppSearchImpl.getSchema( 7565 "package", 7566 "database", 7567 new CallerAccess(/* callingPackageName= */ "package")); 7568 assertThat(getResponse.getSchemas()).containsExactlyElementsIn(schemas); 7569 } 7570 7571 @Test testGetSchema_global_partialAccess()7572 public void testGetSchema_global_partialAccess() throws Exception { 7573 List<AppSearchSchema> schemas = 7574 ImmutableList.of( 7575 new AppSearchSchema.Builder("VisibleType").build(), 7576 new AppSearchSchema.Builder("PrivateType").build()); 7577 7578 // Create a new mAppSearchImpl with a mock Visibility Checker 7579 mAppSearchImpl.close(); 7580 File tempFolder = mTemporaryFolder.newFolder(); 7581 VisibilityChecker mockVisibilityChecker = 7582 new VisibilityChecker() { 7583 @Override 7584 public boolean isSchemaSearchableByCaller( 7585 @NonNull CallerAccess callerAccess, 7586 @NonNull String packageName, 7587 @NonNull String prefixedSchema, 7588 @NonNull VisibilityStore visibilityStore) { 7589 return prefixedSchema.endsWith("VisibleType"); 7590 } 7591 7592 @Override 7593 public boolean doesCallerHaveSystemAccess(@NonNull String callerPackageName) { 7594 return false; 7595 } 7596 }; 7597 7598 mAppSearchImpl = 7599 AppSearchImpl.create( 7600 tempFolder, 7601 new AppSearchConfigImpl( 7602 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 7603 /* initStatsBuilder= */ null, 7604 mockVisibilityChecker, 7605 /* revocableFileDescriptorStore= */ null, 7606 ALWAYS_OPTIMIZE); 7607 7608 // Add two schema types that are not displayed by the system. 7609 InternalSetSchemaResponse internalSetSchemaResponse = 7610 mAppSearchImpl.setSchema( 7611 "package", 7612 "database", 7613 schemas, 7614 /* visibilityConfigs= */ ImmutableList.of( 7615 new InternalVisibilityConfig.Builder("VisibleType") 7616 .setNotDisplayedBySystem(true) 7617 .build(), 7618 new InternalVisibilityConfig.Builder("PrivateType") 7619 .setNotDisplayedBySystem(true) 7620 .build()), 7621 /* forceOverride= */ false, 7622 /* version= */ 1, 7623 /* setSchemaStatsBuilder= */ null); 7624 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7625 7626 GetSchemaResponse getResponse = 7627 mAppSearchImpl.getSchema( 7628 "package", 7629 "database", 7630 new CallerAccess( 7631 /* callingPackageName= */ "com.android.appsearch.fake.package")); 7632 assertThat(getResponse.getSchemas()).containsExactly(schemas.get(0)); 7633 assertThat(getResponse.getSchemaTypesNotDisplayedBySystem()).containsExactly("VisibleType"); 7634 assertThat(getResponse.getVersion()).isEqualTo(1); 7635 } 7636 7637 @Test testGetSchema_global_publicAcl()7638 public void testGetSchema_global_publicAcl() throws Exception { 7639 List<AppSearchSchema> schemas = 7640 ImmutableList.of( 7641 new AppSearchSchema.Builder("PublicTypeA").build(), 7642 new AppSearchSchema.Builder("PublicTypeB").build(), 7643 new AppSearchSchema.Builder("PublicTypeC").build()); 7644 7645 PackageIdentifier pkgA = new PackageIdentifier("A", new byte[32]); 7646 PackageIdentifier pkgB = new PackageIdentifier("B", new byte[32]); 7647 PackageIdentifier pkgC = new PackageIdentifier("C", new byte[32]); 7648 7649 // Create a new mAppSearchImpl with a mock Visibility Checker 7650 mAppSearchImpl.close(); 7651 File tempFolder = mTemporaryFolder.newFolder(); 7652 7653 // Package A is visible to package B & C, package B is visible to package C (based on 7654 // canPackageQuery, which we are mocking). 7655 Map<String, Set<String>> packageCanSee = 7656 ImmutableMap.of( 7657 "A", ImmutableSet.of("A"), 7658 "B", ImmutableSet.of("A", "B"), 7659 "C", ImmutableSet.of("A", "B", "C")); 7660 final VisibilityChecker publicAclMockChecker = 7661 new VisibilityChecker() { 7662 @Override 7663 public boolean isSchemaSearchableByCaller( 7664 @NonNull CallerAccess callerAccess, 7665 @NonNull String packageName, 7666 @NonNull String prefixedSchema, 7667 @NonNull VisibilityStore visibilityStore) { 7668 InternalVisibilityConfig param = 7669 visibilityStore.getVisibility(prefixedSchema); 7670 return packageCanSee 7671 .get(callerAccess.getCallingPackageName()) 7672 .contains( 7673 param.getVisibilityConfig() 7674 .getPubliclyVisibleTargetPackage() 7675 .getPackageName()); 7676 } 7677 7678 @Override 7679 public boolean doesCallerHaveSystemAccess(@NonNull String callerPackageName) { 7680 return false; 7681 } 7682 }; 7683 7684 mAppSearchImpl = 7685 AppSearchImpl.create( 7686 tempFolder, 7687 new AppSearchConfigImpl( 7688 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 7689 /* initStatsBuilder= */ null, 7690 publicAclMockChecker, 7691 /* revocableFileDescriptorStore= */ null, 7692 ALWAYS_OPTIMIZE); 7693 7694 List<InternalVisibilityConfig> visibilityConfigs = 7695 ImmutableList.of( 7696 new InternalVisibilityConfig.Builder("PublicTypeA") 7697 .setPubliclyVisibleTargetPackage(pkgA) 7698 .build(), 7699 new InternalVisibilityConfig.Builder("PublicTypeB") 7700 .setPubliclyVisibleTargetPackage(pkgB) 7701 .build(), 7702 new InternalVisibilityConfig.Builder("PublicTypeC") 7703 .setPubliclyVisibleTargetPackage(pkgC) 7704 .build()); 7705 7706 // Add the three schema types, each with their own publicly visible target package. 7707 InternalSetSchemaResponse internalSetSchemaResponse = 7708 mAppSearchImpl.setSchema( 7709 "package", 7710 "database", 7711 schemas, 7712 visibilityConfigs, 7713 /* forceOverride= */ true, 7714 /* version= */ 1, 7715 /* setSchemaStatsBuilder= */ null); 7716 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7717 7718 // Verify access to schemas based on calling package 7719 GetSchemaResponse getResponse = 7720 mAppSearchImpl.getSchema( 7721 "package", "database", new CallerAccess(pkgA.getPackageName())); 7722 assertThat(getResponse.getSchemas()).containsExactly(schemas.get(0)); 7723 assertThat(getResponse.getPubliclyVisibleSchemas()).containsKey("PublicTypeA"); 7724 7725 getResponse = 7726 mAppSearchImpl.getSchema( 7727 "package", "database", new CallerAccess(pkgB.getPackageName())); 7728 assertThat(getResponse.getSchemas()).containsExactly(schemas.get(0), schemas.get(1)); 7729 assertThat(getResponse.getPubliclyVisibleSchemas()).containsKey("PublicTypeA"); 7730 assertThat(getResponse.getPubliclyVisibleSchemas()).containsKey("PublicTypeB"); 7731 7732 getResponse = 7733 mAppSearchImpl.getSchema( 7734 "package", "database", new CallerAccess(pkgC.getPackageName())); 7735 assertThat(getResponse.getSchemas()).containsExactlyElementsIn(schemas); 7736 assertThat(getResponse.getPubliclyVisibleSchemas()).containsKey("PublicTypeA"); 7737 assertThat(getResponse.getPubliclyVisibleSchemas()).containsKey("PublicTypeB"); 7738 assertThat(getResponse.getPubliclyVisibleSchemas()).containsKey("PublicTypeC"); 7739 } 7740 7741 @Test testGetSchema_global_publicAcl_removal()7742 public void testGetSchema_global_publicAcl_removal() throws Exception { 7743 // This test to ensure the proper documents are created through setSchema, then removed 7744 // when setSchema is called again 7745 List<AppSearchSchema> schemas = 7746 ImmutableList.of( 7747 new AppSearchSchema.Builder("PublicTypeA").build(), 7748 new AppSearchSchema.Builder("PublicTypeB").build(), 7749 new AppSearchSchema.Builder("PublicTypeC").build()); 7750 7751 PackageIdentifier pkgA = new PackageIdentifier("A", new byte[32]); 7752 PackageIdentifier pkgB = new PackageIdentifier("B", new byte[32]); 7753 PackageIdentifier pkgC = new PackageIdentifier("C", new byte[32]); 7754 7755 // Create a new mAppSearchImpl with a mock Visibility Checker 7756 mAppSearchImpl.close(); 7757 File tempFolder = mTemporaryFolder.newFolder(); 7758 7759 // Package A is visible to package B & C, package B is visible to package C (based on 7760 // canPackageQuery, which we are mocking). 7761 Map<String, Set<String>> packageCanSee = 7762 ImmutableMap.of( 7763 "A", ImmutableSet.of("A"), 7764 "B", ImmutableSet.of("A", "B"), 7765 "C", ImmutableSet.of("A", "B", "C")); 7766 final VisibilityChecker publicAclMockChecker = 7767 new VisibilityChecker() { 7768 @Override 7769 public boolean isSchemaSearchableByCaller( 7770 @NonNull CallerAccess callerAccess, 7771 @NonNull String packageName, 7772 @NonNull String prefixedSchema, 7773 @NonNull VisibilityStore visibilityStore) { 7774 InternalVisibilityConfig param = 7775 visibilityStore.getVisibility(prefixedSchema); 7776 return packageCanSee 7777 .get(callerAccess.getCallingPackageName()) 7778 .contains( 7779 param.getVisibilityConfig() 7780 .getPubliclyVisibleTargetPackage() 7781 .getPackageName()); 7782 } 7783 7784 @Override 7785 public boolean doesCallerHaveSystemAccess(@NonNull String callerPackageName) { 7786 return false; 7787 } 7788 }; 7789 7790 mAppSearchImpl = 7791 AppSearchImpl.create( 7792 tempFolder, 7793 new AppSearchConfigImpl( 7794 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 7795 /* initStatsBuilder= */ null, 7796 publicAclMockChecker, 7797 /* revocableFileDescriptorStore= */ null, 7798 ALWAYS_OPTIMIZE); 7799 7800 List<InternalVisibilityConfig> visibilityConfigs = 7801 ImmutableList.of( 7802 new InternalVisibilityConfig.Builder("PublicTypeA") 7803 .setPubliclyVisibleTargetPackage(pkgA) 7804 .build(), 7805 new InternalVisibilityConfig.Builder("PublicTypeB") 7806 .setPubliclyVisibleTargetPackage(pkgB) 7807 .build(), 7808 new InternalVisibilityConfig.Builder("PublicTypeC") 7809 .setPubliclyVisibleTargetPackage(pkgC) 7810 .build()); 7811 7812 // Add two schema types that are not displayed by the system. 7813 InternalSetSchemaResponse internalSetSchemaResponse = 7814 mAppSearchImpl.setSchema( 7815 "package", 7816 "database", 7817 schemas, 7818 visibilityConfigs, 7819 /* forceOverride= */ true, 7820 /* version= */ 1, 7821 /* setSchemaStatsBuilder= */ null); 7822 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7823 7824 // Now check for documents 7825 GenericDocument visibilityOverlayA = 7826 mAppSearchImpl.getDocument( 7827 VISIBILITY_PACKAGE_NAME, 7828 DOCUMENT_ANDROID_V_OVERLAY_DATABASE_NAME, 7829 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 7830 "package$database/PublicTypeA", 7831 Collections.emptyMap()); 7832 GenericDocument visibilityOverlayB = 7833 mAppSearchImpl.getDocument( 7834 VISIBILITY_PACKAGE_NAME, 7835 DOCUMENT_ANDROID_V_OVERLAY_DATABASE_NAME, 7836 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 7837 "package$database/PublicTypeB", 7838 Collections.emptyMap()); 7839 GenericDocument visibilityOverlayC = 7840 mAppSearchImpl.getDocument( 7841 VISIBILITY_PACKAGE_NAME, 7842 DOCUMENT_ANDROID_V_OVERLAY_DATABASE_NAME, 7843 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 7844 "package$database/PublicTypeC", 7845 Collections.emptyMap()); 7846 7847 AndroidVOverlayProto overlayProtoA = 7848 AndroidVOverlayProto.newBuilder() 7849 .setVisibilityConfig( 7850 VisibilityConfigProto.newBuilder() 7851 .setPubliclyVisibleTargetPackage( 7852 PackageIdentifierProto.newBuilder() 7853 .setPackageName("A") 7854 .setPackageSha256Cert( 7855 ByteString.copyFrom(new byte[32])) 7856 .build()) 7857 .build()) 7858 .build(); 7859 AndroidVOverlayProto overlayProtoB = 7860 AndroidVOverlayProto.newBuilder() 7861 .setVisibilityConfig( 7862 VisibilityConfigProto.newBuilder() 7863 .setPubliclyVisibleTargetPackage( 7864 PackageIdentifierProto.newBuilder() 7865 .setPackageName("B") 7866 .setPackageSha256Cert( 7867 ByteString.copyFrom(new byte[32])) 7868 .build()) 7869 .build()) 7870 .build(); 7871 AndroidVOverlayProto overlayProtoC = 7872 AndroidVOverlayProto.newBuilder() 7873 .setVisibilityConfig( 7874 VisibilityConfigProto.newBuilder() 7875 .setPubliclyVisibleTargetPackage( 7876 PackageIdentifierProto.newBuilder() 7877 .setPackageName("C") 7878 .setPackageSha256Cert( 7879 ByteString.copyFrom(new byte[32])) 7880 .build()) 7881 .build()) 7882 .build(); 7883 7884 assertThat(visibilityOverlayA.getPropertyBytes("visibilityProtoSerializeProperty")) 7885 .isEqualTo(overlayProtoA.toByteArray()); 7886 assertThat(visibilityOverlayB.getPropertyBytes("visibilityProtoSerializeProperty")) 7887 .isEqualTo(overlayProtoB.toByteArray()); 7888 assertThat(visibilityOverlayC.getPropertyBytes("visibilityProtoSerializeProperty")) 7889 .isEqualTo(overlayProtoC.toByteArray()); 7890 7891 // now undo the "public" setting 7892 visibilityConfigs = 7893 ImmutableList.of( 7894 new InternalVisibilityConfig.Builder("PublicTypeA").build(), 7895 new InternalVisibilityConfig.Builder("PublicTypeB").build(), 7896 new InternalVisibilityConfig.Builder("PublicTypeC").build()); 7897 7898 InternalSetSchemaResponse internalSetSchemaResponseRemoved = 7899 mAppSearchImpl.setSchema( 7900 "package", 7901 "database", 7902 schemas, 7903 visibilityConfigs, 7904 /* forceOverride= */ true, 7905 /* version= */ 1, 7906 /* setSchemaStatsBuilder= */ null); 7907 assertThat(internalSetSchemaResponseRemoved.isSuccess()).isTrue(); 7908 7909 // Now check for documents again 7910 Exception e = 7911 assertThrows( 7912 AppSearchException.class, 7913 () -> 7914 mAppSearchImpl.getDocument( 7915 VISIBILITY_PACKAGE_NAME, 7916 DOCUMENT_VISIBILITY_DATABASE_NAME, 7917 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 7918 "package$database/PublicTypeA", 7919 Collections.emptyMap())); 7920 assertThat(e.getMessage()).endsWith("not found."); 7921 e = 7922 assertThrows( 7923 AppSearchException.class, 7924 () -> 7925 mAppSearchImpl.getDocument( 7926 VISIBILITY_PACKAGE_NAME, 7927 DOCUMENT_VISIBILITY_DATABASE_NAME, 7928 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 7929 "package$database/PublicTypeB", 7930 Collections.emptyMap())); 7931 assertThat(e.getMessage()).endsWith("not found."); 7932 e = 7933 assertThrows( 7934 AppSearchException.class, 7935 () -> 7936 mAppSearchImpl.getDocument( 7937 VISIBILITY_PACKAGE_NAME, 7938 DOCUMENT_VISIBILITY_DATABASE_NAME, 7939 VisibilityToDocumentConverter.ANDROID_V_OVERLAY_NAMESPACE, 7940 "package$database/PublicTypeC", 7941 Collections.emptyMap())); 7942 assertThat(e.getMessage()).endsWith("not found."); 7943 } 7944 7945 @Test testDispatchObserver_samePackage_noVisStore_accept()7946 public void testDispatchObserver_samePackage_noVisStore_accept() throws Exception { 7947 // Add a schema type 7948 InternalSetSchemaResponse internalSetSchemaResponse = 7949 mAppSearchImpl.setSchema( 7950 mContext.getPackageName(), 7951 "database1", 7952 ImmutableList.of(new AppSearchSchema.Builder("Type1").build()), 7953 /* visibilityConfigs= */ Collections.emptyList(), 7954 /* forceOverride= */ false, 7955 /* version= */ 0, 7956 /* setSchemaStatsBuilder= */ null); 7957 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 7958 7959 // Register an observer 7960 TestObserverCallback observer = new TestObserverCallback(); 7961 mAppSearchImpl.registerObserverCallback( 7962 /* listeningPackageAccess= */ mSelfCallerAccess, 7963 /* targetPackageName= */ mContext.getPackageName(), 7964 new ObserverSpec.Builder().build(), 7965 MoreExecutors.directExecutor(), 7966 observer); 7967 7968 // Insert a valid doc 7969 assertThat(observer.getSchemaChanges()).isEmpty(); 7970 assertThat(observer.getDocumentChanges()).isEmpty(); 7971 mAppSearchImpl.putDocument( 7972 mContext.getPackageName(), 7973 "database1", 7974 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(), 7975 /* sendChangeNotifications= */ true, 7976 /* logger= */ null); 7977 assertThat(observer.getSchemaChanges()).isEmpty(); 7978 assertThat(observer.getDocumentChanges()).isEmpty(); 7979 7980 // Dispatch notifications 7981 mAppSearchImpl.dispatchAndClearChangeNotifications(); 7982 assertThat(observer.getSchemaChanges()).isEmpty(); 7983 assertThat(observer.getDocumentChanges()) 7984 .containsExactly( 7985 new DocumentChangeInfo( 7986 mContext.getPackageName(), 7987 "database1", 7988 "namespace1", 7989 "Type1", 7990 ImmutableSet.of("id1"))); 7991 } 7992 7993 @Test testDispatchObserver_samePackage_withVisStore_accept()7994 public void testDispatchObserver_samePackage_withVisStore_accept() throws Exception { 7995 // Make a visibility checker that rejects everything 7996 final VisibilityChecker rejectChecker = createMockVisibilityChecker(false); 7997 mAppSearchImpl.close(); 7998 mAppSearchImpl = 7999 AppSearchImpl.create( 8000 mAppSearchDir, 8001 new AppSearchConfigImpl( 8002 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 8003 /* initStatsBuilder= */ null, 8004 rejectChecker, 8005 /* revocableFileDescriptorStore= */ null, 8006 ALWAYS_OPTIMIZE); 8007 8008 // Add a schema type 8009 InternalSetSchemaResponse internalSetSchemaResponse = 8010 mAppSearchImpl.setSchema( 8011 mContext.getPackageName(), 8012 "database1", 8013 ImmutableList.of(new AppSearchSchema.Builder("Type1").build()), 8014 /* visibilityConfigs= */ Collections.emptyList(), 8015 /* forceOverride= */ false, 8016 /* version= */ 0, 8017 /* setSchemaStatsBuilder= */ null); 8018 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8019 8020 // Register an observer 8021 TestObserverCallback observer = new TestObserverCallback(); 8022 mAppSearchImpl.registerObserverCallback( 8023 /* listeningPackageAccess= */ mSelfCallerAccess, 8024 /* targetPackageName= */ mContext.getPackageName(), 8025 new ObserverSpec.Builder().build(), 8026 MoreExecutors.directExecutor(), 8027 observer); 8028 8029 // Insert a valid doc 8030 assertThat(observer.getSchemaChanges()).isEmpty(); 8031 assertThat(observer.getDocumentChanges()).isEmpty(); 8032 mAppSearchImpl.putDocument( 8033 mContext.getPackageName(), 8034 "database1", 8035 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(), 8036 /* sendChangeNotifications= */ true, 8037 /* logger= */ null); 8038 assertThat(observer.getSchemaChanges()).isEmpty(); 8039 assertThat(observer.getDocumentChanges()).isEmpty(); 8040 8041 // Dispatch notifications 8042 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8043 assertThat(observer.getSchemaChanges()).isEmpty(); 8044 assertThat(observer.getDocumentChanges()) 8045 .containsExactly( 8046 new DocumentChangeInfo( 8047 mContext.getPackageName(), 8048 "database1", 8049 "namespace1", 8050 "Type1", 8051 ImmutableSet.of("id1"))); 8052 } 8053 8054 @Test testDispatchObserver_differentPackage_noVisStore_reject()8055 public void testDispatchObserver_differentPackage_noVisStore_reject() throws Exception { 8056 // Add a schema type 8057 InternalSetSchemaResponse internalSetSchemaResponse = 8058 mAppSearchImpl.setSchema( 8059 mContext.getPackageName(), 8060 "database1", 8061 ImmutableList.of(new AppSearchSchema.Builder("Type1").build()), 8062 /* visibilityConfigs= */ Collections.emptyList(), 8063 /* forceOverride= */ false, 8064 /* version= */ 0, 8065 /* setSchemaStatsBuilder= */ null); 8066 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8067 8068 // Register an observer from a simulated different package 8069 TestObserverCallback observer = new TestObserverCallback(); 8070 mAppSearchImpl.registerObserverCallback( 8071 new CallerAccess(/* callingPackageName= */ "com.fake.Listening.package"), 8072 /* targetPackageName= */ mContext.getPackageName(), 8073 new ObserverSpec.Builder().build(), 8074 MoreExecutors.directExecutor(), 8075 observer); 8076 8077 // Insert a valid doc 8078 assertThat(observer.getSchemaChanges()).isEmpty(); 8079 assertThat(observer.getDocumentChanges()).isEmpty(); 8080 mAppSearchImpl.putDocument( 8081 mContext.getPackageName(), 8082 "database1", 8083 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(), 8084 /* sendChangeNotifications= */ true, 8085 /* logger= */ null); 8086 assertThat(observer.getSchemaChanges()).isEmpty(); 8087 assertThat(observer.getDocumentChanges()).isEmpty(); 8088 8089 // Dispatch notifications 8090 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8091 assertThat(observer.getSchemaChanges()).isEmpty(); 8092 assertThat(observer.getDocumentChanges()).isEmpty(); 8093 } 8094 8095 @Test testDispatchObserver_differentPackage_withVisStore_accept()8096 public void testDispatchObserver_differentPackage_withVisStore_accept() throws Exception { 8097 final String fakeListeningPackage = "com.fake.listening.package"; 8098 8099 // Make a visibility checker that allows only fakeListeningPackage. 8100 final VisibilityChecker visibilityChecker = 8101 new VisibilityChecker() { 8102 @Override 8103 public boolean isSchemaSearchableByCaller( 8104 @NonNull CallerAccess callerAccess, 8105 @NonNull String packageName, 8106 @NonNull String prefixedSchema, 8107 @NonNull VisibilityStore visibilityStore) { 8108 return callerAccess.getCallingPackageName().equals(fakeListeningPackage); 8109 } 8110 8111 @Override 8112 public boolean doesCallerHaveSystemAccess(@NonNull String callerPackageName) { 8113 return false; 8114 } 8115 }; 8116 mAppSearchImpl.close(); 8117 mAppSearchImpl = 8118 AppSearchImpl.create( 8119 mAppSearchDir, 8120 new AppSearchConfigImpl( 8121 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 8122 /* initStatsBuilder= */ null, 8123 visibilityChecker, 8124 /* revocableFileDescriptorStore= */ null, 8125 ALWAYS_OPTIMIZE); 8126 8127 // Add a schema type 8128 InternalSetSchemaResponse internalSetSchemaResponse = 8129 mAppSearchImpl.setSchema( 8130 mContext.getPackageName(), 8131 "database1", 8132 ImmutableList.of(new AppSearchSchema.Builder("Type1").build()), 8133 /* visibilityConfigs= */ Collections.emptyList(), 8134 /* forceOverride= */ false, 8135 /* version= */ 0, 8136 /* setSchemaStatsBuilder= */ null); 8137 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8138 8139 // Register an observer 8140 TestObserverCallback observer = new TestObserverCallback(); 8141 mAppSearchImpl.registerObserverCallback( 8142 new CallerAccess(/* callingPackageName= */ fakeListeningPackage), 8143 /* targetPackageName= */ mContext.getPackageName(), 8144 new ObserverSpec.Builder().build(), 8145 MoreExecutors.directExecutor(), 8146 observer); 8147 8148 // Insert a valid doc 8149 assertThat(observer.getSchemaChanges()).isEmpty(); 8150 assertThat(observer.getDocumentChanges()).isEmpty(); 8151 mAppSearchImpl.putDocument( 8152 mContext.getPackageName(), 8153 "database1", 8154 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(), 8155 /* sendChangeNotifications= */ true, 8156 /* logger= */ null); 8157 assertThat(observer.getSchemaChanges()).isEmpty(); 8158 assertThat(observer.getDocumentChanges()).isEmpty(); 8159 8160 // Dispatch notifications 8161 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8162 assertThat(observer.getSchemaChanges()).isEmpty(); 8163 assertThat(observer.getDocumentChanges()) 8164 .containsExactly( 8165 new DocumentChangeInfo( 8166 mContext.getPackageName(), 8167 "database1", 8168 "namespace1", 8169 "Type1", 8170 ImmutableSet.of("id1"))); 8171 } 8172 8173 @Test testDispatchObserver_differentPackage_withVisStore_reject()8174 public void testDispatchObserver_differentPackage_withVisStore_reject() throws Exception { 8175 final String fakeListeningPackage = "com.fake.Listening.package"; 8176 8177 // Make a visibility checker that rejects everything. 8178 final VisibilityChecker rejectChecker = createMockVisibilityChecker(false); 8179 mAppSearchImpl.close(); 8180 mAppSearchImpl = 8181 AppSearchImpl.create( 8182 mAppSearchDir, 8183 new AppSearchConfigImpl( 8184 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 8185 /* initStatsBuilder= */ null, 8186 rejectChecker, 8187 /* revocableFileDescriptorStore= */ null, 8188 ALWAYS_OPTIMIZE); 8189 8190 // Add a schema type 8191 InternalSetSchemaResponse internalSetSchemaResponse = 8192 mAppSearchImpl.setSchema( 8193 mContext.getPackageName(), 8194 "database1", 8195 ImmutableList.of(new AppSearchSchema.Builder("Type1").build()), 8196 /* visibilityConfigs= */ Collections.emptyList(), 8197 /* forceOverride= */ false, 8198 /* version= */ 0, 8199 /* setSchemaStatsBuilder= */ null); 8200 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8201 8202 // Register an observer 8203 TestObserverCallback observer = new TestObserverCallback(); 8204 mAppSearchImpl.registerObserverCallback( 8205 new CallerAccess(/* callingPackageName= */ fakeListeningPackage), 8206 /* targetPackageName= */ mContext.getPackageName(), 8207 new ObserverSpec.Builder().build(), 8208 MoreExecutors.directExecutor(), 8209 observer); 8210 8211 // Insert a doc 8212 assertThat(observer.getSchemaChanges()).isEmpty(); 8213 assertThat(observer.getDocumentChanges()).isEmpty(); 8214 mAppSearchImpl.putDocument( 8215 mContext.getPackageName(), 8216 "database1", 8217 new GenericDocument.Builder<>("namespace1", "id1", "Type1").build(), 8218 /* sendChangeNotifications= */ true, 8219 /* logger= */ null); 8220 assertThat(observer.getSchemaChanges()).isEmpty(); 8221 assertThat(observer.getDocumentChanges()).isEmpty(); 8222 8223 // Dispatch notifications 8224 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8225 assertThat(observer.getSchemaChanges()).isEmpty(); 8226 assertThat(observer.getDocumentChanges()).isEmpty(); 8227 } 8228 8229 @Test testAddObserver_schemaChange_added()8230 public void testAddObserver_schemaChange_added() throws Exception { 8231 // Register an observer 8232 TestObserverCallback observer = new TestObserverCallback(); 8233 mAppSearchImpl.registerObserverCallback( 8234 /* listeningPackageAccess= */ mSelfCallerAccess, 8235 /* targetPackageName= */ mContext.getPackageName(), 8236 new ObserverSpec.Builder().build(), 8237 MoreExecutors.directExecutor(), 8238 observer); 8239 8240 // Add a schema type 8241 assertThat(observer.getSchemaChanges()).isEmpty(); 8242 assertThat(observer.getDocumentChanges()).isEmpty(); 8243 InternalSetSchemaResponse internalSetSchemaResponse = 8244 mAppSearchImpl.setSchema( 8245 mContext.getPackageName(), 8246 "database1", 8247 ImmutableList.of(new AppSearchSchema.Builder("Type1").build()), 8248 /* visibilityConfigs= */ Collections.emptyList(), 8249 /* forceOverride= */ false, 8250 /* version= */ 0, 8251 /* setSchemaStatsBuilder= */ null); 8252 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8253 assertThat(observer.getSchemaChanges()).isEmpty(); 8254 assertThat(observer.getDocumentChanges()).isEmpty(); 8255 8256 // Dispatch notifications 8257 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8258 assertThat(observer.getSchemaChanges()) 8259 .containsExactly( 8260 new SchemaChangeInfo( 8261 mContext.getPackageName(), "database1", ImmutableSet.of("Type1"))); 8262 assertThat(observer.getDocumentChanges()).isEmpty(); 8263 8264 // Add two more schema types without touching the existing one 8265 observer.clear(); 8266 internalSetSchemaResponse = 8267 mAppSearchImpl.setSchema( 8268 mContext.getPackageName(), 8269 "database1", 8270 ImmutableList.of( 8271 new AppSearchSchema.Builder("Type1").build(), 8272 new AppSearchSchema.Builder("Type2").build(), 8273 new AppSearchSchema.Builder("Type3").build()), 8274 /* visibilityConfigs= */ Collections.emptyList(), 8275 /* forceOverride= */ false, 8276 /* version= */ 0, 8277 /* setSchemaStatsBuilder= */ null); 8278 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8279 assertThat(observer.getSchemaChanges()).isEmpty(); 8280 assertThat(observer.getDocumentChanges()).isEmpty(); 8281 8282 // Dispatch notifications 8283 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8284 assertThat(observer.getSchemaChanges()) 8285 .containsExactly( 8286 new SchemaChangeInfo( 8287 mContext.getPackageName(), 8288 "database1", 8289 ImmutableSet.of("Type2", "Type3"))); 8290 assertThat(observer.getDocumentChanges()).isEmpty(); 8291 } 8292 8293 @Test testAddObserver_schemaChange_removed()8294 public void testAddObserver_schemaChange_removed() throws Exception { 8295 // Add a schema type 8296 InternalSetSchemaResponse internalSetSchemaResponse = 8297 mAppSearchImpl.setSchema( 8298 mContext.getPackageName(), 8299 "database1", 8300 ImmutableList.of( 8301 new AppSearchSchema.Builder("Type1").build(), 8302 new AppSearchSchema.Builder("Type2").build()), 8303 /* visibilityConfigs= */ Collections.emptyList(), 8304 /* forceOverride= */ false, 8305 /* version= */ 0, 8306 /* setSchemaStatsBuilder= */ null); 8307 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8308 8309 // Register an observer 8310 TestObserverCallback observer = new TestObserverCallback(); 8311 mAppSearchImpl.registerObserverCallback( 8312 /* listeningPackageAccess= */ mSelfCallerAccess, 8313 /* targetPackageName= */ mContext.getPackageName(), 8314 new ObserverSpec.Builder().build(), 8315 MoreExecutors.directExecutor(), 8316 observer); 8317 8318 // Remove Type2 8319 internalSetSchemaResponse = 8320 mAppSearchImpl.setSchema( 8321 mContext.getPackageName(), 8322 "database1", 8323 ImmutableList.of(new AppSearchSchema.Builder("Type1").build()), 8324 /* visibilityConfigs= */ Collections.emptyList(), 8325 /* forceOverride= */ true, 8326 /* version= */ 0, 8327 /* setSchemaStatsBuilder= */ null); 8328 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8329 8330 // Dispatch notifications 8331 assertThat(observer.getSchemaChanges()).isEmpty(); 8332 assertThat(observer.getDocumentChanges()).isEmpty(); 8333 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8334 assertThat(observer.getSchemaChanges()) 8335 .containsExactly( 8336 new SchemaChangeInfo( 8337 mContext.getPackageName(), "database1", ImmutableSet.of("Type2"))); 8338 assertThat(observer.getDocumentChanges()).isEmpty(); 8339 } 8340 8341 @Test testAddObserver_schemaChange_contents()8342 public void testAddObserver_schemaChange_contents() throws Exception { 8343 // Add a schema 8344 InternalSetSchemaResponse internalSetSchemaResponse = 8345 mAppSearchImpl.setSchema( 8346 mContext.getPackageName(), 8347 "database1", 8348 ImmutableList.of( 8349 new AppSearchSchema.Builder("Type1").build(), 8350 new AppSearchSchema.Builder("Type2") 8351 .addProperty( 8352 new AppSearchSchema.BooleanPropertyConfig.Builder( 8353 "booleanProp") 8354 .setCardinality( 8355 AppSearchSchema.PropertyConfig 8356 .CARDINALITY_REQUIRED) 8357 .build()) 8358 .build()), 8359 /* visibilityConfigs= */ Collections.emptyList(), 8360 /* forceOverride= */ false, 8361 /* version= */ 0, 8362 /* setSchemaStatsBuilder= */ null); 8363 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8364 8365 // Register an observer 8366 TestObserverCallback observer = new TestObserverCallback(); 8367 mAppSearchImpl.registerObserverCallback( 8368 /* listeningPackageAccess= */ mSelfCallerAccess, 8369 /* targetPackageName= */ mContext.getPackageName(), 8370 new ObserverSpec.Builder().build(), 8371 MoreExecutors.directExecutor(), 8372 observer); 8373 8374 // Update the schema, but don't make any actual changes 8375 internalSetSchemaResponse = 8376 mAppSearchImpl.setSchema( 8377 mContext.getPackageName(), 8378 "database1", 8379 ImmutableList.of( 8380 new AppSearchSchema.Builder("Type1").build(), 8381 new AppSearchSchema.Builder("Type2") 8382 .addProperty( 8383 new AppSearchSchema.BooleanPropertyConfig.Builder( 8384 "booleanProp") 8385 .setCardinality( 8386 AppSearchSchema.PropertyConfig 8387 .CARDINALITY_REQUIRED) 8388 .build()) 8389 .build()), 8390 /* visibilityConfigs= */ Collections.emptyList(), 8391 /* forceOverride= */ false, 8392 /* version= */ 1, 8393 /* setSchemaStatsBuilder= */ null); 8394 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8395 8396 // Dispatch notifications 8397 assertThat(observer.getSchemaChanges()).isEmpty(); 8398 assertThat(observer.getDocumentChanges()).isEmpty(); 8399 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8400 assertThat(observer.getSchemaChanges()).isEmpty(); 8401 assertThat(observer.getDocumentChanges()).isEmpty(); 8402 8403 // Now update the schema again, but this time actually make a change (cardinality of the 8404 // property) 8405 internalSetSchemaResponse = 8406 mAppSearchImpl.setSchema( 8407 mContext.getPackageName(), 8408 "database1", 8409 ImmutableList.of( 8410 new AppSearchSchema.Builder("Type1").build(), 8411 new AppSearchSchema.Builder("Type2") 8412 .addProperty( 8413 new AppSearchSchema.BooleanPropertyConfig.Builder( 8414 "booleanProp") 8415 .setCardinality( 8416 AppSearchSchema.PropertyConfig 8417 .CARDINALITY_OPTIONAL) 8418 .build()) 8419 .build()), 8420 /* visibilityConfigs= */ Collections.emptyList(), 8421 /* forceOverride= */ false, 8422 /* version= */ 2, 8423 /* setSchemaStatsBuilder= */ null); 8424 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8425 8426 // Dispatch notifications 8427 assertThat(observer.getSchemaChanges()).isEmpty(); 8428 assertThat(observer.getDocumentChanges()).isEmpty(); 8429 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8430 assertThat(observer.getSchemaChanges()) 8431 .containsExactly( 8432 new SchemaChangeInfo( 8433 mContext.getPackageName(), "database1", ImmutableSet.of("Type2"))); 8434 assertThat(observer.getDocumentChanges()).isEmpty(); 8435 } 8436 8437 @Test testAddObserver_schemaChange_contents_skipBySpec()8438 public void testAddObserver_schemaChange_contents_skipBySpec() throws Exception { 8439 // Add a schema 8440 InternalSetSchemaResponse internalSetSchemaResponse = 8441 mAppSearchImpl.setSchema( 8442 mContext.getPackageName(), 8443 "database1", 8444 ImmutableList.of( 8445 new AppSearchSchema.Builder("Type1") 8446 .addProperty( 8447 new AppSearchSchema.BooleanPropertyConfig.Builder( 8448 "booleanProp") 8449 .setCardinality( 8450 AppSearchSchema.PropertyConfig 8451 .CARDINALITY_REQUIRED) 8452 .build()) 8453 .build(), 8454 new AppSearchSchema.Builder("Type2") 8455 .addProperty( 8456 new AppSearchSchema.BooleanPropertyConfig.Builder( 8457 "booleanProp") 8458 .setCardinality( 8459 AppSearchSchema.PropertyConfig 8460 .CARDINALITY_REQUIRED) 8461 .build()) 8462 .build()), 8463 /* visibilityConfigs= */ Collections.emptyList(), 8464 /* forceOverride= */ false, 8465 /* version= */ 0, 8466 /* setSchemaStatsBuilder= */ null); 8467 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8468 8469 // Register an observer that only listens for Type2 8470 TestObserverCallback observer = new TestObserverCallback(); 8471 mAppSearchImpl.registerObserverCallback( 8472 /* listeningPackageAccess= */ mSelfCallerAccess, 8473 /* targetPackageName= */ mContext.getPackageName(), 8474 new ObserverSpec.Builder().addFilterSchemas("Type2").build(), 8475 MoreExecutors.directExecutor(), 8476 observer); 8477 8478 // Update both types of the schema (changed cardinalities) 8479 internalSetSchemaResponse = 8480 mAppSearchImpl.setSchema( 8481 mContext.getPackageName(), 8482 "database1", 8483 ImmutableList.of( 8484 new AppSearchSchema.Builder("Type1") 8485 .addProperty( 8486 new AppSearchSchema.BooleanPropertyConfig.Builder( 8487 "booleanProp") 8488 .setCardinality( 8489 AppSearchSchema.PropertyConfig 8490 .CARDINALITY_OPTIONAL) 8491 .build()) 8492 .build(), 8493 new AppSearchSchema.Builder("Type2") 8494 .addProperty( 8495 new AppSearchSchema.BooleanPropertyConfig.Builder( 8496 "booleanProp") 8497 .setCardinality( 8498 AppSearchSchema.PropertyConfig 8499 .CARDINALITY_OPTIONAL) 8500 .build()) 8501 .build()), 8502 /* visibilityConfigs= */ Collections.emptyList(), 8503 /* forceOverride= */ false, 8504 /* version= */ 0, 8505 /* setSchemaStatsBuilder= */ null); 8506 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8507 8508 // Dispatch notifications 8509 assertThat(observer.getSchemaChanges()).isEmpty(); 8510 assertThat(observer.getDocumentChanges()).isEmpty(); 8511 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8512 assertThat(observer.getSchemaChanges()) 8513 .containsExactly( 8514 new SchemaChangeInfo( 8515 mContext.getPackageName(), "database1", ImmutableSet.of("Type2"))); 8516 assertThat(observer.getDocumentChanges()).isEmpty(); 8517 } 8518 8519 @Test testAddObserver_schemaChange_visibilityOnly()8520 public void testAddObserver_schemaChange_visibilityOnly() throws Exception { 8521 final String fakeListeningPackage = "com.fake.listening.package"; 8522 8523 // Make a fake visibility checker that actually looks at visibility store 8524 final VisibilityChecker visibilityChecker = 8525 new VisibilityChecker() { 8526 @Override 8527 public boolean isSchemaSearchableByCaller( 8528 @NonNull CallerAccess callerAccess, 8529 @NonNull String packageName, 8530 @NonNull String prefixedSchema, 8531 @NonNull VisibilityStore visibilityStore) { 8532 if (!callerAccess.getCallingPackageName().equals(fakeListeningPackage)) { 8533 return false; 8534 } 8535 8536 for (PackageIdentifier packageIdentifier : 8537 visibilityStore 8538 .getVisibility(prefixedSchema) 8539 .getVisibilityConfig() 8540 .getAllowedPackages()) { 8541 if (packageIdentifier.getPackageName().equals(fakeListeningPackage)) { 8542 return true; 8543 } 8544 } 8545 return false; 8546 } 8547 8548 @Override 8549 public boolean doesCallerHaveSystemAccess(@NonNull String callerPackageName) { 8550 return false; 8551 } 8552 }; 8553 mAppSearchImpl.close(); 8554 mAppSearchImpl = 8555 AppSearchImpl.create( 8556 mAppSearchDir, 8557 new AppSearchConfigImpl( 8558 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 8559 /* initStatsBuilder= */ null, 8560 visibilityChecker, 8561 /* revocableFileDescriptorStore= */ null, 8562 ALWAYS_OPTIMIZE); 8563 8564 // Register an observer 8565 TestObserverCallback observer = new TestObserverCallback(); 8566 mAppSearchImpl.registerObserverCallback( 8567 new CallerAccess(/* callingPackageName= */ fakeListeningPackage), 8568 /* targetPackageName= */ mContext.getPackageName(), 8569 new ObserverSpec.Builder().build(), 8570 MoreExecutors.directExecutor(), 8571 observer); 8572 8573 // Add a schema where both types are visible to the fake package. 8574 List<AppSearchSchema> schemas = 8575 ImmutableList.of( 8576 new AppSearchSchema.Builder("Type1").build(), 8577 new AppSearchSchema.Builder("Type2").build()); 8578 InternalSetSchemaResponse internalSetSchemaResponse = 8579 mAppSearchImpl.setSchema( 8580 mContext.getPackageName(), 8581 "database1", 8582 schemas, 8583 /* visibilityConfigs= */ ImmutableList.of( 8584 new InternalVisibilityConfig.Builder("Type1") 8585 .addVisibleToPackage( 8586 new PackageIdentifier( 8587 fakeListeningPackage, new byte[0])) 8588 .build(), 8589 new InternalVisibilityConfig.Builder("Type2") 8590 .addVisibleToPackage( 8591 new PackageIdentifier( 8592 fakeListeningPackage, new byte[0])) 8593 .build()), 8594 /* forceOverride= */ false, 8595 /* version= */ 0, 8596 /* setSchemaStatsBuilder= */ null); 8597 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8598 8599 // Notifications of addition should now be dispatched 8600 assertThat(observer.getSchemaChanges()).isEmpty(); 8601 assertThat(observer.getDocumentChanges()).isEmpty(); 8602 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8603 assertThat(observer.getSchemaChanges()) 8604 .containsExactly( 8605 new SchemaChangeInfo( 8606 mContext.getPackageName(), 8607 "database1", 8608 ImmutableSet.of("Type1", "Type2"))); 8609 assertThat(observer.getDocumentChanges()).isEmpty(); 8610 observer.clear(); 8611 8612 // Update schema, keeping the types identical but denying visibility to type2 8613 internalSetSchemaResponse = 8614 mAppSearchImpl.setSchema( 8615 mContext.getPackageName(), 8616 "database1", 8617 schemas, 8618 /* visibilityConfigs= */ ImmutableList.of( 8619 new InternalVisibilityConfig.Builder("Type1") 8620 .addVisibleToPackage( 8621 new PackageIdentifier( 8622 fakeListeningPackage, new byte[0])) 8623 .build(), 8624 new InternalVisibilityConfig.Builder("Type2").build()), 8625 /* forceOverride= */ false, 8626 /* version= */ 0, 8627 /* setSchemaStatsBuilder= */ null); 8628 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8629 8630 // Dispatch notifications. This should look like a deletion of Type2. 8631 assertThat(observer.getSchemaChanges()).isEmpty(); 8632 assertThat(observer.getDocumentChanges()).isEmpty(); 8633 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8634 assertThat(observer.getSchemaChanges()) 8635 .containsExactly( 8636 new SchemaChangeInfo( 8637 mContext.getPackageName(), "database1", ImmutableSet.of("Type2"))); 8638 assertThat(observer.getDocumentChanges()).isEmpty(); 8639 observer.clear(); 8640 8641 // Now update Type2 and make sure no further notification is received. 8642 internalSetSchemaResponse = 8643 mAppSearchImpl.setSchema( 8644 mContext.getPackageName(), 8645 "database1", 8646 ImmutableList.of( 8647 new AppSearchSchema.Builder("Type1").build(), 8648 new AppSearchSchema.Builder("Type2") 8649 .addProperty( 8650 new AppSearchSchema.BooleanPropertyConfig.Builder( 8651 "booleanProp") 8652 .setCardinality( 8653 AppSearchSchema.PropertyConfig 8654 .CARDINALITY_OPTIONAL) 8655 .build()) 8656 .build()), 8657 /* visibilityConfigs= */ ImmutableList.of( 8658 new InternalVisibilityConfig.Builder("Type1") 8659 .addVisibleToPackage( 8660 new PackageIdentifier( 8661 fakeListeningPackage, new byte[0])) 8662 .build(), 8663 new InternalVisibilityConfig.Builder("Type2").build()), 8664 /* forceOverride= */ false, 8665 /* version= */ 0, 8666 /* setSchemaStatsBuilder= */ null); 8667 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8668 8669 assertThat(observer.getSchemaChanges()).isEmpty(); 8670 assertThat(observer.getDocumentChanges()).isEmpty(); 8671 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8672 assertThat(observer.getSchemaChanges()).isEmpty(); 8673 assertThat(observer.getDocumentChanges()).isEmpty(); 8674 8675 // Grant visibility to Type2 again and make sure it appears 8676 internalSetSchemaResponse = 8677 mAppSearchImpl.setSchema( 8678 mContext.getPackageName(), 8679 "database1", 8680 ImmutableList.of( 8681 new AppSearchSchema.Builder("Type1").build(), 8682 new AppSearchSchema.Builder("Type2") 8683 .addProperty( 8684 new AppSearchSchema.BooleanPropertyConfig.Builder( 8685 "booleanProp") 8686 .setCardinality( 8687 AppSearchSchema.PropertyConfig 8688 .CARDINALITY_OPTIONAL) 8689 .build()) 8690 .build()), 8691 /* visibilityConfigs= */ ImmutableList.of( 8692 new InternalVisibilityConfig.Builder("Type1") 8693 .addVisibleToPackage( 8694 new PackageIdentifier( 8695 fakeListeningPackage, new byte[0])) 8696 .build(), 8697 new InternalVisibilityConfig.Builder("Type2") 8698 .addVisibleToPackage( 8699 new PackageIdentifier( 8700 fakeListeningPackage, new byte[0])) 8701 .build()), 8702 /* forceOverride= */ false, 8703 /* version= */ 0, 8704 /* setSchemaStatsBuilder= */ null); 8705 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8706 8707 // Dispatch notifications. This should look like a creation of Type2. 8708 assertThat(observer.getSchemaChanges()).isEmpty(); 8709 assertThat(observer.getDocumentChanges()).isEmpty(); 8710 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8711 assertThat(observer.getSchemaChanges()) 8712 .containsExactly( 8713 new SchemaChangeInfo( 8714 mContext.getPackageName(), "database1", ImmutableSet.of("Type2"))); 8715 assertThat(observer.getDocumentChanges()).isEmpty(); 8716 } 8717 8718 @Test testAddObserver_schemaChange_visibilityAndContents()8719 public void testAddObserver_schemaChange_visibilityAndContents() throws Exception { 8720 final String fakeListeningPackage = "com.fake.listening.package"; 8721 8722 // Make a visibility checker that allows fakeListeningPackage access only to Type2. 8723 final VisibilityChecker visibilityChecker = 8724 new VisibilityChecker() { 8725 @Override 8726 public boolean isSchemaSearchableByCaller( 8727 @NonNull CallerAccess callerAccess, 8728 @NonNull String packageName, 8729 @NonNull String prefixedSchema, 8730 @NonNull VisibilityStore visibilityStore) { 8731 return callerAccess.getCallingPackageName().equals(fakeListeningPackage) 8732 && prefixedSchema.endsWith("Type2"); 8733 } 8734 8735 @Override 8736 public boolean doesCallerHaveSystemAccess(@NonNull String callerPackageName) { 8737 return false; 8738 } 8739 }; 8740 mAppSearchImpl.close(); 8741 mAppSearchImpl = 8742 AppSearchImpl.create( 8743 mAppSearchDir, 8744 new AppSearchConfigImpl( 8745 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 8746 /* initStatsBuilder= */ null, 8747 visibilityChecker, 8748 /* revocableFileDescriptorStore= */ null, 8749 ALWAYS_OPTIMIZE); 8750 8751 // Add a schema. 8752 InternalSetSchemaResponse internalSetSchemaResponse = 8753 mAppSearchImpl.setSchema( 8754 mContext.getPackageName(), 8755 "database1", 8756 ImmutableList.of( 8757 new AppSearchSchema.Builder("Type1") 8758 .addProperty( 8759 new AppSearchSchema.BooleanPropertyConfig.Builder( 8760 "booleanProp") 8761 .setCardinality( 8762 AppSearchSchema.PropertyConfig 8763 .CARDINALITY_REQUIRED) 8764 .build()) 8765 .build(), 8766 new AppSearchSchema.Builder("Type2") 8767 .addProperty( 8768 new AppSearchSchema.BooleanPropertyConfig.Builder( 8769 "booleanProp") 8770 .setCardinality( 8771 AppSearchSchema.PropertyConfig 8772 .CARDINALITY_REQUIRED) 8773 .build()) 8774 .build()), 8775 /* visibilityConfigs= */ Collections.emptyList(), 8776 /* forceOverride= */ false, 8777 /* version= */ 0, 8778 /* setSchemaStatsBuilder= */ null); 8779 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8780 8781 // Register an observer 8782 TestObserverCallback observer = new TestObserverCallback(); 8783 mAppSearchImpl.registerObserverCallback( 8784 new CallerAccess(/* callingPackageName= */ fakeListeningPackage), 8785 /* targetPackageName= */ mContext.getPackageName(), 8786 new ObserverSpec.Builder().build(), 8787 MoreExecutors.directExecutor(), 8788 observer); 8789 8790 // Update both types of the schema (changed cardinalities) 8791 internalSetSchemaResponse = 8792 mAppSearchImpl.setSchema( 8793 mContext.getPackageName(), 8794 "database1", 8795 ImmutableList.of( 8796 new AppSearchSchema.Builder("Type1") 8797 .addProperty( 8798 new AppSearchSchema.BooleanPropertyConfig.Builder( 8799 "booleanProp") 8800 .setCardinality( 8801 AppSearchSchema.PropertyConfig 8802 .CARDINALITY_OPTIONAL) 8803 .build()) 8804 .build(), 8805 new AppSearchSchema.Builder("Type2") 8806 .addProperty( 8807 new AppSearchSchema.BooleanPropertyConfig.Builder( 8808 "booleanProp") 8809 .setCardinality( 8810 AppSearchSchema.PropertyConfig 8811 .CARDINALITY_OPTIONAL) 8812 .build()) 8813 .build()), 8814 /* visibilityConfigs= */ Collections.emptyList(), 8815 /* forceOverride= */ false, 8816 /* version= */ 0, 8817 /* setSchemaStatsBuilder= */ null); 8818 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8819 8820 // Dispatch notifications 8821 assertThat(observer.getSchemaChanges()).isEmpty(); 8822 assertThat(observer.getDocumentChanges()).isEmpty(); 8823 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8824 assertThat(observer.getSchemaChanges()) 8825 .containsExactly( 8826 new SchemaChangeInfo( 8827 mContext.getPackageName(), "database1", ImmutableSet.of("Type2"))); 8828 assertThat(observer.getDocumentChanges()).isEmpty(); 8829 } 8830 8831 @Test testAddObserver_schemaChange_partialVisibility_removed()8832 public void testAddObserver_schemaChange_partialVisibility_removed() throws Exception { 8833 final String fakeListeningPackage = "com.fake.listening.package"; 8834 8835 // Make a visibility checker that allows fakeListeningPackage access only to Type2. 8836 final VisibilityChecker visibilityChecker = 8837 new VisibilityChecker() { 8838 @Override 8839 public boolean isSchemaSearchableByCaller( 8840 @NonNull CallerAccess callerAccess, 8841 @NonNull String packageName, 8842 @NonNull String prefixedSchema, 8843 @NonNull VisibilityStore visibilityStore) { 8844 return callerAccess.getCallingPackageName().equals(fakeListeningPackage) 8845 && prefixedSchema.endsWith("Type2"); 8846 } 8847 8848 @Override 8849 public boolean doesCallerHaveSystemAccess(@NonNull String callerPackageName) { 8850 return false; 8851 } 8852 }; 8853 mAppSearchImpl.close(); 8854 mAppSearchImpl = 8855 AppSearchImpl.create( 8856 mAppSearchDir, 8857 new AppSearchConfigImpl( 8858 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 8859 /* initStatsBuilder= */ null, 8860 visibilityChecker, 8861 /* revocableFileDescriptorStore= */ null, 8862 ALWAYS_OPTIMIZE); 8863 8864 // Add a schema. 8865 InternalSetSchemaResponse internalSetSchemaResponse = 8866 mAppSearchImpl.setSchema( 8867 mContext.getPackageName(), 8868 "database1", 8869 ImmutableList.of( 8870 new AppSearchSchema.Builder("Type1").build(), 8871 new AppSearchSchema.Builder("Type2").build()), 8872 /* visibilityConfigs= */ Collections.emptyList(), 8873 /* forceOverride= */ false, 8874 /* version= */ 0, 8875 /* setSchemaStatsBuilder= */ null); 8876 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8877 8878 // Register an observer 8879 TestObserverCallback observer = new TestObserverCallback(); 8880 mAppSearchImpl.registerObserverCallback( 8881 new CallerAccess(/* callingPackageName= */ fakeListeningPackage), 8882 /* targetPackageName= */ mContext.getPackageName(), 8883 new ObserverSpec.Builder().build(), 8884 MoreExecutors.directExecutor(), 8885 observer); 8886 8887 // Remove Type1 8888 internalSetSchemaResponse = 8889 mAppSearchImpl.setSchema( 8890 mContext.getPackageName(), 8891 "database1", 8892 ImmutableList.of(new AppSearchSchema.Builder("Type2").build()), 8893 /* visibilityConfigs= */ Collections.emptyList(), 8894 /* forceOverride= */ true, 8895 /* version= */ 0, 8896 /* setSchemaStatsBuilder= */ null); 8897 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8898 8899 // Dispatch notifications. Nothing should appear since Type1 is not visible to us. 8900 assertThat(observer.getSchemaChanges()).isEmpty(); 8901 assertThat(observer.getDocumentChanges()).isEmpty(); 8902 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8903 assertThat(observer.getSchemaChanges()).isEmpty(); 8904 assertThat(observer.getDocumentChanges()).isEmpty(); 8905 8906 // Now remove Type2. This should cause a notification. 8907 internalSetSchemaResponse = 8908 mAppSearchImpl.setSchema( 8909 mContext.getPackageName(), 8910 "database1", 8911 ImmutableList.of(), 8912 /* visibilityConfigs= */ Collections.emptyList(), 8913 /* forceOverride= */ true, 8914 /* version= */ 0, 8915 /* setSchemaStatsBuilder= */ null); 8916 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8917 assertThat(observer.getSchemaChanges()).isEmpty(); 8918 assertThat(observer.getDocumentChanges()).isEmpty(); 8919 mAppSearchImpl.dispatchAndClearChangeNotifications(); 8920 assertThat(observer.getSchemaChanges()) 8921 .containsExactly( 8922 new SchemaChangeInfo( 8923 mContext.getPackageName(), "database1", ImmutableSet.of("Type2"))); 8924 assertThat(observer.getDocumentChanges()).isEmpty(); 8925 } 8926 8927 @Test testAddObserver_schemaChange_multipleObservers()8928 public void testAddObserver_schemaChange_multipleObservers() throws Exception { 8929 // Create two fake packages. One can access Type1, one can access Type2, they both can 8930 // access Type3, and no one can access Type4. 8931 final String fakePackage1 = "com.fake.listening.package1"; 8932 8933 final String fakePackage2 = "com.fake.listening.package2"; 8934 8935 final VisibilityChecker visibilityChecker = 8936 new VisibilityChecker() { 8937 @Override 8938 public boolean isSchemaSearchableByCaller( 8939 @NonNull CallerAccess callerAccess, 8940 @NonNull String packageName, 8941 @NonNull String prefixedSchema, 8942 @NonNull VisibilityStore visibilityStore) { 8943 if (prefixedSchema.endsWith("Type1")) { 8944 return callerAccess.getCallingPackageName().equals(fakePackage1); 8945 } else if (prefixedSchema.endsWith("Type2")) { 8946 return callerAccess.getCallingPackageName().equals(fakePackage2); 8947 } else if (prefixedSchema.endsWith("Type3")) { 8948 return false; 8949 } else if (prefixedSchema.endsWith("Type4")) { 8950 return true; 8951 } else { 8952 throw new IllegalArgumentException(prefixedSchema); 8953 } 8954 } 8955 8956 @Override 8957 public boolean doesCallerHaveSystemAccess(@NonNull String callerPackageName) { 8958 return false; 8959 } 8960 }; 8961 mAppSearchImpl.close(); 8962 mAppSearchImpl = 8963 AppSearchImpl.create( 8964 mAppSearchDir, 8965 new AppSearchConfigImpl( 8966 new UnlimitedLimitConfig(), new LocalStorageIcingOptionsConfig()), 8967 /* initStatsBuilder= */ null, 8968 visibilityChecker, 8969 /* revocableFileDescriptorStore= */ null, 8970 ALWAYS_OPTIMIZE); 8971 8972 // Add a schema. 8973 InternalSetSchemaResponse internalSetSchemaResponse = 8974 mAppSearchImpl.setSchema( 8975 mContext.getPackageName(), 8976 "database1", 8977 ImmutableList.of( 8978 new AppSearchSchema.Builder("Type1").build(), 8979 new AppSearchSchema.Builder("Type2").build(), 8980 new AppSearchSchema.Builder("Type3").build(), 8981 new AppSearchSchema.Builder("Type4").build()), 8982 /* visibilityConfigs= */ Collections.emptyList(), 8983 /* forceOverride= */ false, 8984 /* version= */ 0, 8985 /* setSchemaStatsBuilder= */ null); 8986 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 8987 8988 // Register three observers: one in each package, and another in package1 with a filter. 8989 TestObserverCallback observerPkg1NoFilter = new TestObserverCallback(); 8990 mAppSearchImpl.registerObserverCallback( 8991 new CallerAccess(/* callingPackageName= */ fakePackage1), 8992 /* targetPackageName= */ mContext.getPackageName(), 8993 new ObserverSpec.Builder().build(), 8994 MoreExecutors.directExecutor(), 8995 observerPkg1NoFilter); 8996 8997 TestObserverCallback observerPkg2NoFilter = new TestObserverCallback(); 8998 mAppSearchImpl.registerObserverCallback( 8999 new CallerAccess(/* callingPackageName= */ fakePackage2), 9000 /* targetPackageName= */ mContext.getPackageName(), 9001 new ObserverSpec.Builder().build(), 9002 MoreExecutors.directExecutor(), 9003 observerPkg2NoFilter); 9004 9005 TestObserverCallback observerPkg1FilterType4 = new TestObserverCallback(); 9006 mAppSearchImpl.registerObserverCallback( 9007 new CallerAccess(/* callingPackageName= */ fakePackage1), 9008 /* targetPackageName= */ mContext.getPackageName(), 9009 new ObserverSpec.Builder().addFilterSchemas("Type4").build(), 9010 MoreExecutors.directExecutor(), 9011 observerPkg1FilterType4); 9012 9013 // Remove everything 9014 internalSetSchemaResponse = 9015 mAppSearchImpl.setSchema( 9016 mContext.getPackageName(), 9017 "database1", 9018 ImmutableList.of(), 9019 /* visibilityConfigs= */ Collections.emptyList(), 9020 /* forceOverride= */ true, 9021 /* version= */ 0, 9022 /* setSchemaStatsBuilder= */ null); 9023 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 9024 9025 // Dispatch notifications. 9026 mAppSearchImpl.dispatchAndClearChangeNotifications(); 9027 9028 // observerPkg1NoFilter should see Type1 and Type4 vanish. 9029 // observerPkg2NoFilter should see Type2 and Type4 vanish. 9030 // observerPkg2WithFilter should see Type4 vanish. 9031 assertThat(observerPkg1NoFilter.getSchemaChanges()) 9032 .containsExactly( 9033 new SchemaChangeInfo( 9034 mContext.getPackageName(), 9035 "database1", 9036 ImmutableSet.of("Type1", "Type4"))); 9037 assertThat(observerPkg1NoFilter.getDocumentChanges()).isEmpty(); 9038 9039 assertThat(observerPkg2NoFilter.getSchemaChanges()) 9040 .containsExactly( 9041 new SchemaChangeInfo( 9042 mContext.getPackageName(), 9043 "database1", 9044 ImmutableSet.of("Type2", "Type4"))); 9045 assertThat(observerPkg2NoFilter.getDocumentChanges()).isEmpty(); 9046 9047 assertThat(observerPkg1FilterType4.getSchemaChanges()) 9048 .containsExactly( 9049 new SchemaChangeInfo( 9050 mContext.getPackageName(), "database1", ImmutableSet.of("Type4"))); 9051 assertThat(observerPkg1FilterType4.getDocumentChanges()).isEmpty(); 9052 } 9053 9054 @Test testAddObserver_schemaChange_noChangeIfIncompatible()9055 public void testAddObserver_schemaChange_noChangeIfIncompatible() throws Exception { 9056 // Add a schema with two types. 9057 InternalSetSchemaResponse internalSetSchemaResponse = 9058 mAppSearchImpl.setSchema( 9059 mContext.getPackageName(), 9060 "database1", 9061 ImmutableList.of( 9062 new AppSearchSchema.Builder("Type1") 9063 .addProperty( 9064 new AppSearchSchema.StringPropertyConfig.Builder( 9065 "strProp") 9066 .setCardinality( 9067 AppSearchSchema.PropertyConfig 9068 .CARDINALITY_OPTIONAL) 9069 .build()) 9070 .build(), 9071 new AppSearchSchema.Builder("Type2") 9072 .addProperty( 9073 new AppSearchSchema.StringPropertyConfig.Builder( 9074 "strProp") 9075 .setCardinality( 9076 AppSearchSchema.PropertyConfig 9077 .CARDINALITY_OPTIONAL) 9078 .build()) 9079 .build()), 9080 /* visibilityConfigs= */ Collections.emptyList(), 9081 /* forceOverride= */ false, 9082 /* version= */ 1, 9083 /* setSchemaStatsBuilder= */ null); 9084 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 9085 9086 // Register an observer 9087 TestObserverCallback observer = new TestObserverCallback(); 9088 mAppSearchImpl.registerObserverCallback( 9089 new CallerAccess(/* callingPackageName= */ mContext.getPackageName()), 9090 /* targetPackageName= */ mContext.getPackageName(), 9091 new ObserverSpec.Builder().build(), 9092 MoreExecutors.directExecutor(), 9093 observer); 9094 9095 // Update schema to try to make an incompatible change to Type1, and a compatible change to 9096 // Type2. 9097 List<AppSearchSchema> updatedSchemaTypes = 9098 ImmutableList.of( 9099 new AppSearchSchema.Builder("Type1") 9100 .addProperty( 9101 new AppSearchSchema.StringPropertyConfig.Builder("strProp") 9102 .setCardinality( 9103 AppSearchSchema.PropertyConfig 9104 .CARDINALITY_REQUIRED) 9105 .build()) 9106 .build(), 9107 new AppSearchSchema.Builder("Type2") 9108 .addProperty( 9109 new AppSearchSchema.StringPropertyConfig.Builder("strProp") 9110 .setCardinality( 9111 AppSearchSchema.PropertyConfig 9112 .CARDINALITY_REPEATED) 9113 .build()) 9114 .build()); 9115 internalSetSchemaResponse = 9116 mAppSearchImpl.setSchema( 9117 mContext.getPackageName(), 9118 "database1", 9119 updatedSchemaTypes, 9120 /* visibilityConfigs= */ Collections.emptyList(), 9121 /* forceOverride= */ false, 9122 /* version= */ 2, 9123 /* setSchemaStatsBuilder= */ null); 9124 assertThat(internalSetSchemaResponse.isSuccess()).isFalse(); 9125 SetSchemaResponse setSchemaResponse = internalSetSchemaResponse.getSetSchemaResponse(); 9126 assertThat(setSchemaResponse.getDeletedTypes()).isEmpty(); 9127 assertThat(setSchemaResponse.getIncompatibleTypes()).containsExactly("Type1"); 9128 9129 // Dispatch notifications. Nothing should appear since the schema was incompatible and has 9130 // not changed. 9131 assertThat(observer.getSchemaChanges()).isEmpty(); 9132 assertThat(observer.getDocumentChanges()).isEmpty(); 9133 mAppSearchImpl.dispatchAndClearChangeNotifications(); 9134 assertThat(observer.getSchemaChanges()).isEmpty(); 9135 assertThat(observer.getDocumentChanges()).isEmpty(); 9136 9137 // Now force apply the schemas Type2. This should cause a notification. 9138 internalSetSchemaResponse = 9139 mAppSearchImpl.setSchema( 9140 mContext.getPackageName(), 9141 "database1", 9142 updatedSchemaTypes, 9143 /* visibilityConfigs= */ Collections.emptyList(), 9144 /* forceOverride= */ true, 9145 /* version= */ 3, 9146 /* setSchemaStatsBuilder= */ null); 9147 assertThat(internalSetSchemaResponse.isSuccess()).isTrue(); 9148 assertThat(observer.getSchemaChanges()).isEmpty(); 9149 assertThat(observer.getDocumentChanges()).isEmpty(); 9150 mAppSearchImpl.dispatchAndClearChangeNotifications(); 9151 assertThat(observer.getSchemaChanges()) 9152 .containsExactly( 9153 new SchemaChangeInfo( 9154 mContext.getPackageName(), 9155 "database1", 9156 ImmutableSet.of("Type1", "Type2"))); 9157 assertThat(observer.getDocumentChanges()).isEmpty(); 9158 } 9159 } 9160