Merge "SF: Start restructuring display creation" into rvc-dev
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index cf75bba..544e26c 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -571,81 +571,6 @@
     return true;
 }
 
-// Poke all the binder-enabled processes in the system to get them to re-read
-// their system properties.
-static bool pokeBinderServices()
-{
-    sp<IServiceManager> sm = defaultServiceManager();
-    Vector<String16> services = sm->listServices();
-    for (size_t i = 0; i < services.size(); i++) {
-        sp<IBinder> obj = sm->checkService(services[i]);
-        if (obj != nullptr) {
-            Parcel data;
-            if (obj->transact(IBinder::SYSPROPS_TRANSACTION, data,
-                    nullptr, 0) != OK) {
-                if (false) {
-                    // XXX: For some reason this fails on tablets trying to
-                    // poke the "phone" service.  It's not clear whether some
-                    // are expected to fail.
-                    String8 svc(services[i]);
-                    fprintf(stderr, "error poking binder service %s\n",
-                        svc.string());
-                    return false;
-                }
-            }
-        }
-    }
-    return true;
-}
-
-// Poke all the HAL processes in the system to get them to re-read
-// their system properties.
-static void pokeHalServices()
-{
-    using ::android::hidl::base::V1_0::IBase;
-    using ::android::hidl::manager::V1_0::IServiceManager;
-    using ::android::hardware::hidl_string;
-    using ::android::hardware::Return;
-
-    sp<IServiceManager> sm = ::android::hardware::defaultServiceManager();
-
-    if (sm == nullptr) {
-        fprintf(stderr, "failed to get IServiceManager to poke hal services\n");
-        return;
-    }
-
-    auto listRet = sm->list([&](const auto &interfaces) {
-        for (size_t i = 0; i < interfaces.size(); i++) {
-            string fqInstanceName = interfaces[i];
-            string::size_type n = fqInstanceName.find('/');
-            if (n == std::string::npos || interfaces[i].size() == n+1)
-                continue;
-            hidl_string fqInterfaceName = fqInstanceName.substr(0, n);
-            hidl_string instanceName = fqInstanceName.substr(n+1, std::string::npos);
-            Return<sp<IBase>> interfaceRet = sm->get(fqInterfaceName, instanceName);
-            if (!interfaceRet.isOk()) {
-                // ignore
-                continue;
-            }
-
-            sp<IBase> interface = interfaceRet;
-            if (interface == nullptr) {
-                // ignore
-                continue;
-            }
-
-            auto notifyRet = interface->notifySyspropsChanged();
-            if (!notifyRet.isOk()) {
-                // ignore
-            }
-        }
-    });
-    if (!listRet.isOk()) {
-        // TODO(b/34242478) fix this when we determine the correct ACL
-        //fprintf(stderr, "failed to list services: %s\n", listRet.description().c_str());
-    }
-}
-
 // Set the trace tags that userland tracing uses, and poke the running
 // processes to pick up the new value.
 static bool setTagsProperty(uint64_t tags)
@@ -876,10 +801,6 @@
     }
     ok &= setAppCmdlineProperty(&packageList[0]);
     ok &= setTagsProperty(tags);
-#if !ATRACE_SHMEM
-    ok &= pokeBinderServices();
-    pokeHalServices();
-#endif
     if (g_tracePdx) {
         ok &= ServiceUtility::PokeServices();
     }
@@ -891,8 +812,6 @@
 {
     setTagsProperty(0);
     clearAppProperties();
-    pokeBinderServices();
-    pokeHalServices();
 
     if (g_tracePdx) {
         ServiceUtility::PokeServices();
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 3d77059..1075f16 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -59,8 +59,6 @@
         "gl/Program.cpp",
         "gl/ProgramCache.cpp",
         "gl/filters/BlurFilter.cpp",
-        "gl/filters/KawaseBlurFilter.cpp",
-        "gl/filters/GaussianBlurFilter.cpp",
         "gl/filters/GenericProgram.cpp",
     ],
 }
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
index e11b59f..daf7d72 100644
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ b/libs/renderengine/gl/GLESRenderEngine.cpp
@@ -50,8 +50,6 @@
 #include "Program.h"
 #include "ProgramCache.h"
 #include "filters/BlurFilter.h"
-#include "filters/GaussianBlurFilter.h"
-#include "filters/KawaseBlurFilter.h"
 
 extern "C" EGLAPI const char* eglQueryStringImplementationANDROID(EGLDisplay dpy, EGLint name);
 
@@ -430,13 +428,7 @@
     }
 
     if (args.supportsBackgroundBlur) {
-        char isGaussian[PROPERTY_VALUE_MAX];
-        property_get("debug.sf.gaussianBlur", isGaussian, "0");
-        if (atoi(isGaussian)) {
-            mBlurFilter = new GaussianBlurFilter(*this);
-        } else {
-            mBlurFilter = new KawaseBlurFilter(*this);
-        }
+        mBlurFilter = new BlurFilter(*this);
         checkErrors("BlurFilter creation");
     }
 
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
index ebf78fe..4cd0b3d 100644
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ b/libs/renderengine/gl/GLESRenderEngine.h
@@ -261,8 +261,6 @@
     friend class ImageManager;
     friend class GLFramebuffer;
     friend class BlurFilter;
-    friend class GaussianBlurFilter;
-    friend class KawaseBlurFilter;
     friend class GenericProgram;
     std::unique_ptr<FlushTracer> mFlushTracer;
     std::unique_ptr<ImageManager> mImageManager = std::make_unique<ImageManager>(this);
diff --git a/libs/renderengine/gl/filters/BlurFilter.cpp b/libs/renderengine/gl/filters/BlurFilter.cpp
index eb66c8f..e704907 100644
--- a/libs/renderengine/gl/filters/BlurFilter.cpp
+++ b/libs/renderengine/gl/filters/BlurFilter.cpp
@@ -31,13 +31,24 @@
 namespace gl {
 
 BlurFilter::BlurFilter(GLESRenderEngine& engine)
-      : mEngine(engine), mCompositionFbo(engine), mBlurredFbo(engine), mMixProgram(engine) {
+      : mEngine(engine),
+        mCompositionFbo(engine),
+        mPingFbo(engine),
+        mPongFbo(engine),
+        mMixProgram(engine),
+        mBlurProgram(engine) {
     mMixProgram.compile(getVertexShader(), getMixFragShader());
     mMPosLoc = mMixProgram.getAttributeLocation("aPosition");
     mMUvLoc = mMixProgram.getAttributeLocation("aUV");
     mMTextureLoc = mMixProgram.getUniformLocation("uTexture");
     mMCompositionTextureLoc = mMixProgram.getUniformLocation("uCompositionTexture");
     mMMixLoc = mMixProgram.getUniformLocation("uMix");
+
+    mBlurProgram.compile(getVertexShader(), getFragmentShader());
+    mBPosLoc = mBlurProgram.getAttributeLocation("aPosition");
+    mBUvLoc = mBlurProgram.getAttributeLocation("aUV");
+    mBTextureLoc = mBlurProgram.getUniformLocation("uTexture");
+    mBOffsetLoc = mBlurProgram.getUniformLocation("uOffset");
 }
 
 status_t BlurFilter::setAsDrawTarget(const DisplaySettings& display, uint32_t radius) {
@@ -51,14 +62,14 @@
 
         const uint32_t fboWidth = floorf(mDisplayWidth * kFboScale);
         const uint32_t fboHeight = floorf(mDisplayHeight * kFboScale);
-        mBlurredFbo.allocateBuffers(fboWidth, fboHeight);
-        allocateTextures();
+        mPingFbo.allocateBuffers(fboWidth, fboHeight);
+        mPongFbo.allocateBuffers(fboWidth, fboHeight);
         mTexturesAllocated = true;
     }
 
-    if (mBlurredFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
+    if (mPingFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
         ALOGE("Invalid blur buffer");
-        return mBlurredFbo.getStatus();
+        return mPingFbo.getStatus();
     }
     if (mCompositionFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
         ALOGE("Invalid composition buffer");
@@ -96,6 +107,61 @@
     mEngine.checkErrors("Drawing blur mesh");
 }
 
+status_t BlurFilter::prepare() {
+    ATRACE_NAME("BlurFilter::prepare");
+
+    if (mPongFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
+        ALOGE("Invalid FBO");
+        return mPongFbo.getStatus();
+    }
+    if (!mBlurProgram.isValid()) {
+        ALOGE("Invalid shader");
+        return GL_INVALID_OPERATION;
+    }
+
+    blit(mCompositionFbo, mPingFbo);
+
+    // Kawase is an approximation of Gaussian, but it behaves differently from it.
+    // A radius transformation is required for approximating them, and also to introduce
+    // non-integer steps, necessary to smoothly interpolate large radii.
+    auto radius = mRadius / 6.0f;
+
+    // Calculate how many passes we'll do, based on the radius.
+    // Too many passes will make the operation expensive.
+    auto passes = min(kMaxPasses, (uint32_t)ceil(radius));
+
+    // We'll ping pong between our textures, to accumulate the result of various offsets.
+    mBlurProgram.useProgram();
+    GLFramebuffer* read = &mPingFbo;
+    GLFramebuffer* draw = &mPongFbo;
+    float stepX = radius / (float)mCompositionFbo.getBufferWidth() / (float)passes;
+    float stepY = radius / (float)mCompositionFbo.getBufferHeight() / (float)passes;
+    glActiveTexture(GL_TEXTURE0);
+    glUniform1i(mBTextureLoc, 0);
+    for (auto i = 0; i < passes; i++) {
+        ATRACE_NAME("BlurFilter::renderPass");
+        draw->bind();
+
+        glViewport(0, 0, draw->getBufferWidth(), draw->getBufferHeight());
+        glBindTexture(GL_TEXTURE_2D, read->getTextureName());
+        glUniform2f(mBOffsetLoc, stepX * i, stepY * i);
+        mEngine.checkErrors("Setting uniforms");
+
+        drawMesh(mBUvLoc, mBPosLoc);
+
+        // Swap buffers for next iteration
+        auto tmp = draw;
+        draw = read;
+        read = tmp;
+    }
+    mLastDrawTarget = read;
+
+    // Cleanup
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+    return NO_ERROR;
+}
+
 status_t BlurFilter::render(bool multiPass) {
     ATRACE_NAME("BlurFilter::render");
 
@@ -107,9 +173,10 @@
     // be writing onto it. Let's disable the crossfade, otherwise we'd need 1 extra frame buffer,
     // as large as the screen size.
     if (mix >= 1 || multiPass) {
-        mBlurredFbo.bindAsReadBuffer();
-        glBlitFramebuffer(0, 0, mBlurredFbo.getBufferWidth(), mBlurredFbo.getBufferHeight(), 0, 0,
-                          mDisplayWidth, mDisplayHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
+        mLastDrawTarget->bindAsReadBuffer();
+        glBlitFramebuffer(0, 0, mLastDrawTarget->getBufferWidth(),
+                          mLastDrawTarget->getBufferHeight(), 0, 0, mDisplayWidth, mDisplayHeight,
+                          GL_COLOR_BUFFER_BIT, GL_LINEAR);
         glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
         return NO_ERROR;
     }
@@ -117,7 +184,7 @@
     mMixProgram.useProgram();
     glUniform1f(mMMixLoc, mix);
     glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, mBlurredFbo.getTextureName());
+    glBindTexture(GL_TEXTURE_2D, mLastDrawTarget->getTextureName());
     glUniform1i(mMTextureLoc, 0);
     glActiveTexture(GL_TEXTURE1);
     glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
@@ -145,6 +212,28 @@
     )SHADER";
 }
 
+string BlurFilter::getFragmentShader() const {
+    return R"SHADER(#version 310 es
+        precision mediump float;
+
+        uniform sampler2D uTexture;
+        uniform vec2 uOffset;
+
+        highp in vec2 vUV;
+        out vec4 fragColor;
+
+        void main() {
+            fragColor  = texture(uTexture, vUV, 0.0);
+            fragColor += texture(uTexture, vUV + vec2( uOffset.x,  uOffset.y), 0.0);
+            fragColor += texture(uTexture, vUV + vec2( uOffset.x, -uOffset.y), 0.0);
+            fragColor += texture(uTexture, vUV + vec2(-uOffset.x,  uOffset.y), 0.0);
+            fragColor += texture(uTexture, vUV + vec2(-uOffset.x, -uOffset.y), 0.0);
+
+            fragColor = vec4(fragColor.rgb * 0.2, 1.0);
+        }
+    )SHADER";
+}
+
 string BlurFilter::getMixFragShader() const {
     string shader = R"SHADER(#version 310 es
         precision mediump float;
@@ -165,6 +254,15 @@
     return shader;
 }
 
+void BlurFilter::blit(GLFramebuffer& read, GLFramebuffer& draw) const {
+    read.bindAsReadBuffer();
+    draw.bindAsDrawBuffer();
+    glBlitFramebuffer(0, 0, read.getBufferWidth(), read.getBufferHeight(), 0, 0,
+                      draw.getBufferWidth(), draw.getBufferHeight(), GL_COLOR_BUFFER_BIT,
+                      GL_LINEAR);
+    glBindFramebuffer(GL_FRAMEBUFFER, 0);
+}
+
 } // namespace gl
 } // namespace renderengine
 } // namespace android
diff --git a/libs/renderengine/gl/filters/BlurFilter.h b/libs/renderengine/gl/filters/BlurFilter.h
index 52dc8aa..eb6120b 100644
--- a/libs/renderengine/gl/filters/BlurFilter.h
+++ b/libs/renderengine/gl/filters/BlurFilter.h
@@ -27,10 +27,17 @@
 namespace renderengine {
 namespace gl {
 
+/**
+ * This is an implementation of a Kawase blur, as described in here:
+ * https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/
+ * 00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
+ */
 class BlurFilter {
 public:
     // Downsample FBO to improve performance
     static constexpr float kFboScale = 0.25f;
+    // Maximum number of render passes
+    static constexpr uint32_t kMaxPasses = 6;
     // To avoid downscaling artifacts, we interpolate the blurred fbo with the full composited
     // image, up to this radius.
     static constexpr float kMaxCrossFadeRadius = 30.0f;
@@ -40,28 +47,29 @@
 
     // Set up render targets, redirecting output to offscreen texture.
     status_t setAsDrawTarget(const DisplaySettings&, uint32_t radius);
-    // Allocate any textures needed for the filter.
-    virtual void allocateTextures() = 0;
     // Execute blur passes, rendering to offscreen texture.
-    virtual status_t prepare() = 0;
+    status_t prepare();
     // Render blur to the bound framebuffer (screen).
     status_t render(bool multiPass);
 
-protected:
+private:
     uint32_t mRadius;
     void drawMesh(GLuint uv, GLuint position);
+    void blit(GLFramebuffer& read, GLFramebuffer& draw) const;
     string getVertexShader() const;
+    string getFragmentShader() const;
+    string getMixFragShader() const;
 
     GLESRenderEngine& mEngine;
     // Frame buffer holding the composited background.
     GLFramebuffer mCompositionFbo;
-    // Frame buffer holding the blur result.
-    GLFramebuffer mBlurredFbo;
+    // Frame buffers holding the blur passes.
+    GLFramebuffer mPingFbo;
+    GLFramebuffer mPongFbo;
     uint32_t mDisplayWidth;
     uint32_t mDisplayHeight;
-
-private:
-    string getMixFragShader() const;
+    // Buffer holding the final blur pass.
+    GLFramebuffer* mLastDrawTarget;
     bool mTexturesAllocated = false;
 
     GenericProgram mMixProgram;
@@ -70,6 +78,12 @@
     GLuint mMMixLoc;
     GLuint mMTextureLoc;
     GLuint mMCompositionTextureLoc;
+
+    GenericProgram mBlurProgram;
+    GLuint mBPosLoc;
+    GLuint mBUvLoc;
+    GLuint mBTextureLoc;
+    GLuint mBOffsetLoc;
 };
 
 } // namespace gl
diff --git a/libs/renderengine/gl/filters/GaussianBlurFilter.cpp b/libs/renderengine/gl/filters/GaussianBlurFilter.cpp
deleted file mode 100644
index a0d7af8..0000000
--- a/libs/renderengine/gl/filters/GaussianBlurFilter.cpp
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright 2019 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.
- */
-
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "GaussianBlurFilter.h"
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES3/gl3.h>
-#include <GLES3/gl3ext.h>
-#include <ui/GraphicTypes.h>
-#include <cstdint>
-
-#include <utils/Trace.h>
-
-#define PI 3.14159265359
-#define THETA 0.352
-#define K 1.0 / (2.0 * THETA * THETA)
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GaussianBlurFilter::GaussianBlurFilter(GLESRenderEngine& engine)
-      : BlurFilter(engine),
-        mVerticalPassFbo(engine),
-        mVerticalProgram(engine),
-        mHorizontalProgram(engine) {
-    mVerticalProgram.compile(getVertexShader(), getFragmentShader(false));
-    mVPosLoc = mVerticalProgram.getAttributeLocation("aPosition");
-    mVUvLoc = mVerticalProgram.getAttributeLocation("aUV");
-    mVTextureLoc = mVerticalProgram.getUniformLocation("uTexture");
-    mVGaussianOffsetLoc = mVerticalProgram.getUniformLocation("uGaussianOffsets");
-    mVNumSamplesLoc = mVerticalProgram.getUniformLocation("uSamples");
-    mVGaussianWeightLoc = mVerticalProgram.getUniformLocation("uGaussianWeights");
-
-    mHorizontalProgram.compile(getVertexShader(), getFragmentShader(true));
-    mHPosLoc = mHorizontalProgram.getAttributeLocation("aPosition");
-    mHUvLoc = mHorizontalProgram.getAttributeLocation("aUV");
-    mHTextureLoc = mHorizontalProgram.getUniformLocation("uTexture");
-    mHGaussianOffsetLoc = mHorizontalProgram.getUniformLocation("uGaussianOffsets");
-    mHNumSamplesLoc = mHorizontalProgram.getUniformLocation("uSamples");
-    mHGaussianWeightLoc = mHorizontalProgram.getUniformLocation("uGaussianWeights");
-}
-
-void GaussianBlurFilter::allocateTextures() {
-    mVerticalPassFbo.allocateBuffers(mBlurredFbo.getBufferWidth(), mBlurredFbo.getBufferHeight());
-}
-
-static void calculateLinearGaussian(uint32_t samples, double dimension,
-                                    GLfloat* gaussianLinearOffsets, GLfloat* gaussianWeights,
-                                    GLfloat* gaussianLinearWeights) {
-    // The central point in the symmetric bell curve is not offset.
-    // This decision allows one less sampling in the GPU.
-    gaussianLinearWeights[0] = gaussianWeights[0];
-    gaussianLinearOffsets[0] = 0.0;
-
-    // Calculate the linear weights.
-    // This is a vector reduction where an element of the packed reduced array
-    // contains the sum of two adjacent members of the original packed array.
-    // We start preserving the element 1 of the array and then perform sum for
-    // every other (i+=2) element of the gaussianWeights array.
-    gaussianLinearWeights[1] = gaussianWeights[1];
-    const auto start = 1 + ((samples - 1) & 0x1);
-    for (size_t i = start; i < samples; i += 2) {
-        gaussianLinearWeights[start + i / 2] = gaussianWeights[i] + gaussianWeights[i + 1];
-    }
-
-    // Calculate the texture coordinates offsets as an average of the initial offsets,
-    // weighted by the Gaussian weights as described in the original article.
-    gaussianLinearOffsets[1] = 1.0 / dimension;
-    for (size_t i = start; i < samples; i += 2) {
-        GLfloat offset_1 = float(i) / dimension;
-        GLfloat offset_2 = float(i + 1) / dimension;
-        gaussianLinearOffsets[start + i / 2] =
-                (offset_1 * gaussianWeights[i] + offset_2 * gaussianWeights[i + 1]) /
-                gaussianLinearWeights[start + i / 2];
-    }
-}
-status_t GaussianBlurFilter::prepare() {
-    ATRACE_NAME("GaussianBlurFilter::prepare");
-
-    if (mVerticalPassFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
-        ALOGE("Invalid vertical FBO");
-        return mVerticalPassFbo.getStatus();
-    }
-    if (!mVerticalProgram.isValid()) {
-        ALOGE("Invalid vertical shader");
-        return GL_INVALID_OPERATION;
-    }
-    if (!mHorizontalProgram.isValid()) {
-        ALOGE("Invalid horizontal shader");
-        return GL_INVALID_OPERATION;
-    }
-
-    mCompositionFbo.bindAsReadBuffer();
-    mBlurredFbo.bindAsDrawBuffer();
-    glBlitFramebuffer(0, 0, mCompositionFbo.getBufferWidth(), mCompositionFbo.getBufferHeight(), 0,
-                      0, mBlurredFbo.getBufferWidth(), mBlurredFbo.getBufferHeight(),
-                      GL_COLOR_BUFFER_BIT, GL_LINEAR);
-    glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
-    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
-
-    // First, we'll apply the vertical pass, that receives the flattened background layers.
-    mVerticalPassFbo.bind();
-    mVerticalProgram.useProgram();
-
-    // Precompute gaussian bell curve, and send it to the shader to avoid unnecessary computations.
-    double radiusD = fmax(1.0, mRadius * kFboScale);
-    auto samples = int(fmin(radiusD, kNumSamples));
-    GLfloat gaussianWeights[kNumSamples] = {};
-
-    gaussianWeights[0] = 1.0f;
-    auto totalWeight = gaussianWeights[0];
-
-    // Gaussian weights calculation.
-    for (size_t i = 1; i < samples; i++) {
-        const double normalized = i / radiusD;
-        gaussianWeights[i] = (float)exp(-K * normalized * normalized);
-        totalWeight += 2.0 * gaussianWeights[i];
-    }
-
-    // Gaussian weights normalization to avoid work in the GPU.
-    for (size_t i = 0; i < samples; i++) {
-        gaussianWeights[i] /= totalWeight;
-    }
-
-    auto width = mVerticalPassFbo.getBufferWidth();
-    auto height = mVerticalPassFbo.getBufferHeight();
-    glViewport(0, 0, width, height);
-
-    // Allocate space for the corrected Gaussian weights and offsets.
-    // We could use less space, but let's keep the code simple.
-    GLfloat gaussianLinearWeights[kNumSamples] = {};
-    GLfloat gaussianLinearOffsets[kNumSamples] = {};
-
-    // Calculate the weights and offsets for the vertical pass.
-    // This only need to be called every time mRadius or height changes, so it could be optimized.
-    calculateLinearGaussian(samples, double(height), gaussianLinearOffsets, gaussianWeights,
-                            gaussianLinearWeights);
-    // set uniforms
-    glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, mBlurredFbo.getTextureName());
-    glUniform1i(mVTextureLoc, 0);
-    glUniform1i(mVNumSamplesLoc, 1 + (samples + 1) / 2);
-    glUniform1fv(mVGaussianWeightLoc, kNumSamples, gaussianLinearWeights);
-    glUniform1fv(mVGaussianOffsetLoc, kNumSamples, gaussianLinearOffsets);
-    mEngine.checkErrors("Setting vertical pass uniforms");
-
-    drawMesh(mVUvLoc, mVPosLoc);
-
-    // Blur vertically on a secondary pass
-    mBlurredFbo.bind();
-    mHorizontalProgram.useProgram();
-
-    // Calculate the weights and offsets for the horizontal pass.
-    // This only needs to be called every time mRadius or width change, so it could be optimized.
-    calculateLinearGaussian(samples, double(width), gaussianLinearOffsets, gaussianWeights,
-                            gaussianLinearWeights);
-    // set uniforms
-    glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, mVerticalPassFbo.getTextureName());
-    glUniform1i(mHTextureLoc, 0);
-    glUniform1i(mHNumSamplesLoc, 1 + (samples + 1) / 2);
-    glUniform1fv(mHGaussianWeightLoc, kNumSamples, gaussianLinearWeights);
-    glUniform1fv(mHGaussianOffsetLoc, kNumSamples, gaussianLinearOffsets);
-    mEngine.checkErrors("Setting horizontal pass uniforms");
-
-    drawMesh(mHUvLoc, mHPosLoc);
-
-    // reset active texture
-    mBlurredFbo.unbind();
-    glActiveTexture(GL_TEXTURE0);
-    glBindTexture(GL_TEXTURE_2D, 0);
-
-    // unbind program
-    glUseProgram(0);
-
-    return NO_ERROR;
-}
-
-string GaussianBlurFilter::getFragmentShader(bool horizontal) const {
-    stringstream shader;
-    shader << "#version 310 es\n"
-           << "#define DIRECTION " << (horizontal ? "1" : "0") << "\n"
-           << "#define NUM_SAMPLES " << 1 + (kNumSamples + 1) / 2 <<
-            R"SHADER(
-        precision mediump float;
-
-        uniform sampler2D uTexture;
-        uniform float[NUM_SAMPLES] uGaussianWeights;
-        uniform float[NUM_SAMPLES] uGaussianOffsets;
-        uniform int uSamples;
-
-        highp in vec2 vUV;
-        out vec4 fragColor;
-
-        void main() {
-            #if DIRECTION == 1
-            const vec2 direction = vec2(1.0, 0.0);
-            #else
-            const vec2 direction = vec2(0.0, 1.0);
-            #endif
-
-            // Iteration zero outside loop to avoid sampling the central point twice.
-            vec4 blurred = uGaussianWeights[0] * (texture(uTexture, vUV, 0.0));
-
-            // Iterate one side of the bell to halve the loop iterations.
-            for (int i = 1; i <= uSamples; i++) {
-                vec2 offset = uGaussianOffsets[i] * direction;
-                blurred += uGaussianWeights[i] * (texture(uTexture, vUV + offset, 0.0));
-                blurred += uGaussianWeights[i] * (texture(uTexture, vUV - offset, 0.0));
-            }
-
-            fragColor = vec4(blurred.rgb, 1.0);
-        }
-    )SHADER";
-    return shader.str();
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/GaussianBlurFilter.h b/libs/renderengine/gl/filters/GaussianBlurFilter.h
deleted file mode 100644
index 44f5fde..0000000
--- a/libs/renderengine/gl/filters/GaussianBlurFilter.h
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright 2019 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 <ui/GraphicTypes.h>
-#include "../GLESRenderEngine.h"
-#include "../GLFramebuffer.h"
-#include "BlurFilter.h"
-#include "GenericProgram.h"
-
-using namespace std;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-// Class that implements a Gaussian Filter that uses Linear Sampling
-// to halve the number of samples and reduce runtime by 40% as described in:
-// http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling
-class GaussianBlurFilter : public BlurFilter {
-public:
-    static constexpr uint32_t kNumSamples = 22;
-
-    explicit GaussianBlurFilter(GLESRenderEngine& engine);
-    status_t prepare() override;
-    void allocateTextures() override;
-
-private:
-    string getFragmentShader(bool horizontal) const;
-
-    // Initial, vertical render pass
-    GLFramebuffer mVerticalPassFbo;
-
-    // Vertical pass and its uniforms
-    GenericProgram mVerticalProgram;
-    GLuint mVPosLoc;
-    GLuint mVUvLoc;
-    GLuint mVTextureLoc;
-    GLuint mVGaussianOffsetLoc;
-    GLuint mVNumSamplesLoc;
-    GLuint mVGaussianWeightLoc;
-
-    // Horizontal pass and its uniforms
-    GenericProgram mHorizontalProgram;
-    GLuint mHPosLoc;
-    GLuint mHUvLoc;
-    GLuint mHTextureLoc;
-    GLuint mHGaussianOffsetLoc;
-    GLuint mHNumSamplesLoc;
-    GLuint mHGaussianWeightLoc;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/gl/filters/KawaseBlurFilter.cpp b/libs/renderengine/gl/filters/KawaseBlurFilter.cpp
deleted file mode 100644
index 7524c6d..0000000
--- a/libs/renderengine/gl/filters/KawaseBlurFilter.cpp
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2020 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.
- */
-
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "KawaseBlurFilter.h"
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES3/gl3.h>
-#include <GLES3/gl3ext.h>
-#include <ui/GraphicTypes.h>
-
-#include <utils/Trace.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-KawaseBlurFilter::KawaseBlurFilter(GLESRenderEngine& engine)
-      : BlurFilter(engine), mFbo(engine), mProgram(engine) {
-    mProgram.compile(getVertexShader(), getFragmentShader());
-    mPosLoc = mProgram.getAttributeLocation("aPosition");
-    mUvLoc = mProgram.getAttributeLocation("aUV");
-    mTextureLoc = mProgram.getUniformLocation("uTexture");
-    mOffsetLoc = mProgram.getUniformLocation("uOffset");
-}
-
-void KawaseBlurFilter::allocateTextures() {
-    mFbo.allocateBuffers(mBlurredFbo.getBufferWidth(), mBlurredFbo.getBufferHeight());
-}
-
-status_t KawaseBlurFilter::prepare() {
-    ATRACE_NAME("KawaseBlurFilter::prepare");
-
-    if (mFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
-        ALOGE("Invalid FBO");
-        return mFbo.getStatus();
-    }
-    if (!mProgram.isValid()) {
-        ALOGE("Invalid shader");
-        return GL_INVALID_OPERATION;
-    }
-
-    blit(mCompositionFbo, mBlurredFbo);
-
-    // Kawase is an approximation of Gaussian, but it behaves differently from it.
-    // A radius transformation is required for approximating them, and also to introduce
-    // non-integer steps, necessary to smoothly interpolate large radii.
-    auto radius = mRadius / 6.0f;
-
-    // Calculate how many passes we'll do, based on the radius.
-    // Too many passes will make the operation expensive.
-    auto passes = min(kMaxPasses, (uint32_t)ceil(radius));
-
-    // We'll ping pong between our textures, to accumulate the result of various offsets.
-    mProgram.useProgram();
-    GLFramebuffer* draw = &mFbo;
-    GLFramebuffer* read = &mBlurredFbo;
-    float stepX = radius / (float)mCompositionFbo.getBufferWidth() / (float)passes;
-    float stepY = radius / (float)mCompositionFbo.getBufferHeight() / (float)passes;
-    glActiveTexture(GL_TEXTURE0);
-    glUniform1i(mTextureLoc, 0);
-    for (auto i = 0; i < passes; i++) {
-        ATRACE_NAME("KawaseBlurFilter::renderPass");
-        draw->bind();
-
-        glViewport(0, 0, draw->getBufferWidth(), draw->getBufferHeight());
-        glBindTexture(GL_TEXTURE_2D, read->getTextureName());
-        glUniform2f(mOffsetLoc, stepX * i, stepY * i);
-        mEngine.checkErrors("Setting uniforms");
-
-        drawMesh(mUvLoc, mPosLoc);
-
-        // Swap buffers for next iteration
-        auto tmp = draw;
-        draw = read;
-        read = tmp;
-    }
-
-    // Copy texture, given that we're expected to end on mBlurredFbo.
-    if (draw == &mBlurredFbo) {
-        blit(mFbo, mBlurredFbo);
-    }
-
-    // Cleanup
-    glBindFramebuffer(GL_FRAMEBUFFER, 0);
-
-    return NO_ERROR;
-}
-
-string KawaseBlurFilter::getFragmentShader() const {
-    return R"SHADER(#version 310 es
-        precision mediump float;
-
-        uniform sampler2D uTexture;
-        uniform vec2 uOffset;
-
-        highp in vec2 vUV;
-        out vec4 fragColor;
-
-        void main() {
-            fragColor  = texture(uTexture, vUV, 0.0);
-            fragColor += texture(uTexture, vUV + vec2( uOffset.x,  uOffset.y), 0.0);
-            fragColor += texture(uTexture, vUV + vec2( uOffset.x, -uOffset.y), 0.0);
-            fragColor += texture(uTexture, vUV + vec2(-uOffset.x,  uOffset.y), 0.0);
-            fragColor += texture(uTexture, vUV + vec2(-uOffset.x, -uOffset.y), 0.0);
-
-            fragColor = vec4(fragColor.rgb * 0.2, 1.0);
-        }
-    )SHADER";
-}
-
-void KawaseBlurFilter::blit(GLFramebuffer& read, GLFramebuffer& draw) const {
-    read.bindAsReadBuffer();
-    draw.bindAsDrawBuffer();
-    glBlitFramebuffer(0, 0, read.getBufferWidth(), read.getBufferHeight(), 0, 0,
-                      draw.getBufferWidth(), draw.getBufferHeight(), GL_COLOR_BUFFER_BIT,
-                      GL_LINEAR);
-    glBindFramebuffer(GL_FRAMEBUFFER, 0);
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/KawaseBlurFilter.h b/libs/renderengine/gl/filters/KawaseBlurFilter.h
deleted file mode 100644
index 20009cf..0000000
--- a/libs/renderengine/gl/filters/KawaseBlurFilter.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright 2020 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 <ui/GraphicTypes.h>
-#include "../GLESRenderEngine.h"
-#include "../GLFramebuffer.h"
-#include "BlurFilter.h"
-#include "GenericProgram.h"
-
-using namespace std;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class KawaseBlurFilter : public BlurFilter {
-public:
-    static constexpr uint32_t kMaxPasses = 6;
-
-    explicit KawaseBlurFilter(GLESRenderEngine& engine);
-    status_t prepare() override;
-    void allocateTextures() override;
-
-private:
-    string getFragmentShader() const;
-    void blit(GLFramebuffer& read, GLFramebuffer& draw) const;
-
-    GLFramebuffer mFbo;
-
-    GenericProgram mProgram;
-    GLuint mPosLoc;
-    GLuint mUvLoc;
-    GLuint mTextureLoc;
-    GLuint mOffsetLoc;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index 30c48c8..6fd4b80 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -364,7 +364,7 @@
     }
 
     *outYcbcr = ycbcr;
-    return static_cast<status_t>(Error::UNSUPPORTED);
+    return static_cast<status_t>(Error::NONE);
 }
 
 int Gralloc4Mapper::unlock(buffer_handle_t bufferHandle) const {
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index 08d7d3f..c6f1f7e 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -52,6 +52,7 @@
         "libpdx_default_transport",
         "libprocessgroup",
         "libprotobuf-cpp-lite",
+        "libstatslog",
         "libsync",
         "libtimestats",
         "libui",
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index b876ccd..1765d2d 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -107,6 +107,12 @@
         return *mAvailableRefreshRates.back();
     }
 
+    // If there are not layers, there is not content detection, so return the current
+    // refresh rate.
+    if (layers.empty()) {
+        return getCurrentRefreshRateByPolicyLocked();
+    }
+
     int noVoteLayers = 0;
     int minVoteLayers = 0;
     int maxVoteLayers = 0;
@@ -272,6 +278,10 @@
 
 const RefreshRate& RefreshRateConfigs::getCurrentRefreshRateByPolicy() const {
     std::lock_guard lock(mLock);
+    return getCurrentRefreshRateByPolicyLocked();
+}
+
+const RefreshRate& RefreshRateConfigs::getCurrentRefreshRateByPolicyLocked() const {
     if (std::find(mAvailableRefreshRates.begin(), mAvailableRefreshRates.end(),
                   mCurrentRefreshRate) != mAvailableRefreshRates.end()) {
         return *mCurrentRefreshRate;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 0b5c73c..c8aec86 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -199,6 +199,10 @@
     // display refresh period.
     std::pair<nsecs_t, nsecs_t> getDisplayFrames(nsecs_t layerPeriod, nsecs_t displayPeriod) const;
 
+    // Returns the current refresh rate, if allowed. Otherwise the default that is allowed by
+    // the policy.
+    const RefreshRate& getCurrentRefreshRateByPolicyLocked() const REQUIRES(mLock);
+
     // The list of refresh rates, indexed by display config ID. This must not change after this
     // object is initialized.
     AllRefreshRatesMapType mRefreshRates;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 3a44332..5444239 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -99,12 +99,14 @@
 
 Scheduler::Scheduler(impl::EventControlThread::SetVSyncEnabledFunction function,
                      const scheduler::RefreshRateConfigs& refreshRateConfig,
-                     ISchedulerCallback& schedulerCallback, bool useContentDetectionV2)
+                     ISchedulerCallback& schedulerCallback, bool useContentDetectionV2,
+                     bool useContentDetection)
       : mPrimaryDispSync(createDispSync()),
         mEventControlThread(new impl::EventControlThread(std::move(function))),
         mSupportKernelTimer(sysprop::support_kernel_idle_timer(false)),
         mSchedulerCallback(schedulerCallback),
         mRefreshRateConfigs(refreshRateConfig),
+        mUseContentDetection(useContentDetection),
         mUseContentDetectionV2(useContentDetectionV2) {
     using namespace sysprop;
 
@@ -147,12 +149,14 @@
 Scheduler::Scheduler(std::unique_ptr<DispSync> primaryDispSync,
                      std::unique_ptr<EventControlThread> eventControlThread,
                      const scheduler::RefreshRateConfigs& configs,
-                     ISchedulerCallback& schedulerCallback, bool useContentDetectionV2)
+                     ISchedulerCallback& schedulerCallback, bool useContentDetectionV2,
+                     bool useContentDetection)
       : mPrimaryDispSync(std::move(primaryDispSync)),
         mEventControlThread(std::move(eventControlThread)),
         mSupportKernelTimer(false),
         mSchedulerCallback(schedulerCallback),
         mRefreshRateConfigs(configs),
+        mUseContentDetection(useContentDetection),
         mUseContentDetectionV2(useContentDetectionV2) {}
 
 Scheduler::~Scheduler() {
@@ -381,6 +385,17 @@
 void Scheduler::registerLayer(Layer* layer) {
     if (!mLayerHistory) return;
 
+    // If the content detection feature is off, all layers are registered at NoVote. We still
+    // keep the layer history, since we use it for other features (like Frame Rate API), so layers
+    // still need to be registered.
+    if (!mUseContentDetection) {
+        mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
+                                     mRefreshRateConfigs.getMaxRefreshRate().fps,
+                                     scheduler::LayerHistory::LayerVoteType::NoVote);
+        return;
+    }
+
+    // In V1 of content detection, all layers are registered as Heuristic (unless it's wallpaper).
     if (!mUseContentDetectionV2) {
         const auto lowFps = mRefreshRateConfigs.getMinRefreshRate().fps;
         const auto highFps = layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER
@@ -391,6 +406,7 @@
                                      scheduler::LayerHistory::LayerVoteType::Heuristic);
     } else {
         if (layer->getWindowType() == InputWindowInfo::TYPE_WALLPAPER) {
+            // Running Wallpaper at Min is considered as part of content detection.
             mLayerHistory->registerLayer(layer, mRefreshRateConfigs.getMinRefreshRate().fps,
                                          mRefreshRateConfigs.getMaxRefreshRate().fps,
                                          scheduler::LayerHistory::LayerVoteType::Min);
@@ -512,6 +528,8 @@
         // need to update the DispSync model anyway.
         disableHardwareVsync(false /* makeUnavailable */);
     }
+
+    mSchedulerCallback.kernelTimerChanged(state == TimerState::Expired);
 }
 
 void Scheduler::idleTimerCallback(TimerState state) {
@@ -537,8 +555,10 @@
 
     StringAppendF(&result, "+  Idle timer: %s\n",
                   mIdleTimer ? mIdleTimer->dump().c_str() : states[0]);
-    StringAppendF(&result, "+  Touch timer: %s\n\n",
+    StringAppendF(&result, "+  Touch timer: %s\n",
                   mTouchTimer ? mTouchTimer->dump().c_str() : states[0]);
+    StringAppendF(&result, "+  Use content detection: %s\n\n",
+                  sysprop::use_content_detection_for_refresh_rate(false) ? "on" : "off");
 }
 
 template <class T>
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 46d1a5e..04cc96a 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -51,6 +51,7 @@
     virtual void changeRefreshRate(const scheduler::RefreshRateConfigs::RefreshRate&,
                                    scheduler::RefreshRateConfigEvent) = 0;
     virtual void repaintEverythingForHWC() = 0;
+    virtual void kernelTimerChanged(bool expired) = 0;
 };
 
 class Scheduler {
@@ -63,7 +64,7 @@
 
     Scheduler(impl::EventControlThread::SetVSyncEnabledFunction,
               const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback,
-              bool useContentDetectionV2);
+              bool useContentDetectionV2, bool useContentDetection);
 
     virtual ~Scheduler();
 
@@ -159,7 +160,7 @@
     // Used by tests to inject mocks.
     Scheduler(std::unique_ptr<DispSync>, std::unique_ptr<EventControlThread>,
               const scheduler::RefreshRateConfigs&, ISchedulerCallback& schedulerCallback,
-              bool useContentDetectionV2);
+              bool useContentDetectionV2, bool useContentDetection);
 
     std::unique_ptr<VSyncSource> makePrimaryDispSyncSource(const char* name, nsecs_t phaseOffsetNs);
 
@@ -245,6 +246,9 @@
             GUARDED_BY(mVsyncTimelineLock);
     static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
 
+    // This variable indicates whether to use the content detection feature at all.
+    const bool mUseContentDetection;
+    // This variable indicates whether to use V2 version of the content detection.
     const bool mUseContentDetectionV2;
 };
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 236a30f..f3755f4 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -21,28 +21,17 @@
 //#define LOG_NDEBUG 0
 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
 
-#include <sys/types.h>
-#include <errno.h>
-#include <dlfcn.h>
+#include "SurfaceFlinger.h"
 
-#include <algorithm>
-#include <cinttypes>
-#include <cmath>
-#include <cstdint>
-#include <functional>
-#include <mutex>
-#include <optional>
-#include <unordered_map>
-
+#include <android/configuration.h>
+#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
+#include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
+#include <android/hardware/configstore/1.1/types.h>
+#include <android/hardware/power/1.0/IPower.h>
 #include <android/native_window.h>
-
-#include <cutils/properties.h>
-#include <log/log.h>
-
 #include <binder/IPCThreadState.h>
 #include <binder/IServiceManager.h>
 #include <binder/PermissionCache.h>
-
 #include <compositionengine/CompositionEngine.h>
 #include <compositionengine/CompositionRefreshArgs.h>
 #include <compositionengine/Display.h>
@@ -52,10 +41,14 @@
 #include <compositionengine/OutputLayer.h>
 #include <compositionengine/RenderSurface.h>
 #include <compositionengine/impl/OutputCompositionState.h>
+#include <configstore/Utils.h>
+#include <cutils/compiler.h>
+#include <cutils/properties.h>
+#include <dlfcn.h>
 #include <dvr/vr_flinger.h>
+#include <errno.h>
 #include <gui/BufferQueue.h>
 #include <gui/DebugEGLImageTracker.h>
-
 #include <gui/GuiConfig.h>
 #include <gui/IDisplayEventConnection.h>
 #include <gui/IProducerListener.h>
@@ -64,7 +57,13 @@
 #include <gui/LayerState.h>
 #include <gui/Surface.h>
 #include <input/IInputFlinger.h>
+#include <layerproto/LayerProtoParser.h>
+#include <log/log.h>
+#include <private/android_filesystem_config.h>
+#include <private/gui/SyncFeatures.h>
 #include <renderengine/RenderEngine.h>
+#include <statslog.h>
+#include <sys/types.h>
 #include <ui/ColorSpace.h>
 #include <ui/DebugUtils.h>
 #include <ui/DisplayConfig.h>
@@ -81,8 +80,14 @@
 #include <utils/Trace.h>
 #include <utils/misc.h>
 
-#include <private/android_filesystem_config.h>
-#include <private/gui/SyncFeatures.h>
+#include <algorithm>
+#include <cinttypes>
+#include <cmath>
+#include <cstdint>
+#include <functional>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
 
 #include "BufferLayer.h"
 #include "BufferQueueLayer.h"
@@ -91,23 +96,19 @@
 #include "Colorizer.h"
 #include "ContainerLayer.h"
 #include "DisplayDevice.h"
-#include "EffectLayer.h"
-#include "Layer.h"
-#include "LayerVector.h"
-#include "MonitoredProducer.h"
-#include "NativeWindowSurface.h"
-#include "RefreshRateOverlay.h"
-#include "StartPropertySetThread.h"
-#include "SurfaceFlinger.h"
-#include "SurfaceInterceptor.h"
-
 #include "DisplayHardware/ComposerHal.h"
 #include "DisplayHardware/DisplayIdentification.h"
 #include "DisplayHardware/FramebufferSurface.h"
 #include "DisplayHardware/HWComposer.h"
 #include "DisplayHardware/VirtualDisplaySurface.h"
+#include "EffectLayer.h"
 #include "Effects/Daltonizer.h"
 #include "FrameTracer/FrameTracer.h"
+#include "Layer.h"
+#include "LayerVector.h"
+#include "MonitoredProducer.h"
+#include "NativeWindowSurface.h"
+#include "RefreshRateOverlay.h"
 #include "RegionSamplingThread.h"
 #include "Scheduler/DispSync.h"
 #include "Scheduler/DispSyncSource.h"
@@ -116,23 +117,13 @@
 #include "Scheduler/MessageQueue.h"
 #include "Scheduler/PhaseOffsets.h"
 #include "Scheduler/Scheduler.h"
+#include "StartPropertySetThread.h"
+#include "SurfaceFlingerProperties.h"
+#include "SurfaceInterceptor.h"
 #include "TimeStats/TimeStats.h"
-
-#include <cutils/compiler.h>
-
 #include "android-base/parseint.h"
 #include "android-base/stringprintf.h"
 
-#include <android/configuration.h>
-#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
-#include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
-#include <android/hardware/configstore/1.1/types.h>
-#include <android/hardware/power/1.0/IPower.h>
-#include <configstore/Utils.h>
-
-#include <layerproto/LayerProtoParser.h>
-#include "SurfaceFlingerProperties.h"
-
 namespace android {
 
 using namespace std::string_literals;
@@ -217,6 +208,7 @@
 const String16 sAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER");
 const String16 sReadFramebuffer("android.permission.READ_FRAME_BUFFER");
 const String16 sDump("android.permission.DUMP");
+const char* KERNEL_IDLE_TIMER_PROP = "vendor.display.enable_kernel_idle_timer";
 
 // ---------------------------------------------------------------------------
 int64_t SurfaceFlinger::dispSyncPresentTimeOffset;
@@ -335,6 +327,9 @@
     property_get("ro.bq.gpu_to_cpu_unsupported", value, "0");
     mGpuToCpuSupported = !atoi(value);
 
+    property_get("ro.build.type", value, "user");
+    mIsUserBuild = strcmp(value, "user") == 0;
+
     property_get("debug.sf.showupdates", value, "0");
     mDebugRegion = atoi(value);
 
@@ -1813,6 +1808,10 @@
             if (frameMissed) {
                 mFrameMissedCount++;
                 mTimeStats->incrementMissedFrames();
+                if (mMissedFrameJankCount == 0) {
+                    mMissedFrameJankStart = systemTime();
+                }
+                mMissedFrameJankCount++;
             }
 
             if (hwcFrameMissed) {
@@ -1831,6 +1830,40 @@
                 }
             }
 
+            // Our jank window is always at least 100ms since we missed a
+            // frame...
+            static constexpr nsecs_t kMinJankyDuration =
+                    std::chrono::duration_cast<std::chrono::nanoseconds>(100ms).count();
+            // ...but if it's larger than 1s then we missed the trace cutoff.
+            static constexpr nsecs_t kMaxJankyDuration =
+                    std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
+            // If we're in a user build then don't push any atoms
+            if (!mIsUserBuild && mMissedFrameJankCount > 0) {
+                const auto displayDevice = getDefaultDisplayDeviceLocked();
+                // Only report jank when the display is on, as displays in DOZE
+                // power mode may operate at a different frame rate than is
+                // reported in their config, which causes noticeable (but less
+                // severe) jank.
+                if (displayDevice && displayDevice->getPowerMode() == HWC_POWER_MODE_NORMAL) {
+                    const nsecs_t currentTime = systemTime();
+                    const nsecs_t jankDuration = currentTime - mMissedFrameJankStart;
+                    if (jankDuration > kMinJankyDuration && jankDuration < kMaxJankyDuration) {
+                        ATRACE_NAME("Jank detected");
+                        ALOGD("Detected janky event. Missed frames: %d", mMissedFrameJankCount);
+                        const int32_t jankyDurationMillis = jankDuration / (1000 * 1000);
+                        android::util::stats_write(android::util::DISPLAY_JANK_REPORTED,
+                                                   jankyDurationMillis, mMissedFrameJankCount);
+                    }
+
+                    // We either reported a jank event or we missed the trace
+                    // window, so clear counters here.
+                    if (jankDuration > kMinJankyDuration) {
+                        mMissedFrameJankCount = 0;
+                        mMissedFrameJankStart = 0;
+                    }
+                }
+            }
+
             // Now that we're going to make it to the handleMessageTransaction()
             // call below it's safe to call updateVrFlinger(), which will
             // potentially trigger a display handoff.
@@ -5124,6 +5157,36 @@
     mEventQueue->invalidate();
 }
 
+void SurfaceFlinger::kernelTimerChanged(bool expired) {
+    static bool updateOverlay =
+            property_get_bool("debug.sf.kernel_idle_timer_update_overlay", true);
+    if (!updateOverlay || !mRefreshRateOverlay) return;
+
+    // Update the overlay on the main thread to avoid race conditions with
+    // mRefreshRateConfigs->getCurrentRefreshRate()
+    postMessageAsync(new LambdaMessage([this, expired]() NO_THREAD_SAFETY_ANALYSIS {
+        if (mRefreshRateOverlay) {
+            const auto kernelTimerEnabled = property_get_bool(KERNEL_IDLE_TIMER_PROP, false);
+            const bool timerExpired = kernelTimerEnabled && expired;
+            const auto& current = [this]() {
+                std::lock_guard<std::mutex> lock(mActiveConfigLock);
+                if (mDesiredActiveConfigChanged) {
+                    return mRefreshRateConfigs->getRefreshRateFromConfigId(
+                            mDesiredActiveConfig.configId);
+                }
+
+                return mRefreshRateConfigs->getCurrentRefreshRate();
+            }();
+            const auto& min = mRefreshRateConfigs->getMinRefreshRate();
+
+            if (current != min) {
+                mRefreshRateOverlay->changeRefreshRate(timerExpired ? min : current);
+                mEventQueue->invalidate();
+            }
+        }
+    }));
+}
+
 // A simple RAII class to disconnect from an ANativeWindow* when it goes out of scope
 class WindowDisconnector {
 public:
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 9b16a28..477d7a5 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -524,6 +524,8 @@
     void changeRefreshRate(const Scheduler::RefreshRate&, Scheduler::ConfigEvent) override;
     // force full composition on all displays without resetting the scheduler idle timer.
     void repaintEverythingForHWC() override;
+    // Called when kernel idle timer has expired. Used to update the refresh rate overlay.
+    void kernelTimerChanged(bool expired) override;
     /* ------------------------------------------------------------------------
      * Message handling
      */
@@ -983,6 +985,7 @@
     // constant members (no synchronization needed for access)
     const nsecs_t mBootTime = systemTime();
     bool mGpuToCpuSupported = false;
+    bool mIsUserBuild = true;
 
     // Can only accessed from the main thread, these members
     // don't need synchronization
@@ -1226,6 +1229,11 @@
     // Flags to capture the state of Vsync in HWC
     HWC2::Vsync mHWCVsyncState = HWC2::Vsync::Disable;
     HWC2::Vsync mHWCVsyncPendingState = HWC2::Vsync::Disable;
+
+    // Fields tracking the current jank event: when it started and how many
+    // janky frames there are.
+    nsecs_t mMissedFrameJankStart = 0;
+    int32_t mMissedFrameJankCount = 0;
 };
 
 } // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index 033e542..ddd20a5 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -33,6 +33,7 @@
 #include "NativeWindowSurface.h"
 #include "StartPropertySetThread.h"
 #include "SurfaceFlingerDefaultFactory.h"
+#include "SurfaceFlingerProperties.h"
 #include "SurfaceInterceptor.h"
 
 #include "DisplayHardware/ComposerHal.h"
@@ -76,8 +77,8 @@
         SetVSyncEnabled setVSyncEnabled, const scheduler::RefreshRateConfigs& configs,
         ISchedulerCallback& schedulerCallback) {
     return std::make_unique<Scheduler>(std::move(setVSyncEnabled), configs, schedulerCallback,
-                                       property_get_bool("debug.sf.use_content_detection_v2",
-                                                         true));
+                                       property_get_bool("debug.sf.use_content_detection_v2", true),
+                                       sysprop::use_content_detection_for_refresh_rate(false));
 }
 
 std::unique_ptr<SurfaceInterceptor> DefaultFactory::createSurfaceInterceptor(
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index 7e62513..e7e7f66 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -247,6 +247,31 @@
               refreshRateConfigs->getRefreshRateForContent(makeLayerRequirements(24.0f)));
 }
 
+TEST_F(RefreshRateConfigsTest, getRefreshRateForContentV2_noLayers) {
+    std::vector<RefreshRateConfigs::InputConfig> configs{
+            {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
+             {HWC_CONFIG_ID_72, HWC_GROUP_ID_0, VSYNC_72},
+             {HWC_CONFIG_ID_90, HWC_GROUP_ID_0, VSYNC_90}}};
+    auto refreshRateConfigs = std::make_unique<RefreshRateConfigs>(configs, /*currentConfigId=*/
+                                                                   HWC_CONFIG_ID_72);
+
+    RefreshRate expected60Config = {HWC_CONFIG_ID_60, VSYNC_60, HWC_GROUP_ID_0, "60fps", 60};
+    RefreshRate expected72Config = {HWC_CONFIG_ID_72, VSYNC_72, HWC_GROUP_ID_0, "72fps", 72};
+
+    // If there are not layers, there is not content detection, so return the current
+    // refresh rate.
+    auto layers = std::vector<LayerRequirement>{};
+    EXPECT_EQ(expected72Config,
+              refreshRateConfigs->getRefreshRateForContentV2(layers, /*touchActive*/
+                                                             false));
+
+    // Current refresh rate can always be changed.
+    refreshRateConfigs->setCurrentConfigId(HWC_CONFIG_ID_60);
+    EXPECT_EQ(expected60Config,
+              refreshRateConfigs->getRefreshRateForContentV2(layers, /*touchActive*/
+                                                             false));
+}
+
 TEST_F(RefreshRateConfigsTest, getRefreshRateForContentV2_60_90) {
     std::vector<RefreshRateConfigs::InputConfig> configs{
             {{HWC_CONFIG_ID_60, HWC_GROUP_ID_0, VSYNC_60},
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 52da34b..41b5d49 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -29,7 +29,7 @@
 class TestableScheduler : public Scheduler, private ISchedulerCallback {
 public:
     TestableScheduler(const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
-          : Scheduler([](bool) {}, configs, *this, useContentDetectionV2) {
+          : Scheduler([](bool) {}, configs, *this, useContentDetectionV2, true) {
         if (mUseContentDetectionV2) {
             mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
         } else {
@@ -41,7 +41,7 @@
                       std::unique_ptr<EventControlThread> eventControlThread,
                       const scheduler::RefreshRateConfigs& configs, bool useContentDetectionV2)
           : Scheduler(std::move(primaryDispSync), std::move(eventControlThread), configs, *this,
-                      useContentDetectionV2) {
+                      useContentDetectionV2, true) {
         if (mUseContentDetectionV2) {
             mLayerHistory = std::make_unique<scheduler::impl::LayerHistoryV2>();
         } else {
@@ -92,6 +92,7 @@
 private:
     void changeRefreshRate(const RefreshRate&, ConfigEvent) override {}
     void repaintEverythingForHWC() override {}
+    void kernelTimerChanged(bool /*expired*/) override {}
 };
 
 } // namespace android