Merge "Skip creating external textures that exceeds size limit" into sc-v2-dev
diff --git a/include/input/Input.h b/include/input/Input.h
index d88dcfe..cd110e8 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -577,6 +577,8 @@
 
     void setCursorPosition(float x, float y);
 
+    uint32_t getDisplayOrientation() const { return mDisplayOrientation; }
+
     int2 getDisplaySize() const { return {mDisplayWidth, mDisplayHeight}; }
 
     static inline bool isValidCursorPosition(float x, float y) { return !isnan(x) && !isnan(y); }
@@ -753,8 +755,8 @@
                     int32_t flags, int32_t edgeFlags, int32_t metaState, int32_t buttonState,
                     MotionClassification classification, const ui::Transform& transform,
                     float xPrecision, float yPrecision, float rawXCursorPosition,
-                    float rawYCursorPosition, int32_t displayWidth, int32_t displayHeight,
-                    nsecs_t downTime, nsecs_t eventTime, size_t pointerCount,
+                    float rawYCursorPosition, uint32_t displayOrientation, int32_t displayWidth,
+                    int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime, size_t pointerCount,
                     const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
 
     void copyFrom(const MotionEvent* other, bool keepHistory);
@@ -812,6 +814,7 @@
     float mYPrecision;
     float mRawXCursorPosition;
     float mRawYCursorPosition;
+    uint32_t mDisplayOrientation;
     int32_t mDisplayWidth;
     int32_t mDisplayHeight;
     nsecs_t mDownTime;
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 360dfbf..a790b56 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -136,10 +136,10 @@
             float yPrecision;
             float xCursorPosition;
             float yCursorPosition;
+            uint32_t displayOrientation;
             int32_t displayWidth;
             int32_t displayHeight;
             uint32_t pointerCount;
-            uint32_t empty3;
             /**
              * The "pointers" field must be the last field of the struct InputMessage.
              * When we send the struct InputMessage across the socket, we are not
@@ -355,8 +355,9 @@
                                 int32_t metaState, int32_t buttonState,
                                 MotionClassification classification, const ui::Transform& transform,
                                 float xPrecision, float yPrecision, float xCursorPosition,
-                                float yCursorPosition, int32_t displayWidth, int32_t displayHeight,
-                                nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,
+                                float yCursorPosition, uint32_t displayOrientation,
+                                int32_t displayWidth, int32_t displayHeight, nsecs_t downTime,
+                                nsecs_t eventTime, uint32_t pointerCount,
                                 const PointerProperties* pointerProperties,
                                 const PointerCoords* pointerCoords);
 
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 821ec16..2edb4e4 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -70,6 +70,7 @@
         mGenerationNumber(0),
         mSharedBufferMode(false),
         mAutoRefresh(false),
+        mAutoPrerotation(false),
         mSharedBufferSlot(BufferItem::INVALID_BUFFER_SLOT),
         mSharedBufferHasBeenQueued(false),
         mQueriedSupportedTimestamps(false),
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 51ddc50..b0c631a 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -164,6 +164,9 @@
     // Transform applied to individual windows.
     ui::Transform transform;
 
+    // Display orientation. Used for compatibility raw coordinates.
+    uint32_t displayOrientation = ui::Transform::ROT_0;
+
     // Display size in its natural rotation. Used to rotate raw coordinates for compatibility.
     int32_t displayWidth = 0;
     int32_t displayHeight = 0;
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index 09c02ca..58f3981 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -59,6 +59,7 @@
     i.globalScaleFactor = 0.3;
     i.alpha = 0.7;
     i.transform.set({0.4, -1, 100, 0.5, 0, 40, 0, 0, 1});
+    i.displayOrientation = ui::Transform::ROT_0;
     i.displayWidth = 1000;
     i.displayHeight = 2000;
     i.visible = false;
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 1a142c9..02cf9eb 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -68,12 +68,10 @@
 
     static_libs: [
         "libui-types",
-        "libgui_window_info_static",
     ],
 
     export_static_lib_headers: [
         "libui-types",
-        "libgui_window_info_static",
     ],
 
     target: {
@@ -96,6 +94,14 @@
                 "libui",
             ],
 
+            static_libs: [
+                "libgui_window_info_static",
+            ],
+
+            export_static_lib_headers: [
+                "libgui_window_info_static",
+            ],
+
             sanitize: {
                 misc_undefined: ["integer"],
             },
@@ -117,10 +123,15 @@
             ],
             static_libs: [
                 "libhostgraphics",
+                "libgui_window_info_static",
             ],
             shared_libs: [
                 "libbinder",
             ],
+
+            export_static_lib_headers: [
+                "libgui_window_info_static",
+            ],
         },
     },
 
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 906e3e5..2860aa3 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -66,14 +66,11 @@
     return result;
 }
 
-// Rotates the given point to the transform's orientation. If the display width and height are
+// Rotates the given point to the specified orientation. If the display width and height are
 // provided, the point is rotated in the screen space. Otherwise, the point is rotated about the
 // origin. This helper is used to avoid the extra overhead of creating new Transforms.
-vec2 rotatePoint(const ui::Transform& transform, float x, float y, int32_t displayWidth = 0,
+vec2 rotatePoint(uint32_t orientation, float x, float y, int32_t displayWidth = 0,
                  int32_t displayHeight = 0) {
-    // 0x7 encapsulates all 3 rotations (see ui::Transform::RotationFlags)
-    static const int ALL_ROTATIONS_MASK = 0x7;
-    const uint32_t orientation = (transform.getOrientation() & ALL_ROTATIONS_MASK);
     if (orientation == ui::Transform::ROT_0) {
         return {x, y};
     }
@@ -425,9 +422,9 @@
                              int32_t buttonState, MotionClassification classification,
                              const ui::Transform& transform, float xPrecision, float yPrecision,
                              float rawXCursorPosition, float rawYCursorPosition,
-                             int32_t displayWidth, int32_t displayHeight, nsecs_t downTime,
-                             nsecs_t eventTime, size_t pointerCount,
-                             const PointerProperties* pointerProperties,
+                             uint32_t displayOrientation, int32_t displayWidth,
+                             int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime,
+                             size_t pointerCount, const PointerProperties* pointerProperties,
                              const PointerCoords* pointerCoords) {
     InputEvent::initialize(id, deviceId, source, displayId, hmac);
     mAction = action;
@@ -442,6 +439,7 @@
     mYPrecision = yPrecision;
     mRawXCursorPosition = rawXCursorPosition;
     mRawYCursorPosition = rawYCursorPosition;
+    mDisplayOrientation = displayOrientation;
     mDisplayWidth = displayWidth;
     mDisplayHeight = displayHeight;
     mDownTime = downTime;
@@ -467,6 +465,7 @@
     mYPrecision = other->mYPrecision;
     mRawXCursorPosition = other->mRawXCursorPosition;
     mRawYCursorPosition = other->mRawYCursorPosition;
+    mDisplayOrientation = other->mDisplayOrientation;
     mDisplayWidth = other->mDisplayWidth;
     mDisplayHeight = other->mDisplayHeight;
     mDownTime = other->mDownTime;
@@ -535,8 +534,8 @@
         // For compatibility, convert raw coordinates into "oriented screen space". Once app
         // developers are educated about getRaw, we can consider removing this.
         const vec2 xy = shouldDisregardWindowTranslation(mSource)
-                ? rotatePoint(mTransform, coords->getX(), coords->getY())
-                : rotatePoint(mTransform, coords->getX(), coords->getY(), mDisplayWidth,
+                ? rotatePoint(mDisplayOrientation, coords->getX(), coords->getY())
+                : rotatePoint(mDisplayOrientation, coords->getX(), coords->getY(), mDisplayWidth,
                               mDisplayHeight);
         static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
         return xy[axis];
@@ -545,9 +544,9 @@
     if (axis == AMOTION_EVENT_AXIS_RELATIVE_X || axis == AMOTION_EVENT_AXIS_RELATIVE_Y) {
         // For compatibility, since we convert raw coordinates into "oriented screen space", we
         // need to convert the relative axes into the same orientation for consistency.
-        const vec2 relativeXy =
-                rotatePoint(mTransform, coords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
-                            coords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y));
+        const vec2 relativeXy = rotatePoint(mDisplayOrientation,
+                                            coords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X),
+                                            coords->getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y));
         return axis == AMOTION_EVENT_AXIS_RELATIVE_X ? relativeXy.x : relativeXy.y;
     }
 
@@ -698,6 +697,7 @@
     mYPrecision = parcel->readFloat();
     mRawXCursorPosition = parcel->readFloat();
     mRawYCursorPosition = parcel->readFloat();
+    mDisplayOrientation = parcel->readUint32();
     mDisplayWidth = parcel->readInt32();
     mDisplayHeight = parcel->readInt32();
     mDownTime = parcel->readInt64();
@@ -759,6 +759,7 @@
     parcel->writeFloat(mYPrecision);
     parcel->writeFloat(mRawXCursorPosition);
     parcel->writeFloat(mRawYCursorPosition);
+    parcel->writeUint32(mDisplayOrientation);
     parcel->writeInt32(mDisplayWidth);
     parcel->writeInt32(mDisplayHeight);
     parcel->writeInt64(mDownTime);
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index d6c1161..ea8b9a7 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -242,9 +242,11 @@
             msg->body.motion.xCursorPosition = body.motion.xCursorPosition;
             // float yCursorPosition
             msg->body.motion.yCursorPosition = body.motion.yCursorPosition;
-            // int32_t displayW
+            // uint32_t displayOrientation
+            msg->body.motion.displayOrientation = body.motion.displayOrientation;
+            // int32_t displayWidth
             msg->body.motion.displayWidth = body.motion.displayWidth;
-            // int32_t displayH
+            // int32_t displayHeight
             msg->body.motion.displayHeight = body.motion.displayHeight;
             // uint32_t pointerCount
             msg->body.motion.pointerCount = body.motion.pointerCount;
@@ -533,9 +535,10 @@
         std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton, int32_t flags,
         int32_t edgeFlags, int32_t metaState, int32_t buttonState,
         MotionClassification classification, const ui::Transform& transform, float xPrecision,
-        float yPrecision, float xCursorPosition, float yCursorPosition, int32_t displayWidth,
-        int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,
-        const PointerProperties* pointerProperties, const PointerCoords* pointerCoords) {
+        float yPrecision, float xCursorPosition, float yCursorPosition, uint32_t displayOrientation,
+        int32_t displayWidth, int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime,
+        uint32_t pointerCount, const PointerProperties* pointerProperties,
+        const PointerCoords* pointerCoords) {
     if (ATRACE_ENABLED()) {
         std::string message = StringPrintf(
                 "publishMotionEvent(inputChannel=%s, action=%" PRId32 ")",
@@ -593,6 +596,7 @@
     msg.body.motion.yPrecision = yPrecision;
     msg.body.motion.xCursorPosition = xCursorPosition;
     msg.body.motion.yCursorPosition = yCursorPosition;
+    msg.body.motion.displayOrientation = displayOrientation;
     msg.body.motion.displayWidth = displayWidth;
     msg.body.motion.displayHeight = displayHeight;
     msg.body.motion.downTime = downTime;
@@ -1361,9 +1365,9 @@
                       msg->body.motion.buttonState, msg->body.motion.classification, transform,
                       msg->body.motion.xPrecision, msg->body.motion.yPrecision,
                       msg->body.motion.xCursorPosition, msg->body.motion.yCursorPosition,
-                      msg->body.motion.displayWidth, msg->body.motion.displayHeight,
-                      msg->body.motion.downTime, msg->body.motion.eventTime, pointerCount,
-                      pointerProperties, pointerCoords);
+                      msg->body.motion.displayOrientation, msg->body.motion.displayWidth,
+                      msg->body.motion.displayHeight, msg->body.motion.downTime,
+                      msg->body.motion.eventTime, pointerCount, pointerProperties, pointerCoords);
 }
 
 void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index f1a26ec..a2fa319 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -272,8 +272,9 @@
                       AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY,
                       MotionClassification::NONE, mTransform, 2.0f, 2.1f,
                       AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                      INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_DOWN_TIME,
-                      ARBITRARY_EVENT_TIME, 2, pointerProperties, pointerCoords);
+                      ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
+                      ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2, pointerProperties,
+                      pointerCoords);
 
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111);
@@ -593,8 +594,9 @@
                      AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
                      MotionClassification::NONE, identityTransform, 0 /*xPrecision*/,
                      0 /*yPrecision*/, 3 + RADIUS /*xCursorPosition*/, 2 /*yCursorPosition*/,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 0 /*downTime*/, 0 /*eventTime*/,
-                     pointerCount, pointerProperties, pointerCoords);
+                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
+                     0 /*downTime*/, 0 /*eventTime*/, pointerCount, pointerProperties,
+                     pointerCoords);
     float originalRawX = 0 + 3;
     float originalRawY = -RADIUS + 2;
 
@@ -636,7 +638,8 @@
 }
 
 MotionEvent createTouchDownEvent(float x, float y, float dx, float dy,
-                                 const ui::Transform& transform) {
+                                 const ui::Transform& transform,
+                                 uint32_t displayOrientation = ui::Transform::ROT_0) {
     std::vector<PointerProperties> pointerProperties;
     pointerProperties.push_back(PointerProperties{/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER});
     std::vector<PointerCoords> pointerCoords;
@@ -652,7 +655,8 @@
                      /* actionButton */ 0, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE,
                      /* buttonState */ 0, MotionClassification::NONE, transform,
                      /* xPrecision */ 0, /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, /* displayWidth */ 400,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, displayOrientation,
+                     /* displayWidth */ 400,
                      /* displayHeight */ 800, eventTime, eventTime, pointerCoords.size(),
                      pointerProperties.data(), pointerCoords.data());
     return event;
@@ -663,7 +667,7 @@
     ui::Transform identity;
     ui::Transform xform(ui::Transform::ROT_90, 800, 400);
     xform.set(xform.tx() + 20, xform.ty() + 40);
-    MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform);
+    MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_90);
     ASSERT_EQ(700, event.getRawX(0));
     ASSERT_EQ(60, event.getRawY(0));
     ASSERT_NE(event.getRawX(0), event.getX(0));
@@ -703,7 +707,7 @@
         // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
         ui::Transform xform(ui::Transform::ROT_90, 800, 400);
         xform.set(xform.tx() + 20, xform.ty() + 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform);
+        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_90);
         event.setSource(source);
 
         // Since this event comes from a non-pointer source, it should include rotation but not
@@ -736,7 +740,7 @@
         // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
         ui::Transform xform(ui::Transform::ROT_90, 800, 400);
         xform.set(xform.tx() + 20, xform.ty() + 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform);
+        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_90);
         ASSERT_EQ(700, event.getRawX(0));
         ASSERT_EQ(60, event.getRawY(0));
         ASSERT_NE(event.getRawX(0), event.getX(0));
@@ -750,7 +754,7 @@
         // Same as above, but check rotate-180.
         ui::Transform xform(ui::Transform::ROT_180, 400, 800);
         xform.set(xform.tx() + 20, xform.ty() + 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform);
+        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_180);
         ASSERT_EQ(340, event.getRawX(0));
         ASSERT_EQ(700, event.getRawY(0));
         // Relative values should be rotated but not translated.
@@ -762,13 +766,25 @@
         // Same as above, but check rotate-270.
         ui::Transform xform(ui::Transform::ROT_270, 800, 400);
         xform.set(xform.tx() + 20, xform.ty() + 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform);
+        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_270);
         ASSERT_EQ(100, event.getRawX(0));
         ASSERT_EQ(340, event.getRawY(0));
         // Relative values should be rotated but not translated.
         ASSERT_EQ(96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
         ASSERT_EQ(-42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
     }
+
+    {
+        // Finally, check that raw isn't effected by transform
+        ui::Transform xform(ui::Transform::ROT_270, 800, 400);
+        xform.set(xform.tx() + 20, xform.ty() + 40);
+        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform, ui::Transform::ROT_90);
+        ASSERT_EQ(700, event.getRawX(0));
+        ASSERT_EQ(60, event.getRawY(0));
+        // Relative values should be rotated but not translated.
+        ASSERT_EQ(96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
+        ASSERT_EQ(-42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
+    }
 }
 
 TEST_F(MotionEventTest, Initialize_SetsClassification) {
@@ -794,9 +810,9 @@
                          DISPLAY_ID, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0,
                          AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0, classification,
                          identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION, INVALID_DISPLAY_SIZE,
-                         INVALID_DISPLAY_SIZE, 0 /*downTime*/, 0 /*eventTime*/, pointerCount,
-                         pointerProperties, pointerCoords);
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+                         INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 0 /*downTime*/,
+                         0 /*eventTime*/, pointerCount, pointerProperties, pointerCoords);
         ASSERT_EQ(classification, event.getClassification());
     }
 }
@@ -816,9 +832,9 @@
     event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_MOUSE, DISPLAY_ID,
                      INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0, AMOTION_EVENT_EDGE_FLAG_NONE,
                      AMETA_NONE, 0, MotionClassification::NONE, identityTransform, 0, 0,
-                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, INVALID_DISPLAY_SIZE,
-                     INVALID_DISPLAY_SIZE, 0 /*downTime*/, 0 /*eventTime*/, pointerCount,
-                     pointerProperties, pointerCoords);
+                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, ui::Transform::ROT_0,
+                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 0 /*downTime*/, 0 /*eventTime*/,
+                     pointerCount, pointerProperties, pointerCoords);
     event.offsetLocation(20, 60);
     ASSERT_EQ(280, event.getRawXCursorPosition());
     ASSERT_EQ(540, event.getRawYCursorPosition());
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index d0c337c..5d1f2c3 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -163,6 +163,7 @@
     constexpr float yPrecision = 0.5;
     constexpr float xCursorPosition = 1.3;
     constexpr float yCursorPosition = 50.6;
+    constexpr uint32_t displayOrientation = ui::Transform::ROT_0;
     constexpr int32_t displayWidth = 1000;
     constexpr int32_t displayHeight = 2000;
     constexpr nsecs_t downTime = 3;
@@ -193,9 +194,9 @@
     status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
                                             actionButton, flags, edgeFlags, metaState, buttonState,
                                             classification, transform, xPrecision, yPrecision,
-                                            xCursorPosition, yCursorPosition, displayWidth,
-                                            displayHeight, downTime, eventTime, pointerCount,
-                                            pointerProperties, pointerCoords);
+                                            xCursorPosition, yCursorPosition, displayOrientation,
+                                            displayWidth, displayHeight, downTime, eventTime,
+                                            pointerCount, pointerProperties, pointerCoords);
     ASSERT_EQ(OK, status)
             << "publisher publishMotionEvent should return OK";
 
@@ -232,6 +233,7 @@
     EXPECT_EQ(yCursorPosition, motionEvent->getRawYCursorPosition());
     EXPECT_EQ(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition());
     EXPECT_EQ(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition());
+    EXPECT_EQ(displayOrientation, motionEvent->getDisplayOrientation());
     EXPECT_EQ(displayWidth, motionEvent->getDisplaySize().x);
     EXPECT_EQ(displayHeight, motionEvent->getDisplaySize().y);
     EXPECT_EQ(downTime, motionEvent->getDownTime());
@@ -461,8 +463,9 @@
     status = mPublisher->publishMotionEvent(0, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
                                             0, 0, 0, MotionClassification::NONE, identityTransform,
                                             0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, 0, 0, 0,
-                                            pointerCount, pointerProperties, pointerCoords);
+                                            AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                            ui::Transform::ROT_0, 0, 0, 0, 0, pointerCount,
+                                            pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
 }
@@ -477,8 +480,9 @@
     status = mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
                                             0, 0, 0, MotionClassification::NONE, identityTransform,
                                             0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, 0, 0, 0,
-                                            pointerCount, pointerProperties, pointerCoords);
+                                            AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                            ui::Transform::ROT_0, 0, 0, 0, 0, pointerCount,
+                                            pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
 }
@@ -498,8 +502,9 @@
     status = mPublisher->publishMotionEvent(1, InputEvent::nextId(), 0, 0, 0, INVALID_HMAC, 0, 0, 0,
                                             0, 0, 0, MotionClassification::NONE, identityTransform,
                                             0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                                            AMOTION_EVENT_INVALID_CURSOR_POSITION, 0, 0, 0, 0,
-                                            pointerCount, pointerProperties, pointerCoords);
+                                            AMOTION_EVENT_INVALID_CURSOR_POSITION,
+                                            ui::Transform::ROT_0, 0, 0, 0, 0, pointerCount,
+                                            pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
 }
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index 5861d55..59fed1f 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -74,10 +74,10 @@
   CHECK_OFFSET(InputMessage::Body::Motion, yPrecision, 124);
   CHECK_OFFSET(InputMessage::Body::Motion, xCursorPosition, 128);
   CHECK_OFFSET(InputMessage::Body::Motion, yCursorPosition, 132);
-  CHECK_OFFSET(InputMessage::Body::Motion, displayWidth, 136);
-  CHECK_OFFSET(InputMessage::Body::Motion, displayHeight, 140);
-  CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 144);
-  CHECK_OFFSET(InputMessage::Body::Motion, empty3, 148);
+  CHECK_OFFSET(InputMessage::Body::Motion, displayOrientation, 136);
+  CHECK_OFFSET(InputMessage::Body::Motion, displayWidth, 140);
+  CHECK_OFFSET(InputMessage::Body::Motion, displayHeight, 144);
+  CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 148);
   CHECK_OFFSET(InputMessage::Body::Motion, pointers, 152);
 
   CHECK_OFFSET(InputMessage::Body::Focus, eventId, 0);
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 8ae7dcc..13e2b02 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -184,9 +184,9 @@
                          AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
                          MotionClassification::NONE, identityTransform, 0 /*xPrecision*/,
                          0 /*yPrecision*/, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION, INVALID_DISPLAY_SIZE,
-                         INVALID_DISPLAY_SIZE, 0 /*downTime*/, entry.eventTime.count(),
-                         pointerCount, properties, coords);
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+                         INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 0 /*downTime*/,
+                         entry.eventTime.count(), pointerCount, properties, coords);
 
         events.emplace_back(event);
     }
diff --git a/libs/input/tests/VerifiedInputEvent_test.cpp b/libs/input/tests/VerifiedInputEvent_test.cpp
index a0c9c17..b29c9a4 100644
--- a/libs/input/tests/VerifiedInputEvent_test.cpp
+++ b/libs/input/tests/VerifiedInputEvent_test.cpp
@@ -47,9 +47,9 @@
                      INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0 /*actionButton*/, flags,
                      AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
                      MotionClassification::NONE, transform, 0.1 /*xPrecision*/, 0.2 /*yPrecision*/,
-                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, INVALID_DISPLAY_SIZE,
-                     INVALID_DISPLAY_SIZE, 100 /*downTime*/, 200 /*eventTime*/, pointerCount,
-                     pointerProperties, pointerCoords);
+                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, ui::Transform::ROT_0,
+                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 100 /*downTime*/,
+                     200 /*eventTime*/, pointerCount, pointerProperties, pointerCoords);
     return event;
 }
 
diff --git a/libs/renderengine/skia/filters/BlurFilter.h b/libs/renderengine/skia/filters/BlurFilter.h
index a8e6dd7..7110018 100644
--- a/libs/renderengine/skia/filters/BlurFilter.h
+++ b/libs/renderengine/skia/filters/BlurFilter.h
@@ -42,7 +42,7 @@
     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;
+    static constexpr float kMaxCrossFadeRadius = 10.0f;
 
     explicit BlurFilter();
     virtual ~BlurFilter(){};
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 55deec4..f77c029 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -237,8 +237,8 @@
                      /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
                      identityTransform, /* xPrecision */ 0,
                      /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, INVALID_DISPLAY_SIZE,
-                     INVALID_DISPLAY_SIZE, currentTime, currentTime,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, currentTime, currentTime,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     return event;
 }
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 881024f..b5ed064 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -296,12 +296,14 @@
 volatile int32_t DispatchEntry::sNextSeqAtomic;
 
 DispatchEntry::DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                             ui::Transform transform, float globalScaleFactor, int2 displaySize)
+                             ui::Transform transform, float globalScaleFactor,
+                             uint32_t displayOrientation, int2 displaySize)
       : seq(nextSeq()),
         eventEntry(std::move(eventEntry)),
         targetFlags(targetFlags),
         transform(transform),
         globalScaleFactor(globalScaleFactor),
+        displayOrientation(displayOrientation),
         displaySize(displaySize),
         deliveryTime(0),
         resolvedAction(0),
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 89d7659..1b7fcf2 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -215,6 +215,7 @@
     int32_t targetFlags;
     ui::Transform transform;
     float globalScaleFactor;
+    uint32_t displayOrientation;
     int2 displaySize;
     // Both deliveryTime and timeoutTime are only populated when the entry is sent to the app,
     // and will be undefined before that.
@@ -228,7 +229,8 @@
     int32_t resolvedFlags;
 
     DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                  ui::Transform transform, float globalScaleFactor, int2 displaySize);
+                  ui::Transform transform, float globalScaleFactor, uint32_t displayOrientation,
+                  int2 displaySize);
 
     inline bool hasForegroundTarget() const { return targetFlags & InputTarget::FLAG_FOREGROUND; }
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 4835795..9f4108d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -331,6 +331,7 @@
             // don't depend on the window transform.
             return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, identityTransform,
                                                    1.0f /*globalScaleFactor*/,
+                                                   inputTarget.displayOrientation,
                                                    inputTarget.displaySize);
         }
     }
@@ -339,6 +340,7 @@
         const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
         return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform,
                                                inputTarget.globalScaleFactor,
+                                               inputTarget.displayOrientation,
                                                inputTarget.displaySize);
     }
 
@@ -391,6 +393,7 @@
     std::unique_ptr<DispatchEntry> dispatchEntry =
             std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags,
                                             firstPointerTransform, inputTarget.globalScaleFactor,
+                                            inputTarget.displayOrientation,
                                             inputTarget.displaySize);
     return dispatchEntry;
 }
@@ -2448,6 +2451,7 @@
         inputTarget.inputChannel = inputChannel;
         inputTarget.flags = targetFlags;
         inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
+        inputTarget.displayOrientation = windowInfo->displayOrientation;
         inputTarget.displaySize =
                 int2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight);
         inputTargets.push_back(inputTarget);
@@ -3161,6 +3165,7 @@
                                                      motionEntry.xPrecision, motionEntry.yPrecision,
                                                      motionEntry.xCursorPosition,
                                                      motionEntry.yCursorPosition,
+                                                     dispatchEntry->displayOrientation,
                                                      dispatchEntry->displaySize.x,
                                                      dispatchEntry->displaySize.y,
                                                      motionEntry.downTime, motionEntry.eventTime,
@@ -3873,9 +3878,9 @@
                              args->action, args->actionButton, args->flags, args->edgeFlags,
                              args->metaState, args->buttonState, args->classification, transform,
                              args->xPrecision, args->yPrecision, args->xCursorPosition,
-                             args->yCursorPosition, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                             args->downTime, args->eventTime, args->pointerCount,
-                             args->pointerProperties, args->pointerCoords);
+                             args->yCursorPosition, ui::Transform::ROT_0, INVALID_DISPLAY_SIZE,
+                             INVALID_DISPLAY_SIZE, args->downTime, args->eventTime,
+                             args->pointerCount, args->pointerProperties, args->pointerCoords);
 
             policyFlags |= POLICY_FLAG_FILTERED;
             if (!mPolicy->filterInputEvent(&event, policyFlags)) {
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 9c62b7e..7c463c8 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -101,6 +101,9 @@
     // (ignored for KeyEvents)
     float globalScaleFactor = 1.0f;
 
+    // Current display orientation
+    uint32_t displayOrientation = ui::Transform::ROT_0;
+
     // Display-size in its natural rotation. Used for compatibility transform of raw coordinates.
     int2 displaySize = {INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE};
 
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 6cad839..a568220 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -530,8 +530,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      /*action*/ -1, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, INVALID_DISPLAY_SIZE,
-                     INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -544,7 +544,8 @@
                              (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
+                     ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -556,7 +557,8 @@
                              (~0U << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
+                     ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -569,7 +571,8 @@
                              (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
+                     ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -581,7 +584,8 @@
                              (~0U << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                      0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
                      AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
+                     ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -592,8 +596,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, INVALID_DISPLAY_SIZE,
-                     INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 0, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -603,8 +607,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, INVALID_DISPLAY_SIZE,
-                     INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -616,8 +620,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, INVALID_DISPLAY_SIZE,
-                     INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -628,8 +632,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, INVALID_DISPLAY_SIZE,
-                     INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -642,8 +646,8 @@
     event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
                      AMOTION_EVENT_ACTION_DOWN, 0, 0, edgeFlags, metaState, 0, classification,
                      identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, INVALID_DISPLAY_SIZE,
-                     INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 2, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -1264,8 +1268,9 @@
                          mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE,
                          mButtonState, MotionClassification::NONE, identityTransform,
                          /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
-                         mRawYCursorPosition, mDisplayWidth, mDisplayHeight, mEventTime, mEventTime,
-                         mPointers.size(), pointerProperties.data(), pointerCoords.data());
+                         mRawYCursorPosition, mDisplayOrientation, mDisplayWidth, mDisplayHeight,
+                         mEventTime, mEventTime, mPointers.size(), pointerProperties.data(),
+                         pointerCoords.data());
 
         return event;
     }
@@ -1280,6 +1285,7 @@
     int32_t mFlags{0};
     float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
     float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+    uint32_t mDisplayOrientation{ui::Transform::ROT_0};
     int32_t mDisplayWidth{INVALID_DISPLAY_SIZE};
     int32_t mDisplayHeight{INVALID_DISPLAY_SIZE};
 
@@ -3200,9 +3206,9 @@
                          DISPLAY_ID, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN, 0, 0,
                          AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0, MotionClassification::NONE,
                          identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                         0 /*AMOTION_EVENT_INVALID_DISPLAY_SIZE*/,
-                         0 /*AMOTION_EVENT_INVALID_DISPLAY_SIZE*/, eventTime, eventTime,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
+                         0 /*INVALID_DISPLAY_SIZE*/, 0 /*INVALID_DISPLAY_SIZE*/, eventTime,
+                         eventTime,
                          /*pointerCount*/ 1, pointerProperties, pointerCoords);
 
         const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
index 1416b1e..0ef0b99 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -166,6 +166,9 @@
     // Enables (or disables) layer caching on this output
     virtual void setLayerCachingEnabled(bool) = 0;
 
+    // Enables (or disables) layer caching texture pool on this output
+    virtual void setLayerCachingTexturePoolEnabled(bool) = 0;
+
     // Sets the projection state to use
     virtual void setProjection(ui::Rotation orientation, const Rect& layerStackSpaceRect,
                                const Rect& orientedDisplaySpaceRect) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
index f832084..ddcc907 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -41,6 +41,7 @@
     std::optional<DisplayId> getDisplayId() const override;
     void setCompositionEnabled(bool) override;
     void setLayerCachingEnabled(bool) override;
+    void setLayerCachingTexturePoolEnabled(bool) override;
     void setProjection(ui::Rotation orientation, const Rect& layerStackSpaceRect,
                        const Rect& orientedDisplaySpaceRect) override;
     void setDisplaySize(const ui::Size&) override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
index 7534548..8ec15ed 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Flattener.h
@@ -80,6 +80,8 @@
     void renderCachedSets(const OutputCompositionState& outputState,
                           std::optional<std::chrono::steady_clock::time_point> renderDeadline);
 
+    void setTexturePoolEnabled(bool enabled) { mTexturePool.setEnabled(enabled); }
+
     void dump(std::string& result) const;
     void dumpLayers(std::string& result) const;
 
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
index be34153..76d5e81 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/Planner.h
@@ -64,6 +64,8 @@
     void renderCachedSets(const OutputCompositionState& outputState,
                           std::optional<std::chrono::steady_clock::time_point> renderDeadline);
 
+    void setTexturePoolEnabled(bool enabled) { mFlattener.setTexturePoolEnabled(enabled); }
+
     void dump(const Vector<String16>& args, std::string&);
 
 private:
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/TexturePool.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/TexturePool.h
index fb53ee0..d607c75 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/TexturePool.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/TexturePool.h
@@ -63,7 +63,8 @@
         sp<Fence> mFence;
     };
 
-    TexturePool(renderengine::RenderEngine& renderEngine) : mRenderEngine(renderEngine) {}
+    TexturePool(renderengine::RenderEngine& renderEngine)
+          : mRenderEngine(renderEngine), mEnabled(false) {}
 
     virtual ~TexturePool() = default;
 
@@ -78,6 +79,12 @@
     // to the pool.
     std::shared_ptr<AutoTexture> borrowTexture();
 
+    // Enables or disables the pool. When the pool is disabled, no buffers will
+    // be held by the pool. This is useful when the active display changes.
+    void setEnabled(bool enable);
+
+    void dump(std::string& out) const;
+
 protected:
     // Proteted visibility so that they can be used for testing
     const static constexpr size_t kMinPoolSize = 3;
@@ -95,8 +102,10 @@
     // Returns a previously borrowed texture to the pool.
     void returnTexture(std::shared_ptr<renderengine::ExternalTexture>&& texture,
                        const sp<Fence>& fence);
+    void allocatePool();
     renderengine::RenderEngine& mRenderEngine;
     ui::Size mSize;
+    bool mEnabled;
 };
 
 } // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
index 8e777e3..8fdf3ae 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -37,6 +37,7 @@
 
     MOCK_METHOD1(setCompositionEnabled, void(bool));
     MOCK_METHOD1(setLayerCachingEnabled, void(bool));
+    MOCK_METHOD1(setLayerCachingTexturePoolEnabled, void(bool));
     MOCK_METHOD3(setProjection, void(ui::Rotation, const Rect&, const Rect&));
     MOCK_METHOD1(setDisplaySize, void(const ui::Size&));
     MOCK_METHOD2(setLayerStackFilter, void(uint32_t, bool));
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index cafcb40..3310a71 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -146,6 +146,12 @@
     }
 }
 
+void Output::setLayerCachingTexturePoolEnabled(bool enabled) {
+    if (mPlanner) {
+        mPlanner->setTexturePoolEnabled(enabled);
+    }
+}
+
 void Output::setProjection(ui::Rotation orientation, const Rect& layerStackSpaceRect,
                            const Rect& orientedDisplaySpaceRect) {
     auto& outputState = editState();
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
index b24274e..f80ec22 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -175,7 +175,7 @@
     LayerFE::ClientCompositionTargetSettings targetSettings{
             .clip = Region(viewport),
             .needsFiltering = false,
-            .isSecure = true,
+            .isSecure = outputState.isSecure,
             .supportsProtectedContent = false,
             .clearRegion = clearRegion,
             .viewport = viewport,
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index f033279..8e2c182 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -205,6 +205,9 @@
                         durationString(lastUpdate).c_str());
 
     dumpLayers(result);
+
+    base::StringAppendF(&result, "\n");
+    mTexturePool.dump(result);
 }
 
 size_t Flattener::calculateDisplayCost(const std::vector<const LayerState*>& layers) const {
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp b/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp
index e3772a2..497c433 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/TexturePool.cpp
@@ -24,14 +24,22 @@
 
 namespace android::compositionengine::impl::planner {
 
+void TexturePool::allocatePool() {
+    mPool.clear();
+    if (mEnabled && mSize.isValid()) {
+        mPool.resize(kMinPoolSize);
+        std::generate_n(mPool.begin(), kMinPoolSize, [&]() {
+            return Entry{genTexture(), nullptr};
+        });
+    }
+}
+
 void TexturePool::setDisplaySize(ui::Size size) {
     if (mSize == size) {
         return;
     }
     mSize = size;
-    mPool.clear();
-    mPool.resize(kMinPoolSize);
-    std::generate_n(mPool.begin(), kMinPoolSize, [&]() { return Entry{genTexture(), nullptr}; });
+    allocatePool();
 }
 
 std::shared_ptr<TexturePool::AutoTexture> TexturePool::borrowTexture() {
@@ -46,7 +54,12 @@
 
 void TexturePool::returnTexture(std::shared_ptr<renderengine::ExternalTexture>&& texture,
                                 const sp<Fence>& fence) {
-    // Drop the texture on the floor if the pool is no longer tracking textures of the same size.
+    // Drop the texture on the floor if the pool is not enabled
+    if (!mEnabled) {
+        return;
+    }
+
+    // Or the texture on the floor if the pool is no longer tracking textures of the same size.
     if (static_cast<int32_t>(texture->getBuffer()->getWidth()) != mSize.getWidth() ||
         static_cast<int32_t>(texture->getBuffer()->getHeight()) != mSize.getHeight()) {
         ALOGV("Deallocating texture from Planner's pool - display size changed (previous: (%dx%d), "
@@ -81,4 +94,15 @@
                                                    renderengine::ExternalTexture::Usage::WRITEABLE);
 }
 
+void TexturePool::setEnabled(bool enabled) {
+    mEnabled = enabled;
+    allocatePool();
+}
+
+void TexturePool::dump(std::string& out) const {
+    base::StringAppendF(&out,
+                        "TexturePool (%s) has %zu buffers of size [%" PRId32 ", %" PRId32 "]\n",
+                        mEnabled ? "enabled" : "disabled", mPool.size(), mSize.width, mSize.height);
+}
+
 } // namespace android::compositionengine::impl::planner
\ No newline at end of file
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index b05a594..b765337 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -51,6 +51,15 @@
 
     return expectedBlurSetting == arg.blurSetting;
 }
+
+MATCHER_P(ClientCompositionTargetSettingsSecureEq, expectedSecureSetting, "") {
+    *result_listener << "ClientCompositionTargetSettings' SecureSettings aren't equal \n";
+    *result_listener << "expected " << expectedSecureSetting << "\n";
+    *result_listener << "actual " << arg.isSecure << "\n";
+
+    return expectedSecureSetting == arg.isSecure;
+}
+
 static const ui::Size kOutputSize = ui::Size(1, 1);
 
 class CachedSetTest : public testing::Test {
@@ -315,7 +324,7 @@
     EXPECT_EQ(0u, cachedSet.getAge());
 }
 
-TEST_F(CachedSetTest, render) {
+TEST_F(CachedSetTest, renderUnsecureOutput) {
     // Skip the 0th layer to ensure that the bounding box of the layers is offset from (0, 0)
     CachedSet::Layer& layer1 = *mTestLayers[1]->cachedSetLayer.get();
     sp<mock::LayerFE> layerFE1 = mTestLayers[1]->layerFE;
@@ -348,9 +357,66 @@
         return NO_ERROR;
     };
 
-    EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1));
-    EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2));
+    EXPECT_CALL(*layerFE1,
+                prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(false)))
+            .WillOnce(Return(clientCompList1));
+    EXPECT_CALL(*layerFE2,
+                prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(false)))
+            .WillOnce(Return(clientCompList2));
     EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    mOutputState.isSecure = false;
+    cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
+    expectReadyBuffer(cachedSet);
+
+    EXPECT_EQ(mOutputState.framebufferSpace, cachedSet.getOutputSpace());
+    EXPECT_EQ(Rect(kOutputSize.width, kOutputSize.height), cachedSet.getTextureBounds());
+
+    // Now check that appending a new cached set properly cleans up RenderEngine resources.
+    CachedSet::Layer& layer3 = *mTestLayers[2]->cachedSetLayer.get();
+    cachedSet.append(CachedSet(layer3));
+}
+
+TEST_F(CachedSetTest, renderSecureOutput) {
+    // Skip the 0th layer to ensure that the bounding box of the layers is offset from (0, 0)
+    CachedSet::Layer& layer1 = *mTestLayers[1]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE1 = mTestLayers[1]->layerFE;
+    CachedSet::Layer& layer2 = *mTestLayers[2]->cachedSetLayer.get();
+    sp<mock::LayerFE> layerFE2 = mTestLayers[2]->layerFE;
+
+    CachedSet cachedSet(layer1);
+    cachedSet.append(CachedSet(layer2));
+
+    std::vector<compositionengine::LayerFE::LayerSettings> clientCompList1;
+    clientCompList1.push_back({});
+    clientCompList1[0].alpha = 0.5f;
+
+    std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
+    clientCompList2.push_back({});
+    clientCompList2[0].alpha = 0.75f;
+
+    const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
+                                const std::vector<const renderengine::LayerSettings*>& layers,
+                                const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
+                                base::unique_fd&&, base::unique_fd*) -> size_t {
+        EXPECT_EQ(mOutputState.framebufferSpace.content, displaySettings.physicalDisplay);
+        EXPECT_EQ(mOutputState.layerStackSpace.content, displaySettings.clip);
+        EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.orientation),
+                  displaySettings.orientation);
+        EXPECT_EQ(0.5f, layers[0]->alpha);
+        EXPECT_EQ(0.75f, layers[1]->alpha);
+        EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace);
+
+        return NO_ERROR;
+    };
+
+    EXPECT_CALL(*layerFE1,
+                prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(true)))
+            .WillOnce(Return(clientCompList1));
+    EXPECT_CALL(*layerFE2,
+                prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(true)))
+            .WillOnce(Return(clientCompList2));
+    EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Invoke(drawLayers));
+    mOutputState.isSecure = true;
     cachedSet.render(mRenderEngine, mTexturePool, mOutputState);
     expectReadyBuffer(cachedSet);
 
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/TexturePoolTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/TexturePoolTest.cpp
index b802e51..6fc90fe 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/TexturePoolTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/TexturePoolTest.cpp
@@ -42,6 +42,7 @@
         const ::testing::TestInfo* const test_info =
                 ::testing::UnitTest::GetInstance()->current_test_info();
         ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
+        mTexturePool.setEnabled(true);
         mTexturePool.setDisplaySize(kDisplaySize);
     }
 
@@ -130,5 +131,44 @@
               static_cast<int32_t>(texture->get()->getBuffer()->getHeight()));
 }
 
+TEST_F(TexturePoolTest, freesBuffersWhenDisabled) {
+    EXPECT_EQ(mTexturePool.getPoolSize(), mTexturePool.getMinPoolSize());
+
+    std::deque<std::shared_ptr<TexturePool::AutoTexture>> textures;
+    for (size_t i = 0; i < mTexturePool.getMinPoolSize() - 1; i++) {
+        textures.emplace_back(mTexturePool.borrowTexture());
+    }
+
+    EXPECT_EQ(mTexturePool.getPoolSize(), 1u);
+    mTexturePool.setEnabled(false);
+    EXPECT_EQ(mTexturePool.getPoolSize(), 0u);
+
+    textures.clear();
+    EXPECT_EQ(mTexturePool.getPoolSize(), 0u);
+}
+
+TEST_F(TexturePoolTest, doesNotHoldBuffersWhenDisabled) {
+    EXPECT_EQ(mTexturePool.getPoolSize(), mTexturePool.getMinPoolSize());
+    mTexturePool.setEnabled(false);
+    EXPECT_EQ(mTexturePool.getPoolSize(), 0u);
+
+    std::deque<std::shared_ptr<TexturePool::AutoTexture>> textures;
+    for (size_t i = 0; i < mTexturePool.getMinPoolSize() - 1; i++) {
+        textures.emplace_back(mTexturePool.borrowTexture());
+    }
+
+    EXPECT_EQ(mTexturePool.getPoolSize(), 0u);
+    textures.clear();
+    EXPECT_EQ(mTexturePool.getPoolSize(), 0u);
+}
+
+TEST_F(TexturePoolTest, reallocatesWhenReEnabled) {
+    EXPECT_EQ(mTexturePool.getPoolSize(), mTexturePool.getMinPoolSize());
+    mTexturePool.setEnabled(false);
+    EXPECT_EQ(mTexturePool.getPoolSize(), 0u);
+    mTexturePool.setEnabled(true);
+    EXPECT_EQ(mTexturePool.getPoolSize(), mTexturePool.getMinPoolSize());
+}
+
 } // namespace
 } // namespace android::compositionengine::impl::planner
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index d824649..c5dcd8e 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -958,7 +958,6 @@
 }
 
 bool Layer::setBlurRegions(const std::vector<BlurRegion>& blurRegions) {
-    mDrawingState.sequence++;
     mDrawingState.blurRegions = blurRegions;
     mDrawingState.modified = true;
     setTransactionFlags(eTransactionNeeded);
@@ -2298,6 +2297,10 @@
     ui::Transform toPhysicalDisplay;
     if (display) {
         toPhysicalDisplay = display->getTransform();
+        // getOrientation() without masking can contain more-significant bits (eg. ROT_INVALID).
+        static constexpr uint32_t ALL_ROTATIONS_MASK =
+                ui::Transform::ROT_90 | ui::Transform::ROT_180 | ui::Transform::ROT_270;
+        info.displayOrientation = toPhysicalDisplay.getOrientation() & ALL_ROTATIONS_MASK;
         info.displayWidth = display->getWidth();
         info.displayHeight = display->getHeight();
     }
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 28be962..b805bf6 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -84,10 +84,13 @@
                                                       VSyncTracker& tracker, nsecs_t now) {
     auto nextVsyncTime = tracker.nextAnticipatedVSyncTimeFrom(
             std::max(timing.earliestVsync, now + timing.workDuration + timing.readyDuration));
+    auto nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
 
     bool const wouldSkipAVsyncTarget =
             mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
-    if (wouldSkipAVsyncTarget) {
+    bool const wouldSkipAWakeup =
+            mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
+    if (wouldSkipAVsyncTarget && wouldSkipAWakeup) {
         return getExpectedCallbackTime(nextVsyncTime, timing);
     }
 
@@ -97,9 +100,9 @@
     if (alreadyDispatchedForVsync) {
         nextVsyncTime =
                 tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance);
+        nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
     }
 
-    auto const nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
     auto const nextReadyTime = nextVsyncTime - timing.readyDuration;
     mScheduleTiming = timing;
     mArmedInfo = {nextWakeupTime, nextVsyncTime, nextReadyTime};
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
index 194d808..245db0f 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
@@ -46,27 +46,36 @@
     return updateVsyncConfigLocked();
 }
 
-VsyncModulator::VsyncConfigOpt VsyncModulator::setTransactionSchedule(
-        TransactionSchedule schedule) {
+VsyncModulator::VsyncConfigOpt VsyncModulator::setTransactionSchedule(TransactionSchedule schedule,
+                                                                      const sp<IBinder>& token) {
+    std::lock_guard<std::mutex> lock(mMutex);
     switch (schedule) {
         case Schedule::EarlyStart:
-            ALOGW_IF(mEarlyWakeup, "%s: Duplicate EarlyStart", __FUNCTION__);
-            mEarlyWakeup = true;
+            if (token) {
+                mEarlyWakeupRequests.emplace(token);
+                token->linkToDeath(this);
+            } else {
+                ALOGW("%s: EarlyStart requested without a valid token", __func__);
+            }
             break;
-        case Schedule::EarlyEnd:
-            ALOGW_IF(!mEarlyWakeup, "%s: Unexpected EarlyEnd", __FUNCTION__);
-            mEarlyWakeup = false;
+        case Schedule::EarlyEnd: {
+            if (token && mEarlyWakeupRequests.erase(token) > 0) {
+                token->unlinkToDeath(this);
+            } else {
+                ALOGW("%s: Unexpected EarlyEnd", __func__);
+            }
             break;
+        }
         case Schedule::Late:
             // No change to mEarlyWakeup for non-explicit states.
             break;
     }
 
     if (mTraceDetailedInfo) {
-        ATRACE_INT("mEarlyWakeup", mEarlyWakeup);
+        ATRACE_INT("mEarlyWakeup", static_cast<int>(mEarlyWakeupRequests.size()));
     }
 
-    if (!mEarlyWakeup && schedule == Schedule::EarlyEnd) {
+    if (mEarlyWakeupRequests.empty() && schedule == Schedule::EarlyEnd) {
         mEarlyTransactionFrames = MIN_EARLY_TRANSACTION_FRAMES;
         mEarlyTransactionStartTime = mNow();
     }
@@ -76,7 +85,7 @@
         return std::nullopt;
     }
     mTransactionSchedule = schedule;
-    return updateVsyncConfig();
+    return updateVsyncConfigLocked();
 }
 
 VsyncModulator::VsyncConfigOpt VsyncModulator::onTransactionCommit() {
@@ -128,8 +137,8 @@
 const VsyncModulator::VsyncConfig& VsyncModulator::getNextVsyncConfig() const {
     // Early offsets are used if we're in the middle of a refresh rate
     // change, or if we recently begin a transaction.
-    if (mEarlyWakeup || mTransactionSchedule == Schedule::EarlyEnd || mEarlyTransactionFrames > 0 ||
-        mRefreshRateChangePending) {
+    if (!mEarlyWakeupRequests.empty() || mTransactionSchedule == Schedule::EarlyEnd ||
+        mEarlyTransactionFrames > 0 || mRefreshRateChangePending) {
         return mVsyncConfigSet.early;
     } else if (mEarlyGpuFrames > 0) {
         return mVsyncConfigSet.earlyGpu;
@@ -160,4 +169,11 @@
     return offsets;
 }
 
+void VsyncModulator::binderDied(const wp<IBinder>& who) {
+    std::lock_guard<std::mutex> lock(mMutex);
+    mEarlyWakeupRequests.erase(who);
+
+    static_cast<void>(updateVsyncConfigLocked());
+}
+
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h
index 9410768..b2b0451 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.h
@@ -19,8 +19,10 @@
 #include <chrono>
 #include <mutex>
 #include <optional>
+#include <unordered_set>
 
 #include <android-base/thread_annotations.h>
+#include <binder/IBinder.h>
 #include <utils/Timers.h>
 
 namespace android::scheduler {
@@ -35,7 +37,7 @@
 };
 
 // Modulates VSYNC phase depending on transaction schedule and refresh rate changes.
-class VsyncModulator {
+class VsyncModulator : public IBinder::DeathRecipient {
 public:
     // Number of frames to keep early offsets after an early transaction or GPU composition.
     // This acts as a low-pass filter in case subsequent transactions are delayed, or if the
@@ -91,7 +93,8 @@
     [[nodiscard]] VsyncConfig setVsyncConfigSet(const VsyncConfigSet&) EXCLUDES(mMutex);
 
     // Changes offsets in response to transaction flags or commit.
-    [[nodiscard]] VsyncConfigOpt setTransactionSchedule(TransactionSchedule);
+    [[nodiscard]] VsyncConfigOpt setTransactionSchedule(TransactionSchedule,
+                                                        const sp<IBinder>& = {}) EXCLUDES(mMutex);
     [[nodiscard]] VsyncConfigOpt onTransactionCommit();
 
     // Called when we send a refresh rate change to hardware composer, so that
@@ -104,6 +107,10 @@
 
     [[nodiscard]] VsyncConfigOpt onDisplayRefresh(bool usedGpuComposition);
 
+protected:
+    // Called from unit tests as well
+    void binderDied(const wp<IBinder>&) override EXCLUDES(mMutex);
+
 private:
     const VsyncConfig& getNextVsyncConfig() const REQUIRES(mMutex);
     [[nodiscard]] VsyncConfig updateVsyncConfig() EXCLUDES(mMutex);
@@ -116,8 +123,14 @@
 
     using Schedule = TransactionSchedule;
     std::atomic<Schedule> mTransactionSchedule = Schedule::Late;
-    std::atomic<bool> mEarlyWakeup = false;
 
+    struct WpHash {
+        size_t operator()(const wp<IBinder>& p) const {
+            return std::hash<IBinder*>()(p.unsafe_get());
+        }
+    };
+
+    std::unordered_set<wp<IBinder>, WpHash> mEarlyWakeupRequests GUARDED_BY(mMutex);
     std::atomic<bool> mRefreshRateChangePending = false;
 
     std::atomic<int> mEarlyTransactionFrames = 0;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 07f8562..7f5569b 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1151,6 +1151,12 @@
     }
 
     const auto upcomingModeInfo = MAIN_THREAD_GUARD(display->getUpcomingActiveMode());
+    if (!upcomingModeInfo.mode) {
+        // There is no pending mode change. This can happen if the active
+        // display changed and the mode change happened on a different display.
+        return;
+    }
+
     if (display->getActiveMode()->getSize() != upcomingModeInfo.mode->getSize()) {
         auto& state = mCurrentState.displays.editValueFor(display->getDisplayToken());
         // We need to generate new sequenceId in order to recreate the display (and this
@@ -1701,10 +1707,10 @@
     ATRACE_CALL();
 
     Mutex::Autolock lock(mStateLock);
-
-    if (const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId)) {
-        auto token = getPhysicalDisplayTokenLocked(*displayId);
-        auto display = getDisplayDeviceLocked(token);
+    const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId);
+    if (displayId) {
+        const auto token = getPhysicalDisplayTokenLocked(*displayId);
+        const auto display = getDisplayDeviceLocked(token);
         display->onVsync(timestamp);
     }
 
@@ -1712,8 +1718,10 @@
         return;
     }
 
-    if (hwcDisplayId != getHwComposer().getInternalHwcDisplayId()) {
-        // For now, we don't do anything with external display vsyncs.
+    const bool isActiveDisplay =
+            displayId && getPhysicalDisplayTokenLocked(*displayId) == mActiveDisplayToken;
+    if (!isActiveDisplay) {
+        // For now, we don't do anything with non active display vsyncs.
         return;
     }
 
@@ -3110,9 +3118,11 @@
 
 void SurfaceFlinger::initScheduler(const sp<DisplayDevice>& display) {
     if (mScheduler) {
-        // In practice it's not allowed to hotplug in/out the primary display once it's been
-        // connected during startup, but some tests do it, so just warn and return.
-        ALOGW("Can't re-init scheduler");
+        // If the scheduler is already initialized, this means that we received
+        // a hotplug(connected) on the primary display. In that case we should
+        // update the scheduler with the most recent display information.
+        ALOGW("Scheduler already initialized, updating instead");
+        mScheduler->setRefreshRateConfigs(display->holdRefreshRateConfigs());
         return;
     }
     const auto currRefreshRate = display->getActiveMode()->getFps();
@@ -3120,7 +3130,7 @@
                                                                       hal::PowerMode::OFF);
 
     mVsyncConfiguration = getFactory().createVsyncConfiguration(currRefreshRate);
-    mVsyncModulator.emplace(mVsyncConfiguration->getCurrentConfigs());
+    mVsyncModulator = sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs());
 
     // start the EventThread
     mScheduler = getFactory().createScheduler(display->holdRefreshRateConfigs(), *this);
@@ -3343,7 +3353,7 @@
 status_t SurfaceFlinger::addClientLayer(const sp<Client>& client, const sp<IBinder>& handle,
                                         const sp<IGraphicBufferProducer>& gbc, const sp<Layer>& lbc,
                                         const sp<IBinder>& parentHandle,
-                                        const sp<Layer>& parentLayer, bool addToCurrentState,
+                                        const sp<Layer>& parentLayer, bool addToRoot,
                                         uint32_t* outTransformHint) {
     if (mNumLayers >= ISurfaceComposer::MAX_LAYERS) {
         ALOGE("AddClientLayer failed, mNumLayers (%zu) >= MAX_LAYERS (%zu)", mNumLayers.load(),
@@ -3355,7 +3365,7 @@
     if (gbc != nullptr) {
         initialProducer = IInterface::asBinder(gbc);
     }
-    setLayerCreatedState(handle, lbc, parentHandle, parentLayer, initialProducer);
+    setLayerCreatedState(handle, lbc, parentHandle, parentLayer, initialProducer, addToRoot);
 
     // Create a transaction includes the initial parent and producer.
     Vector<ComposerState> states;
@@ -3398,9 +3408,10 @@
     return setTransactionFlags(flags, TransactionSchedule::Late);
 }
 
-uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags, TransactionSchedule schedule) {
+uint32_t SurfaceFlinger::setTransactionFlags(uint32_t flags, TransactionSchedule schedule,
+                                             const sp<IBinder>& token) {
     uint32_t old = mTransactionFlags.fetch_or(flags);
-    modulateVsync(&VsyncModulator::setTransactionSchedule, schedule);
+    modulateVsync(&VsyncModulator::setTransactionSchedule, schedule, token);
     if ((old & flags) == 0) signalTransaction();
     return old;
 }
@@ -3620,7 +3631,7 @@
         return TransactionSchedule::Late;
     }(state.flags);
 
-    setTransactionFlags(eTransactionFlushNeeded, schedule);
+    setTransactionFlags(eTransactionFlushNeeded, schedule, state.applyToken);
 }
 
 void SurfaceFlinger::waitForSynchronousTransaction(
@@ -3884,7 +3895,7 @@
     sp<Layer> layer = nullptr;
     if (s.surface) {
         if (what & layer_state_t::eLayerCreated) {
-            layer = handleLayerCreatedLocked(s.surface, privileged);
+            layer = handleLayerCreatedLocked(s.surface);
             if (layer) {
                 // put the created layer into mLayersByLocalBinderToken.
                 mLayersByLocalBinderToken.emplace(s.surface->localBinder(), layer);
@@ -4307,9 +4318,9 @@
         return result;
     }
 
-    bool addToCurrentState = callingThreadHasUnscopedSurfaceFlingerAccess();
-    result = addClientLayer(client, *handle, *gbp, layer, parentHandle, parentLayer,
-                            addToCurrentState, outTransformHint);
+    bool addToRoot = callingThreadHasUnscopedSurfaceFlingerAccess();
+    result = addClientLayer(client, *handle, *gbp, layer, parentHandle, parentLayer, addToRoot,
+                            outTransformHint);
     if (result != NO_ERROR) {
         return result;
     }
@@ -4536,8 +4547,7 @@
     if (currentMode == hal::PowerMode::OFF) {
         const auto activeDisplay = getDisplayDeviceLocked(mActiveDisplayToken);
         if (display->isInternal() && (!activeDisplay || !activeDisplay->isPoweredOn())) {
-            mActiveDisplayToken = display->getDisplayToken();
-            onActiveDisplayChangedLocked(getDisplayDeviceLocked(mActiveDisplayToken));
+            onActiveDisplayChangedLocked(display);
         }
         // Keep uclamp in a separate syscall and set it before changing to RT due to b/190237315.
         // We can merge the syscall later.
@@ -6860,10 +6870,10 @@
 
 void SurfaceFlinger::setLayerCreatedState(const sp<IBinder>& handle, const wp<Layer>& layer,
                                           const wp<IBinder>& parent, const wp<Layer> parentLayer,
-                                          const wp<IBinder>& producer) {
+                                          const wp<IBinder>& producer, bool addToRoot) {
     Mutex::Autolock lock(mCreatedLayersLock);
     mCreatedLayers[handle->localBinder()] =
-            std::make_unique<LayerCreatedState>(layer, parent, parentLayer, producer);
+            std::make_unique<LayerCreatedState>(layer, parent, parentLayer, producer, addToRoot);
 }
 
 auto SurfaceFlinger::getLayerCreatedState(const sp<IBinder>& handle) {
@@ -6888,7 +6898,7 @@
     return state;
 }
 
-sp<Layer> SurfaceFlinger::handleLayerCreatedLocked(const sp<IBinder>& handle, bool privileged) {
+sp<Layer> SurfaceFlinger::handleLayerCreatedLocked(const sp<IBinder>& handle) {
     const auto& state = getLayerCreatedState(handle);
     if (!state) {
         return nullptr;
@@ -6901,7 +6911,7 @@
     }
 
     sp<Layer> parent;
-    bool allowAddRoot = privileged;
+    bool allowAddRoot = state->addToRoot;
     if (state->initialParent != nullptr) {
         parent = fromHandleLocked(state->initialParent.promote()).promote();
         if (parent == nullptr) {
@@ -6963,10 +6973,17 @@
 void SurfaceFlinger::onActiveDisplayChangedLocked(const sp<DisplayDevice>& activeDisplay) {
     ATRACE_CALL();
 
+    if (const auto display = getDisplayDeviceLocked(mActiveDisplayToken)) {
+        display->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
+    }
+
     if (!activeDisplay) {
         ALOGE("%s: activeDisplay is null", __func__);
         return;
     }
+    mActiveDisplayToken = activeDisplay->getDisplayToken();
+
+    activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
     updateInternalDisplayVsyncLocked(activeDisplay);
     mScheduler->setModeChangePending(false);
     mScheduler->setRefreshRateConfigs(activeDisplay->holdRefreshRateConfigs());
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 071ea66..a88de87 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -847,7 +847,7 @@
     // but there is no need to try and wake up immediately to do it. Rather we rely on
     // onFrameAvailable or another layer update to wake us up.
     void setTraversalNeeded();
-    uint32_t setTransactionFlags(uint32_t flags, TransactionSchedule);
+    uint32_t setTransactionFlags(uint32_t flags, TransactionSchedule, const sp<IBinder>& = {});
     void commitTransaction() REQUIRES(mStateLock);
     void commitOffscreenLayers();
     bool transactionIsReadyToBeApplied(
@@ -901,7 +901,7 @@
     status_t addClientLayer(const sp<Client>& client, const sp<IBinder>& handle,
                             const sp<IGraphicBufferProducer>& gbc, const sp<Layer>& lbc,
                             const sp<IBinder>& parentHandle, const sp<Layer>& parentLayer,
-                            bool addToCurrentState, uint32_t* outTransformHint);
+                            bool addToRoot, uint32_t* outTransformHint);
 
     // Traverse through all the layers and compute and cache its bounds.
     void computeLayerBounds();
@@ -1404,7 +1404,7 @@
     std::unique_ptr<scheduler::VsyncConfiguration> mVsyncConfiguration;
 
     // Optional to defer construction until PhaseConfiguration is created.
-    std::optional<scheduler::VsyncModulator> mVsyncModulator;
+    sp<VsyncModulator> mVsyncModulator;
 
     std::unique_ptr<scheduler::RefreshRateStats> mRefreshRateStats;
 
@@ -1457,11 +1457,12 @@
     mutable Mutex mCreatedLayersLock;
     struct LayerCreatedState {
         LayerCreatedState(const wp<Layer>& layer, const wp<IBinder>& parent,
-                          const wp<Layer> parentLayer, const wp<IBinder>& producer)
+                          const wp<Layer> parentLayer, const wp<IBinder>& producer, bool addToRoot)
               : layer(layer),
                 initialParent(parent),
                 initialParentLayer(parentLayer),
-                initialProducer(producer) {}
+                initialProducer(producer),
+                addToRoot(addToRoot) {}
         wp<Layer> layer;
         // Indicates the initial parent of the created layer, only used for creating layer in
         // SurfaceFlinger. If nullptr, it may add the created layer into the current root layers.
@@ -1470,6 +1471,10 @@
         // Indicates the initial graphic buffer producer of the created layer, only used for
         // creating layer in SurfaceFlinger.
         wp<IBinder> initialProducer;
+        // Indicates whether the layer getting created should be added at root if there's no parent
+        // and has permission ACCESS_SURFACE_FLINGER. If set to false and no parent, the layer will
+        // be added offscreen.
+        bool addToRoot;
     };
 
     // A temporay pool that store the created layers and will be added to current state in main
@@ -1477,10 +1482,9 @@
     std::unordered_map<BBinder*, std::unique_ptr<LayerCreatedState>> mCreatedLayers;
     void setLayerCreatedState(const sp<IBinder>& handle, const wp<Layer>& layer,
                               const wp<IBinder>& parent, const wp<Layer> parentLayer,
-                              const wp<IBinder>& producer);
+                              const wp<IBinder>& producer, bool addToRoot);
     auto getLayerCreatedState(const sp<IBinder>& handle);
-    sp<Layer> handleLayerCreatedLocked(const sp<IBinder>& handle, bool privileged)
-            REQUIRES(mStateLock);
+    sp<Layer> handleLayerCreatedLocked(const sp<IBinder>& handle) REQUIRES(mStateLock);
 
     std::atomic<ui::Transform::RotationFlags> mDefaultDisplayTransformHint;
 
diff --git a/services/surfaceflinger/tests/MirrorLayer_test.cpp b/services/surfaceflinger/tests/MirrorLayer_test.cpp
index ccf434d..d027865 100644
--- a/services/surfaceflinger/tests/MirrorLayer_test.cpp
+++ b/services/surfaceflinger/tests/MirrorLayer_test.cpp
@@ -18,7 +18,9 @@
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
 
+#include <private/android_filesystem_config.h>
 #include "LayerTransactionTest.h"
+#include "utils/TransactionUtils.h"
 
 namespace android {
 
@@ -227,6 +229,50 @@
     }
 }
 
+// Test that the mirror layer is initially offscreen.
+TEST_F(MirrorLayerTest, InitialMirrorState) {
+    const auto display = SurfaceComposerClient::getInternalDisplayToken();
+    ui::DisplayMode mode;
+    SurfaceComposerClient::getActiveDisplayMode(display, &mode);
+    const ui::Size& size = mode.resolution;
+
+    sp<SurfaceControl> mirrorLayer = nullptr;
+    {
+        // Run as system to get the ACCESS_SURFACE_FLINGER permission when mirroring
+        UIDFaker f(AID_SYSTEM);
+        // Mirror mChildLayer
+        mirrorLayer = mClient->mirrorSurface(mChildLayer.get());
+        ASSERT_NE(mirrorLayer, nullptr);
+    }
+
+    // Show the mirror layer, but don't reparent to a layer on screen.
+    Transaction()
+            .setPosition(mirrorLayer, 500, 500)
+            .show(mirrorLayer)
+            .setLayer(mirrorLayer, INT32_MAX - 1)
+            .apply();
+
+    {
+        SCOPED_TRACE("Offscreen Mirror");
+        auto shot = screenshot();
+        shot->expectColor(Rect(0, 0, size.getWidth(), 50), Color::RED);
+        shot->expectColor(Rect(0, 0, 50, size.getHeight()), Color::RED);
+        shot->expectColor(Rect(450, 0, size.getWidth(), size.getHeight()), Color::RED);
+        shot->expectColor(Rect(0, 450, size.getWidth(), size.getHeight()), Color::RED);
+        shot->expectColor(Rect(50, 50, 450, 450), Color::GREEN);
+    }
+
+    // Add mirrorLayer as child of mParentLayer so it's shown on the display
+    Transaction().reparent(mirrorLayer, mParentLayer).apply();
+
+    {
+        SCOPED_TRACE("On Screen Mirror");
+        auto shot = screenshot();
+        // Child mirror
+        shot->expectColor(Rect(550, 550, 950, 950), Color::GREEN);
+    }
+}
+
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 4f96541..40c881e 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -227,7 +227,8 @@
         mRefreshRateConfigs = std::make_shared<scheduler::RefreshRateConfigs>(modes, currMode);
         const auto currFps = mRefreshRateConfigs->getCurrentRefreshRate().getFps();
         mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(currFps);
-        mFlinger->mVsyncModulator.emplace(mFlinger->mVsyncConfiguration->getCurrentConfigs());
+        mFlinger->mVsyncModulator = sp<scheduler::VsyncModulator>::make(
+                mFlinger->mVsyncConfiguration->getCurrentConfigs());
         mFlinger->mRefreshRateStats =
                 std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, currFps,
                                                               /*powerMode=*/hal::PowerMode::OFF);
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index d59d64b..ddc02bf 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -942,6 +942,27 @@
     EXPECT_THAT(cb.mReadyTime[0], 970);
 }
 
+TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) {
+    Sequence seq;
+    EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
+
+    CountingCallback cb(mDispatch);
+
+    mDispatch.schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+    mDispatch.schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+
+    advanceToNextCallback();
+
+    advanceToNextCallback();
+
+    ASSERT_THAT(cb.mCalls.size(), Eq(1));
+    EXPECT_THAT(cb.mCalls[0], Eq(2000));
+    ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
+    EXPECT_THAT(cb.mWakeupTime[0], Eq(600));
+    ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
+    EXPECT_THAT(cb.mReadyTime[0], Eq(2000));
+}
+
 class VSyncDispatchTimerQueueEntryTest : public testing::Test {
 protected:
     nsecs_t const mPeriod = 1000;
diff --git a/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp b/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
index 60952bf..b519582 100644
--- a/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <binder/Binder.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
@@ -21,6 +22,13 @@
 
 namespace android::scheduler {
 
+class TestableVsyncModulator : public VsyncModulator {
+public:
+    TestableVsyncModulator(const VsyncConfigSet& config, Now now) : VsyncModulator(config, now) {}
+
+    void binderDied(const wp<IBinder>& token) { VsyncModulator::binderDied(token); }
+};
+
 class VsyncModulatorTest : public testing::Test {
     enum {
         SF_OFFSET_LATE,
@@ -60,30 +68,31 @@
 
     const VsyncModulator::VsyncConfigSet mOffsets = {kEarly, kEarlyGpu, kLate,
                                                      nanos(HWC_MIN_WORK_DURATION)};
-    VsyncModulator mVsyncModulator{mOffsets, Now};
+    sp<TestableVsyncModulator> mVsyncModulator = sp<TestableVsyncModulator>::make(mOffsets, Now);
 
-    void SetUp() override { EXPECT_EQ(kLate, mVsyncModulator.setVsyncConfigSet(mOffsets)); }
+    void SetUp() override { EXPECT_EQ(kLate, mVsyncModulator->setVsyncConfigSet(mOffsets)); }
 };
 
-#define CHECK_COMMIT(result, configs)                         \
-    EXPECT_EQ(result, mVsyncModulator.onTransactionCommit()); \
-    EXPECT_EQ(configs, mVsyncModulator.getVsyncConfig());
+#define CHECK_COMMIT(result, configs)                          \
+    EXPECT_EQ(result, mVsyncModulator->onTransactionCommit()); \
+    EXPECT_EQ(configs, mVsyncModulator->getVsyncConfig());
 
-#define CHECK_REFRESH(N, result, configs)                           \
-    for (int i = 0; i < N; i++) {                                   \
-        EXPECT_EQ(result, mVsyncModulator.onDisplayRefresh(false)); \
-        EXPECT_EQ(configs, mVsyncModulator.getVsyncConfig());       \
+#define CHECK_REFRESH(N, result, configs)                            \
+    for (int i = 0; i < N; i++) {                                    \
+        EXPECT_EQ(result, mVsyncModulator->onDisplayRefresh(false)); \
+        EXPECT_EQ(configs, mVsyncModulator->getVsyncConfig());       \
     }
 
 TEST_F(VsyncModulatorTest, Late) {
-    EXPECT_FALSE(mVsyncModulator.setTransactionSchedule(Schedule::Late));
+    EXPECT_FALSE(mVsyncModulator->setTransactionSchedule(Schedule::Late));
 
     CHECK_COMMIT(std::nullopt, kLate);
     CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kLate);
 }
 
 TEST_F(VsyncModulatorTest, EarlyEnd) {
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
+    const auto token = sp<BBinder>::make();
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyEnd, token));
 
     CHECK_COMMIT(kEarly, kEarly);
     CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
@@ -91,12 +100,13 @@
 }
 
 TEST_F(VsyncModulatorTest, EarlyStart) {
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyStart));
+    const auto token = sp<BBinder>::make();
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyStart, token));
 
     CHECK_COMMIT(kEarly, kEarly);
     CHECK_REFRESH(5 * MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kEarly);
 
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyEnd, token));
 
     CHECK_COMMIT(kEarly, kEarly);
     CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
@@ -104,16 +114,17 @@
 }
 
 TEST_F(VsyncModulatorTest, EarlyStartWithMoreTransactions) {
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyStart));
+    const auto token = sp<BBinder>::make();
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyStart, token));
 
     CHECK_COMMIT(kEarly, kEarly);
 
     for (int i = 0; i < 5 * MIN_EARLY_TRANSACTION_FRAMES; i++) {
-        EXPECT_FALSE(mVsyncModulator.setTransactionSchedule(Schedule::Late));
+        EXPECT_FALSE(mVsyncModulator->setTransactionSchedule(Schedule::Late));
         CHECK_REFRESH(1, std::nullopt, kEarly);
     }
 
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyEnd, token));
 
     CHECK_COMMIT(kEarly, kEarly);
     CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
@@ -121,18 +132,19 @@
 }
 
 TEST_F(VsyncModulatorTest, EarlyStartAfterEarlyEnd) {
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
+    const auto token = sp<BBinder>::make();
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyEnd, token));
 
     CHECK_COMMIT(kEarly, kEarly);
     CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
 
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyStart));
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyStart, token));
 
     CHECK_COMMIT(kEarly, kEarly);
     CHECK_REFRESH(1, kEarly, kEarly);
     CHECK_REFRESH(5 * MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kEarly);
 
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyEnd, token));
 
     CHECK_COMMIT(kEarly, kEarly);
     CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
@@ -140,26 +152,64 @@
 }
 
 TEST_F(VsyncModulatorTest, EarlyStartAfterEarlyEndWithMoreTransactions) {
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
+    const auto token = sp<BBinder>::make();
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyEnd, token));
 
     CHECK_COMMIT(kEarly, kEarly);
     CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
 
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyStart));
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyStart, token));
 
     CHECK_COMMIT(kEarly, kEarly);
     CHECK_REFRESH(1, kEarly, kEarly);
 
     for (int i = 0; i < 5 * MIN_EARLY_TRANSACTION_FRAMES; i++) {
-        EXPECT_FALSE(mVsyncModulator.setTransactionSchedule(Schedule::Late));
+        EXPECT_FALSE(mVsyncModulator->setTransactionSchedule(Schedule::Late));
         CHECK_REFRESH(1, std::nullopt, kEarly);
     }
 
-    EXPECT_EQ(kEarly, mVsyncModulator.setTransactionSchedule(Schedule::EarlyEnd));
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyEnd, token));
 
     CHECK_COMMIT(kEarly, kEarly);
     CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
     CHECK_REFRESH(1, kLate, kLate);
 }
 
+TEST_F(VsyncModulatorTest, EarlyStartDifferentClients) {
+    const auto token1 = sp<BBinder>::make();
+    const auto token2 = sp<BBinder>::make();
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyStart, token1));
+
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(5 * MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kEarly);
+
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyStart, token2));
+
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(5 * MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kEarly);
+
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyEnd, token1));
+
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(5 * MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kEarly);
+
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyEnd, token2));
+
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(MIN_EARLY_TRANSACTION_FRAMES - 1, kEarly, kEarly);
+    CHECK_REFRESH(1, kLate, kLate);
+}
+
+TEST_F(VsyncModulatorTest, EarlyStartWithBinderDeath) {
+    const auto token = sp<BBinder>::make();
+    EXPECT_EQ(kEarly, mVsyncModulator->setTransactionSchedule(Schedule::EarlyStart, token));
+
+    CHECK_COMMIT(kEarly, kEarly);
+    CHECK_REFRESH(5 * MIN_EARLY_TRANSACTION_FRAMES, std::nullopt, kEarly);
+
+    mVsyncModulator->binderDied(token);
+
+    CHECK_COMMIT(std::nullopt, kLate);
+}
+
 } // namespace android::scheduler