Merge "Plumb ANGLE EGL features for debugging."
diff --git a/cmds/surfacereplayer/OWNERS b/cmds/surfacereplayer/OWNERS
index cc4c842..32bcc83 100644
--- a/cmds/surfacereplayer/OWNERS
+++ b/cmds/surfacereplayer/OWNERS
@@ -1,2 +1 @@
-mathias@google.com
-racarr@google.com
+include platform/frameworks/native:/services/surfaceflinger/OWNERS
diff --git a/data/etc/car_core_hardware.xml b/data/etc/car_core_hardware.xml
index cf78102..ccf4dc8 100644
--- a/data/etc/car_core_hardware.xml
+++ b/data/etc/car_core_hardware.xml
@@ -38,7 +38,6 @@
     <!-- basic system services -->
     <feature name="android.software.connectionservice" />
     <feature name="android.software.voice_recognizers" notLowRam="true" />
-    <feature name="android.software.backup" />
     <feature name="android.software.home_screen" />
     <feature name="android.software.companion_device_setup" />
     <feature name="android.software.autofill" />
diff --git a/include/input/Input.h b/include/input/Input.h
index 258adae..7d81fed 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -141,14 +141,6 @@
 #define MAX_CONTROLLER_LEDS 4
 
 /*
- * SystemUiVisibility constants from View.
- */
-enum {
-    ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE = 0,
-    ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN = 0x00000001,
-};
-
-/*
  * Maximum number of pointers supported per motion event.
  * Smallest number of pointers is 1.
  * (We want at least 10 but some touch controllers obstensibly configured for 10 pointers
diff --git a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
index 16afc68..365e788 100644
--- a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
@@ -51,7 +51,15 @@
     }
 
     int slot = item.mSlot;
+    *outQueueEmpty = false;
     if (item.mFence->isValid()) {
+        // If fence is not signaled, that means frame is not ready and
+        // outQueueEmpty is set to true. By the time the fence is signaled,
+        // there may be a new buffer queued. This is a proper detection for an
+        // empty queue and it is needed to avoid infinite loop in
+        // ASurfaceTexture_dequeueBuffer (see b/159921224).
+        *outQueueEmpty = item.mFence->getStatus() == Fence::Status::Unsignaled;
+
         // Wait on the producer fence for the buffer to be ready.
         err = fenceWait(item.mFence->get(), fencePassThroughHandle);
         if (err != OK) {
@@ -112,7 +120,6 @@
     st.mCurrentFrameNumber = item.mFrameNumber;
     st.computeCurrentTransformMatrixLocked();
 
-    *outQueueEmpty = false;
     *outDataspace = item.mDataSpace;
     *outSlotid = slot;
     return st.mSlots[slot].mGraphicBuffer;
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 1ccbff1..eb967ce 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -77,6 +77,7 @@
     srcs: [
         "skia/SkiaRenderEngine.cpp",
         "skia/SkiaGLRenderEngine.cpp",
+        "skia/filters/BlurFilter.cpp",
     ],
 }
 
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index cb752b0..0fed08b 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -29,12 +29,14 @@
 #include <utils/Trace.h>
 #include "../gl/GLExtensions.h"
 #include "SkiaGLRenderEngine.h"
+#include "filters/BlurFilter.h"
 
 #include <GrContextOptions.h>
 #include <gl/GrGLInterface.h>
 
 #include <SkCanvas.h>
 #include <SkImage.h>
+#include <SkImageFilters.h>
 #include <SkShadowUtils.h>
 #include <SkSurface.h>
 
@@ -194,7 +196,7 @@
 
     // initialize the renderer while GL is current
     std::unique_ptr<SkiaGLRenderEngine> engine =
-            std::make_unique<SkiaGLRenderEngine>(display, config, ctxt, placeholder,
+            std::make_unique<SkiaGLRenderEngine>(args, display, config, ctxt, placeholder,
                                                  protectedContext, protectedPlaceholder);
 
     ALOGI("OpenGL ES informations:");
@@ -247,9 +249,9 @@
     return config;
 }
 
-SkiaGLRenderEngine::SkiaGLRenderEngine(EGLDisplay display, EGLConfig config, EGLContext ctxt,
-                                       EGLSurface placeholder, EGLContext protectedContext,
-                                       EGLSurface protectedPlaceholder)
+SkiaGLRenderEngine::SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
+                                       EGLConfig config, EGLContext ctxt, EGLSurface placeholder,
+                                       EGLContext protectedContext, EGLSurface protectedPlaceholder)
       : mEGLDisplay(display),
         mEGLConfig(config),
         mEGLContext(ctxt),
@@ -273,6 +275,10 @@
     options.fPreferExternalImagesOverES3 = true;
     options.fDisableDistanceFieldPaths = true;
     mGrContext = GrDirectContext::MakeGL(std::move(glInterface), options);
+
+    if (args.supportsBackgroundBlur) {
+        mBlurFilter = new BlurFilter();
+    }
 }
 
 base::unique_fd SkiaGLRenderEngine::flush() {
@@ -408,6 +414,11 @@
         const auto& bounds = layer->geometry.boundaries;
         const auto dest = getSkRect(bounds);
 
+        if (layer->backgroundBlurRadius > 0) {
+            ATRACE_NAME("BackgroundBlur");
+            mBlurFilter->draw(canvas, surface, layer->backgroundBlurRadius);
+        }
+
         if (layer->source.buffer.buffer) {
             ATRACE_NAME("DrawImage");
             const auto& item = layer->source.buffer;
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.h b/libs/renderengine/skia/SkiaGLRenderEngine.h
index 3da7f25..898dc54 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.h
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.h
@@ -28,6 +28,7 @@
 #include <SkSurface.h>
 
 #include "SkiaRenderEngine.h"
+#include "filters/BlurFilter.h"
 
 namespace android {
 namespace renderengine {
@@ -36,8 +37,8 @@
 class SkiaGLRenderEngine : public skia::SkiaRenderEngine {
 public:
     static std::unique_ptr<SkiaGLRenderEngine> create(const RenderEngineCreationArgs& args);
-    SkiaGLRenderEngine(EGLDisplay display, EGLConfig config, EGLContext ctxt,
-                       EGLSurface placeholder, EGLContext protectedContext,
+    SkiaGLRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLConfig config,
+                       EGLContext ctxt, EGLSurface placeholder, EGLContext protectedContext,
                        EGLSurface protectedPlaceholder);
     ~SkiaGLRenderEngine() override{};
 
@@ -78,6 +79,7 @@
     EGLSurface mPlaceholderSurface;
     EGLContext mProtectedEGLContext;
     EGLSurface mProtectedPlaceholderSurface;
+    BlurFilter* mBlurFilter = nullptr;
 
     // Cache of GL images that we'll store per GraphicBuffer ID
     std::unordered_map<uint64_t, sk_sp<SkImage>> mImageCache GUARDED_BY(mRenderingMutex);
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
new file mode 100644
index 0000000..eb791a7
--- /dev/null
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+#include "BlurFilter.h"
+#include <SkCanvas.h>
+#include <SkData.h>
+#include <SkPaint.h>
+#include <SkRuntimeEffect.h>
+#include <SkSize.h>
+#include <SkString.h>
+#include <SkSurface.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+BlurFilter::BlurFilter() {
+    SkString blurString(R"(
+        in shader input;
+        uniform float in_inverseScale;
+        uniform float2 in_blurOffset;
+
+        half4 main(float2 xy) {
+            float2 scaled_xy = float2(xy.x * in_inverseScale, xy.y * in_inverseScale);
+
+            float4 c = float4(sample(input, scaled_xy));
+            c += float4(sample(input, scaled_xy + float2( in_blurOffset.x,  in_blurOffset.y)));
+            c += float4(sample(input, scaled_xy + float2( in_blurOffset.x, -in_blurOffset.y)));
+            c += float4(sample(input, scaled_xy + float2(-in_blurOffset.x,  in_blurOffset.y)));
+            c += float4(sample(input, scaled_xy + float2(-in_blurOffset.x, -in_blurOffset.y)));
+
+            return half4(c.rgb * 0.2, 1.0);
+        }
+    )");
+
+    auto [blurEffect, error] = SkRuntimeEffect::Make(blurString);
+    if (!blurEffect) {
+        LOG_ALWAYS_FATAL("RuntimeShader error: %s", error.c_str());
+    }
+    mBlurEffect = std::move(blurEffect);
+}
+
+void BlurFilter::draw(SkCanvas* canvas, sk_sp<SkSurface> input, const uint32_t blurRadius) const {
+    ATRACE_CALL();
+    // 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.
+    float tmpRadius = (float)blurRadius / 6.0f;
+    float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
+    float radiusByPasses = tmpRadius / (float)numberOfPasses;
+
+    SkImageInfo scaledInfo = SkImageInfo::MakeN32Premul((float)input->width() * kInputScale,
+                                                        (float)input->height() * kInputScale);
+    auto drawSurface = canvas->makeSurface(scaledInfo);
+
+    const float stepX = radiusByPasses;
+    const float stepY = radiusByPasses;
+
+    // start by drawing and downscaling and doing the first blur pass
+    SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
+    blurBuilder.child("input") = input->makeImageSnapshot()->makeShader();
+    blurBuilder.uniform("in_inverseScale") = kInverseInputScale;
+    blurBuilder.uniform("in_blurOffset") =
+            SkV2{stepX * kInverseInputScale, stepY * kInverseInputScale};
+
+    {
+        // limit the lifetime of the input surface's snapshot to ensure that it goes out of
+        // scope before the surface is written into to avoid any copy-on-write behavior.
+        SkPaint paint;
+        paint.setShader(blurBuilder.makeShader(nullptr, false));
+        paint.setFilterQuality(kLow_SkFilterQuality);
+        drawSurface->getCanvas()->drawIRect(scaledInfo.bounds(), paint);
+        blurBuilder.child("input") = nullptr;
+    }
+
+    // And now we'll ping pong between our surfaces, to accumulate the result of various offsets.
+    auto lastDrawTarget = drawSurface;
+    if (numberOfPasses > 1) {
+        auto readSurface = drawSurface;
+        drawSurface = canvas->makeSurface(scaledInfo);
+
+        for (auto i = 1; i < numberOfPasses; i++) {
+            const float stepScale = (float)i * kInputScale;
+
+            blurBuilder.child("input") = readSurface->makeImageSnapshot()->makeShader();
+            blurBuilder.uniform("in_inverseScale") = 1.0f;
+            blurBuilder.uniform("in_blurOffset") = SkV2{stepX * stepScale, stepY * stepScale};
+
+            SkPaint paint;
+            paint.setShader(blurBuilder.makeShader(nullptr, false));
+            paint.setFilterQuality(kLow_SkFilterQuality);
+            drawSurface->getCanvas()->drawIRect(scaledInfo.bounds(), paint);
+
+            // Swap buffers for next iteration
+            auto tmp = drawSurface;
+            drawSurface = readSurface;
+            readSurface = tmp;
+            blurBuilder.child("input") = nullptr;
+        }
+        lastDrawTarget = readSurface;
+    }
+
+    drawSurface->flushAndSubmit();
+
+    // do the final composition, with alpha blending to hide downscaling artifacts.
+    {
+        SkPaint paint;
+        paint.setShader(lastDrawTarget->makeImageSnapshot()->makeShader(
+                SkMatrix::MakeScale(kInverseInputScale)));
+        paint.setFilterQuality(kLow_SkFilterQuality);
+        paint.setAlpha(std::min(1.0f, (float)blurRadius / kMaxCrossFadeRadius) * 255);
+        canvas->drawIRect(SkIRect::MakeWH(input->width(), input->height()), paint);
+    }
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
\ No newline at end of file
diff --git a/libs/renderengine/skia/filters/BlurFilter.h b/libs/renderengine/skia/filters/BlurFilter.h
new file mode 100644
index 0000000..94b3673
--- /dev/null
+++ b/libs/renderengine/skia/filters/BlurFilter.h
@@ -0,0 +1,59 @@
+/*
+ * 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 <SkCanvas.h>
+#include <SkImage.h>
+#include <SkRuntimeEffect.h>
+#include <SkSurface.h>
+
+using namespace std;
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+/**
+ * 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 kInputScale = 0.25f;
+    // Downsample scale factor used to improve performance
+    static constexpr float kInverseInputScale = 1.0f / kInputScale;
+    // Maximum number of render passes
+    static constexpr uint32_t kMaxPasses = 4;
+    // To avoid downscaling artifacts, we interpolate the blurred fbo with the full composited
+    // image, up to this radius.
+    static constexpr float kMaxCrossFadeRadius = 30.0f;
+
+    explicit BlurFilter();
+    virtual ~BlurFilter(){};
+
+    // Execute blur passes, rendering to a canvas.
+    void draw(SkCanvas* canvas, sk_sp<SkSurface> input, const uint32_t radius) const;
+
+private:
+    sk_sp<SkRuntimeEffect> mBlurEffect;
+};
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/ui/include/ui/Rect.h b/libs/ui/include/ui/Rect.h
index 6670dc0..58323e5 100644
--- a/libs/ui/include/ui/Rect.h
+++ b/libs/ui/include/ui/Rect.h
@@ -202,6 +202,15 @@
     // the input.
     Rect transform(uint32_t xform, int32_t width, int32_t height) const;
 
+    Rect scale(float scaleX, float scaleY) const {
+        return Rect(FloatRect(left * scaleX, top * scaleY, right * scaleX, bottom * scaleY));
+    }
+
+    Rect& scaleSelf(float scaleX, float scaleY) {
+        set(scale(scaleX, scaleY));
+        return *this;
+    }
+
     // this calculates (Region(*this) - exclude).bounds() efficiently
     Rect reduce(const Rect& exclude) const;
 
diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp
index 28ef77a..032dd4c 100644
--- a/libs/ui/tests/Android.bp
+++ b/libs/ui/tests/Android.bp
@@ -85,6 +85,14 @@
 }
 
 cc_test {
+    name: "Rect_test",
+    test_suites: ["device-tests"],
+    shared_libs: ["libui"],
+    srcs: ["Rect_test.cpp"],
+    cflags: ["-Wall", "-Werror"],
+}
+
+cc_test {
     name: "Size_test",
     test_suites: ["device-tests"],
     shared_libs: ["libui"],
diff --git a/libs/ui/tests/Rect_test.cpp b/libs/ui/tests/Rect_test.cpp
new file mode 100644
index 0000000..5499a5b
--- /dev/null
+++ b/libs/ui/tests/Rect_test.cpp
@@ -0,0 +1,262 @@
+/*
+ * 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.
+ */
+
+#include <system/graphics.h>
+#include <ui/FloatRect.h>
+#include <ui/Point.h>
+#include <ui/Rect.h>
+#include <ui/Size.h>
+
+#include <gtest/gtest.h>
+
+namespace android::ui {
+
+TEST(RectTest, constructDefault) {
+    const Rect rect;
+    EXPECT_FALSE(rect.isValid());
+    EXPECT_TRUE(rect.isEmpty());
+}
+
+TEST(RectTest, constructFromWidthAndHeight) {
+    const Rect rect(100, 200);
+    EXPECT_TRUE(rect.isValid());
+    EXPECT_FALSE(rect.isEmpty());
+    EXPECT_EQ(0, rect.top);
+    EXPECT_EQ(0, rect.left);
+    EXPECT_EQ(100, rect.right);
+    EXPECT_EQ(200, rect.bottom);
+    EXPECT_EQ(100, rect.getWidth());
+    EXPECT_EQ(200, rect.getHeight());
+}
+
+TEST(RectTest, constructFromSize) {
+    const Rect rect(Size(100, 200));
+    EXPECT_TRUE(rect.isValid());
+    EXPECT_FALSE(rect.isEmpty());
+    EXPECT_EQ(0, rect.top);
+    EXPECT_EQ(0, rect.left);
+    EXPECT_EQ(100, rect.right);
+    EXPECT_EQ(200, rect.bottom);
+    EXPECT_EQ(100, rect.getWidth());
+    EXPECT_EQ(200, rect.getHeight());
+}
+
+TEST(RectTest, constructFromLTRB) {
+    const Rect rect(11, 12, 14, 14);
+    EXPECT_TRUE(rect.isValid());
+    EXPECT_FALSE(rect.isEmpty());
+    EXPECT_EQ(11, rect.left);
+    EXPECT_EQ(12, rect.top);
+    EXPECT_EQ(14, rect.right);
+    EXPECT_EQ(14, rect.bottom);
+    EXPECT_EQ(3, rect.getWidth());
+    EXPECT_EQ(2, rect.getHeight());
+}
+
+TEST(RectTest, constructFromPoints) {
+    const Rect rect(Point(11, 12), Point(14, 14));
+    EXPECT_TRUE(rect.isValid());
+    EXPECT_FALSE(rect.isEmpty());
+    EXPECT_EQ(11, rect.left);
+    EXPECT_EQ(12, rect.top);
+    EXPECT_EQ(14, rect.right);
+    EXPECT_EQ(14, rect.bottom);
+    EXPECT_EQ(3, rect.getWidth());
+    EXPECT_EQ(2, rect.getHeight());
+}
+
+TEST(RectTest, constructFromFloatRect) {
+    {
+        const Rect rect(FloatRect(10, 20, 30, 40));
+        EXPECT_TRUE(rect.isValid());
+        EXPECT_FALSE(rect.isEmpty());
+        EXPECT_EQ(10, rect.left);
+        EXPECT_EQ(20, rect.top);
+        EXPECT_EQ(30, rect.right);
+        EXPECT_EQ(40, rect.bottom);
+    }
+    // Construct with floating point error
+    {
+        constexpr float kError = 1e-3;
+        const Rect rect(FloatRect(10 - kError, 20 - kError, 30 - kError, 40 - kError));
+        EXPECT_TRUE(rect.isValid());
+        EXPECT_FALSE(rect.isEmpty());
+        EXPECT_EQ(10, rect.left);
+        EXPECT_EQ(20, rect.top);
+        EXPECT_EQ(30, rect.right);
+        EXPECT_EQ(40, rect.bottom);
+    }
+}
+
+TEST(RectTest, makeInvalid) {
+    Rect rect(10, 20, 60, 60);
+    EXPECT_TRUE(rect.isValid());
+    rect.makeInvalid();
+    EXPECT_FALSE(rect.isValid());
+}
+
+TEST(RectTest, clear) {
+    Rect rect(10, 20, 60, 60);
+    EXPECT_FALSE(rect.isEmpty());
+    rect.clear();
+    EXPECT_TRUE(rect.isEmpty());
+}
+
+TEST(RectTest, getSize) {
+    const Rect rect(10, 20, 60, 60);
+    EXPECT_EQ(Size(50, 40), rect.getSize());
+}
+
+TEST(RectTest, getBounds) {
+    const Rect rect(10, 20, 60, 60);
+    const Rect bounds = rect.getBounds();
+    EXPECT_EQ(0, bounds.left);
+    EXPECT_EQ(0, bounds.top);
+    EXPECT_EQ(50, bounds.right);
+    EXPECT_EQ(40, bounds.bottom);
+    EXPECT_EQ(rect.getSize(), bounds.getSize());
+}
+
+TEST(RectTest, getCornerPoints) {
+    const Rect rect(10, 20, 50, 60);
+    EXPECT_EQ(Point(10, 20), rect.leftTop());
+    EXPECT_EQ(Point(10, 60), rect.leftBottom());
+    EXPECT_EQ(Point(50, 20), rect.rightTop());
+    EXPECT_EQ(Point(50, 60), rect.rightBottom());
+}
+
+TEST(RectTest, operatorEquals) {
+    const Rect rect(10, 20, 50, 60);
+    EXPECT_EQ(rect, rect);
+    EXPECT_NE(Rect(0, 20, 50, 60), rect);
+    EXPECT_NE(Rect(10, 0, 50, 60), rect);
+    EXPECT_NE(Rect(10, 20, 0, 60), rect);
+    EXPECT_NE(Rect(10, 20, 50, 0), rect);
+}
+
+TEST(RectTest, operatorsPlusMinus) {
+    Rect rect = Rect(10, 20, 50, 60) + Point(1, 2);
+    EXPECT_EQ(Rect(11, 22, 51, 62), rect);
+    rect -= Point(1, 2);
+    EXPECT_EQ(Rect(10, 20, 50, 60), rect);
+
+    rect = Rect(10, 20, 50, 60) - Point(1, 2);
+    EXPECT_EQ(Rect(9, 18, 49, 58), rect);
+    rect += Point(1, 2);
+    EXPECT_EQ(Rect(10, 20, 50, 60), rect);
+}
+
+TEST(RectTest, scale) {
+    Rect rect(10, 20, 50, 60);
+    EXPECT_EQ(Rect(20, 60, 100, 180), rect.scale(2.f, 3.f));
+    rect.scaleSelf(2.f, 3.f);
+    EXPECT_EQ(Rect(20, 60, 100, 180), rect);
+
+    rect = Rect(10, 20, 50, 60);
+    constexpr float kError = 1e-3;
+    EXPECT_EQ(Rect(20, 60, 100, 180), rect.scale(2.f - kError, 3.f - kError));
+    rect.scaleSelf(2.f - kError, 3.f - kError);
+    EXPECT_EQ(Rect(20, 60, 100, 180), rect);
+}
+
+TEST(RectTest, inset) {
+    Rect rect(10, 20, 50, 60);
+    rect.inset(0, 0, 0, 0);
+    EXPECT_EQ(Rect(10, 20, 50, 60), rect);
+    rect.inset(1, 2, 3, 4);
+    EXPECT_EQ(Rect(11, 22, 47, 56), rect);
+}
+
+TEST(RectTest, intersect) {
+    const Rect rect(10, 20, 50, 60);
+    Rect intersection;
+
+    // Intersect with self is self
+    intersection.makeInvalid();
+    EXPECT_TRUE(rect.intersect(rect, &intersection));
+    EXPECT_EQ(Rect(10, 20, 50, 60), intersection);
+
+    // Intersect with rect contained in us
+    const Rect insideRect(11, 21, 45, 55);
+    intersection.makeInvalid();
+    EXPECT_TRUE(rect.intersect(insideRect, &intersection));
+    EXPECT_EQ(insideRect, intersection);
+
+    // Intersect with rect we are contained in
+    intersection.makeInvalid();
+    EXPECT_TRUE(insideRect.intersect(rect, &intersection));
+    EXPECT_EQ(insideRect, intersection);
+
+    // Empty intersection
+    intersection.makeInvalid();
+    EXPECT_FALSE(rect.intersect(Rect(100, 202, 150, 260), &intersection));
+    EXPECT_TRUE(intersection.isEmpty());
+
+    // Partial intersection
+    const Rect other(30, 40, 70, 80);
+    intersection.makeInvalid();
+    EXPECT_TRUE(rect.intersect(other, &intersection));
+    EXPECT_EQ(Rect(30, 40, 50, 60), intersection);
+
+    // Intersetion is commutative
+    intersection.makeInvalid();
+    EXPECT_TRUE(other.intersect(rect, &intersection));
+    EXPECT_EQ(Rect(30, 40, 50, 60), intersection);
+}
+
+TEST(RectTest, reduce) {
+    const Rect rect(10, 20, 50, 60);
+
+    // Reduce with self is empty
+    EXPECT_TRUE(rect.reduce(rect).isEmpty());
+
+    // Reduce with rect entirely inside is a noop
+    const Rect insideRect(11, 21, 45, 55);
+    EXPECT_EQ(rect, rect.reduce(insideRect));
+
+    // Reduce with rect entirely outside is empty
+    EXPECT_TRUE(insideRect.reduce(rect).isEmpty());
+
+    // Reduce with rect on the right
+    EXPECT_EQ(Rect(10, 20, 20, 60), rect.reduce(Rect(20, 0, 60, 70)));
+
+    // Reduce with rect on the left
+    EXPECT_EQ(Rect(40, 20, 50, 60), rect.reduce(Rect(0, 0, 40, 70)));
+
+    // Reduce with rect at the top
+    EXPECT_EQ(Rect(10, 40, 50, 60), rect.reduce(Rect(0, 0, 70, 40)));
+
+    // Reduce with rect at the bottom
+    EXPECT_EQ(Rect(10, 20, 50, 40), rect.reduce(Rect(0, 40, 70, 70)));
+}
+
+TEST(RectTest, transform) {
+    const int32_t width = 100, height = 200;
+    const Rect rect(1, 1, 2, 3);
+    EXPECT_EQ(Rect(98, 1, 99, 3), rect.transform(HAL_TRANSFORM_FLIP_H, width, height));
+    EXPECT_EQ(Rect(1, 197, 2, 199), rect.transform(HAL_TRANSFORM_FLIP_V, width, height));
+    EXPECT_EQ(Rect(197, 1, 199, 2), rect.transform(HAL_TRANSFORM_ROT_90, width, height));
+    EXPECT_EQ(Rect(98, 197, 99, 199), rect.transform(HAL_TRANSFORM_ROT_180, width, height));
+    EXPECT_EQ(Rect(1, 98, 3, 99), rect.transform(HAL_TRANSFORM_ROT_270, width, height));
+}
+
+TEST(RectTest, toFloatRect) {
+    const Rect rect(10, 20, 50, 60);
+    const FloatRect floatRect = rect.toFloatRect();
+    EXPECT_EQ(FloatRect(10.f, 20.f, 50.f, 60.f), floatRect);
+}
+
+} // namespace android::ui
diff --git a/libs/ui/tests/Size_test.cpp b/libs/ui/tests/Size_test.cpp
index 38f37ad..5f75aea 100644
--- a/libs/ui/tests/Size_test.cpp
+++ b/libs/ui/tests/Size_test.cpp
@@ -33,8 +33,7 @@
 
 #include <gtest/gtest.h>
 
-namespace android {
-namespace ui {
+namespace android::ui {
 
 TEST(SizeTest, BasicConstructionAndEqualityComparison) {
     Size s(123, 456);
@@ -215,5 +214,4 @@
     ClampTest(uint32_t(0), int32_t(0));
 }
 
-} // namespace ui
-} // namespace android
+} // namespace android::ui
diff --git a/libs/ui/tests/TEST_MAPPING b/libs/ui/tests/TEST_MAPPING
index 7fcd7de..eece18e 100644
--- a/libs/ui/tests/TEST_MAPPING
+++ b/libs/ui/tests/TEST_MAPPING
@@ -2,6 +2,9 @@
   "presubmit": [
     {
       "name": "Size_test"
+    },
+    {
+      "name": "Rect_test"
     }
   ]
 }
diff --git a/services/gpuservice/tests/unittests/GpuMemTest.cpp b/services/gpuservice/tests/unittests/GpuMemTest.cpp
index 45e8367..c5f8859 100644
--- a/services/gpuservice/tests/unittests/GpuMemTest.cpp
+++ b/services/gpuservice/tests/unittests/GpuMemTest.cpp
@@ -60,7 +60,7 @@
 
     void SetUp() override {
         SKIP_IF_BPF_NOT_SUPPORTED;
-        ASSERT_EQ(0, bpf::setrlimitForTest());
+        bpf::setrlimitForTest();
 
         mGpuMem = std::make_unique<GpuMem>();
         mTestableGpuMem = TestableGpuMem(mGpuMem.get());
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index b224476..271bc2f 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -85,12 +85,13 @@
     bumpGeneration();
 }
 
-void InputDevice::dump(std::string& dump) {
+void InputDevice::dump(std::string& dump, const std::string& eventHubDevStr) {
     InputDeviceInfo deviceInfo;
     getDeviceInfo(&deviceInfo);
 
     dump += StringPrintf(INDENT "Device %d: %s\n", deviceInfo.getId(),
                          deviceInfo.getDisplayName().c_str());
+    dump += StringPrintf(INDENT "%s", eventHubDevStr.c_str());
     dump += StringPrintf(INDENT2 "Generation: %d\n", mGeneration);
     dump += StringPrintf(INDENT2 "IsExternal: %s\n", toString(mIsExternal));
     dump += StringPrintf(INDENT2 "AssociatedDisplayPort: ");
@@ -102,6 +103,7 @@
     dump += StringPrintf(INDENT2 "HasMic:     %s\n", toString(mHasMic));
     dump += StringPrintf(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
     dump += StringPrintf(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
+    dump += StringPrintf(INDENT2 "ControllerNum: %d\n", deviceInfo.getControllerNumber());
 
     const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
     if (!ranges.empty()) {
@@ -201,6 +203,8 @@
 
     // insert the context into the devices set
     mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
+    // Must change generation to flag this device as changed
+    bumpGeneration();
 }
 
 void InputDevice::removeEventHubDevice(int32_t eventHubId) {
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index feaacb3..7c96e54 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -207,6 +207,14 @@
     }
 
     mDevices.emplace(eventHubId, device);
+    // Add device to device to EventHub ids map.
+    const auto mapIt = mDeviceToEventHubIdsMap.find(device);
+    if (mapIt == mDeviceToEventHubIdsMap.end()) {
+        std::vector<int32_t> ids = {eventHubId};
+        mDeviceToEventHubIdsMap.emplace(device, ids);
+    } else {
+        mapIt->second.push_back(eventHubId);
+    }
     bumpGenerationLocked();
 
     if (device->getClasses().test(InputDeviceClass::EXTERNAL_STYLUS)) {
@@ -223,6 +231,17 @@
 
     std::shared_ptr<InputDevice> device = std::move(deviceIt->second);
     mDevices.erase(deviceIt);
+    // Erase device from device to EventHub ids map.
+    auto mapIt = mDeviceToEventHubIdsMap.find(device);
+    if (mapIt != mDeviceToEventHubIdsMap.end()) {
+        std::vector<int32_t>& eventHubIds = mapIt->second;
+        eventHubIds.erase(std::remove_if(eventHubIds.begin(), eventHubIds.end(),
+                                         [eventHubId](int32_t eId) { return eId == eventHubId; }),
+                          eventHubIds.end());
+        if (eventHubIds.size() == 0) {
+            mDeviceToEventHubIdsMap.erase(mapIt);
+        }
+    }
     bumpGenerationLocked();
 
     if (device->isIgnored()) {
@@ -463,8 +482,7 @@
 void InputReader::getInputDevicesLocked(std::vector<InputDeviceInfo>& outInputDevices) {
     outInputDevices.clear();
 
-    for (auto& devicePair : mDevices) {
-        std::shared_ptr<InputDevice>& device = devicePair.second;
+    for (const auto& [device, eventHubIds] : mDeviceToEventHubIdsMap) {
         if (!device->isIgnored()) {
             InputDeviceInfo info;
             device->getDeviceInfo(&info);
@@ -635,11 +653,17 @@
     mEventHub->dump(dump);
     dump += "\n";
 
-    dump += "Input Reader State:\n";
+    dump += StringPrintf("Input Reader State (Nums of device: %zu):\n",
+                         mDeviceToEventHubIdsMap.size());
 
-    for (const auto& devicePair : mDevices) {
-        const std::shared_ptr<InputDevice>& device = devicePair.second;
-        device->dump(dump);
+    for (const auto& devicePair : mDeviceToEventHubIdsMap) {
+        const std::shared_ptr<InputDevice>& device = devicePair.first;
+        std::string eventHubDevStr = INDENT "EventHub Devices: [ ";
+        for (const auto& eId : devicePair.second) {
+            eventHubDevStr += StringPrintf("%d ", eId);
+        }
+        eventHubDevStr += "] \n";
+        device->dump(dump, eventHubDevStr);
     }
 
     dump += INDENT "Configuration:\n";
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 6b28069..7d160eb 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -67,7 +67,7 @@
     bool isEnabled();
     void setEnabled(bool enabled, nsecs_t when);
 
-    void dump(std::string& dump);
+    void dump(std::string& dump, const std::string& eventHubDevStr);
     void addEventHubDevice(int32_t eventHubId, bool populateMappers = true);
     void removeEventHubDevice(int32_t eventHubId);
     void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes);
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 2d6ccf5..a902482 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -142,6 +142,11 @@
     // to lookup the input device instance from the EventHub device id.
     std::unordered_map<int32_t /*eventHubId*/, std::shared_ptr<InputDevice>> mDevices;
 
+    // An input device contains one or more eventHubId, this map provides a way to lookup the
+    // EventHubIds contained in the input device from the input device instance.
+    std::unordered_map<std::shared_ptr<InputDevice>, std::vector<int32_t> /*eventHubId*/>
+            mDeviceToEventHubIdsMap;
+
     // low-level input event decoding and device management
     void processEventsLocked(const RawEvent* rawEvents, size_t count);
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 4b0a945..54a36a9 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -1354,6 +1354,29 @@
     ASSERT_EQ(size_t(0), inputDevices[0].getMotionRanges().size());
 }
 
+TEST_F(InputReaderTest, GetMergedInputDevices) {
+    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+    constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
+    // Add two subdevices to device
+    std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
+    // Must add at least one mapper or the device will be ignored!
+    device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
+    device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
+
+    // Push same device instance for next device to be added, so they'll have same identifier.
+    mReader->pushNextDevice(device);
+    mReader->pushNextDevice(device);
+    ASSERT_NO_FATAL_FAILURE(
+            addDevice(eventHubIds[0], "fake1", InputDeviceClass::KEYBOARD, nullptr));
+    ASSERT_NO_FATAL_FAILURE(
+            addDevice(eventHubIds[1], "fake2", InputDeviceClass::KEYBOARD, nullptr));
+
+    // Two devices will be merged to one input device as they have same identifier
+    std::vector<InputDeviceInfo> inputDevices;
+    mReader->getInputDevices(inputDevices);
+    ASSERT_EQ(1U, inputDevices.size());
+}
+
 TEST_F(InputReaderTest, WhenEnabledChanges_SendsDeviceResetNotification) {
     constexpr int32_t deviceId = END_RESERVED_ID + 1000;
     constexpr Flags<InputDeviceClass> deviceClass(InputDeviceClass::KEYBOARD);
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index fc1adcc..d590f23 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -166,7 +166,7 @@
     virtual void setProjection(ui::Rotation orientation, const Rect& layerStackSpaceRect,
                                const Rect& orientedDisplaySpaceRect) = 0;
     // Sets the bounds to use
-    virtual void setDisplaySpaceSize(const ui::Size&) = 0;
+    virtual void setDisplaySize(const ui::Size&) = 0;
 
     // Sets the layer stack filtering settings for this output. See
     // belongsInOutput for full details.
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index 6fe93bf..5b832a5 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -40,7 +40,7 @@
     void setCompositionEnabled(bool) override;
     void setProjection(ui::Rotation orientation, const Rect& layerStackSpaceRect,
                        const Rect& orientedDisplaySpaceRect) override;
-    void setDisplaySpaceSize(const ui::Size&) override;
+    void setDisplaySize(const ui::Size&) override;
     void setLayerStackFilter(uint32_t layerStackId, bool isInternal) override;
 
     void setColorTransform(const compositionengine::CompositionRefreshArgs&) override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index f4d2b56..06e6a6f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -39,7 +39,7 @@
 namespace compositionengine::impl {
 
 struct OutputCompositionState {
-    // If false, composition will not per performed for this display
+    // If false, composition will not be performed for this display
     bool isEnabled{false};
 
     // If false, this output is not considered secure
@@ -71,6 +71,11 @@
     // match the orientation of layerStackSpace. The orientation of this space is always ROTATION_0.
     ProjectionSpace orientedDisplaySpace;
 
+    // The space of the framebuffer. Its bounds match the size of the framebuffer and its
+    // orientation matches the orientation of the display. Typically the framebuffer space will
+    // be identical to the physical display space.
+    ProjectionSpace framebufferSpace;
+
     // The space of the physical display. It is as big as the currently active display mode. The
     // content in this space can be rotated.
     ProjectionSpace displaySpace;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 19025c1..d6fbd7f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -37,7 +37,7 @@
 
     MOCK_METHOD1(setCompositionEnabled, void(bool));
     MOCK_METHOD3(setProjection, void(ui::Rotation, const Rect&, const Rect&));
-    MOCK_METHOD1(setDisplaySpaceSize, void(const ui::Size&));
+    MOCK_METHOD1(setDisplaySize, void(const ui::Size&));
     MOCK_METHOD2(setLayerStackFilter, void(uint32_t, bool));
 
     MOCK_METHOD1(setColorTransform, void(const compositionengine::CompositionRefreshArgs&));
diff --git a/services/surfaceflinger/CompositionEngine/src/Display.cpp b/services/surfaceflinger/CompositionEngine/src/Display.cpp
index d201104..5459861 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -55,6 +55,7 @@
     mPowerAdvisor = args.powerAdvisor;
 
     editState().isSecure = args.isSecure;
+    editState().displaySpace.bounds = Rect(args.pixels);
 
     setLayerStackFilter(args.layerStackId,
                         args.physical ? args.physical->type == DisplayConnectionType::Internal
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index abb8769..44edb6e 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -69,6 +69,19 @@
     return Reversed<T>(c);
 }
 
+struct ScaleVector {
+    float x;
+    float y;
+};
+
+// Returns a ScaleVector (x, y) such that from.scale(x, y) = to',
+// where to' will have the same size as "to". In the case where "from" and "to"
+// start at the origin to'=to.
+ScaleVector getScale(const Rect& from, const Rect& to) {
+    return {.x = static_cast<float>(to.width()) / from.width(),
+            .y = static_cast<float>(to.height()) / from.height()};
+}
+
 } // namespace
 
 std::shared_ptr<Output> createOutput(
@@ -110,9 +123,10 @@
     auto& outputState = editState();
 
     outputState.displaySpace.orientation = orientation;
-    // outputState.displaySpace.bounds should be already set from setDisplaySpaceSize().
+    LOG_FATAL_IF(outputState.displaySpace.bounds == Rect::INVALID_RECT,
+                 "The display bounds are unknown.");
 
-    // Compute the orientedDisplaySpace bounds
+    // Compute orientedDisplaySpace
     ui::Size orientedSize = outputState.displaySpace.bounds.getSize();
     if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270) {
         std::swap(orientedSize.width, orientedSize.height);
@@ -129,17 +143,51 @@
     }
     outputState.displaySpace.content = rotation.transform(orientedDisplaySpaceRect);
 
+    // Compute framebufferSpace
+    outputState.framebufferSpace.orientation = orientation;
+    LOG_FATAL_IF(outputState.framebufferSpace.bounds == Rect::INVALID_RECT,
+                 "The framebuffer bounds are unknown.");
+    const auto scale =
+            getScale(outputState.framebufferSpace.bounds, outputState.displaySpace.bounds);
+    outputState.framebufferSpace.content = outputState.displaySpace.content.scale(scale.x, scale.y);
+
+    // Compute layerStackSpace
     outputState.layerStackSpace.content = layerStackSpaceRect;
     outputState.layerStackSpace.bounds = layerStackSpaceRect;
+
     outputState.transform = outputState.layerStackSpace.getTransform(outputState.displaySpace);
     outputState.needsFiltering = outputState.transform.needsBilinearFiltering();
-
     dirtyEntireOutput();
 }
 
-void Output::setDisplaySpaceSize(const ui::Size& size) {
+void Output::setDisplaySize(const ui::Size& size) {
     mRenderSurface->setDisplaySize(size);
-    editState().displaySpace.bounds = Rect(mRenderSurface->getSize());
+
+    auto& state = editState();
+
+    // Update framebuffer space
+    const Rect newBounds(size);
+    ScaleVector scale;
+    scale = getScale(state.framebufferSpace.bounds, newBounds);
+    state.framebufferSpace.bounds = newBounds;
+    state.framebufferSpace.content.scaleSelf(scale.x, scale.y);
+
+    // Update display space
+    scale = getScale(state.displaySpace.bounds, newBounds);
+    state.displaySpace.bounds = newBounds;
+    state.displaySpace.content.scaleSelf(scale.x, scale.y);
+    state.transform = state.layerStackSpace.getTransform(state.displaySpace);
+
+    // Update oriented display space
+    const auto orientation = state.displaySpace.orientation;
+    ui::Size orientedSize = size;
+    if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270) {
+        std::swap(orientedSize.width, orientedSize.height);
+    }
+    const Rect newOrientedBounds(orientedSize);
+    scale = getScale(state.orientedDisplaySpace.bounds, newOrientedBounds);
+    state.orientedDisplaySpace.bounds = newOrientedBounds;
+    state.orientedDisplaySpace.content.scaleSelf(scale.x, scale.y);
 
     dirtyEntireOutput();
 }
@@ -247,8 +295,7 @@
 
 void Output::setRenderSurface(std::unique_ptr<compositionengine::RenderSurface> surface) {
     mRenderSurface = std::move(surface);
-    editState().displaySpace.bounds = Rect(mRenderSurface->getSize());
-
+    editState().framebufferSpace.bounds = Rect(mRenderSurface->getSize());
     dirtyEntireOutput();
 }
 
@@ -877,7 +924,7 @@
     ALOGV("hasClientComposition");
 
     renderengine::DisplaySettings clientCompositionDisplay;
-    clientCompositionDisplay.physicalDisplay = outputState.displaySpace.content;
+    clientCompositionDisplay.physicalDisplay = outputState.framebufferSpace.content;
     clientCompositionDisplay.clip = outputState.layerStackSpace.content;
     clientCompositionDisplay.orientation =
             ui::Transform::toRotationFlags(outputState.displaySpace.orientation);
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 776fdde..ee30ad8 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -39,6 +39,8 @@
     out.append("\n   ");
     dumpVal(out, "layerStackSpace", to_string(layerStackSpace));
     out.append("\n   ");
+    dumpVal(out, "framebufferSpace", to_string(framebufferSpace));
+    out.append("\n   ");
     dumpVal(out, "orientedDisplaySpace", to_string(orientedDisplaySpace));
     out.append("\n   ");
     dumpVal(out, "displaySpace", to_string(displaySpace));
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index c01f3e0..57b399a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -248,20 +248,46 @@
 }
 
 /*
- * Output::setDisplaySpaceSize()
+ * Output::setDisplaySize()
  */
 
-TEST_F(OutputTest, setBoundsSetsSizeAndDirtiesEntireOutput) {
-    const ui::Size displaySize{200, 400};
+TEST_F(OutputTest, setDisplaySpaceSizeUpdatesOutputStateAndDirtiesEntireOutput) {
+    mOutput->editState().layerStackSpace.content = Rect(0, 0, 2000, 1000);
+    mOutput->editState().layerStackSpace.bounds = Rect(0, 0, 2000, 1000);
+    mOutput->editState().orientedDisplaySpace.content = Rect(0, 0, 1800, 900);
+    mOutput->editState().orientedDisplaySpace.bounds = Rect(0, 0, 2000, 1000);
+    mOutput->editState().framebufferSpace.content = Rect(0, 0, 900, 1800);
+    mOutput->editState().framebufferSpace.bounds = Rect(0, 0, 1000, 2000);
+    mOutput->editState().framebufferSpace.orientation = ui::ROTATION_90;
+    mOutput->editState().displaySpace.content = Rect(0, 0, 900, 1800);
+    mOutput->editState().displaySpace.bounds = Rect(0, 0, 1000, 2000);
+    mOutput->editState().displaySpace.orientation = ui::ROTATION_90;
 
-    EXPECT_CALL(*mRenderSurface, setDisplaySize(displaySize)).Times(1);
-    EXPECT_CALL(*mRenderSurface, getSize()).WillOnce(ReturnRef(displaySize));
+    const ui::Size newDisplaySize{500, 1000};
 
-    mOutput->setDisplaySpaceSize(displaySize);
+    EXPECT_CALL(*mRenderSurface, setDisplaySize(newDisplaySize)).Times(1);
 
-    EXPECT_EQ(Rect(displaySize), mOutput->getState().displaySpace.bounds);
+    mOutput->setDisplaySize(newDisplaySize);
 
-    EXPECT_THAT(mOutput->getState().dirtyRegion, RegionEq(Region(Rect(displaySize))));
+    const auto state = mOutput->getState();
+
+    const Rect displayRect(newDisplaySize);
+    EXPECT_EQ(ui::ROTATION_0, state.layerStackSpace.orientation);
+    EXPECT_EQ(Rect(0, 0, 2000, 1000), state.layerStackSpace.content);
+    EXPECT_EQ(Rect(0, 0, 2000, 1000), state.layerStackSpace.bounds);
+    EXPECT_EQ(ui::ROTATION_0, state.orientedDisplaySpace.orientation);
+    EXPECT_EQ(Rect(0, 0, 900, 450), state.orientedDisplaySpace.content);
+    EXPECT_EQ(Rect(0, 0, 1000, 500), state.orientedDisplaySpace.bounds);
+    EXPECT_EQ(displayRect, state.displaySpace.bounds);
+    EXPECT_EQ(Rect(0, 0, 450, 900), state.displaySpace.content);
+    EXPECT_EQ(ui::ROTATION_90, state.displaySpace.orientation);
+    EXPECT_EQ(displayRect, state.framebufferSpace.bounds);
+    EXPECT_EQ(Rect(0, 0, 450, 900), state.framebufferSpace.content);
+    EXPECT_EQ(ui::ROTATION_90, state.framebufferSpace.orientation);
+
+    EXPECT_EQ(state.displaySpace.content, state.transform.transform(state.layerStackSpace.content));
+
+    EXPECT_THAT(state.dirtyRegion, RegionEq(Region(displayRect)));
 }
 
 /*
@@ -422,7 +448,7 @@
 
     mOutput->setRenderSurface(std::unique_ptr<RenderSurface>(renderSurface));
 
-    EXPECT_EQ(Rect(newDisplaySize), mOutput->getState().displaySpace.bounds);
+    EXPECT_EQ(Rect(newDisplaySize), mOutput->getState().framebufferSpace.bounds);
 }
 
 /*
@@ -2778,9 +2804,10 @@
 
         mOutput.mState.orientedDisplaySpace.content = kDefaultOutputFrame;
         mOutput.mState.layerStackSpace.content = kDefaultOutputViewport;
+        mOutput.mState.framebufferSpace.content = kDefaultOutputDestinationClip;
         mOutput.mState.displaySpace.content = kDefaultOutputDestinationClip;
-        mOutput.mState.transform = ui::Transform{kDefaultOutputOrientationFlags};
         mOutput.mState.displaySpace.orientation = kDefaultOutputOrientation;
+        mOutput.mState.transform = ui::Transform{kDefaultOutputOrientationFlags};
         mOutput.mState.dataspace = kDefaultOutputDataspace;
         mOutput.mState.colorTransformMatrix = kDefaultColorTransformMat;
         mOutput.mState.isSecure = false;
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 7df9b76..a1ccaad 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -156,7 +156,8 @@
 }
 
 void DisplayDevice::setDisplaySize(int width, int height) {
-    mCompositionDisplay->setDisplaySpaceSize(ui::Size(width, height));
+    LOG_FATAL_IF(!isVirtual(), "Changing the display size is supported only for virtual displays.");
+    mCompositionDisplay->setDisplaySize(ui::Size(width, height));
 }
 
 void DisplayDevice::setProjection(ui::Rotation orientation, Rect layerStackSpaceRect,
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 9c145cc..7b8448f 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -229,6 +229,7 @@
     auto connection =
             createConnectionInternal(eventThread.get(), ISurfaceComposer::eConfigChangedSuppress);
 
+    std::lock_guard<std::mutex> lock(mConnectionsLock);
     mConnections.emplace(handle, Connection{connection, std::move(eventThread)});
     return handle;
 }
@@ -240,29 +241,47 @@
 
 sp<IDisplayEventConnection> Scheduler::createDisplayEventConnection(
         ConnectionHandle handle, ISurfaceComposer::ConfigChanged configChanged) {
+    std::lock_guard<std::mutex> lock(mConnectionsLock);
     RETURN_IF_INVALID_HANDLE(handle, nullptr);
     return createConnectionInternal(mConnections[handle].thread.get(), configChanged);
 }
 
 sp<EventThreadConnection> Scheduler::getEventConnection(ConnectionHandle handle) {
+    std::lock_guard<std::mutex> lock(mConnectionsLock);
     RETURN_IF_INVALID_HANDLE(handle, nullptr);
     return mConnections[handle].connection;
 }
 
 void Scheduler::onHotplugReceived(ConnectionHandle handle, PhysicalDisplayId displayId,
                                   bool connected) {
-    RETURN_IF_INVALID_HANDLE(handle);
-    mConnections[handle].thread->onHotplugReceived(displayId, connected);
+    android::EventThread* thread;
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle);
+        thread = mConnections[handle].thread.get();
+    }
+
+    thread->onHotplugReceived(displayId, connected);
 }
 
 void Scheduler::onScreenAcquired(ConnectionHandle handle) {
-    RETURN_IF_INVALID_HANDLE(handle);
-    mConnections[handle].thread->onScreenAcquired();
+    android::EventThread* thread;
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle);
+        thread = mConnections[handle].thread.get();
+    }
+    thread->onScreenAcquired();
 }
 
 void Scheduler::onScreenReleased(ConnectionHandle handle) {
-    RETURN_IF_INVALID_HANDLE(handle);
-    mConnections[handle].thread->onScreenReleased();
+    android::EventThread* thread;
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle);
+        thread = mConnections[handle].thread.get();
+    }
+    thread->onScreenReleased();
 }
 
 void Scheduler::onPrimaryDisplayConfigChanged(ConnectionHandle handle, PhysicalDisplayId displayId,
@@ -274,6 +293,16 @@
 }
 
 void Scheduler::dispatchCachedReportedConfig() {
+    // Check optional fields first.
+    if (!mFeatures.configId.has_value()) {
+        ALOGW("No config ID found, not dispatching cached config.");
+        return;
+    }
+    if (!mFeatures.cachedConfigChangedParams.has_value()) {
+        ALOGW("No config changed params found, not dispatching cached config.");
+        return;
+    }
+
     const auto configId = *mFeatures.configId;
     const auto vsyncPeriod =
             mRefreshRateConfigs.getRefreshRateFromConfigId(configId).getVsyncPeriod();
@@ -295,24 +324,40 @@
 void Scheduler::onNonPrimaryDisplayConfigChanged(ConnectionHandle handle,
                                                  PhysicalDisplayId displayId,
                                                  HwcConfigIndexType configId, nsecs_t vsyncPeriod) {
-    RETURN_IF_INVALID_HANDLE(handle);
-    mConnections[handle].thread->onConfigChanged(displayId, configId, vsyncPeriod);
+    android::EventThread* thread;
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle);
+        thread = mConnections[handle].thread.get();
+    }
+    thread->onConfigChanged(displayId, configId, vsyncPeriod);
 }
 
 size_t Scheduler::getEventThreadConnectionCount(ConnectionHandle handle) {
+    std::lock_guard<std::mutex> lock(mConnectionsLock);
     RETURN_IF_INVALID_HANDLE(handle, 0);
     return mConnections[handle].thread->getEventThreadConnectionCount();
 }
 
 void Scheduler::dump(ConnectionHandle handle, std::string& result) const {
-    RETURN_IF_INVALID_HANDLE(handle);
-    mConnections.at(handle).thread->dump(result);
+    android::EventThread* thread;
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle);
+        thread = mConnections.at(handle).thread.get();
+    }
+    thread->dump(result);
 }
 
 void Scheduler::setDuration(ConnectionHandle handle, std::chrono::nanoseconds workDuration,
                             std::chrono::nanoseconds readyDuration) {
-    RETURN_IF_INVALID_HANDLE(handle);
-    mConnections[handle].thread->setDuration(workDuration, readyDuration);
+    android::EventThread* thread;
+    {
+        std::lock_guard<std::mutex> lock(mConnectionsLock);
+        RETURN_IF_INVALID_HANDLE(handle);
+        thread = mConnections[handle].thread.get();
+    }
+    thread->setDuration(workDuration, readyDuration);
 }
 
 void Scheduler::getDisplayStatInfo(DisplayStatInfo* stats, nsecs_t now) {
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index da25f5c..3ecda58 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -228,7 +228,8 @@
     };
 
     ConnectionHandle::Id mNextConnectionHandleId = 0;
-    std::unordered_map<ConnectionHandle, Connection> mConnections;
+    mutable std::mutex mConnectionsLock;
+    std::unordered_map<ConnectionHandle, Connection> mConnections GUARDED_BY(mConnectionsLock);
 
     bool mInjectVSyncs = false;
     InjectVSyncSource* mVSyncInjector = nullptr;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 6a0f24a..8ac459b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1982,7 +1982,7 @@
     mFrameTimeline->setSfPresent(systemTime(),
                                  std::make_shared<FenceTime>(mPreviousPresentFences[0]));
 
-    const bool prevFrameHadDeviceComposition = mHadDeviceComposition;
+    const bool prevFrameHadClientComposition = mHadClientComposition;
 
     mHadClientComposition = std::any_of(displays.cbegin(), displays.cend(), [](const auto& pair) {
         const auto& state = pair.second->getCompositionDisplay()->getState();
@@ -1997,9 +1997,8 @@
                 const auto& state = pair.second->getCompositionDisplay()->getState();
                 return state.reusedClientComposition;
             });
-
-    // Only report a strategy change if we move in and out of composition with hw overlays
-    if (prevFrameHadDeviceComposition != mHadDeviceComposition) {
+    // Only report a strategy change if we move in and out of client composition
+    if (prevFrameHadClientComposition != mHadClientComposition) {
         mTimeStats->incrementCompositionStrategyChanges();
     }
 
@@ -6189,6 +6188,11 @@
     }
 
     sp<Layer> layer = (static_cast<MonitoredProducer*>(surface.get()))->getLayer();
+    if (layer == nullptr) {
+        ALOGE("Attempt to set frame timeline vsync on a layer that no longer exists");
+        return BAD_VALUE;
+    }
+
     layer->setFrameTimelineVsync(frameTimelineVsyncId);
     return NO_ERROR;
 }
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 35619a3..eee9400 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -175,4 +175,25 @@
     mScheduler.chooseRefreshRateForContent();
 }
 
+TEST_F(SchedulerTest, testDispatchCachedReportedConfig) {
+    // If the optional fields are cleared, the function should return before
+    // onConfigChange is called.
+    mScheduler.clearOptionalFieldsInFeatures();
+    EXPECT_NO_FATAL_FAILURE(mScheduler.dispatchCachedReportedConfig());
+    EXPECT_CALL(*mEventThread, onConfigChanged(_, _, _)).Times(0);
+}
+
+TEST_F(SchedulerTest, onNonPrimaryDisplayConfigChanged_invalidParameters) {
+    HwcConfigIndexType configId = HwcConfigIndexType(111);
+    nsecs_t vsyncPeriod = 111111;
+
+    // If the handle is incorrect, the function should return before
+    // onConfigChange is called.
+    Scheduler::ConnectionHandle invalidHandle = {.id = 123};
+    EXPECT_NO_FATAL_FAILURE(mScheduler.onNonPrimaryDisplayConfigChanged(invalidHandle,
+                                                                        PHYSICAL_DISPLAY_ID,
+                                                                        configId, vsyncPeriod));
+    EXPECT_CALL(*mEventThread, onConfigChanged(_, _, _)).Times(0);
+}
+
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index db3e0bd..a9d9dc0 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <Scheduler/Scheduler.h>
 #include <gmock/gmock.h>
 #include <gui/ISurfaceComposer.h>
 
@@ -93,6 +94,22 @@
         return mFeatures.touch == Scheduler::TouchState::Active;
     }
 
+    void dispatchCachedReportedConfig() {
+        std::lock_guard<std::mutex> lock(mFeatureStateLock);
+        return Scheduler::dispatchCachedReportedConfig();
+    }
+
+    void clearOptionalFieldsInFeatures() {
+        std::lock_guard<std::mutex> lock(mFeatureStateLock);
+        mFeatures.cachedConfigChangedParams.reset();
+    }
+
+    void onNonPrimaryDisplayConfigChanged(ConnectionHandle handle, PhysicalDisplayId displayId,
+                                          HwcConfigIndexType configId, nsecs_t vsyncPeriod) {
+        return Scheduler::onNonPrimaryDisplayConfigChanged(handle, displayId, configId,
+                                                           vsyncPeriod);
+    }
+
     ~TestableScheduler() {
         // All these pointer and container clears help ensure that GMock does
         // not report a leaked object, since the Scheduler instance may