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/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index cf5f10b..7a9f3c1 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6662,6 +6662,9 @@
 
         // Pass the current context to HardwareRenderer
         HardwareRenderer.setContextForInit(getSystemContext());
+        if (data.persistent) {
+            HardwareRenderer.setIsSystemOrPersistent();
+        }
 
         // Instrumentation info affects the class loader, so load it before
         // setting up the app context.
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index c97eb73..d77e882 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -196,8 +196,6 @@
      */
     public static boolean sRendererEnabled = true;
 
-    public static boolean sTrimForeground = false;
-
     /**
      * Controls whether or not the renderer should aggressively trim
      * memory. Note that this must not be set for any process that uses
@@ -205,9 +203,10 @@
      * that do not go into the background.
      */
     public static void enableForegroundTrimming() {
-        sTrimForeground = true;
+        // TODO: Remove
     }
 
+
     /**
      * Initialize HWUI for being in a system process like system_server
      * Should not be called in non-system processes
@@ -218,9 +217,8 @@
         // process.
         if (!ActivityManager.isHighEndGfx()) {
             sRendererEnabled = false;
-        } else {
-            enableForegroundTrimming();
         }
+        setIsSystemOrPersistent();
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b6f775d..28fa77c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1766,9 +1766,6 @@
                 mAppVisibilityChanged = true;
                 scheduleTraversals();
             }
-            if (!mAppVisible) {
-                WindowManagerGlobal.trimForeground();
-            }
             AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
         }
     }
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index d377565..4a9dc5b 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -19,9 +19,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityManager;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ComponentCallbacks2;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
@@ -524,9 +522,6 @@
             }
             allViewsRemoved = mRoots.isEmpty();
         }
-        if (ThreadedRenderer.sTrimForeground) {
-            doTrimForeground();
-        }
 
         // If we don't have any views anymore in our process, we no longer need the
         // InsetsAnimationThread to save some resources.
@@ -543,65 +538,9 @@
         return index;
     }
 
-    public static boolean shouldDestroyEglContext(int trimLevel) {
-        // On low-end gfx devices we trim when memory is moderate;
-        // on high-end devices we do this when low.
-        if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
-            return true;
-        }
-        if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
-                && !ActivityManager.isHighEndGfx()) {
-            return true;
-        }
-        return false;
-    }
-
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public void trimMemory(int level) {
-
-        if (shouldDestroyEglContext(level)) {
-            // Destroy all hardware surfaces and resources associated to
-            // known windows
-            synchronized (mLock) {
-                for (int i = mRoots.size() - 1; i >= 0; --i) {
-                    mRoots.get(i).destroyHardwareResources();
-                }
-            }
-            // Force a full memory flush
-            level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
-        }
-
         ThreadedRenderer.trimMemory(level);
-
-        if (ThreadedRenderer.sTrimForeground) {
-            doTrimForeground();
-        }
-    }
-
-    public static void trimForeground() {
-        if (ThreadedRenderer.sTrimForeground) {
-            WindowManagerGlobal wm = WindowManagerGlobal.getInstance();
-            wm.doTrimForeground();
-        }
-    }
-
-    private void doTrimForeground() {
-        boolean hasVisibleWindows = false;
-        synchronized (mLock) {
-            for (int i = mRoots.size() - 1; i >= 0; --i) {
-                final ViewRootImpl root = mRoots.get(i);
-                if (root.mView != null && root.getHostVisibility() == View.VISIBLE
-                        && root.mAttachInfo.mThreadedRenderer != null) {
-                    hasVisibleWindows = true;
-                } else {
-                    root.destroyHardwareResources();
-                }
-            }
-        }
-        if (!hasVisibleWindows) {
-            ThreadedRenderer.trimMemory(
-                    ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
-        }
     }
 
     public void dumpGfxInfo(FileDescriptor fd, String[] args) {
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 0e67f1f..c6731d1 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -47,9 +47,7 @@
 import java.io.FileDescriptor;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Optional;
 import java.util.concurrent.Executor;
-import java.util.stream.Stream;
 
 import sun.misc.Cleaner;
 
@@ -1142,6 +1140,16 @@
     }
 
     /**
+     * Sets whether or not the current process is a system or persistent process. Used to influence
+     * the chosen memory usage policy.
+     *
+     * @hide
+     **/
+    public static void setIsSystemOrPersistent() {
+        nSetIsSystemOrPersistent(true);
+    }
+
+    /**
      * Returns true if HardwareRender will produce output.
      *
      * This value is global to the process and affects all uses of HardwareRenderer,
@@ -1204,30 +1212,6 @@
     private static class ProcessInitializer {
         static ProcessInitializer sInstance = new ProcessInitializer();
 
-        // Magic values from android/data_space.h
-        private static final int INTERNAL_DATASPACE_SRGB = 142671872;
-        private static final int INTERNAL_DATASPACE_DISPLAY_P3 = 143261696;
-        private static final int INTERNAL_DATASPACE_SCRGB = 411107328;
-
-        private enum Dataspace {
-            DISPLAY_P3(ColorSpace.Named.DISPLAY_P3, INTERNAL_DATASPACE_DISPLAY_P3),
-            SCRGB(ColorSpace.Named.EXTENDED_SRGB, INTERNAL_DATASPACE_SCRGB),
-            SRGB(ColorSpace.Named.SRGB, INTERNAL_DATASPACE_SRGB);
-
-            private final ColorSpace.Named mColorSpace;
-            private final int mNativeDataspace;
-            Dataspace(ColorSpace.Named colorSpace, int nativeDataspace) {
-                this.mColorSpace = colorSpace;
-                this.mNativeDataspace = nativeDataspace;
-            }
-
-            static Optional<Dataspace> find(ColorSpace colorSpace) {
-                return Stream.of(Dataspace.values())
-                        .filter(d -> ColorSpace.get(d.mColorSpace).equals(colorSpace))
-                        .findFirst();
-            }
-        }
-
         private boolean mInitialized = false;
         private boolean mDisplayInitialized = false;
 
@@ -1296,6 +1280,7 @@
             initDisplayInfo();
 
             nSetIsHighEndGfx(ActivityManager.isHighEndGfx());
+            nSetIsLowRam(ActivityManager.isLowRamDeviceStatic());
             // Defensively clear out the context in case we were passed a context that can leak
             // if we live longer than it, e.g. an activity context.
             mContext = null;
@@ -1314,26 +1299,55 @@
                 return;
             }
 
-            Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
-            if (display == null) {
+            final Display defaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
+            if (defaultDisplay == null) {
                 Log.d(LOG_TAG, "Failed to find default display for display-based configuration");
                 return;
             }
 
-            Dataspace wideColorDataspace =
-                    Optional.ofNullable(display.getPreferredWideGamutColorSpace())
-                            .flatMap(Dataspace::find)
-                            // Default to SRGB if the display doesn't support wide color
-                            .orElse(Dataspace.SRGB);
+            final Display[] allDisplays = dm.getDisplays();
+            if (allDisplays.length == 0) {
+                Log.d(LOG_TAG, "Failed to query displays");
+                return;
+            }
 
-            // Grab the physical screen dimensions from the active display mode
-            // Strictly speaking the screen resolution may not always be constant - it is for
-            // sizing the font cache for the underlying rendering thread. Since it's a
-            // heuristic we don't need to be always 100% correct.
-            Mode activeMode = display.getMode();
-            nInitDisplayInfo(activeMode.getPhysicalWidth(), activeMode.getPhysicalHeight(),
-                    display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
-                    display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
+            final Mode activeMode = defaultDisplay.getMode();
+            final ColorSpace defaultWideColorSpace =
+                    defaultDisplay.getPreferredWideGamutColorSpace();
+            int wideColorDataspace = defaultWideColorSpace != null
+                    ? defaultWideColorSpace.getDataSpace() : 0;
+            // largest width & height are used to size the default HWUI cache sizes. So find the
+            // largest display resolution we could encounter & use that as the guidance. The actual
+            // memory policy in play will interpret these values differently.
+            int largestWidth = activeMode.getPhysicalWidth();
+            int largestHeight = activeMode.getPhysicalHeight();
+
+            for (int i = 0; i < allDisplays.length; i++) {
+                final Display display = allDisplays[i];
+                // Take the first wide gamut dataspace as the source of truth
+                // Possibly should do per-HardwareRenderer wide gamut dataspace so we can use the
+                // target display's ideal instead
+                if (wideColorDataspace == 0) {
+                    ColorSpace cs = display.getPreferredWideGamutColorSpace();
+                    if (cs != null) {
+                        wideColorDataspace = cs.getDataSpace();
+                    }
+                }
+                Mode[] modes = display.getSupportedModes();
+                for (int j = 0; j < modes.length; j++) {
+                    Mode mode = modes[j];
+                    int width = mode.getPhysicalWidth();
+                    int height = mode.getPhysicalHeight();
+                    if ((width * height) > (largestWidth * largestHeight)) {
+                        largestWidth = width;
+                        largestHeight = height;
+                    }
+                }
+            }
+
+            nInitDisplayInfo(largestWidth, largestHeight, defaultDisplay.getRefreshRate(),
+                    wideColorDataspace, defaultDisplay.getAppVsyncOffsetNanos(),
+                    defaultDisplay.getPresentationDeadlineNanos());
 
             mDisplayInitialized = true;
         }
@@ -1418,6 +1432,10 @@
 
     private static native void nSetIsHighEndGfx(boolean isHighEndGfx);
 
+    private static native void nSetIsLowRam(boolean isLowRam);
+
+    private static native void nSetIsSystemOrPersistent(boolean isSystemOrPersistent);
+
     private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
 
     private static native void nDestroy(long nativeProxy, long rootRenderNode);
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());
 }