Merge "Disable key repeat for PendingKey_IsDroppedWhileMotionIsProcessedAndNewTouchComesIn test" into main
diff --git a/include/android/thermal.h b/include/android/thermal.h
index 1f477f8..0b57e93 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -111,7 +111,7 @@
  * It's passed the updated thermal status as parameter, as well as the
  * pointer provided by the client that registered a callback.
  */
-typedef void (*AThermal_StatusCallback)(void *data, AThermalStatus status);
+typedef void (*AThermal_StatusCallback)(void* data, AThermalStatus status);
 
 /**
   * Acquire an instance of the thermal manager. This must be freed using
@@ -222,6 +222,70 @@
 float AThermal_getThermalHeadroom(AThermalManager *manager,
         int forecastSeconds) __INTRODUCED_IN(31);
 
+/**
+ * This struct defines an instance of headroom threshold value and its status.
+ * <p>
+ * The value should be monotonically non-decreasing as the thermal status increases.
+ * For {@link ATHERMAL_STATUS_SEVERE}, its headroom threshold is guaranteed to
+ * be 1.0f. For status below severe status, the value should be lower or equal
+ * to 1.0f, and for status above severe, the value should be larger or equal to 1.0f.
+ * <p>
+ * Also see {@link AThermal_getThermalHeadroom} for explanation on headroom, and
+ * {@link AThermal_getThermalHeadroomThresholds} for how to use this.
+ */
+struct AThermalHeadroomThreshold {
+    float headroom;
+    AThermalStatus thermalStatus;
+};
+
+/**
+ * Gets the thermal headroom thresholds for all available thermal status.
+ *
+ * A thermal status will only exist in output if the device manufacturer has the
+ * corresponding threshold defined for at least one of its slow-moving skin temperature
+ * sensors. If it's set, one should also expect to get it from
+ * {@link #AThermal_getCurrentThermalStatus} or {@link AThermal_StatusCallback}.
+ * <p>
+ * The headroom threshold is used to interpret the possible thermal throttling status based on
+ * the headroom prediction. For example, if the headroom threshold for
+ * {@link ATHERMAL_STATUS_LIGHT} is 0.7, and a headroom prediction in 10s returns 0.75
+ * (or {@code AThermal_getThermalHeadroom(10)=0.75}), one can expect that in 10 seconds the system
+ * could be in lightly throttled state if the workload remains the same. The app can consider
+ * taking actions according to the nearest throttling status the difference between the headroom and
+ * the threshold.
+ * <p>
+ * For new devices it's guaranteed to have a single sensor, but for older devices with multiple
+ * sensors reporting different threshold values, the minimum threshold is taken to be conservative
+ * on predictions. Thus, when reading real-time headroom, it's not guaranteed that a real-time value
+ * of 0.75 (or {@code AThermal_getThermalHeadroom(0)}=0.75) exceeding the threshold of 0.7 above
+ * will always come with lightly throttled state
+ * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_LIGHT}) but it can be lower
+ * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_NONE}).
+ * While it's always guaranteed that the device won't be throttled heavier than the unmet
+ * threshold's state, so a real-time headroom of 0.75 will never come with
+ * {@link #ATHERMAL_STATUS_MODERATE} but always lower, and 0.65 will never come with
+ * {@link ATHERMAL_STATUS_LIGHT} but {@link #ATHERMAL_STATUS_NONE}.
+ * <p>
+ * The returned list of thresholds is cached on first successful query and owned by the thermal
+ * manager, which will not change between calls to this function. The caller should only need to
+ * free the manager with {@link AThermal_releaseManager}.
+ *
+ * @param manager The manager instance to use.
+ *                Acquired via {@link AThermal_acquireManager}.
+ * @param outThresholds non-null output pointer to null AThermalHeadroomThreshold pointer, which
+ *                will be set to the cached array of thresholds if thermal thresholds are supported
+ *                by the system or device, otherwise nullptr or unmodified.
+ * @param size non-null output pointer whose value will be set to the size of the threshold array
+ *             or 0 if it's not supported.
+ * @return 0 on success
+ *         EINVAL if outThresholds or size_t is nullptr, or *outThresholds is not nullptr.
+ *         EPIPE if communication with the system service has failed.
+ *         ENOSYS if the feature is disabled by the current system.
+ */
+int AThermal_getThermalHeadroomThresholds(AThermalManager* manager,
+                                          const AThermalHeadroomThreshold ** outThresholds,
+                                          size_t* size) __INTRODUCED_IN(35);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/private/thermal_private.h b/include/private/thermal_private.h
new file mode 100644
index 0000000..951d953
--- /dev/null
+++ b/include/private/thermal_private.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#ifndef ANDROID_PRIVATE_NATIVE_THERMAL_H
+#define ANDROID_PRIVATE_NATIVE_THERMAL_H
+
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+/**
+ * For testing only.
+ */
+void AThermal_setIThermalServiceForTesting(void* iThermalService);
+
+__END_DECLS
+
+#endif // ANDROID_PRIVATE_NATIVE_THERMAL_H
\ No newline at end of file
diff --git a/libs/binder/FdTrigger.cpp b/libs/binder/FdTrigger.cpp
index a1fbbf3..895690f 100644
--- a/libs/binder/FdTrigger.cpp
+++ b/libs/binder/FdTrigger.cpp
@@ -53,7 +53,7 @@
 #ifdef BINDER_RPC_SINGLE_THREADED
     return mTriggered;
 #else
-    return mWrite == -1;
+    return !mWrite.ok();
 #endif
 }
 
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 1ba20b3..fefaa81 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -168,7 +168,7 @@
 
 void RpcServer::setServerSocketModifier(std::function<void(base::borrowed_fd)>&& modifier) {
     RpcMutexLockGuard _l(mLock);
-    LOG_ALWAYS_FATAL_IF(mServer.fd != -1, "Already started");
+    LOG_ALWAYS_FATAL_IF(mServer.fd.ok(), "Already started");
     mServerSocketModifier = std::move(modifier);
 }
 
@@ -200,7 +200,7 @@
 status_t RpcServer::acceptSocketConnection(const RpcServer& server, RpcTransportFd* out) {
     RpcTransportFd clientSocket(unique_fd(TEMP_FAILURE_RETRY(
             accept4(server.mServer.fd.get(), nullptr, nullptr, SOCK_CLOEXEC | SOCK_NONBLOCK))));
-    if (clientSocket.fd < 0) {
+    if (!clientSocket.fd.ok()) {
         int savedErrno = errno;
         ALOGE("Could not accept4 socket: %s", strerror(savedErrno));
         return -savedErrno;
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index c895b21..cd8f417 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -208,7 +208,7 @@
 
     unique_fd serverFd(TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC)));
 
-    if (serverFd == -1) {
+    if (!serverFd.ok()) {
         int savedErrno = errno;
         ALOGE("Could not connect to /dev/null: %s", strerror(savedErrno));
         return -savedErrno;
@@ -594,7 +594,7 @@
 
         unique_fd serverFd(TEMP_FAILURE_RETRY(
                 socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
-        if (serverFd == -1) {
+        if (!serverFd.ok()) {
             int savedErrno = errno;
             ALOGE("Could not create socket at %s: %s", addr.toString().c_str(),
                   strerror(savedErrno));
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 78f8877..6d122c5 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -27,7 +27,7 @@
 use std::convert::TryFrom;
 use std::ffi::{c_void, CStr, CString};
 use std::fmt;
-use std::fs::File;
+use std::io::Write;
 use std::marker::PhantomData;
 use std::ops::Deref;
 use std::os::raw::c_char;
@@ -62,7 +62,7 @@
     ///
     /// This handler is a no-op by default and should be implemented for each
     /// Binder service struct that wishes to respond to dump transactions.
-    fn dump(&self, _file: &File, _args: &[&CStr]) -> Result<()> {
+    fn dump(&self, _writer: &mut dyn Write, _args: &[&CStr]) -> Result<()> {
         Ok(())
     }
 }
@@ -165,7 +165,7 @@
 
     /// Handle a request to invoke the dump transaction on this
     /// object.
-    fn on_dump(&self, file: &File, args: &[&CStr]) -> Result<()>;
+    fn on_dump(&self, file: &mut dyn Write, args: &[&CStr]) -> Result<()>;
 
     /// Retrieve the class of this remote object.
     ///
@@ -934,8 +934,8 @@
                 }
             }
 
-            fn on_dump(&self, file: &std::fs::File, args: &[&std::ffi::CStr]) -> std::result::Result<(), $crate::StatusCode> {
-                self.0.dump(file, args)
+            fn on_dump(&self, writer: &mut dyn std::io::Write, args: &[&std::ffi::CStr]) -> std::result::Result<(), $crate::StatusCode> {
+                self.0.dump(writer, args)
             }
 
             fn get_class() -> $crate::binder_impl::InterfaceClass {
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index b248f5e..b250012 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -25,6 +25,7 @@
 use std::convert::TryFrom;
 use std::ffi::{c_void, CStr, CString};
 use std::fs::File;
+use std::io::Write;
 use std::mem::ManuallyDrop;
 use std::ops::Deref;
 use std::os::raw::c_char;
@@ -341,7 +342,7 @@
         }
         // Safety: Our caller promised that fd is a file descriptor. We don't
         // own this file descriptor, so we need to be careful not to drop it.
-        let file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
+        let mut file = unsafe { ManuallyDrop::new(File::from_raw_fd(fd)) };
 
         if args.is_null() && num_args != 0 {
             return StatusCode::UNEXPECTED_NULL as status_t;
@@ -366,7 +367,7 @@
         // Safety: Our caller promised that the binder has a `T` pointer in its
         // user data.
         let binder: &T = unsafe { &*(object as *const T) };
-        let res = binder.on_dump(&file, &args);
+        let res = binder.on_dump(&mut *file, &args);
 
         match res {
             Ok(()) => 0,
@@ -569,7 +570,7 @@
         Ok(())
     }
 
-    fn on_dump(&self, _file: &File, _args: &[&CStr]) -> Result<()> {
+    fn on_dump(&self, _writer: &mut dyn Write, _args: &[&CStr]) -> Result<()> {
         Ok(())
     }
 
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index c049b80..c87fa89 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -26,7 +26,7 @@
 
 use std::convert::{TryFrom, TryInto};
 use std::ffi::CStr;
-use std::fs::File;
+use std::io::Write;
 use std::sync::Mutex;
 
 /// Name of service runner.
@@ -118,7 +118,7 @@
 }
 
 impl Interface for TestService {
-    fn dump(&self, _file: &File, args: &[&CStr]) -> Result<(), StatusCode> {
+    fn dump(&self, _writer: &mut dyn Write, args: &[&CStr]) -> Result<(), StatusCode> {
         let mut dump_args = self.dump_args.lock().unwrap();
         dump_args.extend(args.iter().map(|s| s.to_str().unwrap().to_owned()));
         Ok(())
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 8e42ec2..624edba 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -1159,7 +1159,7 @@
         return false;
     }
 
-    LOG_ALWAYS_FATAL_IF(serverFd == -1, "Could not create socket: %s", strerror(errno));
+    LOG_ALWAYS_FATAL_IF(!serverFd.ok(), "Could not create socket: %s", strerror(errno));
 
     sockaddr_vm serverAddr{
             .svm_family = AF_VSOCK,
@@ -1181,7 +1181,7 @@
     // and they return ETIMEDOUT after that.
     android::base::unique_fd connectFd(
             TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
-    LOG_ALWAYS_FATAL_IF(connectFd == -1, "Could not create socket for port %u: %s", vsockPort,
+    LOG_ALWAYS_FATAL_IF(!connectFd.ok(), "Could not create socket for port %u: %s", vsockPort,
                         strerror(errno));
 
     bool success = false;
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
index 9a459b1..db7031a 100644
--- a/libs/input/VirtualInputDevice.cpp
+++ b/libs/input/VirtualInputDevice.cpp
@@ -193,6 +193,7 @@
         {AKEYCODE_NUMPAD_ENTER, KEY_KPENTER},
         {AKEYCODE_NUMPAD_EQUALS, KEY_KPEQUAL},
         {AKEYCODE_NUMPAD_COMMA, KEY_KPCOMMA},
+        {AKEYCODE_LANGUAGE_SWITCH, KEY_LANGUAGE},
 };
 VirtualKeyboard::VirtualKeyboard(unique_fd fd) : VirtualInputDevice(std::move(fd)) {}
 VirtualKeyboard::~VirtualKeyboard() {}
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
index ed3c616..9905210 100644
--- a/opengl/libs/EGL/MultifileBlobCache.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -18,6 +18,7 @@
 
 #include "MultifileBlobCache.h"
 
+#include <android-base/properties.h>
 #include <dirent.h>
 #include <fcntl.h>
 #include <inttypes.h>
@@ -62,12 +63,15 @@
 namespace android {
 
 MultifileBlobCache::MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
-                                       const std::string& baseDir)
+                                       size_t maxTotalEntries, const std::string& baseDir)
       : mInitialized(false),
+        mCacheVersion(0),
         mMaxKeySize(maxKeySize),
         mMaxValueSize(maxValueSize),
         mMaxTotalSize(maxTotalSize),
+        mMaxTotalEntries(maxTotalEntries),
         mTotalCacheSize(0),
+        mTotalCacheEntries(0),
         mHotCacheLimit(0),
         mHotCacheSize(0),
         mWorkerThreadIdle(true) {
@@ -76,6 +80,26 @@
         return;
     }
 
+    // Set the cache version, override if debug value set
+    mCacheVersion = kMultifileBlobCacheVersion;
+    int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1);
+    if (debugCacheVersion >= 0) {
+        ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion);
+        mCacheVersion = debugCacheVersion;
+    }
+
+    // Set the platform build ID, override if debug value set
+    mBuildId = base::GetProperty("ro.build.id", "");
+    std::string debugBuildId = base::GetProperty("debug.egl.blobcache.build_id", "");
+    if (!debugBuildId.empty()) {
+        ALOGV("INIT: Using %s as buildId instead of %s", debugBuildId.c_str(), mBuildId.c_str());
+        if (debugBuildId.length() > PROP_VALUE_MAX) {
+            ALOGV("INIT: debugBuildId is too long (%zu), reduce it to %u", debugBuildId.length(),
+                  PROP_VALUE_MAX);
+        }
+        mBuildId = debugBuildId;
+    }
+
     // Establish the name of our multifile directory
     mMultifileDirName = baseDir + ".multifile";
 
@@ -93,14 +117,30 @@
     mTaskThread = std::thread(&MultifileBlobCache::processTasks, this);
 
     // See if the dir exists, and initialize using its contents
+    bool statusGood = false;
+
+    // Check that our cacheVersion and buildId match
     struct stat st;
     if (stat(mMultifileDirName.c_str(), &st) == 0) {
+        if (checkStatus(mMultifileDirName.c_str())) {
+            statusGood = true;
+        } else {
+            ALOGV("INIT: Cache status has changed, clearing the cache");
+            if (!clearCache()) {
+                ALOGE("INIT: Unable to clear cache");
+                return;
+            }
+        }
+    }
+
+    if (statusGood) {
         // Read all the files and gather details, then preload their contents
         DIR* dir;
         struct dirent* entry;
         if ((dir = opendir(mMultifileDirName.c_str())) != nullptr) {
             while ((entry = readdir(dir)) != nullptr) {
-                if (entry->d_name == "."s || entry->d_name == ".."s) {
+                if (entry->d_name == "."s || entry->d_name == ".."s ||
+                    strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
                     continue;
                 }
 
@@ -123,7 +163,8 @@
                 if (st.st_size <= 0 || st.st_atime <= 0) {
                     ALOGE("INIT: Entry %u has invalid stats! Removing.", entryHash);
                     if (remove(fullPath.c_str()) != 0) {
-                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                        ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
+                              std::strerror(errno));
                     }
                     continue;
                 }
@@ -140,7 +181,7 @@
                 MultifileHeader header;
                 size_t result = read(fd, static_cast<void*>(&header), sizeof(MultifileHeader));
                 if (result != sizeof(MultifileHeader)) {
-                    ALOGE("Error reading MultifileHeader from cache entry (%s): %s",
+                    ALOGE("INIT: Error reading MultifileHeader from cache entry (%s): %s",
                           fullPath.c_str(), std::strerror(errno));
                     close(fd);
                     return;
@@ -150,7 +191,8 @@
                 if (header.magic != kMultifileMagic) {
                     ALOGE("INIT: Entry %u has bad magic (%u)! Removing.", entryHash, header.magic);
                     if (remove(fullPath.c_str()) != 0) {
-                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                        ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
+                              std::strerror(errno));
                     }
                     close(fd);
                     continue;
@@ -175,7 +217,7 @@
                 if (header.crc !=
                     crc32c(mappedEntry + sizeof(MultifileHeader),
                            fileSize - sizeof(MultifileHeader))) {
-                    ALOGE("INIT: Entry %u failed CRC check! Removing.", entryHash);
+                    ALOGV("INIT: Entry %u failed CRC check! Removing.", entryHash);
                     if (remove(fullPath.c_str()) != 0) {
                         ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
                     }
@@ -184,11 +226,12 @@
 
                 // If the cache entry is damaged or no good, remove it
                 if (header.keySize <= 0 || header.valueSize <= 0) {
-                    ALOGE("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
+                    ALOGV("INIT: Entry %u has a bad header keySize (%lu) or valueSize (%lu), "
                           "removing.",
                           entryHash, header.keySize, header.valueSize);
                     if (remove(fullPath.c_str()) != 0) {
-                        ALOGE("Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+                        ALOGE("INIT: Error removing %s: %s", fullPath.c_str(),
+                              std::strerror(errno));
                     }
                     continue;
                 }
@@ -226,9 +269,17 @@
         // If the multifile directory does not exist, create it and start from scratch
         if (mkdir(mMultifileDirName.c_str(), 0755) != 0 && (errno != EEXIST)) {
             ALOGE("Unable to create directory (%s), errno (%i)", mMultifileDirName.c_str(), errno);
+            return;
+        }
+
+        // Create new status file
+        if (!createStatus(mMultifileDirName.c_str())) {
+            ALOGE("INIT: Failed to create status file!");
+            return;
         }
     }
 
+    ALOGV("INIT: Multifile BlobCache initialization succeeded");
     mInitialized = true;
 }
 
@@ -270,7 +321,7 @@
     size_t fileSize = sizeof(MultifileHeader) + keySize + valueSize;
 
     // If we're going to be over the cache limit, kick off a trim to clear space
-    if (getTotalSize() + fileSize > mMaxTotalSize) {
+    if (getTotalSize() + fileSize > mMaxTotalSize || getTotalEntries() + 1 > mMaxTotalEntries) {
         ALOGV("SET: Cache is full, calling trimCache to clear space");
         trimCache();
     }
@@ -469,6 +520,112 @@
     }
 }
 
+bool MultifileBlobCache::createStatus(const std::string& baseDir) {
+    // Populate the status struct
+    struct MultifileStatus status;
+    memset(&status, 0, sizeof(status));
+    status.magic = kMultifileMagic;
+    status.cacheVersion = mCacheVersion;
+
+    // Copy the buildId string in, up to our allocated space
+    strncpy(status.buildId, mBuildId.c_str(),
+            mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
+
+    // Finally update the crc, using cacheVersion and everything the follows
+    status.crc =
+            crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
+                   sizeof(status) - offsetof(MultifileStatus, cacheVersion));
+
+    // Create the status file
+    std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
+    int fd = open(cacheStatus.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+    if (fd == -1) {
+        ALOGE("STATUS(CREATE): Unable to create status file: %s, error: %s", cacheStatus.c_str(),
+              std::strerror(errno));
+        return false;
+    }
+
+    // Write the buffer contents to disk
+    ssize_t result = write(fd, &status, sizeof(status));
+    close(fd);
+    if (result != sizeof(status)) {
+        ALOGE("STATUS(CREATE): Error writing cache status file: %s, error %s", cacheStatus.c_str(),
+              std::strerror(errno));
+        return false;
+    }
+
+    ALOGV("STATUS(CREATE): Created status file: %s", cacheStatus.c_str());
+    return true;
+}
+
+bool MultifileBlobCache::checkStatus(const std::string& baseDir) {
+    std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
+
+    // Does status exist
+    struct stat st;
+    if (stat(cacheStatus.c_str(), &st) != 0) {
+        ALOGV("STATUS(CHECK): Status file (%s) missing", cacheStatus.c_str());
+        return false;
+    }
+
+    // If the status entry is damaged or no good, remove it
+    if (st.st_size <= 0 || st.st_atime <= 0) {
+        ALOGE("STATUS(CHECK): Cache status has invalid stats!");
+        return false;
+    }
+
+    // Open the file so we can read its header
+    int fd = open(cacheStatus.c_str(), O_RDONLY);
+    if (fd == -1) {
+        ALOGE("STATUS(CHECK): Cache error - failed to open cacheStatus: %s, error: %s",
+              cacheStatus.c_str(), std::strerror(errno));
+        return false;
+    }
+
+    // Read in the status header
+    MultifileStatus status;
+    size_t result = read(fd, static_cast<void*>(&status), sizeof(MultifileStatus));
+    close(fd);
+    if (result != sizeof(MultifileStatus)) {
+        ALOGE("STATUS(CHECK): Error reading cache status (%s): %s", cacheStatus.c_str(),
+              std::strerror(errno));
+        return false;
+    }
+
+    // Verify header magic
+    if (status.magic != kMultifileMagic) {
+        ALOGE("STATUS(CHECK): Cache status has bad magic (%u)!", status.magic);
+        return false;
+    }
+
+    // Ensure we have a good CRC
+    if (status.crc !=
+        crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
+               sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
+        ALOGE("STATUS(CHECK): Cache status failed CRC check!");
+        return false;
+    }
+
+    // Check cacheVersion
+    if (status.cacheVersion != mCacheVersion) {
+        ALOGV("STATUS(CHECK): Cache version has changed! old(%u) new(%u)", status.cacheVersion,
+              mCacheVersion);
+        return false;
+    }
+
+    // Check buildId
+    if (strcmp(status.buildId, mBuildId.c_str()) != 0) {
+        ALOGV("STATUS(CHECK): BuildId has changed! old(%s) new(%s)", status.buildId,
+              mBuildId.c_str());
+        return false;
+    }
+
+    // All checks passed!
+    ALOGV("STATUS(CHECK): Status file is good! cacheVersion(%u), buildId(%s) file(%s)",
+          status.cacheVersion, status.buildId, cacheStatus.c_str());
+    return true;
+}
+
 void MultifileBlobCache::trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
                                     time_t accessTime) {
     mEntries.insert(entryHash);
@@ -485,10 +642,12 @@
 
 void MultifileBlobCache::increaseTotalCacheSize(size_t fileSize) {
     mTotalCacheSize += fileSize;
+    mTotalCacheEntries++;
 }
 
 void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
     mTotalCacheSize -= fileSize;
+    mTotalCacheEntries--;
 }
 
 bool MultifileBlobCache::addToHotCache(uint32_t newEntryHash, int newFd, uint8_t* newEntryBuffer,
@@ -557,7 +716,7 @@
     return false;
 }
 
-bool MultifileBlobCache::applyLRU(size_t cacheLimit) {
+bool MultifileBlobCache::applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit) {
     // Walk through our map of sorted last access times and remove files until under the limit
     for (auto cacheEntryIter = mEntryStats.begin(); cacheEntryIter != mEntryStats.end();) {
         uint32_t entryHash = cacheEntryIter->first;
@@ -590,9 +749,10 @@
 
         // See if it has been reduced enough
         size_t totalCacheSize = getTotalSize();
-        if (totalCacheSize <= cacheLimit) {
+        size_t totalCacheEntries = getTotalEntries();
+        if (totalCacheSize <= cacheSizeLimit && totalCacheEntries <= cacheEntryLimit) {
             // Success
-            ALOGV("LRU: Reduced cache to %zu", totalCacheSize);
+            ALOGV("LRU: Reduced cache to size %zu entries %zu", totalCacheSize, totalCacheEntries);
             return true;
         }
     }
@@ -601,8 +761,43 @@
     return false;
 }
 
+// Clear the cache by removing all entries and deleting the directory
+bool MultifileBlobCache::clearCache() {
+    DIR* dir;
+    struct dirent* entry;
+    dir = opendir(mMultifileDirName.c_str());
+    if (dir == nullptr) {
+        ALOGE("CLEAR: Unable to open multifile dir: %s", mMultifileDirName.c_str());
+        return false;
+    }
+
+    // Delete all entries and the status file
+    while ((entry = readdir(dir)) != nullptr) {
+        if (entry->d_name == "."s || entry->d_name == ".."s) {
+            continue;
+        }
+
+        std::string entryName = entry->d_name;
+        std::string fullPath = mMultifileDirName + "/" + entryName;
+        if (remove(fullPath.c_str()) != 0) {
+            ALOGE("CLEAR: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+            return false;
+        }
+    }
+
+    // Delete the directory
+    if (remove(mMultifileDirName.c_str()) != 0) {
+        ALOGE("CLEAR: Error removing %s: %s", mMultifileDirName.c_str(), std::strerror(errno));
+        return false;
+    }
+
+    ALOGV("CLEAR: Cleared the multifile blobcache");
+    return true;
+}
+
 // When removing files, what fraction of the overall limit should be reached when removing files
 // A divisor of two will decrease the cache to 50%, four to 25% and so on
+// We use the same limit to manage size and entry count
 constexpr uint32_t kCacheLimitDivisor = 2;
 
 // Calculate the cache size and remove old entries until under the limit
@@ -611,8 +806,9 @@
     ALOGV("TRIM: Waiting for work to complete.");
     waitForWorkComplete();
 
-    ALOGV("TRIM: Reducing multifile cache size to %zu", mMaxTotalSize / kCacheLimitDivisor);
-    if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor)) {
+    ALOGV("TRIM: Reducing multifile cache size to %zu, entries %zu",
+          mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor);
+    if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor)) {
         ALOGE("Error when clearing multifile shader cache");
         return;
     }
diff --git a/opengl/libs/EGL/MultifileBlobCache.h b/opengl/libs/EGL/MultifileBlobCache.h
index 5e527dc..18566c2 100644
--- a/opengl/libs/EGL/MultifileBlobCache.h
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -21,6 +21,7 @@
 #include <EGL/eglext.h>
 
 #include <android-base/thread_annotations.h>
+#include <cutils/properties.h>
 #include <future>
 #include <map>
 #include <queue>
@@ -33,6 +34,9 @@
 
 namespace android {
 
+constexpr uint32_t kMultifileBlobCacheVersion = 1;
+constexpr char kMultifileBlobCacheStatusFile[] = "cache.status";
+
 struct MultifileHeader {
     uint32_t magic;
     uint32_t crc;
@@ -46,6 +50,13 @@
     time_t accessTime;
 };
 
+struct MultifileStatus {
+    uint32_t magic;
+    uint32_t crc;
+    uint32_t cacheVersion;
+    char buildId[PROP_VALUE_MAX];
+};
+
 struct MultifileHotCache {
     int entryFd;
     uint8_t* entryBuffer;
@@ -92,7 +103,7 @@
 class MultifileBlobCache {
 public:
     MultifileBlobCache(size_t maxKeySize, size_t maxValueSize, size_t maxTotalSize,
-                       const std::string& baseDir);
+                       size_t maxTotalEntries, const std::string& baseDir);
     ~MultifileBlobCache();
 
     void set(const void* key, EGLsizeiANDROID keySize, const void* value,
@@ -103,6 +114,13 @@
     void finish();
 
     size_t getTotalSize() const { return mTotalCacheSize; }
+    size_t getTotalEntries() const { return mTotalCacheEntries; }
+
+    const std::string& getCurrentBuildId() const { return mBuildId; }
+    void setCurrentBuildId(const std::string& buildId) { mBuildId = buildId; }
+
+    uint32_t getCurrentCacheVersion() const { return mCacheVersion; }
+    void setCurrentCacheVersion(uint32_t cacheVersion) { mCacheVersion = cacheVersion; }
 
 private:
     void trackEntry(uint32_t entryHash, EGLsizeiANDROID valueSize, size_t fileSize,
@@ -111,6 +129,9 @@
     bool removeEntry(uint32_t entryHash);
     MultifileEntryStats getEntryStats(uint32_t entryHash);
 
+    bool createStatus(const std::string& baseDir);
+    bool checkStatus(const std::string& baseDir);
+
     size_t getFileSize(uint32_t entryHash);
     size_t getValueSize(uint32_t entryHash);
 
@@ -120,12 +141,16 @@
     bool addToHotCache(uint32_t entryHash, int fd, uint8_t* entryBufer, size_t entrySize);
     bool removeFromHotCache(uint32_t entryHash);
 
+    bool clearCache();
     void trimCache();
-    bool applyLRU(size_t cacheLimit);
+    bool applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit);
 
     bool mInitialized;
     std::string mMultifileDirName;
 
+    std::string mBuildId;
+    uint32_t mCacheVersion;
+
     std::unordered_set<uint32_t> mEntries;
     std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats;
     std::unordered_map<uint32_t, MultifileHotCache> mHotCache;
@@ -133,7 +158,9 @@
     size_t mMaxKeySize;
     size_t mMaxValueSize;
     size_t mMaxTotalSize;
+    size_t mMaxTotalEntries;
     size_t mTotalCacheSize;
+    size_t mTotalCacheEntries;
     size_t mHotCacheLimit;
     size_t mHotCacheEntryLimit;
     size_t mHotCacheSize;
diff --git a/opengl/libs/EGL/MultifileBlobCache_test.cpp b/opengl/libs/EGL/MultifileBlobCache_test.cpp
index 1639be6..90a0f1e 100644
--- a/opengl/libs/EGL/MultifileBlobCache_test.cpp
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -16,13 +16,17 @@
 
 #include "MultifileBlobCache.h"
 
+#include <android-base/properties.h>
 #include <android-base/test_utils.h>
 #include <fcntl.h>
 #include <gtest/gtest.h>
 #include <stdio.h>
 
+#include <fstream>
 #include <memory>
 
+using namespace std::literals;
+
 namespace android {
 
 template <typename T>
@@ -31,23 +35,40 @@
 constexpr size_t kMaxKeySize = 2 * 1024;
 constexpr size_t kMaxValueSize = 6 * 1024;
 constexpr size_t kMaxTotalSize = 32 * 1024;
+constexpr size_t kMaxTotalEntries = 64;
 
 class MultifileBlobCacheTest : public ::testing::Test {
 protected:
     virtual void SetUp() {
+        clearProperties();
         mTempFile.reset(new TemporaryFile());
         mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize,
-                                          &mTempFile->path[0]));
+                                          kMaxTotalEntries, &mTempFile->path[0]));
     }
 
-    virtual void TearDown() { mMBC.reset(); }
+    virtual void TearDown() {
+        clearProperties();
+        mMBC.reset();
+    }
 
     int getFileDescriptorCount();
+    std::vector<std::string> getCacheEntries();
+
+    void clearProperties();
 
     std::unique_ptr<TemporaryFile> mTempFile;
     std::unique_ptr<MultifileBlobCache> mMBC;
 };
 
+void MultifileBlobCacheTest::clearProperties() {
+    // Clear any debug properties used in the tests
+    base::SetProperty("debug.egl.blobcache.cache_version", "");
+    base::WaitForProperty("debug.egl.blobcache.cache_version", "");
+
+    base::SetProperty("debug.egl.blobcache.build_id", "");
+    base::WaitForProperty("debug.egl.blobcache.build_id", "");
+}
+
 TEST_F(MultifileBlobCacheTest, CacheSingleValueSucceeds) {
     unsigned char buf[4] = {0xee, 0xee, 0xee, 0xee};
     mMBC->set("abcd", 4, "efgh", 4);
@@ -211,6 +232,23 @@
     }
 }
 
+TEST_F(MultifileBlobCacheTest, CacheMaxEntrySucceeds) {
+    // Fill the cache with max entries
+    int i = 0;
+    for (i = 0; i < kMaxTotalEntries; i++) {
+        mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
+    }
+
+    // Ensure it is full
+    ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
+
+    // Add another entry
+    mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
+
+    // Ensure total entries is cut in half + 1
+    ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries / 2 + 1);
+}
+
 TEST_F(MultifileBlobCacheTest, CacheMinKeyAndValueSizeSucceeds) {
     unsigned char buf[1] = {0xee};
     mMBC->set("x", 1, "y", 1);
@@ -234,8 +272,7 @@
 
 TEST_F(MultifileBlobCacheTest, EnsureFileDescriptorsClosed) {
     // Populate the cache with a bunch of entries
-    size_t kLargeNumberOfEntries = 1024;
-    for (int i = 0; i < kLargeNumberOfEntries; i++) {
+    for (int i = 0; i < kMaxTotalEntries; i++) {
         // printf("Caching: %i", i);
 
         // Use the index as the key and value
@@ -247,27 +284,223 @@
     }
 
     // Ensure we don't have a bunch of open fds
-    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+    ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
 
     // Close the cache so everything writes out
     mMBC->finish();
     mMBC.reset();
 
     // Now open it again and ensure we still don't have a bunch of open fds
-    mMBC.reset(
-            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &mTempFile->path[0]));
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
 
     // Check after initialization
-    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+    ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
 
-    for (int i = 0; i < kLargeNumberOfEntries; i++) {
+    for (int i = 0; i < kMaxTotalEntries; i++) {
         int result = 0;
         ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
         ASSERT_EQ(i, result);
     }
 
     // And again after we've actually used it
-    ASSERT_LT(getFileDescriptorCount(), kLargeNumberOfEntries / 2);
+    ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
+}
+
+std::vector<std::string> MultifileBlobCacheTest::getCacheEntries() {
+    std::string cachePath = &mTempFile->path[0];
+    std::string multifileDirName = cachePath + ".multifile";
+    std::vector<std::string> cacheEntries;
+
+    struct stat info;
+    if (stat(multifileDirName.c_str(), &info) == 0) {
+        // We have a multifile dir. Skip the status file and return the only entry.
+        DIR* dir;
+        struct dirent* entry;
+        if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
+            while ((entry = readdir(dir)) != nullptr) {
+                if (entry->d_name == "."s || entry->d_name == ".."s) {
+                    continue;
+                }
+                if (strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
+                    continue;
+                }
+                cacheEntries.push_back(multifileDirName + "/" + entry->d_name);
+            }
+        } else {
+            printf("Unable to open %s, error: %s\n", multifileDirName.c_str(),
+                   std::strerror(errno));
+        }
+    } else {
+        printf("Unable to stat %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno));
+    }
+
+    return cacheEntries;
+}
+
+TEST_F(MultifileBlobCacheTest, CacheContainsStatus) {
+    struct stat info;
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+    // After INIT, cache should have a status
+    ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure status lives after closing the cache
+    ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+
+    // Open the cache again
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we still have a status
+    ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+}
+
+// Verify missing cache status file causes cache the be cleared
+TEST_F(MultifileBlobCacheTest, MissingCacheStatusClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Delete the status file
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+    remove(statusFile.str().c_str());
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify modified cache status file BEGIN causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusBeginClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Modify the status file
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+    // Stomp on the beginning of the cache file
+    const char* stomp = "BADF00D";
+    std::fstream fs(statusFile.str());
+    fs.seekp(0, std::ios_base::beg);
+    fs.write(stomp, strlen(stomp));
+    fs.flush();
+    fs.close();
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify modified cache status file END causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusEndClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Modify the status file
+    std::stringstream statusFile;
+    statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+    // Stomp on the END of the cache status file, modifying its contents
+    const char* stomp = "BADF00D";
+    std::fstream fs(statusFile.str());
+    fs.seekp(-strlen(stomp), std::ios_base::end);
+    fs.write(stomp, strlen(stomp));
+    fs.flush();
+    fs.close();
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify mismatched cacheVersion causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, MismatchedCacheVersionClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Set a debug cacheVersion
+    std::string newCacheVersion = std::to_string(kMultifileBlobCacheVersion + 1);
+    ASSERT_TRUE(base::SetProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
+    ASSERT_TRUE(
+            base::WaitForProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify mismatched buildId causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, MismatchedBuildIdClears) {
+    // Set one entry
+    mMBC->set("abcd", 4, "efgh", 4);
+
+    // Close the cache so everything writes out
+    mMBC->finish();
+    mMBC.reset();
+
+    // Ensure there is one cache entry
+    ASSERT_EQ(getCacheEntries().size(), 1);
+
+    // Set a debug buildId
+    base::SetProperty("debug.egl.blobcache.build_id", "foo");
+    base::WaitForProperty("debug.egl.blobcache.build_id", "foo");
+
+    // Open the cache again and ensure no cache hits
+    mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                      &mTempFile->path[0]));
+
+    // Ensure we have no entries
+    ASSERT_EQ(getCacheEntries().size(), 0);
 }
 
 } // namespace android
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index 1b68344..98ff1d1 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -41,6 +41,7 @@
 constexpr uint32_t kMaxMultifileKeySize = 1 * 1024 * 1024;
 constexpr uint32_t kMaxMultifileValueSize = 8 * 1024 * 1024;
 constexpr uint32_t kMaxMultifileTotalSize = 32 * 1024 * 1024;
+constexpr uint32_t kMaxMultifileTotalEntries = 4 * 1024;
 
 namespace android {
 
@@ -277,7 +278,7 @@
     if (mMultifileBlobCache == nullptr) {
         mMultifileBlobCache.reset(new MultifileBlobCache(kMaxMultifileKeySize,
                                                          kMaxMultifileValueSize, mCacheByteLimit,
-                                                         mFilename));
+                                                         kMaxMultifileTotalEntries, mFilename));
     }
     return mMultifileBlobCache.get();
 }
diff --git a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
index 633cc9c..2d9ee3a 100644
--- a/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
+++ b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
@@ -28,6 +28,7 @@
 constexpr size_t kMaxKeySize = 2 * 1024;
 constexpr size_t kMaxValueSize = 6 * 1024;
 constexpr size_t kMaxTotalSize = 32 * 1024;
+constexpr size_t kMaxTotalEntries = 64;
 
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
     // To fuzz this, we're going to create a key/value pair from data
@@ -79,8 +80,8 @@
     std::unique_ptr<MultifileBlobCache> mbc;
 
     tempFile.reset(new TemporaryFile());
-    mbc.reset(
-            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+    mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                     &tempFile->path[0]));
     // With remaining data, select different paths below
     int loopCount = 1;
     uint8_t bumpCount = 0;
@@ -131,8 +132,8 @@
     // Place the maxKey/maxValue twice
     // The first will fit, the second will trigger hot cache trimming
     tempFile.reset(new TemporaryFile());
-    mbc.reset(
-            new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, &tempFile->path[0]));
+    mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+                                     &tempFile->path[0]));
     uint8_t* buffer = new uint8_t[kMaxValueSize];
     mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
     mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
@@ -145,7 +146,7 @@
     // overflow
     tempFile.reset(new TemporaryFile());
     mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, 2 * (kMaxKeySize + kMaxValueSize),
-                                     &tempFile->path[0]));
+                                     kMaxTotalEntries, &tempFile->path[0]));
     mbc->set(maxKey1.data(), kMaxKeySize, maxValue1.data(), kMaxValueSize);
     mbc->set(maxKey2.data(), kMaxKeySize, maxValue2.data(), kMaxValueSize);
     mbc->get(maxKey1.data(), kMaxKeySize, buffer, kMaxValueSize);
diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp
index f81c68f..ce58182 100644
--- a/opengl/tests/EGLTest/egl_cache_test.cpp
+++ b/opengl/tests/EGLTest/egl_cache_test.cpp
@@ -114,25 +114,26 @@
     struct stat info;
     if (stat(multifileDirName.c_str(), &info) == 0) {
         // Ensure we only have one file to manage
-        int realFileCount = 0;
+        int entryFileCount = 0;
 
-        // We have a multifile dir. Return the only real file in it.
+        // We have a multifile dir. Return the only entry file in it.
         DIR* dir;
         struct dirent* entry;
         if ((dir = opendir(multifileDirName.c_str())) != nullptr) {
             while ((entry = readdir(dir)) != nullptr) {
-                if (entry->d_name == "."s || entry->d_name == ".."s) {
+                if (entry->d_name == "."s || entry->d_name == ".."s ||
+                    strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
                     continue;
                 }
                 cachefileName = multifileDirName + "/" + entry->d_name;
-                realFileCount++;
+                entryFileCount++;
             }
         } else {
             printf("Unable to open %s, error: %s\n",
                    multifileDirName.c_str(), std::strerror(errno));
         }
 
-        if (realFileCount != 1) {
+        if (entryFileCount != 1) {
             // If there was more than one real file in the directory, this
             // violates test assumptions
             cachefileName = "";
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 17fa7be..0989863 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -93,6 +93,7 @@
         "libscheduler",
         "libserviceutils",
         "libshaders",
+        "libsurfaceflinger_common",
         "libtimestats",
         "libtonemap",
         "libsurfaceflingerflags",
@@ -175,7 +176,6 @@
         "FrontEnd/LayerLifecycleManager.cpp",
         "FrontEnd/RequestedLayerState.cpp",
         "FrontEnd/TransactionHandler.cpp",
-        "FlagManager.cpp",
         "FpsReporter.cpp",
         "FrameTracer/FrameTracer.cpp",
         "FrameTracker.cpp",
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 370e4b6..2740a97 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -63,7 +63,10 @@
 cc_library {
     name: "libcompositionengine",
     defaults: ["libcompositionengine_defaults"],
-    static_libs: ["libsurfaceflingerflags"],
+    static_libs: [
+        "libsurfaceflinger_common",
+        "libsurfaceflingerflags",
+    ],
     srcs: [
         "src/planner/CachedSet.cpp",
         "src/planner/Flattener.cpp",
@@ -108,6 +111,7 @@
         "libgtest",
         "libgmock",
         "libcompositionengine",
+        "libsurfaceflinger_common_test",
         "libsurfaceflingerflags_test",
     ],
     local_include_dirs: ["include"],
@@ -143,6 +147,7 @@
         "librenderengine_mocks",
         "libgmock",
         "libgtest",
+        "libsurfaceflinger_common_test",
         "libsurfaceflingerflags_test",
     ],
     // For some reason, libvulkan isn't picked up from librenderengine
diff --git a/services/surfaceflinger/CompositionEngine/AndroidTest.xml b/services/surfaceflinger/CompositionEngine/AndroidTest.xml
new file mode 100644
index 0000000..94e92f0
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2023 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.
+-->
+<configuration description="Config for libcompositionengine_test">
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push"
+                value="libcompositionengine_test->/data/local/tmp/libcompositionengine_test" />
+    </target_preparer>
+
+    <!--
+        Disable SELinux so that crashes in the test suite produces symbolized stack traces.
+    -->
+    <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
+
+    <option name="test-suite-tag" value="apct" />
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="libcompositionengine_test" />
+    </test>
+</configuration>
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index 317f2b0..2d957e6 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -19,12 +19,12 @@
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
 #include "AidlComposerHal.h"
-#include "FlagManager.h"
 
 #include <SurfaceFlingerProperties.h>
 #include <android-base/file.h>
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_manager.h>
+#include <common/FlagManager.h>
 #include <gui/TraceUtils.h>
 #include <log/log.h>
 #include <utils/Trace.h>
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index f32fb3a..ba0825c 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -29,8 +29,8 @@
 
 #include <scheduler/Fps.h>
 
+#include <common/FlagManager.h>
 #include "DisplayHardware/Hal.h"
-#include "FlagManager.h"
 #include "Scheduler/StrongTyping.h"
 
 namespace android {
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 6752a0b..b960e33 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -16,8 +16,8 @@
 
 #include <algorithm>
 
+#include <common/FlagManager.h>
 #include "Client.h"
-#include "FlagManager.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
 
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 7f627f8..693a357 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -43,9 +43,9 @@
 #include <utils/Errors.h>
 #include <utils/Trace.h>
 
+#include <common/FlagManager.h>
 #include <scheduler/VsyncConfig.h>
 #include "DisplayHardware/DisplayMode.h"
-#include "FlagManager.h"
 #include "FrameTimeline.h"
 #include "VSyncDispatch.h"
 #include "VSyncTracker.h"
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 450ba1d..d309adc 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -31,9 +31,9 @@
 #include <string>
 #include <utility>
 
+#include <common/FlagManager.h>
 #include "../Layer.h"
 #include "EventThread.h"
-#include "FlagManager.h"
 #include "LayerInfo.h"
 
 namespace android::scheduler {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index f41243c..b54f334 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -45,9 +45,9 @@
 #include <memory>
 #include <numeric>
 
+#include <common/FlagManager.h>
 #include "../Layer.h"
 #include "EventThread.h"
-#include "FlagManager.h"
 #include "FrameRateOverrideMappings.h"
 #include "FrontEnd/LayerHandle.h"
 #include "OneShotTimer.h"
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 3e7ec49..ef30887 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -25,7 +25,7 @@
 
 #include <scheduler/TimeKeeper.h>
 
-#include "FlagManager.h"
+#include <common/FlagManager.h>
 #include "VSyncDispatchTimerQueue.h"
 #include "VSyncTracker.h"
 
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 57aa010..f5f93ce 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -29,13 +29,13 @@
 
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
 #include <cutils/compiler.h>
 #include <cutils/properties.h>
 #include <ftl/concat.h>
 #include <gui/TraceUtils.h>
 #include <utils/Log.h>
 
-#include "FlagManager.h"
 #include "RefreshRateSelector.h"
 #include "VSyncPredictor.h"
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 437e27e..829eea6 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -113,6 +113,7 @@
 #include <unordered_map>
 #include <vector>
 
+#include <common/FlagManager.h>
 #include <gui/LayerStatePermissions.h>
 #include <gui/SchedulingPolicy.h>
 #include <ui/DisplayIdentification.h>
@@ -129,7 +130,6 @@
 #include "DisplayHardware/VirtualDisplaySurface.h"
 #include "DisplayRenderArea.h"
 #include "Effects/Daltonizer.h"
-#include "FlagManager.h"
 #include "FpsReporter.h"
 #include "FrameTimeline/FrameTimeline.h"
 #include "FrameTracer/FrameTracer.h"
@@ -875,7 +875,7 @@
         mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache(shouldPrimeUltraHDR);
 
         if (setSchedFifo(true) != NO_ERROR) {
-            ALOGW("Can't set SCHED_OTHER for primeCache");
+            ALOGW("Can't set SCHED_FIFO after primeCache");
         }
     }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 1e90340..fa48549 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -63,13 +63,13 @@
 #include <scheduler/interface/ICompositor.h>
 #include <ui/FenceResult.h>
 
+#include <common/FlagManager.h>
 #include "Display/PhysicalDisplay.h"
 #include "DisplayDevice.h"
 #include "DisplayHardware/HWC2.h"
 #include "DisplayHardware/PowerAdvisor.h"
 #include "DisplayIdGenerator.h"
 #include "Effects/Daltonizer.h"
-#include "FlagManager.h"
 #include "FrontEnd/DisplayInfo.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/LayerLifecycleManager.h"
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
new file mode 100644
index 0000000..5ef22b5
--- /dev/null
+++ b/services/surfaceflinger/common/Android.bp
@@ -0,0 +1,48 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_defaults {
+    name: "libsurfaceflinger_common_defaults",
+    defaults: [
+        "android.hardware.graphics.composer3-ndk_shared",
+        "surfaceflinger_defaults",
+    ],
+    shared_libs: [
+        "libSurfaceFlingerProp",
+        "server_configurable_flags",
+    ],
+    static_libs: [
+        "librenderengine",
+    ],
+    srcs: [
+        "FlagManager.cpp",
+    ],
+    local_include_dirs: ["include"],
+    export_include_dirs: ["include"],
+}
+
+cc_library_static {
+    name: "libsurfaceflinger_common",
+    defaults: [
+        "libsurfaceflinger_common_defaults",
+    ],
+    static_libs: [
+        "libsurfaceflingerflags",
+    ],
+}
+
+cc_library_static {
+    name: "libsurfaceflinger_common_test",
+    defaults: [
+        "libsurfaceflinger_common_defaults",
+    ],
+    static_libs: [
+        "libsurfaceflingerflags_test",
+    ],
+}
diff --git a/services/surfaceflinger/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
similarity index 99%
rename from services/surfaceflinger/FlagManager.cpp
rename to services/surfaceflinger/common/FlagManager.cpp
index 13b8a6c..8da7d8e 100644
--- a/services/surfaceflinger/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#include "FlagManager.h"
+#include <common/FlagManager.h>
 
 #include <SurfaceFlingerProperties.sysprop.h>
 #include <android-base/parsebool.h>
diff --git a/services/surfaceflinger/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
similarity index 100%
rename from services/surfaceflinger/FlagManager.h
rename to services/surfaceflinger/common/include/common/FlagManager.h
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
index 243b8e0..ab3b352 100644
--- a/services/surfaceflinger/fuzzer/Android.bp
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -39,6 +39,7 @@
         "libgtest_ndk_c++",
         "libgmock_main_ndk",
         "librenderengine_mocks",
+        "libsurfaceflinger_common",
         "perfetto_trace_protos",
         "libcompositionengine_mocks",
         "perfetto_trace_protos",
diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index 9889cb9..6c8972f 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -29,12 +29,12 @@
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
+#include <common/FlagManager.h>
 #include <configstore/Utils.h>
 #include <displayservice/DisplayService.h>
 #include <errno.h>
 #include <hidl/LegacySupport.h>
 #include <processgroup/sched_policy.h>
-#include "FlagManager.h"
 #include "SurfaceFlinger.h"
 #include "SurfaceFlingerFactory.h"
 #include "SurfaceFlingerProperties.h"
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 6e6c6d8..dea0194 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -170,6 +170,7 @@
         "librenderengine_mocks",
         "libscheduler",
         "libserviceutils",
+        "libsurfaceflinger_common_test",
         "libtimestats",
         "libtimestats_atoms_proto",
         "libtimestats_proto",
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
index aa37754..c040f29 100644
--- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -17,7 +17,7 @@
 #undef LOG_TAG
 #define LOG_TAG "FlagManagerTest"
 
-#include "FlagManager.h"
+#include <common/FlagManager.h>
 #include "FlagUtils.h"
 
 #include <gmock/gmock.h>
diff --git a/services/surfaceflinger/tests/unittests/FlagUtils.h b/services/surfaceflinger/tests/unittests/FlagUtils.h
index 333e4e7..550c70d 100644
--- a/services/surfaceflinger/tests/unittests/FlagUtils.h
+++ b/services/surfaceflinger/tests/unittests/FlagUtils.h
@@ -16,7 +16,7 @@
 
 #pragma once
 
-#include "FlagManager.h"
+#include <common/FlagManager.h>
 
 #define SET_FLAG_FOR_TEST(name, value) TestFlagSetter _testflag_((name), (name), (value))
 
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 788fa51..f1e841b 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -30,6 +30,7 @@
 #include <gmock/gmock.h>
 #pragma clang diagnostic pop
 
+#include <common/FlagManager.h>
 #include <gui/LayerMetadata.h>
 #include <log/log.h>
 #include <chrono>
@@ -38,7 +39,6 @@
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayIdentificationTestHelpers.h"
-#include "FlagManager.h"
 #include "FlagUtils.h"
 #include "mock/DisplayHardware/MockComposer.h"
 #include "mock/DisplayHardware/MockHWC2.h"