Merge Android 24Q1 Release (ab/11220357)

Bug: 319669529
Merged-In: I46c7859ff042ee7aa9193757e5df8269f4892362
Change-Id: I0c7b5036c0b0f5f2caad551edb063350f6eb87e7
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index e0ccf17..14e8653 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -28,6 +28,19 @@
     ],
 }
 
+aconfig_declarations {
+    name: "hwui_flags",
+    package: "com.android.graphics.hwui.flags",
+    srcs: [
+        "aconfig/hwui_flags.aconfig",
+    ],
+}
+
+cc_aconfig_library {
+    name: "hwui_flags_cc_lib",
+    aconfig_declarations: "hwui_flags",
+}
+
 cc_defaults {
     name: "hwui_defaults",
     defaults: [
@@ -44,7 +57,7 @@
         "-DEGL_EGLEXT_PROTOTYPES",
         "-DGL_GLEXT_PROTOTYPES",
         "-DATRACE_TAG=ATRACE_TAG_VIEW",
-        "-DLOG_TAG=\"OpenGLRenderer\"",
+        "-DLOG_TAG=\"HWUI\"",
         "-Wall",
         "-Wthread-safety",
         "-Wno-unused-parameter",
@@ -122,6 +135,8 @@
                 "libcrypto",
                 "libsync",
                 "libui",
+                "aconfig_text_flags_c_lib",
+                "server_configurable_flags",
             ],
             static_libs: [
                 "libEGL_blobCache",
@@ -131,6 +146,7 @@
                 "libstatspull_lazy",
                 "libstatssocket_lazy",
                 "libtonemap",
+                "hwui_flags_cc_lib",
             ],
         },
         host: {
@@ -520,6 +536,7 @@
         "utils/Blur.cpp",
         "utils/Color.cpp",
         "utils/LinearAllocator.cpp",
+        "utils/TypefaceUtils.cpp",
         "utils/VectorDrawableUtils.cpp",
         "AnimationContext.cpp",
         "Animator.cpp",
@@ -681,11 +698,13 @@
     ],
 
     static_libs: [
+        "libflagtest",
         "libgmock",
         "libhwui_static",
     ],
     shared_libs: [
         "libmemunreachable",
+        "server_configurable_flags",
     ],
     srcs: [
         "tests/unit/main.cpp",
@@ -725,6 +744,7 @@
         "tests/unit/TestUtilsTests.cpp",
         "tests/unit/ThreadBaseTests.cpp",
         "tests/unit/TypefaceTests.cpp",
+        "tests/unit/UnderlineTest.cpp",
         "tests/unit/VectorDrawableTests.cpp",
         "tests/unit/WebViewFunctorManagerTests.cpp",
     ],
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index b656b6a..4d020c5 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -16,6 +16,11 @@
 
 #include "AutoBackendTextureRelease.h"
 
+#include <SkImage.h>
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#include <include/gpu/GrDirectContext.h>
+#include <include/gpu/GrBackendSurface.h>
+#include <include/gpu/MutableTextureState.h>
 #include "renderthread/RenderThread.h"
 #include "utils/Color.h"
 #include "utils/PaintUtils.h"
@@ -30,15 +35,47 @@
     AHardwareBuffer_Desc desc;
     AHardwareBuffer_describe(buffer, &desc);
     bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
-    GrBackendFormat backendFormat =
-            GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+
+    GrBackendFormat backendFormat;
+    GrBackendApi backend = context->backend();
+    if (backend == GrBackendApi::kOpenGL) {
+        backendFormat =
+                GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeGLBackendTexture(context,
+                                                             buffer,
+                                                             desc.width,
+                                                             desc.height,
+                                                             &mDeleteProc,
+                                                             &mUpdateProc,
+                                                             &mImageCtx,
+                                                             createProtectedImage,
+                                                             backendFormat,
+                                                             false);
+    } else if (backend == GrBackendApi::kVulkan) {
+        backendFormat =
+                GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
+                                                               buffer,
+                                                               desc.format,
+                                                               false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
+                                                                 buffer,
+                                                                 desc.width,
+                                                                 desc.height,
+                                                                 &mDeleteProc,
+                                                                 &mUpdateProc,
+                                                                 &mImageCtx,
+                                                                 createProtectedImage,
+                                                                 backendFormat,
+                                                                 false);
+    } else {
+        LOG_ALWAYS_FATAL("Unexpected backend %d", backend);
+    }
     LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
                         __FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
                                  ", AHardwareBuffer_Format==%" PRIu32 ".",
                         static_cast<int>(context->backend()), desc.format);
-    mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
-            context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
-            createProtectedImage, backendFormat, false);
     LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
                         __FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
                                  ", protected==%d",
@@ -70,7 +107,7 @@
 
 // releaseProc is invoked by SkImage, when texture is no longer in use.
 // "releaseContext" contains an "AutoBackendTextureRelease*".
-static void releaseProc(SkImage::ReleaseContext releaseContext) {
+static void releaseProc(SkImages::ReleaseContext releaseContext) {
     AutoBackendTextureRelease* textureRelease =
             reinterpret_cast<AutoBackendTextureRelease*>(releaseContext);
     textureRelease->unref(false);
@@ -83,10 +120,10 @@
     AHardwareBuffer_describe(buffer, &desc);
     SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
     // The following ref will be counteracted by Skia calling releaseProc, either during
-    // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must
-    // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure.
+    // BorrowTextureFrom if there is a failure, or later when SkImage is discarded. It must
+    // be called before BorrowTextureFrom, otherwise Skia may remove HWUI's ref on failure.
     ref();
-    mImage = SkImage::MakeFromTexture(
+    mImage = SkImages::BorrowTextureFrom(
             context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
             uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this);
 }
@@ -105,8 +142,8 @@
     LOG_ALWAYS_FATAL_IF(Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan);
     if (mBackendTexture.isValid()) {
         // Passing in VK_IMAGE_LAYOUT_UNDEFINED means we keep the old layout.
-        GrBackendSurfaceMutableState newState(VK_IMAGE_LAYOUT_UNDEFINED,
-                                              VK_QUEUE_FAMILY_FOREIGN_EXT);
+        skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED,
+                                            VK_QUEUE_FAMILY_FOREIGN_EXT);
 
         // The unref for this ref happens in the releaseProc passed into setBackendTextureState. The
         // releaseProc callback will be made when the work to set the new state has finished on the
diff --git a/libs/hwui/CanvasTransform.cpp b/libs/hwui/CanvasTransform.cpp
index cd4fae8..b667daf 100644
--- a/libs/hwui/CanvasTransform.cpp
+++ b/libs/hwui/CanvasTransform.cpp
@@ -80,6 +80,19 @@
 static void applyColorTransform(ColorTransform transform, SkPaint& paint) {
     if (transform == ColorTransform::None) return;
 
+    if (transform == ColorTransform::Invert) {
+        auto filter = SkHighContrastFilter::Make(
+                {/* grayscale= */ false, SkHighContrastConfig::InvertStyle::kInvertLightness,
+                 /* contrast= */ 0.0f});
+
+        if (paint.getColorFilter()) {
+            paint.setColorFilter(SkColorFilters::Compose(filter, paint.refColorFilter()));
+        } else {
+            paint.setColorFilter(filter);
+        }
+        return;
+    }
+
     SkColor newColor = transformColor(transform, paint.getColor());
     paint.setColor(newColor);
 
diff --git a/libs/hwui/CanvasTransform.h b/libs/hwui/CanvasTransform.h
index 291f4cf..288dca4 100644
--- a/libs/hwui/CanvasTransform.h
+++ b/libs/hwui/CanvasTransform.h
@@ -29,12 +29,15 @@
     Unknown = 0,
     Background = 1,
     Foreground = 2,
+    // Contains foreground (usually text), like a button or chip
+    Container = 3
 };
 
 enum class ColorTransform {
     None,
     Light,
     Dark,
+    Invert
 };
 
 // True if the paint was modified, false otherwise
diff --git a/libs/hwui/ColorFilter.h b/libs/hwui/ColorFilter.h
new file mode 100644
index 0000000..1a5b938
--- /dev/null
+++ b/libs/hwui/ColorFilter.h
@@ -0,0 +1,94 @@
+/*
+ * 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 COLORFILTER_H_
+#define COLORFILTER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "GraphicsJNI.h"
+#include "SkColorFilter.h"
+#include "SkiaWrapper.h"
+
+namespace android {
+namespace uirenderer {
+
+class ColorFilter : public SkiaWrapper<SkColorFilter> {
+public:
+    static ColorFilter* fromJava(jlong handle) { return reinterpret_cast<ColorFilter*>(handle); }
+
+protected:
+    ColorFilter() = default;
+};
+
+class BlendModeColorFilter : public ColorFilter {
+public:
+    BlendModeColorFilter(SkColor color, SkBlendMode mode) : mColor(color), mMode(mode) {}
+
+private:
+    sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Blend(mColor, mMode); }
+
+private:
+    const SkColor mColor;
+    const SkBlendMode mMode;
+};
+
+class LightingFilter : public ColorFilter {
+public:
+    LightingFilter(SkColor mul, SkColor add) : mMul(mul), mAdd(add) {}
+
+    void setMul(SkColor mul) {
+        mMul = mul;
+        discardInstance();
+    }
+
+    void setAdd(SkColor add) {
+        mAdd = add;
+        discardInstance();
+    }
+
+private:
+    sk_sp<SkColorFilter> createInstance() override { return SkColorFilters::Lighting(mMul, mAdd); }
+
+private:
+    SkColor mMul;
+    SkColor mAdd;
+};
+
+class ColorMatrixColorFilter : public ColorFilter {
+public:
+    ColorMatrixColorFilter(std::vector<float>&& matrix) : mMatrix(std::move(matrix)) {}
+
+    void setMatrix(std::vector<float>&& matrix) {
+        mMatrix = std::move(matrix);
+        discardInstance();
+    }
+
+private:
+    sk_sp<SkColorFilter> createInstance() override {
+        return SkColorFilters::Matrix(mMatrix.data());
+    }
+
+private:
+    std::vector<float> mMatrix;
+};
+
+}  // namespace uirenderer
+}  // namespace android
+
+#endif  // COLORFILTER_H_
diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp
index a8d170d..fd27641 100644
--- a/libs/hwui/DamageAccumulator.cpp
+++ b/libs/hwui/DamageAccumulator.cpp
@@ -242,6 +242,47 @@
     }
 }
 
+SkRect DamageAccumulator::computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const {
+    const DirtyStack* frame = mHead;
+    Matrix4 transform;
+    SkRect pretransformResult = bounds;
+    while (true) {
+        SkRect currentBounds = pretransformResult;
+        pretransformResult.setEmpty();
+        switch (frame->type) {
+            case TransformRenderNode: {
+                const RenderProperties& props = frame->renderNode->properties();
+                // Perform clipping
+                if (props.getClipDamageToBounds() && !currentBounds.isEmpty()) {
+                    if (!currentBounds.intersect(
+                                SkRect::MakeIWH(props.getWidth(), props.getHeight()))) {
+                        currentBounds.setEmpty();
+                    }
+                }
+
+                // apply all transforms
+                mapRect(props, currentBounds, &pretransformResult);
+                frame->renderNode->applyViewPropertyTransforms(transform);
+            } break;
+            case TransformMatrix4:
+                mapRect(frame->matrix4, currentBounds, &pretransformResult);
+                transform.multiply(*frame->matrix4);
+                break;
+            default:
+                pretransformResult = currentBounds;
+                break;
+        }
+        if (frame->prev == frame) break;
+        frame = frame->prev;
+    }
+    SkRect result;
+    Matrix4 globalToLocal;
+    globalToLocal.loadInverse(transform);
+    mapRect(&globalToLocal, pretransformResult, &result);
+    *outMatrix = transform;
+    return result;
+}
+
 void DamageAccumulator::dirty(float left, float top, float right, float bottom) {
     mHead->pendingDirty.join({left, top, right, bottom});
 }
diff --git a/libs/hwui/DamageAccumulator.h b/libs/hwui/DamageAccumulator.h
index c4249af..30bf706 100644
--- a/libs/hwui/DamageAccumulator.h
+++ b/libs/hwui/DamageAccumulator.h
@@ -61,6 +61,8 @@
 
     void computeCurrentTransform(Matrix4* outMatrix) const;
 
+    SkRect computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const;
+
     void finish(SkRect* totalDirty);
 
     struct StretchResult {
diff --git a/libs/hwui/DisplayList.h b/libs/hwui/DisplayList.h
index eb5878d..b1c5bf4 100644
--- a/libs/hwui/DisplayList.h
+++ b/libs/hwui/DisplayList.h
@@ -54,6 +54,8 @@
         mImpl->updateChildren(std::move(updateFn));
     }
 
+    void visit(std::function<void(const RenderNode&)> func) const { mImpl->visit(std::move(func)); }
+
     [[nodiscard]] explicit operator bool() const {
         return mImpl.get() != nullptr;
     }
@@ -143,6 +145,8 @@
         return mImpl && mImpl->hasText();
     }
 
+    [[nodiscard]] bool hasFill() const { return mImpl && mImpl->hasFill(); }
+
     void applyColorTransform(ColorTransform transform) {
         if (mImpl) {
             mImpl->applyColorTransform(transform);
diff --git a/libs/hwui/DisplayListOps.in b/libs/hwui/DisplayListOps.in
index a18ba1c..d21f07ef 100644
--- a/libs/hwui/DisplayListOps.in
+++ b/libs/hwui/DisplayListOps.in
@@ -14,7 +14,6 @@
  * limitations under the License.
  */
 
-X(Flush)
 X(Save)
 X(Restore)
 X(SaveLayer)
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
new file mode 100644
index 0000000..00d049c
--- /dev/null
+++ b/libs/hwui/FeatureFlags.h
@@ -0,0 +1,48 @@
+/*
+ * 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_HWUI_FEATURE_FLAGS_H
+#define ANDROID_HWUI_FEATURE_FLAGS_H
+
+#ifdef __ANDROID__
+#include <com_android_text_flags.h>
+#endif  // __ANDROID__
+
+namespace android {
+
+namespace text_feature {
+
+inline bool fix_double_underline() {
+#ifdef __ANDROID__
+    return com_android_text_flags_fix_double_underline();
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
+inline bool deprecate_ui_fonts() {
+#ifdef __ANDROID__
+    return com_android_text_flags_deprecate_ui_fonts();
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
+}  // namespace text_feature
+
+}  // namespace android
+
+#endif  // ANDROID_HWUI_FEATURE_FLAGS_H
diff --git a/libs/hwui/FrameInfo.cpp b/libs/hwui/FrameInfo.cpp
index 8191f5e..a958a09 100644
--- a/libs/hwui/FrameInfo.cpp
+++ b/libs/hwui/FrameInfo.cpp
@@ -15,6 +15,8 @@
  */
 #include "FrameInfo.h"
 
+#include <gui/TraceUtils.h>
+
 #include <cstring>
 
 namespace android {
@@ -51,6 +53,30 @@
 
 void FrameInfo::importUiThreadInfo(int64_t* info) {
     memcpy(mFrameInfo, info, UI_THREAD_FRAME_INFO_SIZE * sizeof(int64_t));
+    mSkippedFrameReason.reset();
+}
+
+const char* toString(SkippedFrameReason reason) {
+    switch (reason) {
+        case SkippedFrameReason::DrawingOff:
+            return "DrawingOff";
+        case SkippedFrameReason::ContextIsStopped:
+            return "ContextIsStopped";
+        case SkippedFrameReason::NothingToDraw:
+            return "NothingToDraw";
+        case SkippedFrameReason::NoOutputTarget:
+            return "NoOutputTarget";
+        case SkippedFrameReason::NoBuffer:
+            return "NoBuffer";
+        case SkippedFrameReason::AlreadyDrawn:
+            return "AlreadyDrawn";
+    }
+}
+
+void FrameInfo::setSkippedFrameReason(android::uirenderer::SkippedFrameReason reason) {
+    ATRACE_FORMAT_INSTANT("Frame skipped: %s", toString(reason));
+    addFlag(FrameInfoFlags::SkippedFrame);
+    mSkippedFrameReason = reason;
 }
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index b15b6cb..f7ad139 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -16,15 +16,17 @@
 #ifndef FRAMEINFO_H_
 #define FRAMEINFO_H_
 
-#include "utils/Macros.h"
-
 #include <cutils/compiler.h>
+#include <memory.h>
 #include <utils/Timers.h>
 
 #include <array>
-#include <memory.h>
+#include <optional>
 #include <string>
 
+#include "SkippedFrameInfo.h"
+#include "utils/Macros.h"
+
 namespace android {
 namespace uirenderer {
 
@@ -186,8 +188,14 @@
         return mFrameInfo[static_cast<int>(index)];
     }
 
+    void setSkippedFrameReason(SkippedFrameReason reason);
+    inline std::optional<SkippedFrameReason> getSkippedFrameReason() const {
+        return mSkippedFrameReason;
+    }
+
 private:
     int64_t mFrameInfo[static_cast<int>(FrameInfoIndex::NumIndexes)];
+    std::optional<SkippedFrameReason> mSkippedFrameReason;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/FrameInfoVisualizer.cpp b/libs/hwui/FrameInfoVisualizer.cpp
index 687e4dd..59f2169 100644
--- a/libs/hwui/FrameInfoVisualizer.cpp
+++ b/libs/hwui/FrameInfoVisualizer.cpp
@@ -148,7 +148,7 @@
     int fast_i = 0, janky_i = 0;
     // Set the bottom of all the shapes to the baseline
     for (int fi = mFrameSource.size() - 1; fi >= 0; fi--) {
-        if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
+        if (mFrameSource[fi].getSkippedFrameReason()) {
             continue;
         }
         float lineWidth = baseLineWidth;
@@ -181,7 +181,7 @@
     int janky_i = (mNumJankyRects - 1) * 4;
 
     for (size_t fi = 0; fi < mFrameSource.size(); fi++) {
-        if (mFrameSource[fi][FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame) {
+        if (mFrameSource[fi].getSkippedFrameReason()) {
             continue;
         }
 
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index b7e9999..16de21d 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -22,9 +22,11 @@
 #include <GLES2/gl2ext.h>
 #include <GLES3/gl3.h>
 #include <GrDirectContext.h>
+#include <GrTypes.h>
 #include <SkBitmap.h>
 #include <SkCanvas.h>
 #include <SkImage.h>
+#include <SkImageAndroid.h>
 #include <SkImageInfo.h>
 #include <SkRefCnt.h>
 #include <gui/TraceUtils.h>
@@ -262,8 +264,9 @@
           }
 
           sk_sp<SkImage> image =
-              SkImage::MakeFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(), ahb);
-          mGrContext->submit(true);
+              SkImages::TextureFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(),
+                                                           ahb);
+          mGrContext->submit(GrSyncCpu::kYes);
 
           uploadSucceeded = (image.get() != nullptr);
         });
diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp
index ca1312e7..21f4ca7 100644
--- a/libs/hwui/MemoryPolicy.cpp
+++ b/libs/hwui/MemoryPolicy.cpp
@@ -28,7 +28,10 @@
 constexpr static MemoryPolicy sDefaultMemoryPolicy;
 constexpr static MemoryPolicy sPersistentOrSystemPolicy{
         .contextTimeout = 10_s,
+        .minimumResourceRetention = 1_s,
+        .maximumResourceRetention = 10_s,
         .useAlternativeUiHidden = true,
+        .purgeScratchOnly = false,
 };
 constexpr static MemoryPolicy sLowRamPolicy{
         .useAlternativeUiHidden = true,
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
index 347daf34..e10dda9 100644
--- a/libs/hwui/MemoryPolicy.h
+++ b/libs/hwui/MemoryPolicy.h
@@ -53,6 +53,8 @@
     // 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;
+    // The maximum amount of time to hold onto items in the resource cache
+    nsecs_t maximumResourceRetention = 100000_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 = true;
diff --git a/libs/hwui/Mesh.h b/libs/hwui/Mesh.h
index 13e3c8e..69fda34 100644
--- a/libs/hwui/Mesh.h
+++ b/libs/hwui/Mesh.h
@@ -19,6 +19,7 @@
 
 #include <GrDirectContext.h>
 #include <SkMesh.h>
+#include <include/gpu/ganesh/SkMeshGanesh.h>
 #include <jni.h>
 #include <log/log.h>
 
@@ -143,21 +144,34 @@
         }
 
         if (mIsDirty || genId != mGenerationId) {
-            auto vb = SkMesh::MakeVertexBuffer(
-                    context, reinterpret_cast<const void*>(mVertexBufferData.data()),
-                    mVertexBufferData.size());
+            auto vertexData = reinterpret_cast<const void*>(mVertexBufferData.data());
+#ifdef __ANDROID__
+            auto vb = SkMeshes::MakeVertexBuffer(context,
+                                                 vertexData,
+                                                 mVertexBufferData.size());
+#else
+            auto vb = SkMeshes::MakeVertexBuffer(vertexData,
+                                                 mVertexBufferData.size());
+#endif
             auto meshMode = SkMesh::Mode(mMode);
             if (!mIndexBufferData.empty()) {
-                auto ib = SkMesh::MakeIndexBuffer(
-                        context, reinterpret_cast<const void*>(mIndexBufferData.data()),
-                        mIndexBufferData.size());
+                auto indexData = reinterpret_cast<const void*>(mIndexBufferData.data());
+#ifdef __ANDROID__
+                auto ib = SkMeshes::MakeIndexBuffer(context,
+                                                    indexData,
+                                                    mIndexBufferData.size());
+#else
+                auto ib = SkMeshes::MakeIndexBuffer(indexData,
+                                                    mIndexBufferData.size());
+#endif
                 mMesh = SkMesh::MakeIndexed(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
                                             ib, mIndexCount, mIndexOffset, mBuilder->fUniforms,
-                                            mBounds)
+                                            SkSpan<SkRuntimeEffect::ChildPtr>(), mBounds)
                                 .mesh;
             } else {
                 mMesh = SkMesh::Make(mMeshSpec, meshMode, vb, mVertexCount, mVertexOffset,
-                                     mBuilder->fUniforms, mBounds)
+                                     mBuilder->fUniforms, SkSpan<SkRuntimeEffect::ChildPtr>(),
+                                     mBounds)
                                 .mesh;
             }
             mIsDirty = false;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 09d3f05..6c3172a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -20,15 +20,26 @@
 #ifdef __ANDROID__
 #include "HWUIProperties.sysprop.h"
 #endif
-#include "SkTraceEventCommon.h"
+#include <android-base/properties.h>
+#include <cutils/compiler.h>
+#include <log/log.h>
 
 #include <algorithm>
 #include <cstdlib>
 #include <optional>
 
-#include <android-base/properties.h>
-#include <cutils/compiler.h>
-#include <log/log.h>
+#include "src/core/SkTraceEventCommon.h"
+
+#ifdef __ANDROID__
+#include <com_android_graphics_hwui_flags.h>
+namespace hwui_flags = com::android::graphics::hwui::flags;
+#else
+namespace hwui_flags {
+constexpr bool clip_surfaceviews() {
+    return false;
+}
+}  // namespace hwui_flags
+#endif
 
 namespace android {
 namespace uirenderer {
@@ -92,6 +103,8 @@
 
 float Properties::maxHdrHeadroomOn8bit = 5.f;  // TODO: Refine this number
 
+bool Properties::clipSurfaceViews = false;
+
 StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
 
 DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
@@ -159,6 +172,9 @@
     // call isDrawingEnabled to force loading of the property
     isDrawingEnabled();
 
+    clipSurfaceViews =
+            base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
+
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
 }
 
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index bb47744..bca57e9 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -325,6 +325,8 @@
 
     static float maxHdrHeadroomOn8bit;
 
+    static bool clipSurfaceViews;
+
     static StretchEffectBehavior getStretchEffectBehavior() {
         return stretchEffectBehavior;
     }
diff --git a/libs/hwui/Readback.cpp b/libs/hwui/Readback.cpp
index 045de35..afe4c38 100644
--- a/libs/hwui/Readback.cpp
+++ b/libs/hwui/Readback.cpp
@@ -21,6 +21,7 @@
 #include <SkCanvas.h>
 #include <SkColorSpace.h>
 #include <SkImage.h>
+#include <SkImageAndroid.h>
 #include <SkImageInfo.h>
 #include <SkMatrix.h>
 #include <SkPaint.h>
@@ -29,6 +30,7 @@
 #include <SkSamplingOptions.h>
 #include <SkSurface.h>
 #include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include <gui/TraceUtils.h>
 #include <private/android/AHardwareBufferHelpers.h>
 #include <shaders/shaders.h>
@@ -108,7 +110,8 @@
     sk_sp<SkColorSpace> colorSpace =
             DataSpaceToColorSpace(static_cast<android_dataspace>(dataspace));
     sk_sp<SkImage> image =
-            SkImage::MakeFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, colorSpace);
+            SkImages::DeferredFromAHardwareBuffer(sourceBuffer.get(), kPremul_SkAlphaType, 
+                                                  colorSpace);
 
     if (!image.get()) {
         return request->onCopyFinished(CopyResult::UnknownError);
@@ -171,16 +174,16 @@
     SkBitmap skBitmap = request->getDestinationBitmap(srcRect.width(), srcRect.height());
     SkBitmap* bitmap = &skBitmap;
     sk_sp<SkSurface> tmpSurface =
-            SkSurface::MakeRenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
-                                        bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
+            SkSurfaces::RenderTarget(mRenderThread.getGrContext(), skgpu::Budgeted::kYes,
+                                     bitmap->info(), 0, kTopLeft_GrSurfaceOrigin, nullptr);
 
     // if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
     // attempt to do the intermediate rendering step in 8888
     if (!tmpSurface.get()) {
         SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
-        tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
-                                                 skgpu::Budgeted::kYes,
-                                                 tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
+        tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+                                              skgpu::Budgeted::kYes,
+                                              tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
         if (!tmpSurface.get()) {
             ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
             return request->onCopyFinished(CopyResult::UnknownError);
@@ -346,19 +349,19 @@
      * a scaling issue (b/62262733) that was encountered when sampling from an EGLImage into a
      * software buffer.
      */
-    sk_sp<SkSurface> tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
-                                                              skgpu::Budgeted::kYes,
-                                                              bitmap->info(),
-                                                              0,
-                                                              kTopLeft_GrSurfaceOrigin, nullptr);
+    sk_sp<SkSurface> tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+                                                           skgpu::Budgeted::kYes,
+                                                           bitmap->info(),
+                                                           0,
+                                                           kTopLeft_GrSurfaceOrigin, nullptr);
 
     // if we can't generate a GPU surface that matches the destination bitmap (e.g. 565) then we
     // attempt to do the intermediate rendering step in 8888
     if (!tmpSurface.get()) {
         SkImageInfo tmpInfo = bitmap->info().makeColorType(SkColorType::kN32_SkColorType);
-        tmpSurface = SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
-                                                 skgpu::Budgeted::kYes,
-                                                 tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
+        tmpSurface = SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+                                              skgpu::Budgeted::kYes,
+                                              tmpInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr);
         if (!tmpSurface.get()) {
             ALOGW("Unable to generate GPU buffer in a format compatible with the provided bitmap");
             return false;
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 924fbd6..3b694c5 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -36,6 +36,7 @@
 #include "SkImageFilter.h"
 #include "SkImageInfo.h"
 #include "SkLatticeIter.h"
+#include "SkMesh.h"
 #include "SkPaint.h"
 #include "SkPicture.h"
 #include "SkRRect.h"
@@ -49,6 +50,7 @@
 #include "effects/GainmapRenderer.h"
 #include "include/gpu/GpuTypes.h"  // from Skia
 #include "include/gpu/GrDirectContext.h"
+#include "include/gpu/ganesh/SkMeshGanesh.h"
 #include "pipeline/skia/AnimatedDrawables.h"
 #include "pipeline/skia/FunctorDrawable.h"
 #ifdef __ANDROID__
@@ -107,11 +109,6 @@
 };
 static_assert(sizeof(Op) == 4, "");
 
-struct Flush final : Op {
-    static const auto kType = Type::Flush;
-    void draw(SkCanvas* c, const SkMatrix&) const { c->flush(); }
-};
-
 struct Save final : Op {
     static const auto kType = Type::Save;
     void draw(SkCanvas* c, const SkMatrix&) const { c->save(); }
@@ -532,24 +529,26 @@
     mutable bool isGpuBased;
     mutable GrDirectContext::DirectContextID contextId;
     void draw(SkCanvas* c, const SkMatrix&) const {
+#ifdef __ANDROID__
         GrDirectContext* directContext = c->recordingContext()->asDirectContext();
 
         GrDirectContext::DirectContextID id = directContext->directContextID();
         if (!isGpuBased || contextId != id) {
             sk_sp<SkMesh::VertexBuffer> vb =
-                    SkMesh::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
+                    SkMeshes::CopyVertexBuffer(directContext, cpuMesh.refVertexBuffer());
             if (!cpuMesh.indexBuffer()) {
                 gpuMesh = SkMesh::Make(cpuMesh.refSpec(), cpuMesh.mode(), vb, cpuMesh.vertexCount(),
                                        cpuMesh.vertexOffset(), cpuMesh.refUniforms(),
-                                       cpuMesh.bounds())
+                                       SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds())
                                   .mesh;
             } else {
                 sk_sp<SkMesh::IndexBuffer> ib =
-                        SkMesh::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
+                        SkMeshes::CopyIndexBuffer(directContext, cpuMesh.refIndexBuffer());
                 gpuMesh = SkMesh::MakeIndexed(cpuMesh.refSpec(), cpuMesh.mode(), vb,
                                               cpuMesh.vertexCount(), cpuMesh.vertexOffset(), ib,
                                               cpuMesh.indexCount(), cpuMesh.indexOffset(),
-                                              cpuMesh.refUniforms(), cpuMesh.bounds())
+                                              cpuMesh.refUniforms(),
+                                              SkSpan<SkRuntimeEffect::ChildPtr>(), cpuMesh.bounds())
                                   .mesh;
             }
 
@@ -558,6 +557,9 @@
         }
 
         c->drawMesh(gpuMesh, blender, paint);
+#else
+        c->drawMesh(cpuMesh, blender, paint);
+#endif
     }
 };
 
@@ -675,12 +677,11 @@
             // because the webview functor still doesn't respect the canvas clip stack.
             const SkIRect deviceBounds = c->getDeviceClipBounds();
             if (mLayerSurface == nullptr || c->imageInfo() != mLayerImageInfo) {
-                GrRecordingContext* directContext = c->recordingContext();
                 mLayerImageInfo =
                         c->imageInfo().makeWH(deviceBounds.width(), deviceBounds.height());
-                mLayerSurface = SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes,
-                                                            mLayerImageInfo, 0,
-                                                            kTopLeft_GrSurfaceOrigin, nullptr);
+                // SkCanvas::makeSurface returns a new surface that will be GPU-backed if
+                // canvas was also.
+                mLayerSurface = c->makeSurface(mLayerImageInfo);
             }
 
             SkCanvas* layerCanvas = mLayerSurface->getCanvas();
@@ -717,6 +718,27 @@
     return (value & (value - 1)) == 0;
 }
 
+template <typename T>
+constexpr bool doesPaintHaveFill(T& paint) {
+    using T1 = std::remove_cv_t<T>;
+    if constexpr (std::is_same_v<T1, SkPaint>) {
+        return paint.getStyle() != SkPaint::Style::kStroke_Style;
+    } else if constexpr (std::is_same_v<T1, SkPaint&>) {
+        return paint.getStyle() != SkPaint::Style::kStroke_Style;
+    } else if constexpr (std::is_same_v<T1, SkPaint*>) {
+        return paint && paint->getStyle() != SkPaint::Style::kStroke_Style;
+    } else if constexpr (std::is_same_v<T1, const SkPaint*>) {
+        return paint && paint->getStyle() != SkPaint::Style::kStroke_Style;
+    }
+
+    return false;
+}
+
+template <typename... Args>
+constexpr bool hasPaintWithFill(Args&&... args) {
+    return (... || doesPaintHaveFill(args));
+}
+
 template <typename T, typename... Args>
 void* DisplayListData::push(size_t pod, Args&&... args) {
     size_t skip = SkAlignPtr(sizeof(T) + pod);
@@ -735,6 +757,14 @@
     new (op) T{std::forward<Args>(args)...};
     op->type = (uint32_t)T::kType;
     op->skip = skip;
+
+    // check if this is a fill op or not, in case we need to avoid messing with it with force invert
+    if constexpr (!std::is_same_v<T, DrawTextBlob>) {
+        if (hasPaintWithFill(args...)) {
+            mHasFill = true;
+        }
+    }
+
     return op + 1;
 }
 
@@ -752,10 +782,6 @@
     }
 }
 
-void DisplayListData::flush() {
-    this->push<Flush>(0);
-}
-
 void DisplayListData::save() {
     this->push<Save>(0);
 }
@@ -1047,10 +1073,6 @@
     return nullptr;
 }
 
-void RecordingCanvas::onFlush() {
-    fDL->flush();
-}
-
 void RecordingCanvas::willSave() {
     mSaveCount++;
     fDL->save();
diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h
index 1f4ba5d..afadbfd 100644
--- a/libs/hwui/RecordingCanvas.h
+++ b/libs/hwui/RecordingCanvas.h
@@ -110,7 +110,7 @@
 
 class DisplayListData final {
 public:
-    DisplayListData() : mHasText(false) {}
+    DisplayListData() : mHasText(false), mHasFill(false) {}
     ~DisplayListData();
 
     void draw(SkCanvas* canvas) const;
@@ -121,14 +121,13 @@
     void applyColorTransform(ColorTransform transform);
 
     bool hasText() const { return mHasText; }
+    bool hasFill() const { return mHasFill; }
     size_t usedSize() const { return fUsed; }
     size_t allocatedSize() const { return fReserved; }
 
 private:
     friend class RecordingCanvas;
 
-    void flush();
-
     void save();
     void saveLayer(const SkRect*, const SkPaint*, const SkImageFilter*, SkCanvas::SaveLayerFlags);
     void saveBehind(const SkRect*);
@@ -194,6 +193,7 @@
     size_t fReserved = 0;
 
     bool mHasText : 1;
+    bool mHasFill : 1;
 };
 
 class RecordingCanvas final : public SkCanvasVirtualEnforcer<SkNoDrawCanvas> {
@@ -208,8 +208,6 @@
     void willRestore() override;
     bool onDoSaveBehind(const SkRect*) override;
 
-    void onFlush() override;
-
     void didConcat44(const SkM44&) override;
     void didSetM44(const SkM44&) override;
     void didScale(SkScalar, SkScalar) override;
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index a733d17..f526a28 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -28,16 +28,21 @@
 #include "DamageAccumulator.h"
 #include "pipeline/skia/SkiaDisplayList.h"
 #endif
-#include <gui/TraceUtils.h>
-#include "utils/MathUtils.h"
-#include "utils/StringUtils.h"
-
 #include <SkPathOps.h>
+#include <gui/TraceUtils.h>
+#include <ui/FatVector.h>
+
 #include <algorithm>
 #include <atomic>
 #include <sstream>
 #include <string>
-#include <ui/FatVector.h>
+
+#ifdef __ANDROID__
+#include "include/gpu/ganesh/SkImageGanesh.h"
+#endif
+#include "utils/ForceDark.h"
+#include "utils/MathUtils.h"
+#include "utils/StringUtils.h"
 
 namespace android {
 namespace uirenderer {
@@ -109,6 +114,13 @@
     output << std::endl;
 }
 
+void RenderNode::visit(std::function<void(const RenderNode&)> func) const {
+    func(*this);
+    if (mDisplayList) {
+        mDisplayList.visit(func);
+    }
+}
+
 int RenderNode::getUsageSize() {
     int size = sizeof(RenderNode);
     size += mStagingDisplayList.getUsedSize();
@@ -363,13 +375,18 @@
                mImageFilterClipBounds != clipBounds ||
                mTargetImageFilterLayerSurfaceGenerationId != layerSurfaceGenerationId) {
         // Otherwise create a new snapshot with the given filter and snapshot
-        mSnapshotResult.snapshot =
-                snapshot->makeWithFilter(context,
-                                         imageFilter,
-                                         subset,
-                                         clipBounds,
-                                         &mSnapshotResult.outSubset,
-                                         &mSnapshotResult.outOffset);
+#ifdef __ANDROID__
+        if (context) {
+            mSnapshotResult.snapshot = SkImages::MakeWithFilter(
+                    context, snapshot, imageFilter, subset, clipBounds, &mSnapshotResult.outSubset,
+                    &mSnapshotResult.outOffset);
+        } else
+#endif
+        {
+            mSnapshotResult.snapshot = SkImages::MakeWithFilter(
+                    snapshot, imageFilter, subset, clipBounds, &mSnapshotResult.outSubset,
+                    &mSnapshotResult.outOffset);
+        }
         mTargetImageFilter = sk_ref_sp(imageFilter);
         mImageFilterClipBounds = clipBounds;
         mTargetImageFilterLayerSurfaceGenerationId = layerSurfaceGenerationId;
@@ -387,16 +404,21 @@
     deleteDisplayList(observer, info);
     mDisplayList = std::move(mStagingDisplayList);
     if (mDisplayList) {
-        WebViewSyncData syncData {
-            .applyForceDark = info && !info->disableForceDark
-        };
+        WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)};
         mDisplayList.syncContents(syncData);
         handleForceDark(info);
     }
 }
 
+inline bool RenderNode::shouldEnableForceDark(TreeInfo* info) {
+    return CC_UNLIKELY(
+            info &&
+            (!info->disableForceDark ||
+             info->forceDarkType == android::uirenderer::ForceDarkType::FORCE_INVERT_COLOR_DARK));
+}
+
 void RenderNode::handleForceDark(android::uirenderer::TreeInfo *info) {
-    if (CC_LIKELY(!info || info->disableForceDark)) {
+    if (!shouldEnableForceDark(info)) {
         return;
     }
     auto usage = usageHint();
@@ -405,7 +427,13 @@
         children.push_back(node);
     });
     if (mDisplayList.hasText()) {
-        usage = UsageHint::Foreground;
+        if (mDisplayList.hasFill()) {
+            // Handle a special case for custom views that draw both text and background in the
+            // same RenderNode, which would otherwise be altered to white-on-white text.
+            usage = UsageHint::Container;
+        } else {
+            usage = UsageHint::Foreground;
+        }
     }
     if (usage == UsageHint::Unknown) {
         if (children.size() > 1) {
@@ -431,8 +459,13 @@
             drawn.join(bounds);
         }
     }
-    mDisplayList.applyColorTransform(
-            usage == UsageHint::Background ? ColorTransform::Dark : ColorTransform::Light);
+
+    if (usage == UsageHint::Container) {
+        mDisplayList.applyColorTransform(ColorTransform::Invert);
+    } else {
+        mDisplayList.applyColorTransform(usage == UsageHint::Background ? ColorTransform::Dark
+                                                                        : ColorTransform::Light);
+    }
 }
 
 void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 4d03bf1..c904542 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -129,10 +129,6 @@
 
     StretchMask& getStretchMask() { return mStretchMask; }
 
-    VirtualLightRefBase* getUserContext() const { return mUserContext.get(); }
-
-    void setUserContext(VirtualLightRefBase* context) { mUserContext = context; }
-
     bool isPropertyFieldDirty(DirtyPropertyMask field) const {
         return mDirtyPropertyFields & field;
     }
@@ -215,6 +211,8 @@
 
     void output(std::ostream& output, uint32_t level);
 
+    void visit(std::function<void(const RenderNode&)>) const;
+
     void setUsageHint(UsageHint usageHint) { mUsageHint = usageHint; }
 
     UsageHint usageHint() const { return mUsageHint; }
@@ -222,6 +220,7 @@
     int64_t uniqueId() const { return mUniqueId; }
 
     void setIsTextureView() { mIsTextureView = true; }
+    bool isTextureView() const { return mIsTextureView; }
 
     void markDrawStart(SkCanvas& canvas);
     void markDrawEnd(SkCanvas& canvas);
@@ -234,6 +233,7 @@
     void syncProperties();
     void syncDisplayList(TreeObserver& observer, TreeInfo* info);
     void handleForceDark(TreeInfo* info);
+    bool shouldEnableForceDark(TreeInfo* info);
 
     void prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer);
     void pushStagingPropertiesChanges(TreeInfo& info);
@@ -248,7 +248,6 @@
 
     const int64_t mUniqueId;
     String8 mName;
-    sp<VirtualLightRefBase> mUserContext;
 
     uint32_t mDirtyPropertyFields;
     RenderProperties mProperties;
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index ea9b6c9..008ea3a 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -47,6 +47,7 @@
 #include <utility>
 
 #include "CanvasProperty.h"
+#include "FeatureFlags.h"
 #include "Mesh.h"
 #include "NinePatchUtils.h"
 #include "VectorDrawable.h"
@@ -825,7 +826,9 @@
     sk_sp<SkTextBlob> textBlob(builder.make());
 
     applyLooper(&paintCopy, [&](const SkPaint& p) { mCanvas->drawTextBlob(textBlob, 0, 0, p); });
-    drawTextDecorations(x, y, totalAdvance, paintCopy);
+    if (!text_feature::fix_double_underline()) {
+        drawTextDecorations(x, y, totalAdvance, paintCopy);
+    }
 }
 
 void SkiaCanvas::drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
diff --git a/libs/hwui/SkiaCanvas.h b/libs/hwui/SkiaCanvas.h
index 9cb50ed..4bf1790 100644
--- a/libs/hwui/SkiaCanvas.h
+++ b/libs/hwui/SkiaCanvas.h
@@ -175,7 +175,7 @@
                                   const Paint& paint, const SkPath& path, size_t start,
                                   size_t end) override;
 
-    void onFilterPaint(Paint& paint);
+    virtual void onFilterPaint(Paint& paint);
 
     Paint filterPaint(const Paint& src) {
         Paint dst(src);
diff --git a/libs/hwui/SkiaInterpolator.cpp b/libs/hwui/SkiaInterpolator.cpp
index b58f517..c67b135 100644
--- a/libs/hwui/SkiaInterpolator.cpp
+++ b/libs/hwui/SkiaInterpolator.cpp
@@ -18,9 +18,8 @@
 
 #include "include/core/SkScalar.h"
 #include "include/core/SkTypes.h"
-#include "include/private/SkFixed.h"
-#include "src/core/SkTSearch.h"
 
+#include <cstdlib>
 #include <log/log.h>
 
 typedef int Dot14;
@@ -41,18 +40,18 @@
     if (x <= 0) {
         return 0;
     }
-    if (x >= SK_Scalar1) {
+    if (x >= 1.0f) {
         return Dot14_ONE;
     }
-    return SkScalarToFixed(x) >> 2;
+    return static_cast<Dot14>(x * Dot14_ONE);
 }
 
 static float SkUnitCubicInterp(float value, float bx, float by, float cx, float cy) {
     // pin to the unit-square, and convert to 2.14
     Dot14 x = pin_and_convert(value);
 
-    if (x == 0) return 0;
-    if (x == Dot14_ONE) return SK_Scalar1;
+    if (x == 0) return 0.0f;
+    if (x == Dot14_ONE) return 1.0f;
 
     Dot14 b = pin_and_convert(bx);
     Dot14 c = pin_and_convert(cx);
@@ -84,7 +83,7 @@
     A = 3 * b;
     B = 3 * (c - 2 * b);
     C = 3 * (b - c) + Dot14_ONE;
-    return SkFixedToScalar(eval_cubic(t, A, B, C) << 2);
+    return Dot14ToFloat(eval_cubic(t, A, B, C));
 }
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
@@ -104,7 +103,7 @@
     fFlags = 0;
     fElemCount = static_cast<uint8_t>(elemCount);
     fFrameCount = static_cast<int16_t>(frameCount);
-    fRepeat = SK_Scalar1;
+    fRepeat = 1.0f;
     if (fStorage) {
         free(fStorage);
         fStorage = nullptr;
@@ -136,17 +135,46 @@
 
 float SkiaInterpolatorBase::ComputeRelativeT(SkMSec time, SkMSec prevTime, SkMSec nextTime,
                                              const float blend[4]) {
-    SkASSERT(time > prevTime && time < nextTime);
+    LOG_FATAL_IF(time < prevTime || time > nextTime);
 
     float t = (float)(time - prevTime) / (float)(nextTime - prevTime);
     return blend ? SkUnitCubicInterp(t, blend[0], blend[1], blend[2], blend[3]) : t;
 }
 
+// Returns the index of where the item is or the bit not of the index
+// where the item should go in order to keep arr sorted in ascending order.
+int SkiaInterpolatorBase::binarySearch(const SkTimeCode* arr, int count, SkMSec target) {
+    if (count <= 0) {
+        return ~0;
+    }
+
+    int lo = 0;
+    int hi = count - 1;
+
+    while (lo < hi) {
+        int mid = (hi + lo) / 2;
+        SkMSec elem = arr[mid].fTime;
+        if (elem == target) {
+            return mid;
+        } else if (elem < target) {
+            lo = mid + 1;
+        } else {
+            hi = mid;
+        }
+    }
+    // Check to see if target is greater or less than where we stopped
+    if (target < arr[lo].fTime) {
+        return ~lo;
+    }
+    // e.g. it should go at the end.
+    return ~(lo + 1);
+}
+
 SkiaInterpolatorBase::Result SkiaInterpolatorBase::timeToT(SkMSec time, float* T, int* indexPtr,
                                                            bool* exactPtr) const {
-    SkASSERT(fFrameCount > 0);
+    LOG_FATAL_IF(fFrameCount <= 0);
     Result result = kNormal_Result;
-    if (fRepeat != SK_Scalar1) {
+    if (fRepeat != 1.0f) {
         SkMSec startTime = 0, endTime = 0;  // initialize to avoid warning
         this->getDuration(&startTime, &endTime);
         SkMSec totalTime = endTime - startTime;
@@ -168,10 +196,8 @@
         time = offsetTime + startTime;
     }
 
-    int index = SkTSearch<SkMSec>(&fTimes[0].fTime, fFrameCount, time, sizeof(SkTimeCode));
-
+    int index = SkiaInterpolatorBase::binarySearch(fTimes, fFrameCount, time);
     bool exact = true;
-
     if (index < 0) {
         index = ~index;
         if (index == 0) {
@@ -184,10 +210,11 @@
             }
             result = kFreezeEnd_Result;
         } else {
+            // Need to interpolate between two frames.
             exact = false;
         }
     }
-    SkASSERT(index < fFrameCount);
+    LOG_FATAL_IF(index >= fFrameCount);
     const SkTimeCode* nextTime = &fTimes[index];
     SkMSec nextT = nextTime[0].fTime;
     if (exact) {
@@ -207,7 +234,7 @@
 }
 
 SkiaInterpolator::SkiaInterpolator(int elemCount, int frameCount) {
-    SkASSERT(elemCount > 0);
+    LOG_FATAL_IF(elemCount <= 0);
     this->reset(elemCount, frameCount);
 }
 
@@ -221,21 +248,19 @@
     fValues = (float*)((char*)fStorage + sizeof(SkTimeCode) * frameCount);
 }
 
-#define SK_Fixed1Third (SK_Fixed1 / 3)
-#define SK_Fixed2Third (SK_Fixed1 * 2 / 3)
-
 static const float gIdentityBlend[4] = {0.33333333f, 0.33333333f, 0.66666667f, 0.66666667f};
 
 bool SkiaInterpolator::setKeyFrame(int index, SkMSec time, const float values[],
                                    const float blend[4]) {
-    SkASSERT(values != nullptr);
+    LOG_FATAL_IF(values == nullptr);
 
     if (blend == nullptr) {
         blend = gIdentityBlend;
     }
 
-    bool success = ~index == SkTSearch<SkMSec>(&fTimes->fTime, index, time, sizeof(SkTimeCode));
-    SkASSERT(success);
+    // Verify the time should go after all the frames before index
+    bool success = ~index == SkiaInterpolatorBase::binarySearch(fTimes, index, time);
+    LOG_FATAL_IF(!success);
     if (success) {
         SkTimeCode* timeCode = &fTimes[index];
         timeCode->fTime = time;
@@ -257,7 +282,7 @@
         if (exact) {
             memcpy(values, nextSrc, fElemCount * sizeof(float));
         } else {
-            SkASSERT(index > 0);
+            LOG_FATAL_IF(index <= 0);
 
             const float* prevSrc = nextSrc - fElemCount;
 
diff --git a/libs/hwui/SkiaInterpolator.h b/libs/hwui/SkiaInterpolator.h
index 9422cb5..62e6c1e 100644
--- a/libs/hwui/SkiaInterpolator.h
+++ b/libs/hwui/SkiaInterpolator.h
@@ -68,14 +68,16 @@
     enum Flags { kMirror = 1, kReset = 2, kHasBlend = 4 };
     static float ComputeRelativeT(uint32_t time, uint32_t prevTime, uint32_t nextTime,
                                   const float blend[4] = nullptr);
-    int16_t fFrameCount;
-    uint8_t fElemCount;
-    uint8_t fFlags;
-    float fRepeat;
     struct SkTimeCode {
         uint32_t fTime;
         float fBlend[4];
     };
+    static int binarySearch(const SkTimeCode* arr, int count, uint32_t target);
+
+    int16_t fFrameCount;
+    uint8_t fElemCount;
+    uint8_t fFlags;
+    float fRepeat;
     SkTimeCode* fTimes;  // pointer into fStorage
     void* fStorage;
 };
diff --git a/libs/hwui/SkiaWrapper.h b/libs/hwui/SkiaWrapper.h
new file mode 100644
index 0000000..bd0e35a
--- /dev/null
+++ b/libs/hwui/SkiaWrapper.h
@@ -0,0 +1,56 @@
+/*
+ * 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 SKIA_WRAPPER_H_
+#define SKIA_WRAPPER_H_
+
+#include <SkRefCnt.h>
+#include <utils/RefBase.h>
+
+namespace android::uirenderer {
+
+template <typename T>
+class SkiaWrapper : public VirtualLightRefBase {
+public:
+    sk_sp<T> getInstance() {
+        if (mInstance != nullptr && shouldDiscardInstance()) {
+            mInstance = nullptr;
+        }
+
+        if (mInstance == nullptr) {
+            mInstance = createInstance();
+            mGenerationId++;
+        }
+        return mInstance;
+    }
+
+    virtual bool shouldDiscardInstance() const { return false; }
+
+    void discardInstance() { mInstance = nullptr; }
+
+    [[nodiscard]] int32_t getGenerationId() const { return mGenerationId; }
+
+protected:
+    virtual sk_sp<T> createInstance() = 0;
+
+private:
+    sk_sp<T> mInstance = nullptr;
+    int32_t mGenerationId = 0;
+};
+
+}  // namespace android::uirenderer
+
+#endif  // SKIA_WRAPPER_H_
diff --git a/libs/hwui/SkippedFrameInfo.h b/libs/hwui/SkippedFrameInfo.h
new file mode 100644
index 0000000..de56d9a
--- /dev/null
+++ b/libs/hwui/SkippedFrameInfo.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+namespace android::uirenderer {
+
+enum class SkippedFrameReason {
+    DrawingOff,
+    ContextIsStopped,
+    NothingToDraw,
+    NoOutputTarget,
+    NoBuffer,
+    AlreadyDrawn,
+};
+
+} /* namespace android::uirenderer */
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index 974a5d0..ae29edf 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -20,6 +20,7 @@
 #include <log/log.h>
 // libshaders only exists on Android devices
 #ifdef __ANDROID__
+#include <renderthread/CanvasContext.h>
 #include <shaders/shaders.h>
 #endif
 
@@ -53,8 +54,17 @@
 
     ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
 
+    auto colorTransform = android::mat4();
+    const auto* context = renderthread::CanvasContext::getActiveContext();
+    if (context) {
+        const auto ratio = context->targetSdrHdrRatio();
+        if (ratio > 1.0f) {
+            colorTransform = android::mat4::scale(vec4(ratio, ratio, ratio, 1.f));
+        }
+    }
+
     const auto uniforms =
-            shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
+            shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance,
                                                currentDisplayLuminanceNits, maxLuminance);
 
     for (const auto& uniform : uniforms) {
diff --git a/libs/hwui/TreeInfo.cpp b/libs/hwui/TreeInfo.cpp
index 750f869..717157c 100644
--- a/libs/hwui/TreeInfo.cpp
+++ b/libs/hwui/TreeInfo.cpp
@@ -24,7 +24,8 @@
         : mode(mode)
         , prepareTextures(mode == MODE_FULL)
         , canvasContext(canvasContext)
-        , disableForceDark(canvasContext.useForceDark() ? 0 : 1)
+        , disableForceDark(canvasContext.getForceDarkType() == ForceDarkType::NONE ? 1 : 0)
+        , forceDarkType(canvasContext.getForceDarkType())
         , screenSize(canvasContext.getNextFrameSize()) {}
 
 }  // namespace android::uirenderer
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 2bff9cb..88449f3 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -16,14 +16,17 @@
 
 #pragma once
 
-#include "Properties.h"
-#include "utils/Macros.h"
-
 #include <utils/Timers.h>
-#include "SkSize.h"
 
+#include <optional>
 #include <string>
 
+#include "Properties.h"
+#include "SkSize.h"
+#include "SkippedFrameInfo.h"
+#include "utils/ForceDark.h"
+#include "utils/Macros.h"
+
 namespace android {
 namespace uirenderer {
 
@@ -95,6 +98,7 @@
     bool updateWindowPositions = false;
 
     int disableForceDark;
+    ForceDarkType forceDarkType = ForceDarkType::NONE;
 
     const SkISize screenSize;
 
@@ -110,13 +114,13 @@
         // animate itself, such as if hasFunctors is true
         // This is only set if hasAnimations is true
         bool requiresUiRedraw = false;
-        // This is set to true if draw() can be called this frame
-        // false means that we must delay until the next vsync pulse as frame
+        // This is set to nullopt if draw() can be called this frame
+        // A value means that we must delay until the next vsync pulse as frame
         // production is outrunning consumption
-        // NOTE that if this is false CanvasContext will set either requiresUiRedraw
+        // NOTE that if this has a value CanvasContext will set either requiresUiRedraw
         // *OR* will post itself for the next vsync automatically, use this
         // only to avoid calling draw()
-        bool canDrawThisFrame = true;
+        std::optional<SkippedFrameReason> skippedFrameReason;
         // Sentinel for animatedImageDelay meaning there is no need to post such
         // a message.
         static constexpr nsecs_t kNoAnimatedImageDelay = -1;
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
new file mode 100644
index 0000000..ca11975
--- /dev/null
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -0,0 +1,57 @@
+package: "com.android.graphics.hwui.flags"
+
+flag {
+  name: "matrix_44"
+  namespace: "core_graphics"
+  description: "API for 4x4 matrix and related canvas functions"
+  bug: "280116960"
+}
+
+flag {
+  name: "limited_hdr"
+  namespace: "core_graphics"
+  description: "API to enable apps to restrict the amount of HDR headroom that is used"
+  bug: "234181960"
+}
+
+flag {
+  name: "hdr_10bit_plus"
+  namespace: "core_graphics"
+  description: "Use 10101010 and FP16 formats for HDR-UI when available"
+  bug: "284159488"
+}
+
+flag {
+  name: "gainmap_animations"
+  namespace: "core_graphics"
+  description: "APIs to help enable animations involving gainmaps"
+  bug: "296482289"
+}
+
+flag {
+  name: "gainmap_constructor_with_metadata"
+  namespace: "core_graphics"
+  description: "APIs to create a new gainmap with a bitmap for metadata."
+  bug: "304478551"
+}
+
+flag {
+  name: "clip_surfaceviews"
+  namespace: "core_graphics"
+  description: "Clip z-above surfaceviews to global clip rect"
+  bug: "298621623"
+}
+
+flag {
+  name: "requested_formats_v"
+  namespace: "core_graphics"
+  description: "Enable r_8, r_16_uint, rg_1616_uint, and rgba_10101010 in the SDK"
+  bug: "292545615"
+}
+
+flag {
+  name: "animate_hdr_transitions"
+  namespace: "core_graphics"
+  description: "Automatically animate all changes in HDR headroom"
+  bug: "314810174"
+}
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index c442a7b..c80a9b4 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
 #include <log/log.h>
 
 #include "android/graphics/bitmap.h"
diff --git a/libs/hwui/apex/android_canvas.cpp b/libs/hwui/apex/android_canvas.cpp
index 905b123..19f726a 100644
--- a/libs/hwui/apex/android_canvas.cpp
+++ b/libs/hwui/apex/android_canvas.cpp
@@ -45,9 +45,9 @@
     SkImageInfo imageInfo = uirenderer::ANativeWindowToImageInfo(*buffer, cs);
     size_t rowBytes = buffer->stride * imageInfo.bytesPerPixel();
 
-    // If SkSurface::MakeRasterDirect fails then we should as well as we will not be able to
+    // If SkSurfaces::WrapPixels fails then we should as well as we will not be able to
     // draw into the canvas.
-    sk_sp<SkSurface> surface = SkSurface::MakeRasterDirect(imageInfo, buffer->bits, rowBytes);
+    sk_sp<SkSurface> surface = SkSurfaces::WrapPixels(imageInfo, buffer->bits, rowBytes);
     if (surface.get() != nullptr) {
         if (outBitmap) {
             outBitmap->setInfo(imageInfo, rowBytes);
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 09ae7e7..883f273 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -25,9 +25,6 @@
 #include <sys/cdefs.h>
 #include <vulkan/vulkan.h>
 
-#undef LOG_TAG
-#define LOG_TAG "AndroidGraphicsJNI"
-
 extern int register_android_graphics_Bitmap(JNIEnv*);
 extern int register_android_graphics_BitmapFactory(JNIEnv*);
 extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 651d526..3ebf7d1 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -244,11 +244,18 @@
             // This can happen if a BitmapShader is used on multiple canvas', such as a
             // software + hardware canvas, which is otherwise valid as SkShader is "immutable"
             std::lock_guard _lock(mUniformGuard);
-            const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
-                                      sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
-                                     (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
-                                      sk_float_log(mGainmapInfo.fDisplayRatioSdr));
-            const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+            // Compute the weight parameter that will be used to blend between the images.
+            float W = 0.f;
+            if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) {
+                if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) {
+                    W = (sk_float_log(targetHdrSdrRatio) -
+                         sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+                        (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+                         sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+                } else {
+                    W = 1.f;
+                }
+            }
             mBuilder.uniform("W") = W;
             uniforms = mBuilder.uniforms();
         }
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 8049dc9..27773a6 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -111,7 +111,7 @@
     {
         std::unique_lock lock{mImageLock};
         snap.mDurationMS = adjustFrameDuration(mSkAnimatedImage->decodeNextFrame());
-        snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
+        snap.mPic = mSkAnimatedImage->makePictureSnapshot();
     }
 
     return snap;
@@ -123,7 +123,7 @@
     {
         std::unique_lock lock{mImageLock};
         mSkAnimatedImage->reset();
-        snap.mPic.reset(mSkAnimatedImage->newPictureSnapshot());
+        snap.mPic = mSkAnimatedImage->makePictureSnapshot();
         snap.mDurationMS = currentFrameDuration();
     }
 
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 92d875b..8344a86 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -43,12 +43,15 @@
 #include <SkColor.h>
 #include <SkEncodedImageFormat.h>
 #include <SkHighContrastFilter.h>
-#include <SkImageEncoder.h>
+#include <SkImage.h>
+#include <SkImageAndroid.h>
 #include <SkImagePriv.h>
 #include <SkJpegGainmapEncoder.h>
 #include <SkPixmap.h>
 #include <SkRect.h>
 #include <SkStream.h>
+#include <SkJpegEncoder.h>
+#include <SkPngEncoder.h>
 #include <SkWebpEncoder.h>
 
 #include <limits>
@@ -296,7 +299,8 @@
     mPixelStorage.hardware.size = AHardwareBuffer_getAllocationSize(buffer);
     AHardwareBuffer_acquire(buffer);
     setImmutable();  // HW bitmaps are always immutable
-    mImage = SkImage::MakeFromAHardwareBuffer(buffer, mInfo.alphaType(), mInfo.refColorSpace());
+    mImage = SkImages::DeferredFromAHardwareBuffer(buffer, mInfo.alphaType(),
+                                                   mInfo.refColorSpace());
 }
 #endif
 
@@ -407,7 +411,12 @@
         // Note we don't cache in this case, because the raster image holds a pointer to this Bitmap
         // internally and ~Bitmap won't be invoked.
         // TODO: refactor Bitmap to not derive from SkPixelRef, which would allow caching here.
+#ifdef __ANDROID__
+        // pinnable images are only supported with the Ganesh GPU backend compiled in.
+        image = SkImages::PinnableRasterFromBitmap(skiaBitmap);
+#else
         image = SkMakeImageFromRasterBitmap(skiaBitmap, kNever_SkCopyPixelsMode);
+#endif
     }
     return image;
 }
@@ -528,17 +537,25 @@
         return false;
     }
 
-    SkEncodedImageFormat fm;
     switch (format) {
-        case JavaCompressFormat::Jpeg:
-            fm = SkEncodedImageFormat::kJPEG;
-            break;
+        case JavaCompressFormat::Jpeg: {
+            SkJpegEncoder::Options options;
+            options.fQuality = quality;
+            return SkJpegEncoder::Encode(stream, bitmap.pixmap(), options);
+        }
         case JavaCompressFormat::Png:
-            fm = SkEncodedImageFormat::kPNG;
-            break;
-        case JavaCompressFormat::Webp:
-            fm = SkEncodedImageFormat::kWEBP;
-            break;
+            return SkPngEncoder::Encode(stream, bitmap.pixmap(), {});
+        case JavaCompressFormat::Webp: {
+            SkWebpEncoder::Options options;
+            if (quality >= 100) {
+                options.fCompression = SkWebpEncoder::Compression::kLossless;
+                options.fQuality = 75; // This is effort to compress
+            } else {
+                options.fCompression = SkWebpEncoder::Compression::kLossy;
+                options.fQuality = quality;
+            }
+            return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options);
+        }
         case JavaCompressFormat::WebpLossy:
         case JavaCompressFormat::WebpLossless: {
             SkWebpEncoder::Options options;
@@ -548,8 +565,6 @@
             return SkWebpEncoder::Encode(stream, bitmap.pixmap(), options);
         }
     }
-
-    return SkEncodeImage(stream, bitmap, fm, quality);
 }
 
 sp<uirenderer::Gainmap> Bitmap::gainmap() const {
diff --git a/libs/hwui/hwui/Canvas.cpp b/libs/hwui/hwui/Canvas.cpp
index cd8af3d..80b6c03 100644
--- a/libs/hwui/hwui/Canvas.cpp
+++ b/libs/hwui/hwui/Canvas.cpp
@@ -16,17 +16,18 @@
 
 #include "Canvas.h"
 
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "FeatureFlags.h"
 #include "MinikinUtils.h"
 #include "Paint.h"
 #include "Properties.h"
 #include "RenderNode.h"
 #include "Typeface.h"
-#include "pipeline/skia/SkiaRecordingCanvas.h"
-
+#include "hwui/DrawTextFunctor.h"
 #include "hwui/PaintFilter.h"
-
-#include <SkFontMetrics.h>
-#include <SkRRect.h>
+#include "pipeline/skia/SkiaRecordingCanvas.h"
 
 namespace android {
 
@@ -34,13 +35,6 @@
     return new uirenderer::skiapipeline::SkiaRecordingCanvas(renderNode, width, height);
 }
 
-static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
-                              const Paint& paint, Canvas* canvas) {
-    const SkScalar strokeWidth = fmax(thickness, 1.0f);
-    const SkScalar bottom = top + strokeWidth;
-    canvas->drawRect(left, top, right, bottom, paint);
-}
-
 void Canvas::drawTextDecorations(float x, float y, float length, const Paint& paint) {
     // paint has already been filtered by our caller, so we can ignore any filter
     const bool strikeThru = paint.isStrikeThru();
@@ -72,73 +66,6 @@
     }
 }
 
-static void simplifyPaint(int color, Paint* paint) {
-    paint->setColor(color);
-    paint->setShader(nullptr);
-    paint->setColorFilter(nullptr);
-    paint->setLooper(nullptr);
-    paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
-    paint->setStrokeJoin(SkPaint::kRound_Join);
-    paint->setLooper(nullptr);
-}
-
-class DrawTextFunctor {
-public:
-    DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
-                    float y, float totalAdvance)
-            : layout(layout)
-            , canvas(canvas)
-            , paint(paint)
-            , x(x)
-            , y(y)
-            , totalAdvance(totalAdvance) {}
-
-    void operator()(size_t start, size_t end) {
-        auto glyphFunc = [&](uint16_t* text, float* positions) {
-            for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
-                text[textIndex++] = layout.getGlyphId(i);
-                positions[posIndex++] = x + layout.getX(i);
-                positions[posIndex++] = y + layout.getY(i);
-            }
-        };
-
-        size_t glyphCount = end - start;
-
-        if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
-            // high contrast draw path
-            int color = paint.getColor();
-            int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
-            bool darken = channelSum < (128 * 3);
-
-            // outline
-            gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
-            Paint outlinePaint(paint);
-            simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
-            outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
-            canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
-
-            // inner
-            gDrawTextBlobMode = DrawTextBlobMode::HctInner;
-            Paint innerPaint(paint);
-            simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
-            innerPaint.setStyle(SkPaint::kFill_Style);
-            canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
-            gDrawTextBlobMode = DrawTextBlobMode::Normal;
-        } else {
-            // standard draw path
-            canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
-        }
-    }
-
-private:
-    const minikin::Layout& layout;
-    Canvas* canvas;
-    const Paint& paint;
-    float x;
-    float y;
-    float totalAdvance;
-};
-
 void Canvas::drawGlyphs(const minikin::Font& font, const int* glyphIds, const float* positions,
                         int glyphCount, const Paint& paint) {
     // Minikin modify skFont for auto-fakebold/auto-fakeitalic.
@@ -151,7 +78,7 @@
         memcpy(outPositions, positions, sizeof(float) * 2 * glyphCount);
     };
 
-    const minikin::MinikinFont* minikinFont = font.typeface().get();
+    const minikin::MinikinFont* minikinFont = font.baseTypeface().get();
     SkFont* skfont = &copied.getSkFont();
     MinikinFontSkia::populateSkFont(skfont, minikinFont, minikin::FontFakery());
 
@@ -182,6 +109,31 @@
 
     DrawTextFunctor f(layout, this, paint, x, y, layout.getAdvance());
     MinikinUtils::forFontRun(layout, &paint, f);
+
+    if (text_feature::fix_double_underline()) {
+        Paint copied(paint);
+        PaintFilter* filter = getPaintFilter();
+        if (filter != nullptr) {
+            filter->filterFullPaint(&copied);
+        }
+        const bool isUnderline = copied.isUnderline();
+        const bool isStrikeThru = copied.isStrikeThru();
+        if (isUnderline || isStrikeThru) {
+            const SkScalar left = x;
+            const SkScalar right = x + layout.getAdvance();
+            if (isUnderline) {
+                const SkScalar top = y + f.getUnderlinePosition();
+                drawStroke(left, right, top, f.getUnderlineThickness(), copied, this);
+            }
+            if (isStrikeThru) {
+                float textSize = paint.getSkFont().getSize();
+                const float position = textSize * Paint::kStdStrikeThru_Top;
+                const SkScalar thickness = textSize * Paint::kStdStrikeThru_Thickness;
+                const SkScalar top = y + position;
+                drawStroke(left, right, top, thickness, copied, this);
+            }
+        }
+    }
 }
 
 void Canvas::drawDoubleRoundRectXY(float outerLeft, float outerTop, float outerRight,
diff --git a/libs/hwui/hwui/Canvas.h b/libs/hwui/hwui/Canvas.h
index 44ee31d..9ec023b 100644
--- a/libs/hwui/hwui/Canvas.h
+++ b/libs/hwui/hwui/Canvas.h
@@ -285,7 +285,7 @@
      * totalAdvance: used to define width of text decorations (underlines, strikethroughs).
      */
     virtual void drawGlyphs(ReadGlyphFunc glyphFunc, int count, const Paint& paint, float x,
-                            float y,float totalAdvance) = 0;
+                            float y, float totalAdvance) = 0;
     virtual void drawLayoutOnPath(const minikin::Layout& layout, float hOffset, float vOffset,
                                   const Paint& paint, const SkPath& path, size_t start,
                                   size_t end) = 0;
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
new file mode 100644
index 0000000..2e6e976
--- /dev/null
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+#include <SkFontMetrics.h>
+#include <SkRRect.h>
+
+#include "Canvas.h"
+#include "FeatureFlags.h"
+#include "MinikinUtils.h"
+#include "Paint.h"
+#include "Properties.h"
+#include "RenderNode.h"
+#include "Typeface.h"
+#include "hwui/PaintFilter.h"
+#include "pipeline/skia/SkiaRecordingCanvas.h"
+
+namespace android {
+
+static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
+                              const Paint& paint, Canvas* canvas) {
+    const SkScalar strokeWidth = fmax(thickness, 1.0f);
+    const SkScalar bottom = top + strokeWidth;
+    canvas->drawRect(left, top, right, bottom, paint);
+}
+
+static void simplifyPaint(int color, Paint* paint) {
+    paint->setColor(color);
+    paint->setShader(nullptr);
+    paint->setColorFilter(nullptr);
+    paint->setLooper(nullptr);
+    paint->setStrokeWidth(4 + 0.04 * paint->getSkFont().getSize());
+    paint->setStrokeJoin(SkPaint::kRound_Join);
+    paint->setLooper(nullptr);
+}
+
+class DrawTextFunctor {
+public:
+    DrawTextFunctor(const minikin::Layout& layout, Canvas* canvas, const Paint& paint, float x,
+                    float y, float totalAdvance)
+            : layout(layout)
+            , canvas(canvas)
+            , paint(paint)
+            , x(x)
+            , y(y)
+            , totalAdvance(totalAdvance)
+            , underlinePosition(0)
+            , underlineThickness(0) {}
+
+    void operator()(size_t start, size_t end) {
+        auto glyphFunc = [&](uint16_t* text, float* positions) {
+            for (size_t i = start, textIndex = 0, posIndex = 0; i < end; i++) {
+                text[textIndex++] = layout.getGlyphId(i);
+                positions[posIndex++] = x + layout.getX(i);
+                positions[posIndex++] = y + layout.getY(i);
+            }
+        };
+
+        size_t glyphCount = end - start;
+
+        if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
+            // high contrast draw path
+            int color = paint.getColor();
+            int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+            bool darken = channelSum < (128 * 3);
+
+            // outline
+            gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
+            Paint outlinePaint(paint);
+            simplifyPaint(darken ? SK_ColorWHITE : SK_ColorBLACK, &outlinePaint);
+            outlinePaint.setStyle(SkPaint::kStrokeAndFill_Style);
+            canvas->drawGlyphs(glyphFunc, glyphCount, outlinePaint, x, y, totalAdvance);
+
+            // inner
+            gDrawTextBlobMode = DrawTextBlobMode::HctInner;
+            Paint innerPaint(paint);
+            simplifyPaint(darken ? SK_ColorBLACK : SK_ColorWHITE, &innerPaint);
+            innerPaint.setStyle(SkPaint::kFill_Style);
+            canvas->drawGlyphs(glyphFunc, glyphCount, innerPaint, x, y, totalAdvance);
+            gDrawTextBlobMode = DrawTextBlobMode::Normal;
+        } else {
+            // standard draw path
+            canvas->drawGlyphs(glyphFunc, glyphCount, paint, x, y, totalAdvance);
+        }
+
+        if (text_feature::fix_double_underline()) {
+            // Extract underline position and thickness.
+            if (paint.isUnderline()) {
+                SkFontMetrics metrics;
+                paint.getSkFont().getMetrics(&metrics);
+                const float textSize = paint.getSkFont().getSize();
+                SkScalar position;
+                if (!metrics.hasUnderlinePosition(&position)) {
+                    position = textSize * Paint::kStdUnderline_Top;
+                }
+                SkScalar thickness;
+                if (!metrics.hasUnderlineThickness(&thickness)) {
+                    thickness = textSize * Paint::kStdUnderline_Thickness;
+                }
+
+                // If multiple fonts are used, use the most bottom position and most thick stroke
+                // width as the underline position. This follows the CSS standard:
+                // https://www.w3.org/TR/css-text-decor-3/#text-underline-position-property
+                // <quote>
+                // The exact position and thickness of line decorations is UA-defined in this level.
+                // However, for underlines and overlines the UA must use a single thickness and
+                // position on each line for the decorations deriving from a single decorating box.
+                // </quote>
+                underlinePosition = std::max(underlinePosition, position);
+                underlineThickness = std::max(underlineThickness, thickness);
+            }
+        }
+    }
+
+    float getUnderlinePosition() const { return underlinePosition; }
+    float getUnderlineThickness() const { return underlineThickness; }
+
+private:
+    const minikin::Layout& layout;
+    Canvas* canvas;
+    const Paint& paint;
+    float x;
+    float y;
+    float totalAdvance;
+    float underlinePosition;
+    float underlineThickness;
+};
+
+}  // namespace android
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 701a87f..588463c 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -43,9 +43,6 @@
 
 #include <memory>
 
-#undef LOG_TAG
-#define LOG_TAG "ImageDecoder"
-
 using namespace android;
 
 sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index 34cb4ae..f4ee36ec 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -30,6 +30,7 @@
 #include <minikin/MinikinExtent.h>
 #include <minikin/MinikinPaint.h>
 #include <minikin/MinikinRect.h>
+#include <utils/TypefaceUtils.h>
 
 namespace android {
 
@@ -142,7 +143,7 @@
         skVariation[i].value = SkFloatToScalar(variations[i].value);
     }
     args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
     sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
 
     return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index e359145..7552b56d 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -16,12 +16,15 @@
 
 #include "MinikinUtils.h"
 
-#include <string>
-
 #include <log/log.h>
-
+#include <minikin/FamilyVariant.h>
 #include <minikin/MeasuredText.h>
 #include <minikin/Measurement.h>
+
+#include <optional>
+#include <string>
+
+#include "FeatureFlags.h"
 #include "Paint.h"
 #include "SkPathMeasure.h"
 #include "Typeface.h"
@@ -43,9 +46,17 @@
     minikinPaint.wordSpacing = paint->getWordSpacing();
     minikinPaint.fontFlags = MinikinFontSkia::packFontFlags(font);
     minikinPaint.localeListId = paint->getMinikinLocaleListId();
-    minikinPaint.familyVariant = paint->getFamilyVariant();
     minikinPaint.fontStyle = resolvedFace->fStyle;
     minikinPaint.fontFeatureSettings = paint->getFontFeatureSettings();
+
+    const std::optional<minikin::FamilyVariant>& familyVariant = paint->getFamilyVariant();
+    if (familyVariant.has_value()) {
+        minikinPaint.familyVariant = familyVariant.value();
+    } else {
+        minikinPaint.familyVariant = text_feature::deprecate_ui_fonts()
+                                             ? minikin::FamilyVariant::ELEGANT
+                                             : minikin::FamilyVariant::DEFAULT;
+    }
     return minikinPaint;
 }
 
@@ -84,7 +95,8 @@
 
 float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
                                 const Typeface* typeface, const uint16_t* buf, size_t start,
-                                size_t count, size_t bufSize, float* advances) {
+                                size_t count, size_t bufSize, float* advances,
+                                minikin::MinikinRect* bounds) {
     minikin::MinikinPaint minikinPaint = prepareMinikinPaint(paint, typeface);
     const minikin::U16StringPiece textBuf(buf, bufSize);
     const minikin::Range range(start, start + count);
@@ -92,7 +104,7 @@
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
 
     return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
-                                        endHyphen, advances);
+                                        endHyphen, advances, bounds);
 }
 
 minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/MinikinUtils.h b/libs/hwui/hwui/MinikinUtils.h
index 009b84b..61bc881 100644
--- a/libs/hwui/hwui/MinikinUtils.h
+++ b/libs/hwui/hwui/MinikinUtils.h
@@ -51,10 +51,9 @@
     static void getBounds(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
                           const uint16_t* buf, size_t bufSize, minikin::MinikinRect* out);
 
-    static float measureText(const Paint* paint, minikin::Bidi bidiFlags,
-                                         const Typeface* typeface, const uint16_t* buf,
-                                         size_t start, size_t count, size_t bufSize,
-                                         float* advances);
+    static float measureText(const Paint* paint, minikin::Bidi bidiFlags, const Typeface* typeface,
+                             const uint16_t* buf, size_t start, size_t count, size_t bufSize,
+                             float* advances, minikin::MinikinRect* bounds);
 
     static minikin::MinikinExtent getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
                                                 const Typeface* typeface, const uint16_t* buf,
@@ -76,7 +75,7 @@
         size_t start = 0;
         size_t nGlyphs = layout.nGlyphs();
         for (size_t i = 0; i < nGlyphs; i++) {
-            const minikin::MinikinFont* nextFont = layout.getFont(i)->typeface().get();
+            const minikin::MinikinFont* nextFont = layout.typeface(i).get();
             if (i > 0 && nextFont != curFont) {
                 SkFont* skfont = &paint->getSkFont();
                 MinikinFontSkia::populateSkFont(skfont, curFont, layout.getFakery(start));
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index 4a8f3e1..ef4dce5 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -17,18 +17,18 @@
 #ifndef ANDROID_GRAPHICS_PAINT_H_
 #define ANDROID_GRAPHICS_PAINT_H_
 
-#include "Typeface.h"
-
-#include <cutils/compiler.h>
-
 #include <SkFont.h>
 #include <SkPaint.h>
 #include <SkSamplingOptions.h>
+#include <cutils/compiler.h>
+#include <minikin/FamilyVariant.h>
+#include <minikin/FontFamily.h>
+#include <minikin/FontFeature.h>
+#include <minikin/Hyphenator.h>
+
 #include <string>
 
-#include <minikin/FontFamily.h>
-#include <minikin/FamilyVariant.h>
-#include <minikin/Hyphenator.h>
+#include "Typeface.h"
 
 namespace android {
 
@@ -82,11 +82,15 @@
 
     float getWordSpacing() const { return mWordSpacing; }
 
-    void setFontFeatureSettings(const std::string& fontFeatureSettings) {
-        mFontFeatureSettings = fontFeatureSettings;
+    void setFontFeatureSettings(std::string_view fontFeatures) {
+        mFontFeatureSettings = minikin::FontFeature::parse(fontFeatures);
     }
 
-    std::string getFontFeatureSettings() const { return mFontFeatureSettings; }
+    void resetFontFeatures() { mFontFeatureSettings.clear(); }
+
+    const std::vector<minikin::FontFeature>& getFontFeatureSettings() const {
+        return mFontFeatureSettings;
+    }
 
     void setMinikinLocaleListId(uint32_t minikinLocaleListId) {
         mMinikinLocaleListId = minikinLocaleListId;
@@ -94,9 +98,10 @@
 
     uint32_t getMinikinLocaleListId() const { return mMinikinLocaleListId; }
 
+    void resetFamilyVariant() { mFamilyVariant.reset(); }
     void setFamilyVariant(minikin::FamilyVariant variant) { mFamilyVariant = variant; }
 
-    minikin::FamilyVariant getFamilyVariant() const { return mFamilyVariant; }
+    std::optional<minikin::FamilyVariant> getFamilyVariant() const { return mFamilyVariant; }
 
     void setStartHyphenEdit(uint32_t startHyphen) {
         mHyphenEdit = minikin::packHyphenEdit(
@@ -169,9 +174,9 @@
 
     float mLetterSpacing = 0;
     float mWordSpacing = 0;
-    std::string mFontFeatureSettings;
+    std::vector<minikin::FontFeature> mFontFeatureSettings;
     uint32_t mMinikinLocaleListId;
-    minikin::FamilyVariant mFamilyVariant;
+    std::optional<minikin::FamilyVariant> mFamilyVariant;
     uint32_t mHyphenEdit = 0;
     // The native Typeface object has the same lifetime of the Java Typeface
     // object. The Java Paint object holds a strong reference to the Java Typeface
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 3c67edc..a9d1a2a 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -25,8 +25,9 @@
 
 #include "MinikinSkia.h"
 #include "SkPaint.h"
-#include "SkStream.h"  // Fot tests.
+#include "SkStream.h"  // For tests.
 #include "SkTypeface.h"
+#include "utils/TypefaceUtils.h"
 
 #include <minikin/FontCollection.h>
 #include <minikin/FontFamily.h>
@@ -140,9 +141,8 @@
 
         const minikin::FontStyle defaultStyle;
         const minikin::MinikinFont* mf =
-                families.empty()
-                        ? nullptr
-                        : families[0]->getClosestMatch(defaultStyle).font->typeface().get();
+                families.empty() ? nullptr
+                                 : families[0]->getClosestMatch(defaultStyle).typeface().get();
         if (mf != nullptr) {
             SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
             const SkFontStyle& style = skTypeface->fontStyle();
@@ -187,7 +187,9 @@
     LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", kRobotoFont);
     void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
     std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(data, st.st_size));
-    sk_sp<SkTypeface> typeface = SkTypeface::MakeFromStream(std::move(fontData));
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
+    LOG_ALWAYS_FATAL_IF(fm == nullptr, "Could not load FreeType SkFontMgr");
+    sk_sp<SkTypeface> typeface = fm->makeFromStream(std::move(fontData));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", kRobotoFont);
 
     std::shared_ptr<minikin::MinikinFont> font =
diff --git a/libs/hwui/jni/AnimatedImageDrawable.cpp b/libs/hwui/jni/AnimatedImageDrawable.cpp
index a7f5aa83..90b1da8 100644
--- a/libs/hwui/jni/AnimatedImageDrawable.cpp
+++ b/libs/hwui/jni/AnimatedImageDrawable.cpp
@@ -14,10 +14,6 @@
  * limitations under the License.
  */
 
-#include "GraphicsJNI.h"
-#include "ImageDecoder.h"
-#include "Utils.h"
-
 #include <SkAndroidCodec.h>
 #include <SkAnimatedImage.h>
 #include <SkColorFilter.h>
@@ -27,10 +23,15 @@
 #include <SkRect.h>
 #include <SkRefCnt.h>
 #include <hwui/AnimatedImageDrawable.h>
-#include <hwui/ImageDecoder.h>
 #include <hwui/Canvas.h>
+#include <hwui/ImageDecoder.h>
 #include <utils/Looper.h>
 
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
+#include "ImageDecoder.h"
+#include "Utils.h"
+
 using namespace android;
 
 static jclass gAnimatedImageDrawableClass;
@@ -145,8 +146,9 @@
 static void AnimatedImageDrawable_nSetColorFilter(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
                                                   jlong nativeFilter) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    auto* filter = reinterpret_cast<SkColorFilter*>(nativeFilter);
-    drawable->setStagingColorFilter(sk_ref_sp(filter));
+    auto filter = uirenderer::ColorFilter::fromJava(nativeFilter);
+    auto skColorFilter = filter != nullptr ? filter->getInstance() : sk_sp<SkColorFilter>();
+    drawable->setStagingColorFilter(skColorFilter);
 }
 
 static jboolean AnimatedImageDrawable_nIsRunning(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 6ee7576..9e21f86 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,5 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
 // #define LOG_NDEBUG 0
 #include "Bitmap.h"
 
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 8abcd9a..3d0a534 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "BitmapFactory"
-
 #include "BitmapFactory.h"
 
 #include <Gainmap.h>
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 740988f..ea5c144 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "BitmapRegionDecoder"
-
 #include "BitmapRegionDecoder.h"
 
 #include <HardwareBitmapUploader.h>
diff --git a/libs/hwui/jni/ColorFilter.cpp b/libs/hwui/jni/ColorFilter.cpp
index 4bd7ef4..0b95148 100644
--- a/libs/hwui/jni/ColorFilter.cpp
+++ b/libs/hwui/jni/ColorFilter.cpp
@@ -15,20 +15,21 @@
 ** limitations under the License.
 */
 
-#include "GraphicsJNI.h"
+#include "ColorFilter.h"
 
+#include "GraphicsJNI.h"
 #include "SkBlendMode.h"
-#include "SkColorFilter.h"
-#include "SkColorMatrixFilter.h"
 
 namespace android {
 
 using namespace uirenderer;
 
-class SkColorFilterGlue {
+class ColorFilterGlue {
 public:
-    static void SafeUnref(SkColorFilter* filter) {
-        SkSafeUnref(filter);
+    static void SafeUnref(ColorFilter* filter) {
+        if (filter) {
+            filter->decStrong(nullptr);
+        }
     }
 
     static jlong GetNativeFinalizer(JNIEnv*, jobject) {
@@ -36,41 +37,75 @@
     }
 
     static jlong CreateBlendModeFilter(JNIEnv* env, jobject, jint srcColor, jint modeHandle) {
-        SkBlendMode mode = static_cast<SkBlendMode>(modeHandle);
-        return reinterpret_cast<jlong>(SkColorFilters::Blend(srcColor, mode).release());
+        auto mode = static_cast<SkBlendMode>(modeHandle);
+        auto* blendModeFilter = new BlendModeColorFilter(srcColor, mode);
+        blendModeFilter->incStrong(nullptr);
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(blendModeFilter));
     }
 
     static jlong CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
-        return reinterpret_cast<jlong>(SkColorMatrixFilter::MakeLightingFilter(mul, add).release());
+        auto* lightingFilter = new LightingFilter(mul, add);
+        lightingFilter->incStrong(nullptr);
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(lightingFilter));
     }
 
-    static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
-        float matrix[20];
-        env->GetFloatArrayRegion(jarray, 0, 20, matrix);
+    static void SetLightingFilterMul(JNIEnv* env, jobject, jlong lightingFilterPtr, jint mul) {
+        auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr);
+        if (filter) {
+            filter->setMul(mul);
+        }
+    }
+
+    static void SetLightingFilterAdd(JNIEnv* env, jobject, jlong lightingFilterPtr, jint add) {
+        auto* filter = reinterpret_cast<LightingFilter*>(lightingFilterPtr);
+        if (filter) {
+            filter->setAdd(add);
+        }
+    }
+
+    static std::vector<float> getMatrixFromJFloatArray(JNIEnv* env, jfloatArray jarray) {
+        std::vector<float> matrix(20);
+        // float matrix[20];
+        env->GetFloatArrayRegion(jarray, 0, 20, matrix.data());
         // java biases the translates by 255, so undo that before calling skia
         matrix[ 4] *= (1.0f/255);
         matrix[ 9] *= (1.0f/255);
         matrix[14] *= (1.0f/255);
         matrix[19] *= (1.0f/255);
-        return reinterpret_cast<jlong>(SkColorFilters::Matrix(matrix).release());
+        return matrix;
+    }
+
+    static jlong CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+        std::vector<float> matrix = getMatrixFromJFloatArray(env, jarray);
+        auto* colorMatrixColorFilter = new ColorMatrixColorFilter(std::move(matrix));
+        colorMatrixColorFilter->incStrong(nullptr);
+        return static_cast<jlong>(reinterpret_cast<uintptr_t>(colorMatrixColorFilter));
+    }
+
+    static void SetColorMatrix(JNIEnv* env, jobject, jlong colorMatrixColorFilterPtr,
+                               jfloatArray jarray) {
+        auto* filter = reinterpret_cast<ColorMatrixColorFilter*>(colorMatrixColorFilterPtr);
+        if (filter) {
+            filter->setMatrix(getMatrixFromJFloatArray(env, jarray));
+        }
     }
 };
 
 static const JNINativeMethod colorfilter_methods[] = {
-    {"nativeGetFinalizer", "()J", (void*) SkColorFilterGlue::GetNativeFinalizer }
-};
+        {"nativeGetFinalizer", "()J", (void*)ColorFilterGlue::GetNativeFinalizer}};
 
 static const JNINativeMethod blendmode_methods[] = {
-    { "native_CreateBlendModeFilter", "(II)J", (void*) SkColorFilterGlue::CreateBlendModeFilter },
+        {"native_CreateBlendModeFilter", "(II)J", (void*)ColorFilterGlue::CreateBlendModeFilter},
 };
 
 static const JNINativeMethod lighting_methods[] = {
-    { "native_CreateLightingFilter", "(II)J", (void*) SkColorFilterGlue::CreateLightingFilter },
-};
+        {"native_CreateLightingFilter", "(II)J", (void*)ColorFilterGlue::CreateLightingFilter},
+        {"native_SetLightingFilterAdd", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterAdd},
+        {"native_SetLightingFilterMul", "(JI)V", (void*)ColorFilterGlue::SetLightingFilterMul}};
 
 static const JNINativeMethod colormatrix_methods[] = {
-    { "nativeColorMatrixFilter", "([F)J", (void*) SkColorFilterGlue::CreateColorMatrixFilter },
-};
+        {"nativeColorMatrixFilter", "([F)J", (void*)ColorFilterGlue::CreateColorMatrixFilter},
+        {"nativeSetColorMatrix", "(J[F)V", (void*)ColorFilterGlue::SetColorMatrix}};
 
 int register_android_graphics_ColorFilter(JNIEnv* env) {
     android::RegisterMethodsOrDie(env, "android/graphics/ColorFilter", colorfilter_methods,
diff --git a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
index 15e529e..a66d3b8 100644
--- a/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
+++ b/libs/hwui/jni/CreateJavaOutputStreamAdaptor.cpp
@@ -1,11 +1,11 @@
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "SkData.h"
-#include "SkMalloc.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
 #include "SkTypes.h"
 #include "Utils.h"
 
+#include <cstdlib>
 #include <nativehelper/JNIHelp.h>
 #include <log/log.h>
 #include <memory>
@@ -177,6 +177,10 @@
     return JavaInputStreamAdaptor::Create(env, stream, storage, swallowExceptions);
 }
 
+static void free_pointer_skproc(const void* ptr, void*) {
+    free((void*)ptr);
+}
+
 sk_sp<SkData> CopyJavaInputStream(JNIEnv* env, jobject inputStream, jbyteArray storage) {
     std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, inputStream, storage));
     if (!stream) {
@@ -186,19 +190,31 @@
     size_t bufferSize = 4096;
     size_t streamLen = 0;
     size_t len;
-    char* data = (char*)sk_malloc_throw(bufferSize);
+    char* data = (char*)malloc(bufferSize);
+    LOG_ALWAYS_FATAL_IF(!data);
 
     while ((len = stream->read(data + streamLen,
                                bufferSize - streamLen)) != 0) {
         streamLen += len;
         if (streamLen == bufferSize) {
             bufferSize *= 2;
-            data = (char*)sk_realloc_throw(data, bufferSize);
+            data = (char*)realloc(data, bufferSize);
+            LOG_ALWAYS_FATAL_IF(!data);
         }
     }
-    data = (char*)sk_realloc_throw(data, streamLen);
-
-    return SkData::MakeFromMalloc(data, streamLen);
+    if (streamLen == 0) {
+        // realloc with size 0 is unspecified behavior in C++11
+        free(data);
+        data = nullptr;
+    } else {
+        // Trim down the buffer to the actual size of the data.
+        LOG_FATAL_IF(streamLen > bufferSize);
+        data = (char*)realloc(data, streamLen);
+        LOG_ALWAYS_FATAL_IF(!data);
+    }
+    // Just in case sk_free differs from free, we ask Skia to use
+    // free to cleanup the buffer that SkData wraps.
+    return SkData::MakeWithProc(data, streamLen, free_pointer_skproc, nullptr);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index 69774cc..e6d790f 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include "FontUtils.h"
@@ -34,6 +31,7 @@
 #include <minikin/FontFamily.h>
 #include <minikin/LocaleList.h>
 #include <ui/FatVector.h>
+#include <utils/TypefaceUtils.h>
 
 #include <memory>
 
@@ -89,7 +87,8 @@
     }
     std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
             builder->langId, builder->variant, std::move(builder->fonts),
-            true /* isCustomFallback */, false /* isDefaultFallback */);
+            true /* isCustomFallback */, false /* isDefaultFallback */,
+            minikin::VariationFamilyType::None);
     if (family->getCoverage().length() == 0) {
         return 0;
     }
@@ -127,7 +126,7 @@
     args.setCollectionIndex(ttcIndex);
     args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
 
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
     sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args));
     if (face == NULL) {
         ALOGE("addFont failed to create font, invalid request");
diff --git a/libs/hwui/jni/Gainmap.cpp b/libs/hwui/jni/Gainmap.cpp
index cec0ee7..0fffee7 100644
--- a/libs/hwui/jni/Gainmap.cpp
+++ b/libs/hwui/jni/Gainmap.cpp
@@ -208,8 +208,6 @@
     p.writeFloat(info.fDisplayRatioHdr);
     // base image type
     p.writeInt32(static_cast<int32_t>(info.fBaseImageType));
-    // type
-    p.writeInt32(static_cast<int32_t>(info.fType));
 #else
     doThrowRE(env, "Cannot use parcels outside of Android!");
 #endif
@@ -232,7 +230,6 @@
     info.fDisplayRatioSdr = p.readFloat();
     info.fDisplayRatioHdr = p.readFloat();
     info.fBaseImageType = static_cast<SkGainmapInfo::BaseImageType>(p.readInt32());
-    info.fType = static_cast<SkGainmapInfo::Type>(p.readInt32());
 
     fromJava(nativeObject)->info = info;
 #else
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 78b4f7b..7cc4866 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "GraphicsJNI"
-
 #include <assert.h>
 #include <unistd.h>
 
diff --git a/libs/hwui/jni/GraphicsJNI.h b/libs/hwui/jni/GraphicsJNI.h
index 23ab5dd..b9fff36 100644
--- a/libs/hwui/jni/GraphicsJNI.h
+++ b/libs/hwui/jni/GraphicsJNI.h
@@ -125,14 +125,6 @@
     static jobject createBitmapRegionDecoder(JNIEnv* env,
                                              android::BitmapRegionDecoderWrapper* bitmap);
 
-    /**
-     * Given a bitmap we natively allocate a memory block to store the contents
-     * of that bitmap.  The memory is then attached to the bitmap via an
-     * SkPixelRef, which ensures that upon deletion the appropriate caches
-     * are notified.
-     */
-    static bool allocatePixels(JNIEnv* env, SkBitmap* bitmap);
-
     /** Copy the colors in colors[] to the bitmap, convert to the correct
         format along the way.
         Whether to use premultiplied pixels is determined by dstBitmap's alphaType.
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index e32c911..54369b9 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "GraphicsStatsService"
-
 #include <JankTracker.h>
 #include <log/log.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/MaskFilter.cpp b/libs/hwui/jni/MaskFilter.cpp
index 048ce02..cbd4520 100644
--- a/libs/hwui/jni/MaskFilter.cpp
+++ b/libs/hwui/jni/MaskFilter.cpp
@@ -1,6 +1,5 @@
 #include "GraphicsJNI.h"
 #include "SkMaskFilter.h"
-#include "SkBlurMask.h"
 #include "SkBlurMaskFilter.h"
 #include "SkBlurTypes.h"
 #include "SkTableMaskFilter.h"
@@ -11,6 +10,13 @@
     }
 }
 
+// From https://skia.googlesource.com/skia/+/d74c99a3cd5eef5f16b2eb226e6b45fe523c8552/src/core/SkBlurMask.cpp#28
+static constexpr float kBLUR_SIGMA_SCALE = 0.57735f;
+
+static float convertRadiusToSigma(float radius) {
+    return radius > 0 ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f;
+}
+
 class SkMaskFilterGlue {
 public:
     static void destructor(JNIEnv* env, jobject, jlong filterHandle) {
@@ -19,7 +25,7 @@
     }
 
     static jlong createBlur(JNIEnv* env, jobject, jfloat radius, jint blurStyle) {
-        SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
+        SkScalar sigma = convertRadiusToSigma(radius);
         SkMaskFilter* filter = SkMaskFilter::MakeBlur((SkBlurStyle)blurStyle, sigma).release();
         ThrowIAE_IfNull(env, filter);
         return reinterpret_cast<jlong>(filter);
@@ -34,7 +40,7 @@
             direction[i] = values[i];
         }
 
-        SkScalar sigma = SkBlurMask::ConvertRadiusToSigma(radius);
+        SkScalar sigma = convertRadiusToSigma(radius);
         SkMaskFilter* filter =  SkBlurMaskFilter::MakeEmboss(sigma,
                 direction, ambient, specular).release();
         ThrowIAE_IfNull(env, filter);
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index d50a8a2..67ef143 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -15,8 +15,6 @@
 ** limitations under the License.
 */
 
-#undef LOG_TAG
-#define LOG_TAG "9patch"
 #define LOG_NDEBUG 1
 
 #include <androidfw/ResourceTypes.h>
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index 13357fa..d84b73d 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -15,16 +15,30 @@
 ** limitations under the License.
 */
 
-#undef LOG_TAG
-#define LOG_TAG "Paint"
-
-#include <utils/Log.h>
-
-#include "GraphicsJNI.h"
+#include <hwui/BlurDrawLooper.h>
+#include <hwui/MinikinSkia.h>
+#include <hwui/MinikinUtils.h>
+#include <hwui/Paint.h>
+#include <hwui/Typeface.h>
+#include <minikin/GraphemeBreak.h>
+#include <minikin/LocaleList.h>
+#include <minikin/Measurement.h>
+#include <minikin/MinikinPaint.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedStringChars.h>
 #include <nativehelper/ScopedUtfChars.h>
-#include <nativehelper/ScopedPrimitiveArray.h>
+#include <unicode/utf16.h>
+#include <utils/Log.h>
 
+#include <cassert>
+#include <cstring>
+#include <memory>
+#include <string_view>
+#include <vector>
+
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
+#include "SkBlendMode.h"
 #include "SkColorFilter.h"
 #include "SkColorSpace.h"
 #include "SkFont.h"
@@ -35,28 +49,22 @@
 #include "SkPathEffect.h"
 #include "SkPathUtils.h"
 #include "SkShader.h"
-#include "SkBlendMode.h"
 #include "unicode/uloc.h"
 #include "utils/Blur.h"
 
-#include <hwui/BlurDrawLooper.h>
-#include <hwui/MinikinSkia.h>
-#include <hwui/MinikinUtils.h>
-#include <hwui/Paint.h>
-#include <hwui/Typeface.h>
-#include <minikin/GraphemeBreak.h>
-#include <minikin/LocaleList.h>
-#include <minikin/Measurement.h>
-#include <minikin/MinikinPaint.h>
-#include <unicode/utf16.h>
-
-#include <cassert>
-#include <cstring>
-#include <memory>
-#include <vector>
-
 namespace android {
 
+namespace {
+
+void copyMinikinRectToSkRect(const minikin::MinikinRect& minikinRect, SkRect* skRect) {
+    skRect->fLeft = minikinRect.mLeft;
+    skRect->fTop = minikinRect.mTop;
+    skRect->fRight = minikinRect.mRight;
+    skRect->fBottom = minikinRect.mBottom;
+}
+
+}  // namespace
+
 static void getPosTextPath(const SkFont& font, const uint16_t glyphs[], int count,
                            const SkPoint pos[], SkPath* dst) {
     dst->reset();
@@ -105,8 +113,8 @@
         float measured = 0;
 
         std::unique_ptr<float[]> advancesArray(new float[count]);
-        MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text,
-                0, count, count, advancesArray.get());
+        MinikinUtils::measureText(&paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, 0,
+                                  count, count, advancesArray.get(), nullptr);
 
         for (int i = 0; i < count; i++) {
             // traverse in the given direction
@@ -196,9 +204,9 @@
         if (advances) {
             advancesArray.reset(new jfloat[count]);
         }
-        const float advance = MinikinUtils::measureText(paint,
-                static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count, contextCount,
-                advancesArray.get());
+        const float advance = MinikinUtils::measureText(
+                paint, static_cast<minikin::Bidi>(bidiFlags), typeface, text, start, count,
+                contextCount, advancesArray.get(), nullptr);
         if (advances) {
             env->SetFloatArrayRegion(advances, advancesIndex, count, advancesArray.get());
         }
@@ -236,7 +244,7 @@
         minikin::Bidi bidiFlags = dir == 1 ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
         std::unique_ptr<float[]> advancesArray(new float[count]);
         MinikinUtils::measureText(paint, bidiFlags, typeface, text, start, count, start + count,
-                advancesArray.get());
+                                  advancesArray.get(), nullptr);
         size_t result = minikin::GraphemeBreak::getTextRunCursor(advancesArray.get(), text,
                 start, count, offset, moveOpt);
         return static_cast<jint>(result);
@@ -500,7 +508,7 @@
     static jfloat doRunAdvance(JNIEnv* env, const Paint* paint, const Typeface* typeface,
                                const jchar buf[], jint start, jint count, jint bufSize,
                                jboolean isRtl, jint offset, jfloatArray advances,
-                               jint advancesIndex) {
+                               jint advancesIndex, SkRect* drawBounds) {
         if (advances) {
             size_t advancesLength = env->GetArrayLength(advances);
             if ((size_t)(count + advancesIndex) > advancesLength) {
@@ -509,14 +517,23 @@
             }
         }
         minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
+        minikin::MinikinRect bounds;
         if (offset == start + count && advances == nullptr) {
-            return MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
-                    bufSize, nullptr);
+            float result =
+                    MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count,
+                                              bufSize, nullptr, drawBounds ? &bounds : nullptr);
+            if (drawBounds) {
+                copyMinikinRectToSkRect(bounds, drawBounds);
+            }
+            return result;
         }
         std::unique_ptr<float[]> advancesArray(new float[count]);
         MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
-                advancesArray.get());
+                                  advancesArray.get(), drawBounds ? &bounds : nullptr);
 
+        if (drawBounds) {
+            copyMinikinRectToSkRect(bounds, drawBounds);
+        }
         float result = minikin::getRunAdvance(advancesArray.get(), buf, start, count, offset);
         if (advances) {
             minikin::distributeAdvances(advancesArray.get(), buf, start, count);
@@ -532,7 +549,7 @@
         ScopedCharArrayRO textArray(env, text);
         jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
                                      start - contextStart, end - start, contextEnd - contextStart,
-                                     isRtl, offset - contextStart, nullptr, 0);
+                                     isRtl, offset - contextStart, nullptr, 0, nullptr);
         return result;
     }
 
@@ -540,13 +557,19 @@
                                                         jcharArray text, jint start, jint end,
                                                         jint contextStart, jint contextEnd,
                                                         jboolean isRtl, jint offset,
-                                                        jfloatArray advances, jint advancesIndex) {
+                                                        jfloatArray advances, jint advancesIndex,
+                                                        jobject drawBounds) {
         const Paint* paint = reinterpret_cast<Paint*>(paintHandle);
         const Typeface* typeface = paint->getAndroidTypeface();
         ScopedCharArrayRO textArray(env, text);
+        SkRect skDrawBounds;
         jfloat result = doRunAdvance(env, paint, typeface, textArray.get() + contextStart,
                                      start - contextStart, end - start, contextEnd - contextStart,
-                                     isRtl, offset - contextStart, advances, advancesIndex);
+                                     isRtl, offset - contextStart, advances, advancesIndex,
+                                     drawBounds ? &skDrawBounds : nullptr);
+        if (drawBounds != nullptr) {
+            GraphicsJNI::rect_to_jrectf(skDrawBounds, env, drawBounds);
+        }
         return result;
     }
 
@@ -555,7 +578,7 @@
         minikin::Bidi bidiFlags = isRtl ? minikin::Bidi::FORCE_RTL : minikin::Bidi::FORCE_LTR;
         std::unique_ptr<float[]> advancesArray(new float[count]);
         MinikinUtils::measureText(paint, bidiFlags, typeface, buf, start, count, bufSize,
-                advancesArray.get());
+                                  advancesArray.get(), nullptr);
         return minikin::getOffsetForAdvance(advancesArray.get(), buf, start, count, advance);
     }
 
@@ -571,7 +594,7 @@
         return result;
     }
 
-    static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics *metrics) {
+    static SkScalar getMetricsInternal(jlong paintHandle, SkFontMetrics* metrics, bool useLocale) {
         const int kElegantTop = 2500;
         const int kElegantBottom = -1000;
         const int kElegantAscent = 1900;
@@ -584,7 +607,7 @@
         minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
         float saveSkewX = font->getSkewX();
         bool savefakeBold = font->isEmbolden();
-        MinikinFontSkia::populateSkFont(font, baseFont.font->typeface().get(), baseFont.fakery);
+        MinikinFontSkia::populateSkFont(font, baseFont.typeface().get(), baseFont.fakery);
         SkScalar spacing = font->getMetrics(metrics);
         // The populateSkPaint call may have changed fake bold / text skew
         // because we want to measure with those effects applied, so now
@@ -600,6 +623,17 @@
             metrics->fLeading = size * kElegantLeading / 2048;
             spacing = metrics->fDescent - metrics->fAscent + metrics->fLeading;
         }
+
+        if (useLocale) {
+            minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
+            minikin::MinikinExtent extent =
+                    typeface->fFontCollection->getReferenceExtentForLocale(minikinPaint);
+            metrics->fAscent = std::min(extent.ascent, metrics->fAscent);
+            metrics->fDescent = std::max(extent.descent, metrics->fDescent);
+            metrics->fTop = std::min(metrics->fAscent, metrics->fTop);
+            metrics->fBottom = std::max(metrics->fDescent, metrics->fBottom);
+        }
+
         return spacing;
     }
 
@@ -612,7 +646,7 @@
                 MinikinUtils::getFontExtent(paint, bidiFlags, typeface, buf, start, count, bufSize);
 
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
 
         metrics.fAscent = extent.ascent;
         metrics.fDescent = extent.descent;
@@ -657,27 +691,29 @@
                                        jstring settings) {
         Paint* paint = reinterpret_cast<Paint*>(paintHandle);
         if (!settings) {
-            paint->setFontFeatureSettings(std::string());
+            paint->resetFontFeatures();
         } else {
             ScopedUtfChars settingsChars(env, settings);
-            paint->setFontFeatureSettings(std::string(settingsChars.c_str(), settingsChars.size()));
+            paint->setFontFeatureSettings(
+                    std::string_view(settingsChars.c_str(), settingsChars.size()));
         }
     }
 
-    static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+    static jfloat getFontMetrics(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+                                 jboolean useLocale) {
         SkFontMetrics metrics;
-        SkScalar spacing = getMetricsInternal(paintHandle, &metrics);
+        SkScalar spacing = getMetricsInternal(paintHandle, &metrics, useLocale);
         GraphicsJNI::set_metrics(env, metricsObj, metrics);
         return SkScalarToFloat(spacing);
     }
 
-    static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj) {
+    static jint getFontMetricsInt(JNIEnv* env, jobject, jlong paintHandle, jobject metricsObj,
+                                  jboolean useLocale) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, useLocale);
         return GraphicsJNI::set_metrics_int(env, metricsObj, metrics);
     }
 
-
     // ------------------ @CriticalNative ---------------------------
 
     static void reset(CRITICAL_JNI_PARAMS_COMMA jlong objHandle) {
@@ -821,9 +857,11 @@
 
     static jlong setColorFilter(CRITICAL_JNI_PARAMS_COMMA jlong objHandle, jlong filterHandle) {
         Paint* obj = reinterpret_cast<Paint *>(objHandle);
-        SkColorFilter* filter  = reinterpret_cast<SkColorFilter *>(filterHandle);
-        obj->setColorFilter(sk_ref_sp(filter));
-        return reinterpret_cast<jlong>(obj->getColorFilter());
+        auto colorFilter = uirenderer::ColorFilter::fromJava(filterHandle);
+        auto skColorFilter =
+                colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>();
+        obj->setColorFilter(skColorFilter);
+        return filterHandle;
     }
 
     static void setXfermode(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint xfermodeHandle) {
@@ -899,15 +937,39 @@
         obj->setMinikinLocaleListId(minikinLocaleListId);
     }
 
-    static jboolean isElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
+    // Note: Following three values must be equal to the ones in Java file: Paint.java.
+    constexpr jint ELEGANT_TEXT_HEIGHT_UNSET = -1;
+    constexpr jint ELEGANT_TEXT_HEIGHT_ENABLED = 0;
+    constexpr jint ELEGANT_TEXT_HEIGHT_DISABLED = 1;
+
+    static jint getElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         Paint* obj = reinterpret_cast<Paint*>(paintHandle);
-        return obj->getFamilyVariant() == minikin::FamilyVariant::ELEGANT;
+        const std::optional<minikin::FamilyVariant>& familyVariant = obj->getFamilyVariant();
+        if (familyVariant.has_value()) {
+            if (familyVariant.value() == minikin::FamilyVariant::ELEGANT) {
+                return ELEGANT_TEXT_HEIGHT_ENABLED;
+            } else {
+                return ELEGANT_TEXT_HEIGHT_DISABLED;
+            }
+        } else {
+            return ELEGANT_TEXT_HEIGHT_UNSET;
+        }
     }
 
-    static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jboolean aa) {
+    static void setElegantTextHeight(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle, jint value) {
         Paint* obj = reinterpret_cast<Paint*>(paintHandle);
-        obj->setFamilyVariant(
-                aa ? minikin::FamilyVariant::ELEGANT : minikin::FamilyVariant::DEFAULT);
+        switch (value) {
+            case ELEGANT_TEXT_HEIGHT_ENABLED:
+                obj->setFamilyVariant(minikin::FamilyVariant::ELEGANT);
+                return;
+            case ELEGANT_TEXT_HEIGHT_DISABLED:
+                obj->setFamilyVariant(minikin::FamilyVariant::DEFAULT);
+                return;
+            case ELEGANT_TEXT_HEIGHT_UNSET:
+            default:
+                obj->resetFamilyVariant();
+                return;
+        }
     }
 
     static jfloat getTextSize(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
@@ -978,19 +1040,19 @@
 
     static jfloat ascent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         return SkScalarToFloat(metrics.fAscent);
     }
 
     static jfloat descent(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         return SkScalarToFloat(metrics.fDescent);
     }
 
     static jfloat getUnderlinePosition(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         SkScalar position;
         if (metrics.hasUnderlinePosition(&position)) {
             return SkScalarToFloat(position);
@@ -1002,7 +1064,7 @@
 
     static jfloat getUnderlineThickness(CRITICAL_JNI_PARAMS_COMMA jlong paintHandle) {
         SkFontMetrics metrics;
-        getMetricsInternal(paintHandle, &metrics);
+        getMetricsInternal(paintHandle, &metrics, false /* useLocale */);
         SkScalar thickness;
         if (metrics.hasUnderlineThickness(&thickness)) {
             return SkScalarToFloat(thickness);
@@ -1083,7 +1145,7 @@
          (void*)PaintGlue::getCharArrayBounds},
         {"nHasGlyph", "(JILjava/lang/String;)Z", (void*)PaintGlue::hasGlyph},
         {"nGetRunAdvance", "(J[CIIIIZI)F", (void*)PaintGlue::getRunAdvance___CIIIIZI_F},
-        {"nGetRunCharacterAdvance", "(J[CIIIIZI[FI)F",
+        {"nGetRunCharacterAdvance", "(J[CIIIIZI[FILandroid/graphics/RectF;)F",
          (void*)PaintGlue::getRunCharacterAdvance___CIIIIZI_FI_F},
         {"nGetOffsetForAdvance", "(J[CIIIIZF)I", (void*)PaintGlue::getOffsetForAdvance___CIIIIZF_I},
         {"nGetFontMetricsIntForText", "(J[CIIIIZLandroid/graphics/Paint$FontMetricsInt;)V",
@@ -1097,9 +1159,9 @@
         {"nSetTextLocales", "(JLjava/lang/String;)I", (void*)PaintGlue::setTextLocales},
         {"nSetFontFeatureSettings", "(JLjava/lang/String;)V",
          (void*)PaintGlue::setFontFeatureSettings},
-        {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;)F",
+        {"nGetFontMetrics", "(JLandroid/graphics/Paint$FontMetrics;Z)F",
          (void*)PaintGlue::getFontMetrics},
-        {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;)I",
+        {"nGetFontMetricsInt", "(JLandroid/graphics/Paint$FontMetricsInt;Z)I",
          (void*)PaintGlue::getFontMetricsInt},
 
         // --------------- @CriticalNative ------------------
@@ -1142,8 +1204,8 @@
         {"nSetTextAlign", "(JI)V", (void*)PaintGlue::setTextAlign},
         {"nSetTextLocalesByMinikinLocaleListId", "(JI)V",
          (void*)PaintGlue::setTextLocalesByMinikinLocaleListId},
-        {"nIsElegantTextHeight", "(J)Z", (void*)PaintGlue::isElegantTextHeight},
-        {"nSetElegantTextHeight", "(JZ)V", (void*)PaintGlue::setElegantTextHeight},
+        {"nGetElegantTextHeight", "(J)I", (void*)PaintGlue::getElegantTextHeight},
+        {"nSetElegantTextHeight", "(JI)V", (void*)PaintGlue::setElegantTextHeight},
         {"nGetTextSize", "(J)F", (void*)PaintGlue::getTextSize},
         {"nSetTextSize", "(JF)V", (void*)PaintGlue::setTextSize},
         {"nGetTextScaleX", "(J)F", (void*)PaintGlue::getTextScaleX},
diff --git a/libs/hwui/jni/RenderEffect.cpp b/libs/hwui/jni/RenderEffect.cpp
index f3db170..dcd3fa4 100644
--- a/libs/hwui/jni/RenderEffect.cpp
+++ b/libs/hwui/jni/RenderEffect.cpp
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 #include "Bitmap.h"
+#include "ColorFilter.h"
 #include "GraphicsJNI.h"
 #include "SkBlendMode.h"
 #include "SkImageFilter.h"
 #include "SkImageFilters.h"
 #include "graphics_jni_helpers.h"
 #include "utils/Blur.h"
-#include <utils/Log.h>
 
 using namespace android::uirenderer;
 
@@ -76,11 +76,13 @@
     jlong colorFilterHandle,
     jlong inputFilterHandle
 ) {
-    auto* colorFilter = reinterpret_cast<const SkColorFilter*>(colorFilterHandle);
+    auto colorFilter = android::uirenderer::ColorFilter::fromJava(colorFilterHandle);
+    auto skColorFilter =
+            colorFilter != nullptr ? colorFilter->getInstance() : sk_sp<SkColorFilter>();
     auto* inputFilter = reinterpret_cast<const SkImageFilter*>(inputFilterHandle);
-    sk_sp<SkImageFilter> colorFilterImageFilter = SkImageFilters::ColorFilter(
-            sk_ref_sp(colorFilter), sk_ref_sp(inputFilter), nullptr);
-   return reinterpret_cast<jlong>(colorFilterImageFilter.release());
+    sk_sp<SkImageFilter> colorFilterImageFilter =
+            SkImageFilters::ColorFilter(skColorFilter, sk_ref_sp(inputFilter), nullptr);
+    return reinterpret_cast<jlong>(colorFilterImageFilter.release());
 }
 
 static jlong createBlendModeEffect(
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 7eb79be..a952be0 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "ShaderJNI"
-
 #include <vector>
 
 #include "Gainmap.h"
@@ -68,21 +65,41 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&Shader_safeUnref));
 }
 
-static jlong createBitmapShaderHelper(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
-                                      jint tileModeX, jint tileModeY, bool isDirectSampled,
-                                      const SkSamplingOptions& sampling) {
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+static SkGainmapInfo sNoOpGainmap = {
+        .fGainmapRatioMin = {1.f, 1.f, 1.f, 1.0},
+        .fGainmapRatioMax = {1.f, 1.f, 1.f, 1.0},
+        .fGainmapGamma = {1.f, 1.f, 1.f, 1.f},
+        .fEpsilonSdr = {0.f, 0.f, 0.f, 1.0},
+        .fEpsilonHdr = {0.f, 0.f, 0.f, 1.0},
+        .fDisplayRatioSdr = 1.f,
+        .fDisplayRatioHdr = 1.f,
+};
+
+static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
+                                      jint tileModeX, jint tileModeY, jint maxAniso, bool filter,
+                                      bool isDirectSampled, jlong overrideGainmapPtr) {
+    SkSamplingOptions sampling = maxAniso > 0 ? SkSamplingOptions::Aniso(static_cast<int>(maxAniso))
+                                              : SkSamplingOptions(filter ? SkFilterMode::kLinear
+                                                                         : SkFilterMode::kNearest,
+                                                                  SkMipmapMode::kNone);
     const SkMatrix* matrix = reinterpret_cast<const SkMatrix*>(matrixPtr);
+    const Gainmap* gainmap = reinterpret_cast<Gainmap*>(overrideGainmapPtr);
     sk_sp<SkImage> image;
     if (bitmapHandle) {
         // Only pass a valid SkBitmap object to the constructor if the Bitmap exists. Otherwise,
         // we'll pass an empty SkBitmap to avoid crashing/excepting for compatibility.
         auto& bitmap = android::bitmap::toBitmap(bitmapHandle);
         image = bitmap.makeImage();
+        if (!gainmap && bitmap.hasGainmap()) {
+            gainmap = bitmap.gainmap().get();
+        }
 
-        if (!isDirectSampled && bitmap.hasGainmap()) {
-            sk_sp<SkShader> gainmapShader = MakeGainmapShader(
-                    image, bitmap.gainmap()->bitmap->makeImage(), bitmap.gainmap()->info,
-                    (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
+        if (!isDirectSampled && gainmap && gainmap->info != sNoOpGainmap) {
+            sk_sp<SkShader> gainmapShader =
+                    MakeGainmapShader(image, gainmap->bitmap->makeImage(), gainmap->info,
+                                      (SkTileMode)tileModeX, (SkTileMode)tileModeY, sampling);
             if (gainmapShader) {
                 if (matrix) {
                     gainmapShader = gainmapShader->makeWithLocalMatrix(*matrix);
@@ -114,26 +131,6 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////
 
-static jlong BitmapShader_constructor(JNIEnv* env, jobject o, jlong matrixPtr, jlong bitmapHandle,
-                                      jint tileModeX, jint tileModeY, bool filter,
-                                      bool isDirectSampled) {
-    SkSamplingOptions sampling(filter ? SkFilterMode::kLinear : SkFilterMode::kNearest,
-                               SkMipmapMode::kNone);
-    return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
-                                    isDirectSampled, sampling);
-}
-
-static jlong BitmapShader_constructorWithMaxAniso(JNIEnv* env, jobject o, jlong matrixPtr,
-                                                  jlong bitmapHandle, jint tileModeX,
-                                                  jint tileModeY, jint maxAniso,
-                                                  bool isDirectSampled) {
-    auto sampling = SkSamplingOptions::Aniso(static_cast<int>(maxAniso));
-    return createBitmapShaderHelper(env, o, matrixPtr, bitmapHandle, tileModeX, tileModeY,
-                                    isDirectSampled, sampling);
-}
-
-///////////////////////////////////////////////////////////////////////////////////////////////
-
 static std::vector<SkColor4f> convertColorLongs(JNIEnv* env, jlongArray colorArray) {
     const size_t count = env->GetArrayLength(colorArray);
     const jlong* colorValues = env->GetLongArrayElements(colorArray, nullptr);
@@ -422,8 +419,7 @@
 };
 
 static const JNINativeMethod gBitmapShaderMethods[] = {
-        {"nativeCreate", "(JJIIZZ)J", (void*)BitmapShader_constructor},
-        {"nativeCreateWithMaxAniso", "(JJIIIZ)J", (void*)BitmapShader_constructorWithMaxAniso},
+        {"nativeCreate", "(JJIIIZZJ)J", (void*)BitmapShader_constructor},
 
 };
 
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 7298906..3533001 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "YuvToJpegEncoder"
-
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "SkStream.h"
 #include "YuvToJpegEncoder.h"
@@ -336,7 +333,8 @@
 
 bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env,
         SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
-        int width, int height, int jpegQuality) {
+        int width, int height, int jpegQuality, ScopedByteArrayRO* jExif,
+        ScopedIntArrayRO* jHdrStrides, ScopedIntArrayRO* jSdrStrides) {
     // Check SDR color space. Now we only support SRGB transfer function
     if ((sdrColorSpace & ADataSpace::TRANSFER_MASK) !=  ADataSpace::TRANSFER_SRGB) {
         jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
@@ -344,6 +342,19 @@
             "The requested SDR color space is not supported. Transfer function must be SRGB");
         return false;
     }
+    // Check HDR and SDR strides length.
+    // HDR is YCBCR_P010 color format, and its strides length must be 2 (Y, chroma (Cb, Cr)).
+    // SDR is YUV_420_888 color format, and its strides length must be 3 (Y, Cb, Cr).
+    if (jHdrStrides->size() != 2) {
+        jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+        env->ThrowNew(IllegalArgumentException, "HDR stride length must be 2.");
+        return false;
+    }
+    if (jSdrStrides->size() != 3) {
+        jclass IllegalArgumentException = env->FindClass("java/lang/IllegalArgumentException");
+        env->ThrowNew(IllegalArgumentException, "SDR stride length must be 3.");
+        return false;
+    }
 
     ultrahdr_color_gamut hdrColorGamut = findColorGamut(env, hdrColorSpace);
     ultrahdr_color_gamut sdrColorGamut = findColorGamut(env, sdrColorSpace);
@@ -355,20 +366,32 @@
         return false;
     }
 
+    const int* hdrStrides = reinterpret_cast<const int*>(jHdrStrides->get());
+    const int* sdrStrides = reinterpret_cast<const int*>(jSdrStrides->get());
+
     JpegR jpegREncoder;
 
     jpegr_uncompressed_struct p010;
     p010.data = hdr;
     p010.width = width;
     p010.height = height;
+    // Divided by 2 because unit in libultrader is pixel and in YuvImage it is byte.
+    p010.luma_stride = (hdrStrides[0] + 1) / 2;
+    p010.chroma_stride = (hdrStrides[1] + 1) / 2;
     p010.colorGamut = hdrColorGamut;
 
     jpegr_uncompressed_struct yuv420;
     yuv420.data = sdr;
     yuv420.width = width;
     yuv420.height = height;
+    yuv420.luma_stride = sdrStrides[0];
+    yuv420.chroma_stride = sdrStrides[1];
     yuv420.colorGamut = sdrColorGamut;
 
+    jpegr_exif_struct exif;
+    exif.data = const_cast<void*>(reinterpret_cast<const void*>(jExif->get()));
+    exif.length = jExif->size();
+
     jpegr_compressed_struct jpegR;
     jpegR.maxLength = width * height * sizeof(uint8_t);
 
@@ -377,7 +400,8 @@
 
     if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420,
             hdrTransferFunction,
-            &jpegR, jpegQuality, nullptr); success != android::OK) {
+            &jpegR, jpegQuality,
+            exif.length > 0 ? &exif : NULL); success != JPEGR_NO_ERROR) {
         ALOGW("Encode JPEG/R failed, error code: %d.", success);
         return false;
     }
@@ -419,20 +443,27 @@
 static jboolean YuvImage_compressToJpegR(JNIEnv* env, jobject, jbyteArray inHdr,
         jint hdrColorSpace, jbyteArray inSdr, jint sdrColorSpace,
         jint width, jint height, jint quality, jobject jstream,
-        jbyteArray jstorage) {
+        jbyteArray jstorage, jbyteArray jExif,
+        jintArray jHdrStrides, jintArray jSdrStrides) {
     jbyte* hdr = env->GetByteArrayElements(inHdr, NULL);
     jbyte* sdr = env->GetByteArrayElements(inSdr, NULL);
+    ScopedByteArrayRO exif(env, jExif);
+    ScopedIntArrayRO hdrStrides(env, jHdrStrides);
+    ScopedIntArrayRO sdrStrides(env, jSdrStrides);
+
     SkWStream* strm = CreateJavaOutputStreamAdaptor(env, jstream, jstorage);
     P010Yuv420ToJpegREncoder encoder;
 
     jboolean result = JNI_FALSE;
     if (encoder.encode(env, strm, hdr, hdrColorSpace, sdr, sdrColorSpace,
-                       width, height, quality)) {
+                       width, height, quality, &exif,
+                       &hdrStrides, &sdrStrides)) {
         result = JNI_TRUE;
     }
 
     env->ReleaseByteArrayElements(inHdr, hdr, 0);
     env->ReleaseByteArrayElements(inSdr, sdr, 0);
+
     delete strm;
     return result;
 }
@@ -441,7 +472,7 @@
 static const JNINativeMethod gYuvImageMethods[] = {
     {   "nativeCompressToJpeg",  "([BIII[I[IILjava/io/OutputStream;[B)Z",
         (void*)YuvImage_compressToJpeg },
-    {   "nativeCompressToJpegR",  "([BI[BIIIILjava/io/OutputStream;[B)Z",
+    {   "nativeCompressToJpegR",  "([BI[BIIIILjava/io/OutputStream;[B[B[I[I)Z",
         (void*)YuvImage_compressToJpegR }
 };
 
diff --git a/libs/hwui/jni/YuvToJpegEncoder.h b/libs/hwui/jni/YuvToJpegEncoder.h
index 0e711ef..629f1e6 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.h
+++ b/libs/hwui/jni/YuvToJpegEncoder.h
@@ -2,6 +2,7 @@
 #define _ANDROID_GRAPHICS_YUV_TO_JPEG_ENCODER_H_
 
 #include <android/data_space.h>
+#include <nativehelper/ScopedPrimitiveArray.h>
 #include <ultrahdr/jpegr.h>
 
 extern "C" {
@@ -90,11 +91,15 @@
      *  @param width Width of the Yuv data in terms of pixels.
      *  @param height Height of the Yuv data in terms of pixels.
      *  @param jpegQuality Picture quality in [0, 100].
+     *  @param exif Buffer holds EXIF package.
+     *  @param hdrStrides The number of row bytes in each image plane of the HDR input.
+     *  @param sdrStrides The number of row bytes in each image plane of the SDR input.
      *  @return true if successfully compressed the stream.
      */
     bool encode(JNIEnv* env,
             SkWStream* stream, void* hdr, int hdrColorSpace, void* sdr, int sdrColorSpace,
-            int width, int height, int jpegQuality);
+            int width, int height, int jpegQuality, ScopedByteArrayRO* exif,
+            ScopedIntArrayRO* hdrStrides, ScopedIntArrayRO* sdrStrides);
 
     /** Map data space (defined in DataSpace.java and data_space.h) to the color gamut
      *  used in JPEG/R
diff --git a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
index f060bb3..426644e 100644
--- a/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
+++ b/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp
@@ -84,7 +84,7 @@
     canvas->resetRecording(width, height, renderNode);
 }
 
-static jint android_view_DisplayListCanvas_getMaxTextureSize(CRITICAL_JNI_PARAMS) {
+static jint android_view_DisplayListCanvas_getMaxTextureSize(JNIEnv*, jobject) {
 #ifdef __ANDROID__ // Layoutlib does not support RenderProxy (RenderThread)
     return android::uirenderer::renderthread::RenderProxy::maxTextureSize();
 #else
@@ -175,14 +175,14 @@
 const char* const kClassPathName = "android/graphics/RecordingCanvas";
 
 static JNINativeMethod gMethods[] = {
+        {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
+        {"nGetMaximumTextureHeight", "()I",
+         (void*)android_view_DisplayListCanvas_getMaxTextureSize},
         // ------------ @CriticalNative --------------
         {"nCreateDisplayListCanvas", "(JII)J",
          (void*)android_view_DisplayListCanvas_createDisplayListCanvas},
         {"nResetDisplayListCanvas", "(JJII)V",
          (void*)android_view_DisplayListCanvas_resetDisplayListCanvas},
-        {"nGetMaximumTextureWidth", "()I", (void*)android_view_DisplayListCanvas_getMaxTextureSize},
-        {"nGetMaximumTextureHeight", "()I",
-         (void*)android_view_DisplayListCanvas_getMaxTextureSize},
         {"nEnableZ", "(JZ)V", (void*)android_view_DisplayListCanvas_enableZ},
         {"nFinishRecording", "(JJ)V", (void*)android_view_DisplayListCanvas_finishRecording},
         {"nDrawRenderNode", "(JJ)V", (void*)android_view_DisplayListCanvas_drawRenderNode},
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 706f18c..e3cdee6 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "HardwareBufferRenderer"
 #define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <GraphicsJNI.h>
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index d04de37..d15b1680 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "ThreadedRenderer"
 #define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <FrameInfo.h>
@@ -27,7 +25,7 @@
 #include <SkColorSpace.h>
 #include <SkData.h>
 #include <SkImage.h>
-#include <SkImagePriv.h>
+#include <SkImageAndroid.h>
 #include <SkPicture.h>
 #include <SkPixmap.h>
 #include <SkSerialProcs.h>
@@ -35,7 +33,9 @@
 #include <SkTypeface.h>
 #include <dlfcn.h>
 #include <gui/TraceUtils.h>
+#include <include/encode/SkPngEncoder.h>
 #include <inttypes.h>
+#include <log/log.h>
 #include <media/NdkImage.h>
 #include <media/NdkImageReader.h>
 #include <nativehelper/JNIPlatformHelp.h>
@@ -58,6 +58,7 @@
 
 #include "JvmErrorReporter.h"
 #include "android_graphics_HardwareRendererObserver.h"
+#include "utils/ForceDark.h"
 
 namespace android {
 
@@ -477,7 +478,7 @@
         // actually cross thread boundaries here, make a copy so it's immutable proper
         if (bitmap && !bitmap->isImmutable()) {
             ATRACE_NAME("Copying mutable bitmap");
-            return SkImage::MakeFromBitmap(*bitmap);
+            return SkImages::RasterFromBitmap(*bitmap);
         }
         if (img->isTextureBacked()) {
             ATRACE_NAME("Readback of texture image");
@@ -497,7 +498,7 @@
                 return sk_ref_sp(img);
             }
             bm.setImmutable();
-            return SkMakeImageFromRasterBitmap(bm, kNever_SkCopyPixelsMode);
+            return SkImages::PinnableRasterFromBitmap(bm);
         }
         return sk_ref_sp(img);
     }
@@ -524,7 +525,16 @@
         if (iter != context->mTextureMap.end()) {
             img = iter->second.get();
         }
-        return img->encodeToData();
+        if (!img) {
+            return nullptr;
+        }
+        // The following encode (specifically the pixel readback) will fail on a
+        // texture-backed image. They should already be raster images, but on
+        // the off-chance they aren't, we will just serialize it as nothing.
+        if (img->isTextureBacked()) {
+            return SkData::MakeEmpty();
+        }
+        return SkPngEncoder::Encode(nullptr, img, {});
     }
 
     void serialize(SkWStream* stream) const override {
@@ -815,10 +825,10 @@
     proxy->allocateBuffers();
 }
 
-static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz,
-        jlong proxyPtr, jboolean enable) {
+static void android_view_ThreadedRenderer_setForceDark(JNIEnv* env, jobject clazz, jlong proxyPtr,
+                                                       jint type) {
     RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
-    proxy->setForceDark(enable);
+    proxy->setForceDark(static_cast<ForceDarkType>(type));
 }
 
 static void android_view_ThreadedRenderer_preload(JNIEnv*, jclass) {
@@ -1007,7 +1017,7 @@
         {"nSetIsolatedProcess", "(Z)V", (void*)android_view_ThreadedRenderer_setIsolatedProcess},
         {"nSetContextPriority", "(I)V", (void*)android_view_ThreadedRenderer_setContextPriority},
         {"nAllocateBuffers", "(J)V", (void*)android_view_ThreadedRenderer_allocateBuffers},
-        {"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark},
+        {"nSetForceDark", "(JI)V", (void*)android_view_ThreadedRenderer_setForceDark},
         {"nSetDisplayDensityDpi", "(I)V",
          (void*)android_view_ThreadedRenderer_setDisplayDensityDpi},
         {"nInitDisplayInfo", "(IIFIJJZZ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo},
diff --git a/libs/hwui/jni/android_graphics_RenderNode.cpp b/libs/hwui/jni/android_graphics_RenderNode.cpp
index 2a218a2..a7d6423 100644
--- a/libs/hwui/jni/android_graphics_RenderNode.cpp
+++ b/libs/hwui/jni/android_graphics_RenderNode.cpp
@@ -568,6 +568,7 @@
 struct {
     jclass clazz;
     jmethodID callPositionChanged;
+    jmethodID callPositionChanged2;
     jmethodID callApplyStretch;
     jmethodID callPositionLost;
 } gPositionListener;
@@ -589,14 +590,31 @@
         virtual void onPositionUpdated(RenderNode& node, const TreeInfo& info) override {
             if (CC_UNLIKELY(!mListener || !info.updateWindowPositions)) return;
 
-            Matrix4 transform;
-            info.damageAccumulator->computeCurrentTransform(&transform);
             const RenderProperties& props = node.properties();
+            const bool enableClip = Properties::clipSurfaceViews;
 
-            uirenderer::Rect bounds(props.getWidth(), props.getHeight());
+            Matrix4 transform;
+            SkIRect clipBounds;
+            if (enableClip) {
+                uirenderer::Rect initialClipBounds;
+                const auto clipFlags = props.getClippingFlags();
+                if (clipFlags) {
+                    props.getClippingRectForFlags(clipFlags, &initialClipBounds);
+                } else {
+                    // Works for RenderNode::damageSelf()
+                    initialClipBounds.set(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
+                }
+                clipBounds =
+                        info.damageAccumulator
+                                ->computeClipAndTransform(initialClipBounds.toSkRect(), &transform)
+                                .roundOut();
+            } else {
+                info.damageAccumulator->computeCurrentTransform(&transform);
+            }
             bool useStretchShader =
                     Properties::getStretchEffectBehavior() != StretchEffectBehavior::UniformScale;
             // Compute the transform bounds first before calculating the stretch
+            uirenderer::Rect bounds(props.getWidth(), props.getHeight());
             transform.mapRect(bounds);
 
             bool hasStretch = useStretchShader && info.stretchEffectCount;
@@ -614,10 +632,11 @@
                 bounds.roundOut();
             }
 
-            if (mPreviousPosition == bounds) {
+            if (mPreviousPosition == bounds && mPreviousClip == clipBounds) {
                 return;
             }
             mPreviousPosition = bounds;
+            mPreviousClip = clipBounds;
 
             ATRACE_NAME("Update SurfaceView position");
 
@@ -629,11 +648,23 @@
             // In particular if the app removes a view from the view tree before
             // this callback is dispatched, then we lose the position
             // information for this frame.
-            jboolean keepListening = env->CallStaticBooleanMethod(
-                    gPositionListener.clazz, gPositionListener.callPositionChanged, mListener,
-                    static_cast<jlong>(info.canvasContext.getFrameNumber()),
-                    static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
-                    static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom));
+            jboolean keepListening;
+            if (!enableClip) {
+                keepListening = env->CallStaticBooleanMethod(
+                        gPositionListener.clazz, gPositionListener.callPositionChanged, mListener,
+                        static_cast<jlong>(info.canvasContext.getFrameNumber()),
+                        static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
+                        static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom));
+            } else {
+                keepListening = env->CallStaticBooleanMethod(
+                        gPositionListener.clazz, gPositionListener.callPositionChanged2, mListener,
+                        static_cast<jlong>(info.canvasContext.getFrameNumber()),
+                        static_cast<jint>(bounds.left), static_cast<jint>(bounds.top),
+                        static_cast<jint>(bounds.right), static_cast<jint>(bounds.bottom),
+                        static_cast<jint>(clipBounds.fLeft), static_cast<jint>(clipBounds.fTop),
+                        static_cast<jint>(clipBounds.fRight),
+                        static_cast<jint>(clipBounds.fBottom));
+            }
             if (!keepListening) {
                 env->DeleteGlobalRef(mListener);
                 mListener = nullptr;
@@ -738,6 +769,7 @@
         JavaVM* mVm;
         jobject mListener;
         uirenderer::Rect mPreviousPosition;
+        uirenderer::Rect mPreviousClip;
     };
 
     RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
@@ -866,6 +898,8 @@
     gPositionListener.clazz = MakeGlobalRefOrDie(env, clazz);
     gPositionListener.callPositionChanged = GetStaticMethodIDOrDie(
             env, clazz, "callPositionChanged", "(Ljava/lang/ref/WeakReference;JIIII)Z");
+    gPositionListener.callPositionChanged2 = GetStaticMethodIDOrDie(
+            env, clazz, "callPositionChanged2", "(Ljava/lang/ref/WeakReference;JIIIIIIII)Z");
     gPositionListener.callApplyStretch = GetStaticMethodIDOrDie(
             env, clazz, "callApplyStretch", "(Ljava/lang/ref/WeakReference;JFFFFFFFFFF)Z");
     gPositionListener.callPositionLost = GetStaticMethodIDOrDie(
diff --git a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
index 764eff9..b86c74fe 100644
--- a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
+++ b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "OpenGLRenderer"
-
 #include <Interpolator.h>
 #include <cutils/log.h>
 
diff --git a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
index c6d26f8..40be924 100644
--- a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
+++ b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "OpenGLRenderer"
-
 #include <Animator.h>
 #include <Interpolator.h>
 #include <RenderProperties.h>
diff --git a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
index 9cffceb..ade48f2 100644
--- a/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
+++ b/libs/hwui/jni/android_graphics_drawable_VectorDrawable.cpp
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-#include "GraphicsJNI.h"
+#include <hwui/Paint.h>
 
+#include "ColorFilter.h"
+#include "GraphicsJNI.h"
 #include "PathParser.h"
 #include "VectorDrawable.h"
 
-#include <hwui/Paint.h>
-
 namespace android {
 using namespace uirenderer;
 using namespace uirenderer::VectorDrawable;
@@ -108,8 +108,9 @@
     Canvas* canvas = reinterpret_cast<Canvas*>(canvasPtr);
     SkRect rect;
     GraphicsJNI::jrect_to_rect(env, jrect, &rect);
-    SkColorFilter* colorFilter = reinterpret_cast<SkColorFilter*>(colorFilterPtr);
-    return tree->draw(canvas, colorFilter, rect, needsMirroring, canReuseCache);
+    auto colorFilter = ColorFilter::fromJava(colorFilterPtr);
+    auto skColorFilter = colorFilter != nullptr ? colorFilter->getInstance() : nullptr;
+    return tree->draw(canvas, skColorFilter.get(), rect, needsMirroring, canReuseCache);
 }
 
 /**
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 1af60b2..f405aba 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include "Font.h"
 #include "SkData.h"
 #include "SkFont.h"
@@ -40,6 +37,7 @@
 #include <minikin/LocaleList.h>
 #include <minikin/SystemFonts.h>
 #include <ui/FatVector.h>
+#include <utils/TypefaceUtils.h>
 
 #include <memory>
 
@@ -127,7 +125,7 @@
 static jlong Font_Builder_clone(JNIEnv* env, jobject clazz, jlong fontPtr, jlong builderPtr,
                                 jint weight, jboolean italic, jint ttcIndex) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
-    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
     std::unique_ptr<NativeFontBuilder> builder(toBuilder(builderPtr));
 
     // Reconstruct SkTypeface with different arguments from existing SkTypeface.
@@ -159,7 +157,7 @@
 static jfloat Font_getGlyphBounds(JNIEnv* env, jobject, jlong fontHandle, jint glyphId,
                                   jlong paintHandle, jobject rect) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
-    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
 
     SkFont* skFont = &paint->getSkFont();
@@ -179,7 +177,7 @@
 static jfloat Font_getFontMetrics(JNIEnv* env, jobject, jlong fontHandle, jlong paintHandle,
                                   jobject metricsObj) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontHandle);
-    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->typeface().get());
+    MinikinFontSkia* minikinSkia = static_cast<MinikinFontSkia*>(font->font->baseTypeface().get());
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
 
     SkFont* skFont = &paint->getSkFont();
@@ -209,7 +207,7 @@
 // Fast Native
 static jobject Font_newByteBuffer(JNIEnv* env, jobject, jlong fontPtr) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
-    const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+    const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
     return env->NewDirectByteBuffer(const_cast<void*>(minikinFont->GetFontData()),
                                     minikinFont->GetFontSize());
 }
@@ -217,7 +215,7 @@
 // Critical Native
 static jlong Font_getBufferAddress(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
-    return reinterpret_cast<jlong>(font->font->typeface()->GetFontData());
+    return reinterpret_cast<jlong>(font->font->baseTypeface()->GetFontData());
 }
 
 // Critical Native
@@ -236,7 +234,7 @@
         }
         return env->NewStringUTF(path.c_str());
     } else {
-        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
         const std::string& path = minikinFont->GetFontPath();
         if (path.empty()) {
             return nullptr;
@@ -275,7 +273,7 @@
         reader.skipString();  // fontPath
         return reader.read<int>();
     } else {
-        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
         return minikinFont->GetFontIndex();
     }
 }
@@ -289,7 +287,7 @@
         reader.skip<int>();   // fontIndex
         return reader.readArray<minikin::FontVariation>().second;
     } else {
-        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
         return minikinFont->GetAxes().size();
     }
 }
@@ -304,7 +302,7 @@
         reader.skip<int>();   // fontIndex
         var = reader.readArray<minikin::FontVariation>().first[index];
     } else {
-        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->typeface();
+        const std::shared_ptr<minikin::MinikinFont>& minikinFont = font->font->baseTypeface();
         var = minikinFont->GetAxes().at(index);
     }
     uint32_t floatBinary = *reinterpret_cast<const uint32_t*>(&var.value);
@@ -314,7 +312,7 @@
 // Critical Native
 static jint Font_getSourceId(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) {
     FontWrapper* font = reinterpret_cast<FontWrapper*>(fontPtr);
-    return font->font->typeface()->GetSourceId();
+    return font->font->baseTypeface()->GetSourceId();
 }
 
 static jlongArray Font_getAvailableFontSet(JNIEnv* env, jobject) {
@@ -462,7 +460,7 @@
     args.setCollectionIndex(ttcIndex);
     args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
 
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
     sk_sp<SkTypeface> face(fm->makeFromStream(std::move(fontData), args));
     if (face == nullptr) {
         return nullptr;
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index ee158ee..462c8c8 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include "graphics_jni_helpers.h"
 #include <nativehelper/ScopedUtfChars.h>
 
@@ -60,7 +57,7 @@
 // Regular JNI
 static jlong FontFamily_Builder_build(JNIEnv* env, jobject clazz, jlong builderPtr,
                                       jstring langTags, jint variant, jboolean isCustomFallback,
-                                      jboolean isDefaultFallback) {
+                                      jboolean isDefaultFallback, jint variationFamilyType) {
     std::unique_ptr<NativeFamilyBuilder> builder(toBuilder(builderPtr));
     uint32_t localeId;
     if (langTags == nullptr) {
@@ -71,7 +68,8 @@
     }
     std::shared_ptr<minikin::FontFamily> family = minikin::FontFamily::create(
             localeId, static_cast<minikin::FamilyVariant>(variant), std::move(builder->fonts),
-            isCustomFallback, isDefaultFallback);
+            isCustomFallback, isDefaultFallback,
+            static_cast<minikin::VariationFamilyType>(variationFamilyType));
     if (family->getCoverage().length() == 0) {
         // No coverage means minikin rejected given font for some reasons.
         jniThrowException(env, "java/lang/IllegalArgumentException",
@@ -121,7 +119,7 @@
 static const JNINativeMethod gFontFamilyBuilderMethods[] = {
         {"nInitBuilder", "()J", (void*)FontFamily_Builder_initBuilder},
         {"nAddFont", "(JJ)V", (void*)FontFamily_Builder_addFont},
-        {"nBuild", "(JLjava/lang/String;IZZ)J", (void*)FontFamily_Builder_build},
+        {"nBuild", "(JLjava/lang/String;IZZI)J", (void*)FontFamily_Builder_build},
         {"nGetReleaseNativeFamily", "()J", (void*)FontFamily_Builder_GetReleaseFunc},
 };
 
diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp
index 427bafa..3b18f5f 100644
--- a/libs/hwui/jni/pdf/PdfEditor.cpp
+++ b/libs/hwui/jni/pdf/PdfEditor.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "PdfEditor"
-
 #include <sys/types.h>
 #include <unistd.h>
 
diff --git a/libs/hwui/jni/pdf/PdfUtils.cpp b/libs/hwui/jni/pdf/PdfUtils.cpp
index 06d2028..6887fda 100644
--- a/libs/hwui/jni/pdf/PdfUtils.cpp
+++ b/libs/hwui/jni/pdf/PdfUtils.cpp
@@ -16,14 +16,11 @@
 
 #include "PdfUtils.h"
 
-#include "jni.h"
 #include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
 
 #include "fpdfview.h"
-
-#undef LOG_TAG
-#define LOG_TAG "PdfUtils"
-#include <utils/Log.h>
+#include "jni.h"
 
 namespace android {
 
diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp
index 55f03bd..322af7e 100644
--- a/libs/hwui/jni/text/GraphemeBreak.cpp
+++ b/libs/hwui/jni/text/GraphemeBreak.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "GraphemeBreaker"
-
 #include <minikin/GraphemeBreak.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 
diff --git a/libs/hwui/jni/text/LineBreaker.cpp b/libs/hwui/jni/text/LineBreaker.cpp
index 6986517..c512256 100644
--- a/libs/hwui/jni/text/LineBreaker.cpp
+++ b/libs/hwui/jni/text/LineBreaker.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "LineBreaker"
-
 #include "utils/misc.h"
 #include "utils/Log.h"
 #include "graphics_jni_helpers.h"
@@ -54,13 +51,12 @@
 
 // set text and set a number of parameters for creating a layout (width, tabstops, strategy,
 // hyphenFrequency)
-static jlong nInit(JNIEnv* env, jclass /* unused */,
-        jint breakStrategy, jint hyphenationFrequency, jboolean isJustified, jintArray indents) {
+static jlong nInit(JNIEnv* env, jclass /* unused */, jint breakStrategy, jint hyphenationFrequency,
+                   jboolean isJustified, jintArray indents, jboolean useBoundsForWidth) {
     return reinterpret_cast<jlong>(new minikin::android::StaticLayoutNative(
             static_cast<minikin::BreakStrategy>(breakStrategy),
-            static_cast<minikin::HyphenationFrequency>(hyphenationFrequency),
-            isJustified,
-            jintArrayToFloatVector(env, indents)));
+            static_cast<minikin::HyphenationFrequency>(hyphenationFrequency), isJustified,
+            jintArrayToFloatVector(env, indents), useBoundsForWidth));
 }
 
 static void nFinish(jlong nativePtr) {
@@ -131,39 +127,44 @@
 }
 
 static const JNINativeMethod gMethods[] = {
-    // Fast Natives
-    {"nInit", "("
-        "I"  // breakStrategy
-        "I"  // hyphenationFrequency
-        "Z"  // isJustified
-        "[I"  // indents
-        ")J", (void*) nInit},
+        // Fast Natives
+        {"nInit",
+         "("
+         "I"   // breakStrategy
+         "I"   // hyphenationFrequency
+         "Z"   // isJustified
+         "[I"  // indents
+         "Z"   // useBoundsForWidth
+         ")J",
+         (void*)nInit},
 
-    // Critical Natives
-    {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},
+        // Critical Natives
+        {"nGetReleaseFunc", "()J", (void*)nGetReleaseFunc},
 
-    // Regular JNI
-    {"nComputeLineBreaks", "("
-        "J"  // nativePtr
-        "[C"  // text
-        "J"  // MeasuredParagraph ptr.
-        "I"  // length
-        "F"  // firstWidth
-        "I"  // firstWidthLineCount
-        "F"  // restWidth
-        "[F"  // variableTabStops
-        "F"  // defaultTabStop
-        "I"  // indentsOffset
-        ")J", (void*) nComputeLineBreaks},
+        // Regular JNI
+        {"nComputeLineBreaks",
+         "("
+         "J"   // nativePtr
+         "[C"  // text
+         "J"   // MeasuredParagraph ptr.
+         "I"   // length
+         "F"   // firstWidth
+         "I"   // firstWidthLineCount
+         "F"   // restWidth
+         "[F"  // variableTabStops
+         "F"   // defaultTabStop
+         "I"   // indentsOffset
+         ")J",
+         (void*)nComputeLineBreaks},
 
-    // Result accessors, CriticalNatives
-    {"nGetLineCount", "(J)I", (void*)nGetLineCount},
-    {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset},
-    {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth},
-    {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent},
-    {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent},
-    {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag},
-    {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc},
+        // Result accessors, CriticalNatives
+        {"nGetLineCount", "(J)I", (void*)nGetLineCount},
+        {"nGetLineBreakOffset", "(JI)I", (void*)nGetLineBreakOffset},
+        {"nGetLineWidth", "(JI)F", (void*)nGetLineWidth},
+        {"nGetLineAscent", "(JI)F", (void*)nGetLineAscent},
+        {"nGetLineDescent", "(JI)F", (void*)nGetLineDescent},
+        {"nGetLineFlag", "(JI)I", (void*)nGetLineFlag},
+        {"nGetReleaseResultFunc", "()J", (void*)nGetReleaseResultFunc},
 };
 
 int register_android_graphics_text_LineBreaker(JNIEnv* env) {
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index c13c800..746745a 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "MeasuredText"
-
 #include "GraphicsJNI.h"
 #include "utils/misc.h"
 #include "utils/Log.h"
@@ -65,13 +62,14 @@
 
 // Regular JNI
 static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
-                         jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
-                         jboolean isRtl) {
+                         jlong paintPtr, jint lbStyle, jint lbWordStyle, jboolean hyphenation,
+                         jint start, jint end, jboolean isRtl) {
     Paint* paint = toPaint(paintPtr);
     const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
     minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
     toBuilder(builderPtr)
-            ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl);
+            ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, hyphenation,
+                          isRtl);
 }
 
 // Regular JNI
@@ -84,13 +82,14 @@
 // Regular JNI
 static jlong nBuildMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr, jlong hintPtr,
                                 jcharArray javaText, jboolean computeHyphenation,
-                                jboolean computeLayout, jboolean fastHyphenationMode) {
+                                jboolean computeLayout, jboolean computeBounds,
+                                jboolean fastHyphenationMode) {
     ScopedCharArrayRO text(env, javaText);
     const minikin::U16StringPiece textBuffer(text.get(), text.size());
 
     // Pass the ownership to Java.
     return toJLong(toBuilder(builderPtr)
-                           ->build(textBuffer, computeHyphenation, computeLayout,
+                           ->build(textBuffer, computeHyphenation, computeLayout, computeBounds,
                                    fastHyphenationMode, toMeasuredParagraph(hintPtr))
                            .release());
 }
@@ -161,9 +160,9 @@
 static const JNINativeMethod gMTBuilderMethods[] = {
         // MeasuredParagraphBuilder native functions.
         {"nInitBuilder", "()J", (void*)nInitBuilder},
-        {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
+        {"nAddStyleRun", "(JJIIZIIZ)V", (void*)nAddStyleRun},
         {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
-        {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText},
+        {"nBuildMeasuredText", "(JJ[CZZZZ)J", (void*)nBuildMeasuredText},
         {"nFreeBuilder", "(J)V", (void*)nFreeBuilder},
 };
 
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 8e4dd53..6c05346 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "TextShaper"
-
 #include "graphics_jni_helpers.h"
 #include <nativehelper/ScopedStringChars.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
@@ -62,7 +59,7 @@
         const minikin::Font* font = layout.getFont(i);
         if (seenFonts.find(font) != seenFonts.end()) continue;
         minikin::MinikinExtent extent = {};
-        font->typeface()->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
+        layout.typeface(i)->GetFontExtent(&extent, minikinPaint, layout.getFakery(i));
         overallAscent = std::min(overallAscent, extent.ascent);
         overallDescent = std::max(overallDescent, extent.descent);
     }
@@ -148,6 +145,30 @@
 }
 
 // CriticalNative
+static jboolean TextShaper_Result_getFakeBold(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+    const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+    return layout->layout.getFakery(i).isFakeBold();
+}
+
+// CriticalNative
+static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+    const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+    return layout->layout.getFakery(i).isFakeItalic();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+    const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+    return layout->layout.getFakery(i).wghtAdjustment();
+}
+
+// CriticalNative
+static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
+    const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
+    return layout->layout.getFakery(i).italAdjustment();
+}
+
+// CriticalNative
 static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint i) {
     const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr);
     std::shared_ptr<minikin::Font> fontRef = layout->layout.getFontRef(i);
@@ -185,15 +206,19 @@
 };
 
 static const JNINativeMethod gResultMethods[] = {
-    { "nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount },
-    { "nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance },
-    { "nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent },
-    { "nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent },
-    { "nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId },
-    { "nGetX", "(JI)F", (void*)TextShaper_Result_getX },
-    { "nGetY", "(JI)F", (void*)TextShaper_Result_getY },
-    { "nGetFont", "(JI)J", (void*)TextShaper_Result_getFont },
-    { "nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc },
+        {"nGetGlyphCount", "(J)I", (void*)TextShaper_Result_getGlyphCount},
+        {"nGetTotalAdvance", "(J)F", (void*)TextShaper_Result_getTotalAdvance},
+        {"nGetAscent", "(J)F", (void*)TextShaper_Result_getAscent},
+        {"nGetDescent", "(J)F", (void*)TextShaper_Result_getDescent},
+        {"nGetGlyphId", "(JI)I", (void*)TextShaper_Result_getGlyphId},
+        {"nGetX", "(JI)F", (void*)TextShaper_Result_getX},
+        {"nGetY", "(JI)F", (void*)TextShaper_Result_getY},
+        {"nGetFont", "(JI)J", (void*)TextShaper_Result_getFont},
+        {"nGetFakeBold", "(JI)Z", (void*)TextShaper_Result_getFakeBold},
+        {"nGetFakeItalic", "(JI)Z", (void*)TextShaper_Result_getFakeItalic},
+        {"nGetWeightOverride", "(JI)F", (void*)TextShaper_Result_getWeightOverride},
+        {"nGetItalicOverride", "(JI)F", (void*)TextShaper_Result_getItalicOverride},
+        {"nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc},
 };
 
 int register_android_graphics_text_TextShaper(JNIEnv* env) {
diff --git a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
index ffad699..e81cbfb 100644
--- a/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
+++ b/libs/hwui/pipeline/skia/BackdropFilterDrawable.cpp
@@ -21,6 +21,9 @@
 
 #include "RenderNode.h"
 #include "RenderNodeDrawable.h"
+#ifdef __ANDROID__
+#include "include/gpu/ganesh/SkImageGanesh.h"
+#endif
 
 namespace android {
 namespace uirenderer {
@@ -72,9 +75,17 @@
     }
 
     auto imageSubset = mImageSubset.roundOut();
-    backdropImage =
-            backdropImage->makeWithFilter(canvas->recordingContext(), backdropFilter, imageSubset,
-                                          imageSubset, &mOutSubset, &mOutOffset);
+#ifdef __ANDROID__
+    if (canvas->recordingContext()) {
+        backdropImage =
+                SkImages::MakeWithFilter(canvas->recordingContext(), backdropImage, backdropFilter,
+                                         imageSubset, imageSubset, &mOutSubset, &mOutOffset);
+    } else
+#endif
+    {
+        backdropImage = SkImages::MakeWithFilter(backdropImage, backdropFilter, imageSubset,
+                                                 imageSubset, &mOutSubset, &mOutOffset);
+    }
     canvas->drawImageRect(backdropImage, SkRect::Make(mOutSubset), mDstBounds,
                           SkSamplingOptions(SkFilterMode::kLinear), &mPaint,
                           SkCanvas::kStrict_SrcRectConstraint);
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index 8d5967b..5d3fb30 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -21,10 +21,15 @@
 #include "GrBackendSurface.h"
 #include "RenderNode.h"
 #include "SkAndroidFrameworkUtils.h"
+#include "SkCanvas.h"
+#include "SkCanvasAndroid.h"
 #include "SkClipStack.h"
 #include "SkRect.h"
 #include "SkM44.h"
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/gl/GrGLTypes.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
 #include "utils/GLUtils.h"
 #include <effects/GainmapRenderer.h>
 #include "renderthread/CanvasContext.h"
@@ -34,7 +39,7 @@
 namespace skiapipeline {
 
 static void setScissor(int viewportHeight, const SkIRect& clip) {
-    SkASSERT(!clip.isEmpty());
+    LOG_FATAL_IF(clip.isEmpty(), "empty scissor clip");
     // transform to Y-flipped GL space, and prevent negatives
     GLint y = viewportHeight - clip.fBottom;
     GLint height = (viewportHeight - clip.fTop) - y;
@@ -42,9 +47,9 @@
 }
 
 static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) {
-    GrBackendRenderTarget renderTarget = canvas->topLayerBackendRenderTarget();
+    GrBackendRenderTarget renderTarget = skgpu::ganesh::TopLayerBackendRenderTarget(canvas);
     GrGLFramebufferInfo fboInfo;
-    LOG_ALWAYS_FATAL_IF(!renderTarget.getGLFramebufferInfo(&fboInfo),
+    LOG_ALWAYS_FATAL_IF(!GrBackendRenderTargets::GetGLFramebufferInfo(renderTarget, &fboInfo),
         "getGLFrameBufferInfo failed");
 
     *outFboID = fboInfo.fFBOID;
@@ -76,13 +81,13 @@
     }
 
     // flush will create a GrRenderTarget if not already present.
-    canvas->flush();
+    directContext->flushAndSubmit();
 
     GLuint fboID = 0;
     SkISize fboSize;
     GetFboDetails(canvas, &fboID, &fboSize);
 
-    SkIRect surfaceBounds = canvas->topLayerBounds();
+    SkIRect surfaceBounds = skgpu::ganesh::TopLayerBounds(canvas);
     SkIRect clipBounds = canvas->getDeviceClipBounds();
     SkM44 mat4(canvas->getLocalToDevice());
     SkRegion clipRegion;
@@ -95,12 +100,14 @@
         SkImageInfo surfaceInfo =
                 canvas->imageInfo().makeWH(clipBounds.width(), clipBounds.height());
         tmpSurface =
-                SkSurface::MakeRenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
+                SkSurfaces::RenderTarget(directContext, skgpu::Budgeted::kYes, surfaceInfo);
         tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
 
         GrGLFramebufferInfo fboInfo;
-        if (!tmpSurface->getBackendRenderTarget(SkSurface::kFlushWrite_BackendHandleAccess)
-                     .getGLFramebufferInfo(&fboInfo)) {
+        if (!GrBackendRenderTargets::GetGLFramebufferInfo(
+                    SkSurfaces::GetBackendRenderTarget(
+                        tmpSurface.get(), SkSurfaces::BackendHandleAccess::kFlushWrite),
+                    &fboInfo)) {
             ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor");
             return;
         }
@@ -163,7 +170,7 @@
 
         // GL ops get inserted here if previous flush is missing, which could dirty the stencil
         bool stencilWritten = SkAndroidFrameworkUtils::clipWithStencil(tmpCanvas);
-        tmpCanvas->flush();  // need this flush for the single op that draws into the stencil
+        directContext->flushAndSubmit();  // need this flush for the single op that draws into the stencil
 
         // ensure that the framebuffer that the webview will render into is bound before after we
         // draw into the stencil
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 9d72c23..ffa915a 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -15,21 +15,25 @@
  */
 
 #include "RenderNodeDrawable.h"
+
 #include <SkPaint.h>
 #include <SkPaintFilterCanvas.h>
 #include <SkPoint.h>
 #include <SkRRect.h>
 #include <SkRect.h>
 #include <gui/TraceUtils.h>
+#include <include/effects/SkImageFilters.h>
+#ifdef __ANDROID__
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#endif
+
+#include <optional>
+
 #include "RenderNode.h"
 #include "SkiaDisplayList.h"
 #include "StretchMask.h"
 #include "TransformCanvas.h"
 
-#include <include/effects/SkImageFilters.h>
-
-#include <optional>
-
 namespace android {
 namespace uirenderer {
 namespace skiapipeline {
@@ -52,6 +56,7 @@
                                                      int nestLevel) const {
     LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
     for (auto& child : displayList.mChildNodes) {
+        if (!child.getRenderNode()->isRenderable()) continue;
         const RenderProperties& childProperties = child.getNodeProperties();
 
         // immediate children cannot be projected on their parent
@@ -255,9 +260,19 @@
                 snapshotImage = renderNode->getLayerSurface()->makeImageSnapshot();
                 if (imageFilter) {
                     auto subset = SkIRect::MakeWH(srcBounds.width(), srcBounds.height());
-                    snapshotImage = snapshotImage->makeWithFilter(recordingContext, imageFilter,
-                                                                  subset, clipBounds.roundOut(),
-                                                                  &srcBounds, &offset);
+
+#ifdef __ANDROID__
+                    if (recordingContext) {
+                        snapshotImage = SkImages::MakeWithFilter(
+                                recordingContext, snapshotImage, imageFilter, subset,
+                                clipBounds.roundOut(), &srcBounds, &offset);
+                    } else
+#endif
+                    {
+                        snapshotImage = SkImages::MakeWithFilter(snapshotImage, imageFilter, subset,
+                                                                 clipBounds.roundOut(), &srcBounds,
+                                                                 &offset);
+                    }
                 }
             } else {
                 const auto snapshotResult = renderNode->updateSnapshotIfRequired(
diff --git a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
index 11977bd..136740c 100644
--- a/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
+++ b/libs/hwui/pipeline/skia/ReorderBarrierDrawables.cpp
@@ -27,7 +27,6 @@
 #include <SkRect.h>
 #include <SkScalar.h>
 #include <SkShadowUtils.h>
-#include <include/private/SkShadowFlags.h>
 
 namespace android {
 namespace uirenderer {
diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h
index 7495550..6ccb212 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.h
+++ b/libs/hwui/pipeline/skia/ShaderCache.h
@@ -26,6 +26,7 @@
 #include <string>
 #include <vector>
 
+class GrDirectContext;
 class SkData;
 
 namespace android {
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index af2d3b3..5c8285a 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -66,6 +66,12 @@
     }
 }
 
+void SkiaDisplayList::visit(std::function<void(const RenderNode&)> func) const {
+    for (auto& child : mChildNodes) {
+        child.getRenderNode()->visit(func);
+    }
+}
+
 static bool intersects(const SkISize screenSize, const Matrix4& mat, const SkRect& bounds) {
     Vector3 points[] = { Vector3 {bounds.fLeft, bounds.fTop, 0},
                          Vector3 {bounds.fRight, bounds.fTop, 0},
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index 7af31a4..b9dc1c4 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -96,6 +96,8 @@
 
     bool hasText() const { return mDisplayList.hasText(); }
 
+    bool hasFill() const { return mDisplayList.hasFill(); }
+
     /**
      * Attempts to reset and reuse this DisplayList.
      *
@@ -145,6 +147,8 @@
      */
     void updateChildren(std::function<void(RenderNode*)> updateFn);
 
+    void visit(std::function<void(const RenderNode&)> func) const;
+
     /**
      *  Returns true if there is a child render node that is a projection receiver.
      */
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
index 1042703..814b682 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.cpp
@@ -32,13 +32,13 @@
         , mTotalSize("bytes", 0)
         , mPurgeableSize("bytes", 0) {}
 
-const char* SkiaMemoryTracer::mapName(const char* resourceName) {
+std::optional<std::string> SkiaMemoryTracer::mapName(const std::string& resourceName) {
     for (auto& resource : mResourceMap) {
-        if (SkStrContains(resourceName, resource.first)) {
+        if (resourceName.find(resource.first) != std::string::npos) {
             return resource.second;
         }
     }
-    return nullptr;
+    return std::nullopt;
 }
 
 void SkiaMemoryTracer::processElement() {
@@ -62,7 +62,7 @@
         }
 
         // find the type if one exists
-        const char* type;
+        std::string type;
         auto typeResult = mCurrentValues.find("type");
         if (typeResult != mCurrentValues.end()) {
             type = typeResult->second.units;
@@ -71,14 +71,13 @@
         }
 
         // compute the type if we are itemizing or use the default "size" if we are not
-        const char* key = (mItemizeType) ? type : sizeResult->first;
-        SkASSERT(key != nullptr);
+        std::string key = (mItemizeType) ? type : sizeResult->first;
 
         // compute the top level element name using either the map or category key
-        const char* resourceName = mapName(mCurrentElement.c_str());
-        if (mCategoryKey != nullptr) {
+        std::optional<std::string> resourceName = mapName(mCurrentElement);
+        if (mCategoryKey) {
             // find the category if one exists
-            auto categoryResult = mCurrentValues.find(mCategoryKey);
+            auto categoryResult = mCurrentValues.find(*mCategoryKey);
             if (categoryResult != mCurrentValues.end()) {
                 resourceName = categoryResult->second.units;
             } else if (mItemizeType) {
@@ -87,11 +86,11 @@
         }
 
         // if we don't have a pretty name then use the dumpName
-        if (resourceName == nullptr) {
-            resourceName = mCurrentElement.c_str();
+        if (!resourceName) {
+            resourceName = mCurrentElement;
         }
 
-        auto result = mResults.find(resourceName);
+        auto result = mResults.find(*resourceName);
         if (result != mResults.end()) {
             auto& resourceValues = result->second;
             typeResult = resourceValues.find(key);
@@ -106,7 +105,7 @@
             TraceValue sizeValue = sizeResult->second;
             mCurrentValues.clear();
             mCurrentValues.insert({key, sizeValue});
-            mResults.insert({resourceName, mCurrentValues});
+            mResults.insert({*resourceName, mCurrentValues});
         }
     }
 
@@ -139,8 +138,9 @@
             for (const auto& typedValue : namedItem.second) {
                 TraceValue traceValue = convertUnits(typedValue.second);
                 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
-                log.appendFormat("    %s: %.2f %s (%d %s)\n", typedValue.first, traceValue.value,
-                                 traceValue.units, traceValue.count, entry);
+                log.appendFormat("    %s: %.2f %s (%d %s)\n", typedValue.first.c_str(),
+                                 traceValue.value, traceValue.units.c_str(), traceValue.count,
+                                 entry);
             }
         } else {
             auto result = namedItem.second.find("size");
@@ -148,7 +148,8 @@
                 TraceValue traceValue = convertUnits(result->second);
                 const char* entry = (traceValue.count > 1) ? "entries" : "entry";
                 log.appendFormat("  %s: %.2f %s (%d %s)\n", namedItem.first.c_str(),
-                                 traceValue.value, traceValue.units, traceValue.count, entry);
+                                 traceValue.value, traceValue.units.c_str(), traceValue.count,
+                                 entry);
             }
         }
     }
@@ -156,7 +157,7 @@
 
 size_t SkiaMemoryTracer::total() {
     processElement();
-    if (!strcmp("bytes", mTotalSize.units)) {
+    if ("bytes" == mTotalSize.units) {
         return mTotalSize.value;
     }
     return 0;
@@ -166,16 +167,16 @@
     TraceValue total = convertUnits(mTotalSize);
     TraceValue purgeable = convertUnits(mPurgeableSize);
     log.appendFormat("  %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
-                     total.value, total.units, purgeable.value, purgeable.units);
+                     total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
 }
 
 SkiaMemoryTracer::TraceValue SkiaMemoryTracer::convertUnits(const TraceValue& value) {
     TraceValue output(value);
-    if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+    if ("bytes" == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "KB";
     }
-    if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+    if ("KB" == output.units && output.value >= 1024) {
         output.value = output.value / 1024.0f;
         output.units = "MB";
     }
diff --git a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
index cba3b04..dbfc86b 100644
--- a/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
+++ b/libs/hwui/pipeline/skia/SkiaMemoryTracer.h
@@ -16,8 +16,9 @@
 
 #pragma once
 
-#include <SkString.h>
 #include <SkTraceMemoryDump.h>
+#include <optional>
+#include <string>
 #include <utils/String8.h>
 #include <unordered_map>
 #include <vector>
@@ -60,17 +61,17 @@
         TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
         TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
 
-        const char* units;
+        std::string units;
         float value;
         int count;
     };
 
-    const char* mapName(const char* resourceName);
+    std::optional<std::string> mapName(const std::string& resourceName);
     void processElement();
     TraceValue convertUnits(const TraceValue& value);
 
     const std::vector<ResourcePair> mResourceMap;
-    const char* mCategoryKey = nullptr;
+    std::optional<std::string> mCategoryKey;
     const bool mItemizeType;
 
     // variables storing the size of all elements being dumped
@@ -79,12 +80,12 @@
 
     // variables storing information on the current node being dumped
     std::string mCurrentElement;
-    std::unordered_map<const char*, TraceValue> mCurrentValues;
+    std::unordered_map<std::string, TraceValue> mCurrentValues;
 
     // variable that stores the final format of the data after the individual elements are processed
-    std::unordered_map<std::string, std::unordered_map<const char*, TraceValue>> mResults;
+    std::unordered_map<std::string, std::unordered_map<std::string, TraceValue>> mResults;
 };
 
 } /* namespace skiapipeline */
 } /* namespace uirenderer */
-} /* namespace android */
\ No newline at end of file
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 56f1e64..c8d5987 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -16,6 +16,9 @@
 
 #include "SkiaOpenGLPipeline.h"
 
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/gl/GrGLTypes.h>
 #include <GrBackendSurface.h>
 #include <SkBlendMode.h>
 #include <SkImageInfo.h>
@@ -138,7 +141,8 @@
         LOG_ALWAYS_FATAL("Unsupported color type.");
     }
 
-    GrBackendRenderTarget backendRT(frame.width(), frame.height(), 0, STENCIL_BUFFER_SIZE, fboInfo);
+    auto backendRT = GrBackendRenderTargets::MakeGL(frame.width(), frame.height(), 0,
+                                                    STENCIL_BUFFER_SIZE, fboInfo);
 
     SkSurfaceProps props(mColorMode == ColorMode::Default ? 0 : SkSurfaceProps::kAlwaysDither_Flag,
                          kUnknown_SkPixelGeometry);
@@ -150,9 +154,9 @@
         surface = getBufferSkSurface(bufferParams);
         preTransform = bufferParams.getTransform();
     } else {
-        surface = SkSurface::MakeFromBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
-                                                         getSurfaceOrigin(), colorType,
-                                                         mSurfaceColorSpace, &props);
+        surface = SkSurfaces::WrapBackendRenderTarget(mRenderThread.getGrContext(), backendRT,
+                                                      getSurfaceOrigin(), colorType,
+                                                      mSurfaceColorSpace, &props);
         preTransform = SkMatrix::I();
     }
 
@@ -175,7 +179,7 @@
 
     {
         ATRACE_NAME("flush commands");
-        surface->flushAndSubmit();
+        skgpu::ganesh::FlushAndSubmit(surface);
     }
     layerUpdateQueue->clear();
 
@@ -184,11 +188,12 @@
         dumpResourceCacheUsage();
     }
 
-    return {true, IRenderPipeline::DrawResult::kUnknownTime};
+    return {true, IRenderPipeline::DrawResult::kUnknownTime, android::base::unique_fd{}};
 }
 
-bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
-                                     FrameInfo* currentFrameInfo, bool* requireSwap) {
+bool SkiaOpenGLPipeline::swapBuffers(const Frame& frame, IRenderPipeline::DrawResult& drawResult,
+                                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                                     bool* requireSwap) {
     GL_CHECKPOINT(LOW);
 
     // Even if we decided to cancel the frame, from the perspective of jank
@@ -199,7 +204,7 @@
         return false;
     }
 
-    *requireSwap = drew || mEglManager.damageRequiresSwap();
+    *requireSwap = drawResult.success || mEglManager.damageRequiresSwap();
 
     if (*requireSwap && (CC_UNLIKELY(!mEglManager.swapBuffers(frame, screenDirty)))) {
         return false;
@@ -246,8 +251,7 @@
 
     if (mEglSurface != EGL_NO_SURFACE) {
         const bool preserveBuffer = (swapBehavior != SwapBehavior::kSwap_discardBuffer);
-        const bool isPreserved = mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
-        ALOGE_IF(preserveBuffer != isPreserved, "Unable to match the desired swap behavior.");
+        mEglManager.setPreserveBuffer(mEglSurface, preserveBuffer);
         return true;
     }
 
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 0325593..ebe8b6e 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -45,8 +45,9 @@
             const renderthread::HardwareBufferRenderParams& bufferParams,
             std::mutex& profilerLock) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
-    bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
-                     FrameInfo* currentFrameInfo, bool* requireSwap) override;
+    bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                     bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
     bool setSurface(ANativeWindow* surface, renderthread::SwapBehavior swapBehavior) override;
     [[nodiscard]] android::base::unique_fd flush() override;
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index cb23bcc..e0f1f6e 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -16,14 +16,16 @@
 
 #include "SkiaPipeline.h"
 
+#include <include/android/SkSurfaceAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/encode/SkPngEncoder.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
 #include <SkColorSpace.h>
 #include <SkData.h>
 #include <SkImage.h>
-#include <SkImageEncoder.h>
+#include <SkImageAndroid.h>
 #include <SkImageInfo.h>
-#include <SkImagePriv.h>
 #include <SkMatrix.h>
 #include <SkMultiPictureDocument.h>
 #include <SkOverdrawCanvas.h>
@@ -75,7 +77,7 @@
         return false;
     }
     for (SkImage* image : mutableImages) {
-        if (SkImage_pinAsTexture(image, mRenderThread.getGrContext())) {
+        if (skgpu::ganesh::PinAsTexture(mRenderThread.getGrContext(), image)) {
             mPinnedImages.emplace_back(sk_ref_sp(image));
         } else {
             return false;
@@ -86,7 +88,7 @@
 
 void SkiaPipeline::unpinImages() {
     for (auto& image : mPinnedImages) {
-        SkImage_unpinAsTexture(image.get(), mRenderThread.getGrContext());
+        skgpu::ganesh::UnpinTexture(mRenderThread.getGrContext(), image.get());
     }
     mPinnedImages.clear();
 }
@@ -187,9 +189,9 @@
                                  kPremul_SkAlphaType, getSurfaceColorSpace());
         SkSurfaceProps props(0, kUnknown_SkPixelGeometry);
         SkASSERT(mRenderThread.getGrContext() != nullptr);
-        node->setLayerSurface(SkSurface::MakeRenderTarget(mRenderThread.getGrContext(),
-                                                          skgpu::Budgeted::kYes, info, 0,
-                                                          this->getSurfaceOrigin(), &props));
+        node->setLayerSurface(SkSurfaces::RenderTarget(mRenderThread.getGrContext(),
+                                                       skgpu::Budgeted::kYes, info, 0,
+                                                       this->getSurfaceOrigin(), &props));
         if (node->getLayerSurface()) {
             // update the transform in window of the layer to reset its origin wrt light source
             // position
@@ -222,8 +224,8 @@
         ATRACE_FORMAT("Bitmap#prepareToDraw %dx%d", bitmap->width(), bitmap->height());
         auto image = bitmap->makeImage();
         if (image.get()) {
-            SkImage_pinAsTexture(image.get(), context);
-            SkImage_unpinAsTexture(image.get(), context);
+            skgpu::ganesh::PinAsTexture(context, image.get());
+            skgpu::ganesh::UnpinTexture(context, image.get());
             // A submit is necessary as there may not be a frame coming soon, so without a call
             // to submit these texture uploads can just sit in the queue building up until
             // we run out of RAM
@@ -439,6 +441,13 @@
                 procs.fTypefaceProc = [](SkTypeface* tf, void* ctx){
                     return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
                 };
+                procs.fImageProc = [](SkImage* img, void* ctx) -> sk_sp<SkData> {
+                    GrDirectContext* dCtx = static_cast<GrDirectContext*>(ctx);
+                    return SkPngEncoder::Encode(dCtx,
+                                                img,
+                                                SkPngEncoder::Options{});
+                };
+                procs.fImageCtx = mRenderThread.getGrContext();
                 auto data = picture->serialize(&procs);
                 savePictureAsync(data, mCapturedFile);
                 mCaptureSequence = 0;
@@ -621,7 +630,7 @@
     auto bufferColorSpace = bufferParams.getColorSpace();
     if (mBufferSurface == nullptr || mBufferColorSpace == nullptr ||
         !SkColorSpace::Equals(mBufferColorSpace.get(), bufferColorSpace.get())) {
-        mBufferSurface = SkSurface::MakeFromAHardwareBuffer(
+        mBufferSurface = SkSurfaces::WrapAndroidHardwareBuffer(
                 mRenderThread.getGrContext(), mHardwareBuffer, kTopLeft_GrSurfaceOrigin,
                 bufferColorSpace, nullptr, true);
         mBufferColorSpace = bufferColorSpace;
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 58c14c1..e917f9a 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -236,6 +236,17 @@
     }
 }
 
+void SkiaRecordingCanvas::onFilterPaint(android::Paint& paint) {
+    INHERITED::onFilterPaint(paint);
+    SkShader* shader = paint.getShader();
+    // TODO(b/264559422): This only works for very specifically a BitmapShader.
+    //  It's better than nothing, though
+    SkImage* image = shader ? shader->isAImage(nullptr, nullptr) : nullptr;
+    if (image) {
+        mDisplayList->mMutableImages.push_back(image);
+    }
+}
+
 void SkiaRecordingCanvas::drawBitmap(Bitmap& bitmap, float left, float top, const Paint* paint) {
     auto payload = DrawImagePayload(bitmap);
 
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index a8e4580..3bd091d 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -105,6 +105,8 @@
 
     void handleMutableImages(Bitmap& bitmap, DrawImagePayload& payload);
 
+    void onFilterPaint(Paint& paint) override;
+
     using INHERITED = SkiaCanvas;
 };
 
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index 7d19232..fd0a8e0 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -87,7 +87,7 @@
     }
 
     if (backBuffer.get() == nullptr) {
-        return {false, -1};
+        return {false, -1, android::base::unique_fd{}};
     }
 
     // update the coordinates of the global light position based on surface rotation
@@ -106,15 +106,15 @@
         std::scoped_lock lock(profilerLock);
         SkCanvas* profileCanvas = backBuffer->getCanvas();
         SkAutoCanvasRestore saver(profileCanvas, true);
-        profileCanvas->concat(mVkSurface->getCurrentPreTransform());
+        profileCanvas->concat(preTransform);
         SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
         profiler->draw(profileRenderer);
     }
 
-    nsecs_t submissionTime = IRenderPipeline::DrawResult::kUnknownTime;
+    VulkanManager::VkDrawResult drawResult;
     {
         ATRACE_NAME("flush commands");
-        submissionTime = vulkanManager().finishFrame(backBuffer.get());
+        drawResult = vulkanManager().finishFrame(backBuffer.get());
     }
     layerUpdateQueue->clear();
 
@@ -123,11 +123,12 @@
         dumpResourceCacheUsage();
     }
 
-    return {true, submissionTime};
+    return {true, drawResult.submissionTime, std::move(drawResult.presentFence)};
 }
 
-bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
-                                     FrameInfo* currentFrameInfo, bool* requireSwap) {
+bool SkiaVulkanPipeline::swapBuffers(const Frame& frame, IRenderPipeline::DrawResult& drawResult,
+                                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                                     bool* requireSwap) {
     // Even if we decided to cancel the frame, from the perspective of jank
     // metrics the frame was swapped at this point
     currentFrameInfo->markSwapBuffers();
@@ -136,10 +137,10 @@
         return false;
     }
 
-    *requireSwap = drew;
+    *requireSwap = drawResult.success;
 
     if (*requireSwap) {
-        vulkanManager().swapBuffers(mVkSurface, screenDirty);
+        vulkanManager().swapBuffers(mVkSurface, screenDirty, std::move(drawResult.presentFence));
     }
 
     return *requireSwap;
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index 37b86f1..624eaa5 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -45,8 +45,9 @@
             const renderthread::HardwareBufferRenderParams& bufferParams,
             std::mutex& profilerLock) override;
     GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
-    bool swapBuffers(const renderthread::Frame& frame, bool drew, const SkRect& screenDirty,
-                     FrameInfo* currentFrameInfo, bool* requireSwap) override;
+    bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
+                     const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                     bool* requireSwap) override;
     DeferredLayerUpdater* createTextureLayer() override;
     [[nodiscard]] android::base::unique_fd flush() override;
 
diff --git a/libs/hwui/pipeline/skia/StretchMask.cpp b/libs/hwui/pipeline/skia/StretchMask.cpp
index cad3703..1676787 100644
--- a/libs/hwui/pipeline/skia/StretchMask.cpp
+++ b/libs/hwui/pipeline/skia/StretchMask.cpp
@@ -18,14 +18,13 @@
 #include "SkBlendMode.h"
 #include "SkCanvas.h"
 #include "SkSurface.h"
-#include "include/gpu/GpuTypes.h" // from Skia
 
 #include "TransformCanvas.h"
 #include "SkiaDisplayList.h"
 
 using android::uirenderer::StretchMask;
 
-void StretchMask::draw(GrRecordingContext* context,
+void StretchMask::draw(GrRecordingContext*,
                        const StretchEffect& stretch,
                        const SkRect& bounds,
                        skiapipeline::SkiaDisplayList* displayList,
@@ -35,16 +34,14 @@
     if (mMaskSurface == nullptr || mMaskSurface->width() != width ||
         mMaskSurface->height() != height) {
         // Create a new surface if we don't have one or our existing size does
-        // not match.
-        mMaskSurface = SkSurface::MakeRenderTarget(
-            context,
-            skgpu::Budgeted::kYes,
-            SkImageInfo::Make(
-                width,
-                height,
-                SkColorType::kAlpha_8_SkColorType,
-                SkAlphaType::kPremul_SkAlphaType)
-        );
+        // not match. SkCanvas::makeSurface returns a new surface that will
+        // be GPU-backed if canvas was also.
+        mMaskSurface = canvas->makeSurface(SkImageInfo::Make(
+            width,
+            height,
+            SkColorType::kAlpha_8_SkColorType,
+            SkAlphaType::kPremul_SkAlphaType
+        ));
         mIsDirty = true;
     }
 
@@ -53,7 +50,7 @@
         // Make sure to apply target transformation to the mask canvas
         // to ensure the replayed drawing commands generate the same result
         auto previousMatrix = displayList->mParentMatrix;
-        displayList->mParentMatrix = maskCanvas->getTotalMatrix();
+        displayList->mParentMatrix = maskCanvas->getLocalToDeviceAs3x3();
         maskCanvas->save();
         maskCanvas->drawColor(0, SkBlendMode::kClear);
         TransformCanvas transformCanvas(maskCanvas, SkBlendMode::kSrcOver);
diff --git a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
index adf3c06..475b110 100644
--- a/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/VkInteropFunctorDrawable.cpp
@@ -35,6 +35,8 @@
 #include "effects/GainmapRenderer.h"
 
 #include <SkBlendMode.h>
+#include <SkImage.h>
+#include <SkImageAndroid.h>
 
 namespace android {
 namespace uirenderer {
@@ -183,9 +185,9 @@
     // drawing into the offscreen surface, so we need to reset it here.
     canvas->resetMatrix();
 
-    auto functorImage = SkImage::MakeFromAHardwareBuffer(mFrameBuffer.get(), kPremul_SkAlphaType,
-                                                         canvas->imageInfo().refColorSpace(),
-                                                         kBottomLeft_GrSurfaceOrigin);
+    auto functorImage = SkImages::DeferredFromAHardwareBuffer(
+        mFrameBuffer.get(), kPremul_SkAlphaType, canvas->imageInfo().refColorSpace(),
+        kBottomLeft_GrSurfaceOrigin);
     canvas->drawImage(functorImage, 0, 0, SkSamplingOptions(), &paint);
     canvas->restore();
 }
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index eb1f930..ed3fabc 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -24,8 +24,7 @@
 namespace uirenderer {
 
 /**
- * Structure used by OpenGLRenderer::callDrawGLFunction() to pass and
- * receive data from OpenGL functors.
+ * Structure used to pass and receive data from OpenGL functors.
  */
 struct DrawGlInfo {
     // Input: current clip rect
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index f695556..30d4612 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -17,6 +17,7 @@
 #include "CacheManager.h"
 
 #include <GrContextOptions.h>
+#include <GrTypes.h>
 #include <SkExecutor.h>
 #include <SkGraphics.h>
 #include <math.h>
@@ -110,13 +111,18 @@
     contextOptions->fPersistentCache = &cache;
 }
 
+static GrPurgeResourceOptions toSkiaEnum(bool scratchOnly) {
+    return scratchOnly ? GrPurgeResourceOptions::kScratchResourcesOnly :
+                         GrPurgeResourceOptions::kAllResources;
+}
+
 void CacheManager::trimMemory(TrimLevel mode) {
     if (!mGrContext) {
         return;
     }
 
     // flush and submit all work to the gpu and wait for it to finish
-    mGrContext->flushAndSubmit(/*syncCpu=*/true);
+    mGrContext->flushAndSubmit(GrSyncCpu::kYes);
 
     switch (mode) {
         case TrimLevel::BACKGROUND:
@@ -130,7 +136,7 @@
             // that have persistent data to be purged in LRU order.
             mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
             SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
-            mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
+            mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
             mGrContext->setResourceCacheLimit(mMaxResourceBytes);
             SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
             break;
@@ -150,7 +156,7 @@
         case CacheTrimLevel::ALL_CACHES:
             SkGraphics::PurgeAllCaches();
             if (mGrContext) {
-                mGrContext->purgeUnlockedResources(false);
+                mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources);
             }
             break;
         default:
@@ -163,7 +169,8 @@
         return;
     }
     mGrContext->flushAndSubmit();
-    mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30));
+    mGrContext->performDeferredCleanup(std::chrono::seconds(30),
+                                       GrPurgeResourceOptions::kAllResources);
 }
 
 void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
@@ -282,9 +289,10 @@
         const nsecs_t frameCompleteNanos = mFrameCompletions[0];
         const nsecs_t frameDiffNanos = now - frameCompleteNanos;
         const nsecs_t cleanupMillis =
-                ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
+                ns2ms(std::clamp(frameDiffNanos, mMemoryPolicy.minimumResourceRetention,
+                                 mMemoryPolicy.maximumResourceRetention));
         mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
-                                           mMemoryPolicy.purgeScratchOnly);
+                                           toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
     }
 }
 
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 5e43ac2..bcfa4f3 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -64,12 +64,13 @@
     void unregisterCanvasContext(CanvasContext* context);
     void onContextStopped(CanvasContext* context);
 
+    bool areAllContextsStopped();
+
 private:
     friend class RenderThread;
 
     explicit CacheManager(RenderThread& thread);
     void setupCacheLimits();
-    bool areAllContextsStopped();
     void checkUiHidden();
     void scheduleDestroyContext();
     void cancelDestroyContext();
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index f690783..9c7f7cc 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -123,8 +123,9 @@
         , mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
         , mContentDrawBounds(0, 0, 0, 0)
         , mRenderPipeline(std::move(renderPipeline))
-        , mHintSessionWrapper(uiThreadId, renderThreadId) {
+        , mHintSessionWrapper(std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId)) {
     mRenderThread.cacheManager().registerCanvasContext(this);
+    mRenderThread.renderState().registerContextCallback(this);
     rootRenderNode->makeRoot();
     mRenderNodes.emplace_back(rootRenderNode);
     mProfiler.setDensity(DeviceInfo::getDensity());
@@ -137,6 +138,8 @@
     }
     mRenderNodes.clear();
     mRenderThread.cacheManager().unregisterCanvasContext(this);
+    mRenderThread.renderState().removeContextCallback(this);
+    mHintSessionWrapper->destroy();
 }
 
 void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -160,6 +163,7 @@
     destroyHardwareResources();
     mAnimationContext->destroy();
     mRenderThread.cacheManager().onContextStopped(this);
+    mHintSessionWrapper->delayedDestroy(mRenderThread, 2_s, mHintSessionWrapper);
 }
 
 static void setBufferCount(ANativeWindow* window) {
@@ -356,8 +360,9 @@
     return true;
 }
 
-static bool wasSkipped(FrameInfo* info) {
-    return info && ((*info)[FrameInfoIndex::Flags] & FrameInfoFlags::SkippedFrame);
+static std::optional<SkippedFrameReason> wasSkipped(FrameInfo* info) {
+    if (info) return info->getSkippedFrameReason();
+    return std::nullopt;
 }
 
 bool CanvasContext::isSwapChainStuffed() {
@@ -406,13 +411,26 @@
 
     // If the previous frame was dropped we don't need to hold onto it, so
     // just keep using the previous frame's structure instead
-    if (wasSkipped(mCurrentFrameInfo)) {
+    if (const auto reason = wasSkipped(mCurrentFrameInfo)) {
         // Use the oldest skipped frame in case we skip more than a single frame
         if (!mSkippedFrameInfo) {
-            mSkippedFrameInfo.emplace();
-            mSkippedFrameInfo->vsyncId =
-                mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
-            mSkippedFrameInfo->startTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+            switch (*reason) {
+                case SkippedFrameReason::AlreadyDrawn:
+                case SkippedFrameReason::NoBuffer:
+                case SkippedFrameReason::NoOutputTarget:
+                    mSkippedFrameInfo.emplace();
+                    mSkippedFrameInfo->vsyncId =
+                            mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
+                    mSkippedFrameInfo->startTime =
+                            mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
+                    break;
+                case SkippedFrameReason::DrawingOff:
+                case SkippedFrameReason::ContextIsStopped:
+                case SkippedFrameReason::NothingToDraw:
+                    // Do not report those as skipped frames as there was no frame expected to be
+                    // drawn
+                    break;
+            }
         }
     } else {
         mCurrentFrameInfo = mJankTracker.startFrame();
@@ -426,7 +444,7 @@
     info.damageAccumulator = &mDamageAccumulator;
     info.layerUpdateQueue = &mLayerUpdateQueue;
     info.damageGenerationId = mDamageId++;
-    info.out.canDrawThisFrame = true;
+    info.out.skippedFrameReason = std::nullopt;
 
     mAnimationContext->startFrame(info.mode);
     for (const sp<RenderNode>& node : mRenderNodes) {
@@ -446,8 +464,8 @@
     mIsDirty = true;
 
     if (CC_UNLIKELY(!hasOutputTarget())) {
-        mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
-        info.out.canDrawThisFrame = false;
+        info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
+        mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
         return;
     }
 
@@ -462,23 +480,23 @@
         if (vsyncDelta < 2_ms) {
             // Already drew for this vsync pulse, UI draw request missed
             // the deadline for RT animations
-            info.out.canDrawThisFrame = false;
+            info.out.skippedFrameReason = SkippedFrameReason::AlreadyDrawn;
         }
     } else {
-        info.out.canDrawThisFrame = true;
+        info.out.skippedFrameReason = std::nullopt;
     }
 
     // TODO: Do we need to abort out if the backdrop is added but not ready? Should that even
     // be an allowable combination?
     if (mRenderNodes.size() > 2 && !mRenderNodes[1]->isRenderable()) {
-        info.out.canDrawThisFrame = false;
+        info.out.skippedFrameReason = SkippedFrameReason::NothingToDraw;
     }
 
-    if (info.out.canDrawThisFrame) {
+    if (!info.out.skippedFrameReason) {
         int err = mNativeSurface->reserveNext();
         if (err != OK) {
-            mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
-            info.out.canDrawThisFrame = false;
+            info.out.skippedFrameReason = SkippedFrameReason::NoBuffer;
+            mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
             ALOGW("reserveNext failed, error = %d (%s)", err, strerror(-err));
             if (err != TIMED_OUT) {
                 // A timed out surface can still recover, but assume others are permanently dead.
@@ -487,11 +505,11 @@
             }
         }
     } else {
-        mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
+        mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
     }
 
     bool postedFrameCallback = false;
-    if (info.out.hasAnimations || !info.out.canDrawThisFrame) {
+    if (info.out.hasAnimations || info.out.skippedFrameReason) {
         if (CC_UNLIKELY(!Properties::enableRTAnimations)) {
             info.out.requiresUiRedraw = true;
         }
@@ -544,7 +562,11 @@
 void CanvasContext::draw(bool solelyTextureViewUpdates) {
     if (auto grContext = getGrContext()) {
         if (grContext->abandoned()) {
-            LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
+            if (grContext->isDeviceLost()) {
+                LOG_ALWAYS_FATAL("Lost GPU device unexpectedly");
+                return;
+            }
+            LOG_ALWAYS_FATAL("GrContext is abandoned at start of CanvasContext::draw");
             return;
         }
     }
@@ -557,9 +579,20 @@
     mSyncDelayDuration = 0;
     mIdleDuration = 0;
 
-    if (!Properties::isDrawingEnabled() ||
-        (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw())) {
-        mCurrentFrameInfo->addFlag(FrameInfoFlags::SkippedFrame);
+    const auto skippedFrameReason = [&]() -> std::optional<SkippedFrameReason> {
+        if (!Properties::isDrawingEnabled()) {
+            return SkippedFrameReason::DrawingOff;
+        }
+
+        if (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw()) {
+            return SkippedFrameReason::NothingToDraw;
+        }
+
+        return std::nullopt;
+    }();
+    if (skippedFrameReason) {
+        mCurrentFrameInfo->setSkippedFrameReason(*skippedFrameReason);
+
         if (auto grContext = getGrContext()) {
             // Submit to ensure that any texture uploads complete and Skia can
             // free its staging buffers.
@@ -592,10 +625,9 @@
     {
         // FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
         // or it can lead to memory corruption.
-        drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
-                                           &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
-                                           mLightInfo, mRenderNodes, &(profiler()), mBufferParams,
-                                           profilerLock());
+        drawResult = mRenderPipeline->draw(
+                frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds,
+                mOpaque, mLightInfo, mRenderNodes, &(profiler()), mBufferParams, profilerLock());
     }
 
     uint64_t frameCompleteNr = getFrameNumber();
@@ -627,8 +659,8 @@
     bool didDraw = false;
 
     int error = OK;
-    bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult.success, windowDirty,
-                                                mCurrentFrameInfo, &requireSwap);
+    bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult, windowDirty, mCurrentFrameInfo,
+                                                &requireSwap);
 
     mCurrentFrameInfo->set(FrameInfoIndex::CommandSubmissionCompleted) = std::max(
             drawResult.commandSubmissionTime, mCurrentFrameInfo->get(FrameInfoIndex::SwapBuffers));
@@ -735,7 +767,7 @@
     int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
     int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
 
-    mHintSessionWrapper.updateTargetWorkDuration(frameDeadline - intendedVsync);
+    mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);
 
     if (didDraw) {
         int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
@@ -743,7 +775,7 @@
         int64_t actualDuration = frameDuration -
                                  (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
                                  dequeueBufferDuration - idleDuration;
-        mHintSessionWrapper.reportActualWorkDuration(actualDuration);
+        mHintSessionWrapper->reportActualWorkDuration(actualDuration);
     }
 
     mLastDequeueBufferDuration = dequeueBufferDuration;
@@ -886,10 +918,10 @@
 }
 
 void CanvasContext::prepareAndDraw(RenderNode* node) {
-    ATRACE_CALL();
+    int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
+    ATRACE_FORMAT("%s %" PRId64, __func__, vsyncId);
 
     nsecs_t vsync = mRenderThread.timeLord().computeFrameTimeNanos();
-    int64_t vsyncId = mRenderThread.timeLord().lastVsyncId();
     int64_t frameDeadline = mRenderThread.timeLord().lastFrameDeadline();
     int64_t frameInterval = mRenderThread.timeLord().frameIntervalNanos();
     int64_t frameInfo[UI_THREAD_FRAME_INFO_SIZE];
@@ -899,7 +931,7 @@
 
     TreeInfo info(TreeInfo::MODE_RT_ONLY, *this);
     prepareTree(info, frameInfo, systemTime(SYSTEM_TIME_MONOTONIC), node);
-    if (info.out.canDrawThisFrame) {
+    if (!info.out.skippedFrameReason) {
         draw(info.out.solelyTextureViewUpdates);
     } else {
         // wait on fences so tasks don't overlap next frame
@@ -961,6 +993,10 @@
     }
 }
 
+void CanvasContext::onContextDestroyed() {
+    destroyHardwareResources();
+}
+
 DeferredLayerUpdater* CanvasContext::createTextureLayer() {
     return mRenderPipeline->createTextureLayer();
 }
@@ -1078,11 +1114,11 @@
 }
 
 void CanvasContext::sendLoadResetHint() {
-    mHintSessionWrapper.sendLoadResetHint();
+    mHintSessionWrapper->sendLoadResetHint();
 }
 
 void CanvasContext::sendLoadIncreaseHint() {
-    mHintSessionWrapper.sendLoadIncreaseHint();
+    mHintSessionWrapper->sendLoadIncreaseHint();
 }
 
 void CanvasContext::setSyncDelayDuration(nsecs_t duration) {
@@ -1090,7 +1126,7 @@
 }
 
 void CanvasContext::startHintSession() {
-    mHintSessionWrapper.init();
+    mHintSessionWrapper->init();
 }
 
 bool CanvasContext::shouldDither() {
@@ -1099,6 +1135,12 @@
     return self->mColorMode != ColorMode::Default;
 }
 
+void CanvasContext::visitAllRenderNodes(std::function<void(const RenderNode&)> func) const {
+    for (auto node : mRenderNodes) {
+        node->visit(func);
+    }
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 3978fbc..e2e3fa3 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -43,8 +43,10 @@
 #include "Lighting.h"
 #include "ReliableSurface.h"
 #include "RenderNode.h"
+#include "renderstate/RenderState.h"
 #include "renderthread/RenderTask.h"
 #include "renderthread/RenderThread.h"
+#include "utils/ForceDark.h"
 #include "utils/RingBuffer.h"
 
 namespace android {
@@ -64,7 +66,7 @@
 // This per-renderer class manages the bridge between the global EGL context
 // and the render surface.
 // TODO: Rename to Renderer or some other per-window, top-level manager
-class CanvasContext : public IFrameCallback {
+class CanvasContext : public IFrameCallback, public IGpuContextCallback {
 public:
     static CanvasContext* create(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
                                  IContextFactory* contextFactory, pid_t uiThreadId,
@@ -154,6 +156,7 @@
     void markLayerInUse(RenderNode* node);
 
     void destroyHardwareResources();
+    void onContextDestroyed() override;
 
     DeferredLayerUpdater* createTextureLayer();
 
@@ -193,11 +196,9 @@
         mRenderPipeline->setPictureCapturedCallback(callback);
     }
 
-    void setForceDark(bool enable) { mUseForceDark = enable; }
+    void setForceDark(ForceDarkType type) { mForceDarkType = type; }
 
-    bool useForceDark() {
-        return mUseForceDark;
-    }
+    ForceDarkType getForceDarkType() { return mForceDarkType; }
 
     SkISize getNextFrameSize() const;
 
@@ -237,6 +238,8 @@
 
     static bool shouldDither();
 
+    void visitAllRenderNodes(std::function<void(const RenderNode&)>) const;
+
 private:
     CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode,
                   IContextFactory* contextFactory, std::unique_ptr<IRenderPipeline> renderPipeline,
@@ -318,7 +321,7 @@
     nsecs_t mLastDropVsync = 0;
 
     bool mOpaque;
-    bool mUseForceDark = false;
+    ForceDarkType mForceDarkType = ForceDarkType::NONE;
     LightInfo mLightInfo;
     LightGeometry mLightGeometry = {{0, 0, 0}, 0};
 
@@ -340,8 +343,7 @@
     std::string mName;
     JankTracker mJankTracker;
     FrameInfoVisualizer mProfiler;
-    std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter
-            GUARDED_BY(mFrameInfoMutex);
+    std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter GUARDED_BY(mFrameInfoMutex);
     std::mutex mFrameInfoMutex;
 
     std::set<RenderNode*> mPrefetchedLayers;
@@ -360,7 +362,7 @@
     std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
     std::function<void()> mPrepareSurfaceControlForWebviewCallback;
 
-    HintSessionWrapper mHintSessionWrapper;
+    std::shared_ptr<HintSessionWrapper> mHintSessionWrapper;
     nsecs_t mLastDequeueBufferDuration = 0;
     nsecs_t mSyncDelayDuration = 0;
     nsecs_t mIdleDuration = 0;
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 53b43ba..1b333bf 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -104,7 +104,7 @@
         info.forceDrawFrame = mForceDrawFrame;
         mForceDrawFrame = false;
         canUnblockUiThread = syncFrameState(info);
-        canDrawThisFrame = info.out.canDrawThisFrame;
+        canDrawThisFrame = !info.out.skippedFrameReason.has_value();
         solelyTextureViewUpdates = info.out.solelyTextureViewUpdates;
 
         if (mFrameCommitCallback) {
@@ -192,11 +192,12 @@
     if (CC_UNLIKELY(!hasTarget || !canDraw)) {
         if (!hasTarget) {
             mSyncResult |= SyncResult::LostSurfaceRewardIfFound;
+            info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
         } else {
             // If we have a surface but can't draw we must be stopped
             mSyncResult |= SyncResult::ContextIsStopped;
+            info.out.skippedFrameReason = SkippedFrameReason::ContextIsStopped;
         }
-        info.out.canDrawThisFrame = false;
     }
 
     if (info.out.hasAnimations) {
@@ -204,7 +205,7 @@
             mSyncResult |= SyncResult::UIRedrawRequired;
         }
     }
-    if (!info.out.canDrawThisFrame) {
+    if (info.out.skippedFrameReason) {
         mSyncResult |= SyncResult::FrameDropped;
     }
     // If prepareTextures is false, we ran out of texture cache space
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index b34da51..2362331 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -24,6 +24,7 @@
 #include <vector>
 
 #include "../Properties.h"
+#include "RenderThread.h"
 #include "thread/CommonPool.h"
 
 using namespace std::chrono_literals;
@@ -62,24 +63,26 @@
 }
 
 void HintSessionWrapper::destroy() {
-    if (mHintSessionFuture.valid()) {
-        mHintSession = mHintSessionFuture.get();
+    if (mHintSessionFuture.has_value()) {
+        mHintSession = mHintSessionFuture->get();
+        mHintSessionFuture = std::nullopt;
     }
     if (mHintSession) {
         mBinding->closeSession(mHintSession);
         mSessionValid = true;
         mHintSession = nullptr;
     }
+    mResetsSinceLastReport = 0;
 }
 
 bool HintSessionWrapper::init() {
     if (mHintSession != nullptr) return true;
-
     // If we're waiting for the session
-    if (mHintSessionFuture.valid()) {
+    if (mHintSessionFuture.has_value()) {
         // If the session is here
-        if (mHintSessionFuture.wait_for(0s) == std::future_status::ready) {
-            mHintSession = mHintSessionFuture.get();
+        if (mHintSessionFuture->wait_for(0s) == std::future_status::ready) {
+            mHintSession = mHintSessionFuture->get();
+            mHintSessionFuture = std::nullopt;
             if (mHintSession != nullptr) {
                 mSessionValid = true;
                 return true;
@@ -107,12 +110,13 @@
     tids.push_back(mUiThreadId);
     tids.push_back(mRenderThreadId);
 
-    // Use a placeholder target value to initialize,
-    // this will always be replaced elsewhere before it gets used
-    int64_t defaultTargetDurationNanos = 16666667;
+    // Use the cached target value if there is one, otherwise use a default. This is to ensure
+    // the cached target and target in PowerHAL are consistent, and that it updates correctly
+    // whenever there is a change.
+    int64_t targetDurationNanos =
+            mLastTargetWorkDuration == 0 ? kDefaultTargetDuration : mLastTargetWorkDuration;
     mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
-        return mBinding->createSession(manager, tids.data(), tids.size(),
-                                       defaultTargetDurationNanos);
+        return mBinding->createSession(manager, tids.data(), tids.size(), targetDurationNanos);
     });
     return false;
 }
@@ -136,6 +140,7 @@
         actualDurationNanos < kSanityCheckUpperBound) {
         mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos);
     }
+    mLastFrameNotification = systemTime();
 }
 
 void HintSessionWrapper::sendLoadResetHint() {
@@ -155,6 +160,27 @@
     mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
 }
 
+bool HintSessionWrapper::alive() {
+    return mHintSession != nullptr;
+}
+
+nsecs_t HintSessionWrapper::getLastUpdate() {
+    return mLastFrameNotification;
+}
+
+// Requires passing in its shared_ptr since it shouldn't own a shared_ptr to itself
+void HintSessionWrapper::delayedDestroy(RenderThread& rt, nsecs_t delay,
+                                        std::shared_ptr<HintSessionWrapper> wrapperPtr) {
+    nsecs_t lastUpdate = wrapperPtr->getLastUpdate();
+    rt.queue().postDelayed(delay, [lastUpdate = lastUpdate, wrapper = wrapperPtr]() mutable {
+        if (wrapper->getLastUpdate() == lastUpdate) {
+            wrapper->destroy();
+        }
+        // Ensure the shared_ptr is killed at the end of the method
+        wrapper = nullptr;
+    });
+}
+
 } /* namespace renderthread */
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index f8b876e..41891cd 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -19,6 +19,7 @@
 #include <android/performance_hint.h>
 
 #include <future>
+#include <optional>
 
 #include "utils/TimeUtils.h"
 
@@ -27,6 +28,8 @@
 
 namespace renderthread {
 
+class RenderThread;
+
 class HintSessionWrapper {
 public:
     friend class HintSessionWrapperTests;
@@ -40,10 +43,15 @@
     void sendLoadIncreaseHint();
     bool init();
     void destroy();
+    bool alive();
+    nsecs_t getLastUpdate();
+    void delayedDestroy(renderthread::RenderThread& rt, nsecs_t delay,
+                        std::shared_ptr<HintSessionWrapper> wrapperPtr);
 
 private:
     APerformanceHintSession* mHintSession = nullptr;
-    std::future<APerformanceHintSession*> mHintSessionFuture;
+    // This needs to work concurrently for testing
+    std::optional<std::shared_future<APerformanceHintSession*>> mHintSessionFuture;
 
     int mResetsSinceLastReport = 0;
     nsecs_t mLastFrameNotification = 0;
@@ -57,6 +65,7 @@
     static constexpr nsecs_t kResetHintTimeout = 100_ms;
     static constexpr int64_t kSanityCheckLowerBound = 100_us;
     static constexpr int64_t kSanityCheckUpperBound = 10_s;
+    static constexpr int64_t kDefaultTargetDuration = 16666667;
 
     // Allows easier stub when testing
     class HintSessionBinding {
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 023c29a..b8c3a4d 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -61,6 +61,7 @@
         // submission occurred. -1 if this time is unknown.
         static constexpr nsecs_t kUnknownTime = -1;
         nsecs_t commandSubmissionTime = kUnknownTime;
+        android::base::unique_fd presentFence;
     };
     virtual DrawResult draw(const Frame& frame, const SkRect& screenDirty, const SkRect& dirty,
                             const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
@@ -69,8 +70,9 @@
                             FrameInfoVisualizer* profiler,
                             const HardwareBufferRenderParams& bufferParams,
                             std::mutex& profilerLock) = 0;
-    virtual bool swapBuffers(const Frame& frame, bool drew, const SkRect& screenDirty,
-                             FrameInfo* currentFrameInfo, bool* requireSwap) = 0;
+    virtual bool swapBuffers(const Frame& frame, IRenderPipeline::DrawResult&,
+                             const SkRect& screenDirty, FrameInfo* currentFrameInfo,
+                             bool* requireSwap) = 0;
     virtual DeferredLayerUpdater* createTextureLayer() = 0;
     [[nodiscard]] virtual android::base::unique_fd flush() = 0;
     virtual void setHardwareBuffer(AHardwareBuffer* hardwareBuffer) = 0;
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index 6df34be..64d38b9 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -150,11 +150,11 @@
     }
 
     AHardwareBuffer_Desc desc = AHardwareBuffer_Desc{
-            .usage = mUsage,
-            .format = mFormat,
             .width = 1,
             .height = 1,
             .layers = 1,
+            .format = mFormat,
+            .usage = mUsage,
             .rfu0 = 0,
             .rfu1 = 0,
     };
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 224c878..eab3605 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -16,7 +16,13 @@
 
 #include "RenderProxy.h"
 
+#include <SkBitmap.h>
+#include <SkImage.h>
+#include <SkPicture.h>
 #include <gui/TraceUtils.h>
+#include <pthread.h>
+#include <ui/GraphicBufferAllocator.h>
+
 #include "DeferredLayerUpdater.h"
 #include "DisplayList.h"
 #include "Properties.h"
@@ -29,12 +35,6 @@
 #include "utils/Macros.h"
 #include "utils/TimeUtils.h"
 
-#include <SkBitmap.h>
-#include <SkImage.h>
-#include <SkPicture.h>
-
-#include <pthread.h>
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
@@ -123,7 +123,7 @@
 }
 
 void RenderProxy::allocateBuffers() {
-    mRenderThread.queue().post([=]() { mContext->allocateBuffers(); });
+    mRenderThread.queue().post([this]() { mContext->allocateBuffers(); });
 }
 
 bool RenderProxy::pause() {
@@ -136,15 +136,16 @@
 
 void RenderProxy::setLightAlpha(uint8_t ambientShadowAlpha, uint8_t spotShadowAlpha) {
     mRenderThread.queue().post(
-            [=]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); });
+            [=, this]() { mContext->setLightAlpha(ambientShadowAlpha, spotShadowAlpha); });
 }
 
 void RenderProxy::setLightGeometry(const Vector3& lightCenter, float lightRadius) {
-    mRenderThread.queue().post([=]() { mContext->setLightGeometry(lightCenter, lightRadius); });
+    mRenderThread.queue().post(
+            [=, this]() { mContext->setLightGeometry(lightCenter, lightRadius); });
 }
 
 void RenderProxy::setOpaque(bool opaque) {
-    mRenderThread.queue().post([=]() { mContext->setOpaque(opaque); });
+    mRenderThread.queue().post([=, this]() { mContext->setOpaque(opaque); });
 }
 
 float RenderProxy::setColorMode(ColorMode mode) {
@@ -152,9 +153,9 @@
     // an async call since we already know the return value
     if (mode == ColorMode::Hdr || mode == ColorMode::Hdr10) {
         return mRenderThread.queue().runSync(
-                [=]() -> float { return mContext->setColorMode(mode); });
+                [=, this]() -> float { return mContext->setColorMode(mode); });
     } else {
-        mRenderThread.queue().post([=]() { mContext->setColorMode(mode); });
+        mRenderThread.queue().post([=, this]() { mContext->setColorMode(mode); });
         return 1.f;
     }
 }
@@ -179,7 +180,7 @@
     // destroyCanvasAndSurface() needs a fence as when it returns the
     // underlying BufferQueue is going to be released from under
     // the render thread.
-    mRenderThread.queue().runSync([=]() { mContext->destroy(); });
+    mRenderThread.queue().runSync([this]() { mContext->destroy(); });
 }
 
 void RenderProxy::destroyFunctor(int functor) {
@@ -300,7 +301,7 @@
 }
 
 void RenderProxy::resetProfileInfo() {
-    mRenderThread.queue().runSync([=]() {
+    mRenderThread.queue().runSync([this]() {
         std::lock_guard lock(mRenderThread.getJankDataMutex());
         mContext->resetFrameStats();
     });
@@ -323,6 +324,11 @@
             }
         });
     }
+    if (!Properties::isolatedProcess) {
+        std::string grallocInfo;
+        GraphicBufferAllocator::getInstance().dump(grallocInfo);
+        dprintf(fd, "%s\n", grallocInfo.c_str());
+    }
 }
 
 void RenderProxy::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
@@ -350,15 +356,15 @@
 }
 
 void RenderProxy::addRenderNode(RenderNode* node, bool placeFront) {
-    mRenderThread.queue().post([=]() { mContext->addRenderNode(node, placeFront); });
+    mRenderThread.queue().post([=, this]() { mContext->addRenderNode(node, placeFront); });
 }
 
 void RenderProxy::removeRenderNode(RenderNode* node) {
-    mRenderThread.queue().post([=]() { mContext->removeRenderNode(node); });
+    mRenderThread.queue().post([=, this]() { mContext->removeRenderNode(node); });
 }
 
 void RenderProxy::drawRenderNode(RenderNode* node) {
-    mRenderThread.queue().runSync([=]() { mContext->prepareAndDraw(node); });
+    mRenderThread.queue().runSync([=, this]() { mContext->prepareAndDraw(node); });
 }
 
 void RenderProxy::setContentDrawBounds(int left, int top, int right, int bottom) {
@@ -412,8 +418,8 @@
     });
 }
 
-void RenderProxy::setForceDark(bool enable) {
-    mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); });
+void RenderProxy::setForceDark(ForceDarkType type) {
+    mRenderThread.queue().post([this, type]() { mContext->setForceDark(type); });
 }
 
 void RenderProxy::copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request) {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 47c1b0c..f2d8e94 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -31,6 +31,7 @@
 #include "DrawFrameTask.h"
 #include "SwapBehavior.h"
 #include "hwui/Bitmap.h"
+#include "utils/ForceDark.h"
 
 class SkBitmap;
 class SkPicture;
@@ -142,7 +143,7 @@
 
     void addFrameMetricsObserver(FrameMetricsObserver* observer);
     void removeFrameMetricsObserver(FrameMetricsObserver* observer);
-    void setForceDark(bool enable);
+    void setForceDark(ForceDarkType type);
 
     static void copySurfaceInto(ANativeWindow* window, std::shared_ptr<CopyRequest>&& request);
     static void prepareToDraw(Bitmap& bitmap);
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 0dea941..623ee4e 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -17,6 +17,7 @@
 #include "RenderThread.h"
 
 #include <GrContextOptions.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
 #include <android-base/properties.h>
 #include <dlfcn.h>
 #include <gl/GrGLInterface.h>
@@ -149,7 +150,7 @@
         ATRACE_FORMAT("queue mFrameCallbackTask to run after %.2fms",
                       toFloatMillis(runAt - SteadyClock::now()).count());
         queue().postAt(toNsecs_t(runAt.time_since_epoch()).count(),
-                       [=]() { dispatchFrameCallbacks(); });
+                       [this]() { dispatchFrameCallbacks(); });
     }
 }
 
@@ -286,7 +287,7 @@
     auto glesVersion = reinterpret_cast<const char*>(glGetString(GL_VERSION));
     auto size = glesVersion ? strlen(glesVersion) : -1;
     cacheManager().configureContext(&options, glesVersion, size);
-    sk_sp<GrDirectContext> grContext(GrDirectContext::MakeGL(std::move(glInterface), options));
+    sk_sp<GrDirectContext> grContext(GrDirectContexts::MakeGL(std::move(glInterface), options));
     LOG_ALWAYS_FATAL_IF(!grContext.get());
     setGrContext(grContext);
 }
@@ -357,7 +358,15 @@
 
     String8 cachesOutput;
     mCacheManager->dumpMemoryUsage(cachesOutput, mRenderState);
-    dprintf(fd, "\nPipeline=%s\n%s\n", pipelineToString(), cachesOutput.c_str());
+    dprintf(fd, "\nPipeline=%s\n%s", pipelineToString(), cachesOutput.c_str());
+    for (auto&& context : mCacheManager->mCanvasContexts) {
+        context->visitAllRenderNodes([&](const RenderNode& node) {
+            if (node.isTextureView()) {
+                dprintf(fd, "TextureView: %dx%d\n", node.getWidth(), node.getHeight());
+            }
+        });
+    }
+    dprintf(fd, "\n");
 }
 
 void RenderThread::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index 46698a6..d55d28d 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -24,6 +24,9 @@
 #include <GrTypes.h>
 #include <android/sync.h>
 #include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
 #include <ui/FatVector.h>
 #include <vk/GrVkExtensions.h>
 #include <vk/GrVkTypes.h>
@@ -33,9 +36,6 @@
 #include "pipeline/skia/ShaderCache.h"
 #include "renderstate/RenderState.h"
 
-#undef LOG_TAG
-#define LOG_TAG "VulkanManager"
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
@@ -386,25 +386,23 @@
 }
 
 void VulkanManager::initialize() {
-    std::lock_guard _lock{mInitializeLock};
+    std::call_once(mInitFlag, [&] {
+        GET_PROC(EnumerateInstanceVersion);
+        uint32_t instanceVersion;
+        LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
+        LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
 
-    if (mDevice != VK_NULL_HANDLE) {
-        return;
-    }
+        this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
 
-    GET_PROC(EnumerateInstanceVersion);
-    uint32_t instanceVersion;
-    LOG_ALWAYS_FATAL_IF(mEnumerateInstanceVersion(&instanceVersion));
-    LOG_ALWAYS_FATAL_IF(instanceVersion < VK_MAKE_VERSION(1, 1, 0));
+        mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
+        mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
 
-    this->setupDevice(mExtensions, mPhysicalDeviceFeatures2);
+        if (Properties::enablePartialUpdates && Properties::useBufferAge) {
+            mSwapBehavior = SwapBehavior::BufferAge;
+        }
 
-    mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
-    mGetDeviceQueue(mDevice, mGraphicsQueueIndex, 1, &mAHBUploadQueue);
-
-    if (Properties::enablePartialUpdates && Properties::useBufferAge) {
-        mSwapBehavior = SwapBehavior::BufferAge;
-    }
+        mInitialized = true;
+    });
 }
 
 static void onGrContextReleased(void* context) {
@@ -438,7 +436,7 @@
     options.fContextDeleteContext = this;
     options.fContextDeleteProc = onGrContextReleased;
 
-    return GrDirectContext::MakeVulkan(backendContext, options);
+    return GrDirectContexts::MakeVulkan(backendContext, options);
 }
 
 VkFunctorInitParams VulkanManager::getVkFunctorInitParams() const {
@@ -518,7 +516,7 @@
                         // The following flush blocks the GPU immediately instead of waiting for
                         // other drawing ops. It seems dequeue_fence is not respected otherwise.
                         // TODO: remove the flush after finding why backendSemaphore is not working.
-                        bufferInfo->skSurface->flushAndSubmit();
+                        skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface.get());
                     }
                 }
             }
@@ -529,128 +527,121 @@
     return Frame(surface->logicalWidth(), surface->logicalHeight(), bufferAge);
 }
 
-struct DestroySemaphoreInfo {
+class SharedSemaphoreInfo : public LightRefBase<SharedSemaphoreInfo> {
     PFN_vkDestroySemaphore mDestroyFunction;
     VkDevice mDevice;
     VkSemaphore mSemaphore;
-    // We need to make sure we don't delete the VkSemaphore until it is done being used by both Skia
-    // (including by the GPU) and inside the VulkanManager. So we always start with two refs, one
-    // owned by Skia and one owned by the VulkanManager. The refs are decremented each time
-    // destroy_semaphore is called with this object. Skia will call destroy_semaphore once it is
-    // done with the semaphore and the GPU has finished work on the semaphore. The VulkanManager
-    // calls destroy_semaphore after sending the semaphore to Skia and exporting it if need be.
-    int mRefs = 2;
+    GrBackendSemaphore mGrBackendSemaphore;
 
-    DestroySemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
-                         VkSemaphore semaphore)
-            : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {}
+    SharedSemaphoreInfo(PFN_vkDestroySemaphore destroyFunction, VkDevice device,
+                        VkSemaphore semaphore)
+            : mDestroyFunction(destroyFunction), mDevice(device), mSemaphore(semaphore) {
+        mGrBackendSemaphore.initVulkan(semaphore);
+    }
+
+    ~SharedSemaphoreInfo() { mDestroyFunction(mDevice, mSemaphore, nullptr); }
+
+    friend class LightRefBase<SharedSemaphoreInfo>;
+    friend class sp<SharedSemaphoreInfo>;
+
+public:
+    VkSemaphore semaphore() const { return mSemaphore; }
+
+    GrBackendSemaphore* grBackendSemaphore() { return &mGrBackendSemaphore; }
 };
 
 static void destroy_semaphore(void* context) {
-    DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(context);
-    --info->mRefs;
-    if (!info->mRefs) {
-        info->mDestroyFunction(info->mDevice, info->mSemaphore, nullptr);
-        delete info;
-    }
+    SharedSemaphoreInfo* info = reinterpret_cast<SharedSemaphoreInfo*>(context);
+    info->decStrong(0);
 }
 
-nsecs_t VulkanManager::finishFrame(SkSurface* surface) {
+VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) {
     ATRACE_NAME("Vulkan finish frame");
-    ALOGE_IF(mSwapSemaphore != VK_NULL_HANDLE || mDestroySemaphoreContext != nullptr,
-             "finishFrame already has an outstanding semaphore");
 
-    VkExportSemaphoreCreateInfo exportInfo;
-    exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
-    exportInfo.pNext = nullptr;
-    exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
-    VkSemaphoreCreateInfo semaphoreInfo;
-    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
-    semaphoreInfo.pNext = &exportInfo;
-    semaphoreInfo.flags = 0;
-    VkSemaphore semaphore;
-    VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
-    ALOGE_IF(VK_SUCCESS != err, "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
-
-    GrBackendSemaphore backendSemaphore;
-    backendSemaphore.initVulkan(semaphore);
-
+    sp<SharedSemaphoreInfo> sharedSemaphore;
     GrFlushInfo flushInfo;
-    if (err == VK_SUCCESS) {
-        mDestroySemaphoreContext = new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
-        flushInfo.fNumSemaphores = 1;
-        flushInfo.fSignalSemaphores = &backendSemaphore;
-        flushInfo.fFinishedProc = destroy_semaphore;
-        flushInfo.fFinishedContext = mDestroySemaphoreContext;
-    } else {
-        semaphore = VK_NULL_HANDLE;
-    }
-    GrSemaphoresSubmitted submitted =
-            surface->flush(SkSurface::BackendSurfaceAccess::kPresent, flushInfo);
-    GrDirectContext* context = GrAsDirectContext(surface->recordingContext());
-    ALOGE_IF(!context, "Surface is not backed by gpu");
-    context->submit();
-    const nsecs_t submissionTime = systemTime();
-    if (semaphore != VK_NULL_HANDLE) {
-        if (submitted == GrSemaphoresSubmitted::kYes) {
-            mSwapSemaphore = semaphore;
-            if (mFrameBoundaryANDROID) {
-                // retrieve VkImage used as render target
-                VkImage image = VK_NULL_HANDLE;
-                GrBackendRenderTarget backendRenderTarget =
-                        surface->getBackendRenderTarget(SkSurface::kFlushRead_BackendHandleAccess);
-                if (backendRenderTarget.isValid()) {
-                    GrVkImageInfo info;
-                    if (backendRenderTarget.getVkImageInfo(&info)) {
-                        image = info.fImage;
-                    } else {
-                        ALOGE("Frame boundary: backend is not vulkan");
-                    }
-                } else {
-                    ALOGE("Frame boundary: invalid backend render target");
-                }
-                // frameBoundaryANDROID needs to know about mSwapSemaphore, but
-                // it won't wait on it.
-                mFrameBoundaryANDROID(mDevice, mSwapSemaphore, image);
-            }
-        } else {
-            destroy_semaphore(mDestroySemaphoreContext);
-            mDestroySemaphoreContext = nullptr;
+
+    {
+        VkExportSemaphoreCreateInfo exportInfo;
+        exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
+        exportInfo.pNext = nullptr;
+        exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+        VkSemaphoreCreateInfo semaphoreInfo;
+        semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+        semaphoreInfo.pNext = &exportInfo;
+        semaphoreInfo.flags = 0;
+        VkSemaphore semaphore;
+        VkResult err = mCreateSemaphore(mDevice, &semaphoreInfo, nullptr, &semaphore);
+        ALOGE_IF(VK_SUCCESS != err,
+                 "VulkanManager::makeSwapSemaphore(): Failed to create semaphore");
+
+        if (err == VK_SUCCESS) {
+            sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore);
+            flushInfo.fNumSemaphores = 1;
+            flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore();
+            flushInfo.fFinishedProc = destroy_semaphore;
+            sharedSemaphore->incStrong(0);
+            flushInfo.fFinishedContext = sharedSemaphore.get();
         }
     }
+
+    GrDirectContext* context = GrAsDirectContext(surface->recordingContext());
+    ALOGE_IF(!context, "Surface is not backed by gpu");
+    GrSemaphoresSubmitted submitted = context->flush(
+            surface, SkSurfaces::BackendSurfaceAccess::kPresent, flushInfo);
+    context->submit();
+    VkDrawResult drawResult{
+            .submissionTime = systemTime(),
+    };
+    if (sharedSemaphore) {
+        if (submitted == GrSemaphoresSubmitted::kYes && mFrameBoundaryANDROID) {
+            // retrieve VkImage used as render target
+            VkImage image = VK_NULL_HANDLE;
+            GrBackendRenderTarget backendRenderTarget = SkSurfaces::GetBackendRenderTarget(
+                    surface, SkSurfaces::BackendHandleAccess::kFlushRead);
+            if (backendRenderTarget.isValid()) {
+                GrVkImageInfo info;
+                if (GrBackendRenderTargets::GetVkImageInfo(backendRenderTarget, &info)) {
+                    image = info.fImage;
+                } else {
+                    ALOGE("Frame boundary: backend is not vulkan");
+                }
+            } else {
+                ALOGE("Frame boundary: invalid backend render target");
+            }
+            // frameBoundaryANDROID needs to know about mSwapSemaphore, but
+            // it won't wait on it.
+            mFrameBoundaryANDROID(mDevice, sharedSemaphore->semaphore(), image);
+        }
+        VkSemaphoreGetFdInfoKHR getFdInfo;
+        getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
+        getFdInfo.pNext = nullptr;
+        getFdInfo.semaphore = sharedSemaphore->semaphore();
+        getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
+
+        int fenceFd = -1;
+        VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
+        ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
+        drawResult.presentFence.reset(fenceFd);
+    } else {
+        ALOGE("VulkanManager::finishFrame(): Semaphore submission failed");
+        mQueueWaitIdle(mGraphicsQueue);
+    }
+
     skiapipeline::ShaderCache::get().onVkFrameFlushed(context);
 
-    return submissionTime;
+    return drawResult;
 }
 
-void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect) {
+void VulkanManager::swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect,
+                                android::base::unique_fd&& presentFence) {
     if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
         ATRACE_NAME("Finishing GPU work");
         mDeviceWaitIdle(mDevice);
     }
 
-    int fenceFd = -1;
-    if (mSwapSemaphore != VK_NULL_HANDLE) {
-        VkSemaphoreGetFdInfoKHR getFdInfo;
-        getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
-        getFdInfo.pNext = nullptr;
-        getFdInfo.semaphore = mSwapSemaphore;
-        getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
-
-        VkResult err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
-        ALOGE_IF(VK_SUCCESS != err, "VulkanManager::swapBuffers(): Failed to get semaphore Fd");
-    } else {
-        ALOGE("VulkanManager::swapBuffers(): Semaphore submission failed");
-        mQueueWaitIdle(mGraphicsQueue);
-    }
-    if (mDestroySemaphoreContext) {
-        destroy_semaphore(mDestroySemaphoreContext);
-    }
-
-    surface->presentCurrentBuffer(dirtyRect, fenceFd);
-    mSwapSemaphore = VK_NULL_HANDLE;
-    mDestroySemaphoreContext = nullptr;
+    surface->presentCurrentBuffer(dirtyRect, presentFence.release());
 }
 
 void VulkanManager::destroySurface(VulkanSurface* surface) {
@@ -751,25 +742,20 @@
         return INVALID_OPERATION;
     }
 
-    GrBackendSemaphore backendSemaphore;
-    backendSemaphore.initVulkan(semaphore);
+    auto sharedSemaphore = sp<SharedSemaphoreInfo>::make(mDestroySemaphore, mDevice, semaphore);
 
-    DestroySemaphoreInfo* destroyInfo =
-            new DestroySemaphoreInfo(mDestroySemaphore, mDevice, semaphore);
     // Even if Skia fails to submit the semaphore, it will still call the destroy_semaphore callback
-    // which will remove its ref to the semaphore. The VulkanManager must still release its ref,
-    // when it is done with the semaphore.
     GrFlushInfo flushInfo;
     flushInfo.fNumSemaphores = 1;
-    flushInfo.fSignalSemaphores = &backendSemaphore;
+    flushInfo.fSignalSemaphores = sharedSemaphore->grBackendSemaphore();
     flushInfo.fFinishedProc = destroy_semaphore;
-    flushInfo.fFinishedContext = destroyInfo;
+    sharedSemaphore->incStrong(0);
+    flushInfo.fFinishedContext = sharedSemaphore.get();
     GrSemaphoresSubmitted submitted = grContext->flush(flushInfo);
     grContext->submit();
 
     if (submitted == GrSemaphoresSubmitted::kNo) {
         ALOGE("VulkanManager::createReleaseFence: Failed to submit semaphore");
-        destroy_semaphore(destroyInfo);
         return INVALID_OPERATION;
     }
 
@@ -782,7 +768,6 @@
     int fenceFd = 0;
 
     err = mGetSemaphoreFdKHR(mDevice, &getFdInfo, &fenceFd);
-    destroy_semaphore(destroyInfo);
     if (VK_SUCCESS != err) {
         ALOGE("VulkanManager::createReleaseFence: Failed to get semaphore Fd");
         return INVALID_OPERATION;
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index 2be1ffd..b92ebb3 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -22,6 +22,7 @@
 #endif
 #include <GrContextOptions.h>
 #include <SkSurface.h>
+#include <android-base/unique_fd.h>
 #include <utils/StrongPointer.h>
 #include <vk/GrVkBackendContext.h>
 #include <vk/GrVkExtensions.h>
@@ -70,7 +71,7 @@
     void initialize();
 
     // Quick check to see if the VulkanManager has been initialized.
-    bool hasVkContext() { return mDevice != VK_NULL_HANDLE; }
+    bool hasVkContext() { return mInitialized; }
 
     // Create and destroy functions for wrapping an ANativeWindow in a VulkanSurface
     VulkanSurface* createSurface(ANativeWindow* window,
@@ -82,10 +83,17 @@
     void destroySurface(VulkanSurface* surface);
 
     Frame dequeueNextBuffer(VulkanSurface* surface);
+
+    struct VkDrawResult {
+        // The estimated start time for intiating GPU work, -1 if unknown.
+        nsecs_t submissionTime;
+        android::base::unique_fd presentFence;
+    };
+
     // Finishes the frame and submits work to the GPU
-    // Returns the estimated start time for intiating GPU work, -1 otherwise.
-    nsecs_t finishFrame(SkSurface* surface);
-    void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect);
+    VkDrawResult finishFrame(SkSurface* surface);
+    void swapBuffers(VulkanSurface* surface, const SkRect& dirtyRect,
+                     android::base::unique_fd&& presentFence);
 
     // Inserts a wait on fence command into the Vulkan command buffer.
     status_t fenceWait(int fence, GrDirectContext* grContext);
@@ -201,10 +209,8 @@
     GrVkExtensions mExtensions;
     uint32_t mDriverVersion = 0;
 
-    VkSemaphore mSwapSemaphore = VK_NULL_HANDLE;
-    void* mDestroySemaphoreContext = nullptr;
-
-    std::mutex mInitializeLock;
+    std::once_flag mInitFlag;
+    std::atomic_bool mInitialized = false;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index 3168cb0..20b743b 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -16,6 +16,7 @@
 
 #include "VulkanSurface.h"
 
+#include <include/android/SkSurfaceAndroid.h>
 #include <GrDirectContext.h>
 #include <SkSurface.h>
 #include <algorithm>
@@ -24,9 +25,6 @@
 #include "VulkanManager.h"
 #include "utils/Color.h"
 
-#undef LOG_TAG
-#define LOG_TAG "VulkanSurface"
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
@@ -470,12 +468,12 @@
             surfaceProps = SkSurfaceProps(SkSurfaceProps::kAlwaysDither_Flag | surfaceProps.flags(),
                                           surfaceProps.pixelGeometry());
         }
-        bufferInfo->skSurface = SkSurface::MakeFromAHardwareBuffer(
+        bufferInfo->skSurface = SkSurfaces::WrapAndroidHardwareBuffer(
                 mGrContext, ANativeWindowBuffer_getHardwareBuffer(bufferInfo->buffer.get()),
                 kTopLeft_GrSurfaceOrigin, mWindowInfo.colorspace, &surfaceProps,
                 /*from_window=*/true);
         if (bufferInfo->skSurface.get() == nullptr) {
-            ALOGE("SkSurface::MakeFromAHardwareBuffer failed");
+            ALOGE("SkSurfaces::WrapAndroidHardwareBuffer failed");
             mNativeWindow->cancelBuffer(mNativeWindow.get(), buffer,
                                         mNativeBuffers[idx].dequeue_fence.release());
             mNativeBuffers[idx].dequeued = false;
diff --git a/libs/hwui/tests/common/TestUtils.cpp b/libs/hwui/tests/common/TestUtils.cpp
index a4890ed..ad963dd 100644
--- a/libs/hwui/tests/common/TestUtils.cpp
+++ b/libs/hwui/tests/common/TestUtils.cpp
@@ -19,6 +19,8 @@
 #include "DeferredLayerUpdater.h"
 #include "hwui/Paint.h"
 
+#include <hwui/MinikinSkia.h>
+#include <hwui/Typeface.h>
 #include <minikin/Layout.h>
 #include <pipeline/skia/SkiaOpenGLPipeline.h>
 #include <pipeline/skia/SkiaVulkanPipeline.h>
@@ -179,5 +181,13 @@
     return outlineInLocalCoord;
 }
 
+SkFont TestUtils::defaultFont() {
+    const std::shared_ptr<minikin::MinikinFont>& minikinFont =
+      Typeface::resolveDefault(nullptr)->fFontCollection->getFamilyAt(0)->getFont(0)->baseTypeface();
+    SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(minikinFont.get())->GetSkTypeface();
+    LOG_ALWAYS_FATAL_IF(skTypeface == nullptr);
+    return SkFont(sk_ref_sp(skTypeface));
+}
+
 } /* namespace uirenderer */
 } /* namespace android */
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 81ecfe5..0ede902 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -30,6 +30,7 @@
 
 #include <SkBitmap.h>
 #include <SkColor.h>
+#include <SkFont.h>
 #include <SkImageInfo.h>
 #include <SkRefCnt.h>
 
@@ -61,18 +62,10 @@
         ADD_FAILURE() << "ClipState not a rect";                                     \
     }
 
-#define INNER_PIPELINE_TEST(test_case_name, test_name, pipeline, functionCall) \
-    TEST(test_case_name, test_name##_##pipeline) {                             \
-        RenderPipelineType oldType = Properties::getRenderPipelineType();      \
-        Properties::overrideRenderPipelineType(RenderPipelineType::pipeline);  \
-        functionCall;                                                          \
-        Properties::overrideRenderPipelineType(oldType);                       \
-    };
-
-#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, pipeline) \
-    INNER_PIPELINE_TEST(test_case_name, test_name, pipeline,                  \
-                        TestUtils::runOnRenderThread(                         \
-                                test_case_name##_##test_name##_RenderThreadTest::doTheThing))
+#define INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name)                                \
+    TEST(test_case_name, test_name) {                                                              \
+        TestUtils::runOnRenderThread(test_case_name##_##test_name##_RenderThreadTest::doTheThing); \
+    }
 
 /**
  * Like gtest's TEST, but runs on the RenderThread, and 'renderThread' is passed, in top level scope
@@ -83,21 +76,7 @@
     public:                                                                                 \
         static void doTheThing(renderthread::RenderThread& renderThread);                   \
     };                                                                                      \
-    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL);                    \
-    /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
-    /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */          \
-    void test_case_name##_##test_name##_RenderThreadTest::doTheThing(                       \
-            renderthread::RenderThread& renderThread)
-
-/**
- * Like RENDERTHREAD_TEST, but only runs with the Skia RenderPipelineTypes
- */
-#define RENDERTHREAD_SKIA_PIPELINE_TEST(test_case_name, test_name)                          \
-    class test_case_name##_##test_name##_RenderThreadTest {                                 \
-    public:                                                                                 \
-        static void doTheThing(renderthread::RenderThread& renderThread);                   \
-    };                                                                                      \
-    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaGL);                    \
+    INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name);                            \
     /* Temporarily disabling Vulkan until we can figure out a way to stub out the driver */ \
     /* INNER_PIPELINE_RENDERTHREAD_TEST(test_case_name, test_name, SkiaVulkan); */          \
     void test_case_name##_##test_name##_RenderThreadTest::doTheThing(                       \
@@ -307,13 +286,21 @@
         int destroyed = 0;
         int removeOverlays = 0;
         int glesDraw = 0;
+        int vkInitialize = 0;
+        int vkDraw = 0;
+        int vkPostDraw = 0;
     };
 
     static void expectOnRenderThread(const std::string_view& function = "unknown") {
         EXPECT_EQ(gettid(), TestUtils::getRenderThreadTid()) << "Called on wrong thread: " << function;
     }
 
-    static WebViewFunctorCallbacks createMockFunctor(RenderMode mode) {
+    static int createMockFunctor() {
+        const auto renderMode = WebViewFunctor_queryPlatformRenderMode();
+        return WebViewFunctor_create(nullptr, createMockFunctorCallbacks(renderMode), renderMode);
+    }
+
+    static WebViewFunctorCallbacks createMockFunctorCallbacks(RenderMode mode) {
         auto callbacks = WebViewFunctorCallbacks{
                 .onSync =
                         [](int functor, void* client_data, const WebViewSyncData& data) {
@@ -345,15 +332,30 @@
                     sMockFunctorCounts[functor].glesDraw++;
                 };
                 break;
-            default:
-                ADD_FAILURE();
-                return WebViewFunctorCallbacks{};
+            case RenderMode::Vulkan:
+                callbacks.vk.initialize = [](int functor, void* data,
+                                             const VkFunctorInitParams& params) {
+                    expectOnRenderThread("initialize");
+                    sMockFunctorCounts[functor].vkInitialize++;
+                };
+                callbacks.vk.draw = [](int functor, void* data, const VkFunctorDrawParams& params,
+                                       const WebViewOverlayData& overlayParams) {
+                    expectOnRenderThread("draw");
+                    sMockFunctorCounts[functor].vkDraw++;
+                };
+                callbacks.vk.postDraw = [](int functor, void* data) {
+                    expectOnRenderThread("postDraw");
+                    sMockFunctorCounts[functor].vkPostDraw++;
+                };
+                break;
         }
         return callbacks;
     }
 
     static CallCounts& countsForFunctor(int functor) { return sMockFunctorCounts[functor]; }
 
+    static SkFont defaultFont();
+
 private:
     static std::unordered_map<int, CallCounts> sMockFunctorCounts;
 
diff --git a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
index 4a5d946..97d4c82 100644
--- a/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/ListViewAnimation.cpp
@@ -57,7 +57,7 @@
                 128 * 3;
         paint.setColor(bgDark ? Color::White : Color::Grey_700);
 
-        SkFont font;
+        SkFont font = TestUtils::defaultFont();
         font.setSize(size / 2);
         char charToShow = 'A' + (rand() % 26);
         const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
index bb95490..159541c 100644
--- a/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
+++ b/libs/hwui/tests/common/scenes/StretchyListViewAnimation.cpp
@@ -102,7 +102,7 @@
                 128 * 3;
         paint.setColor(bgDark ? Color::White : Color::Grey_700);
 
-        SkFont font;
+        SkFont font = TestUtils::defaultFont();
         font.setSize(size / 2);
         char charToShow = 'A' + (rand() % 26);
         const SkPoint pos = {SkIntToScalar(size / 2),
diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp
index f3f32eb..e227999 100644
--- a/libs/hwui/tests/macrobench/main.cpp
+++ b/libs/hwui/tests/macrobench/main.cpp
@@ -14,30 +14,32 @@
  * limitations under the License.
  */
 
-#include "tests/common/LeakChecker.h"
-#include "tests/common/TestScene.h"
-
-#include "Properties.h"
-#include "hwui/Typeface.h"
-#include "HardwareBitmapUploader.h"
-#include "renderthread/RenderProxy.h"
-
+#include <android-base/parsebool.h>
 #include <benchmark/benchmark.h>
+#include <errno.h>
+#include <fcntl.h>
 #include <fnmatch.h>
 #include <getopt.h>
 #include <pthread.h>
 #include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 #include <unistd.h>
+
+#include <regex>
 #include <string>
 #include <unordered_map>
 #include <vector>
 
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
+#include "HardwareBitmapUploader.h"
+#include "Properties.h"
+#include "hwui/Typeface.h"
+#include "renderthread/RenderProxy.h"
+#include "tests/common/LeakChecker.h"
+#include "tests/common/TestScene.h"
 
 using namespace android;
+using namespace android::base;
 using namespace android::uirenderer;
 using namespace android::uirenderer::test;
 
@@ -69,6 +71,9 @@
   --onscreen           Render tests on device screen. By default tests
                        are offscreen rendered
   --benchmark_format   Set output format. Possible values are tabular, json, csv
+  --benchmark_list_tests Lists the tests that would run but does not run them
+  --benchmark_filter=<regex> Filters the test set to the given regex. If prefixed with `-` and test
+                       that doesn't match the given regex is run
   --renderer=TYPE      Sets the render pipeline to use. May be skiagl or skiavk
   --skip-leak-check    Skips the memory leak check
   --report-gpu-memory[=verbose]  Dumps the GPU memory usage after each test run
@@ -140,6 +145,9 @@
     if (!strcmp(format, "tabular")) {
         gBenchmarkReporter.reset(new benchmark::ConsoleReporter());
     } else if (!strcmp(format, "json")) {
+        // We cannot print the leak check if outputing to JSON as that will break
+        // JSON parsers since it's not JSON-formatted
+        gRunLeakCheck = false;
         gBenchmarkReporter.reset(new benchmark::JSONReporter());
     } else {
         fprintf(stderr, "Unknown format '%s'\n", format);
@@ -160,6 +168,24 @@
     return true;
 }
 
+static void addTestsThatMatchFilter(std::string spec) {
+    if (spec.empty() || spec == "all") {
+        spec = ".";  // Regexp that matches all benchmarks
+    }
+    bool isNegativeFilter = false;
+    if (spec[0] == '-') {
+        spec.replace(0, 1, "");
+        isNegativeFilter = true;
+    }
+    std::regex re(spec, std::regex_constants::extended);
+    for (auto& iter : TestScene::testMap()) {
+        if ((isNegativeFilter && !std::regex_search(iter.first, re)) ||
+            (!isNegativeFilter && std::regex_search(iter.first, re))) {
+            gRunTests.push_back(iter.second);
+        }
+    }
+}
+
 // For options that only exist in long-form. Anything in the
 // 0-255 range is reserved for short options (which just use their ASCII value)
 namespace LongOpts {
@@ -170,6 +196,8 @@
     ReportFrametime,
     CpuSet,
     BenchmarkFormat,
+    BenchmarkListTests,
+    BenchmarkFilter,
     Onscreen,
     Offscreen,
     Renderer,
@@ -179,14 +207,16 @@
 }
 
 static const struct option LONG_OPTIONS[] = {
-        {"frames", required_argument, nullptr, 'f'},
-        {"repeat", required_argument, nullptr, 'r'},
+        {"count", required_argument, nullptr, 'c'},
+        {"runs", required_argument, nullptr, 'r'},
         {"help", no_argument, nullptr, 'h'},
         {"list", no_argument, nullptr, LongOpts::List},
         {"wait-for-gpu", no_argument, nullptr, LongOpts::WaitForGpu},
         {"report-frametime", optional_argument, nullptr, LongOpts::ReportFrametime},
         {"cpuset", required_argument, nullptr, LongOpts::CpuSet},
         {"benchmark_format", required_argument, nullptr, LongOpts::BenchmarkFormat},
+        {"benchmark_list_tests", optional_argument, nullptr, LongOpts::BenchmarkListTests},
+        {"benchmark_filter", required_argument, nullptr, LongOpts::BenchmarkFilter},
         {"onscreen", no_argument, nullptr, LongOpts::Onscreen},
         {"offscreen", no_argument, nullptr, LongOpts::Offscreen},
         {"renderer", required_argument, nullptr, LongOpts::Renderer},
@@ -197,8 +227,12 @@
 static const char* SHORT_OPTIONS = "c:r:h";
 
 void parseOptions(int argc, char* argv[]) {
+    benchmark::BenchmarkReporter::Context::executable_name = (argc > 0) ? argv[0] : "unknown";
+
     int c;
     bool error = false;
+    bool listTestsOnly = false;
+    bool testsAreFiltered = false;
     opterr = 0;
 
     while (true) {
@@ -272,6 +306,21 @@
                 }
                 break;
 
+            case LongOpts::BenchmarkListTests:
+                if (!optarg || ParseBool(optarg) == ParseBoolResult::kTrue) {
+                    listTestsOnly = true;
+                }
+                break;
+
+            case LongOpts::BenchmarkFilter:
+                if (!optarg) {
+                    error = true;
+                    break;
+                }
+                addTestsThatMatchFilter(optarg);
+                testsAreFiltered = true;
+                break;
+
             case LongOpts::Renderer:
                 if (!optarg) {
                     error = true;
@@ -346,11 +395,18 @@
                 }
             }
         } while (optind < argc);
-    } else {
+    } else if (gRunTests.empty() && !testsAreFiltered) {
         for (auto& iter : TestScene::testMap()) {
             gRunTests.push_back(iter.second);
         }
     }
+
+    if (listTestsOnly) {
+        for (auto& iter : gRunTests) {
+            std::cout << iter.name << std::endl;
+        }
+        exit(EXIT_SUCCESS);
+    }
 }
 
 int main(int argc, char* argv[]) {
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
index 138b3efd..b8b3f0a 100644
--- a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -46,7 +46,7 @@
 
     EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
 
-    // SkImage::MakeFromTexture should fail if given null GrDirectContext.
+    // SkImages::BorrowTextureFrom should fail if given null GrDirectContext.
     textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr);
 
     EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index 2b90bda..89d00d3 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -20,7 +20,8 @@
 #include "renderthread/EglManager.h"
 #include "tests/common/TestUtils.h"
 
-#include <SkImagePriv.h>
+#include <SkImageAndroid.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include "include/gpu/GpuTypes.h" // from Skia
 
 using namespace android;
@@ -34,7 +35,7 @@
 }
 
 // TOOD(258700630): fix this test and re-enable
-RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, DISABLED_trimMemory) {
+RENDERTHREAD_TEST(CacheManager, DISABLED_trimMemory) {
     int32_t width = DeviceInfo::get()->getWidth();
     int32_t height = DeviceInfo::get()->getHeight();
     GrDirectContext* grContext = renderThread.getGrContext();
@@ -46,8 +47,8 @@
 
     while (getCacheUsage(grContext) <= renderThread.cacheManager().getBackgroundCacheSize()) {
         SkImageInfo info = SkImageInfo::MakeA8(width, height);
-        sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(grContext, skgpu::Budgeted::kYes,
-                                                               info);
+        sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(grContext, skgpu::Budgeted::kYes,
+                                                            info);
         surface->getCanvas()->drawColor(SK_AlphaTRANSPARENT);
 
         grContext->flushAndSubmit();
@@ -57,9 +58,8 @@
 
     // create an image and pin it so that we have something with a unique key in the cache
     sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::MakeA8(width, height));
-    sk_sp<SkImage> image = bitmap->makeImage();
-    ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext));
-
+    sk_sp<SkImage> image = bitmap->makeImage(); // calls skgpu::ganesh::PinAsTexture under the hood.
+    ASSERT_TRUE(skgpu::ganesh::PinAsTexture(grContext, image.get()));
     // attempt to trim all memory while we still hold strong refs
     renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
     ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
@@ -71,7 +71,7 @@
     }
 
     // unpin the image which should add a unique purgeable key to the cache
-    SkImage_unpinAsTexture(image.get(), grContext);
+    skgpu::ganesh::UnpinTexture(grContext, image.get());
 
     // verify that we have enough purgeable bytes
     const size_t purgeableBytes = grContext->getResourceCachePurgeableBytes();
diff --git a/libs/hwui/tests/unit/CanvasContextTests.cpp b/libs/hwui/tests/unit/CanvasContextTests.cpp
index 9e376e3..47a4105 100644
--- a/libs/hwui/tests/unit/CanvasContextTests.cpp
+++ b/libs/hwui/tests/unit/CanvasContextTests.cpp
@@ -19,6 +19,7 @@
 #include "AnimationContext.h"
 #include "IContextFactory.h"
 #include "renderthread/CanvasContext.h"
+#include "renderthread/VulkanManager.h"
 #include "tests/common/TestUtils.h"
 
 using namespace android;
@@ -42,3 +43,38 @@
 
     canvasContext->destroy();
 }
+
+RENDERTHREAD_TEST(CanvasContext, buildLayerDoesntLeak) {
+    auto node = TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+        canvas.drawColor(0xFFFF0000, SkBlendMode::kSrc);
+    });
+    ASSERT_TRUE(node->isValid());
+    EXPECT_EQ(LayerType::None, node->stagingProperties().effectiveLayerType());
+    node->mutateStagingProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+
+    auto& cacheManager = renderThread.cacheManager();
+    EXPECT_TRUE(cacheManager.areAllContextsStopped());
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(
+            CanvasContext::create(renderThread, false, node.get(), &contextFactory, 0, 0));
+    canvasContext->buildLayer(node.get());
+    EXPECT_TRUE(node->hasLayer());
+    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+        auto instance = VulkanManager::peekInstance();
+        if (instance) {
+            EXPECT_TRUE(instance->hasVkContext());
+        } else {
+            ADD_FAILURE() << "VulkanManager wasn't initialized to buildLayer?";
+        }
+    }
+    renderThread.destroyRenderingContext();
+    EXPECT_FALSE(node->hasLayer()) << "Node still has a layer after rendering context destroyed";
+
+    if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {
+        auto instance = VulkanManager::peekInstance();
+        if (instance) {
+            ADD_FAILURE() << "VulkanManager still exists";
+            EXPECT_FALSE(instance->hasVkContext());
+        }
+    }
+}
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index 1f6edf3..18c5047 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -225,9 +225,9 @@
 TEST(CanvasOp, simpleDrawRect) {
     CanvasOpBuffer buffer;
     EXPECT_EQ(buffer.size(), 0);
-    buffer.push<Op::DrawRect> ({
-        .paint = SkPaint{},
-        .rect = SkRect::MakeEmpty()
+    buffer.push<Op::DrawRect>({
+            .rect = SkRect::MakeEmpty(),
+            .paint = SkPaint{},
     });
 
     CallCountingCanvas canvas;
@@ -242,9 +242,9 @@
     EXPECT_EQ(buffer.size(), 0);
     SkRegion region;
     region.setRect(SkIRect::MakeWH(12, 50));
-    buffer.push<Op::DrawRegion> ({
-        .paint = SkPaint{},
-        .region = region
+    buffer.push<Op::DrawRegion>({
+            .region = region,
+            .paint = SkPaint{},
     });
 
     CallCountingCanvas canvas;
@@ -264,9 +264,9 @@
     clip.setRect(SkIRect::MakeWH(100, 100));
     SkRegion region;
     region.setPath(path, clip);
-    buffer.push<Op::DrawRegion> ({
-        .paint = SkPaint{},
-        .region = region
+    buffer.push<Op::DrawRegion>({
+            .region = region,
+            .paint = SkPaint{},
     });
 
     CallCountingCanvas canvas;
@@ -279,11 +279,11 @@
 TEST(CanvasOp, simpleDrawRoundRect) {
     CanvasOpBuffer buffer;
     EXPECT_EQ(buffer.size(), 0);
-    buffer.push<Op::DrawRoundRect> ({
-        .paint = SkPaint{},
-        .rect = SkRect::MakeEmpty(),
-        .rx = 10,
-        .ry = 10
+    buffer.push<Op::DrawRoundRect>({
+            .rect = SkRect::MakeEmpty(),
+            .rx = 10,
+            .ry = 10,
+            .paint = SkPaint{},
     });
 
     CallCountingCanvas canvas;
@@ -611,9 +611,9 @@
 
     EXPECT_EQ(0, canvas->sumTotalDrawCalls());
     ImmediateModeRasterizer rasterizer{canvas};
-    auto op = CanvasOp<Op::DrawRect> {
-        .paint = SkPaint{},
-        .rect = SkRect::MakeEmpty()
+    auto op = CanvasOp<Op::DrawRect>{
+            .rect = SkRect::MakeEmpty(),
+            .paint = SkPaint{},
     };
     EXPECT_TRUE(CanvasOpTraits::can_draw<decltype(op)>);
     rasterizer.draw(op);
diff --git a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
index 0c389bfe8..cfa18ae 100644
--- a/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
+++ b/libs/hwui/tests/unit/DeferredLayerUpdaterTests.cpp
@@ -40,7 +40,7 @@
     // push the deferred updates to the layer
     SkBitmap bitmap;
     bitmap.allocN32Pixels(16, 16);
-    sk_sp<SkImage> layerImage = SkImage::MakeFromBitmap(bitmap);
+    sk_sp<SkImage> layerImage = SkImages::RasterFromBitmap(bitmap);
     layerUpdater->updateLayer(true, layerImage, 0, SkRect::MakeEmpty());
 
     // the backing layer should now have all the properties applied.
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
index 623be1e..10a740a1 100644
--- a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -23,9 +23,11 @@
 #include <chrono>
 
 #include "Properties.h"
+#include "tests/common/TestUtils.h"
 
 using namespace testing;
 using namespace std::chrono_literals;
+using namespace android::uirenderer::renderthread;
 
 APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123);
 APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456);
@@ -42,6 +44,9 @@
 protected:
     std::shared_ptr<HintSessionWrapper> mWrapper;
 
+    std::promise<int> blockDestroyCallUntil;
+    std::promise<int> waitForDestroyFinished;
+
     class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding {
     public:
         void init() override;
@@ -53,11 +58,17 @@
         MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
         MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
         MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t));
+        // Needs to be on the binding so it can be accessed from static methods
+        std::promise<int> allowCreationToFinish;
     };
 
     // Must be static so it can have function pointers we can point to with static methods
     static std::shared_ptr<MockHintSessionBinding> sMockBinding;
 
+    static void allowCreationToFinish() { sMockBinding->allowCreationToFinish.set_value(1); }
+    void allowDelayedDestructionToStart() { blockDestroyCallUntil.set_value(1); }
+    void waitForDelayedDestructionToFinish() { waitForDestroyFinished.get_future().wait(); }
+
     // Must be static so we can point to them as normal fn pointers with HintSessionBinding
     static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); };
     static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager,
@@ -65,6 +76,12 @@
                                                       int64_t initialTarget) {
         return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
     }
+    static APerformanceHintSession* stubManagedCreateSession(APerformanceHintManager* manager,
+                                                             const int32_t* ids, size_t idsSize,
+                                                             int64_t initialTarget) {
+        sMockBinding->allowCreationToFinish.get_future().wait();
+        return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+    }
     static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager,
                                                           const int32_t* ids, size_t idsSize,
                                                           int64_t initialTarget) {
@@ -85,7 +102,21 @@
     static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
         sMockBinding->fakeSendHint(session, hintId);
     };
-    void waitForWrapperReady() { mWrapper->mHintSessionFuture.wait(); }
+    void waitForWrapperReady() {
+        if (mWrapper->mHintSessionFuture.has_value()) {
+            mWrapper->mHintSessionFuture->wait();
+        }
+    }
+    void scheduleDelayedDestroyManaged() {
+        TestUtils::runOnRenderThread([&](renderthread::RenderThread& rt) {
+            // Guaranteed to be scheduled first, allows destruction to start
+            rt.queue().postDelayed(0_ms, [&] { blockDestroyCallUntil.get_future().wait(); });
+            // Guaranteed to be scheduled second, destroys the session
+            mWrapper->delayedDestroy(rt, 1_ms, mWrapper);
+            // This is guaranteed to be queued after the destroy, signals that destruction is done
+            rt.queue().postDelayed(1_ms, [&] { waitForDestroyFinished.set_value(1); });
+        });
+    }
 };
 
 std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding>
@@ -113,6 +144,7 @@
 }
 
 void HintSessionWrapperTests::TearDown() {
+    // Ensure that anything running on RT is completely finished
     mWrapper = nullptr;
     sMockBinding = nullptr;
 }
@@ -122,6 +154,7 @@
     sMockBinding->createSession = stubSlowCreateSession;
     mWrapper->init();
     mWrapper = nullptr;
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
 }
 
 TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) {
@@ -148,4 +181,162 @@
     mWrapper->sendLoadResetHint();
 }
 
+TEST_F(HintSessionWrapperTests, delayedDeletionWorksCorrectlyAndOnlyClosesOnce) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to ensure the wrapper grabs the promise value
+    mWrapper->init();
+
+    EXPECT_EQ(mWrapper->alive(), true);
+
+    // Schedule delayed destruction, allow it to run, and check when it's done
+    scheduleDelayedDestroyManaged();
+    allowDelayedDestructionToStart();
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it closed within the timeframe of the test
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+    // If we then delete the wrapper, it shouldn't close the session again
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(_)).Times(0);
+    mWrapper = nullptr;
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionResolvesBeforeAsyncCreationFinishes) {
+    // Here we test whether queueing delayedDestroy works while creation is still happening, if
+    // creation happens after
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+    sMockBinding->createSession = &stubManagedCreateSession;
+
+    // Start creating the session and destroying it at the same time
+    mWrapper->init();
+    scheduleDelayedDestroyManaged();
+
+    // Allow destruction to happen first
+    allowDelayedDestructionToStart();
+
+    // Make sure destruction has had time to happen
+    std::this_thread::sleep_for(50ms);
+
+    // Then, allow creation to finish after delayed destroy runs
+    allowCreationToFinish();
+
+    // Wait for destruction to finish
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it closed within the timeframe of the test
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionResolvesAfterAsyncCreationFinishes) {
+    // Here we test whether queueing delayedDestroy works while creation is still happening, if
+    // creation happens before
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+    sMockBinding->createSession = &stubManagedCreateSession;
+
+    // Start creating the session and destroying it at the same time
+    mWrapper->init();
+    scheduleDelayedDestroyManaged();
+
+    // Allow creation to happen first
+    allowCreationToFinish();
+
+    // Make sure creation has had time to happen
+    waitForWrapperReady();
+
+    // Then allow destruction to happen after creation is done
+    allowDelayedDestructionToStart();
+
+    // Wait for it to finish
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it closed within the timeframe of the test
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, delayedDeletionDoesNotKillReusedSession) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+    EXPECT_CALL(*sMockBinding, fakeReportActualWorkDuration(sessionPtr, 5_ms)).Times(1);
+
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to grab the wrapper from the promise
+    mWrapper->init();
+    EXPECT_EQ(mWrapper->alive(), true);
+
+    // First schedule the deletion
+    scheduleDelayedDestroyManaged();
+
+    // Then, report an actual duration
+    mWrapper->reportActualWorkDuration(5_ms);
+
+    // Then, run the delayed deletion after sending the update
+    allowDelayedDestructionToStart();
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it didn't close within the timeframe of the test
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), true);
+}
+
+TEST_F(HintSessionWrapperTests, loadUpDoesNotResetDeletionTimer) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+    EXPECT_CALL(*sMockBinding,
+                fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
+            .Times(1);
+
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to grab the wrapper from the promise
+    mWrapper->init();
+    EXPECT_EQ(mWrapper->alive(), true);
+
+    // First schedule the deletion
+    scheduleDelayedDestroyManaged();
+
+    // Then, send a load_up hint
+    mWrapper->sendLoadIncreaseHint();
+
+    // Then, run the delayed deletion after sending the update
+    allowDelayedDestructionToStart();
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it closed within the timeframe of the test
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+}
+
+TEST_F(HintSessionWrapperTests, manualSessionDestroyPlaysNiceWithDelayedDestruct) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+
+    mWrapper->init();
+    waitForWrapperReady();
+    // Init a second time just to grab the wrapper from the promise
+    mWrapper->init();
+    EXPECT_EQ(mWrapper->alive(), true);
+
+    // First schedule the deletion
+    scheduleDelayedDestroyManaged();
+
+    // Then, kill the session
+    mWrapper->destroy();
+
+    // Verify it died
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(0);
+
+    // Then, run the delayed deletion after manually killing the session
+    allowDelayedDestructionToStart();
+    waitForDelayedDestructionToFinish();
+
+    // Ensure it didn't close again and is still dead
+    Mock::VerifyAndClearExpectations(sMockBinding.get());
+    EXPECT_EQ(mWrapper->alive(), false);
+}
+
 }  // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index f67042b..073a835 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -14,12 +14,11 @@
  * limitations under the License.
  */
 
-#include <VectorDrawable.h>
-#include <gtest/gtest.h>
-
 #include <SkBlendMode.h>
 #include <SkClipStack.h>
 #include <SkSurface_Base.h>
+#include <VectorDrawable.h>
+#include <gtest/gtest.h>
 #include <include/effects/SkImageFilters.h>
 #include <string.h>
 
@@ -144,7 +143,7 @@
 }
 
 TEST(RenderNodeDrawable, composeOnLayer) {
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
     SkCanvas& canvas = *surface->getCanvas();
     canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
@@ -155,7 +154,7 @@
             });
 
     // attach a layer to the render node
-    auto surfaceLayer = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surfaceLayer = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
     auto canvas2 = surfaceLayer->getCanvas();
     canvas2->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
     rootNode->setLayerSurface(surfaceLayer);
@@ -190,7 +189,7 @@
 }
 
 TEST(RenderNodeDrawable, saveLayerClipAndMatrixRestore) {
-    auto surface = SkSurface::MakeRasterN32Premul(400, 800);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(400, 800));
     SkCanvas& canvas = *surface->getCanvas();
     canvas.drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorWHITE);
@@ -356,7 +355,7 @@
     EXPECT_EQ(3, canvas.getIndex());
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, emptyReceiver) {
+RENDERTHREAD_TEST(RenderNodeDrawable, emptyReceiver) {
     class ProjectionTestCanvas : public SkCanvas {
     public:
         ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
@@ -420,7 +419,7 @@
     EXPECT_EQ(2, canvas.getDrawCounter());
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, projectionHwLayer) {
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
     /* R is backward projected on B and C is a layer.
                 A
                / \
@@ -1053,7 +1052,7 @@
 }
 
 // Verify that layers are composed with linear filtering.
-RENDERTHREAD_SKIA_PIPELINE_TEST(RenderNodeDrawable, layerComposeQuality) {
+RENDERTHREAD_TEST(RenderNodeDrawable, layerComposeQuality) {
     static const int CANVAS_WIDTH = 1;
     static const int CANVAS_HEIGHT = 1;
     static const int LAYER_WIDTH = 1;
@@ -1077,7 +1076,8 @@
             });
 
     layerNode->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
-    layerNode->setLayerSurface(SkSurface::MakeRasterN32Premul(LAYER_WIDTH, LAYER_HEIGHT));
+    layerNode->setLayerSurface(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(LAYER_WIDTH, 
+                                                                             LAYER_HEIGHT)));
 
     FrameTestCanvas canvas;
     RenderNodeDrawable drawable(layerNode.get(), &canvas, true);
@@ -1170,7 +1170,7 @@
 }
 
 // Draw a vector drawable twice but with different bounds and verify correct bounds are used.
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
+RENDERTHREAD_TEST(SkiaRecordingCanvas, drawVectorDrawable) {
     static const int CANVAS_WIDTH = 100;
     static const int CANVAS_HEIGHT = 200;
     class VectorDrawableTestCanvas : public TestCanvasBase {
@@ -1243,7 +1243,7 @@
             SkBitmap bitmap;
             bitmap.allocN32Pixels(CANVAS_WIDTH, CANVAS_HEIGHT);
             bitmap.setImmutable();
-            return SkImage::MakeFromBitmap(bitmap);
+            return bitmap.asImage();
         }
         SkCanvas* onNewCanvas() override { return new SimpleTestCanvas(); }
         sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override { return nullptr; }
diff --git a/libs/hwui/tests/unit/RenderNodeTests.cpp b/libs/hwui/tests/unit/RenderNodeTests.cpp
index 80796f4..e727ea8 100644
--- a/libs/hwui/tests/unit/RenderNodeTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeTests.cpp
@@ -231,8 +231,7 @@
 }
 
 TEST(RenderNode, releasedCallback) {
-    int functor = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+    int functor = TestUtils::createMockFunctor();
 
     auto node = TestUtils::createNode(0, 0, 200, 400, [&](RenderProperties& props, Canvas& canvas) {
         canvas.drawWebViewFunctor(functor);
@@ -332,3 +331,31 @@
     EXPECT_EQ(uirenderer::Rect(0, 0, 200, 400), info.layerUpdateQueue->entries().at(0).damage);
     canvasContext->destroy();
 }
+
+TEST(RenderNode, hasNoFill) {
+    auto rootNode =
+            TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+                Paint paint;
+                paint.setStyle(SkPaint::Style::kStroke_Style);
+                canvas.drawRect(10, 10, 100, 100, paint);
+            });
+
+    TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode);
+
+    EXPECT_FALSE(rootNode.get()->getDisplayList().hasFill());
+    EXPECT_FALSE(rootNode.get()->getDisplayList().hasText());
+}
+
+TEST(RenderNode, hasFill) {
+    auto rootNode =
+            TestUtils::createNode(0, 0, 200, 400, [](RenderProperties& props, Canvas& canvas) {
+                Paint paint;
+                paint.setStyle(SkPaint::kStrokeAndFill_Style);
+                canvas.drawRect(10, 10, 100, 100, paint);
+            });
+
+    TestUtils::syncHierarchyPropertiesAndDisplayList(rootNode);
+
+    EXPECT_TRUE(rootNode.get()->getDisplayList().hasFill());
+    EXPECT_FALSE(rootNode.get()->getDisplayList().hasText());
+}
diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp
index 9aa2e1d..0f8bd13 100644
--- a/libs/hwui/tests/unit/ShaderCacheTests.cpp
+++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp
@@ -370,9 +370,9 @@
 }
 
 using namespace android::uirenderer;
-RENDERTHREAD_SKIA_PIPELINE_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
+RENDERTHREAD_TEST(ShaderCacheTest, testOnVkFrameFlushed) {
     if (Properties::getRenderPipelineType() != RenderPipelineType::SkiaVulkan) {
-        // RENDERTHREAD_SKIA_PIPELINE_TEST declares both SkiaVK and SkiaGL variants.
+        // RENDERTHREAD_TEST declares both SkiaVK and SkiaGL variants.
         GTEST_SKIP() << "This test is only applicable to RenderPipelineType::SkiaVulkan";
     }
     if (!folderExist(getExternalStorageFolder())) {
diff --git a/libs/hwui/tests/unit/SkiaCanvasTests.cpp b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
index 87c5216..e53fcaa 100644
--- a/libs/hwui/tests/unit/SkiaCanvasTests.cpp
+++ b/libs/hwui/tests/unit/SkiaCanvasTests.cpp
@@ -36,7 +36,7 @@
 using namespace android::uirenderer;
 
 TEST(SkiaCanvas, drawShadowLayer) {
-    auto surface = SkSurface::MakeRasterN32Premul(10, 10);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(10, 10));
     SkiaCanvas canvas(surface->getCanvas());
 
     // clear to white
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index f825d7c..064d42e 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -48,8 +48,7 @@
     SkCanvas dummyCanvas;
     RenderNodeDrawable drawable(nullptr, &dummyCanvas);
     skiaDL->mChildNodes.emplace_back(nullptr, &dummyCanvas);
-    int functor1 = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+    int functor1 = TestUtils::createMockFunctor();
     GLFunctorDrawable functorDrawable{functor1, &dummyCanvas};
     WebViewFunctor_release(functor1);
     skiaDL->mChildFunctors.push_back(&functorDrawable);
@@ -101,8 +100,7 @@
 
     SkCanvas dummyCanvas;
 
-    int functor1 = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+    int functor1 = TestUtils::createMockFunctor();
     auto& counts = TestUtils::countsForFunctor(functor1);
     skiaDL.mChildFunctors.push_back(
             skiaDL.allocateDrawable<GLFunctorDrawable>(functor1, &dummyCanvas));
@@ -131,6 +129,33 @@
     EXPECT_EQ(counts.destroyed, 1);
 }
 
+TEST(SkiaDisplayList, recordMutableBitmap) {
+    SkiaRecordingCanvas canvas{nullptr, 100, 100};
+    auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+            10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType));
+    EXPECT_FALSE(bitmap->isImmutable());
+    canvas.drawBitmap(*bitmap, 0, 0, nullptr);
+    auto displayList = canvas.finishRecording();
+    ASSERT_EQ(1, displayList->mMutableImages.size());
+    EXPECT_EQ(10, displayList->mMutableImages[0]->width());
+    EXPECT_EQ(20, displayList->mMutableImages[0]->height());
+}
+
+TEST(SkiaDisplayList, recordMutableBitmapInShader) {
+    SkiaRecordingCanvas canvas{nullptr, 100, 100};
+    auto bitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
+            10, 20, SkColorType::kN32_SkColorType, SkAlphaType::kPremul_SkAlphaType));
+    EXPECT_FALSE(bitmap->isImmutable());
+    SkSamplingOptions sampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
+    Paint paint;
+    paint.setShader(bitmap->makeImage()->makeShader(sampling));
+    canvas.drawPaint(paint);
+    auto displayList = canvas.finishRecording();
+    ASSERT_EQ(1, displayList->mMutableImages.size());
+    EXPECT_EQ(10, displayList->mMutableImages[0]->width());
+    EXPECT_EQ(20, displayList->mMutableImages[0]->height());
+}
+
 class ContextFactory : public IContextFactory {
 public:
     virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
@@ -138,7 +163,7 @@
     }
 };
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren) {
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren) {
     auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
@@ -197,7 +222,7 @@
     canvasContext->destroy();
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
+RENDERTHREAD_TEST(SkiaDisplayList, prepareListAndChildren_vdOffscreen) {
     auto rootNode = TestUtils::createNode(0, 0, 200, 400, nullptr);
     ContextFactory contextFactory;
     std::unique_ptr<CanvasContext> canvasContext(
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 4d0595e..3ded540 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -42,7 +42,7 @@
 using namespace android::uirenderer::renderthread;
 using namespace android::uirenderer::skiapipeline;
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrame) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrame) {
     auto redNode = TestUtils::createSkiaNode(
             0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
                 redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -54,7 +54,7 @@
     bool opaque = true;
     android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
@@ -62,7 +62,7 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckOpaque) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckOpaque) {
     auto halfGreenNode = TestUtils::createSkiaNode(
             0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& bottomHalfGreenCanvas) {
                 Paint greenPaint;
@@ -76,7 +76,7 @@
     renderNodes.push_back(halfGreenNode);
     android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
-    auto surface = SkSurface::MakeRasterN32Premul(2, 2);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
@@ -89,7 +89,7 @@
     ASSERT_EQ(TestUtils::getColor(surface, 0, 1), SK_ColorGREEN);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
+RENDERTHREAD_TEST(SkiaPipeline, renderFrameCheckDirtyRect) {
     auto redNode = TestUtils::createSkiaNode(
             0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
                 redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
@@ -100,7 +100,7 @@
     renderNodes.push_back(redNode);
     android::uirenderer::Rect contentDrawBounds(0, 0, 2, 2);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
-    auto surface = SkSurface::MakeRasterN32Premul(2, 2);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
     pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, true, contentDrawBounds, surface,
@@ -111,12 +111,12 @@
     ASSERT_EQ(TestUtils::getColor(surface, 1, 1), SK_ColorRED);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderLayer) {
+RENDERTHREAD_TEST(SkiaPipeline, renderLayer) {
     auto redNode = TestUtils::createSkiaNode(
             0, 0, 1, 1, [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
                 redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
             });
-    auto surfaceLayer1 = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surfaceLayer1 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
     surfaceLayer1->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surfaceLayer1, 0, 0), SK_ColorWHITE);
     redNode->setLayerSurface(surfaceLayer1);
@@ -127,7 +127,7 @@
             0, 0, 2, 2, [](RenderProperties& props, SkiaRecordingCanvas& blueCanvas) {
                 blueCanvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
             });
-    auto surfaceLayer2 = SkSurface::MakeRasterN32Premul(2, 2);
+    auto surfaceLayer2 = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(2, 2));
     surfaceLayer2->getCanvas()->drawColor(SK_ColorWHITE, SkBlendMode::kSrcOver);
     ASSERT_EQ(TestUtils::getColor(surfaceLayer2, 0, 0), SK_ColorWHITE);
     blueNode->setLayerSurface(surfaceLayer2);
@@ -154,7 +154,7 @@
     blueNode->setLayerSurface(sk_sp<SkSurface>());
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, renderOverdraw) {
+RENDERTHREAD_TEST(SkiaPipeline, renderOverdraw) {
     ScopedProperty<bool> prop(Properties::debugOverdraw, true);
 
     auto whiteNode = TestUtils::createSkiaNode(
@@ -169,7 +169,7 @@
     // empty contentDrawBounds is avoiding backdrop/content logic, which would lead to less overdraw
     android::uirenderer::Rect contentDrawBounds(0, 0, 0, 0);
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
 
     // Initialize the canvas to blue.
     surface->getCanvas()->drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
@@ -227,7 +227,7 @@
 };
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, deferRenderNodeScene) {
+RENDERTHREAD_TEST(SkiaPipeline, deferRenderNodeScene) {
     class DeferTestCanvas : public SkCanvas {
     public:
         DeferTestCanvas() : SkCanvas(800, 600) {}
@@ -297,7 +297,7 @@
     EXPECT_EQ(4, surface->canvas()->mDrawCounter);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped) {
+RENDERTHREAD_TEST(SkiaPipeline, clipped) {
     static const int CANVAS_WIDTH = 200;
     static const int CANVAS_HEIGHT = 200;
     class ClippedTestCanvas : public SkCanvas {
@@ -330,7 +330,7 @@
 }
 
 // Test renderFrame with a dirty clip and a pre-transform matrix.
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clipped_rotated) {
+RENDERTHREAD_TEST(SkiaPipeline, clipped_rotated) {
     static const int CANVAS_WIDTH = 200;
     static const int CANVAS_HEIGHT = 100;
     static const SkMatrix rotateMatrix = SkMatrix::MakeAll(0, -1, CANVAS_HEIGHT, 1, 0, 0, 0, 0, 1);
@@ -366,7 +366,7 @@
     EXPECT_EQ(1, surface->canvas()->mDrawCounter);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, clip_replace) {
+RENDERTHREAD_TEST(SkiaPipeline, clip_replace) {
     static const int CANVAS_WIDTH = 50;
     static const int CANVAS_HEIGHT = 50;
     class ClipReplaceTestCanvas : public SkCanvas {
@@ -396,7 +396,7 @@
     EXPECT_EQ(1, surface->canvas()->mDrawCounter);
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, context_lost) {
+RENDERTHREAD_TEST(SkiaPipeline, context_lost) {
     test::TestContext context;
     auto surface = context.surface();
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
@@ -410,7 +410,7 @@
     EXPECT_TRUE(pipeline->isSurfaceReady());
 }
 
-RENDERTHREAD_SKIA_PIPELINE_TEST(SkiaPipeline, pictureCallback) {
+RENDERTHREAD_TEST(SkiaPipeline, pictureCallback) {
     // create a pipeline and add a picture callback
     auto pipeline = std::make_unique<SkiaOpenGLPipeline>(renderThread);
     int callbackCount = 0;
@@ -428,7 +428,7 @@
     renderNodes.push_back(redNode);
     bool opaque = true;
     android::uirenderer::Rect contentDrawBounds(0, 0, 1, 1);
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
+    auto surface = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(1, 1));
     pipeline->renderFrame(layerUpdateQueue, dirty, renderNodes, opaque, contentDrawBounds, surface,
                           SkMatrix::I());
 
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 499afa0..c71c4d2 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -29,6 +29,7 @@
 
 #include "hwui/MinikinSkia.h"
 #include "hwui/Typeface.h"
+#include "utils/TypefaceUtils.h"
 
 using namespace android;
 
@@ -56,7 +57,7 @@
     sk_sp<SkData> skData =
             SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
     std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
-    sk_sp<SkFontMgr> fm(SkFontMgr::RefDefault());
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
     sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
     LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
     std::shared_ptr<minikin::MinikinFont> font =
diff --git a/libs/hwui/tests/unit/UnderlineTest.cpp b/libs/hwui/tests/unit/UnderlineTest.cpp
new file mode 100644
index 0000000..c70a304
--- /dev/null
+++ b/libs/hwui/tests/unit/UnderlineTest.cpp
@@ -0,0 +1,154 @@
+/*
+ * 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.
+ */
+
+#include <fcntl.h>
+#include <flag_macros.h>
+#include <gtest/gtest.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <utils/Log.h>
+
+#include "SkAlphaType.h"
+#include "SkBitmap.h"
+#include "SkData.h"
+#include "SkFontMgr.h"
+#include "SkImageInfo.h"
+#include "SkRefCnt.h"
+#include "SkStream.h"
+#include "SkTypeface.h"
+#include "SkiaCanvas.h"
+#include "hwui/Bitmap.h"
+#include "hwui/DrawTextFunctor.h"
+#include "hwui/MinikinSkia.h"
+#include "hwui/MinikinUtils.h"
+#include "hwui/Paint.h"
+#include "hwui/Typeface.h"
+#include "utils/TypefaceUtils.h"
+
+using namespace android;
+
+namespace {
+
+constexpr char kRobotoVariable[] = "/system/fonts/Roboto-Regular.ttf";
+constexpr char kJPFont[] = "/system/fonts/NotoSansCJK-Regular.ttc";
+
+// The underline position and thickness are cames from post table.
+constexpr float ROBOTO_POSITION_EM = 150.0 / 2048.0;
+constexpr float ROBOTO_THICKNESS_EM = 100.0 / 2048.0;
+constexpr float NOTO_CJK_POSITION_EM = 125.0 / 1000.0;
+constexpr float NOTO_CJK_THICKNESS_EM = 50.0 / 1000.0;
+
+void unmap(const void* ptr, void* context) {
+    void* p = const_cast<void*>(ptr);
+    size_t len = reinterpret_cast<size_t>(context);
+    munmap(p, len);
+}
+
+// Create a font family from a single font file.
+std::shared_ptr<minikin::FontFamily> buildFamily(const char* fileName) {
+    int fd = open(fileName, O_RDONLY);
+    LOG_ALWAYS_FATAL_IF(fd == -1, "Failed to open file %s", fileName);
+    struct stat st = {};
+    LOG_ALWAYS_FATAL_IF(fstat(fd, &st) == -1, "Failed to stat file %s", fileName);
+    void* data = mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
+    sk_sp<SkData> skData =
+            SkData::MakeWithProc(data, st.st_size, unmap, reinterpret_cast<void*>(st.st_size));
+    std::unique_ptr<SkStreamAsset> fontData(new SkMemoryStream(skData));
+    sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
+    sk_sp<SkTypeface> typeface(fm->makeFromStream(std::move(fontData)));
+    LOG_ALWAYS_FATAL_IF(typeface == nullptr, "Failed to make typeface from %s", fileName);
+    std::shared_ptr<minikin::MinikinFont> font =
+            std::make_shared<MinikinFontSkia>(std::move(typeface), 0, data, st.st_size, fileName, 0,
+                                              std::vector<minikin::FontVariation>());
+    std::vector<std::shared_ptr<minikin::Font>> fonts;
+    fonts.push_back(minikin::Font::Builder(font).build());
+    return minikin::FontFamily::create(std::move(fonts));
+}
+
+// Create a typeface from roboto and NotoCJK.
+Typeface* makeTypeface() {
+    return Typeface::createFromFamilies(
+            std::vector<std::shared_ptr<minikin::FontFamily>>(
+                    {buildFamily(kRobotoVariable), buildFamily(kJPFont)}),
+            RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE, nullptr /* fallback */);
+}
+
+// Execute a text layout.
+minikin::Layout doLayout(const std::vector<uint16_t> text, Paint paint, Typeface* typeface) {
+    return MinikinUtils::doLayout(&paint, minikin::Bidi::LTR, typeface, text.data(), text.size(),
+                                  0 /* start */, text.size(), 0, text.size(), nullptr);
+}
+
+DrawTextFunctor processFunctor(const std::vector<uint16_t>& text, Paint* paint) {
+    // Create canvas
+    SkImageInfo info = SkImageInfo::Make(1, 1, kN32_SkColorType, kOpaque_SkAlphaType);
+    sk_sp<Bitmap> bitmap = Bitmap::allocateHeapBitmap(info);
+    SkBitmap skBitmap;
+    bitmap->getSkBitmap(&skBitmap);
+    SkiaCanvas canvas(skBitmap);
+
+    // Create minikin::Layout
+    std::unique_ptr<Typeface> typeface(makeTypeface());
+    minikin::Layout layout = doLayout(text, *paint, typeface.get());
+
+    DrawTextFunctor f(layout, &canvas, *paint, 0, 0, layout.getAdvance());
+    MinikinUtils::forFontRun(layout, paint, f);
+    return f;
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Roboto,
+                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+                                                    fix_double_underline))) {
+    float textSize = 100;
+    Paint paint;
+    paint.getSkFont().setSize(textSize);
+    paint.setUnderline(true);
+    // the text is "abc"
+    DrawTextFunctor functor = processFunctor({0x0061, 0x0062, 0x0063}, &paint);
+
+    EXPECT_EQ(ROBOTO_POSITION_EM * textSize, functor.getUnderlinePosition());
+    EXPECT_EQ(ROBOTO_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, NotoCJK,
+                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+                                                    fix_double_underline))) {
+    float textSize = 100;
+    Paint paint;
+    paint.getSkFont().setSize(textSize);
+    paint.setUnderline(true);
+    // The text is 恂恄恆 in Japanese
+    DrawTextFunctor functor = processFunctor({0x3042, 0x3044, 0x3046}, &paint);
+
+    EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+    EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+
+TEST_WITH_FLAGS(UnderlineTest, Mixture,
+                REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::text::flags,
+                                                    fix_double_underline))) {
+    float textSize = 100;
+    Paint paint;
+    paint.getSkFont().setSize(textSize);
+    paint.setUnderline(true);
+    // The text is a恄c. The only middle of the character is Japanese.
+    DrawTextFunctor functor = processFunctor({0x0061, 0x3044, 0x0063}, &paint);
+
+    // We use the bottom, thicker line as underline. Here, use Noto's one.
+    EXPECT_EQ(NOTO_CJK_POSITION_EM * textSize, functor.getUnderlinePosition());
+    EXPECT_EQ(NOTO_CJK_THICKNESS_EM * textSize, functor.getUnderlineThickness());
+}
+}  // namespace
diff --git a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
index e1fb8b7..5e8f13d 100644
--- a/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
+++ b/libs/hwui/tests/unit/WebViewFunctorManagerTests.cpp
@@ -26,9 +26,15 @@
 using namespace android;
 using namespace android::uirenderer;
 
+#define ASSUME_GLES()                                                      \
+    if (WebViewFunctor_queryPlatformRenderMode() != RenderMode::OpenGL_ES) \
+    GTEST_SKIP() << "Not in GLES, skipping test"
+
 TEST(WebViewFunctor, createDestroyGLES) {
+    ASSUME_GLES();
     int functor = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+            nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+            RenderMode::OpenGL_ES);
     ASSERT_NE(-1, functor);
     WebViewFunctor_release(functor);
     TestUtils::runOnRenderThreadUnmanaged([](renderthread::RenderThread&) {
@@ -41,8 +47,10 @@
 }
 
 TEST(WebViewFunctor, createSyncHandleGLES) {
+    ASSUME_GLES();
     int functor = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+            nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+            RenderMode::OpenGL_ES);
     ASSERT_NE(-1, functor);
     auto handle = WebViewFunctorManager::instance().handleFor(functor);
     ASSERT_TRUE(handle);
@@ -82,8 +90,10 @@
 }
 
 TEST(WebViewFunctor, createSyncDrawGLES) {
+    ASSUME_GLES();
     int functor = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+            nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+            RenderMode::OpenGL_ES);
     ASSERT_NE(-1, functor);
     auto handle = WebViewFunctorManager::instance().handleFor(functor);
     ASSERT_TRUE(handle);
@@ -108,9 +118,11 @@
     EXPECT_EQ(1, counts.destroyed);
 }
 
-TEST(WebViewFunctor, contextDestroyed) {
+TEST(WebViewFunctor, contextDestroyedGLES) {
+    ASSUME_GLES();
     int functor = WebViewFunctor_create(
-            nullptr, TestUtils::createMockFunctor(RenderMode::OpenGL_ES), RenderMode::OpenGL_ES);
+            nullptr, TestUtils::createMockFunctorCallbacks(RenderMode::OpenGL_ES),
+            RenderMode::OpenGL_ES);
     ASSERT_NE(-1, functor);
     auto handle = WebViewFunctorManager::instance().handleFor(functor);
     ASSERT_TRUE(handle);
diff --git a/libs/hwui/tests/unit/main.cpp b/libs/hwui/tests/unit/main.cpp
index 10c874e..76cbc8a 100644
--- a/libs/hwui/tests/unit/main.cpp
+++ b/libs/hwui/tests/unit/main.cpp
@@ -14,15 +14,15 @@
  * limitations under the License.
  */
 
-#include "gmock/gmock.h"
-#include "gtest/gtest.h"
+#include <getopt.h>
+#include <signal.h>
 
 #include "Properties.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
 #include "hwui/Typeface.h"
 #include "tests/common/LeakChecker.h"
 
-#include <signal.h>
-
 using namespace std;
 using namespace android;
 using namespace android::uirenderer;
@@ -45,6 +45,57 @@
     raise(sig);
 }
 
+// For options that only exist in long-form. Anything in the
+// 0-255 range is reserved for short options (which just use their ASCII value)
+namespace LongOpts {
+enum {
+    Reserved = 255,
+    Renderer,
+};
+}
+
+static const struct option LONG_OPTIONS[] = {
+        {"renderer", required_argument, nullptr, LongOpts::Renderer}, {0, 0, 0, 0}};
+
+static RenderPipelineType parseRenderer(const char* renderer) {
+    // Anything that's not skiavk is skiagl
+    if (!strcmp(renderer, "skiavk")) {
+        return RenderPipelineType::SkiaVulkan;
+    }
+    return RenderPipelineType::SkiaGL;
+}
+
+struct Options {
+    RenderPipelineType renderer = RenderPipelineType::SkiaGL;
+};
+
+Options parseOptions(int argc, char* argv[]) {
+    int c;
+    opterr = 0;
+    Options opts;
+
+    while (true) {
+        /* getopt_long stores the option index here. */
+        int option_index = 0;
+
+        c = getopt_long(argc, argv, "", LONG_OPTIONS, &option_index);
+
+        if (c == -1) break;
+
+        switch (c) {
+            case 0:
+                // Option set a flag, don't need to do anything
+                // (although none of the current LONG_OPTIONS do this...)
+                break;
+
+            case LongOpts::Renderer:
+                opts.renderer = parseRenderer(optarg);
+                break;
+        }
+    }
+    return opts;
+}
+
 class TypefaceEnvironment : public testing::Environment {
 public:
     virtual void SetUp() { Typeface::setRobotoTypefaceForTest(); }
@@ -64,8 +115,9 @@
 
     // Avoid talking to SF
     Properties::isolatedProcess = true;
-    // Default to GLES (Vulkan-aware tests will override this)
-    Properties::overrideRenderPipelineType(RenderPipelineType::SkiaGL);
+
+    auto opts = parseOptions(argc, argv);
+    Properties::overrideRenderPipelineType(opts.renderer);
 
     // Run the tests
     testing::InitGoogleTest(&argc, argv);
diff --git a/libs/hwui/utils/ForceDark.h b/libs/hwui/utils/ForceDark.h
new file mode 100644
index 0000000..28538c4b
--- /dev/null
+++ b/libs/hwui/utils/ForceDark.h
@@ -0,0 +1,34 @@
+/*
+ * 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 FORCEDARKUTILS_H
+#define FORCEDARKUTILS_H
+
+namespace android {
+namespace uirenderer {
+
+/**
+ * The type of force dark set on the renderer, if any.
+ *
+ * This should stay in sync with the java @IntDef in
+ * frameworks/base/graphics/java/android/graphics/ForceDarkType.java
+ */
+enum class ForceDarkType : __uint8_t { NONE = 0, FORCE_DARK = 1, FORCE_INVERT_COLOR_DARK = 2 };
+
+} /* namespace uirenderer */
+} /* namespace android */
+
+#endif  // FORCEDARKUTILS_H
\ No newline at end of file
diff --git a/libs/hwui/utils/TypefaceUtils.cpp b/libs/hwui/utils/TypefaceUtils.cpp
new file mode 100644
index 0000000..a30b925
--- /dev/null
+++ b/libs/hwui/utils/TypefaceUtils.cpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#include <utils/TypefaceUtils.h>
+
+#include "include/ports/SkFontMgr_empty.h"
+
+namespace android {
+
+sk_sp<SkFontMgr> FreeTypeFontMgr() {
+    static sk_sp<SkFontMgr> mgr = SkFontMgr_New_Custom_Empty();
+    return mgr;
+}
+
+}  // namespace android
diff --git a/libs/hwui/utils/TypefaceUtils.h b/libs/hwui/utils/TypefaceUtils.h
new file mode 100644
index 0000000..c0adeae
--- /dev/null
+++ b/libs/hwui/utils/TypefaceUtils.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "SkFontMgr.h"
+#include "SkRefCnt.h"
+
+namespace android {
+
+// Return an SkFontMgr which is capable of turning bytes into a SkTypeface using Freetype.
+// There are no other fonts inside this SkFontMgr (e.g. no system fonts).
+sk_sp<SkFontMgr> FreeTypeFontMgr();
+
+}  // namespace android
\ No newline at end of file