Change how memory policy is configured

Should be a mostly no-op but adds a handful of new options

* Adds a 'MemoryPolicy' to mostly consolidate memory settings
* Moves trim handling into HWUI proper
* Adds settings for UI hidden & context destruction that's not
  dependent on TRIM signals
* Treats persistent process the same as system_server
* Tweaks HardwareBitmapUploader timeout to reduce churn

Bug: 245565051
Test: builds & boots

Change-Id: I1f1b3db884ef7fa45ff2556436464a99440b998e
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b11e542..29f3773 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -521,6 +521,7 @@
         "Interpolator.cpp",
         "LightingInfo.cpp",
         "Matrix.cpp",
+        "MemoryPolicy.cpp",
         "PathParser.cpp",
         "Properties.cpp",
         "PropertyValuesAnimatorSet.cpp",
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 0759471..f06fa24 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -93,12 +93,14 @@
         case ADATASPACE_SCRGB:
             get()->mWideColorSpace = SkColorSpace::MakeSRGB();
             break;
+        default:
+            ALOGW("Unknown dataspace %d", dataspace);
+            // Treat unknown dataspaces as sRGB, so fall through
+            [[fallthrough]];
         case ADATASPACE_SRGB:
             // when sRGB is returned, it means wide color gamut is not supported.
             get()->mWideColorSpace = SkColorSpace::MakeSRGB();
             break;
-        default:
-            LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
     }
 }
 
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 7291cab..b7e9999 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -42,6 +42,8 @@
 
 namespace android::uirenderer {
 
+static constexpr auto kThreadTimeout = 60000_ms;
+
 class AHBUploader;
 // This helper uploader classes allows us to upload using either EGL or Vulkan using the same
 // interface.
@@ -80,7 +82,7 @@
     }
 
     void postIdleTimeoutCheck() {
-        mUploadThread->queue().postDelayed(5000_ms, [this](){ this->idleTimeoutCheck(); });
+        mUploadThread->queue().postDelayed(kThreadTimeout, [this]() { this->idleTimeoutCheck(); });
     }
 
 protected:
@@ -97,7 +99,7 @@
 
     bool shouldTimeOutLocked() {
         nsecs_t durationSince = systemTime() - mLastUpload;
-        return durationSince > 2000_ms;
+        return durationSince > kThreadTimeout;
     }
 
     void idleTimeoutCheck() {
diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp
new file mode 100644
index 0000000..ca1312e7
--- /dev/null
+++ b/libs/hwui/MemoryPolicy.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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 "MemoryPolicy.h"
+
+#include <android-base/properties.h>
+
+#include <optional>
+#include <string_view>
+
+#include "Properties.h"
+
+namespace android::uirenderer {
+
+constexpr static MemoryPolicy sDefaultMemoryPolicy;
+constexpr static MemoryPolicy sPersistentOrSystemPolicy{
+        .contextTimeout = 10_s,
+        .useAlternativeUiHidden = true,
+};
+constexpr static MemoryPolicy sLowRamPolicy{
+        .useAlternativeUiHidden = true,
+        .purgeScratchOnly = false,
+};
+constexpr static MemoryPolicy sExtremeLowRam{
+        .initialMaxSurfaceAreaScale = 0.2f,
+        .surfaceSizeMultiplier = 5 * 4.0f,
+        .backgroundRetentionPercent = 0.2f,
+        .contextTimeout = 5_s,
+        .minimumResourceRetention = 1_s,
+        .useAlternativeUiHidden = true,
+        .purgeScratchOnly = false,
+        .releaseContextOnStoppedOnly = true,
+};
+
+const MemoryPolicy& loadMemoryPolicy() {
+    if (Properties::isSystemOrPersistent) {
+        return sPersistentOrSystemPolicy;
+    }
+    std::string memoryPolicy = base::GetProperty(PROPERTY_MEMORY_POLICY, "");
+    if (memoryPolicy == "default") {
+        return sDefaultMemoryPolicy;
+    }
+    if (memoryPolicy == "lowram") {
+        return sLowRamPolicy;
+    }
+    if (memoryPolicy == "extremelowram") {
+        return sExtremeLowRam;
+    }
+
+    if (Properties::isLowRam) {
+        return sLowRamPolicy;
+    }
+    return sDefaultMemoryPolicy;
+}
+
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
new file mode 100644
index 0000000..e86b338
--- /dev/null
+++ b/libs/hwui/MemoryPolicy.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#pragma once
+
+#include "utils/TimeUtils.h"
+
+namespace android::uirenderer {
+
+// Values mirror those from ComponentCallbacks2.java
+enum class TrimLevel {
+    COMPLETE = 80,
+    MODERATE = 60,
+    BACKGROUND = 40,
+    UI_HIDDEN = 20,
+    RUNNING_CRITICAL = 15,
+    RUNNING_LOW = 10,
+    RUNNING_MODERATE = 5,
+};
+
+struct MemoryPolicy {
+    // The initial scale factor applied to the display resolution. The default is 1, but
+    // lower values may be used to start with a smaller initial cache size. The cache will
+    // be adjusted if larger frames are actually rendered
+    float initialMaxSurfaceAreaScale = 1.0f;
+    // The foreground cache size multiplier. The surface area of the screen will be multiplied
+    // by this
+    float surfaceSizeMultiplier = 12.0f * 4.0f;
+    // How much of the foreground cache size should be preserved when going into the background
+    float backgroundRetentionPercent = 0.5f;
+    // How long after the last renderer goes away before the GPU context is released. A value
+    // of 0 means only drop the context on background TRIM signals
+    nsecs_t contextTimeout = 0_ms;
+    // The minimum amount of time to hold onto items in the resource cache
+    // The actual time used will be the max of this & when frames were actually rendered
+    nsecs_t minimumResourceRetention = 10_s;
+    // If false, use only TRIM_UI_HIDDEN to drive background cache limits;
+    // If true, use all signals (such as all contexts are stopped) to drive the limits
+    bool useAlternativeUiHidden = false;
+    // Whether or not to only purge scratch resources when triggering UI Hidden or background
+    // collection
+    bool purgeScratchOnly = true;
+    // EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped
+    bool releaseContextOnStoppedOnly = false;
+};
+
+const MemoryPolicy& loadMemoryPolicy();
+
+}  // namespace android::uirenderer
\ No newline at end of file
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 5a67eb9..277955e 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -87,6 +87,10 @@
 
 bool Properties::enableWebViewOverlays = true;
 
+bool Properties::isHighEndGfx = true;
+bool Properties::isLowRam = false;
+bool Properties::isSystemOrPersistent = false;
+
 StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
 
 DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 2f8c679..96a5176 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -193,6 +193,8 @@
  */
 #define PROPERTY_DRAWING_ENABLED "debug.hwui.drawing_enabled"
 
+#define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy"
+
 ///////////////////////////////////////////////////////////////////////////////
 // Misc
 ///////////////////////////////////////////////////////////////////////////////
@@ -292,16 +294,27 @@
 
     static bool enableWebViewOverlays;
 
+    static bool isHighEndGfx;
+    static bool isLowRam;
+    static bool isSystemOrPersistent;
+
     static StretchEffectBehavior getStretchEffectBehavior() {
         return stretchEffectBehavior;
     }
 
     static void setIsHighEndGfx(bool isHighEndGfx) {
+        Properties::isHighEndGfx = isHighEndGfx;
         stretchEffectBehavior = isHighEndGfx ?
             StretchEffectBehavior::ShaderHWUI :
             StretchEffectBehavior::UniformScale;
     }
 
+    static void setIsLowRam(bool isLowRam) { Properties::isLowRam = isLowRam; }
+
+    static void setIsSystemOrPersistent(bool isSystemOrPersistent) {
+        Properties::isSystemOrPersistent = isSystemOrPersistent;
+    }
+
     /**
      * Used for testing. Typical configuration of stretch behavior is done
      * through setIsHighEndGfx
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 4f281fc..704fba9 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -271,6 +271,16 @@
     Properties::setIsHighEndGfx(jIsHighEndGfx);
 }
 
+static void android_view_ThreadedRenderer_setIsLowRam(JNIEnv* env, jobject clazz,
+                                                      jboolean isLowRam) {
+    Properties::setIsLowRam(isLowRam);
+}
+
+static void android_view_ThreadedRenderer_setIsSystemOrPersistent(JNIEnv* env, jobject clazz,
+                                                                  jboolean isSystemOrPersistent) {
+    Properties::setIsSystemOrPersistent(isSystemOrPersistent);
+}
+
 static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
                                                           jlong proxyPtr, jlongArray frameInfo,
                                                           jint frameInfoSize) {
@@ -949,6 +959,9 @@
         {"nSetColorMode", "(JI)V", (void*)android_view_ThreadedRenderer_setColorMode},
         {"nSetSdrWhitePoint", "(JF)V", (void*)android_view_ThreadedRenderer_setSdrWhitePoint},
         {"nSetIsHighEndGfx", "(Z)V", (void*)android_view_ThreadedRenderer_setIsHighEndGfx},
+        {"nSetIsLowRam", "(Z)V", (void*)android_view_ThreadedRenderer_setIsLowRam},
+        {"nSetIsSystemOrPersistent", "(Z)V",
+         (void*)android_view_ThreadedRenderer_setIsSystemOrPersistent},
         {"nSyncAndDrawFrame", "(J[JI)I", (void*)android_view_ThreadedRenderer_syncAndDrawFrame},
         {"nDestroy", "(JJ)V", (void*)android_view_ThreadedRenderer_destroy},
         {"nRegisterAnimatingRenderNode", "(JJ)V",
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index ded2b06..1d24e71 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -16,6 +16,16 @@
 
 #include "CacheManager.h"
 
+#include <GrContextOptions.h>
+#include <SkExecutor.h>
+#include <SkGraphics.h>
+#include <SkMathPriv.h>
+#include <math.h>
+#include <utils/Trace.h>
+
+#include <set>
+
+#include "CanvasContext.h"
 #include "DeviceInfo.h"
 #include "Layer.h"
 #include "Properties.h"
@@ -25,40 +35,34 @@
 #include "pipeline/skia/SkiaMemoryTracer.h"
 #include "renderstate/RenderState.h"
 #include "thread/CommonPool.h"
-#include <utils/Trace.h>
-
-#include <GrContextOptions.h>
-#include <SkExecutor.h>
-#include <SkGraphics.h>
-#include <SkMathPriv.h>
-#include <math.h>
-#include <set>
 
 namespace android {
 namespace uirenderer {
 namespace renderthread {
 
-// This multiplier was selected based on historical review of cache sizes relative
-// to the screen resolution. This is meant to be a conservative default based on
-// that analysis. The 4.0f is used because the default pixel format is assumed to
-// be ARGB_8888.
-#define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f)
-#define BACKGROUND_RETENTION_PERCENTAGE (0.5f)
+CacheManager::CacheManager(RenderThread& thread)
+        : mRenderThread(thread), mMemoryPolicy(loadMemoryPolicy()) {
+    mMaxSurfaceArea = static_cast<size_t>((DeviceInfo::getWidth() * DeviceInfo::getHeight()) *
+                                          mMemoryPolicy.initialMaxSurfaceAreaScale);
+    setupCacheLimits();
+}
 
-CacheManager::CacheManager()
-        : mMaxSurfaceArea(DeviceInfo::getWidth() * DeviceInfo::getHeight())
-        , mMaxResourceBytes(mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER)
-        , mBackgroundResourceBytes(mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE)
-        // This sets the maximum size for a single texture atlas in the GPU font cache. If
-        // necessary, the cache can allocate additional textures that are counted against the
-        // total cache limits provided to Skia.
-        , mMaxGpuFontAtlasBytes(GrNextSizePow2(mMaxSurfaceArea))
-        // This sets the maximum size of the CPU font cache to be at least the same size as the
-        // total number of GPU font caches (i.e. 4 separate GPU atlases).
-        , mMaxCpuFontCacheBytes(
-                  std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit()))
-        , mBackgroundCpuFontCacheBytes(mMaxCpuFontCacheBytes * BACKGROUND_RETENTION_PERCENTAGE) {
+void CacheManager::setupCacheLimits() {
+    mMaxResourceBytes = mMaxSurfaceArea * mMemoryPolicy.surfaceSizeMultiplier;
+    mBackgroundResourceBytes = mMaxResourceBytes * mMemoryPolicy.backgroundRetentionPercent;
+    // This sets the maximum size for a single texture atlas in the GPU font cache. If
+    // necessary, the cache can allocate additional textures that are counted against the
+    // total cache limits provided to Skia.
+    mMaxGpuFontAtlasBytes = GrNextSizePow2(mMaxSurfaceArea);
+    // This sets the maximum size of the CPU font cache to be at least the same size as the
+    // total number of GPU font caches (i.e. 4 separate GPU atlases).
+    mMaxCpuFontCacheBytes = std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit());
+    mBackgroundCpuFontCacheBytes = mMaxCpuFontCacheBytes * mMemoryPolicy.backgroundRetentionPercent;
+
     SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
+    if (mGrContext) {
+        mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+    }
 }
 
 void CacheManager::reset(sk_sp<GrDirectContext> context) {
@@ -69,6 +73,7 @@
     if (context) {
         mGrContext = std::move(context);
         mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+        mLastDeferredCleanup = systemTime(CLOCK_MONOTONIC);
     }
 }
 
@@ -96,7 +101,7 @@
     contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
 }
 
-void CacheManager::trimMemory(TrimMemoryMode mode) {
+void CacheManager::trimMemory(TrimLevel mode) {
     if (!mGrContext) {
         return;
     }
@@ -104,21 +109,28 @@
     // flush and submit all work to the gpu and wait for it to finish
     mGrContext->flushAndSubmit(/*syncCpu=*/true);
 
+    if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) {
+        mode = TrimLevel::COMPLETE;
+    }
+
     switch (mode) {
-        case TrimMemoryMode::Complete:
+        case TrimLevel::COMPLETE:
             mGrContext->freeGpuResources();
             SkGraphics::PurgeAllCaches();
+            mRenderThread.destroyRenderingContext();
             break;
-        case TrimMemoryMode::UiHidden:
+        case TrimLevel::UI_HIDDEN:
             // Here we purge all the unlocked scratch resources and then toggle the resources cache
             // limits between the background and max amounts. This causes the unlocked resources
             // that have persistent data to be purged in LRU order.
-            mGrContext->purgeUnlockedResources(true);
             mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
-            mGrContext->setResourceCacheLimit(mMaxResourceBytes);
             SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
+            mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
+            mGrContext->setResourceCacheLimit(mMaxResourceBytes);
             SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
             break;
+        default:
+            break;
     }
 }
 
@@ -147,11 +159,29 @@
 }
 
 void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {
+    log.appendFormat(R"(Memory policy:
+  Max surface area: %zu
+  Max resource usage: %.2fMB (x%.0f)
+  Background retention: %.0f%% (altUiHidden = %s)
+)",
+                     mMaxSurfaceArea, mMaxResourceBytes / 1000000.f,
+                     mMemoryPolicy.surfaceSizeMultiplier,
+                     mMemoryPolicy.backgroundRetentionPercent * 100.0f,
+                     mMemoryPolicy.useAlternativeUiHidden ? "true" : "false");
+    if (Properties::isSystemOrPersistent) {
+        log.appendFormat("  IsSystemOrPersistent\n");
+    }
+    log.appendFormat("  GPU Context timeout: %" PRIu64 "\n", ns2s(mMemoryPolicy.contextTimeout));
+    size_t stoppedContexts = 0;
+    for (auto context : mCanvasContexts) {
+        if (context->isStopped()) stoppedContexts++;
+    }
+    log.appendFormat("Contexts: %zu (stopped = %zu)\n", mCanvasContexts.size(), stoppedContexts);
+
     if (!mGrContext) {
-        log.appendFormat("No valid cache instance.\n");
+        log.appendFormat("No GPU context.\n");
         return;
     }
-
     std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
             {"skia/sk_resource_cache/bitmap_", "Bitmaps"},
             {"skia/sk_resource_cache/rrect-blur_", "Masks"},
@@ -199,6 +229,8 @@
 }
 
 void CacheManager::onFrameCompleted() {
+    cancelDestroyContext();
+    mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
     if (ATRACE_ENABLED()) {
         static skiapipeline::ATraceMemoryDump tracer;
         tracer.startFrame();
@@ -210,11 +242,82 @@
     }
 }
 
-void CacheManager::performDeferredCleanup(nsecs_t cleanupOlderThanMillis) {
-    if (mGrContext) {
-        mGrContext->performDeferredCleanup(
-            std::chrono::milliseconds(cleanupOlderThanMillis),
-            /* scratchResourcesOnly */true);
+void CacheManager::onThreadIdle() {
+    if (!mGrContext || mFrameCompletions.size() == 0) return;
+
+    const nsecs_t now = systemTime(CLOCK_MONOTONIC);
+    // Rate limiting
+    if ((now - mLastDeferredCleanup) < 25_ms) {
+        mLastDeferredCleanup = now;
+        const nsecs_t frameCompleteNanos = mFrameCompletions[0];
+        const nsecs_t frameDiffNanos = now - frameCompleteNanos;
+        const nsecs_t cleanupMillis =
+                ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
+        mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
+                                           mMemoryPolicy.purgeScratchOnly);
+    }
+}
+
+void CacheManager::scheduleDestroyContext() {
+    if (mMemoryPolicy.contextTimeout > 0) {
+        mRenderThread.queue().postDelayed(mMemoryPolicy.contextTimeout,
+                                          [this, genId = mGenerationId] {
+                                              if (mGenerationId != genId) return;
+                                              // GenID should have already stopped this, but just in
+                                              // case
+                                              if (!areAllContextsStopped()) return;
+                                              mRenderThread.destroyRenderingContext();
+                                          });
+    }
+}
+
+void CacheManager::cancelDestroyContext() {
+    if (mIsDestructionPending) {
+        mIsDestructionPending = false;
+        mGenerationId++;
+    }
+}
+
+bool CacheManager::areAllContextsStopped() {
+    for (auto context : mCanvasContexts) {
+        if (!context->isStopped()) return false;
+    }
+    return true;
+}
+
+void CacheManager::checkUiHidden() {
+    if (!mGrContext) return;
+
+    if (mMemoryPolicy.useAlternativeUiHidden && areAllContextsStopped()) {
+        trimMemory(TrimLevel::UI_HIDDEN);
+    }
+}
+
+void CacheManager::registerCanvasContext(CanvasContext* context) {
+    mCanvasContexts.push_back(context);
+    cancelDestroyContext();
+}
+
+void CacheManager::unregisterCanvasContext(CanvasContext* context) {
+    std::erase(mCanvasContexts, context);
+    checkUiHidden();
+    if (mCanvasContexts.empty()) {
+        scheduleDestroyContext();
+    }
+}
+
+void CacheManager::onContextStopped(CanvasContext* context) {
+    checkUiHidden();
+    if (mMemoryPolicy.releaseContextOnStoppedOnly && areAllContextsStopped()) {
+        scheduleDestroyContext();
+    }
+}
+
+void CacheManager::notifyNextFrameSize(int width, int height) {
+    int frameArea = width * height;
+    if (frameArea > mMaxSurfaceArea) {
+        mMaxSurfaceArea = frameArea;
+        setupCacheLimits();
     }
 }
 
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index af82672..d21ac9b 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -22,7 +22,11 @@
 #endif
 #include <SkSurface.h>
 #include <utils/String8.h>
+
 #include <vector>
+
+#include "MemoryPolicy.h"
+#include "utils/RingBuffer.h"
 #include "utils/TimeUtils.h"
 
 namespace android {
@@ -35,17 +39,15 @@
 
 namespace renderthread {
 
-class IRenderPipeline;
 class RenderThread;
+class CanvasContext;
 
 class CacheManager {
 public:
-    enum class TrimMemoryMode { Complete, UiHidden };
-
 #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
     void configureContext(GrContextOptions* context, const void* identity, ssize_t size);
 #endif
-    void trimMemory(TrimMemoryMode mode);
+    void trimMemory(TrimLevel mode);
     void trimStaleResources();
     void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
     void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
@@ -53,30 +55,50 @@
     size_t getCacheSize() const { return mMaxResourceBytes; }
     size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
     void onFrameCompleted();
+    void notifyNextFrameSize(int width, int height);
 
-    void performDeferredCleanup(nsecs_t cleanupOlderThanMillis);
+    void onThreadIdle();
+
+    void registerCanvasContext(CanvasContext* context);
+    void unregisterCanvasContext(CanvasContext* context);
+    void onContextStopped(CanvasContext* context);
 
 private:
     friend class RenderThread;
 
-    explicit CacheManager();
+    explicit CacheManager(RenderThread& thread);
+    void setupCacheLimits();
+    bool areAllContextsStopped();
+    void checkUiHidden();
+    void scheduleDestroyContext();
+    void cancelDestroyContext();
 
 #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
     void reset(sk_sp<GrDirectContext> grContext);
 #endif
     void destroy();
 
-    const size_t mMaxSurfaceArea;
+    RenderThread& mRenderThread;
+    const MemoryPolicy& mMemoryPolicy;
 #ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
     sk_sp<GrDirectContext> mGrContext;
 #endif
 
-    const size_t mMaxResourceBytes;
-    const size_t mBackgroundResourceBytes;
+    size_t mMaxSurfaceArea = 0;
 
-    const size_t mMaxGpuFontAtlasBytes;
-    const size_t mMaxCpuFontCacheBytes;
-    const size_t mBackgroundCpuFontCacheBytes;
+    size_t mMaxResourceBytes = 0;
+    size_t mBackgroundResourceBytes = 0;
+
+    size_t mMaxGpuFontAtlasBytes = 0;
+    size_t mMaxCpuFontCacheBytes = 0;
+    size_t mBackgroundCpuFontCacheBytes = 0;
+
+    std::vector<CanvasContext*> mCanvasContexts;
+    RingBuffer<uint64_t, 100> mFrameCompletions;
+
+    nsecs_t mLastDeferredCleanup = 0;
+    bool mIsDestructionPending = false;
+    uint32_t mGenerationId = 0;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 75d3ff7..6a0c5a8 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -42,9 +42,6 @@
 #include "utils/GLUtils.h"
 #include "utils/TimeUtils.h"
 
-#define TRIM_MEMORY_COMPLETE 80
-#define TRIM_MEMORY_UI_HIDDEN 20
-
 #define LOG_FRAMETIME_MMA 0
 
 #if LOG_FRAMETIME_MMA
@@ -122,6 +119,7 @@
         , mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
         , mContentDrawBounds(0, 0, 0, 0)
         , mRenderPipeline(std::move(renderPipeline)) {
+    mRenderThread.cacheManager().registerCanvasContext(this);
     rootRenderNode->makeRoot();
     mRenderNodes.emplace_back(rootRenderNode);
     mProfiler.setDensity(DeviceInfo::getDensity());
@@ -133,6 +131,7 @@
         node->clearRoot();
     }
     mRenderNodes.clear();
+    mRenderThread.cacheManager().unregisterCanvasContext(this);
 }
 
 void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -154,6 +153,7 @@
     freePrefetchedLayers();
     destroyHardwareResources();
     mAnimationContext->destroy();
+    mRenderThread.cacheManager().onContextStopped(this);
 }
 
 static void setBufferCount(ANativeWindow* window) {
@@ -251,6 +251,7 @@
             mGenerationID++;
             mRenderThread.removeFrameCallback(this);
             mRenderPipeline->onStop();
+            mRenderThread.cacheManager().onContextStopped(this);
         } else if (mIsDirty && hasSurface()) {
             mRenderThread.postFrameCallback(this);
         }
@@ -461,7 +462,6 @@
 }
 
 void CanvasContext::stopDrawing() {
-    cleanupResources();
     mRenderThread.removeFrameCallback(this);
     mAnimationContext->pauseAnimators();
     mGenerationID++;
@@ -648,25 +648,10 @@
         }
     }
 
-    cleanupResources();
     mRenderThread.cacheManager().onFrameCompleted();
     return mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
 }
 
-void CanvasContext::cleanupResources() {
-    auto& tracker = mJankTracker.frames();
-    auto size = tracker.size();
-    auto capacity = tracker.capacity();
-    if (size == capacity) {
-        nsecs_t nowNanos = systemTime(SYSTEM_TIME_MONOTONIC);
-        nsecs_t frameCompleteNanos =
-            tracker[0].get(FrameInfoIndex::FrameCompleted);
-        nsecs_t frameDiffNanos = nowNanos - frameCompleteNanos;
-        nsecs_t cleanupMillis = ns2ms(std::max(frameDiffNanos, 10_s));
-        mRenderThread.cacheManager().performDeferredCleanup(cleanupMillis);
-    }
-}
-
 void CanvasContext::reportMetricsWithPresentTime() {
     {  // acquire lock
         std::scoped_lock lock(mFrameMetricsReporterMutex);
@@ -790,6 +775,7 @@
     SkISize size;
     size.fWidth = ANativeWindow_getWidth(anw);
     size.fHeight = ANativeWindow_getHeight(anw);
+    mRenderThread.cacheManager().notifyNextFrameSize(size.fWidth, size.fHeight);
     return size;
 }
 
@@ -868,18 +854,6 @@
     }
 }
 
-void CanvasContext::trimMemory(RenderThread& thread, int level) {
-    ATRACE_CALL();
-    if (!thread.getGrContext()) return;
-    ATRACE_CALL();
-    if (level >= TRIM_MEMORY_COMPLETE) {
-        thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
-        thread.destroyRenderingContext();
-    } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
-        thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
-    }
-}
-
 DeferredLayerUpdater* CanvasContext::createTextureLayer() {
     return mRenderPipeline->createTextureLayer();
 }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 951ee21..748ab96 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -127,6 +127,7 @@
     void setSurfaceControl(ASurfaceControl* surfaceControl);
     bool pauseSurface();
     void setStopped(bool stopped);
+    bool isStopped() { return mStopped || !hasSurface(); }
     bool hasSurface() const { return mNativeSurface.get(); }
     void allocateBuffers();
 
@@ -148,7 +149,6 @@
     void markLayerInUse(RenderNode* node);
 
     void destroyHardwareResources();
-    static void trimMemory(RenderThread& thread, int level);
 
     DeferredLayerUpdater* createTextureLayer();
 
@@ -330,8 +330,6 @@
 
     std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
     std::function<void()> mPrepareSurfaceControlForWebviewCallback;
-
-    void cleanupResources();
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 40a0bac..3324715 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -196,7 +196,8 @@
     // Avoid creating a RenderThread to do a trimMemory.
     if (RenderThread::hasInstance()) {
         RenderThread& thread = RenderThread::getInstance();
-        thread.queue().post([&thread, level]() { CanvasContext::trimMemory(thread, level); });
+        const auto trimLevel = static_cast<TrimLevel>(level);
+        thread.queue().post([&thread, trimLevel]() { thread.trimMemory(trimLevel); });
     }
 }
 
@@ -205,7 +206,7 @@
         RenderThread& thread = RenderThread::getInstance();
         thread.queue().post([&thread]() {
             if (thread.getGrContext()) {
-                thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+                thread.cacheManager().trimMemory(TrimLevel::COMPLETE);
             }
         });
     }
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 3ff4081..7a7f1ab 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -251,7 +251,7 @@
     mEglManager = new EglManager();
     mRenderState = new RenderState(*this);
     mVkManager = VulkanManager::getInstance();
-    mCacheManager = new CacheManager();
+    mCacheManager = new CacheManager(*this);
 }
 
 void RenderThread::setupFrameInterval() {
@@ -453,6 +453,8 @@
             // next vsync (oops), so none of the callbacks are run.
             requestVsync();
         }
+
+        mCacheManager->onThreadIdle();
     }
 
     return false;
@@ -502,6 +504,11 @@
     HardwareBitmapUploader::initialize();
 }
 
+void RenderThread::trimMemory(TrimLevel level) {
+    ATRACE_CALL();
+    cacheManager().trimMemory(level);
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index c1f6790..0a89e5e 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -17,11 +17,11 @@
 #ifndef RENDERTHREAD_H_
 #define RENDERTHREAD_H_
 
-#include <surface_control_private.h>
 #include <GrDirectContext.h>
 #include <SkBitmap.h>
 #include <cutils/compiler.h>
 #include <private/android/choreographer.h>
+#include <surface_control_private.h>
 #include <thread/ThreadBase.h>
 #include <utils/Looper.h>
 #include <utils/Thread.h>
@@ -31,6 +31,7 @@
 #include <set>
 
 #include "CacheManager.h"
+#include "MemoryPolicy.h"
 #include "ProfileDataContainer.h"
 #include "RenderTask.h"
 #include "TimeLord.h"
@@ -172,6 +173,8 @@
         return mASurfaceControlFunctions;
     }
 
+    void trimMemory(TrimLevel level);
+
     /**
      * isCurrent provides a way to query, if the caller is running on
      * the render thread.
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index edd3e4e..df06ead 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -58,7 +58,7 @@
     ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext));
 
     // attempt to trim all memory while we still hold strong refs
-    renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+    renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
     ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
 
     // free the surfaces
@@ -75,11 +75,11 @@
     ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() < purgeableBytes);
 
     // UI hidden and make sure only some got purged (unique should remain)
-    renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
+    renderThread.cacheManager().trimMemory(TrimLevel::UI_HIDDEN);
     ASSERT_TRUE(0 < grContext->getResourceCachePurgeableBytes());
     ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() > getCacheUsage(grContext));
 
     // complete and make sure all get purged
-    renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+    renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
     ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
 }