Merge "Avoid clipping for common SysUI interactions with roundedCorners." into sc-dev
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 8ac4ff8..25e6dc9 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -1696,6 +1696,12 @@
 
     RUN_SLOW_FUNCTION_WITH_CONSENT_CHECK(RunDumpsysHigh);
 
+    // The dump mechanism in connectivity is refactored due to modularization work. Connectivity can
+    // only register with a default priority(NORMAL priority). Dumpstate has to call connectivity
+    // dump with priority parameters to dump high priority information.
+    RunDumpsys("SERVICE HIGH connectivity", {"connectivity", "--dump-priority", "HIGH"},
+                   CommandOptions::WithTimeout(10).Build());
+
     RunCommand("SYSTEM PROPERTIES", {"getprop"});
 
     RunCommand("STORAGED IO INFO", {"storaged", "-u", "-p"});
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 18b77e6..ef7fd44 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -366,45 +366,19 @@
 
 pid_t IPCThreadState::getCallingPid() const
 {
-    checkContextIsBinderForUse(__func__);
     return mCallingPid;
 }
 
 const char* IPCThreadState::getCallingSid() const
 {
-    checkContextIsBinderForUse(__func__);
     return mCallingSid;
 }
 
 uid_t IPCThreadState::getCallingUid() const
 {
-    checkContextIsBinderForUse(__func__);
     return mCallingUid;
 }
 
-IPCThreadState::SpGuard* IPCThreadState::pushGetCallingSpGuard(SpGuard* guard) {
-    SpGuard* orig = mServingStackPointerGuard;
-    mServingStackPointerGuard = guard;
-    return orig;
-}
-
-void IPCThreadState::restoreGetCallingSpGuard(SpGuard* guard) {
-    mServingStackPointerGuard = guard;
-}
-
-void IPCThreadState::checkContextIsBinderForUse(const char* use) const {
-    if (mServingStackPointerGuard == nullptr) return;
-
-    if (!mServingStackPointer || mServingStackPointerGuard < mServingStackPointer) {
-        LOG_ALWAYS_FATAL("In context %s, %s does not make sense.",
-                         mServingStackPointerGuard->context, use);
-    }
-
-    // in the case mServingStackPointer is deeper in the stack than the guard,
-    // we must be serving a binder transaction (maybe nested). This is a binder
-    // context, so we don't abort
-}
-
 int64_t IPCThreadState::clearCallingIdentity()
 {
     // ignore mCallingSid for legacy reasons
@@ -873,15 +847,15 @@
 }
 
 IPCThreadState::IPCThreadState()
-      : mProcess(ProcessState::self()),
-        mServingStackPointer(nullptr),
-        mServingStackPointerGuard(nullptr),
-        mWorkSource(kUnsetWorkSource),
-        mPropagateWorkSource(false),
-        mIsLooper(false),
-        mStrictModePolicy(0),
-        mLastTransactionBinderFlags(0),
-        mCallRestriction(mProcess->mCallRestriction) {
+    : mProcess(ProcessState::self()),
+      mServingStackPointer(nullptr),
+      mWorkSource(kUnsetWorkSource),
+      mPropagateWorkSource(false),
+      mIsLooper(false),
+      mStrictModePolicy(0),
+      mLastTransactionBinderFlags(0),
+      mCallRestriction(mProcess->mCallRestriction)
+{
     pthread_setspecific(gTLS, this);
     clearCaller();
     mIn.setDataCapacity(256);
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index e5a6026..2ba9fa2 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -18,9 +18,7 @@
 
 #include "RpcState.h"
 
-#include <android-base/scopeguard.h>
 #include <binder/BpBinder.h>
-#include <binder/IPCThreadState.h>
 #include <binder/RpcServer.h>
 
 #include "Debug.h"
@@ -30,8 +28,6 @@
 
 namespace android {
 
-using base::ScopeGuard;
-
 RpcState::RpcState() {}
 RpcState::~RpcState() {}
 
@@ -474,18 +470,6 @@
 
 status_t RpcState::processServerCommand(const base::unique_fd& fd, const sp<RpcSession>& session,
                                         const RpcWireHeader& command) {
-    IPCThreadState* kernelBinderState = IPCThreadState::selfOrNull();
-    IPCThreadState::SpGuard spGuard{"processing binder RPC command"};
-    IPCThreadState::SpGuard* origGuard;
-    if (kernelBinderState != nullptr) {
-        origGuard = kernelBinderState->pushGetCallingSpGuard(&spGuard);
-    }
-    ScopeGuard guardUnguard = [&]() {
-        if (kernelBinderState != nullptr) {
-            kernelBinderState->restoreGetCallingSpGuard(origGuard);
-        }
-    };
-
     switch (command.command) {
         case RPC_COMMAND_TRANSACT:
             return processTransact(fd, session, command);
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 5220b62..23a0cb0 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -81,32 +81,6 @@
              */
             uid_t               getCallingUid() const;
 
-            /**
-             * Make it an abort to rely on getCalling* for a section of
-             * execution.
-             *
-             * Usage:
-             *     IPCThreadState::SpGuard guard { "..." };
-             *     auto* orig = pushGetCallingSpGuard(&guard);
-             *     {
-             *         // will abort if you call getCalling*, unless you are
-             *         // serving a nested binder transaction
-             *     }
-             *     restoreCallingSpGuard(orig);
-             */
-            struct SpGuard {
-                const char* context;
-            };
-            SpGuard* pushGetCallingSpGuard(SpGuard* guard);
-            void restoreGetCallingSpGuard(SpGuard* guard);
-            /**
-             * Used internally by getCalling*. Can also be used to assert that
-             * you are in a binder context (getCalling* is valid). This is
-             * intentionally not exposed as a boolean API since code should be
-             * written to know its environment.
-             */
-            void checkContextIsBinderForUse(const char* use) const;
-
             void                setStrictModePolicy(int32_t policy);
             int32_t             getStrictModePolicy() const;
 
@@ -229,7 +203,6 @@
             Parcel              mOut;
             status_t            mLastError;
             const void*         mServingStackPointer;
-            SpGuard* mServingStackPointerGuard;
             pid_t               mCallingPid;
             const char*         mCallingSid;
             uid_t               mCallingUid;
diff --git a/libs/binder/tests/IBinderRpcTest.aidl b/libs/binder/tests/IBinderRpcTest.aidl
index 41daccc..ef4198d 100644
--- a/libs/binder/tests/IBinderRpcTest.aidl
+++ b/libs/binder/tests/IBinderRpcTest.aidl
@@ -55,6 +55,4 @@
     oneway void sleepMsAsync(int ms);
 
     void die(boolean cleanup);
-
-    void useKernelBinderCallingId();
 }
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 45b2776..0c3fbcd 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -73,7 +73,6 @@
     BINDER_LIB_TEST_REGISTER_SERVER,
     BINDER_LIB_TEST_ADD_SERVER,
     BINDER_LIB_TEST_ADD_POLL_SERVER,
-    BINDER_LIB_TEST_USE_CALLING_GUARD_TRANSACTION,
     BINDER_LIB_TEST_CALL_BACK,
     BINDER_LIB_TEST_CALL_BACK_VERIFY_BUF,
     BINDER_LIB_TEST_DELAYED_CALL_BACK,
@@ -605,24 +604,6 @@
     EXPECT_THAT(callBack->getResult(), StatusEq(NO_ERROR));
 }
 
-TEST_F(BinderLibTest, NoBinderCallContextGuard) {
-    IPCThreadState::SpGuard spGuard{"NoBinderCallContext"};
-    IPCThreadState::SpGuard *origGuard = IPCThreadState::self()->pushGetCallingSpGuard(&spGuard);
-
-    // yes, this test uses threads, but it's careful and uses fork in addServer
-    EXPECT_DEATH({ IPCThreadState::self()->getCallingPid(); },
-                 "In context NoBinderCallContext, getCallingPid does not make sense.");
-
-    IPCThreadState::self()->restoreGetCallingSpGuard(origGuard);
-}
-
-TEST_F(BinderLibTest, BinderCallContextGuard) {
-    sp<IBinder> binder = addServer();
-    Parcel data, reply;
-    EXPECT_THAT(binder->transact(BINDER_LIB_TEST_USE_CALLING_GUARD_TRANSACTION, data, &reply),
-                StatusEq(DEAD_OBJECT));
-}
-
 TEST_F(BinderLibTest, AddServer)
 {
     sp<IBinder> server = addServer();
@@ -1281,18 +1262,6 @@
                 pthread_mutex_unlock(&m_serverWaitMutex);
                 return ret;
             }
-            case BINDER_LIB_TEST_USE_CALLING_GUARD_TRANSACTION: {
-                IPCThreadState::SpGuard spGuard{"GuardInBinderTransaction"};
-                IPCThreadState::SpGuard *origGuard =
-                        IPCThreadState::self()->pushGetCallingSpGuard(&spGuard);
-
-                // if the guard works, this should abort
-                (void)IPCThreadState::self()->getCallingPid();
-
-                IPCThreadState::self()->restoreGetCallingSpGuard(origGuard);
-                return NO_ERROR;
-            }
-
             case BINDER_LIB_TEST_GETPID:
                 reply->writeInt32(getpid());
                 return NO_ERROR;
@@ -1520,11 +1489,6 @@
 {
     binderLibTestServiceName += String16(binderserversuffix);
 
-    // Testing to make sure that calls that we are serving can use getCallin*
-    // even though we don't here.
-    IPCThreadState::SpGuard spGuard{"main server thread"};
-    (void)IPCThreadState::self()->pushGetCallingSpGuard(&spGuard);
-
     status_t ret;
     sp<IServiceManager> sm = defaultServiceManager();
     BinderLibTestService* testServicePtr;
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 3f94df2..a96deb5 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -23,7 +23,6 @@
 #include <android/binder_libbinder.h>
 #include <binder/Binder.h>
 #include <binder/BpBinder.h>
-#include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/ProcessState.h>
 #include <binder/RpcServer.h>
@@ -192,13 +191,6 @@
             _exit(1);
         }
     }
-    Status useKernelBinderCallingId() override {
-        // this is WRONG! It does not make sense when using RPC binder, and
-        // because it is SO wrong, and so much code calls this, it should abort!
-
-        (void)IPCThreadState::self()->getCallingPid();
-        return Status::ok();
-    }
 };
 sp<IBinder> MyBinderRpcTest::mHeldBinder;
 
@@ -895,19 +887,6 @@
     }
 }
 
-TEST_P(BinderRpc, UseKernelBinderCallingId) {
-    auto proc = createRpcTestSocketServerProcess(1);
-
-    // we can't allocate IPCThreadState so actually the first time should
-    // succeed :(
-    EXPECT_OK(proc.rootIface->useKernelBinderCallingId());
-
-    // second time! we catch the error :)
-    EXPECT_EQ(DEAD_OBJECT, proc.rootIface->useKernelBinderCallingId().transactionError());
-
-    proc.expectInvalid = true;
-}
-
 TEST_P(BinderRpc, WorksWithLibbinderNdkPing) {
     auto proc = createRpcTestSocketServerProcess(1);
 
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 08800f7..a2868c6 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -317,6 +317,11 @@
     std::unique_lock _lock{mMutex};
     BQA_LOGV("releaseBufferCallback graphicBufferId=%" PRIu64, graphicBufferId);
 
+    if (mSurfaceControl != nullptr) {
+        mTransformHint = mSurfaceControl->getTransformHint();
+        mBufferItemConsumer->setTransformHint(mTransformHint);
+    }
+
     auto it = mSubmitted.find(graphicBufferId);
     if (it == mSubmitted.end()) {
         BQA_LOGE("ERROR: releaseBufferCallback without corresponding submitted buffer %" PRIu64,
@@ -596,6 +601,14 @@
     status_t setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) override {
         return mBbq->setFrameTimelineInfo(frameTimelineInfo);
     }
+ protected:
+    uint32_t getTransformHint() const override {
+        if (mStickyTransform == 0 && !transformToDisplayInverse()) {
+            return mBbq->getLastTransformHint();
+        } else {
+            return 0;
+        }
+    }
 };
 
 // TODO: Can we coalesce this with frame updates? Need to confirm
@@ -765,4 +778,12 @@
     return convertedFormat;
 }
 
+uint32_t BLASTBufferQueue::getLastTransformHint() const {
+    if (mSurfaceControl != nullptr) {
+        return mSurfaceControl->getTransformHint();
+    } else {
+        return 0;
+    }
+}
+
 } // namespace android
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 2fc9d47..83aaf36 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -1288,7 +1288,7 @@
                         mUserHeight ? mUserHeight : mDefaultHeight);
                 return NO_ERROR;
             case NATIVE_WINDOW_TRANSFORM_HINT:
-                *value = static_cast<int>(mTransformHint);
+                *value = static_cast<int>(getTransformHint());
                 return NO_ERROR;
             case NATIVE_WINDOW_CONSUMER_RUNNING_BEHIND: {
                 status_t err = NO_ERROR;
@@ -1822,7 +1822,7 @@
     return getExtraBufferCount(extraBuffers);
 }
 
-bool Surface::transformToDisplayInverse() {
+bool Surface::transformToDisplayInverse() const {
     return (mTransform & NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY) ==
             NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY;
 }
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 139dbb7..c4ca399 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -103,6 +103,8 @@
 
     void setSidebandStream(const sp<NativeHandle>& stream);
 
+    uint32_t getLastTransformHint() const;
+
     virtual ~BLASTBufferQueue();
 
 private:
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index d22bdaa..89e1909 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -278,7 +278,6 @@
     int dispatchGetLastQueuedBuffer(va_list args);
     int dispatchSetFrameTimelineInfo(va_list args);
     int dispatchGetExtraBufferCount(va_list args);
-    bool transformToDisplayInverse();
 
 protected:
     virtual int dequeueBuffer(ANativeWindowBuffer** buffer, int* fenceFd);
@@ -490,6 +489,8 @@
     // mTransformHint is the transform probably applied to buffers of this
     // window. this is only a hint, actual transform may differ.
     uint32_t mTransformHint;
+    virtual uint32_t getTransformHint() const { return mTransformHint; }
+    bool transformToDisplayInverse() const;
 
     // mProducerControlledByApp whether this buffer producer is controlled
     // by the application
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index bfb7465..63ed1ba 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -194,18 +194,6 @@
             lhs.length == rhs.length && lhs.casterIsTranslucent == rhs.casterIsTranslucent;
 }
 
-static inline bool operator==(const BlurRegion& lhs, const BlurRegion& rhs) {
-    return lhs.alpha == rhs.alpha && lhs.cornerRadiusTL == rhs.cornerRadiusTL &&
-            lhs.cornerRadiusTR == rhs.cornerRadiusTR && lhs.cornerRadiusBL == rhs.cornerRadiusBL &&
-            lhs.cornerRadiusBR == rhs.cornerRadiusBR && lhs.blurRadius == rhs.blurRadius &&
-            lhs.left == rhs.left && lhs.top == rhs.top && lhs.right == rhs.right &&
-            lhs.bottom == rhs.bottom;
-}
-
-static inline bool operator!=(const BlurRegion& lhs, const BlurRegion& rhs) {
-    return !(lhs == rhs);
-}
-
 static inline bool operator==(const LayerSettings& lhs, const LayerSettings& rhs) {
     if (lhs.blurRegions.size() != rhs.blurRegions.size()) {
         return false;
@@ -288,7 +276,7 @@
 }
 
 static inline void PrintTo(const LayerSettings& settings, ::std::ostream* os) {
-    *os << "LayerSettings {";
+    *os << "LayerSettings for '" << settings.name.c_str() << "' {";
     *os << "\n    .geometry = ";
     PrintTo(settings.geometry, os);
     *os << "\n    .source = ";
diff --git a/libs/renderengine/skia/filters/LinearEffect.h b/libs/renderengine/skia/filters/LinearEffect.h
index 9e989ea..14a3b61 100644
--- a/libs/renderengine/skia/filters/LinearEffect.h
+++ b/libs/renderengine/skia/filters/LinearEffect.h
@@ -90,10 +90,8 @@
 // matrix transforming from linear XYZ to linear RGB immediately before OETF.
 // We also provide additional HDR metadata upon creating the shader:
 // * The max display luminance is the max luminance of the physical display in nits
-// * The max mastering luminance is provided as the max luminance from the SMPTE 2086
-// standard.
-// * The max content luminance is provided as the max light level from the CTA 861.3
-// standard.
+// * The max luminance is provided as the max luminance for the buffer, either from the SMPTE 2086
+// or as the max light level from the CTA 861.3 standard.
 sk_sp<SkShader> createLinearEffectShader(sk_sp<SkShader> inputShader,
                                          const LinearEffect& linearEffect,
                                          sk_sp<SkRuntimeEffect> runtimeEffect,
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 636fbde..9dc9beb 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -36,6 +36,7 @@
 using android::hardware::graphics::mapper::V4_0::BufferDescriptor;
 using android::hardware::graphics::mapper::V4_0::Error;
 using android::hardware::graphics::mapper::V4_0::IMapper;
+using AidlDataspace = ::aidl::android::hardware::graphics::common::Dataspace;
 using BufferDump = android::hardware::graphics::mapper::V4_0::IMapper::BufferDump;
 using MetadataDump = android::hardware::graphics::mapper::V4_0::IMapper::MetadataDump;
 using MetadataType = android::hardware::graphics::mapper::V4_0::IMapper::MetadataType;
@@ -597,7 +598,7 @@
     if (!outDataspace) {
         return BAD_VALUE;
     }
-    aidl::android::hardware::graphics::common::Dataspace dataspace;
+    AidlDataspace dataspace;
     status_t error = get(bufferHandle, gralloc4::MetadataType_Dataspace, gralloc4::decodeDataspace,
                          &dataspace);
     if (error) {
@@ -841,6 +842,7 @@
     uint32_t pixelFormatFourCC;
     uint64_t pixelFormatModifier;
     uint64_t usage;
+    AidlDataspace dataspace;
     uint64_t allocationSize;
     uint64_t protectedContent;
     ExtendableType compression;
@@ -892,6 +894,11 @@
     if (error != NO_ERROR) {
         return error;
     }
+    error = metadataDumpHelper(bufferDump, StandardMetadataType::DATASPACE,
+                               gralloc4::decodeDataspace, &dataspace);
+    if (error != NO_ERROR) {
+        return error;
+    }
     error = metadataDumpHelper(bufferDump, StandardMetadataType::ALLOCATION_SIZE,
                                gralloc4::decodeAllocationSize, &allocationSize);
     if (error != NO_ERROR) {
@@ -932,6 +939,7 @@
              << "KiB, w/h:" << width << "x" << height << ", usage: 0x" << std::hex << usage
              << std::dec << ", req fmt:" << static_cast<int32_t>(pixelFormatRequested)
              << ", fourcc/mod:" << pixelFormatFourCC << "/" << pixelFormatModifier
+             << ", dataspace: 0x" << std::hex << static_cast<uint32_t>(dataspace)
              << ", compressed: ";
 
     if (less) {
diff --git a/libs/ui/include/ui/BlurRegion.h b/libs/ui/include/ui/BlurRegion.h
index 69a586e..a9ca369 100644
--- a/libs/ui/include/ui/BlurRegion.h
+++ b/libs/ui/include/ui/BlurRegion.h
@@ -20,6 +20,8 @@
 #include <iosfwd>
 #include <iostream>
 
+#include <math/HashCombine.h>
+
 namespace android {
 
 struct BlurRegion {
@@ -33,6 +35,16 @@
     int top;
     int right;
     int bottom;
+
+    inline bool operator==(const BlurRegion& other) const {
+        return blurRadius == other.blurRadius && cornerRadiusTL == other.cornerRadiusTL &&
+                cornerRadiusTR == other.cornerRadiusTR && cornerRadiusBL == other.cornerRadiusBL &&
+                cornerRadiusBR == other.cornerRadiusBR && alpha == other.alpha &&
+                left == other.left && top == other.top && right == other.right &&
+                bottom == other.bottom;
+    }
+
+    inline bool operator!=(const BlurRegion& other) const { return !(*this == other); }
 };
 
 static inline void PrintTo(const BlurRegion& blurRegion, ::std::ostream* os) {
@@ -50,4 +62,15 @@
     *os << "\n}";
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
+
+namespace std {
+template <>
+struct hash<android::BlurRegion> {
+    size_t operator()(const android::BlurRegion& region) const {
+        return android::hashCombine(region.blurRadius, region.cornerRadiusTL, region.cornerRadiusTR,
+                                    region.cornerRadiusBL, region.cornerRadiusBR, region.alpha,
+                                    region.left, region.top, region.right, region.bottom);
+    }
+};
+} // namespace std
\ No newline at end of file
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index 06f26eb..dc6eab4 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -110,6 +110,9 @@
     // CachedSet and punching a hole.
     bool requiresHolePunch() const;
 
+    // True if any constituent layer is configured to blur any layers behind.
+    bool hasBlurBehind() const;
+
     // Add a layer that will be drawn behind this one. ::render() will render a
     // hole in this CachedSet's buffer, allowing the supplied layer to peek
     // through. Must be called before ::render().
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index 864251f..213c55e 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -20,6 +20,7 @@
 #include <compositionengine/impl/planner/CachedSet.h>
 #include <compositionengine/impl/planner/LayerState.h>
 
+#include <numeric>
 #include <vector>
 
 namespace android {
@@ -60,6 +61,73 @@
     bool mergeWithCachedSets(const std::vector<const LayerState*>& layers,
                              std::chrono::steady_clock::time_point now);
 
+    // A Run is a sequence of CachedSets, which is a candidate for flattening into a single
+    // CachedSet. Because it is wasteful to flatten 1 CachedSet, a Run must contain more than 1
+    // CachedSet
+    class Run {
+    public:
+        // A builder for a Run, to aid in construction
+        class Builder {
+        private:
+            std::vector<CachedSet>::const_iterator mStart;
+            std::vector<size_t> mLengths;
+            const CachedSet* mHolePunchCandidate = nullptr;
+
+        public:
+            // Initializes a Builder a CachedSet to start from.
+            // This start iterator must be an iterator for mLayers
+            void init(const std::vector<CachedSet>::const_iterator& start) {
+                mStart = start;
+                mLengths.push_back(start->getLayerCount());
+            }
+
+            // Appends a new CachedSet to the end of the run
+            // The provided length must be the size of the next sequential CachedSet in layers
+            void append(size_t length) { mLengths.push_back(length); }
+
+            // Sets the hole punch candidate for the Run.
+            void setHolePunchCandidate(const CachedSet* holePunchCandidate) {
+                mHolePunchCandidate = holePunchCandidate;
+            }
+
+            // Builds a Run instance, if a valid Run may be built.
+            std::optional<Run> validateAndBuild() {
+                if (mLengths.size() <= 1) {
+                    return std::nullopt;
+                }
+
+                return Run(mStart,
+                           std::reduce(mLengths.cbegin(), mLengths.cend(), 0u,
+                                       [](size_t left, size_t right) { return left + right; }),
+                           mHolePunchCandidate);
+            }
+
+            void reset() { *this = {}; }
+        };
+
+        // Gets the starting CachedSet of this run.
+        // This is an iterator into mLayers
+        const std::vector<CachedSet>::const_iterator& getStart() const { return mStart; }
+        // Gets the total number of layers encompassing this Run.
+        size_t getLayerLength() const { return mLength; }
+        // Gets the hole punch candidate for this Run.
+        const CachedSet* getHolePunchCandidate() const { return mHolePunchCandidate; }
+
+    private:
+        Run(std::vector<CachedSet>::const_iterator start, size_t length,
+            const CachedSet* holePunchCandidate)
+              : mStart(start), mLength(length), mHolePunchCandidate(holePunchCandidate) {}
+        const std::vector<CachedSet>::const_iterator mStart;
+        const size_t mLength;
+        const CachedSet* const mHolePunchCandidate;
+
+        friend class Builder;
+    };
+
+    std::vector<Run> findCandidateRuns(std::chrono::steady_clock::time_point now) const;
+
+    std::optional<Run> findBestRun(std::vector<Run>& runs) const;
+
     void buildCachedSets(std::chrono::steady_clock::time_point now);
 
     const bool mEnableHolePunch;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
index 3391273..fef0dfb 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h
@@ -26,6 +26,7 @@
 #include <string>
 
 #include "DisplayHardware/Hal.h"
+#include "math/HashCombine.h"
 
 namespace std {
 template <typename T>
@@ -48,24 +49,26 @@
 
 // clang-format off
 enum class LayerStateField : uint32_t {
-    None            = 0u,
-    Id              = 1u << 0,
-    Name            = 1u << 1,
-    DisplayFrame    = 1u << 2,
-    SourceCrop      = 1u << 3,
-    BufferTransform = 1u << 4,
-    BlendMode       = 1u << 5,
-    Alpha           = 1u << 6,
-    LayerMetadata   = 1u << 7,
-    VisibleRegion   = 1u << 8,
-    Dataspace       = 1u << 9,
-    PixelFormat     = 1u << 10,
-    ColorTransform  = 1u << 11,
-    SurfaceDamage   = 1u << 12,
-    CompositionType = 1u << 13,
-    SidebandStream  = 1u << 14,
-    Buffer          = 1u << 15,
-    SolidColor      = 1u << 16,
+    None                  = 0u,
+    Id                    = 1u << 0,
+    Name                  = 1u << 1,
+    DisplayFrame          = 1u << 2,
+    SourceCrop            = 1u << 3,
+    BufferTransform       = 1u << 4,
+    BlendMode             = 1u << 5,
+    Alpha                 = 1u << 6,
+    LayerMetadata         = 1u << 7,
+    VisibleRegion         = 1u << 8,
+    Dataspace             = 1u << 9,
+    PixelFormat           = 1u << 10,
+    ColorTransform        = 1u << 11,
+    SurfaceDamage         = 1u << 12,
+    CompositionType       = 1u << 13,
+    SidebandStream        = 1u << 14,
+    Buffer                = 1u << 15,
+    SolidColor            = 1u << 16,
+    BackgroundBlurRadius  = 1u << 17,
+    BlurRegions           = 1u << 18,
 };
 // clang-format on
 
@@ -225,6 +228,9 @@
     const std::string& getName() const { return mName.get(); }
     Rect getDisplayFrame() const { return mDisplayFrame.get(); }
     const Region& getVisibleRegion() const { return mVisibleRegion.get(); }
+    bool hasBlurBehind() const {
+        return mBackgroundBlurRadius.get() > 0 || !mBlurRegions.get().empty();
+    }
     hardware::graphics::composer::hal::Composition getCompositionType() const {
         return mCompositionType.get();
     }
@@ -398,7 +404,45 @@
                             return std::vector<std::string>{stream.str()};
                         }};
 
-    static const constexpr size_t kNumNonUniqueFields = 14;
+    OutputLayerState<int32_t, LayerStateField::BackgroundBlurRadius> mBackgroundBlurRadius{
+            [](auto layer) {
+                return layer->getLayerFE().getCompositionState()->backgroundBlurRadius;
+            }};
+
+    using BlurRegionsState =
+            OutputLayerState<std::vector<BlurRegion>, LayerStateField::BlurRegions>;
+    BlurRegionsState mBlurRegions{[](auto layer) {
+                                      return layer->getLayerFE().getCompositionState()->blurRegions;
+                                  },
+                                  [](const std::vector<BlurRegion>& regions) {
+                                      std::vector<std::string> result;
+                                      for (const auto region : regions) {
+                                          std::string str;
+                                          base::StringAppendF(&str,
+                                                              "{radius=%du, cornerRadii=[%f, %f, "
+                                                              "%f, %f], alpha=%f, rect=[%d, "
+                                                              "%d, %d, %d]",
+                                                              region.blurRadius,
+                                                              region.cornerRadiusTL,
+                                                              region.cornerRadiusTR,
+                                                              region.cornerRadiusBL,
+                                                              region.cornerRadiusBR, region.alpha,
+                                                              region.left, region.top, region.right,
+                                                              region.bottom);
+                                          result.push_back(str);
+                                      }
+                                      return result;
+                                  },
+                                  BlurRegionsState::getDefaultEquals(),
+                                  [](const std::vector<BlurRegion>& regions) {
+                                      size_t hash = 0;
+                                      for (const auto& region : regions) {
+                                          android::hashCombineSingle(hash, region);
+                                      }
+                                      return hash;
+                                  }};
+
+    static const constexpr size_t kNumNonUniqueFields = 16;
 
     std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() {
         std::array<const StateInterface*, kNumNonUniqueFields> constFields =
@@ -413,10 +457,10 @@
 
     std::array<const StateInterface*, kNumNonUniqueFields> getNonUniqueFields() const {
         return {
-                &mDisplayFrame, &mSourceCrop,     &mBufferTransform, &mBlendMode,
-                &mAlpha,        &mLayerMetadata,  &mVisibleRegion,   &mOutputDataspace,
-                &mPixelFormat,  &mColorTransform, &mCompositionType, &mSidebandStream,
-                &mBuffer,       &mSolidColor,
+                &mDisplayFrame, &mSourceCrop,     &mBufferTransform,      &mBlendMode,
+                &mAlpha,        &mLayerMetadata,  &mVisibleRegion,        &mOutputDataspace,
+                &mPixelFormat,  &mColorTransform, &mCompositionType,      &mSidebandStream,
+                &mBuffer,       &mSolidColor,     &mBackgroundBlurRadius, &mBlurRegions,
         };
     }
 };
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index 67854cf..63085cc 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -280,6 +280,11 @@
     return layerFE.hasRoundedCorners();
 }
 
+bool CachedSet::hasBlurBehind() const {
+    return std::any_of(mLayers.cbegin(), mLayers.cend(),
+                       [](const Layer& layer) { return layer.getState()->hasBlurBehind(); });
+}
+
 namespace {
 bool contains(const Rect& outer, const Rect& inner) {
     return outer.left <= inner.left && outer.right >= inner.right && outer.top <= inner.top &&
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 4453a99..294ec4b 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -327,85 +327,94 @@
     return true;
 }
 
-void Flattener::buildCachedSets(time_point now) {
-    struct Run {
-        Run(std::vector<CachedSet>::const_iterator start, size_t length)
-              : start(start), length(length) {}
-
-        std::vector<CachedSet>::const_iterator start;
-        size_t length;
-    };
-
-    if (mLayers.empty()) {
-        ALOGV("[%s] No layers found, returning", __func__);
-        return;
-    }
-
+std::vector<Flattener::Run> Flattener::findCandidateRuns(time_point now) const {
     std::vector<Run> runs;
     bool isPartOfRun = false;
-
-    // Keep track of the layer that follows a run. It's possible that we will
-    // render it with a hole-punch.
-    const CachedSet* holePunchLayer = nullptr;
+    Run::Builder builder;
+    bool firstLayer = true;
+    bool runHasFirstLayer = false;
 
     for (auto currentSet = mLayers.cbegin(); currentSet != mLayers.cend(); ++currentSet) {
-        if (now - currentSet->getLastUpdate() > kActiveLayerTimeout) {
-            // Layer is inactive
+        const bool layerIsInactive = now - currentSet->getLastUpdate() > kActiveLayerTimeout;
+        const bool layerHasBlur = currentSet->hasBlurBehind();
+        if (layerIsInactive && (firstLayer || runHasFirstLayer || !layerHasBlur)) {
             if (isPartOfRun) {
-                runs.back().length += currentSet->getLayerCount();
+                builder.append(currentSet->getLayerCount());
             } else {
                 // Runs can't start with a non-buffer layer
                 if (currentSet->getFirstLayer().getBuffer() == nullptr) {
                     ALOGV("[%s] Skipping initial non-buffer layer", __func__);
                 } else {
-                    runs.emplace_back(currentSet, currentSet->getLayerCount());
+                    builder.init(currentSet);
+                    if (firstLayer) {
+                        runHasFirstLayer = true;
+                    }
                     isPartOfRun = true;
                 }
             }
         } else if (isPartOfRun) {
-            // Runs must be at least 2 sets long or there's nothing to combine
-            if (runs.back().start->getLayerCount() == runs.back().length) {
-                runs.pop_back();
-            } else {
-                // The prior run contained at least two sets. Currently, we'll
-                // only possibly merge a single run, so only keep track of a
-                // holePunchLayer if this is the first run.
-                if (runs.size() == 1) {
-                    holePunchLayer = &(*currentSet);
-                }
-
-                // TODO(b/185114532: Break out of the loop? We may find more runs, but we
-                // won't do anything with them.
+            builder.setHolePunchCandidate(&(*currentSet));
+            if (auto run = builder.validateAndBuild(); run) {
+                runs.push_back(*run);
             }
 
+            runHasFirstLayer = false;
+            builder.reset();
             isPartOfRun = false;
         }
+
+        firstLayer = false;
     }
 
-    // Check for at least 2 sets one more time in case the set includes the last layer
-    if (isPartOfRun && runs.back().start->getLayerCount() == runs.back().length) {
-        runs.pop_back();
+    // If we're in the middle of a run at the end, we still need to validate and build it.
+    if (isPartOfRun) {
+        if (auto run = builder.validateAndBuild(); run) {
+            runs.push_back(*run);
+        }
     }
 
     ALOGV("[%s] Found %zu candidate runs", __func__, runs.size());
 
+    return runs;
+}
+
+std::optional<Flattener::Run> Flattener::findBestRun(std::vector<Flattener::Run>& runs) const {
     if (runs.empty()) {
+        return std::nullopt;
+    }
+
+    // TODO (b/181192467): Choose the best run, instead of just the first.
+    return runs[0];
+}
+
+void Flattener::buildCachedSets(time_point now) {
+    if (mLayers.empty()) {
+        ALOGV("[%s] No layers found, returning", __func__);
         return;
     }
 
-    mNewCachedSet.emplace(*runs[0].start);
+    std::vector<Run> runs = findCandidateRuns(now);
+
+    std::optional<Run> bestRun = findBestRun(runs);
+
+    if (!bestRun) {
+        return;
+    }
+
+    mNewCachedSet.emplace(*bestRun->getStart());
     mNewCachedSet->setLastUpdate(now);
-    auto currentSet = runs[0].start;
-    while (mNewCachedSet->getLayerCount() < runs[0].length) {
+    auto currentSet = bestRun->getStart();
+    while (mNewCachedSet->getLayerCount() < bestRun->getLayerLength()) {
         ++currentSet;
         mNewCachedSet->append(*currentSet);
     }
 
-    if (mEnableHolePunch && holePunchLayer && holePunchLayer->requiresHolePunch()) {
+    if (mEnableHolePunch && bestRun->getHolePunchCandidate() &&
+        bestRun->getHolePunchCandidate()->requiresHolePunch()) {
         // Add the pip layer to mNewCachedSet, but in a special way - it should
         // replace the buffer with a clear round rect.
-        mNewCachedSet->addHolePunchLayerIfFeasible(*holePunchLayer,
-                                                   runs[0].start == mLayers.cbegin());
+        mNewCachedSet->addHolePunchLayerIfFeasible(*bestRun->getHolePunchCandidate(),
+                                                   bestRun->getStart() == mLayers.cbegin());
     }
 
     // TODO(b/181192467): Actually compute new LayerState vector and corresponding hash for each run
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index a39331c..488f64d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -567,5 +567,30 @@
     }
 }
 
+TEST_F(CachedSetTest, hasBlurBehind) {
+    mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1;
+    mTestLayers[1]->layerState->update(&mTestLayers[1]->outputLayer);
+    mTestLayers[2]->layerFECompositionState.blurRegions.push_back(
+            BlurRegion{1, 0, 0, 0, 0, 0, 0, 0, 0, 0});
+    mTestLayers[2]->layerState->update(&mTestLayers[2]->outputLayer);
+
+    CachedSet::Layer& layer1 = *mTestLayers[0]->cachedSetLayer.get();
+    CachedSet::Layer& layer2 = *mTestLayers[1]->cachedSetLayer.get();
+    CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+
+    CachedSet cachedSet1(layer1);
+    CachedSet cachedSet2(layer2);
+    CachedSet cachedSet3(layer3);
+
+    // Cached set 4 will consist of layers 1 and 2, which will contain a blur behind
+    CachedSet cachedSet4(layer1);
+    cachedSet4.addLayer(layer2.getState(), kStartTime);
+
+    EXPECT_FALSE(cachedSet1.hasBlurBehind());
+    EXPECT_TRUE(cachedSet2.hasBlurBehind());
+    EXPECT_TRUE(cachedSet3.hasBlurBehind());
+    EXPECT_TRUE(cachedSet4.hasBlurBehind());
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 25fab49..42096f3 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -642,5 +642,149 @@
     EXPECT_EQ(&mTestLayers[2]->outputLayer, peekThroughLayer1);
     EXPECT_EQ(peekThroughLayer1, peekThroughLayer2);
 }
+
+TEST_F(FlattenerTest, flattenLayers_flattensBlurBehindRunIfFirstRun) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+
+    auto& layerState2 = mTestLayers[1]->layerState;
+    mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1;
+    layerState2->update(&mTestLayers[1]->outputLayer);
+
+    auto& layerState3 = mTestLayers[2]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+    const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+            layerState3.get(),
+    };
+
+    initializeFlattener(layers);
+
+    // Mark the first two layers inactive, which contain the blur behind
+    mTime += 200ms;
+    layerState3->resetFramesSinceBufferUpdate();
+
+    // layers would be flattened but the buffer would not be overridden
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+    for (const auto layer : layers) {
+        EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
+    }
+
+    // the new flattened layer is replaced
+    initializeOverrideBuffer(layers);
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    EXPECT_NE(nullptr, overrideBuffer1);
+    EXPECT_EQ(overrideBuffer1, overrideBuffer2);
+    EXPECT_EQ(nullptr, overrideBuffer3);
+}
+
+TEST_F(FlattenerTest, flattenLayers_doesNotFlattenBlurBehindRun) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+
+    auto& layerState2 = mTestLayers[1]->layerState;
+    mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1;
+    layerState2->update(&mTestLayers[1]->outputLayer);
+
+    auto& layerState3 = mTestLayers[2]->layerState;
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerState2.get(),
+            layerState3.get(),
+    };
+
+    initializeFlattener(layers);
+
+    // Mark the last two layers inactive, which contains the blur layer, but does not contain the
+    // first layer
+    mTime += 200ms;
+    layerState1->resetFramesSinceBufferUpdate();
+
+    // layers would be flattened but the buffer would not be overridden
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillRepeatedly(Return(NO_ERROR));
+
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+    for (const auto layer : layers) {
+        EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
+    }
+
+    // nothing is flattened because the last two frames cannot be cached due to containing a blur
+    // layer
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    for (const auto layer : layers) {
+        EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
+    }
+}
+
+TEST_F(FlattenerTest, flattenLayers_flattenSkipsLayerWithBlurBehind) {
+    auto& layerState1 = mTestLayers[0]->layerState;
+
+    auto& layerStateWithBlurBehind = mTestLayers[1]->layerState;
+    mTestLayers[1]->layerFECompositionState.backgroundBlurRadius = 1;
+    layerStateWithBlurBehind->update(&mTestLayers[1]->outputLayer);
+
+    auto& layerState3 = mTestLayers[2]->layerState;
+    auto& layerState4 = mTestLayers[3]->layerState;
+    const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+    const auto& blurOverrideBuffer =
+            layerStateWithBlurBehind->getOutputLayer()->getState().overrideInfo.buffer;
+    const auto& overrideBuffer3 = layerState3->getOutputLayer()->getState().overrideInfo.buffer;
+    const auto& overrideBuffer4 = layerState4->getOutputLayer()->getState().overrideInfo.buffer;
+
+    const std::vector<const LayerState*> layers = {
+            layerState1.get(),
+            layerStateWithBlurBehind.get(),
+            layerState3.get(),
+            layerState4.get(),
+    };
+
+    initializeFlattener(layers);
+
+    // Mark the last three layers inactive, which contains the blur layer, but does not contain the
+    // first layer
+    mTime += 200ms;
+    layerState1->resetFramesSinceBufferUpdate();
+
+    // layers would be flattened but the buffer would not be overridden
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+
+    initializeOverrideBuffer(layers);
+    EXPECT_EQ(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+    for (const auto layer : layers) {
+        EXPECT_EQ(nullptr, layer->getOutputLayer()->getState().overrideInfo.buffer);
+    }
+
+    // the new flattened layer is replaced
+    initializeOverrideBuffer(layers);
+    EXPECT_NE(getNonBufferHash(layers),
+              mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+    mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+    EXPECT_EQ(nullptr, overrideBuffer1);
+    EXPECT_EQ(nullptr, blurOverrideBuffer);
+    EXPECT_NE(nullptr, overrideBuffer3);
+    EXPECT_EQ(overrideBuffer3, overrideBuffer4);
+}
+
 } // namespace
 } // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 948c850..a09ce14 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
@@ -58,6 +58,10 @@
 const GenericLayerMetadataEntry sMetadataValueTwo = GenericLayerMetadataEntry{
         .value = std::vector<uint8_t>({1, 3}),
 };
+const constexpr int32_t sBgBlurRadiusOne = 3;
+const constexpr int32_t sBgBlurRadiusTwo = 4;
+const BlurRegion sBlurRegionOne = BlurRegion{1, 2.f, 3.f, 4.f, 5.f, 6.f, 7, 8, 9, 10};
+const BlurRegion sBlurRegionTwo = BlurRegion{2, 3.f, 4.f, 5.f, 6.f, 7.f, 8, 9, 10, 11};
 
 struct LayerStateTest : public testing::Test {
     LayerStateTest() {
@@ -830,6 +834,114 @@
     EXPECT_TRUE(otherLayerState->compare(*mLayerState));
 }
 
+TEST_F(LayerStateTest, updateBackgroundBlur) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.backgroundBlurRadius = sBgBlurRadiusOne;
+    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+
+    mock::OutputLayer newOutputLayer;
+    mock::LayerFE newLayerFE;
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.backgroundBlurRadius = sBgBlurRadiusTwo;
+    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BackgroundBlurRadius), updates);
+}
+
+TEST_F(LayerStateTest, compareBackgroundBlur) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.backgroundBlurRadius = sBgBlurRadiusOne;
+    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    mock::OutputLayer newOutputLayer;
+    mock::LayerFE newLayerFE;
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.backgroundBlurRadius = sBgBlurRadiusTwo;
+    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+    auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
+
+    verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState,
+                                   LayerStateField::BackgroundBlurRadius);
+
+    EXPECT_TRUE(mLayerState->compare(*otherLayerState));
+    EXPECT_TRUE(otherLayerState->compare(*mLayerState));
+}
+
+TEST_F(LayerStateTest, updateBlurRegions) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.blurRegions.push_back(sBlurRegionOne);
+    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+
+    mock::OutputLayer newOutputLayer;
+    mock::LayerFE newLayerFE;
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.blurRegions.push_back(sBlurRegionTwo);
+    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+    Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+    EXPECT_EQ(Flags<LayerStateField>(LayerStateField::BlurRegions), updates);
+}
+
+TEST_F(LayerStateTest, compareBlurRegions) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.blurRegions.push_back(sBlurRegionOne);
+    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    mock::OutputLayer newOutputLayer;
+    mock::LayerFE newLayerFE;
+    LayerFECompositionState layerFECompositionStateTwo;
+    layerFECompositionStateTwo.blurRegions.push_back(sBlurRegionTwo);
+    setupMocksForLayer(newOutputLayer, newLayerFE, outputLayerCompositionState,
+                       layerFECompositionStateTwo);
+    auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
+
+    verifyNonUniqueDifferingFields(*mLayerState, *otherLayerState, LayerStateField::BlurRegions);
+
+    EXPECT_TRUE(mLayerState->compare(*otherLayerState));
+    EXPECT_TRUE(otherLayerState->compare(*mLayerState));
+}
+
+TEST_F(LayerStateTest, hasBlurBehind_noBlur_returnsFalse) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    EXPECT_FALSE(mLayerState->hasBlurBehind());
+}
+
+TEST_F(LayerStateTest, hasBlurBehind_withBackgroundBlur_returnsTrue) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.backgroundBlurRadius = sBgBlurRadiusOne;
+    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    EXPECT_TRUE(mLayerState->hasBlurBehind());
+}
+
+TEST_F(LayerStateTest, hasBlurBehind_withBlurRegion_returnsTrue) {
+    OutputLayerCompositionState outputLayerCompositionState;
+    LayerFECompositionState layerFECompositionState;
+    layerFECompositionState.blurRegions.push_back(sBlurRegionOne);
+    setupMocksForLayer(mOutputLayer, mLayerFE, outputLayerCompositionState,
+                       layerFECompositionState);
+    mLayerState = std::make_unique<LayerState>(&mOutputLayer);
+    EXPECT_TRUE(mLayerState->hasBlurBehind());
+}
+
 TEST_F(LayerStateTest, dumpDoesNotCrash) {
     OutputLayerCompositionState outputLayerCompositionState;
     LayerFECompositionState layerFECompositionState;
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index ba03c89..34cc389 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -272,7 +272,7 @@
 
     // Used for sanitizing the heuristic data. If two frames are less than
     // this period apart from each other they'll be considered as duplicates.
-    static constexpr nsecs_t kMinPeriodBetweenFrames = Fps(120.f).getPeriodNsecs();
+    static constexpr nsecs_t kMinPeriodBetweenFrames = Fps(240.f).getPeriodNsecs();
     // Used for sanitizing the heuristic data. If two frames are more than
     // this period apart from each other, the interval between them won't be
     // taken into account when calculating average frame rate.
diff --git a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
index 2828d61..43d957c 100644
--- a/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp
@@ -273,6 +273,198 @@
     }
 }
 
+TEST_P(LayerTypeAndRenderTypeTransactionTest, SetCornerRadiusBufferRotationTransform) {
+    sp<SurfaceControl> layer;
+    sp<SurfaceControl> parent;
+    ASSERT_NO_FATAL_FAILURE(
+            parent = LayerTransactionTest::createLayer("parent", 0, 0,
+                                                       ISurfaceComposerClient::eFXSurfaceEffect));
+
+    const uint32_t bufferWidth = 1500;
+    const uint32_t bufferHeight = 300;
+
+    const uint32_t layerWidth = 300;
+    const uint32_t layerHeight = 1500;
+
+    const uint32_t testArea = 4;
+    const float cornerRadius = 120.0f;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", bufferWidth, bufferHeight));
+    ASSERT_NO_FATAL_FAILURE(fillLayerColor(layer, Color::RED, bufferWidth, bufferHeight));
+
+    Transaction()
+            .reparent(layer, parent)
+            .setColor(parent, half3(0, 1, 0))
+            .setCrop(parent, Rect(0, 0, layerWidth, layerHeight))
+            .setCornerRadius(parent, cornerRadius)
+
+            .setTransform(layer, ui::Transform::ROT_90)
+            .setDestinationFrame(layer, Rect(0, 0, layerWidth, layerHeight))
+            .apply();
+    {
+        auto shot = getScreenCapture();
+        // Corners are transparent
+        // top-left
+        shot->expectColor(Rect(0, 0, testArea, testArea), Color::BLACK);
+        // top-right
+        shot->expectColor(Rect(layerWidth - testArea, 0, layerWidth, testArea), Color::BLACK);
+        // bottom-left
+        shot->expectColor(Rect(0, layerHeight - testArea, testArea, layerHeight), Color::BLACK);
+        // bottom-right
+        shot->expectColor(Rect(layerWidth - testArea, layerHeight - testArea, layerWidth,
+                               layerHeight),
+                          Color::BLACK);
+
+        // Area after corner radius is solid
+        // top-left to top-right under the corner
+        shot->expectColor(Rect(0, cornerRadius, layerWidth, cornerRadius + testArea), Color::RED);
+        // bottom-left to bottom-right above the corner
+        shot->expectColor(Rect(0, layerHeight - cornerRadius - testArea, layerWidth,
+                               layerHeight - cornerRadius),
+                          Color::RED);
+        // left side after the corner
+        shot->expectColor(Rect(cornerRadius, 0, cornerRadius + testArea, layerHeight), Color::RED);
+        // right side before the corner
+        shot->expectColor(Rect(layerWidth - cornerRadius - testArea, 0, layerWidth - cornerRadius,
+                               layerHeight),
+                          Color::RED);
+    }
+}
+
+TEST_P(LayerTypeAndRenderTypeTransactionTest, SetCornerRadiusBufferCropTransform) {
+    sp<SurfaceControl> layer;
+    sp<SurfaceControl> parent;
+    ASSERT_NO_FATAL_FAILURE(
+            parent = LayerTransactionTest::createLayer("parent", 0, 0,
+                                                       ISurfaceComposerClient::eFXSurfaceEffect));
+
+    const uint32_t bufferWidth = 150 * 2;
+    const uint32_t bufferHeight = 750 * 2;
+
+    const Rect bufferCrop(0, 0, 150, 750);
+
+    const uint32_t layerWidth = 300;
+    const uint32_t layerHeight = 1500;
+
+    const uint32_t testArea = 4;
+    const float cornerRadius = 120.0f;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", bufferWidth, bufferHeight));
+    ASSERT_NO_FATAL_FAILURE(fillLayerQuadrant(layer, bufferWidth, bufferHeight, Color::RED,
+                                              Color::BLACK, Color::GREEN, Color::BLUE));
+
+    Transaction()
+            .reparent(layer, parent)
+            .setColor(parent, half3(0, 1, 0))
+            .setCrop(parent, Rect(0, 0, layerWidth, layerHeight))
+            .setCornerRadius(parent, cornerRadius)
+
+            .setBufferCrop(layer, bufferCrop)
+            .setDestinationFrame(layer, Rect(0, 0, layerWidth, layerHeight))
+            .apply();
+    {
+        auto shot = getScreenCapture();
+        // Corners are transparent
+        // top-left
+        shot->expectColor(Rect(0, 0, testArea, testArea), Color::BLACK);
+        // top-right
+        shot->expectColor(Rect(layerWidth - testArea, 0, layerWidth, testArea), Color::BLACK);
+        // bottom-left
+        shot->expectColor(Rect(0, layerHeight - testArea, testArea, layerHeight), Color::BLACK);
+        // bottom-right
+        shot->expectColor(Rect(layerWidth - testArea, layerHeight - testArea, layerWidth,
+                               layerHeight),
+                          Color::BLACK);
+
+        // Area after corner radius is solid
+        // since the buffer is scaled, there will blending so adjust some of the bounds when
+        // checking.
+        float adjustedCornerRadius = cornerRadius + 15;
+        float adjustedLayerHeight = layerHeight - 15;
+        float adjustedLayerWidth = layerWidth - 15;
+
+        // top-left to top-right under the corner
+        shot->expectColor(Rect(15, adjustedCornerRadius, adjustedLayerWidth,
+                               adjustedCornerRadius + testArea),
+                          Color::RED);
+        // bottom-left to bottom-right above the corner
+        shot->expectColor(Rect(15, adjustedLayerHeight - adjustedCornerRadius - testArea,
+                               adjustedLayerWidth, adjustedLayerHeight - adjustedCornerRadius),
+                          Color::RED);
+        // left side after the corner
+        shot->expectColor(Rect(adjustedCornerRadius, 15, adjustedCornerRadius + testArea,
+                               adjustedLayerHeight),
+                          Color::RED);
+        // right side before the corner
+        shot->expectColor(Rect(adjustedLayerWidth - adjustedCornerRadius - testArea, 15,
+                               adjustedLayerWidth - adjustedCornerRadius, adjustedLayerHeight),
+                          Color::RED);
+    }
+}
+
+TEST_P(LayerTypeAndRenderTypeTransactionTest, SetCornerRadiusChildBufferRotationTransform) {
+    sp<SurfaceControl> layer;
+    sp<SurfaceControl> parent;
+    ASSERT_NO_FATAL_FAILURE(
+            parent = LayerTransactionTest::createLayer("parent", 0, 0,
+                                                       ISurfaceComposerClient::eFXSurfaceEffect));
+
+    const uint32_t bufferWidth = 1500;
+    const uint32_t bufferHeight = 300;
+
+    const uint32_t layerWidth = 300;
+    const uint32_t layerHeight = 1500;
+
+    const uint32_t testArea = 4;
+    const float cornerRadius = 120.0f;
+    ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", bufferWidth, bufferHeight));
+    ASSERT_NO_FATAL_FAILURE(fillLayerColor(layer, Color::BLUE, bufferWidth, bufferHeight));
+
+    sp<SurfaceControl> child;
+    ASSERT_NO_FATAL_FAILURE(child = createLayer("child", bufferWidth, bufferHeight));
+    ASSERT_NO_FATAL_FAILURE(fillLayerColor(child, Color::RED, bufferWidth, bufferHeight));
+
+    Transaction()
+            .reparent(layer, parent)
+            .reparent(child, layer)
+            .setColor(parent, half3(0, 1, 0))
+            .setCrop(parent, Rect(0, 0, layerWidth, layerHeight))
+            .setCornerRadius(parent, cornerRadius) /* */
+
+            .setTransform(layer, ui::Transform::ROT_90)
+            .setDestinationFrame(layer, Rect(0, 0, layerWidth, layerHeight))
+
+            .setTransform(child, ui::Transform::ROT_90)
+            .setDestinationFrame(child, Rect(0, 0, layerWidth, layerHeight))
+            .apply();
+    {
+        auto shot = getScreenCapture();
+        // Corners are transparent
+        // top-left
+        shot->expectColor(Rect(0, 0, testArea, testArea), Color::BLACK);
+        // top-right
+        shot->expectColor(Rect(layerWidth - testArea, 0, layerWidth, testArea), Color::BLACK);
+        // bottom-left
+        shot->expectColor(Rect(0, layerHeight - testArea, testArea, layerHeight), Color::BLACK);
+        // bottom-right
+        shot->expectColor(Rect(layerWidth - testArea, layerHeight - testArea, layerWidth,
+                               layerHeight),
+                          Color::BLACK);
+
+        // Area after corner radius is solid
+        // top-left to top-right under the corner
+        shot->expectColor(Rect(0, cornerRadius, layerWidth, cornerRadius + testArea), Color::RED);
+        // bottom-left to bottom-right above the corner
+        shot->expectColor(Rect(0, layerHeight - cornerRadius - testArea, layerWidth,
+                               layerHeight - cornerRadius),
+                          Color::RED);
+        // left side after the corner
+        shot->expectColor(Rect(cornerRadius, 0, cornerRadius + testArea, layerHeight), Color::RED);
+        // right side before the corner
+        shot->expectColor(Rect(layerWidth - cornerRadius - testArea, 0, layerWidth - cornerRadius,
+                               layerHeight),
+                          Color::RED);
+    }
+}
+
 TEST_P(LayerTypeAndRenderTypeTransactionTest, SetBackgroundBlurRadiusSimple) {
     if (!deviceSupportsBlurs()) GTEST_SKIP();
     if (!deviceUsesSkiaRenderEngine()) GTEST_SKIP();
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index 325fb8f..d6ce5e2 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -126,7 +126,7 @@
     std::deque<FrameTimeData> frameTimes;
     constexpr auto kExpectedFps = Fps(50.0f);
     constexpr auto kExpectedPeriod = kExpectedFps.getPeriodNsecs();
-    constexpr auto kSmallPeriod = Fps(150.0f).getPeriodNsecs();
+    constexpr auto kSmallPeriod = Fps(250.0f).getPeriodNsecs();
     constexpr int kNumIterations = 10;
     for (int i = 1; i <= kNumIterations; i++) {
         frameTimes.push_back(FrameTimeData{.presentTime = kExpectedPeriod * i,