• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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