Add cache validation to ensure the validity

Create cache identity from GL_VERSION and store in ShaderCache.
In the next time ShaderCache is restored from disk, compare the
cache identity to ensure its validity. If GL_VERSION changes in
between, flush out entire FileBlobCache and start from an empty
one.

Bug: b/71800782
Test: Wrote a new unit test to save and restore ShaderCache
Test: hwui_unit_tests
Change-Id: Ie573dc4f18733eee090725be30445d879765231b
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index 6700748..073b481 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -18,6 +18,8 @@
 #include <algorithm>
 #include <log/log.h>
 #include <thread>
+#include <array>
+#include <openssl/sha.h>
 #include "FileBlobCache.h"
 #include "Properties.h"
 #include "utils/TraceUtils.h"
@@ -41,7 +43,40 @@
     return sCache;
 }
 
-void ShaderCache::initShaderDiskCache() {
+bool ShaderCache::validateCache(const void* identity, ssize_t size) {
+    if (nullptr == identity && size == 0)
+        return true;
+
+    if (nullptr == identity || size < 0) {
+        if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) {
+            ALOGW("ShaderCache::validateCache invalid cache identity");
+        }
+        mBlobCache->clear();
+        return false;
+    }
+
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+
+    SHA256_Update(&ctx, identity, size);
+    mIDHash.resize(SHA256_DIGEST_LENGTH);
+    SHA256_Final(mIDHash.data(), &ctx);
+
+    std::array<uint8_t, SHA256_DIGEST_LENGTH> hash;
+    auto key = sIDKey;
+    auto loaded = mBlobCache->get(&key, sizeof(key), hash.data(), hash.size());
+
+    if (loaded && std::equal(hash.begin(), hash.end(), mIDHash.begin()))
+        return true;
+
+    if (CC_UNLIKELY(Properties::debugLevel & kDebugCaches)) {
+        ALOGW("ShaderCache::validateCache cache validation fails");
+    }
+    mBlobCache->clear();
+    return false;
+}
+
+void ShaderCache::initShaderDiskCache(const void* identity, ssize_t size) {
     ATRACE_NAME("initShaderDiskCache");
     std::lock_guard<std::mutex> lock(mMutex);
 
@@ -50,6 +85,7 @@
     // desktop / laptop GPUs. Thus, disable the shader disk cache for emulator builds.
     if (!Properties::runningInEmulator && mFilename.length() > 0) {
         mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
+        validateCache(identity, size);
         mInitialized = true;
     }
 }
@@ -104,6 +140,18 @@
     return SkData::MakeFromMalloc(valueBuffer, valueSize);
 }
 
+void ShaderCache::saveToDiskLocked() {
+    ATRACE_NAME("ShaderCache::saveToDiskLocked");
+    if (mInitialized && mBlobCache && mSavePending) {
+        if (mIDHash.size()) {
+            auto key = sIDKey;
+            mBlobCache->set(&key, sizeof(key), mIDHash.data(), mIDHash.size());
+        }
+        mBlobCache->writeToFile();
+    }
+    mSavePending = false;
+}
+
 void ShaderCache::store(const SkData& key, const SkData& data) {
     ATRACE_NAME("ShaderCache::store");
     std::lock_guard<std::mutex> lock(mMutex);
@@ -129,11 +177,7 @@
         std::thread deferredSaveThread([this]() {
             sleep(mDeferredSaveDelay);
             std::lock_guard<std::mutex> lock(mMutex);
-            ATRACE_NAME("ShaderCache::saveToDisk");
-            if (mInitialized && mBlobCache) {
-                mBlobCache->writeToFile();
-            }
-            mSavePending = false;
+            saveToDiskLocked();
         });
         deferredSaveThread.detach();
     }
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 27473d6..82804cf 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -40,12 +40,21 @@
     ANDROID_API static ShaderCache& get();
 
     /**
-     * "initShaderDiskCache" loads the serialized cache contents from disk and puts the ShaderCache
-     * into an initialized state, such that it is able to insert and retrieve entries from the
-     * cache.  This should be called when HWUI pipeline is initialized.  When not in the initialized
-     * state the load and store methods will return without performing any cache operations.
+     * initShaderDiskCache" loads the serialized cache contents from disk,
+     * optionally checks that the on-disk cache matches a provided identity,
+     * and puts the ShaderCache into an initialized state, such that it is
+     * able to insert and retrieve entries from the cache. If identity is
+     * non-null and validation fails, the cache is initialized but contains
+     * no data. If size is less than zero, the cache is initilaized but
+     * contains no data.
+     *
+     * This should be called when HWUI pipeline is initialized. When not in
+     * the initialized state the load and store methods will return without
+     * performing any cache operations.
      */
-    virtual void initShaderDiskCache();
+    virtual void initShaderDiskCache(const void *identity, ssize_t size);
+
+    virtual void initShaderDiskCache() { initShaderDiskCache(nullptr, 0); }
 
     /**
      * "setFilename" sets the name of the file that should be used to store
@@ -83,6 +92,19 @@
     BlobCache* getBlobCacheLocked();
 
     /**
+     * "validateCache" updates the cache to match the given identity.  If the
+     * cache currently has the wrong identity, all entries in the cache are cleared.
+     */
+    bool validateCache(const void* identity, ssize_t size);
+
+    /**
+     * "saveToDiskLocked" attemps to save the current contents of the cache to
+     * disk. If the identity hash exists, we will insert the identity hash into
+     * the cache for next validation.
+     */
+    void saveToDiskLocked();
+
+    /**
      * "mInitialized" indicates whether the ShaderCache is in the initialized
      * state.  It is initialized to false at construction time, and gets set to
      * true when initialize is called.
@@ -111,6 +133,15 @@
     std::string mFilename;
 
     /**
+     * "mIDHash" is the current identity hash for the cache validation. It is
+     * initialized to an empty vector at construction time, and its content is
+     * generated in the call of the validateCache method. An empty vector
+     * indicates that cache validation is not performed, and the hash should
+     * not be stored on disk.
+     */
+    std::vector<uint8_t> mIDHash;
+
+    /**
      * "mSavePending" indicates whether or not a deferred save operation is
      * pending.  Each time a key/value pair is inserted into the cache via
      * load, a deferred save is initiated if one is not already pending.
@@ -140,6 +171,11 @@
      */
     static ShaderCache sCache;
 
+    /**
+     * "sIDKey" is the cache key of the identity hash
+     */
+    static constexpr uint8_t sIDKey = 0;
+
     friend class ShaderCacheTestUtils; //used for unit testing
 };