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 17 package android.app; 18 19 import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID; 20 import static android.app.PropertyInvalidatedCache.NONCE_UNSET; 21 import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH; 22 import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM; 23 import static android.app.PropertyInvalidatedCache.MODULE_TEST; 24 import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDEX; 25 import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED; 26 27 import static org.junit.Assert.assertEquals; 28 import static org.junit.Assert.assertFalse; 29 import static org.junit.Assert.assertNotEquals; 30 import static org.junit.Assert.assertNotSame; 31 import static org.junit.Assert.assertSame; 32 import static org.junit.Assert.assertTrue; 33 import static org.junit.Assert.fail; 34 35 import android.annotation.SuppressLint; 36 import android.app.PropertyInvalidatedCache.Args; 37 import android.app.PropertyInvalidatedCache.NonceWatcher; 38 import android.app.PropertyInvalidatedCache.NonceStore; 39 import android.os.Binder; 40 import android.util.Log; 41 import com.android.internal.os.ApplicationSharedMemory; 42 43 import android.platform.test.annotations.DisabledOnRavenwood; 44 import android.platform.test.annotations.RequiresFlagsEnabled; 45 import android.platform.test.flag.junit.CheckFlagsRule; 46 import android.platform.test.flag.junit.DeviceFlagsValueProvider; 47 import android.platform.test.ravenwood.RavenwoodRule; 48 49 import androidx.test.filters.SmallTest; 50 51 import com.android.internal.os.ApplicationSharedMemory; 52 53 import org.junit.After; 54 import org.junit.Before; 55 import org.junit.Rule; 56 import org.junit.Test; 57 58 import java.util.concurrent.TimeUnit; 59 60 /** 61 * Test for verifying the behavior of {@link PropertyInvalidatedCache}. This test does 62 * not use any actual binder calls - it is entirely self-contained. This test also relies 63 * on the test mode of {@link PropertyInvalidatedCache} because Android SELinux rules do 64 * not grant test processes the permission to set system properties. 65 * <p> 66 * Build/Install/Run: 67 * atest FrameworksCoreTests:PropertyInvalidatedCacheTests 68 */ 69 @SmallTest 70 public class PropertyInvalidatedCacheTests { 71 @Rule 72 public final CheckFlagsRule mCheckFlagsRule = 73 DeviceFlagsValueProvider.createCheckFlagsRule(); 74 75 // Configuration for creating caches 76 private static final String MODULE = MODULE_TEST; 77 private static final String API = "testApi"; 78 79 // This class is a proxy for binder calls. It contains a counter that increments 80 // every time the class is queried. 81 private static class ServerProxy { 82 // The number of times this class was queried. 83 private int mCount = 0; 84 85 // A single query. The key behavior is that the query count is incremented. query(int x)86 boolean query(int x) { 87 mCount++; 88 return value(x); 89 } 90 91 // Return the expected value of an input, without incrementing the query count. value(int x)92 boolean value(int x) { 93 return x % 3 == 0; 94 } 95 96 // Verify the count. verify(int x)97 void verify(int x) { 98 assertEquals(x, mCount); 99 } 100 } 101 102 // The functions for querying the server. 103 private static class ServerQuery 104 extends PropertyInvalidatedCache.QueryHandler<Integer, Boolean> { 105 private final ServerProxy mServer; 106 ServerQuery(ServerProxy server)107 ServerQuery(ServerProxy server) { 108 mServer = server; 109 } 110 111 @Override apply(Integer x)112 public Boolean apply(Integer x) { 113 return mServer.query(x); 114 } 115 116 @Override shouldBypassCache(Integer x)117 public boolean shouldBypassCache(Integer x) { 118 return x % 13 == 0; 119 } 120 } 121 122 // Prepare for testing. 123 @Before setUp()124 public void setUp() throws Exception { 125 PropertyInvalidatedCache.setTestMode(true); 126 } 127 128 // Ensure all test configurations are cleared. 129 @After tearDown()130 public void tearDown() throws Exception { 131 PropertyInvalidatedCache.setTestMode(false); 132 } 133 134 // This test is disabled pending an sepolicy change that allows any app to set the 135 // test property. 136 @Test testBasicCache()137 public void testBasicCache() { 138 139 // A stand-in for the binder. The test verifies that calls are passed through to 140 // this class properly. 141 ServerProxy tester = new ServerProxy(); 142 143 // Create a cache that uses simple arithmetic to computer its values. 144 PropertyInvalidatedCache<Integer, Boolean> testCache = 145 new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", 146 new ServerQuery(tester)); 147 148 tester.verify(0); 149 assertEquals(tester.value(3), testCache.query(3)); 150 tester.verify(1); 151 assertEquals(tester.value(3), testCache.query(3)); 152 tester.verify(2); 153 testCache.invalidateCache(); 154 assertEquals(tester.value(3), testCache.query(3)); 155 tester.verify(3); 156 assertEquals(tester.value(5), testCache.query(5)); 157 tester.verify(4); 158 assertEquals(tester.value(5), testCache.query(5)); 159 tester.verify(4); 160 assertEquals(tester.value(3), testCache.query(3)); 161 tester.verify(4); 162 163 // Invalidate the cache, and verify that the next read on 3 goes to the server. 164 testCache.invalidateCache(); 165 assertEquals(tester.value(3), testCache.query(3)); 166 tester.verify(5); 167 168 // Test bypass. The query for 13 always bypasses the cache. 169 assertEquals(tester.value(12), testCache.query(12)); 170 assertEquals(tester.value(13), testCache.query(13)); 171 assertEquals(tester.value(14), testCache.query(14)); 172 tester.verify(8); 173 assertEquals(tester.value(12), testCache.query(12)); 174 assertEquals(tester.value(13), testCache.query(13)); 175 assertEquals(tester.value(14), testCache.query(14)); 176 tester.verify(9); 177 } 178 179 @Test testDisableCache()180 public void testDisableCache() { 181 182 // A stand-in for the binder. The test verifies that calls are passed through to 183 // this class properly. 184 ServerProxy tester = new ServerProxy(); 185 186 // Three caches, all using the same system property but one uses a different name. 187 PropertyInvalidatedCache<Integer, Boolean> cache1 = 188 new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", 189 new ServerQuery(tester)); 190 PropertyInvalidatedCache<Integer, Boolean> cache2 = 191 new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", 192 new ServerQuery(tester)); 193 PropertyInvalidatedCache<Integer, Boolean> cache3 = 194 new PropertyInvalidatedCache<>(4, MODULE, API, "cache3", 195 new ServerQuery(tester)); 196 197 // Caches are enabled upon creation. 198 assertFalse(cache1.isDisabled()); 199 assertFalse(cache2.isDisabled()); 200 assertFalse(cache3.isDisabled()); 201 202 // Disable the cache1 instance. Only cache1 is disabled 203 cache1.disableInstance(); 204 assertTrue(cache1.isDisabled()); 205 assertFalse(cache2.isDisabled()); 206 assertFalse(cache3.isDisabled()); 207 208 // Disable cache1. This will disable cache1 and cache2 because they share the 209 // same name. cache3 has a different name and will not be disabled. 210 cache1.disableLocal(); 211 assertTrue(cache1.isDisabled()); 212 assertTrue(cache2.isDisabled()); 213 assertFalse(cache3.isDisabled()); 214 215 // Create a new cache1. Verify that the new instance is disabled. 216 cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", 217 new ServerQuery(tester)); 218 assertTrue(cache1.isDisabled()); 219 220 // Remove the record of caches being locally disabled. This is a clean-up step. 221 cache1.forgetDisableLocal(); 222 assertTrue(cache1.isDisabled()); 223 assertTrue(cache2.isDisabled()); 224 assertFalse(cache3.isDisabled()); 225 226 // Create a new cache1. Verify that the new instance is not disabled. 227 cache1 = new PropertyInvalidatedCache<>(4, MODULE, API, "cache1", 228 new ServerQuery(tester)); 229 assertFalse(cache1.isDisabled()); 230 } 231 232 private static class TestQuery 233 extends PropertyInvalidatedCache.QueryHandler<Integer, String> { 234 235 private int mRecomputeCount = 0; 236 237 @Override apply(Integer qv)238 public String apply(Integer qv) { 239 mRecomputeCount += 1; 240 // Special case for testing caches of nulls. Integers in the range 30-40 return null. 241 if (qv >= 30 && qv < 40) { 242 return null; 243 } else { 244 return "foo" + qv.toString(); 245 } 246 } 247 getRecomputeCount()248 int getRecomputeCount() { 249 return mRecomputeCount; 250 } 251 } 252 253 private static class TestCache extends PropertyInvalidatedCache<Integer, String> { 254 private final TestQuery mQuery; 255 TestCache()256 TestCache() { 257 this(MODULE, API); 258 } 259 TestCache(String module, String api)260 TestCache(String module, String api) { 261 this(module, api, new TestQuery()); 262 } 263 TestCache(String module, String api, TestQuery query)264 TestCache(String module, String api, TestQuery query) { 265 super(4, module, api, api, query); 266 mQuery = query; 267 } 268 269 // Create a cache from the args. The name of the cache is the api. TestCache(Args args, TestQuery query)270 TestCache(Args args, TestQuery query) { 271 super(args, args.mApi(), query); 272 mQuery = query; 273 } 274 getRecomputeCount()275 public int getRecomputeCount() { 276 return mQuery.getRecomputeCount(); 277 } 278 } 279 280 @Test testCacheRecompute()281 public void testCacheRecompute() { 282 TestCache cache = new TestCache(); 283 cache.invalidateCache(); 284 assertFalse(cache.isDisabled()); 285 assertEquals("foo5", cache.query(5)); 286 assertEquals(1, cache.getRecomputeCount()); 287 assertEquals("foo5", cache.query(5)); 288 assertEquals(1, cache.getRecomputeCount()); 289 assertEquals("foo6", cache.query(6)); 290 assertEquals(2, cache.getRecomputeCount()); 291 cache.invalidateCache(); 292 assertEquals("foo5", cache.query(5)); 293 assertEquals("foo5", cache.query(5)); 294 assertEquals(3, cache.getRecomputeCount()); 295 // Invalidate the cache with a direct call to the property. 296 PropertyInvalidatedCache.invalidateCache(MODULE, API); 297 assertEquals("foo5", cache.query(5)); 298 assertEquals("foo5", cache.query(5)); 299 assertEquals(4, cache.getRecomputeCount()); 300 } 301 302 @Test testCacheInitialState()303 public void testCacheInitialState() { 304 TestCache cache = new TestCache(); 305 assertEquals("foo5", cache.query(5)); 306 assertEquals("foo5", cache.query(5)); 307 assertEquals(2, cache.getRecomputeCount()); 308 cache.invalidateCache(); 309 assertEquals("foo5", cache.query(5)); 310 assertEquals("foo5", cache.query(5)); 311 assertEquals(3, cache.getRecomputeCount()); 312 } 313 314 @Test testCachePropertyUnset()315 public void testCachePropertyUnset() { 316 final String UNSET_API = "otherApi"; 317 TestCache cache = new TestCache(MODULE, UNSET_API); 318 assertEquals("foo5", cache.query(5)); 319 assertEquals("foo5", cache.query(5)); 320 assertEquals(2, cache.getRecomputeCount()); 321 } 322 323 @Test testCacheDisableState()324 public void testCacheDisableState() { 325 TestCache cache = new TestCache(); 326 assertEquals("foo5", cache.query(5)); 327 assertEquals("foo5", cache.query(5)); 328 assertEquals(2, cache.getRecomputeCount()); 329 cache.invalidateCache(); 330 assertEquals("foo5", cache.query(5)); 331 assertEquals("foo5", cache.query(5)); 332 assertEquals(3, cache.getRecomputeCount()); 333 cache.disableSystemWide(); 334 assertEquals("foo5", cache.query(5)); 335 assertEquals("foo5", cache.query(5)); 336 assertEquals(5, cache.getRecomputeCount()); 337 cache.invalidateCache(); // Should not reenable 338 assertEquals("foo5", cache.query(5)); 339 assertEquals("foo5", cache.query(5)); 340 assertEquals(7, cache.getRecomputeCount()); 341 } 342 343 @Test testRefreshSameObject()344 public void testRefreshSameObject() { 345 int[] refreshCount = new int[1]; 346 TestCache cache = new TestCache() { 347 @Override 348 public String refresh(String oldResult, Integer query) { 349 refreshCount[0] += 1; 350 return oldResult; 351 } 352 }; 353 cache.invalidateCache(); 354 String result1 = cache.query(5); 355 assertEquals("foo5", result1); 356 String result2 = cache.query(5); 357 assertSame(result1, result2); 358 assertEquals(1, cache.getRecomputeCount()); 359 assertEquals(1, refreshCount[0]); 360 assertEquals("foo5", cache.query(5)); 361 assertEquals(2, refreshCount[0]); 362 } 363 364 @Test testRefreshInvalidateRace()365 public void testRefreshInvalidateRace() { 366 int[] refreshCount = new int[1]; 367 TestCache cache = new TestCache() { 368 @Override 369 public String refresh(String oldResult, Integer query) { 370 refreshCount[0] += 1; 371 invalidateCache(); 372 return new String(oldResult); 373 } 374 }; 375 cache.invalidateCache(); 376 String result1 = cache.query(5); 377 assertEquals("foo5", result1); 378 String result2 = cache.query(5); 379 assertEquals(result1, result2); 380 assertNotSame(result1, result2); 381 assertEquals(2, cache.getRecomputeCount()); 382 } 383 384 @Test testLocalProcessDisable()385 public void testLocalProcessDisable() { 386 TestCache cache = new TestCache(); 387 assertFalse(cache.isDisabled()); 388 cache.invalidateCache(); 389 assertEquals("foo5", cache.query(5)); 390 assertEquals(1, cache.getRecomputeCount()); 391 assertEquals("foo5", cache.query(5)); 392 assertEquals(1, cache.getRecomputeCount()); 393 assertFalse(cache.isDisabled()); 394 cache.disableLocal(); 395 assertTrue(cache.isDisabled()); 396 assertEquals("foo5", cache.query(5)); 397 assertEquals("foo5", cache.query(5)); 398 assertEquals(3, cache.getRecomputeCount()); 399 } 400 401 @Test testPropertyNames()402 public void testPropertyNames() { 403 String n1; 404 n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "getPackageInfo"); 405 assertEquals(n1, "cache_key.system_server.get_package_info"); 406 n1 = PropertyInvalidatedCache.createPropertyName(MODULE_SYSTEM, "get_package_info"); 407 assertEquals(n1, "cache_key.system_server.get_package_info"); 408 n1 = PropertyInvalidatedCache.createPropertyName(MODULE_BLUETOOTH, "getState"); 409 assertEquals(n1, "cache_key.bluetooth.get_state"); 410 } 411 412 // Verify that invalidating the cache from an app process would fail due to lack of permissions. 413 @Test 414 @DisabledOnRavenwood(reason = "SystemProperties doesn't have permission check") testPermissionFailure()415 public void testPermissionFailure() { 416 try { 417 // Disable the test mode for this test, but ensure that it will be enabled when the 418 // test exits. 419 PropertyInvalidatedCache.setTestMode(false); 420 // Create a cache that will write a system nonce. 421 TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1"); 422 try { 423 // Invalidate the cache, which writes the system property. There must be a 424 // permission failure. 425 sysCache.invalidateCache(); 426 fail("expected permission failure"); 427 } catch (RuntimeException e) { 428 // The expected exception is a bare RuntimeException. The test does not attempt 429 // to validate the text of the exception message. 430 } 431 } finally { 432 PropertyInvalidatedCache.setTestMode(true); 433 } 434 } 435 436 // Verify that test mode works properly. 437 @Test testTestMode()438 public void testTestMode() { 439 // Create a cache that will write a system nonce. 440 TestCache sysCache = new TestCache(MODULE_SYSTEM, "mode1"); 441 442 sysCache.testPropertyName(); 443 // Invalidate the cache. This must succeed because the property has been marked for 444 // testing. 445 sysCache.invalidateCache(); 446 447 // Create a cache that uses MODULE_TEST. Invalidation succeeds whether or not the 448 // property is tagged as being tested. 449 TestCache testCache = new TestCache(MODULE_TEST, "mode2"); 450 testCache.invalidateCache(); 451 testCache.testPropertyName(); 452 testCache.invalidateCache(); 453 454 // Clear test mode. This fails if test mode is not enabled. 455 PropertyInvalidatedCache.setTestMode(false); 456 try { 457 PropertyInvalidatedCache.setTestMode(false); 458 if (Flags.enforcePicTestmodeProtocol()) { 459 fail("expected an IllegalStateException"); 460 } 461 } catch (IllegalStateException e) { 462 // The expected exception. 463 } 464 // Configuring a property for testing must fail if test mode is false. 465 TestCache cache2 = new TestCache(MODULE_SYSTEM, "mode3"); 466 try { 467 cache2.testPropertyName(); 468 fail("expected an IllegalStateException"); 469 } catch (IllegalStateException e) { 470 // The expected exception. 471 } 472 473 // Re-enable test mode (so that the cleanup for the test does not throw). 474 PropertyInvalidatedCache.setTestMode(true); 475 } 476 477 // Test the Args-style constructor. 478 @Test testArgsConstructor()479 public void testArgsConstructor() { 480 // Create a cache with a maximum of four entries and non-isolated UIDs. 481 TestCache cache = new TestCache(new Args(MODULE_TEST) 482 .maxEntries(4).isolateUids(false).api("init1"), 483 new TestQuery()); 484 485 cache.invalidateCache(); 486 for (int i = 1; i <= 4; i++) { 487 assertEquals("foo" + i, cache.query(i)); 488 assertEquals(i, cache.getRecomputeCount()); 489 } 490 // Everything is in the cache. The recompute count must not increase. 491 for (int i = 1; i <= 4; i++) { 492 assertEquals("foo" + i, cache.query(i)); 493 assertEquals(4, cache.getRecomputeCount()); 494 } 495 // Overflow the max entries. The recompute count increases by one. 496 assertEquals("foo5", cache.query(5)); 497 assertEquals(5, cache.getRecomputeCount()); 498 // The oldest entry (1) has been evicted. Iterating through the first four entries will 499 // sequentially evict them all because the loop is proceeding oldest to newest. 500 for (int i = 1; i <= 4; i++) { 501 assertEquals("foo" + i, cache.query(i)); 502 assertEquals(5+i, cache.getRecomputeCount()); 503 } 504 } 505 506 // Verify that NonceWatcher change reporting works properly 507 @Test testNonceWatcherChanged()508 public void testNonceWatcherChanged() { 509 // Create a cache that will write a system nonce. 510 TestCache sysCache = new TestCache(MODULE_SYSTEM, "watcher1"); 511 sysCache.testPropertyName(); 512 513 try (NonceWatcher watcher1 = sysCache.getNonceWatcher()) { 514 515 // The property has never been invalidated so it is still unset. 516 assertFalse(watcher1.isChanged()); 517 518 // Invalidate the cache. The first call to isChanged will return true but the second 519 // call will return false; 520 sysCache.invalidateCache(); 521 assertTrue(watcher1.isChanged()); 522 assertFalse(watcher1.isChanged()); 523 524 // Invalidate the cache. The first call to isChanged will return true but the second 525 // call will return false; 526 sysCache.invalidateCache(); 527 sysCache.invalidateCache(); 528 assertTrue(watcher1.isChanged()); 529 assertFalse(watcher1.isChanged()); 530 531 NonceWatcher watcher2 = sysCache.getNonceWatcher(); 532 // This watcher return isChanged() immediately because the nonce is not UNSET. 533 assertTrue(watcher2.isChanged()); 534 } 535 } 536 537 // Verify that NonceWatcher wait-for-change works properly 538 @Test testNonceWatcherWait()539 public void testNonceWatcherWait() throws Exception { 540 // Create a cache that will write a system nonce. 541 TestCache sysCache = new TestCache(MODULE_TEST, "watcher1"); 542 543 // Use the watcher outside a try-with-resources block. 544 NonceWatcher watcher1 = sysCache.getNonceWatcher(); 545 546 // Invalidate the cache and then "wait". 547 sysCache.invalidateCache(); 548 assertEquals(watcher1.waitForChange(), 1); 549 550 // Invalidate the cache three times and then "wait". 551 sysCache.invalidateCache(); 552 sysCache.invalidateCache(); 553 sysCache.invalidateCache(); 554 assertEquals(watcher1.waitForChange(), 3); 555 556 // Wait for a change. It won't happen, but the code will time out after 10ms. 557 assertEquals(watcher1.waitForChange(10, TimeUnit.MILLISECONDS), 0); 558 559 watcher1.close(); 560 } 561 562 // Verify the behavior of shared memory nonce storage. This does not directly test the cache 563 // storing nonces in shared memory. 564 @RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED) 565 @Test 566 @DisabledOnRavenwood(reason = "PIC doesn't use SharedMemory on Ravenwood") testSharedMemoryStorage()567 public void testSharedMemoryStorage() { 568 // Fetch a shared memory instance for testing. 569 ApplicationSharedMemory shmem = ApplicationSharedMemory.create(); 570 571 // Create a server-side store and a client-side store. The server's store is mutable and 572 // the client's store is not mutable. 573 NonceStore server = new NonceStore(shmem.getSystemNonceBlock(), true); 574 NonceStore client = new NonceStore(shmem.getSystemNonceBlock(), false); 575 576 final String name1 = "name1"; 577 assertEquals(server.getHandleForName(name1), INVALID_NONCE_INDEX); 578 assertEquals(client.getHandleForName(name1), INVALID_NONCE_INDEX); 579 final int index1 = server.storeName(name1); 580 assertNotEquals(index1, INVALID_NONCE_INDEX); 581 assertEquals(server.getHandleForName(name1), index1); 582 assertEquals(client.getHandleForName(name1), index1); 583 assertEquals(server.storeName(name1), index1); 584 585 assertEquals(server.getNonce(index1), NONCE_UNSET); 586 assertEquals(client.getNonce(index1), NONCE_UNSET); 587 final int value1 = 4; 588 server.setNonce(index1, value1); 589 assertEquals(server.getNonce(index1), value1); 590 assertEquals(client.getNonce(index1), value1); 591 final int value2 = 8; 592 server.setNonce(index1, value2); 593 assertEquals(server.getNonce(index1), value2); 594 assertEquals(client.getNonce(index1), value2); 595 596 final String name2 = "name2"; 597 assertEquals(server.getHandleForName(name2), INVALID_NONCE_INDEX); 598 assertEquals(client.getHandleForName(name2), INVALID_NONCE_INDEX); 599 final int index2 = server.storeName(name2); 600 assertNotEquals(index2, INVALID_NONCE_INDEX); 601 assertEquals(server.getHandleForName(name2), index2); 602 assertEquals(client.getHandleForName(name2), index2); 603 assertEquals(server.storeName(name2), index2); 604 605 // The names are different, so the indices must be different. 606 assertNotEquals(index1, index2); 607 608 shmem.close(); 609 } 610 611 // Verify that the configured number of nonce slots is actually available. This test 612 // hard-codes the configured number of slots, which means that this test must be changed 613 // whenever the shared memory configuration changes. 614 @RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED) 615 @Test 616 @DisabledOnRavenwood(reason = "PIC doesn't use SharedMemory on Ravenwood") testSharedMemoryNonceConfig()617 public void testSharedMemoryNonceConfig() { 618 // The two configured constants. These are private to this method since they are only 619 // used here. 620 // LINT.IfChange(system_nonce_config) 621 final int maxNonce = 128; 622 final int maxByte = 8192; 623 // LINT.ThenChange(/core/jni/android_app_PropertyInvalidatedCache.h:system_nonce_config) 624 625 // Fetch a shared memory instance for testing. 626 ApplicationSharedMemory shmem = ApplicationSharedMemory.create(); 627 628 // Create a server-side store. 629 NonceStore server = new NonceStore(shmem.getSystemNonceBlock(), true); 630 631 // Verify that the configured limits are as expected. 632 assertEquals(server.mMaxNonce, maxNonce); 633 assertEquals(server.mMaxByte, maxByte); 634 635 // Create mMaxNonce nonces. These all succeed. 636 for (int i = 0; i < server.mMaxNonce; i++) { 637 String name = String.format("name_%03d", i); 638 assertEquals(i, server.storeName(name)); 639 } 640 641 // Verify that we cannot create a nonce over the limit. 642 try { 643 int i = server.mMaxNonce; 644 String name = String.format("name_%03d", i); 645 server.storeName(name); 646 fail("expected a RuntimeException"); 647 } catch (RuntimeException e) { 648 // Okay 649 } 650 651 shmem.close(); 652 } 653 654 // Verify that an invalid module causes an exception. testInvalidModule(String module)655 private void testInvalidModule(String module) { 656 try { 657 @SuppressLint("UnusedVariable") 658 Args arg = new Args(module); 659 fail("expected an invalid module exception: module=" + module); 660 } catch (IllegalArgumentException e) { 661 // Expected exception. 662 } 663 } 664 665 // Test various instantiation errors. The good path is tested in other methods. 666 @Test testArgumentErrors()667 public void testArgumentErrors() { 668 // Verify that an illegal module throws an exception. 669 testInvalidModule(MODULE_SYSTEM.substring(0, MODULE_SYSTEM.length() - 1)); 670 testInvalidModule(MODULE_SYSTEM + "x"); 671 testInvalidModule("mymodule"); 672 673 // Verify that a negative max entries throws. 674 Args arg = new Args(MODULE_SYSTEM); 675 try { 676 arg.maxEntries(0); 677 fail("expected an invalid maxEntries exception"); 678 } catch (IllegalArgumentException e) { 679 // Expected exception. 680 } 681 682 // Verify that creating a cache with an invalid property string throws. 683 try { 684 final String badKey = "cache_key.volume_list"; 685 @SuppressLint("UnusedVariable") 686 var cache = new PropertyInvalidatedCache<Integer, Void>(4, badKey); 687 fail("expected bad property exception: prop=" + badKey); 688 } catch (IllegalArgumentException e) { 689 // Expected exception. 690 } 691 } 692 693 // Verify that a cache created with isolatedUids(true) separates out the results. 694 @RequiresFlagsEnabled(FLAG_PIC_ISOLATE_CACHE_BY_UID) 695 @Test testIsolatedUids()696 public void testIsolatedUids() { 697 TestCache cache = new TestCache(new Args(MODULE_TEST) 698 .maxEntries(4).isolateUids(true).api("testIsolatedUids").testMode(true), 699 new TestQuery()); 700 cache.invalidateCache(); 701 final int uid1 = 1; 702 final int uid2 = 2; 703 704 long token = Binder.setCallingWorkSourceUid(uid1); 705 try { 706 // Populate the cache for user 1 707 assertEquals("foo5", cache.query(5)); 708 assertEquals(1, cache.getRecomputeCount()); 709 assertEquals("foo5", cache.query(5)); 710 assertEquals(1, cache.getRecomputeCount()); 711 assertEquals("foo6", cache.query(6)); 712 assertEquals(2, cache.getRecomputeCount()); 713 714 // Populate the cache for user 2. User 1 values are not reused. 715 Binder.setCallingWorkSourceUid(uid2); 716 assertEquals("foo5", cache.query(5)); 717 assertEquals(3, cache.getRecomputeCount()); 718 assertEquals("foo5", cache.query(5)); 719 assertEquals(3, cache.getRecomputeCount()); 720 721 // Verify that the cache for user 1 is still populated. 722 Binder.setCallingWorkSourceUid(uid1); 723 assertEquals("foo5", cache.query(5)); 724 assertEquals(3, cache.getRecomputeCount()); 725 726 } finally { 727 Binder.restoreCallingWorkSource(token); 728 } 729 730 // Repeat the test with a non-isolated cache. 731 cache = new TestCache(new Args(MODULE_TEST) 732 .maxEntries(4).isolateUids(false).api("testIsolatedUids2").testMode(true), 733 new TestQuery()); 734 cache.invalidateCache(); 735 token = Binder.setCallingWorkSourceUid(uid1); 736 try { 737 // Populate the cache for user 1 738 assertEquals("foo5", cache.query(5)); 739 assertEquals(1, cache.getRecomputeCount()); 740 assertEquals("foo5", cache.query(5)); 741 assertEquals(1, cache.getRecomputeCount()); 742 assertEquals("foo6", cache.query(6)); 743 assertEquals(2, cache.getRecomputeCount()); 744 745 // Populate the cache for user 2. User 1 values are reused. 746 Binder.setCallingWorkSourceUid(uid2); 747 assertEquals("foo5", cache.query(5)); 748 assertEquals(2, cache.getRecomputeCount()); 749 assertEquals("foo5", cache.query(5)); 750 assertEquals(2, cache.getRecomputeCount()); 751 752 // Verify that the cache for user 1 is still populated. 753 Binder.setCallingWorkSourceUid(uid1); 754 assertEquals("foo5", cache.query(5)); 755 assertEquals(2, cache.getRecomputeCount()); 756 757 } finally { 758 Binder.restoreCallingWorkSource(token); 759 } 760 } 761 762 @Test testCachingNulls()763 public void testCachingNulls() { 764 TestCache cache = new TestCache(new Args(MODULE_TEST) 765 .maxEntries(4).api("testCachingNulls").cacheNulls(true), 766 new TestQuery()); 767 cache.invalidateCache(); 768 assertEquals("foo1", cache.query(1)); 769 assertEquals("foo2", cache.query(2)); 770 assertEquals(null, cache.query(30)); 771 assertEquals(3, cache.getRecomputeCount()); 772 assertEquals("foo1", cache.query(1)); 773 assertEquals("foo2", cache.query(2)); 774 assertEquals(null, cache.query(30)); 775 assertEquals(3, cache.getRecomputeCount()); 776 777 cache = new TestCache(new Args(MODULE_TEST) 778 .maxEntries(4).api("testCachingNulls").cacheNulls(false), 779 new TestQuery()); 780 cache.invalidateCache(); 781 assertEquals("foo1", cache.query(1)); 782 assertEquals("foo2", cache.query(2)); 783 assertEquals(null, cache.query(30)); 784 assertEquals(3, cache.getRecomputeCount()); 785 assertEquals("foo1", cache.query(1)); 786 assertEquals("foo2", cache.query(2)); 787 assertEquals(null, cache.query(30)); 788 // The recompute is 4 because nulls were not cached. 789 assertEquals(4, cache.getRecomputeCount()); 790 791 // Verify that the default is not to cache nulls. 792 cache = new TestCache(new Args(MODULE_TEST) 793 .maxEntries(4).api("testCachingNulls"), 794 new TestQuery()); 795 cache.invalidateCache(); 796 assertEquals("foo1", cache.query(1)); 797 assertEquals("foo2", cache.query(2)); 798 assertEquals(null, cache.query(30)); 799 assertEquals(3, cache.getRecomputeCount()); 800 assertEquals("foo1", cache.query(1)); 801 assertEquals("foo2", cache.query(2)); 802 assertEquals(null, cache.query(30)); 803 // The recompute is 4 because nulls were not cached. 804 assertEquals(4, cache.getRecomputeCount()); 805 } 806 } 807