1 /* 2 * Copyright (C) 2021 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 package android.app.appsearch.cts.app; 17 18 import static android.Manifest.permission.READ_CALENDAR; 19 import static android.Manifest.permission.READ_CONTACTS; 20 import static android.Manifest.permission.READ_EXTERNAL_STORAGE; 21 import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA; 22 import static android.Manifest.permission.READ_SMS; 23 import static android.app.appsearch.testutil.AppSearchTestUtils.calculateDigest; 24 import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess; 25 import static android.app.appsearch.testutil.AppSearchTestUtils.generateRandomBytes; 26 27 import static com.google.common.truth.Truth.assertThat; 28 29 import static org.junit.Assert.assertTrue; 30 import static org.junit.Assert.assertFalse; 31 32 import android.annotation.NonNull; 33 import android.app.appsearch.AppSearchBatchResult; 34 import android.app.appsearch.AppSearchBlobHandle; 35 import android.app.appsearch.AppSearchResult; 36 import android.app.appsearch.AppSearchSchema; 37 import android.app.appsearch.AppSearchSchema.PropertyConfig; 38 import android.app.appsearch.AppSearchSchema.StringPropertyConfig; 39 import android.app.appsearch.AppSearchSessionShim; 40 import android.app.appsearch.GenericDocument; 41 import android.app.appsearch.GetByDocumentIdRequest; 42 import android.app.appsearch.GetSchemaResponse; 43 import android.app.appsearch.GlobalSearchSessionShim; 44 import android.app.appsearch.JoinSpec; 45 import android.app.appsearch.OpenBlobForReadResponse; 46 import android.app.appsearch.PackageIdentifier; 47 import android.app.appsearch.PutDocumentsRequest; 48 import android.app.appsearch.ReportSystemUsageRequest; 49 import android.app.appsearch.ReportUsageRequest; 50 import android.app.appsearch.SearchResult; 51 import android.app.appsearch.SearchResultsShim; 52 import android.app.appsearch.SearchSpec; 53 import android.app.appsearch.SetSchemaRequest; 54 import android.app.appsearch.observer.DocumentChangeInfo; 55 import android.app.appsearch.observer.ObserverSpec; 56 import android.app.appsearch.testutil.AppSearchEmail; 57 import android.app.appsearch.testutil.AppSearchTestUtils; 58 import android.app.appsearch.testutil.PackageUtil; 59 import android.app.appsearch.testutil.SystemUtil; 60 import android.app.appsearch.testutil.TestObserverCallback; 61 import android.app.appsearch.util.DocumentIdUtil; 62 import android.content.ComponentName; 63 import android.content.Context; 64 import android.content.Intent; 65 import android.content.ServiceConnection; 66 import android.os.Bundle; 67 import android.os.IBinder; 68 import android.os.ParcelFileDescriptor; 69 import android.platform.test.annotations.RequiresFlagsEnabled; 70 import android.util.Log; 71 72 import androidx.test.core.app.ApplicationProvider; 73 import androidx.test.filters.SdkSuppress; 74 75 import com.android.appsearch.flags.Flags; 76 import com.android.cts.appsearch.ICommandReceiver; 77 78 import com.google.common.collect.ImmutableList; 79 import com.google.common.collect.ImmutableSet; 80 import com.google.common.io.BaseEncoding; 81 import com.google.common.util.concurrent.MoreExecutors; 82 83 import org.junit.After; 84 import org.junit.Before; 85 import org.junit.Rule; 86 import org.junit.Test; 87 import org.junit.rules.RuleChain; 88 import org.junit.runner.RunWith; 89 import org.junit.runners.JUnit4; 90 91 import java.io.InputStream; 92 import java.util.ArrayList; 93 import java.util.Collections; 94 import java.util.List; 95 import java.util.Set; 96 import java.util.concurrent.BlockingQueue; 97 import java.util.concurrent.Executor; 98 import java.util.concurrent.LinkedBlockingQueue; 99 import java.util.concurrent.TimeUnit; 100 import java.util.concurrent.atomic.AtomicReference; 101 import java.util.stream.Collectors; 102 103 /** 104 * This class can be extended by tests in platforms like Android Framework and GMSCore which host an 105 * AppSearch service but can't be run in non-platform environments like JetPack. 106 */ 107 @RunWith(JUnit4.class) 108 public abstract class GlobalSearchSessionServiceCtsTestBase { 109 110 @Rule public final RuleChain mRuleChain = AppSearchTestUtils.createCommonTestRules(); 111 112 private static final long TIMEOUT_BIND_SERVICE_SEC = 10; 113 114 private static final String TAG = "GlobalSearchSessionServiceCtsTestBase"; 115 116 private static final String PKG_A = "com.android.cts.appsearch.helper.a"; 117 118 // To generate, run `apksigner` on the build APK. e.g. 119 // ./apksigner verify --print-certs \ 120 // ~/sc-dev/out/soong/.intermediates/cts/tests/appsearch/CtsAppSearchTestHelperA/\ 121 // android_common/CtsAppSearchTestHelperA.apk` 122 // to get the SHA-256 digest. All characters need to be uppercase. 123 // 124 // Note: May need to switch the "sdk_version" of the test app from "test_current" to "30" before 125 // building the apk and running apksigner 126 private static final byte[] PKG_A_CERT_SHA256 = 127 BaseEncoding.base16() 128 .decode("A90B80BD307B71BB4029674C5C4FE18066994E352EAC933B7B68266210CAFB53"); 129 130 private static final String PKG_B = "com.android.cts.appsearch.helper.b"; 131 132 // To generate, run `apksigner` on the build APK. e.g. 133 // ./apksigner verify --print-certs \ 134 // ~/sc-dev/out/soong/.intermediates/cts/tests/appsearch/CtsAppSearchTestHelperB/\ 135 // android_common/CtsAppSearchTestHelperB.apk` 136 // to get the SHA-256 digest. All characters need to be uppercase. 137 // 138 // Note: May need to switch the "sdk_version" of the test app from "test_current" to "30" before 139 // building the apk and running apksigner 140 private static final byte[] PKG_B_CERT_SHA256 = 141 BaseEncoding.base16() 142 .decode("88C0B41A31943D13226C3F22A86A6B4F300315575A6BC533CBF16C4EF3CFAA37"); 143 144 private static final String HELPER_SERVICE = 145 "com.android.cts.appsearch.helper.AppSearchTestService"; 146 147 private static final String TEXT = "foo"; 148 149 private static final String NAMESPACE_NAME = "namespace"; 150 151 private static final AppSearchEmail EMAIL_DOCUMENT = 152 new AppSearchEmail.Builder(NAMESPACE_NAME, "id1") 153 .setFrom("[email protected]") 154 .setTo("[email protected]", "[email protected]") 155 .setSubject(TEXT) 156 .setBody("this is the body of the email") 157 .build(); 158 159 private static final String DB_NAME = "database"; 160 161 162 private GlobalSearchSessionShim mGlobalSearchSession; 163 private AppSearchSessionShim mDb; 164 165 private Context mContext; 166 167 @Before setUp()168 public void setUp() throws Exception { 169 mContext = ApplicationProvider.getApplicationContext(); 170 mDb = createSearchSessionAsync(DB_NAME); 171 cleanup(); 172 } 173 174 @After tearDown()175 public void tearDown() throws Exception { 176 // Cleanup whatever documents may still exist in these databases. 177 cleanup(); 178 } 179 createSearchSessionAsync( @onNull String dbName)180 protected abstract AppSearchSessionShim createSearchSessionAsync( 181 @NonNull String dbName) throws Exception; 182 createGlobalSearchSessionAsync( @onNull Context context)183 protected abstract GlobalSearchSessionShim createGlobalSearchSessionAsync( 184 @NonNull Context context) throws Exception; 185 cleanup()186 private void cleanup() throws Exception { 187 mDb.setSchemaAsync(new SetSchemaRequest.Builder().setForceOverride(true).build()).get(); 188 189 clearData(PKG_A, DB_NAME); 190 clearData(PKG_B, DB_NAME); 191 } 192 193 // We could add a third package, PKG_C, that the cts package cannot query. However, this does 194 // not allow us to bind to PKG_C, meaning we can't index documents in PKG_C, making it useless. 195 // Instead, we will take advantage of the fact that the cts package can query both PKG_A and 196 // PKG_B, while PKG_A and PKG_B cannot query each other. We'll focus on the fact that PKG_A 197 // cannot query PKG_B, but can query the cts package. 198 // 199 // So we'll index two schemas, one with a publiclyVisibleTargetPackage of the cts package, and 200 // another with a publiclyVisibleTargetPackage of PKG_B. We can't index these in PKG_A, as 201 // packages always have visibility to schemas they hold. We'll index them both in PKG_B, 202 // ensuring that the "cts schema" is still visible, as well as the cts package (this one), 203 // ensuring that the "PKG_B schema" is not visible. 204 // 205 // In summary: 206 // Doc in accessible package, accessible publiclyVisibleTargetPackage: searchable 207 // Doc in inaccessible package, accessible publiclyVisibleTargetPackage: searchable 208 // Doc in accessible package, inaccessible publiclyVisibleTargetPackage: not searchable 209 // Doc in inaccessible package, inaccessible publiclyVisibleTargetPackage: not searchable 210 @SdkSuppress(minSdkVersion = 34) 211 // TODO(b/275592563): Figure out why canPackageQuery works different on T 212 @Test testPublicVisibility_accessibleTestPackage()213 public void testPublicVisibility_accessibleTestPackage() throws Exception { 214 // Ensure manifest files are set up correctly. 215 String ctsPackageName = mContext.getPackageName(); 216 String ctsSchemaName = ctsPackageName + "Schema"; 217 218 assertThat(mContext.getPackageManager().canPackageQuery(PKG_A, ctsPackageName)).isTrue(); 219 assertThat(mContext.getPackageManager().canPackageQuery(PKG_A, PKG_B)).isFalse(); 220 221 AppSearchSchema ctsSchema = new AppSearchSchema.Builder(ctsSchemaName) 222 .addProperty(new StringPropertyConfig.Builder("searchable") 223 .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) 224 .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) 225 .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES).build()) 226 .build(); 227 // pkg b schema 228 AppSearchSchema bSchema = new AppSearchSchema.Builder("BSchema") 229 .addProperty(new StringPropertyConfig.Builder("searchable") 230 .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) 231 .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) 232 .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES).build()) 233 .build(); 234 235 // Index schemas in the cts package 236 mDb.setSchemaAsync(new SetSchemaRequest.Builder() 237 .addSchemas(ctsSchema, bSchema) 238 .setPubliclyVisibleSchema(ctsSchema.getSchemaType(), 239 new PackageIdentifier(ctsPackageName, 240 PackageUtil.getSelfPackageSha256Cert(mContext))) 241 .setPubliclyVisibleSchema(bSchema.getSchemaType(), 242 new PackageIdentifier(PKG_B, PKG_B_CERT_SHA256)) 243 .build()).get(); 244 GenericDocument ctsDoc = 245 new GenericDocument.Builder<>(NAMESPACE_NAME, "id1", ctsSchemaName) 246 .setPropertyString("searchable", "pineapple from com.android.cts.appsearch") 247 .build(); 248 GenericDocument bDoc = 249 new GenericDocument.Builder<>(NAMESPACE_NAME, "id2", "BSchema") 250 .setPropertyString("searchable", 251 "pineapple from com.android.cts.appserach.helper.b") 252 .build(); 253 checkIsBatchResultSuccess(mDb.putAsync(new PutDocumentsRequest.Builder() 254 .addGenericDocuments(ctsDoc, bDoc).build())); 255 256 // Assert that this package can search for 2 257 SearchSpec searchSpec = new SearchSpec.Builder() 258 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 259 .build(); 260 261 SearchResultsShim results = mDb.search("pineapple", searchSpec); 262 List<SearchResult> page = results.getNextPageAsync().get(); 263 assertThat(page).hasSize(2); 264 265 // Assert PKG_A can see cts 266 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 267 bindToHelperService(PKG_A); 268 try { 269 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 270 List<String> packageBResults = commandReceiver.globalSearch("pineapple"); 271 // Only the cts doc 272 assertThat(packageBResults).hasSize(1); 273 assertThat(packageBResults).containsExactly(ctsDoc.toString()); 274 } finally { 275 serviceConnection.unbind(); 276 } 277 } 278 279 @SdkSuppress(minSdkVersion = 34) 280 // TODO(b/275592563): Figure out why canPackageQuery works different on T 281 @Test testPublicVisibility_inaccessibleHelperApp()282 public void testPublicVisibility_inaccessibleHelperApp() throws Exception { 283 String ctsPackageName = mContext.getPackageName(); 284 String ctsSchemaName = ctsPackageName + "Schema"; 285 GenericDocument ctsDoc = 286 new GenericDocument.Builder<>(NAMESPACE_NAME, "id1", ctsSchemaName) 287 .setPropertyString("searchable", "pineapple from " + ctsPackageName) 288 .build(); 289 290 assertThat(mContext.getPackageManager().canPackageQuery(PKG_A, ctsPackageName)).isTrue(); 291 assertThat(mContext.getPackageManager().canPackageQuery(PKG_A, PKG_B)).isFalse(); 292 293 // Index schemas in the B package 294 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 295 bindToHelperService(PKG_B); 296 try { 297 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 298 boolean documentVisibilitySetup = 299 commandReceiver.setUpPubliclyVisibleDocuments( 300 ctsPackageName, PackageUtil.getSelfPackageSha256Cert(mContext), PKG_B, 301 PKG_B_CERT_SHA256); 302 assertThat(documentVisibilitySetup).isTrue(); 303 304 // Assert that B sees two documents 305 List<String> results = commandReceiver.globalSearch("pineapple"); 306 assertThat(results).hasSize(2); 307 } finally { 308 serviceConnection.unbind(); 309 } 310 311 // Assert that A just sees the cts document 312 serviceConnection = bindToHelperService(PKG_A); 313 try { 314 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 315 List<String> results = commandReceiver.globalSearch("pineapple"); 316 // Only the cts doc 317 assertThat(results).hasSize(1); 318 String resultWithoutTimestamp = 319 results.get(0).replaceAll("creationTimestampMillis: [0-9]+", 320 "creationTimestampMillis: " + ctsDoc.getCreationTimestampMillis()); 321 322 assertThat(resultWithoutTimestamp).isEqualTo(ctsDoc.toString()); 323 324 } finally { 325 serviceConnection.unbind(); 326 } 327 } 328 329 @SdkSuppress(minSdkVersion = 34) 330 // TODO(b/275592563): Figure out why canPackageQuery works different on T 331 @Test testPublicVisibility_invalidCertificate()332 public void testPublicVisibility_invalidCertificate() throws Exception { 333 // Ensure manifest files are set up correctly. 334 String ctsPackageName = mContext.getPackageName(); 335 String ctsSchemaName = ctsPackageName + "Schema"; 336 337 // This test will index a publicly accessible document in both PKG_B and the cts package, 338 // but configured with an invalid sha 256 cert. Both should be inaccessible. We can also 339 // check that PKG_A can't access publicly visible documents with the publicly visible target 340 // package set to PKG_A itself if the certificate doesn't match. 341 342 assertThat(mContext.getPackageManager().canPackageQuery(PKG_A, ctsPackageName)).isTrue(); 343 assertThat(mContext.getPackageManager().canPackageQuery(PKG_A, PKG_B)).isFalse(); 344 345 AppSearchSchema ctsSchema = new AppSearchSchema.Builder(ctsSchemaName) 346 .addProperty(new StringPropertyConfig.Builder("searchable") 347 .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) 348 .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) 349 .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES).build()) 350 .build(); 351 // pkg a schema 352 AppSearchSchema aSchema = new AppSearchSchema.Builder("ASchema") 353 .addProperty(new StringPropertyConfig.Builder("searchable") 354 .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) 355 .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) 356 .setIndexingType(StringPropertyConfig.INDEXING_TYPE_PREFIXES).build()) 357 .build(); 358 359 byte[] invalidCert = new byte[32]; 360 invalidCert[16] = 48; 361 362 // Index schemas in the cts package 363 mDb.setSchemaAsync(new SetSchemaRequest.Builder() 364 .addSchemas(ctsSchema, aSchema) 365 .setPubliclyVisibleSchema(ctsSchema.getSchemaType(), 366 new PackageIdentifier(ctsPackageName, invalidCert)) 367 .setPubliclyVisibleSchema(aSchema.getSchemaType(), 368 new PackageIdentifier(PKG_A, invalidCert)) 369 .build()).get(); 370 GenericDocument ctsDoc = 371 new GenericDocument.Builder<>(NAMESPACE_NAME, "id1", ctsSchemaName) 372 .setPropertyString("searchable", "pineapple from com.android.cts.appsearch") 373 .build(); 374 GenericDocument aDoc = 375 new GenericDocument.Builder<>(NAMESPACE_NAME, "id2", "ASchema") 376 .setPropertyString("searchable", 377 "pineapple from com.android.cts.appserach.helper.a") 378 .build(); 379 checkIsBatchResultSuccess(mDb.putAsync(new PutDocumentsRequest.Builder() 380 .addGenericDocuments(ctsDoc, aDoc).build())); 381 382 // Check that this package can search for 2 383 SearchSpec searchSpec = new SearchSpec.Builder() 384 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 385 .build(); 386 SearchResultsShim results = mDb.search("pineapple", searchSpec); 387 List<SearchResult> page = results.getNextPageAsync().get(); 388 assertThat(page).hasSize(2); 389 390 // Assert PKG_A can't see the documents 391 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 392 bindToHelperService(PKG_A); 393 try { 394 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 395 List<String> ctsPackageResults = commandReceiver.globalSearch("pineapple"); 396 assertThat(ctsPackageResults).isEmpty(); 397 } finally { 398 serviceConnection.unbind(); 399 } 400 401 // Index schemas in the B package 402 serviceConnection = bindToHelperService(PKG_B); 403 try { 404 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 405 boolean documentVisibilitySetup = commandReceiver.setUpPubliclyVisibleDocuments( 406 ctsPackageName, invalidCert, PKG_A, invalidCert); 407 assertThat(documentVisibilitySetup).isTrue(); 408 409 // Assert that B sees two documents 410 List<String> bResults = commandReceiver.globalSearch("pineapple"); 411 assertThat(bResults).hasSize(2); 412 } finally { 413 serviceConnection.unbind(); 414 } 415 416 // Assert that A just sees the cts document 417 serviceConnection = bindToHelperService(PKG_A); 418 try { 419 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 420 List<String> aResults = commandReceiver.globalSearch("pineapple"); 421 assertThat(aResults).isEmpty(); 422 } finally { 423 serviceConnection.unbind(); 424 } 425 } 426 427 @Test testNoPackageAccess_default()428 public void testNoPackageAccess_default() throws Exception { 429 mDb.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build()) 430 .get(); 431 checkIsBatchResultSuccess( 432 mDb.putAsync( 433 new PutDocumentsRequest.Builder() 434 .addGenericDocuments(EMAIL_DOCUMENT) 435 .build())); 436 437 // No package has access by default 438 assertPackageCannotAccess(PKG_A); 439 assertPackageCannotAccess(PKG_B); 440 } 441 442 @Test testNoPackageAccess_wrongPackageName()443 public void testNoPackageAccess_wrongPackageName() throws Exception { 444 mDb.setSchemaAsync( 445 new SetSchemaRequest.Builder() 446 .addSchemas(AppSearchEmail.SCHEMA) 447 .setSchemaTypeVisibilityForPackage( 448 AppSearchEmail.SCHEMA_TYPE, 449 /*visible=*/ true, 450 new PackageIdentifier( 451 "some.other.package", PKG_A_CERT_SHA256)) 452 .build()) 453 .get(); 454 checkIsBatchResultSuccess( 455 mDb.putAsync( 456 new PutDocumentsRequest.Builder() 457 .addGenericDocuments(EMAIL_DOCUMENT) 458 .build())); 459 460 assertPackageCannotAccess(PKG_A); 461 } 462 463 @Test testNoPackageAccess_wrongCertificate()464 public void testNoPackageAccess_wrongCertificate() throws Exception { 465 mDb.setSchemaAsync( 466 new SetSchemaRequest.Builder() 467 .addSchemas(AppSearchEmail.SCHEMA) 468 .setSchemaTypeVisibilityForPackage( 469 AppSearchEmail.SCHEMA_TYPE, 470 /*visible=*/ true, 471 new PackageIdentifier(PKG_A, new byte[] {10})) 472 .build()) 473 .get(); 474 checkIsBatchResultSuccess( 475 mDb.putAsync( 476 new PutDocumentsRequest.Builder() 477 .addGenericDocuments(EMAIL_DOCUMENT) 478 .build())); 479 480 assertPackageCannotAccess(PKG_A); 481 } 482 483 @Test testAllowPackageAccess()484 public void testAllowPackageAccess() throws Exception { 485 mDb.setSchemaAsync( 486 new SetSchemaRequest.Builder() 487 .addSchemas(AppSearchEmail.SCHEMA) 488 .setSchemaTypeVisibilityForPackage( 489 AppSearchEmail.SCHEMA_TYPE, 490 /*visible=*/ true, 491 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 492 .build()) 493 .get(); 494 checkIsBatchResultSuccess( 495 mDb.putAsync( 496 new PutDocumentsRequest.Builder() 497 .addGenericDocuments(EMAIL_DOCUMENT) 498 .build())); 499 500 assertPackageCanAccess(EMAIL_DOCUMENT, PKG_A); 501 assertPackageCannotAccess(PKG_B); 502 } 503 504 @SdkSuppress(minSdkVersion = 34) 505 @Test testAllowPackageAccess_polymorphism()506 public void testAllowPackageAccess_polymorphism() throws Exception { 507 // Create two types, Person and Artist, where Artist is a child type of Person. 508 AppSearchSchema personSchema = 509 new AppSearchSchema.Builder("Person") 510 .addProperty( 511 new StringPropertyConfig.Builder("name") 512 .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) 513 .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) 514 .setIndexingType( 515 StringPropertyConfig.INDEXING_TYPE_PREFIXES) 516 .build()) 517 .build(); 518 AppSearchSchema artistSchema = 519 new AppSearchSchema.Builder("Artist") 520 .addParentType("Person") 521 .addProperty( 522 new StringPropertyConfig.Builder("name") 523 .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) 524 .setTokenizerType(StringPropertyConfig.TOKENIZER_TYPE_PLAIN) 525 .setIndexingType( 526 StringPropertyConfig.INDEXING_TYPE_PREFIXES) 527 .build()) 528 .build(); 529 530 // Allow PKG_A to access Person, but not allow to access Artist. 531 mDb.setSchemaAsync( 532 new SetSchemaRequest.Builder() 533 .addSchemas(personSchema) 534 .addSchemas(artistSchema) 535 .setSchemaTypeVisibilityForPackage( 536 "Person", 537 /*visible=*/ true, 538 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 539 .setSchemaTypeVisibilityForPackage( 540 "Artist", 541 /*visible=*/ false, 542 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 543 .build()) 544 .get(); 545 546 // Index a person document and an artist document 547 GenericDocument personDoc = 548 new GenericDocument.Builder<>(NAMESPACE_NAME, "id1", "Person") 549 .setCreationTimestampMillis(1000) 550 .setPropertyString("name", TEXT) 551 .build(); 552 GenericDocument artistDoc = 553 new GenericDocument.Builder<>(NAMESPACE_NAME, "id2", "Artist") 554 .setCreationTimestampMillis(1000) 555 .setPropertyString("name", TEXT) 556 .build(); 557 checkIsBatchResultSuccess( 558 mDb.putAsync( 559 new PutDocumentsRequest.Builder() 560 .addGenericDocuments(personDoc) 561 .addGenericDocuments(artistDoc) 562 .build())); 563 564 // Check that PKG_A can only access personDoc 565 assertPackageCanAccess(List.of(personDoc), PKG_A); 566 assertPackageCannotAccess(PKG_B); 567 } 568 569 @Test testRemoveVisibilitySetting_noRemainingSettings()570 public void testRemoveVisibilitySetting_noRemainingSettings() throws Exception { 571 // Set schema and allow PKG_A to access. 572 mDb.setSchemaAsync( 573 new SetSchemaRequest.Builder() 574 .addSchemas(AppSearchEmail.SCHEMA) 575 .setSchemaTypeVisibilityForPackage( 576 AppSearchEmail.SCHEMA_TYPE, 577 /*visible=*/ true, 578 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 579 .build()) 580 .get(); 581 checkIsBatchResultSuccess( 582 mDb.putAsync( 583 new PutDocumentsRequest.Builder() 584 .addGenericDocuments(EMAIL_DOCUMENT) 585 .build())); 586 587 // PKG_A can access. 588 assertPackageCanAccess(EMAIL_DOCUMENT, PKG_A); 589 assertPackageCannotAccess(PKG_B); 590 591 // Remove the schema. 592 mDb.setSchemaAsync(new SetSchemaRequest.Builder().setForceOverride(true).build()).get(); 593 594 // Add the schema back with default visibility setting. 595 mDb.setSchemaAsync(new SetSchemaRequest.Builder() 596 .addSchemas(AppSearchEmail.SCHEMA).build()).get(); 597 598 // No package can access. 599 assertPackageCannotAccess(PKG_A); 600 assertPackageCannotAccess(PKG_B); 601 } 602 603 @Test testAllowMultiplePackageAccess()604 public void testAllowMultiplePackageAccess() throws Exception { 605 mDb.setSchemaAsync( 606 new SetSchemaRequest.Builder() 607 .addSchemas(AppSearchEmail.SCHEMA) 608 .setSchemaTypeVisibilityForPackage( 609 AppSearchEmail.SCHEMA_TYPE, 610 /*visible=*/ true, 611 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 612 .setSchemaTypeVisibilityForPackage( 613 AppSearchEmail.SCHEMA_TYPE, 614 /*visible=*/ true, 615 new PackageIdentifier(PKG_B, PKG_B_CERT_SHA256)) 616 .build()) 617 .get(); 618 checkIsBatchResultSuccess( 619 mDb.putAsync( 620 new PutDocumentsRequest.Builder() 621 .addGenericDocuments(EMAIL_DOCUMENT) 622 .build())); 623 624 assertPackageCanAccess(EMAIL_DOCUMENT, PKG_A); 625 assertPackageCanAccess(EMAIL_DOCUMENT, PKG_B); 626 } 627 628 @Test testNoPackageAccess_revoked()629 public void testNoPackageAccess_revoked() throws Exception { 630 mDb.setSchemaAsync( 631 new SetSchemaRequest.Builder() 632 .addSchemas(AppSearchEmail.SCHEMA) 633 .setSchemaTypeVisibilityForPackage( 634 AppSearchEmail.SCHEMA_TYPE, 635 /*visible=*/ true, 636 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 637 .build()) 638 .get(); 639 checkIsBatchResultSuccess( 640 mDb.putAsync( 641 new PutDocumentsRequest.Builder() 642 .addGenericDocuments(EMAIL_DOCUMENT) 643 .build())); 644 assertPackageCanAccess(EMAIL_DOCUMENT, PKG_A); 645 646 // Set the schema again, but package access as false. 647 mDb.setSchemaAsync( 648 new SetSchemaRequest.Builder() 649 .addSchemas(AppSearchEmail.SCHEMA) 650 .setSchemaTypeVisibilityForPackage( 651 AppSearchEmail.SCHEMA_TYPE, 652 /*visible=*/ false, 653 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 654 .build()) 655 .get(); 656 checkIsBatchResultSuccess( 657 mDb.putAsync( 658 new PutDocumentsRequest.Builder() 659 .addGenericDocuments(EMAIL_DOCUMENT) 660 .build())); 661 assertPackageCannotAccess(PKG_A); 662 663 // Set the schema again, but with default (i.e. no) access 664 mDb.setSchemaAsync( 665 new SetSchemaRequest.Builder() 666 .addSchemas(AppSearchEmail.SCHEMA) 667 .setSchemaTypeVisibilityForPackage( 668 AppSearchEmail.SCHEMA_TYPE, 669 /*visible=*/ false, 670 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 671 .build()) 672 .get(); 673 checkIsBatchResultSuccess( 674 mDb.putAsync( 675 new PutDocumentsRequest.Builder() 676 .addGenericDocuments(EMAIL_DOCUMENT) 677 .build())); 678 assertPackageCannotAccess(PKG_A); 679 } 680 681 @Test testAllowPermissionAccess()682 public void testAllowPermissionAccess() throws Exception { 683 // index a global searchable document in pkg_A and set it needs READ_SMS to read it. 684 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1", 685 ImmutableSet.of(ImmutableSet.of(SetSchemaRequest.READ_SMS))); 686 687 SystemUtil.runWithShellPermissionIdentity( 688 () -> { 689 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 690 691 // Can get the document 692 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 693 .getByDocumentIdAsync(PKG_A, "database", 694 new GetByDocumentIdRequest.Builder("namespace") 695 .addIds("id1") 696 .build()).get(); 697 assertThat(result.getSuccesses()).hasSize(1); 698 }, 699 READ_SMS); 700 } 701 702 //TODO(b/202194495) add test for READ_HOME_APP_SEARCH_DATA and READ_ASSISTANT_APP_SEARCH_DATA 703 // once they are available in Shell. 704 @Test testRequireAllPermissionsOfAnyCombinationToAccess()705 public void testRequireAllPermissionsOfAnyCombinationToAccess() throws Exception { 706 // index a global searchable document in pkg_A and set it needs both READ_SMS and 707 // READ_CALENDAR or READ_HOME_APP_SEARCH_DATA only or READ_ASSISTANT_APP_SEARCH_DATA 708 // only to read it. 709 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1", 710 ImmutableSet.of( 711 ImmutableSet.of(SetSchemaRequest.READ_SMS, 712 SetSchemaRequest.READ_CALENDAR), 713 ImmutableSet.of(SetSchemaRequest.READ_CONTACTS), 714 ImmutableSet.of(SetSchemaRequest.READ_EXTERNAL_STORAGE))); 715 716 // Has READ_SMS only cannot access the document. 717 SystemUtil.runWithShellPermissionIdentity( 718 () -> { 719 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 720 721 // Can't get the document 722 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 723 .getByDocumentIdAsync(PKG_A, "database", 724 new GetByDocumentIdRequest.Builder("namespace") 725 .addIds("id1") 726 .build()).get(); 727 assertThat(result.getSuccesses()).isEmpty(); 728 }, 729 READ_SMS); 730 731 // Has READ_SMS and READ_CALENDAR can access the document. 732 SystemUtil.runWithShellPermissionIdentity( 733 () -> { 734 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 735 736 // Can get the document 737 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 738 .getByDocumentIdAsync(PKG_A, "database", 739 new GetByDocumentIdRequest.Builder("namespace") 740 .addIds("id1") 741 .build()).get(); 742 assertThat(result.getSuccesses()).hasSize(1); 743 }, 744 READ_SMS, READ_CALENDAR); 745 746 // Has READ_CONTACTS can access the document also. 747 SystemUtil.runWithShellPermissionIdentity( 748 () -> { 749 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 750 751 // Can get the document 752 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 753 .getByDocumentIdAsync(PKG_A, "database", 754 new GetByDocumentIdRequest.Builder("namespace") 755 .addIds("id1") 756 .build()).get(); 757 assertThat(result.getSuccesses()).hasSize(1); 758 }, 759 READ_CONTACTS); 760 761 // Has READ_EXTERNAL_STORAGE can access the document. 762 SystemUtil.runWithShellPermissionIdentity( 763 () -> { 764 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 765 766 // Can get the document 767 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 768 .getByDocumentIdAsync(PKG_A, "database", 769 new GetByDocumentIdRequest.Builder("namespace") 770 .addIds("id1") 771 .build()).get(); 772 assertThat(result.getSuccesses()).hasSize(1); 773 }, 774 READ_EXTERNAL_STORAGE); 775 } 776 777 @Test testAllowPermissions_sameError()778 public void testAllowPermissions_sameError() throws Exception { 779 // Try to get document before we put them, this is not found error. 780 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 781 AppSearchBatchResult<String, GenericDocument> nonExistentResult = mGlobalSearchSession 782 .getByDocumentIdAsync(PKG_A, "database", 783 new GetByDocumentIdRequest.Builder("namespace") 784 .addIds("id1") 785 .build()).get(); 786 assertThat(nonExistentResult.isSuccess()).isFalse(); 787 assertThat(nonExistentResult.getSuccesses()).isEmpty(); 788 assertThat(nonExistentResult.getFailures()).containsKey("id1"); 789 790 // Index a global searchable document in pkg_A and set it needs READ_SMS to read it. 791 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1", 792 ImmutableSet.of(ImmutableSet.of(SetSchemaRequest.READ_SMS))); 793 794 // Try to get document w/o permission, this is unAuthority error. 795 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 796 AppSearchBatchResult<String, GenericDocument> unAuthResult = mGlobalSearchSession 797 .getByDocumentIdAsync(PKG_A, "database", 798 new GetByDocumentIdRequest.Builder("namespace") 799 .addIds("id1") 800 .build()).get(); 801 assertThat(unAuthResult.isSuccess()).isFalse(); 802 assertThat(unAuthResult.getSuccesses()).isEmpty(); 803 assertThat(unAuthResult.getFailures()).containsKey("id1"); 804 805 // The error messages must be same. 806 assertThat(unAuthResult.getFailures().get("id1").getErrorMessage()) 807 .isEqualTo(nonExistentResult.getFailures().get("id1").getErrorMessage()); 808 } 809 810 @Test testGlobalGetById_withAccess()811 public void testGlobalGetById_withAccess() throws Exception { 812 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 813 814 SystemUtil.runWithShellPermissionIdentity( 815 () -> { 816 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 817 818 // Can get the document 819 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 820 .getByDocumentIdAsync(PKG_A, DB_NAME, 821 new GetByDocumentIdRequest.Builder(NAMESPACE_NAME) 822 .addIds("id1") 823 .build()).get(); 824 825 assertThat(result.getSuccesses()).hasSize(1); 826 827 // Can't get non existent document 828 AppSearchBatchResult<String, GenericDocument> nonExistent = mGlobalSearchSession 829 .getByDocumentIdAsync(PKG_A, DB_NAME, 830 new GetByDocumentIdRequest.Builder(NAMESPACE_NAME) 831 .addIds("id2") 832 .build()).get(); 833 834 assertThat(nonExistent.isSuccess()).isFalse(); 835 assertThat(nonExistent.getSuccesses()).isEmpty(); 836 }, 837 READ_GLOBAL_APP_SEARCH_DATA); 838 } 839 840 @Test testGlobalGetById_withoutAccess()841 public void testGlobalGetById_withoutAccess() throws Exception { 842 indexNotGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 843 844 SystemUtil.runWithShellPermissionIdentity( 845 () -> { 846 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 847 848 // Can't get the document 849 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 850 .getByDocumentIdAsync(PKG_A, DB_NAME, 851 new GetByDocumentIdRequest.Builder(NAMESPACE_NAME) 852 .addIds("id1") 853 .build()).get(); 854 assertThat(result.isSuccess()).isFalse(); 855 assertThat(result.getSuccesses()).isEmpty(); 856 assertThat(result.getFailures()).containsKey("id1"); 857 }, 858 READ_GLOBAL_APP_SEARCH_DATA); 859 } 860 861 @Test testGlobalGetById_sameErrorMessages()862 public void testGlobalGetById_sameErrorMessages() throws Exception { 863 AtomicReference<String> errorMessageNonExistent = new AtomicReference<>(); 864 AtomicReference<String> errorMessageUnauth = new AtomicReference<>(); 865 866 // Can't get the document because we haven't added it yet 867 SystemUtil.runWithShellPermissionIdentity( 868 () -> { 869 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 870 871 AppSearchBatchResult<String, GenericDocument> nonExistentResult = 872 mGlobalSearchSession.getByDocumentIdAsync(PKG_A, DB_NAME, 873 new GetByDocumentIdRequest.Builder(NAMESPACE_NAME) 874 .addIds("id1") 875 .build()).get(); 876 assertThat(nonExistentResult.isSuccess()).isFalse(); 877 assertThat(nonExistentResult.getSuccesses()).isEmpty(); 878 assertThat(nonExistentResult.getFailures()).containsKey("id1"); 879 errorMessageNonExistent.set( 880 nonExistentResult.getFailures().get("id1").getErrorMessage()); 881 }, 882 READ_GLOBAL_APP_SEARCH_DATA); 883 884 indexNotGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 885 886 // Can't get the document because the document is not globally searchable 887 SystemUtil.runWithShellPermissionIdentity( 888 () -> { 889 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 890 891 AppSearchBatchResult<String, GenericDocument> unAuthResult = 892 mGlobalSearchSession.getByDocumentIdAsync(PKG_A, DB_NAME, 893 new GetByDocumentIdRequest.Builder(NAMESPACE_NAME) 894 .addIds("id1") 895 .build()).get(); 896 assertThat(unAuthResult.isSuccess()).isFalse(); 897 assertThat(unAuthResult.getSuccesses()).isEmpty(); 898 assertThat(unAuthResult.getFailures()).containsKey("id1"); 899 errorMessageUnauth.set( 900 unAuthResult.getFailures().get("id1").getErrorMessage()); 901 }, 902 READ_GLOBAL_APP_SEARCH_DATA); 903 904 // try adding a global doc here to make sure non-global querier can't get it 905 // and same error message 906 clearData(PKG_A, DB_NAME); 907 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 908 909 // Can't get the document because we don't have global permissions 910 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 911 912 AppSearchBatchResult<String, GenericDocument> noGlobalResult = mGlobalSearchSession 913 .getByDocumentIdAsync(PKG_A, DB_NAME, 914 new GetByDocumentIdRequest.Builder(NAMESPACE_NAME) 915 .addIds("id1") 916 .build()).get(); 917 assertThat(noGlobalResult.isSuccess()).isFalse(); 918 assertThat(noGlobalResult.getSuccesses()).isEmpty(); 919 assertThat(noGlobalResult.getFailures()).containsKey("id1"); 920 921 // compare error messages 922 assertThat(errorMessageNonExistent.get()).isEqualTo(errorMessageUnauth.get()); 923 assertThat(errorMessageNonExistent.get()) 924 .isEqualTo(noGlobalResult.getFailures().get("id1").getErrorMessage()); 925 } 926 927 @Test testGlobalGetById_requireAllVisibleToConfig_pkgAndPermission()928 public void testGlobalGetById_requireAllVisibleToConfig_pkgAndPermission() throws Exception { 929 String ctsPackageName = mContext.getPackageName(); 930 PackageIdentifier ctsPackage = 931 new PackageIdentifier( 932 ctsPackageName, 933 PackageUtil.getSelfPackageSha256Cert(mContext) 934 ); 935 PackageIdentifier otherPackage = 936 new PackageIdentifier("com.android.other", new byte[32]); 937 // index global searchable document in pkg_A and set it requires READ_SMS and accessible 938 // to cts package 939 indexGloballySearchableDocumentVisibleToConfig(PKG_A, DB_NAME, NAMESPACE_NAME, "id", 940 ImmutableSet.of(ctsPackage, otherPackage), 941 ImmutableSet.of(ImmutableSet.of(SetSchemaRequest.READ_SMS)), 942 /*publicAclPackage=*/null); 943 944 // Can get document with READ_SMS permission. 945 SystemUtil.runWithShellPermissionIdentity( 946 () -> { 947 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 948 949 // Can get the document "id1". 950 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 951 .getByDocumentIdAsync(PKG_A, "database", 952 new GetByDocumentIdRequest.Builder("namespace") 953 .addIds("id") 954 .build()).get(); 955 assertThat(result.getSuccesses()).hasSize(1); 956 }, 957 READ_SMS); 958 959 // Cann't get document with READ_CALENDAR permission. 960 SystemUtil.runWithShellPermissionIdentity( 961 () -> { 962 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 963 964 // Can't get the document "id2", even if we are while list package, but we don't 965 // hold required permission. 966 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 967 .getByDocumentIdAsync(PKG_A, "database", 968 new GetByDocumentIdRequest.Builder("namespace") 969 .addIds("id") 970 .build()).get(); 971 assertThat(result.getSuccesses()).isEmpty(); 972 }, 973 READ_CALENDAR); 974 975 // Update the schema visibility setting and index global searchable document in pkg_A 976 // set it requires READ_SMS and NOT accessible to cts package 977 indexGloballySearchableDocumentVisibleToConfig(PKG_A, DB_NAME, NAMESPACE_NAME, "id", 978 ImmutableSet.of(otherPackage), 979 ImmutableSet.of(ImmutableSet.of(SetSchemaRequest.READ_SMS)), 980 /*publicAclPackage=*/null); 981 // Cann't get document with READ_SMS permission. 982 SystemUtil.runWithShellPermissionIdentity( 983 () -> { 984 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 985 // Can't get the document "id2", even if we hold the required permission but 986 // we are not in the while list packages. 987 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 988 .getByDocumentIdAsync(PKG_A, "database", 989 new GetByDocumentIdRequest.Builder("namespace") 990 .addIds("id") 991 .build()).get(); 992 assertThat(result.getSuccesses()).isEmpty(); 993 }, 994 READ_SMS); 995 } 996 997 @Test testGlobalGetById_requireAllVisibleToConfig_publicAclAndPermission()998 public void testGlobalGetById_requireAllVisibleToConfig_publicAclAndPermission() 999 throws Exception { 1000 String ctsPackageName = mContext.getPackageName(); 1001 assertThat(mContext.getPackageManager().canPackageQuery(ctsPackageName, PKG_A)).isTrue(); 1002 PackageIdentifier packageA = new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256); 1003 // index global searchable document in pkg_A and set it requires READ_SMS and is public to 1004 // packages that can query other package 1005 indexGloballySearchableDocumentVisibleToConfig(PKG_A, DB_NAME, NAMESPACE_NAME, "id", 1006 /*visibleToPackages=*/ImmutableSet.of(), 1007 ImmutableSet.of(ImmutableSet.of(SetSchemaRequest.READ_SMS)), 1008 packageA); 1009 1010 // Can get document with READ_SMS permission. 1011 SystemUtil.runWithShellPermissionIdentity( 1012 () -> { 1013 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1014 1015 // Can get the document 1016 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 1017 .getByDocumentIdAsync(PKG_A, "database", 1018 new GetByDocumentIdRequest.Builder("namespace") 1019 .addIds("id") 1020 .build()).get(); 1021 assertThat(result.getSuccesses()).hasSize(1); 1022 }, 1023 READ_SMS); 1024 1025 // Cann't get document with READ_CALENDAR permission. 1026 SystemUtil.runWithShellPermissionIdentity( 1027 () -> { 1028 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1029 1030 // Can't get the document, even if we are while list package, but we don't 1031 // hold required permission. 1032 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 1033 .getByDocumentIdAsync(PKG_A, "database", 1034 new GetByDocumentIdRequest.Builder("namespace") 1035 .addIds("id") 1036 .build()).get(); 1037 assertThat(result.getSuccesses()).isEmpty(); 1038 }, 1039 READ_CALENDAR); 1040 1041 // Update the schema visibility setting and index global searchable document in pkg_A 1042 // set it requires READ_SMS and is public to packages that can query otherPackage 1043 PackageIdentifier otherPackage = 1044 new PackageIdentifier("com.android.other", new byte[32]); 1045 indexGloballySearchableDocumentVisibleToConfig(PKG_A, DB_NAME, NAMESPACE_NAME, "id", 1046 /*visibleToPackages=*/ImmutableSet.of(), 1047 ImmutableSet.of(ImmutableSet.of(SetSchemaRequest.READ_SMS)), 1048 otherPackage); 1049 // Cann't get document with READ_SMS permission. 1050 SystemUtil.runWithShellPermissionIdentity( 1051 () -> { 1052 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1053 // Can't get the document "id2", even if we hold the required permission but 1054 // we are not in the while list packages. 1055 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 1056 .getByDocumentIdAsync(PKG_A, "database", 1057 new GetByDocumentIdRequest.Builder("namespace") 1058 .addIds("id") 1059 .build()).get(); 1060 assertThat(result.getSuccesses()).isEmpty(); 1061 }, 1062 READ_SMS); 1063 } 1064 1065 @Test testGlobalGetById_requireAllVisibleToConfig_all3Settings()1066 public void testGlobalGetById_requireAllVisibleToConfig_all3Settings() 1067 throws Exception { 1068 String ctsPackageName = mContext.getPackageName(); 1069 assertThat(mContext.getPackageManager().canPackageQuery(ctsPackageName, PKG_A)).isTrue(); 1070 PackageIdentifier ctsPackage = 1071 new PackageIdentifier(ctsPackageName, 1072 PackageUtil.getSelfPackageSha256Cert(mContext)); 1073 PackageIdentifier packageA = new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256); 1074 PackageIdentifier otherPackage = 1075 new PackageIdentifier("com.android.other", new byte[32]); 1076 // index global searchable document in pkg_A and set it requires READ_SMS and accessible 1077 // to cts package and is public to packages that can query other package 1078 indexGloballySearchableDocumentVisibleToConfig(PKG_A, DB_NAME, NAMESPACE_NAME, "id", 1079 ImmutableSet.of(ctsPackage, otherPackage), 1080 ImmutableSet.of(ImmutableSet.of(SetSchemaRequest.READ_SMS)), 1081 packageA); 1082 1083 // Can get document with READ_SMS permission. 1084 SystemUtil.runWithShellPermissionIdentity( 1085 () -> { 1086 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1087 1088 // Can get the document 1089 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 1090 .getByDocumentIdAsync(PKG_A, "database", 1091 new GetByDocumentIdRequest.Builder("namespace") 1092 .addIds("id") 1093 .build()).get(); 1094 assertThat(result.getSuccesses()).hasSize(1); 1095 }, 1096 READ_SMS); 1097 1098 // Cann't get document with READ_CALENDAR permission. 1099 SystemUtil.runWithShellPermissionIdentity( 1100 () -> { 1101 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1102 1103 // Can't get the document "id2", even if we are while list package, but we don't 1104 // hold required permission. 1105 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 1106 .getByDocumentIdAsync(PKG_A, "database", 1107 new GetByDocumentIdRequest.Builder("namespace") 1108 .addIds("id1") 1109 .build()).get(); 1110 assertThat(result.getSuccesses()).isEmpty(); 1111 }, 1112 READ_CALENDAR); 1113 1114 // Update the schema visibility setting and index global searchable document in pkg_A. 1115 // set it NOT accessible to cts package 1116 indexGloballySearchableDocumentVisibleToConfig(PKG_A, DB_NAME, NAMESPACE_NAME, "id", 1117 ImmutableSet.of(otherPackage), 1118 ImmutableSet.of(ImmutableSet.of(SetSchemaRequest.READ_SMS)), 1119 packageA); 1120 1121 // Cann't get document with READ_SMS permission. 1122 SystemUtil.runWithShellPermissionIdentity( 1123 () -> { 1124 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1125 // Can't get the document "id2", even if we hold the required permission but 1126 // we are not in the while list packages. 1127 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 1128 .getByDocumentIdAsync(PKG_A, "database", 1129 new GetByDocumentIdRequest.Builder("namespace") 1130 .addIds("id") 1131 .build()).get(); 1132 assertThat(result.getSuccesses()).isEmpty(); 1133 }, 1134 READ_SMS); 1135 1136 // Update the schema visibility setting and index global searchable document in pkg_A. 1137 // set it requires READ_SMS and accessible to cts package and require can query to target 1138 // the otherPackage. 1139 indexGloballySearchableDocumentVisibleToConfig(PKG_A, DB_NAME, NAMESPACE_NAME, "id", 1140 ImmutableSet.of(ctsPackage, otherPackage), 1141 ImmutableSet.of(ImmutableSet.of(SetSchemaRequest.READ_SMS)), 1142 /*publicAclPackage=*/otherPackage); 1143 1144 // Cann't get document with READ_SMS permission. 1145 SystemUtil.runWithShellPermissionIdentity( 1146 () -> { 1147 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1148 // Can't get the document "id2", even if we hold the required permission but 1149 // we are not in the while list packages. 1150 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 1151 .getByDocumentIdAsync(PKG_A, "database", 1152 new GetByDocumentIdRequest.Builder("namespace") 1153 .addIds("id") 1154 .build()).get(); 1155 assertThat(result.getSuccesses()).isEmpty(); 1156 }, 1157 READ_SMS); 1158 } 1159 1160 @Test testGlobalGetById_requireAllVisibleToConfig_emptyConfig()1161 public void testGlobalGetById_requireAllVisibleToConfig_emptyConfig() 1162 throws Exception { 1163 // index global searchable document in pkg_A and set it requires empty config. This 1164 // shouldn't accessible by anyone. 1165 indexGloballySearchableDocumentVisibleToConfig(PKG_A, DB_NAME, NAMESPACE_NAME, "id", 1166 /*visibleToPackages=*/ImmutableSet.of(), 1167 /*visibleToPermissions=*/ImmutableSet.of(), 1168 /*publicAclPackage=*/null); 1169 1170 // Cann't get document with READ_SMS permission. 1171 SystemUtil.runWithShellPermissionIdentity( 1172 () -> { 1173 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1174 1175 AppSearchBatchResult<String, GenericDocument> result = mGlobalSearchSession 1176 .getByDocumentIdAsync(PKG_A, "database", 1177 new GetByDocumentIdRequest.Builder("namespace") 1178 .addIds("id") 1179 .build()).get(); 1180 assertThat(result.getSuccesses()).isEmpty(); 1181 }, 1182 READ_SMS); 1183 } 1184 1185 @Test testGlobalSearch_withAccess()1186 public void testGlobalSearch_withAccess() throws Exception { 1187 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 1188 indexGloballySearchableDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "id1"); 1189 1190 SystemUtil.runWithShellPermissionIdentity( 1191 () -> { 1192 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1193 1194 SearchResultsShim searchResults = 1195 mGlobalSearchSession.search( 1196 /*queryExpression=*/ "", 1197 new SearchSpec.Builder() 1198 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1199 .addFilterPackageNames(PKG_A, PKG_B) 1200 .build()); 1201 List<SearchResult> page = searchResults.getNextPageAsync().get(); 1202 assertThat(page).hasSize(2); 1203 1204 ImmutableSet<String> actualPackageNames = 1205 ImmutableSet.of( 1206 page.get(0).getPackageName(), page.get(1).getPackageName()); 1207 assertThat(actualPackageNames).containsExactly(PKG_A, PKG_B); 1208 }, 1209 READ_GLOBAL_APP_SEARCH_DATA); 1210 } 1211 1212 @Test testGlobalSearch_withPartialAccess()1213 public void testGlobalSearch_withPartialAccess() throws Exception { 1214 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 1215 indexNotGloballySearchableDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "id1"); 1216 1217 SystemUtil.runWithShellPermissionIdentity( 1218 () -> { 1219 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1220 1221 SearchResultsShim searchResults = 1222 mGlobalSearchSession.search( 1223 /*queryExpression=*/ "", 1224 new SearchSpec.Builder() 1225 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1226 .addFilterPackageNames(PKG_A, PKG_B) 1227 .build()); 1228 List<SearchResult> page = searchResults.getNextPageAsync().get(); 1229 assertThat(page).hasSize(1); 1230 1231 assertThat(page.get(0).getPackageName()).isEqualTo(PKG_A); 1232 }, 1233 READ_GLOBAL_APP_SEARCH_DATA); 1234 } 1235 1236 @Test testGlobalSearch_withPackageFilters()1237 public void testGlobalSearch_withPackageFilters() throws Exception { 1238 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 1239 indexGloballySearchableDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "id1"); 1240 1241 SystemUtil.runWithShellPermissionIdentity( 1242 () -> { 1243 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1244 1245 SearchResultsShim searchResults = 1246 mGlobalSearchSession.search( 1247 /*queryExpression=*/ "", 1248 new SearchSpec.Builder() 1249 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1250 .addFilterPackageNames(PKG_B) 1251 .build()); 1252 List<SearchResult> page = searchResults.getNextPageAsync().get(); 1253 assertThat(page).hasSize(1); 1254 1255 assertThat(page.get(0).getPackageName()).isEqualTo(PKG_B); 1256 }, 1257 READ_GLOBAL_APP_SEARCH_DATA); 1258 } 1259 1260 @Test testGlobalSearch_withoutAccess()1261 public void testGlobalSearch_withoutAccess() throws Exception { 1262 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 1263 indexGloballySearchableDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "id1"); 1264 1265 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1266 1267 SearchResultsShim searchResults = 1268 mGlobalSearchSession.search( 1269 /*queryExpression=*/ "", 1270 new SearchSpec.Builder() 1271 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1272 .addFilterPackageNames(PKG_A, PKG_B) 1273 .build()); 1274 List<SearchResult> page = searchResults.getNextPageAsync().get(); 1275 assertThat(page).isEmpty(); 1276 } 1277 1278 @Test testGlobalSearch_withJoin_packageFiltering()1279 public void testGlobalSearch_withJoin_packageFiltering() throws Exception { 1280 // This test ensures that we can place documents in separate packages, then join them 1281 // together while specifying package filters. 1282 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "ida"); 1283 1284 // In pkg B, we report an action on the document in pkg A. When we retrieve the pkg A 1285 // document using a joinspec, the action in pkg B should be joined. 1286 String qualifiedId = 1287 DocumentIdUtil.createQualifiedId( 1288 PKG_A, DB_NAME, NAMESPACE_NAME, "ida"); 1289 1290 indexActionDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "actionId1", qualifiedId, 1291 /*globallySearchable*/true); 1292 1293 AppSearchSchema actionSchema = 1294 new AppSearchSchema.Builder("PlayAction") 1295 .addProperty( 1296 new StringPropertyConfig.Builder("songId") 1297 .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL) 1298 .setJoinableValueType(StringPropertyConfig 1299 .JOINABLE_VALUE_TYPE_QUALIFIED_ID) 1300 .build()) 1301 .build(); 1302 mDb.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(actionSchema).build()).get(); 1303 1304 GenericDocument join = 1305 new GenericDocument.Builder<>(NAMESPACE_NAME, "actionId2", "PlayAction") 1306 .setPropertyString("songId", qualifiedId) 1307 .build(); 1308 1309 // In the test package, index an action on that document 1310 checkIsBatchResultSuccess(mDb.putAsync( 1311 new PutDocumentsRequest.Builder().addGenericDocuments(join).build())); 1312 1313 SystemUtil.runWithShellPermissionIdentity( 1314 () -> { 1315 1316 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1317 1318 SearchSpec nestedSearchSpec = new SearchSpec.Builder() 1319 .addFilterPackageNames(PKG_B) 1320 .build(); 1321 JoinSpec js = new JoinSpec.Builder("songId") 1322 .setNestedSearch("", nestedSearchSpec) 1323 .build(); 1324 1325 SearchResultsShim searchResults = 1326 mGlobalSearchSession.search( 1327 /*queryExpression=*/ "", 1328 new SearchSpec.Builder() 1329 .addFilterPackageNames(PKG_A) 1330 .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE) 1331 .setJoinSpec(js) 1332 .build()); 1333 1334 List<SearchResult> page = searchResults.getNextPageAsync().get(); 1335 assertThat(page).hasSize(1); 1336 1337 SearchResult searchResult = page.get(0); 1338 1339 assertThat(searchResult.getGenericDocument().getId()).isEqualTo("ida"); 1340 1341 assertThat(searchResult.getJoinedResults()).hasSize(1); 1342 SearchResult joined = page.get(0).getJoinedResults().get(0); 1343 assertThat(joined.getGenericDocument().getId()).isEqualTo("actionId1"); 1344 }, 1345 READ_GLOBAL_APP_SEARCH_DATA); 1346 } 1347 1348 @Test testGlobalSearch_withJoin()1349 public void testGlobalSearch_withJoin() throws Exception { 1350 indexGloballySearchableDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "idb"); 1351 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "ida"); 1352 1353 // In pkg B, we report an action on the document in pkg A. When we retrieve the pkg A 1354 // document using a joinspec, the action in pkg B should be joined. 1355 String qualifiedId = 1356 DocumentIdUtil.createQualifiedId( 1357 PKG_A, DB_NAME, NAMESPACE_NAME, "ida"); 1358 1359 indexActionDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "actionId", qualifiedId, 1360 /*globallySearchable*/true); 1361 1362 SystemUtil.runWithShellPermissionIdentity( 1363 () -> { 1364 1365 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1366 1367 SearchSpec nestedSearchSpec = new SearchSpec.Builder() 1368 .addFilterPackageNames(PKG_A, PKG_B) 1369 .build(); 1370 JoinSpec js = new JoinSpec.Builder("songId") 1371 .setNestedSearch("", nestedSearchSpec) 1372 .build(); 1373 1374 // Here, we rank based on document creation timestamp. Since it's ascending, 1375 // the document in package B should be first. 1376 SearchResultsShim searchResults = 1377 mGlobalSearchSession.search( 1378 /*queryExpression=*/ "", 1379 new SearchSpec.Builder() 1380 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1381 .setRankingStrategy(SearchSpec 1382 .RANKING_STRATEGY_CREATION_TIMESTAMP) 1383 .setOrder(SearchSpec.ORDER_ASCENDING) 1384 .addFilterPackageNames(PKG_A, PKG_B) 1385 .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE) 1386 .setJoinSpec(js) 1387 .build()); 1388 1389 List<SearchResult> page = searchResults.getNextPageAsync().get(); 1390 assertThat(page).hasSize(2); 1391 1392 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("idb"); 1393 1394 assertThat(page.get(1).getJoinedResults()).hasSize(1); 1395 assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("ida"); 1396 1397 SearchResult joined = page.get(1).getJoinedResults().get(0); 1398 assertThat(joined.getGenericDocument().getId()).isEqualTo("actionId"); 1399 1400 // Here, we rank based on the number of joined documents a parent document has. 1401 // The results should be in a different order this time. 1402 js = new JoinSpec.Builder("songId") 1403 .setNestedSearch("", nestedSearchSpec) 1404 .setAggregationScoringStrategy(JoinSpec 1405 .AGGREGATION_SCORING_RESULT_COUNT) 1406 .build(); 1407 1408 searchResults = mGlobalSearchSession.search( 1409 /*queryExpression=*/ "", 1410 new SearchSpec.Builder() 1411 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1412 .setRankingStrategy(SearchSpec 1413 .RANKING_STRATEGY_JOIN_AGGREGATE_SCORE) 1414 .addFilterPackageNames(PKG_A, PKG_B) 1415 .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE) 1416 .setJoinSpec(js) 1417 .build()); 1418 1419 page = searchResults.getNextPageAsync().get(); 1420 assertThat(page).hasSize(2); 1421 1422 assertThat(page.get(0).getJoinedResults()).hasSize(1); 1423 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("ida"); 1424 1425 joined = page.get(0).getJoinedResults().get(0); 1426 assertThat(joined.getGenericDocument().getId()).isEqualTo("actionId"); 1427 1428 assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("idb"); 1429 }, 1430 READ_GLOBAL_APP_SEARCH_DATA); 1431 } 1432 1433 @Test testGlobalSearch_withJoin_partialAccess()1434 public void testGlobalSearch_withJoin_partialAccess() throws Exception { 1435 1436 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "ida"); 1437 1438 String qualifiedId = 1439 DocumentIdUtil.createQualifiedId( 1440 PKG_A, DB_NAME, NAMESPACE_NAME, "ida"); 1441 // Reporting an action on a globally searchable document, but the action itself isn't 1442 // globally searchable. Searching with joinspec shouldn't return the action document. 1443 1444 // In pkg B, we report an action on the document in pkg A. When we retrieve the pkg A 1445 // document using a joinspec, the action in pkg B should be joined. 1446 1447 // here, we index an action document inaccessible from the querier 1448 indexActionDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "actionId", qualifiedId, 1449 /*globallySearchable*/false); 1450 1451 SystemUtil.runWithShellPermissionIdentity( 1452 () -> { 1453 1454 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1455 1456 SearchSpec nestedSearchSpec = new SearchSpec.Builder() 1457 .addFilterPackageNames(PKG_A, PKG_B) 1458 .build(); 1459 JoinSpec js = 1460 new JoinSpec.Builder("songId") 1461 .setNestedSearch("", nestedSearchSpec) 1462 .setAggregationScoringStrategy(JoinSpec 1463 .AGGREGATION_SCORING_RESULT_COUNT) 1464 .build(); 1465 1466 SearchResultsShim searchResults = 1467 mGlobalSearchSession.search( 1468 /*queryExpression=*/ "", 1469 new SearchSpec.Builder() 1470 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1471 .setRankingStrategy(SearchSpec 1472 .RANKING_STRATEGY_JOIN_AGGREGATE_SCORE) 1473 .addFilterPackageNames(PKG_A, PKG_B) 1474 .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE) 1475 .setJoinSpec(js) 1476 .build()); 1477 1478 List<SearchResult> page = searchResults.getNextPageAsync().get(); 1479 assertThat(page).hasSize(1); 1480 1481 assertThat(page.get(0).getJoinedResults()).hasSize(0); 1482 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("ida"); 1483 }, 1484 READ_GLOBAL_APP_SEARCH_DATA); 1485 } 1486 1487 @Test testGlobalSearch_withJoin_withoutAccess()1488 public void testGlobalSearch_withJoin_withoutAccess() throws Exception { 1489 1490 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1491 1492 // Insert schema 1493 mDb.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build()) 1494 .get(); 1495 1496 // Insert two docs 1497 GenericDocument document1 = 1498 new GenericDocument.Builder<>(NAMESPACE_NAME, "id1", AppSearchEmail.SCHEMA_TYPE) 1499 .build(); 1500 mDb.putAsync( 1501 new PutDocumentsRequest.Builder().addGenericDocuments(document1).build()) 1502 .get(); 1503 1504 String qualifiedId = 1505 DocumentIdUtil.createQualifiedId( 1506 PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 1507 indexActionDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "actionId", qualifiedId, 1508 /*globallySearchable*/true); 1509 1510 // Query the data 1511 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1512 1513 SearchSpec nestedSearchSpec = new SearchSpec.Builder() 1514 .addFilterPackageNames(PKG_A, PKG_B) 1515 .build(); 1516 JoinSpec js = 1517 new JoinSpec.Builder("songId") 1518 .setNestedSearch("", nestedSearchSpec) 1519 .setAggregationScoringStrategy(JoinSpec 1520 .AGGREGATION_SCORING_RESULT_COUNT) 1521 .build(); 1522 1523 SearchSpec searchSpec = new SearchSpec.Builder() 1524 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1525 .setRankingStrategy(SearchSpec 1526 .RANKING_STRATEGY_JOIN_AGGREGATE_SCORE) 1527 .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE) 1528 .setJoinSpec(js) 1529 .build(); 1530 1531 SearchResultsShim results = mDb.search("", searchSpec); 1532 List<SearchResult> page = results.getNextPageAsync().get(); 1533 assertThat(page).hasSize(1); 1534 1535 assertThat(page.get(0).getJoinedResults()).hasSize(0); 1536 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id1"); 1537 } 1538 1539 @Test testGlobalGetSchema_packageAccess_defaultAccess()1540 public void testGlobalGetSchema_packageAccess_defaultAccess() throws Exception { 1541 // 1. Create a schema in the test with default (no) access. 1542 mDb.setSchemaAsync( 1543 new SetSchemaRequest.Builder() 1544 .addSchemas(AppSearchEmail.SCHEMA) 1545 .build()) 1546 .get(); 1547 1548 // 2. Neither PKG_A nor PKG_B should be able to retrieve the schema. 1549 List<String> schemaStrings = getSchemaAsPackage(PKG_A); 1550 assertThat(schemaStrings).isNull(); 1551 1552 schemaStrings = getSchemaAsPackage(PKG_B); 1553 assertThat(schemaStrings).isNull(); 1554 } 1555 1556 @Test testGlobalGetSchema_packageAccess_singleAccess()1557 public void testGlobalGetSchema_packageAccess_singleAccess() throws Exception { 1558 // 1. Create a schema in the test with access granted to PKG_A, but not PKG_B. 1559 mDb.setSchemaAsync( 1560 new SetSchemaRequest.Builder() 1561 .addSchemas(AppSearchEmail.SCHEMA) 1562 .setSchemaTypeVisibilityForPackage( 1563 AppSearchEmail.SCHEMA_TYPE, 1564 /*visible=*/ true, 1565 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 1566 .build()) 1567 .get(); 1568 1569 // 2. Only PKG_A should be able to retrieve the schema. 1570 List<String> schemaStrings = getSchemaAsPackage(PKG_A); 1571 assertThat(schemaStrings).containsExactly(AppSearchEmail.SCHEMA.toString()); 1572 1573 schemaStrings = getSchemaAsPackage(PKG_B); 1574 assertThat(schemaStrings).isNull(); 1575 } 1576 1577 @Test testGlobalGetSchema_packageAccess_multiAccess()1578 public void testGlobalGetSchema_packageAccess_multiAccess() throws Exception { 1579 // 1. Create a schema in the test with access granted to PKG_A and PKG_B. 1580 mDb.setSchemaAsync( 1581 new SetSchemaRequest.Builder() 1582 .addSchemas(AppSearchEmail.SCHEMA) 1583 .setSchemaTypeVisibilityForPackage( 1584 AppSearchEmail.SCHEMA_TYPE, 1585 /*visible=*/ true, 1586 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 1587 .setSchemaTypeVisibilityForPackage( 1588 AppSearchEmail.SCHEMA_TYPE, 1589 /*visible=*/ true, 1590 new PackageIdentifier(PKG_B, PKG_B_CERT_SHA256)) 1591 .build()) 1592 .get(); 1593 1594 // 2. Both packages should be able to retrieve the schema. 1595 List<String> schemaStrings = getSchemaAsPackage(PKG_A); 1596 assertThat(schemaStrings).containsExactly(AppSearchEmail.SCHEMA.toString()); 1597 1598 schemaStrings = getSchemaAsPackage(PKG_B); 1599 assertThat(schemaStrings).containsExactly(AppSearchEmail.SCHEMA.toString()); 1600 } 1601 1602 @Test testGlobalGetSchema_packageAccess_revokeAccess()1603 public void testGlobalGetSchema_packageAccess_revokeAccess() throws Exception { 1604 // 1. Create a schema in the test with access granted to PKG_A. 1605 mDb.setSchemaAsync( 1606 new SetSchemaRequest.Builder() 1607 .addSchemas(AppSearchEmail.SCHEMA) 1608 .setSchemaTypeVisibilityForPackage( 1609 AppSearchEmail.SCHEMA_TYPE, 1610 /*visible=*/ true, 1611 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 1612 .build()) 1613 .get(); 1614 1615 // 2. Now revoke that access. 1616 mDb.setSchemaAsync( 1617 new SetSchemaRequest.Builder() 1618 .addSchemas(AppSearchEmail.SCHEMA) 1619 .setSchemaTypeVisibilityForPackage( 1620 AppSearchEmail.SCHEMA_TYPE, 1621 /*visible=*/ false, 1622 new PackageIdentifier(PKG_A, PKG_A_CERT_SHA256)) 1623 .build()) 1624 .get(); 1625 1626 // 3. PKG_A should NOT be able to retrieve the schema. 1627 List<String> schemaStrings = getSchemaAsPackage(PKG_A); 1628 assertThat(schemaStrings).isNull(); 1629 } 1630 1631 @Test testGlobalGetSchema_globalAccess_singleAccess()1632 public void testGlobalGetSchema_globalAccess_singleAccess() throws Exception { 1633 // 1. Index documents for PKG_A and PKG_B. This will set the schema for each with the 1634 // corresponding access set. 1635 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 1636 indexNotGloballySearchableDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "id1"); 1637 1638 SystemUtil.runWithShellPermissionIdentity( 1639 () -> { 1640 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1641 1642 // 2. The schema for PKG_A should be retrievable, but PKG_B should not be. 1643 GetSchemaResponse response = 1644 mGlobalSearchSession.getSchemaAsync(PKG_A, DB_NAME).get(); 1645 assertThat(response.getSchemas()).hasSize(1); 1646 1647 response = mGlobalSearchSession.getSchemaAsync(PKG_B, DB_NAME).get(); 1648 assertThat(response.getSchemas()).isEmpty(); 1649 }, 1650 READ_GLOBAL_APP_SEARCH_DATA); 1651 } 1652 1653 @Test testGlobalGetSchema_globalAccess_multiAccess()1654 public void testGlobalGetSchema_globalAccess_multiAccess() throws Exception { 1655 // 1. Index documents for PKG_A and PKG_B. This will set the schema for each with the 1656 // corresponding access set. 1657 indexGloballySearchableDocument(PKG_A, DB_NAME, NAMESPACE_NAME, "id1"); 1658 indexGloballySearchableDocument(PKG_B, DB_NAME, NAMESPACE_NAME, "id1"); 1659 1660 SystemUtil.runWithShellPermissionIdentity( 1661 () -> { 1662 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1663 1664 // 2. The schema for both PKG_A and PKG_B should be retrievable. 1665 GetSchemaResponse response = 1666 mGlobalSearchSession.getSchemaAsync(PKG_A, DB_NAME).get(); 1667 assertThat(response.getSchemas()).hasSize(1); 1668 1669 response = mGlobalSearchSession.getSchemaAsync(PKG_B, DB_NAME).get(); 1670 assertThat(response.getSchemas()).hasSize(1); 1671 }, 1672 READ_GLOBAL_APP_SEARCH_DATA); 1673 } 1674 1675 @Test 1676 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testGlobalOpenBlobRead_visibleToGlobalReader()1677 public void testGlobalOpenBlobRead_visibleToGlobalReader() throws Exception { 1678 byte[] data = generateRandomBytes(10); // 10 Bytes 1679 byte[] digest = calculateDigest(data); 1680 AppSearchBlobHandle handle = AppSearchBlobHandle.createWithSha256( 1681 digest, PKG_A, DB_NAME, NAMESPACE_NAME); 1682 1683 try { 1684 // Set blob is visible to nothing but global reader. 1685 writeGloballySearchableBlobVisibleToConfig(PKG_A, DB_NAME, NAMESPACE_NAME, data, 1686 ImmutableSet.of(), 1687 ImmutableSet.of(), 1688 /*publicAclPackage=*/null); 1689 1690 // READ_GLOBAL_APP_SEARCH_DATA could read 1691 SystemUtil.runWithShellPermissionIdentity( 1692 () -> { 1693 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1694 1695 try (OpenBlobForReadResponse readResponse = mGlobalSearchSession 1696 .openBlobForReadAsync(ImmutableSet.of(handle)).get()) { 1697 AppSearchBatchResult<AppSearchBlobHandle, ParcelFileDescriptor> 1698 readResult = readResponse.getResult(); 1699 assertTrue(readResult.isSuccess()); 1700 1701 byte[] readBytes = new byte[10]; // 10 Bytes 1702 ParcelFileDescriptor readPfd = readResult.getSuccesses().get(handle); 1703 try (InputStream inputStream = 1704 new ParcelFileDescriptor.AutoCloseInputStream(readPfd)) { 1705 inputStream.read(readBytes); 1706 } 1707 assertThat(readBytes).isEqualTo(data); 1708 } 1709 }, 1710 READ_GLOBAL_APP_SEARCH_DATA); 1711 1712 // without READ_GLOBAL_APP_SEARCH_DATA couldn't read 1713 SystemUtil.runWithShellPermissionIdentity( 1714 () -> { 1715 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1716 1717 try (OpenBlobForReadResponse readResponse = mGlobalSearchSession 1718 .openBlobForReadAsync(ImmutableSet.of(handle)).get()) { 1719 AppSearchBatchResult<AppSearchBlobHandle, ParcelFileDescriptor> 1720 readResult = readResponse.getResult(); 1721 assertFalse(readResult.isSuccess()); 1722 1723 assertThat(readResult.getFailures().keySet()).containsExactly(handle); 1724 assertThat(readResult.getFailures().get(handle).getResultCode()) 1725 .isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 1726 assertThat(readResult.getFailures().get(handle).getErrorMessage()) 1727 .contains("Cannot find the blob for handle"); 1728 } 1729 }); 1730 } finally { 1731 removeBlob(PKG_A, DB_NAME, NAMESPACE_NAME, data); 1732 } 1733 } 1734 1735 @Test 1736 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testGlobalOpenBlobRead_notVisibleToGlobalReader()1737 public void testGlobalOpenBlobRead_notVisibleToGlobalReader() throws Exception { 1738 byte[] data = generateRandomBytes(10); // 10 Bytes 1739 byte[] digest = calculateDigest(data); 1740 AppSearchBlobHandle handle = AppSearchBlobHandle.createWithSha256( 1741 digest, PKG_A, DB_NAME, NAMESPACE_NAME); 1742 try { 1743 writeGloballyNotSearchableBlob(PKG_A, DB_NAME, NAMESPACE_NAME, data); 1744 1745 // READ_GLOBAL_APP_SEARCH_DATA couldn't read 1746 SystemUtil.runWithShellPermissionIdentity( 1747 () -> { 1748 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1749 1750 try (OpenBlobForReadResponse readResponse = mGlobalSearchSession 1751 .openBlobForReadAsync(ImmutableSet.of(handle)).get()) { 1752 AppSearchBatchResult<AppSearchBlobHandle, ParcelFileDescriptor> 1753 readResult = readResponse.getResult(); 1754 assertFalse(readResult.isSuccess()); 1755 1756 assertThat(readResult.getFailures().keySet()).containsExactly(handle); 1757 assertThat(readResult.getFailures().get(handle).getResultCode()) 1758 .isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 1759 assertThat(readResult.getFailures().get(handle).getErrorMessage()) 1760 .contains("Cannot find the blob for handle"); 1761 } 1762 }); 1763 } finally { 1764 removeBlob(PKG_A, DB_NAME, NAMESPACE_NAME, data); 1765 } 1766 } 1767 1768 @Test 1769 @RequiresFlagsEnabled(Flags.FLAG_ENABLE_BLOB_STORE) testGlobalOpenBlobRead_visibleToConfig()1770 public void testGlobalOpenBlobRead_visibleToConfig() throws Exception { 1771 byte[] data = generateRandomBytes(10); // 10 Bytes 1772 byte[] digest = calculateDigest(data); 1773 AppSearchBlobHandle handle = AppSearchBlobHandle.createWithSha256( 1774 digest, PKG_A, DB_NAME, NAMESPACE_NAME); 1775 1776 String ctsPackageName = mContext.getPackageName(); 1777 PackageIdentifier ctsPackage = 1778 new PackageIdentifier( 1779 ctsPackageName, 1780 PackageUtil.getSelfPackageSha256Cert(mContext) 1781 ); 1782 try { 1783 // set it is visible to the config that the caller must be cts test package AND hold 1784 // READ_SMS. 1785 writeGloballySearchableBlobVisibleToConfig(PKG_A, DB_NAME, NAMESPACE_NAME, data, 1786 ImmutableSet.of(ctsPackage), 1787 ImmutableSet.of(ImmutableSet.of(SetSchemaRequest.READ_SMS)), 1788 /*publicAclPackage=*/null); 1789 1790 // cts test package AND READ_SMS could read 1791 SystemUtil.runWithShellPermissionIdentity( 1792 () -> { 1793 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1794 1795 try (OpenBlobForReadResponse readResponse = mGlobalSearchSession 1796 .openBlobForReadAsync(ImmutableSet.of(handle)).get()) { 1797 AppSearchBatchResult<AppSearchBlobHandle, ParcelFileDescriptor> 1798 readResult = readResponse.getResult(); 1799 assertTrue(readResult.isSuccess()); 1800 1801 byte[] readBytes = new byte[10]; // 10 Bytes 1802 ParcelFileDescriptor readPfd = readResult.getSuccesses().get(handle); 1803 try (InputStream inputStream = 1804 new ParcelFileDescriptor.AutoCloseInputStream(readPfd)) { 1805 inputStream.read(readBytes); 1806 } 1807 assertThat(readBytes).isEqualTo(data); 1808 } 1809 }, 1810 READ_SMS); 1811 1812 // cts test package AND READ_CALENDAR couldn't read 1813 SystemUtil.runWithShellPermissionIdentity( 1814 () -> { 1815 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1816 1817 try (OpenBlobForReadResponse readResponse = mGlobalSearchSession 1818 .openBlobForReadAsync(ImmutableSet.of(handle)).get()) { 1819 AppSearchBatchResult<AppSearchBlobHandle, ParcelFileDescriptor> 1820 readResult = readResponse.getResult(); 1821 assertFalse(readResult.isSuccess()); 1822 1823 assertThat(readResult.getFailures().keySet()).containsExactly(handle); 1824 assertThat(readResult.getFailures().get(handle).getResultCode()) 1825 .isEqualTo(AppSearchResult.RESULT_NOT_FOUND); 1826 assertThat(readResult.getFailures().get(handle).getErrorMessage()) 1827 .contains("Cannot find the blob for handle"); 1828 } 1829 }, 1830 READ_CALENDAR); 1831 } finally { 1832 removeBlob(PKG_A, DB_NAME, NAMESPACE_NAME, data); 1833 } 1834 } 1835 1836 @Test testReportSystemUsage()1837 public void testReportSystemUsage() throws Exception { 1838 // Insert schema 1839 mDb.setSchemaAsync(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build()) 1840 .get(); 1841 1842 // Insert two docs 1843 GenericDocument document1 = 1844 new GenericDocument.Builder<>(NAMESPACE_NAME, "id1", AppSearchEmail.SCHEMA_TYPE) 1845 .build(); 1846 GenericDocument document2 = 1847 new GenericDocument.Builder<>(NAMESPACE_NAME, "id2", AppSearchEmail.SCHEMA_TYPE) 1848 .build(); 1849 mDb.putAsync( 1850 new PutDocumentsRequest.Builder().addGenericDocuments(document1, document2).build()) 1851 .get(); 1852 1853 // Report some usages. id1 has 2 app and 1 system usage, id2 has 1 app and 2 system usage. 1854 mDb.reportUsageAsync( 1855 new ReportUsageRequest.Builder(NAMESPACE_NAME, "id1") 1856 .setUsageTimestampMillis(10) 1857 .build()) 1858 .get(); 1859 mDb.reportUsageAsync( 1860 new ReportUsageRequest.Builder(NAMESPACE_NAME, "id1") 1861 .setUsageTimestampMillis(20) 1862 .build()) 1863 .get(); 1864 mDb.reportUsageAsync( 1865 new ReportUsageRequest.Builder(NAMESPACE_NAME, "id2") 1866 .setUsageTimestampMillis(100) 1867 .build()) 1868 .get(); 1869 1870 SystemUtil.runWithShellPermissionIdentity(() -> { 1871 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1872 mGlobalSearchSession 1873 .reportSystemUsageAsync( 1874 new ReportSystemUsageRequest.Builder( 1875 mContext.getPackageName(), DB_NAME, NAMESPACE_NAME, "id1") 1876 .setUsageTimestampMillis(1000) 1877 .build()) 1878 .get(); 1879 mGlobalSearchSession 1880 .reportSystemUsageAsync( 1881 new ReportSystemUsageRequest.Builder( 1882 mContext.getPackageName(), DB_NAME, NAMESPACE_NAME, "id2") 1883 .setUsageTimestampMillis(200) 1884 .build()) 1885 .get(); 1886 mGlobalSearchSession 1887 .reportSystemUsageAsync( 1888 new ReportSystemUsageRequest.Builder( 1889 mContext.getPackageName(), DB_NAME, NAMESPACE_NAME, "id2") 1890 .setUsageTimestampMillis(150) 1891 .build()) 1892 .get(); 1893 }, READ_GLOBAL_APP_SEARCH_DATA); 1894 1895 // Query the data 1896 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1897 1898 // Sort by app usage count: id1 should win 1899 try (SearchResultsShim results = mDb.search( 1900 "", 1901 new SearchSpec.Builder() 1902 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1903 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_COUNT) 1904 .build())) { 1905 List<SearchResult> page = results.getNextPageAsync().get(); 1906 assertThat(page).hasSize(2); 1907 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id1"); 1908 assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id2"); 1909 } 1910 1911 // Sort by app usage timestamp: id2 should win 1912 try (SearchResultsShim results = mDb.search( 1913 "", 1914 new SearchSpec.Builder() 1915 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1916 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP) 1917 .build())) { 1918 List<SearchResult> page = results.getNextPageAsync().get(); 1919 assertThat(page).hasSize(2); 1920 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id2"); 1921 assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id1"); 1922 } 1923 1924 // Sort by system usage count: id2 should win 1925 try (SearchResultsShim results = mDb.search( 1926 "", 1927 new SearchSpec.Builder() 1928 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1929 .setRankingStrategy( 1930 SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_COUNT) 1931 .build())) { 1932 List<SearchResult> page = results.getNextPageAsync().get(); 1933 assertThat(page).hasSize(2); 1934 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id2"); 1935 assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id1"); 1936 } 1937 1938 // Sort by system usage timestamp: id1 should win 1939 SearchSpec searchSpec = new SearchSpec.Builder() 1940 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) 1941 .setRankingStrategy(SearchSpec.RANKING_STRATEGY_SYSTEM_USAGE_LAST_USED_TIMESTAMP) 1942 .build(); 1943 try (SearchResultsShim results = mDb.search("", searchSpec)) { 1944 List<SearchResult> page = results.getNextPageAsync().get(); 1945 assertThat(page).hasSize(2); 1946 assertThat(page.get(0).getGenericDocument().getId()).isEqualTo("id1"); 1947 assertThat(page.get(1).getGenericDocument().getId()).isEqualTo("id2"); 1948 } 1949 } 1950 1951 @Test testRemoveObserver_otherPackagesNotRemoved()1952 public void testRemoveObserver_otherPackagesNotRemoved() throws Exception { 1953 final String fakePackage = "com.android.appsearch.fake.package"; 1954 TestObserverCallback observer = new TestObserverCallback(); 1955 1956 // Set up schema 1957 mGlobalSearchSession = createGlobalSearchSessionAsync(mContext); 1958 mDb.setSchemaAsync(new SetSchemaRequest.Builder() 1959 .addSchemas(AppSearchEmail.SCHEMA).build()).get(); 1960 1961 // Register this observer twice, on different packages. 1962 Executor executor = MoreExecutors.directExecutor(); 1963 mGlobalSearchSession.registerObserverCallback( 1964 mContext.getPackageName(), 1965 new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(), 1966 executor, 1967 observer); 1968 mGlobalSearchSession.registerObserverCallback( 1969 /*targetPackageName=*/fakePackage, 1970 new ObserverSpec.Builder().addFilterSchemas("Gift").build(), 1971 executor, 1972 observer); 1973 1974 // Make sure everything is empty 1975 assertThat(observer.getSchemaChanges()).isEmpty(); 1976 assertThat(observer.getDocumentChanges()).isEmpty(); 1977 1978 // Index some documents 1979 AppSearchEmail email1 = new AppSearchEmail.Builder(NAMESPACE_NAME, "id1").build(); 1980 AppSearchEmail email2 = 1981 new AppSearchEmail.Builder(NAMESPACE_NAME, "id2").setBody("caterpillar").build(); 1982 AppSearchEmail email3 = 1983 new AppSearchEmail.Builder(NAMESPACE_NAME, "id3").setBody("foo").build(); 1984 1985 checkIsBatchResultSuccess( 1986 mDb.putAsync(new PutDocumentsRequest.Builder() 1987 .addGenericDocuments(email1).build())); 1988 1989 // Make sure the notifications were received. 1990 observer.waitForNotificationCount(1); 1991 1992 assertThat(observer.getSchemaChanges()).isEmpty(); 1993 assertThat(observer.getDocumentChanges()).containsExactly( 1994 new DocumentChangeInfo( 1995 mContext.getPackageName(), 1996 DB_NAME, 1997 NAMESPACE_NAME, 1998 AppSearchEmail.SCHEMA_TYPE, 1999 ImmutableSet.of("id1"))); 2000 observer.clear(); 2001 2002 // Unregister observer from com.example.package 2003 mGlobalSearchSession.unregisterObserverCallback("com.example.package", observer); 2004 2005 // Index some more documents 2006 assertThat(observer.getDocumentChanges()).isEmpty(); 2007 checkIsBatchResultSuccess( 2008 mDb.putAsync( 2009 new PutDocumentsRequest.Builder().addGenericDocuments(email2).build())); 2010 2011 // Make sure data was still received 2012 observer.waitForNotificationCount(1); 2013 assertThat(observer.getDocumentChanges()).containsExactly( 2014 new DocumentChangeInfo( 2015 mContext.getPackageName(), 2016 DB_NAME, 2017 NAMESPACE_NAME, 2018 AppSearchEmail.SCHEMA_TYPE, 2019 ImmutableSet.of("id2"))); 2020 observer.clear(); 2021 2022 // Unregister the final observer 2023 mGlobalSearchSession.unregisterObserverCallback(mContext.getPackageName(), observer); 2024 2025 // Index some more documents 2026 assertThat(observer.getDocumentChanges()).isEmpty(); 2027 checkIsBatchResultSuccess( 2028 mDb.putAsync( 2029 new PutDocumentsRequest.Builder().addGenericDocuments(email3).build())); 2030 2031 // Make sure there have been no further notifications 2032 assertThat(observer.getDocumentChanges()).isEmpty(); 2033 } 2034 getSchemaAsPackage(String pkg)2035 private List<String> getSchemaAsPackage(String pkg) throws Exception { 2036 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2037 bindToHelperService(pkg); 2038 try { 2039 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 2040 return commandReceiver.globalGetSchema(mContext.getPackageName(), DB_NAME); 2041 } finally { 2042 serviceConnection.unbind(); 2043 } 2044 } 2045 assertPackageCanAccess(List<GenericDocument> expectedDocuments, String pkg)2046 private void assertPackageCanAccess(List<GenericDocument> expectedDocuments, String pkg) 2047 throws Exception { 2048 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2049 bindToHelperService(pkg); 2050 try { 2051 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 2052 List<String> results = commandReceiver.globalSearch(TEXT); 2053 assertThat(results).containsExactlyElementsIn( 2054 expectedDocuments.stream() 2055 .map(GenericDocument::toString) 2056 .collect(Collectors.toList())); 2057 } finally { 2058 serviceConnection.unbind(); 2059 } 2060 } 2061 assertPackageCannotAccess(String pkg)2062 private void assertPackageCannotAccess(String pkg) throws Exception { 2063 assertPackageCanAccess(Collections.emptyList(), pkg); 2064 } 2065 assertPackageCanAccess(GenericDocument expectedDocument, String pkg)2066 private void assertPackageCanAccess(GenericDocument expectedDocument, String pkg) 2067 throws Exception { 2068 assertPackageCanAccess(ImmutableList.of(expectedDocument), pkg); 2069 } 2070 indexGloballySearchableDocument(String pkg, String databaseName, String namespace, String id)2071 private void indexGloballySearchableDocument(String pkg, String databaseName, String namespace, 2072 String id) throws Exception { 2073 indexGloballySearchableDocument(pkg, databaseName, namespace, id, ImmutableSet.of()); 2074 } 2075 indexGloballySearchableDocument(String pkg, String databaseName, String namespace, String id, Set<Set<Integer>> visibleToPermissions)2076 private void indexGloballySearchableDocument(String pkg, String databaseName, String namespace, 2077 String id, Set<Set<Integer>> visibleToPermissions) throws Exception { 2078 // binder won't accept Set or Integer, we need to convert to List<Bundle>. 2079 List<Bundle> permissionBundles = new ArrayList<>(visibleToPermissions.size()); 2080 for (Set<Integer> allRequiredPermissions : visibleToPermissions) { 2081 Bundle permissionBundle = new Bundle(); 2082 permissionBundle.putIntegerArrayList("permission", 2083 new ArrayList<>(allRequiredPermissions)); 2084 permissionBundles.add(permissionBundle); 2085 } 2086 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2087 bindToHelperService(pkg); 2088 try { 2089 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 2090 assertThat(commandReceiver.indexGloballySearchableDocument( 2091 databaseName, namespace, id, permissionBundles)).isTrue(); 2092 } finally { 2093 serviceConnection.unbind(); 2094 } 2095 } 2096 indexGloballySearchableDocumentVisibleToConfig(String pkg, String databaseName, String namespace, String id, Set<PackageIdentifier> visibleToPackages, Set<Set<Integer>> visibleToPermissions, PackageIdentifier publicAclPackage)2097 private void indexGloballySearchableDocumentVisibleToConfig(String pkg, String databaseName, 2098 String namespace, String id, Set<PackageIdentifier> visibleToPackages, 2099 Set<Set<Integer>> visibleToPermissions, PackageIdentifier publicAclPackage) 2100 throws Exception { 2101 // PackageIdentifierParcel is hidden, we need to use bundle to pass PackageIdentifier. 2102 List<Bundle> packagesBundles = new ArrayList<>(visibleToPackages.size()); 2103 for (PackageIdentifier visibleToPackage: visibleToPackages) { 2104 Bundle packageBundle = new Bundle(); 2105 packageBundle.putString("packageName", visibleToPackage.getPackageName()); 2106 packageBundle.putByteArray("sha256Cert", visibleToPackage.getSha256Certificate()); 2107 packagesBundles.add(packageBundle); 2108 } 2109 2110 // binder won't accept Set or Integer, we need to convert to List<Bundle>. 2111 List<Bundle> permissionBundles = new ArrayList<>(visibleToPermissions.size()); 2112 for (Set<Integer> allRequiredPermissions : visibleToPermissions) { 2113 Bundle permissionBundle = new Bundle(); 2114 permissionBundle.putIntegerArrayList("permission", 2115 new ArrayList<>(allRequiredPermissions)); 2116 permissionBundles.add(permissionBundle); 2117 } 2118 2119 Bundle publicAclBundle = null; 2120 if (publicAclPackage != null) { 2121 publicAclBundle = new Bundle(); 2122 publicAclBundle.putString("packageName", publicAclPackage.getPackageName()); 2123 publicAclBundle.putByteArray("sha256Cert", publicAclPackage.getSha256Certificate()); 2124 } 2125 2126 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2127 bindToHelperService(pkg); 2128 try { 2129 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 2130 assertThat(commandReceiver.indexGloballySearchableDocumentVisibleToConfig(databaseName, 2131 namespace, id, packagesBundles, permissionBundles, publicAclBundle)).isTrue(); 2132 } finally { 2133 serviceConnection.unbind(); 2134 } 2135 } 2136 writeGloballySearchableBlobVisibleToConfig(String pkg, String databaseName, String namespace, byte[] data, Set<PackageIdentifier> visibleToPackages, Set<Set<Integer>> visibleToPermissions, PackageIdentifier publicAclPackage)2137 private void writeGloballySearchableBlobVisibleToConfig(String pkg, String databaseName, 2138 String namespace, byte[] data, Set<PackageIdentifier> visibleToPackages, 2139 Set<Set<Integer>> visibleToPermissions, PackageIdentifier publicAclPackage) 2140 throws Exception { 2141 // PackageIdentifierParcel is hidden, we need to use bundle to pass PackageIdentifier. 2142 List<Bundle> packagesBundles = new ArrayList<>(visibleToPackages.size()); 2143 for (PackageIdentifier visibleToPackage: visibleToPackages) { 2144 Bundle packageBundle = new Bundle(); 2145 packageBundle.putString("packageName", visibleToPackage.getPackageName()); 2146 packageBundle.putByteArray("sha256Cert", visibleToPackage.getSha256Certificate()); 2147 packagesBundles.add(packageBundle); 2148 } 2149 2150 // binder won't accept Set or Integer, we need to convert to List<Bundle>. 2151 List<Bundle> permissionBundles = new ArrayList<>(visibleToPermissions.size()); 2152 for (Set<Integer> allRequiredPermissions : visibleToPermissions) { 2153 Bundle permissionBundle = new Bundle(); 2154 permissionBundle.putIntegerArrayList("permission", 2155 new ArrayList<>(allRequiredPermissions)); 2156 permissionBundles.add(permissionBundle); 2157 } 2158 2159 Bundle publicAclBundle = null; 2160 if (publicAclPackage != null) { 2161 publicAclBundle = new Bundle(); 2162 publicAclBundle.putString("packageName", publicAclPackage.getPackageName()); 2163 publicAclBundle.putByteArray("sha256Cert", publicAclPackage.getSha256Certificate()); 2164 } 2165 2166 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2167 bindToHelperService(pkg); 2168 try { 2169 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 2170 assertThat(commandReceiver.writeGloballySearchableBlobVisibleToConfig(pkg, databaseName, 2171 namespace, data, packagesBundles, permissionBundles, publicAclBundle)).isTrue(); 2172 } finally { 2173 serviceConnection.unbind(); 2174 } 2175 } 2176 writeGloballyNotSearchableBlob(String pkg, String databaseName, String namespace, byte[] data)2177 private void writeGloballyNotSearchableBlob(String pkg, String databaseName, 2178 String namespace, byte[] data) 2179 throws Exception { 2180 2181 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2182 bindToHelperService(pkg); 2183 try { 2184 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 2185 assertThat(commandReceiver.writeGloballyNotSearchableBlob(pkg, databaseName, 2186 namespace, data)).isTrue(); 2187 } finally { 2188 serviceConnection.unbind(); 2189 } 2190 } 2191 removeBlob(String pkg, String databaseName, String namespace, byte[] data)2192 private void removeBlob(String pkg, String databaseName, String namespace, byte[] data) 2193 throws Exception { 2194 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2195 bindToHelperService(pkg); 2196 try { 2197 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 2198 assertThat(commandReceiver.removeBlob(pkg, databaseName, namespace, data)).isTrue(); 2199 } finally { 2200 serviceConnection.unbind(); 2201 } 2202 } 2203 indexNotGloballySearchableDocument( String pkg, String databaseName, String namespace, String id)2204 private void indexNotGloballySearchableDocument( 2205 String pkg, String databaseName, String namespace, String id) throws Exception { 2206 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2207 bindToHelperService(pkg); 2208 try { 2209 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 2210 assertThat(commandReceiver 2211 .indexNotGloballySearchableDocument(databaseName, namespace, id)).isTrue(); 2212 } finally { 2213 serviceConnection.unbind(); 2214 } 2215 } 2216 indexActionDocument( String pkg, String databaseName, String namespace, String id, String entityId, boolean globallySearchable)2217 private void indexActionDocument( 2218 String pkg, String databaseName, String namespace, String id, String entityId, 2219 boolean globallySearchable) 2220 throws Exception { 2221 2222 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2223 bindToHelperService(pkg); 2224 try { 2225 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 2226 assertThat(commandReceiver 2227 .indexAction(databaseName, namespace, id, entityId, globallySearchable)) 2228 .isTrue(); 2229 } finally { 2230 serviceConnection.unbind(); 2231 } 2232 } 2233 clearData(String pkg, String databaseName)2234 private void clearData(String pkg, String databaseName) throws Exception { 2235 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2236 bindToHelperService(pkg); 2237 try { 2238 ICommandReceiver commandReceiver = serviceConnection.getCommandReceiver(); 2239 assertThat(commandReceiver.clearData(databaseName)).isTrue(); 2240 } finally { 2241 serviceConnection.unbind(); 2242 } 2243 } 2244 bindToHelperService( String pkg)2245 private GlobalSearchSessionServiceCtsTestBase.TestServiceConnection bindToHelperService( 2246 String pkg) { 2247 GlobalSearchSessionServiceCtsTestBase.TestServiceConnection serviceConnection = 2248 new GlobalSearchSessionServiceCtsTestBase.TestServiceConnection(mContext); 2249 Intent intent = new Intent().setComponent(new ComponentName(pkg, HELPER_SERVICE)); 2250 mContext.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE); 2251 return serviceConnection; 2252 } 2253 2254 private static class TestServiceConnection implements ServiceConnection { 2255 private final Context mContext; 2256 private final BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>(); 2257 private ICommandReceiver mCommandReceiver; 2258 TestServiceConnection(Context context)2259 TestServiceConnection(Context context) { 2260 mContext = context; 2261 } 2262 2263 @Override onServiceConnected(ComponentName componentName, IBinder service)2264 public void onServiceConnected(ComponentName componentName, IBinder service) { 2265 Log.i(TAG, "Service got connected: " + componentName); 2266 mBlockingQueue.offer(service); 2267 } 2268 2269 @Override onServiceDisconnected(ComponentName componentName)2270 public void onServiceDisconnected(ComponentName componentName) { 2271 Log.e(TAG, "Service got disconnected: " + componentName); 2272 } 2273 getService()2274 private IBinder getService() throws Exception { 2275 return mBlockingQueue.poll(TIMEOUT_BIND_SERVICE_SEC, TimeUnit.SECONDS); 2276 } 2277 getCommandReceiver()2278 public ICommandReceiver getCommandReceiver() throws Exception { 2279 if (mCommandReceiver == null) { 2280 mCommandReceiver = ICommandReceiver.Stub.asInterface(getService()); 2281 } 2282 if (mCommandReceiver == null) { 2283 Log.e(TAG, "Cannot bind to a service in " + TIMEOUT_BIND_SERVICE_SEC + " second."); 2284 } 2285 return mCommandReceiver; 2286 } 2287 unbind()2288 public void unbind() { 2289 mCommandReceiver = null; 2290 Log.i(TAG, "Service got unbinded."); 2291 mContext.unbindService(this); 2292 } 2293 } 2294 } 2295