Merge "Put PIC nonces in shared memory" into main
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index c93a6dd..bc9e709 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -30,16 +30,23 @@
 import android.os.Process;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.ApplicationSharedMemory;
 import com.android.internal.os.BackgroundThread;
 
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
 import java.io.ByteArrayOutputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
@@ -203,19 +210,14 @@
     };
 
     /**
-     * Verify that the property name conforms to the standard.  Log a warning if this is not true.
-     * Note that this is done once in the cache constructor; it does not have to be very fast.
+     * Verify that the property name conforms to the standard and throw if this is not true.  Note
+     * that this is done only once for a given property name; it does not have to be very fast.
      */
-    private void validateCacheKey(String name) {
-        if (Build.IS_USER) {
-            // Do not bother checking keys in user builds.  The keys will have been tested in
-            // eng/userdebug builds already.
-            return;
-        }
+    private static void throwIfInvalidCacheKey(String name) {
         for (int i = 0; i < sValidKeyPrefix.length; i++) {
             if (name.startsWith(sValidKeyPrefix[i])) return;
         }
-        Log.w(TAG, "invalid cache name: " + name);
+        throw new IllegalArgumentException("invalid cache name: " + name);
     }
 
     /**
@@ -234,7 +236,8 @@
      * reserved values cause the cache to be skipped.
      */
     // This is the initial value of all cache keys.  It is changed when a cache is invalidated.
-    private static final int NONCE_UNSET = 0;
+    @VisibleForTesting
+    static final int NONCE_UNSET = 0;
     // This value is used in two ways.  First, it is used internally to indicate that the cache is
     // disabled for the current query.  Secondly, it is used to globally disable the cache across
     // the entire system.  Once a cache is disabled, there is no way to enable it again.  The
@@ -685,6 +688,77 @@
     }
 
     /**
+     * Manage nonces that are stored in shared memory.
+     */
+    private static final class NonceSharedMem extends NonceHandler {
+        // The shared memory.
+        private volatile NonceStore mStore;
+
+        // The index of the nonce in shared memory.
+        private volatile int mHandle = NonceStore.INVALID_NONCE_INDEX;
+
+        // True if the string has been stored, ever.
+        private volatile boolean mRecorded = false;
+
+        // A short name that is saved in shared memory.  This is the portion of the property name
+        // that follows the prefix.
+        private final String mShortName;
+
+        NonceSharedMem(@NonNull String name, @Nullable String prefix) {
+            super(name);
+            if ((prefix != null) && name.startsWith(prefix)) {
+                mShortName = name.substring(prefix.length());
+            } else {
+                mShortName = name;
+            }
+        }
+
+        // Fetch the nonce from shared memory.  If the shared memory is not available, return
+        // UNSET.  If the shared memory is available but the nonce name is not known (it may not
+        // have been invalidated by the server yet), return UNSET.
+        @Override
+        long getNonceInternal() {
+            if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
+                if (mStore == null) {
+                    mStore = NonceStore.getInstance();
+                    if (mStore == null) {
+                        return NONCE_UNSET;
+                    }
+                }
+                mHandle = mStore.getHandleForName(mShortName);
+                if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
+                    return NONCE_UNSET;
+                }
+            }
+            return mStore.getNonce(mHandle);
+        }
+
+        // Set the nonce in shared mmory.  If the shared memory is not available, throw an
+        // exception.  Otherwise, if the nonce name has never been recorded, record it now and
+        // fetch the handle for the name.  If the handle cannot be created, throw an exception.
+        @Override
+        void setNonceInternal(long value) {
+            if (mHandle == NonceStore.INVALID_NONCE_INDEX || !mRecorded) {
+                if (mStore == null) {
+                    mStore = NonceStore.getInstance();
+                    if (mStore == null) {
+                        throw new IllegalStateException("setNonce: shared memory not ready");
+                    }
+                }
+                // Always store the name before fetching the handle.  storeName() is idempotent
+                // but does take a little time, so this code calls it just once.
+                mStore.storeName(mShortName);
+                mRecorded = true;
+                mHandle = mStore.getHandleForName(mShortName);
+                if (mHandle == NonceStore.INVALID_NONCE_INDEX) {
+                    throw new IllegalStateException("setNonce: shared memory store failed");
+                }
+            }
+            mStore.setNonce(mHandle, value);
+        }
+    }
+
+    /**
      * SystemProperties and shared storage are protected and cannot be written by random
      * processes.  So, for testing purposes, the NonceLocal handler stores the nonce locally.  The
      * NonceLocal uses the mTestNonce in the superclass, regardless of test mode.
@@ -712,6 +786,7 @@
      * Complete key prefixes.
      */
     private static final String PREFIX_TEST = CACHE_KEY_PREFIX + "." + MODULE_TEST + ".";
+    private static final String PREFIX_SYSTEM = CACHE_KEY_PREFIX + "." + MODULE_SYSTEM + ".";
 
     /**
      * A static list of nonce handlers, indexed by name.  NonceHandlers can be safely shared by
@@ -722,16 +797,32 @@
     private static final ConcurrentHashMap<String, NonceHandler> sHandlers
             = new ConcurrentHashMap<>();
 
+    // True if shared memory is flag-enabled, false otherwise.  Read the flags exactly once.
+    private static final boolean sSharedMemoryAvailable =
+            com.android.internal.os.Flags.applicationSharedMemoryEnabled()
+            && android.app.Flags.picUsesSharedMemory();
+
+    // Return true if this cache can use shared memory for its nonce.  Shared memory may be used
+    // if the module is the system.
+    private static boolean sharedMemoryOkay(@NonNull String name) {
+        return sSharedMemoryAvailable && name.startsWith(PREFIX_SYSTEM);
+    }
+
     /**
-     * Return the proper nonce handler, based on the property name.
+     * Return the proper nonce handler, based on the property name.  A handler is created if
+     * necessary.  Before a handler is created, the name is checked, and an exception is thrown if
+     * the name is not valid.
      */
     private static NonceHandler getNonceHandler(@NonNull String name) {
         NonceHandler h = sHandlers.get(name);
         if (h == null) {
             synchronized (sGlobalLock) {
+                throwIfInvalidCacheKey(name);
                 h = sHandlers.get(name);
                 if (h == null) {
-                    if (name.startsWith(PREFIX_TEST)) {
+                    if (sharedMemoryOkay(name)) {
+                        h = new NonceSharedMem(name, PREFIX_SYSTEM);
+                    } else if (name.startsWith(PREFIX_TEST)) {
                         h = new NonceLocal(name);
                     } else {
                         h = new NonceSysprop(name);
@@ -774,7 +865,6 @@
     public PropertyInvalidatedCache(int maxEntries, @NonNull String propertyName,
             @NonNull String cacheName) {
         mPropertyName = propertyName;
-        validateCacheKey(mPropertyName);
         mCacheName = cacheName;
         mNonce = getNonceHandler(mPropertyName);
         mMaxEntries = maxEntries;
@@ -799,7 +889,6 @@
     public PropertyInvalidatedCache(int maxEntries, @NonNull String module, @NonNull String api,
             @NonNull String cacheName, @NonNull QueryHandler<Query, Result> computer) {
         mPropertyName = createPropertyName(module, api);
-        validateCacheKey(mPropertyName);
         mCacheName = cacheName;
         mNonce = getNonceHandler(mPropertyName);
         mMaxEntries = maxEntries;
@@ -1620,6 +1709,14 @@
         // then only that cache is reported.
         boolean detail = anyDetailed(args);
 
+        if (sSharedMemoryAvailable) {
+            pw.println("  SharedMemory: enabled");
+            NonceStore.getInstance().dump(pw, "    ", detail);
+        } else {
+            pw.println("  SharedMemory: disabled");
+         }
+        pw.println();
+
         ArrayList<PropertyInvalidatedCache> activeCaches = getActiveCaches();
         for (int i = 0; i < activeCaches.size(); i++) {
             PropertyInvalidatedCache currentCache = activeCaches.get(i);
@@ -1654,4 +1751,363 @@
             Log.e(TAG, "Failed to dump PropertyInvalidatedCache instances");
         }
     }
+
+    /**
+     * Nonces in shared memory are supported by a string block that acts as a table of contents
+     * for nonce names, and an array of nonce values.  There are two key design principles with
+     * respect to nonce maps:
+     *
+     * 1. It is always okay if a nonce value cannot be determined.  If the nonce is UNSET, the
+     * cache is bypassed, which is always functionally correct.  Clients do not take extraordinary
+     * measures to be current with the nonce map.  Clients must be current with the nonce itself;
+     * this is achieved through the shared memory.
+     *
+     * 2. Once a name is mapped to a nonce index, the mapping is fixed for the lifetime of the
+     * system.  It is only necessary to distinguish between the unmapped and mapped states.  Once
+     * a client has mapped a nonce, that mapping is known to be good for the lifetime of the
+     * system.
+     * @hide
+     */
+    @VisibleForTesting
+    public static class NonceStore {
+
+        // A lock for the store.
+        private final Object mLock = new Object();
+
+        // The native pointer.  This is not owned by this class.  It is owned by
+        // ApplicationSharedMemory, and it disappears when the owning instance is closed.
+        private final long mPtr;
+
+        // True if the memory is immutable.
+        private final boolean mMutable;
+
+        // The maximum length of a string in the string block.  The maximum length must fit in a
+        // byte, but a smaller value has been chosen to limit memory use.  Because strings are
+        // run-length encoded, a string consumes at most MAX_STRING_LENGTH+1 bytes in the string
+        // block.
+        private static final int MAX_STRING_LENGTH = 63;
+
+        // The raw byte block.  Strings are stored as run-length encoded byte arrays.  The first
+        // byte is the length of the following string.  It is an axiom of the system that the
+        // string block is initially all zeros and that it is write-once memory: new strings are
+        // appended to existing strings, so there is never a need to revisit strings that have
+        // already been pulled from the string block.
+        @GuardedBy("mLock")
+        private final byte[] mStringBlock;
+
+        // The expected hash code of the string block.  If the hash over the string block equals
+        // this value, then the string block is valid.  Otherwise, the block is not valid and
+        // should be re-read.  An invalid block generally means that a client has read the shared
+        // memory while the server was still writing it.
+        @GuardedBy("mLock")
+        private int mBlockHash = 0;
+
+        // The number of nonces that the native layer can hold.  This is maintained for debug and
+        // logging.
+        private final int mMaxNonce;
+
+        /** @hide */
+        @VisibleForTesting
+        public NonceStore(long ptr, boolean mutable) {
+            mPtr = ptr;
+            mMutable = mutable;
+            mStringBlock = new byte[nativeGetMaxByte(ptr)];
+            mMaxNonce = nativeGetMaxNonce(ptr);
+            refreshStringBlockLocked();
+        }
+
+        // The static lock for singleton acquisition.
+        private static Object sLock = new Object();
+
+        // NonceStore is supposed to be a singleton.
+        private static NonceStore sInstance;
+
+        // Return the singleton instance.
+        static NonceStore getInstance() {
+            synchronized (sLock) {
+                if (sInstance == null) {
+                    try {
+                        ApplicationSharedMemory shmem = ApplicationSharedMemory.getInstance();
+                        sInstance = (shmem == null)
+                                    ? null
+                                    : new NonceStore(shmem.getSystemNonceBlock(),
+                                            shmem.isMutable());
+                    } catch (IllegalStateException e) {
+                        // ApplicationSharedMemory.getInstance() throws if the shared memory is
+                        // not yet mapped.  Swallow the exception and leave sInstance null.
+                    }
+                }
+                return sInstance;
+            }
+        }
+
+        // The index value of an unmapped name.
+        public static final int INVALID_NONCE_INDEX = -1;
+
+        // The highest string index extracted from the string block.  -1 means no strings have
+        // been seen.  This is used to skip strings that have already been processed, when the
+        // string block is updated.
+        @GuardedBy("mLock")
+        private int mHighestIndex = -1;
+
+        // The number bytes of the string block that has been used.  This is a statistics.
+        @GuardedBy("mLock")
+        private int mStringBytes = 0;
+
+        // The number of partial reads on the string block.  This is a statistic.
+        @GuardedBy("mLock")
+        private int mPartialReads = 0;
+
+        // The number of times the string block was updated.  This is a statistic.
+        @GuardedBy("mLock")
+        private int mStringUpdated = 0;
+
+        // Map a string to a native index.
+        @GuardedBy("mLock")
+        private final ArrayMap<String, Integer> mStringHandle = new ArrayMap<>();
+
+        // Update the string map from the current string block.  The string block is not modified
+        // and the block hash is not checked.  The function skips past strings that have already
+        // been read, and then processes any new strings.
+        @GuardedBy("mLock")
+        private void updateStringMapLocked() {
+            int index = 0;
+            int offset = 0;
+            while (offset < mStringBlock.length && mStringBlock[offset] != 0) {
+                if (index > mHighestIndex) {
+                    // Only record the string if it has not been seen yet.
+                    final String s = new String(mStringBlock, offset+1, mStringBlock[offset]);
+                    mStringHandle.put(s, index);
+                    mHighestIndex = index;
+                }
+                offset += mStringBlock[offset] + 1;
+                index++;
+            }
+            mStringBytes = offset;
+        }
+
+        // Append a string to the string block and update the hash.  This does not write the block
+        // to shared memory.
+        @GuardedBy("mLock")
+        private void appendStringToMapLocked(@NonNull String str) {
+            int offset = 0;
+            while (offset < mStringBlock.length && mStringBlock[offset] != 0) {
+                offset += mStringBlock[offset] + 1;
+            }
+            final byte[] strBytes = str.getBytes();
+
+            if (offset + strBytes.length >= mStringBlock.length) {
+                // Overflow.  Do not add the string to the block; the string will remain undefined.
+                return;
+            }
+
+            mStringBlock[offset] = (byte) strBytes.length;
+            offset++;
+            for (int i = 0; i < strBytes.length; i++, offset++) {
+                mStringBlock[offset] = strBytes[i];
+            }
+            mBlockHash = Arrays.hashCode(mStringBlock);
+        }
+
+        // Possibly update the string block.  If the native shared memory has a new block hash,
+        // then read the new string block values from shared memory, as well as the new hash.
+        @GuardedBy("mLock")
+        private void refreshStringBlockLocked() {
+            if (mBlockHash == nativeGetByteBlockHash(mPtr)) {
+                // The fastest way to know that the shared memory string block has not changed.
+                return;
+            }
+            final int hash = nativeGetByteBlock(mPtr, mBlockHash, mStringBlock);
+            if (hash != Arrays.hashCode(mStringBlock)) {
+                // This is a partial read: ignore it.  The next time someone needs this string
+                // the memory will be read again and should succeed.  Set the local hash to
+                // zero to ensure that the next read attempt will actually read from shared
+                // memory.
+                mBlockHash = 0;
+                mPartialReads++;
+                return;
+            }
+            // The hash has changed.  Update the strings from the byte block.
+            mStringUpdated++;
+            mBlockHash = hash;
+            updateStringMapLocked();
+        }
+
+        // Throw an exception if the string cannot be stored in the string block.
+        private static void throwIfBadString(@NonNull String s) {
+            if (s.length() == 0) {
+                throw new IllegalArgumentException("cannot store an empty string");
+            }
+            if (s.length() > MAX_STRING_LENGTH) {
+                throw new IllegalArgumentException("cannot store a string longer than "
+                        + MAX_STRING_LENGTH);
+            }
+        }
+
+        // Throw an exception if the nonce handle is invalid.  The handle is bad if it is out of
+        // range of allocated handles.  Note that NONCE_HANDLE_INVALID will throw: this is
+        // important for setNonce().
+        @GuardedBy("mLock")
+        private void throwIfBadHandle(int handle) {
+            if (handle < 0 || handle > mHighestIndex) {
+                throw new IllegalArgumentException("invalid nonce handle: " + handle);
+            }
+        }
+
+        // Throw if the memory is immutable (the process does not have write permission).  The
+        // exception mimics the permission-denied exception thrown when a process writes to an
+        // unauthorized system property.
+        private void throwIfImmutable() {
+            if (!mMutable) {
+                throw new RuntimeException("write permission denied");
+            }
+        }
+
+        // Add a string to the local copy of the block and write the block to shared memory.
+        // Return the index of the new string.  If the string has already been recorded, the
+        // shared memory is not updated but the index of the existing string is returned.
+        public int storeName(@NonNull String str) {
+            synchronized (mLock) {
+                Integer handle = mStringHandle.get(str);
+                if (handle == null) {
+                    throwIfImmutable();
+                    throwIfBadString(str);
+                    appendStringToMapLocked(str);
+                    nativeSetByteBlock(mPtr, mBlockHash, mStringBlock);
+                    updateStringMapLocked();
+                    handle = mStringHandle.get(str);
+                }
+                return handle;
+            }
+        }
+
+        // Retrieve the handle for a string.  -1 is returned if the string is not found.
+        public int getHandleForName(@NonNull String str) {
+            synchronized (mLock) {
+                Integer handle = mStringHandle.get(str);
+                if (handle == null) {
+                    refreshStringBlockLocked();
+                    handle  = mStringHandle.get(str);
+                }
+                return (handle != null) ? handle : INVALID_NONCE_INDEX;
+            }
+        }
+
+        // Thin wrapper around the native method.
+        public boolean setNonce(int handle, long value) {
+            synchronized (mLock) {
+                throwIfBadHandle(handle);
+                throwIfImmutable();
+                return nativeSetNonce(mPtr, handle, value);
+            }
+        }
+
+        public long getNonce(int handle) {
+            synchronized (mLock) {
+                throwIfBadHandle(handle);
+                return nativeGetNonce(mPtr, handle);
+            }
+        }
+
+        /**
+         * Dump the nonce statistics
+         */
+        public void dump(@NonNull PrintWriter pw, @NonNull String prefix, boolean detailed) {
+            synchronized (mLock) {
+                pw.println(formatSimple(
+                    "%sStringsMapped: %d, BytesUsed: %d",
+                    prefix, mHighestIndex, mStringBytes));
+                pw.println(formatSimple(
+                    "%sPartialReads: %d, StringUpdates: %d",
+                    prefix, mPartialReads, mStringUpdated));
+
+                if (detailed) {
+                    for (String s: mStringHandle.keySet()) {
+                        int h = mStringHandle.get(s);
+                        pw.println(formatSimple(
+                            "%sHandle:%d Name:%s", prefix, h, s));
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Return the maximum number of nonces supported in the native layer.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @return the number of nonces supported by the shared memory.
+     */
+    private static native int nativeGetMaxNonce(long mPtr);
+
+    /**
+     * Return the maximum number of string bytes supported in the native layer.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @return the number of string bytes supported by the shared memory.
+     */
+    private static native int nativeGetMaxByte(long mPtr);
+
+    /**
+     * Write the byte block and set the hash into shared memory.  The method is relatively
+     * forgiving, in that any non-null byte array will be stored without error.  The number of
+     * bytes will the lesser of the length of the block parameter and the size of the native
+     * array.  The native layer performs no checks on either byte block or the hash.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @param hash a value to be stored in the native block hash.
+     * @param block the byte array to be store.
+     */
+    @FastNative
+    private static native void nativeSetByteBlock(long mPtr, int hash, @NonNull byte[] block);
+
+    /**
+     * Retrieve the string block into the array and return the hash value.  If the incoming hash
+     * value is the same as the hash in shared memory, the native function returns immediately
+     * without touching the block parameter.  Note that a zero hash value will always cause shared
+     * memory to be read.  The number of bytes read is the lesser of the length of the block
+     * parameter and the size of the native array.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @param hash a value to be compared against the hash in the native layer.
+     * @param block an array to receive the bytes from the native layer.
+     * @return the hash from the native layer.
+     */
+    @FastNative
+    private static native int nativeGetByteBlock(long mPtr, int hash, @NonNull byte[] block);
+
+    /**
+     * Retrieve just the byte block hash from the native layer.  The function is CriticalNative
+     * and thus very fast.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @return the current native hash value.
+     */
+    @CriticalNative
+    private static native int nativeGetByteBlockHash(long mPtr);
+
+    /**
+     * Set a nonce at the specified index.  The index is checked against the size of the native
+     * nonce array and the function returns true if the index is valid, and false.  The function
+     * is CriticalNative and thus very fast.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @param index the index of the nonce to set.
+     * @param value the value to set for the nonce.
+     * @return true if the index is inside the nonce array and false otherwise.
+     */
+    @CriticalNative
+    private static native boolean nativeSetNonce(long mPtr, int index, long value);
+
+    /**
+     * Get the nonce from the specified index.  The index is checked against the size of the
+     * native nonce array; the function returns the nonce value if the index is valid, and 0
+     * otherwise.  The function is CriticalNative and thus very fast.
+     *
+     * @param mPtr the pointer to the native shared memory.
+     * @param index the index of the nonce to retrieve.
+     * @return the value of the specified nonce, of 0 if the index is out of bounds.
+     */
+    @CriticalNative
+    private static native long nativeGetNonce(long mPtr, int index);
 }
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
new file mode 100644
index 0000000..7c6989e
--- /dev/null
+++ b/core/java/android/app/performance.aconfig
@@ -0,0 +1,11 @@
+package: "android.app"
+container: "system"
+
+flag {
+     namespace: "system_performance"
+     name: "pic_uses_shared_memory"
+     is_exported: true
+     is_fixed_read_only: true
+     description: "PropertyInvalidatedCache uses shared memory for nonces."
+     bug: "366552454"
+}
diff --git a/core/java/com/android/internal/os/ApplicationSharedMemory.java b/core/java/com/android/internal/os/ApplicationSharedMemory.java
index 84f713e..e6ea29e 100644
--- a/core/java/com/android/internal/os/ApplicationSharedMemory.java
+++ b/core/java/com/android/internal/os/ApplicationSharedMemory.java
@@ -21,6 +21,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
 
 import libcore.io.IoUtils;
 
@@ -293,4 +294,34 @@
             throw new IllegalStateException("Not mutable");
         }
     }
+
+    /**
+     * Return true if the memory has been mapped.  This never throws.
+     */
+    public boolean isMapped() {
+        return mPtr != 0;
+    }
+
+    /**
+     * Return true if the memory is mapped and mutable.  This never throws.  Note that it returns
+     * false if the memory is not mapped.
+     */
+    public boolean isMutable() {
+        return isMapped() && mMutable;
+    }
+
+    /**
+     * Provide access to the nonce block needed by {@link PropertyInvalidatedCache}.  This method
+     * returns 0 if the shared memory is not (yet) mapped.
+     */
+    public long getSystemNonceBlock() {
+        return isMapped() ? nativeGetSystemNonceBlock(mPtr) : 0;
+    }
+
+    /**
+     * Return a pointer to the system nonce cache in the shared memory region.  The method is
+     * idempotent.
+     */
+    @FastNative
+    private static native long nativeGetSystemNonceBlock(long ptr);
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 816ace2..eb07f7c 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -249,6 +249,7 @@
                 "android_backup_BackupDataOutput.cpp",
                 "android_backup_FileBackupHelperBase.cpp",
                 "android_backup_BackupHelperDispatcher.cpp",
+                "android_app_PropertyInvalidatedCache.cpp",
                 "android_app_backup_FullBackup.cpp",
                 "android_content_res_ApkAssets.cpp",
                 "android_content_res_ObbScanner.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 76f66cd..821861e 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -177,6 +177,7 @@
 extern int register_android_app_Activity(JNIEnv *env);
 extern int register_android_app_ActivityThread(JNIEnv *env);
 extern int register_android_app_NativeActivity(JNIEnv *env);
+extern int register_android_app_PropertyInvalidatedCache(JNIEnv* env);
 extern int register_android_media_RemoteDisplay(JNIEnv *env);
 extern int register_android_util_jar_StrictJarFile(JNIEnv* env);
 extern int register_android_view_InputChannel(JNIEnv* env);
@@ -1659,6 +1660,7 @@
         REG_JNI(register_android_app_Activity),
         REG_JNI(register_android_app_ActivityThread),
         REG_JNI(register_android_app_NativeActivity),
+        REG_JNI(register_android_app_PropertyInvalidatedCache),
         REG_JNI(register_android_util_jar_StrictJarFile),
         REG_JNI(register_android_view_InputChannel),
         REG_JNI(register_android_view_InputEventReceiver),
diff --git a/core/jni/android_app_PropertyInvalidatedCache.cpp b/core/jni/android_app_PropertyInvalidatedCache.cpp
new file mode 100644
index 0000000..ead6666
--- /dev/null
+++ b/core/jni/android_app_PropertyInvalidatedCache.cpp
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "CacheNonce"
+
+#include <string.h>
+#include <memory.h>
+
+#include <atomic>
+
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/scoped_primitive_array.h>
+#include <android-base/logging.h>
+
+#include "core_jni_helpers.h"
+#include "android_app_PropertyInvalidatedCache.h"
+
+namespace {
+
+using namespace android::app::PropertyInvalidatedCache;
+
+// Convert a jlong to a nonce block.  This is a convenience function that should be inlined by
+// the compiler.
+inline SystemCacheNonce* sysCache(jlong ptr) {
+    return reinterpret_cast<SystemCacheNonce*>(ptr);
+}
+
+// Return the number of nonces in the nonce block.
+jint getMaxNonce(JNIEnv*, jclass, jlong ptr) {
+    return sysCache(ptr)->getMaxNonce();
+}
+
+// Return the number of string bytes in the nonce block.
+jint getMaxByte(JNIEnv*, jclass, jlong ptr) {
+    return sysCache(ptr)->getMaxByte();
+}
+
+// Set the byte block.  The first int is the hash to set and the second is the array to copy.
+// This should be synchronized in the Java layer.
+void setByteBlock(JNIEnv* env, jclass, jlong ptr, jint hash, jbyteArray val) {
+    ScopedByteArrayRO value(env, val);
+    if (value.get() == nullptr) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException", "null byte block");
+        return;
+    }
+    sysCache(ptr)->setByteBlock(hash, value.get(), value.size());
+}
+
+// Fetch the byte block.  If the incoming hash is the same as the local hash, the Java layer is
+// presumed to have an up-to-date copy of the byte block; do not copy byte array.  The local
+// hash is returned.
+jint getByteBlock(JNIEnv* env, jclass, jlong ptr, jint hash, jbyteArray val) {
+    if (sysCache(ptr)->getHash() == hash) {
+        return hash;
+    }
+    ScopedByteArrayRW value(env, val);
+    return sysCache(ptr)->getByteBlock(value.get(), value.size());
+}
+
+// Fetch the byte block hash.
+//
+// This is a CriticalNative method and therefore does not get the JNIEnv or jclass parameters.
+jint getByteBlockHash(jlong ptr) {
+    return sysCache(ptr)->getHash();
+}
+
+// Get a nonce value. So that this method can be CriticalNative, it returns 0 if the value is
+// out of range, rather than throwing an exception.  This is a CriticalNative method and
+// therefore does not get the JNIEnv or jclass parameters.
+//
+// This method is @CriticalNative and does not take a JNIEnv* or jclass argument.
+jlong getNonce(jlong ptr, jint index) {
+    return sysCache(ptr)->getNonce(index);
+}
+
+// Set a nonce value. So that this method can be CriticalNative, it returns a boolean: false if
+// the index is out of range and true otherwise.  Callers may test the returned boolean and
+// generate an exception.
+//
+// This method is @CriticalNative and does not take a JNIEnv* or jclass argument.
+jboolean setNonce(jlong ptr, jint index, jlong value) {
+    return sysCache(ptr)->setNonce(index, value);
+}
+
+static const JNINativeMethod gMethods[] = {
+    {"nativeGetMaxNonce",      "(J)I",    (void*) getMaxNonce },
+    {"nativeGetMaxByte",       "(J)I",    (void*) getMaxByte },
+    {"nativeSetByteBlock",     "(JI[B)V", (void*) setByteBlock },
+    {"nativeGetByteBlock",     "(JI[B)I", (void*) getByteBlock },
+    {"nativeGetByteBlockHash", "(J)I",    (void*) getByteBlockHash },
+    {"nativeGetNonce",         "(JI)J",   (void*) getNonce },
+    {"nativeSetNonce",         "(JIJ)Z",  (void*) setNonce },
+};
+
+static const char* kClassName = "android/app/PropertyInvalidatedCache";
+
+} // anonymous namespace
+
+namespace android {
+
+int register_android_app_PropertyInvalidatedCache(JNIEnv* env) {
+    RegisterMethodsOrDie(env, kClassName, gMethods, NELEM(gMethods));
+    return JNI_OK;
+}
+
+} // namespace android
diff --git a/core/jni/android_app_PropertyInvalidatedCache.h b/core/jni/android_app_PropertyInvalidatedCache.h
new file mode 100644
index 0000000..eefa8fa
--- /dev/null
+++ b/core/jni/android_app_PropertyInvalidatedCache.h
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string.h>
+#include <memory.h>
+
+#include <atomic>
+
+namespace android {
+namespace app {
+namespace PropertyInvalidatedCache {
+
+/**
+ * A cache nonce block contains an array of std::atomic<int64_t> and an array of bytes.  The
+ * byte array has an associated hash.  This class provides methods to read and write the fields
+ * of the block but it does not interpret the fields.
+ *
+ * On initialization, all fields are set to zero.
+ *
+ * In general, methods do not report errors.  This allows the methods to be used in
+ * CriticalNative JNI APIs.
+ *
+ * The template is parameterized by the number of nonces it supports and the number of bytes in
+ * the string block.
+ */
+template<int maxNonce, size_t maxByte> class CacheNonce {
+
+    // The value of an unset field.
+    static const int UNSET = 0;
+
+    // A convenient typedef.  The jbyteArray element type is jbyte, which the compiler treats as
+    // signed char.
+    typedef signed char block_t;
+
+    // The array of nonces
+    volatile std::atomic<int64_t> mNonce[maxNonce];
+
+    // The byte array.  This is not atomic but it is guarded by the mByteHash.
+    volatile block_t mByteBlock[maxByte];
+
+    // The hash that validates the byte block
+    volatile std::atomic<int32_t> mByteHash;
+
+    // Pad the class to a multiple of 8 bytes.
+    int32_t _pad;
+
+  public:
+
+    // The expected size of this instance.  This is a compile-time constant and can be used in a
+    // static assertion.
+    static const int expectedSize =
+            maxNonce * sizeof(std::atomic<int64_t>)
+            + sizeof(std::atomic<int32_t>)
+            + maxByte * sizeof(block_t)
+            + sizeof(int32_t);
+
+    // These provide run-time access to the sizing parameters.
+    int getMaxNonce() const {
+        return maxNonce;
+    }
+
+    size_t getMaxByte() const {
+        return maxByte;
+    }
+
+    // Construct and initialize the memory.
+    CacheNonce() {
+        for (int i = 0; i < maxNonce; i++) {
+            mNonce[i] = UNSET;
+        }
+        mByteHash = UNSET;
+        memset((void*) mByteBlock, UNSET, sizeof(mByteBlock));
+    }
+
+    // Fetch a nonce, returning UNSET if the index is out of range.  This method specifically
+    // does not throw or generate an error if the index is out of range; this allows the method
+    // to be called in a CriticalNative JNI API.
+    int64_t getNonce(int index) const {
+        if (index < 0 || index >= maxNonce) {
+            return UNSET;
+        } else {
+            return mNonce[index];
+        }
+    }
+
+    // Set a nonce and return true. Return false if the index is out of range.  This method
+    // specifically does not throw or generate an error if the index is out of range; this
+    // allows the method to be called in a CriticalNative JNI API.
+    bool setNonce(int index, int64_t value) {
+        if (index < 0 || index >= maxNonce) {
+            return false;
+        } else {
+            mNonce[index] = value;
+            return true;
+        }
+    }
+
+    // Fetch just the byte-block hash
+    int32_t getHash() const {
+        return mByteHash;
+    }
+
+    // Copy the byte block to the target and return the current hash.
+    int32_t getByteBlock(block_t* block, size_t len) const {
+        memcpy(block, (void*) mByteBlock, std::min(maxByte, len));
+        return mByteHash;
+    }
+
+    // Set the byte block and the hash.
+    void setByteBlock(int hash, const block_t* block, size_t len) {
+        memcpy((void*) mByteBlock, block, len = std::min(maxByte, len));
+        mByteHash = hash;
+    }
+};
+
+/**
+ * Sizing parameters for the system_server PropertyInvalidatedCache support.  A client can
+ * retrieve the values through the accessors in CacheNonce instances.
+ */
+static const int MAX_NONCE = 64;
+static const int BYTE_BLOCK_SIZE = 8192;
+
+// The CacheNonce for system server holds 64 nonces with a string block of 8192 bytes.
+typedef CacheNonce<MAX_NONCE, BYTE_BLOCK_SIZE> SystemCacheNonce;
+
+// The goal of this assertion is to ensure that the data structure is the same size across 32-bit
+// and 64-bit systems.
+static_assert(sizeof(SystemCacheNonce) == SystemCacheNonce::expectedSize,
+              "Unexpected SystemCacheNonce size");
+
+} // namespace PropertyInvalidatedCache
+} // namespace app
+} // namespace android
diff --git a/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp b/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
index 453e539..cc1687c 100644
--- a/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
+++ b/core/jni/com_android_internal_os_ApplicationSharedMemory.cpp
@@ -29,8 +29,12 @@
 
 #include "core_jni_helpers.h"
 
+#include "android_app_PropertyInvalidatedCache.h"
+
 namespace {
 
+using namespace android::app::PropertyInvalidatedCache;
+
 // Atomics should be safe to use across processes if they are lock free.
 static_assert(std::atomic<int64_t>::is_always_lock_free == true,
               "atomic<int64_t> is not always lock free");
@@ -64,12 +68,15 @@
     void setLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis(int64_t offset) {
         latestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis = offset;
     }
+
+    // The nonce storage for pic.  The sizing is suitable for the system server module.
+    SystemCacheNonce systemPic;
 };
 
 // Update the expected value when modifying the members of SharedMemory.
 // The goal of this assertion is to ensure that the data structure is the same size across 32-bit
 // and 64-bit systems.
-static_assert(sizeof(SharedMemory) == 8, "Unexpected SharedMemory size");
+static_assert(sizeof(SharedMemory) == 8 + sizeof(SystemCacheNonce), "Unexpected SharedMemory size");
 
 static jint nativeCreate(JNIEnv* env, jclass) {
     // Create anonymous shared memory region
@@ -133,6 +140,12 @@
     return sharedMemory->getLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis();
 }
 
+// This is a FastNative method.  It takes the usual JNIEnv* and jclass* arguments.
+static jlong nativeGetSystemNonceBlock(JNIEnv*, jclass*, jlong ptr) {
+    SharedMemory* sharedMemory = reinterpret_cast<SharedMemory*>(ptr);
+    return reinterpret_cast<jlong>(&sharedMemory->systemPic);
+}
+
 static const JNINativeMethod gMethods[] = {
         {"nativeCreate", "()I", (void*)nativeCreate},
         {"nativeMap", "(IZ)J", (void*)nativeMap},
@@ -143,16 +156,17 @@
          (void*)nativeSetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis},
         {"nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis", "(J)J",
          (void*)nativeGetLatestNetworkTimeUnixEpochMillisAtZeroElapsedRealtimeMillis},
+        {"nativeGetSystemNonceBlock", "(J)J", (void*) nativeGetSystemNonceBlock},
 };
 
-} // anonymous namespace
-
-namespace android {
-
 static const char kApplicationSharedMemoryClassName[] =
         "com/android/internal/os/ApplicationSharedMemory";
 static jclass gApplicationSharedMemoryClass;
 
+} // anonymous namespace
+
+namespace android {
+
 int register_com_android_internal_os_ApplicationSharedMemory(JNIEnv* env) {
     gApplicationSharedMemoryClass =
             MakeGlobalRefOrDie(env, FindClassOrDie(env, kApplicationSharedMemoryClassName));
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index dcea5b2..65153f5 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -16,13 +16,23 @@
 
 package android.app;
 
+import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
+import static android.app.PropertyInvalidatedCache.NonceStore.INVALID_NONCE_INDEX;
+import static com.android.internal.os.Flags.FLAG_APPLICATION_SHARED_MEMORY_ENABLED;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.internal.os.ApplicationSharedMemory;
+
 import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
@@ -47,6 +57,9 @@
     @Rule
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     // Configuration for creating caches
     private static final String MODULE = PropertyInvalidatedCache.MODULE_TEST;
     private static final String API = "testApi";
@@ -423,4 +436,54 @@
         // Re-enable test mode (so that the cleanup for the test does not throw).
         PropertyInvalidatedCache.setTestMode(true);
     }
+
+    // Verify the behavior of shared memory nonce storage.  This does not directly test the cache
+    // storing nonces in shared memory.
+    @RequiresFlagsEnabled(FLAG_APPLICATION_SHARED_MEMORY_ENABLED)
+    @Test
+    public void testSharedMemoryStorage() {
+        // Fetch a shared memory instance for testing.
+        ApplicationSharedMemory shmem = ApplicationSharedMemory.create();
+
+        // Create a server-side store and a client-side store.  The server's store is mutable and
+        // the client's store is not mutable.
+        PropertyInvalidatedCache.NonceStore server =
+                new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), true);
+        PropertyInvalidatedCache.NonceStore client =
+                new PropertyInvalidatedCache.NonceStore(shmem.getSystemNonceBlock(), false);
+
+        final String name1 = "name1";
+        assertEquals(server.getHandleForName(name1), INVALID_NONCE_INDEX);
+        assertEquals(client.getHandleForName(name1), INVALID_NONCE_INDEX);
+        final int index1 = server.storeName(name1);
+        assertNotEquals(index1, INVALID_NONCE_INDEX);
+        assertEquals(server.getHandleForName(name1), index1);
+        assertEquals(client.getHandleForName(name1), index1);
+        assertEquals(server.storeName(name1), index1);
+
+        assertEquals(server.getNonce(index1), NONCE_UNSET);
+        assertEquals(client.getNonce(index1), NONCE_UNSET);
+        final int value1 = 4;
+        server.setNonce(index1, value1);
+        assertEquals(server.getNonce(index1), value1);
+        assertEquals(client.getNonce(index1), value1);
+        final int value2 = 8;
+        server.setNonce(index1, value2);
+        assertEquals(server.getNonce(index1), value2);
+        assertEquals(client.getNonce(index1), value2);
+
+        final String name2 = "name2";
+        assertEquals(server.getHandleForName(name2), INVALID_NONCE_INDEX);
+        assertEquals(client.getHandleForName(name2), INVALID_NONCE_INDEX);
+        final int index2 = server.storeName(name2);
+        assertNotEquals(index2, INVALID_NONCE_INDEX);
+        assertEquals(server.getHandleForName(name2), index2);
+        assertEquals(client.getHandleForName(name2), index2);
+        assertEquals(server.storeName(name2), index2);
+
+        // The names are different, so the indices must be different.
+        assertNotEquals(index1, index2);
+
+        shmem.close();
+    }
 }