Merge "libs/ui: Add MockFence"
diff --git a/cmds/cmd/cmd.cpp b/cmds/cmd/cmd.cpp
index be2c702..8f1c01a 100644
--- a/cmds/cmd/cmd.cpp
+++ b/cmds/cmd/cmd.cpp
@@ -52,7 +52,7 @@
 }
 
 struct SecurityContext_Delete {
-    void operator()(security_context_t p) const {
+    void operator()(char* p) const {
         freecon(p);
     }
 };
@@ -108,7 +108,7 @@
         }
         if (is_selinux_enabled() && seLinuxContext.size() > 0) {
             String8 seLinuxContext8(seLinuxContext);
-            security_context_t tmp = nullptr;
+            char* tmp = nullptr;
             getfilecon(fullPath.string(), &tmp);
             Unique_SecurityContext context(tmp);
             if (checkWrite) {
diff --git a/include/input/Input.h b/include/input/Input.h
index d2d9fd4..d397313 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -524,13 +524,17 @@
 
     inline int32_t getAction() const { return mAction; }
 
-    inline int32_t getActionMasked() const { return mAction & AMOTION_EVENT_ACTION_MASK; }
+    static int32_t getActionMasked(int32_t action) { return action & AMOTION_EVENT_ACTION_MASK; }
 
-    inline int32_t getActionIndex() const {
-        return (mAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
-                >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+    inline int32_t getActionMasked() const { return getActionMasked(mAction); }
+
+    static int32_t getActionIndex(int32_t action) {
+        return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
+                AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
     }
 
+    inline int32_t getActionIndex() const { return getActionIndex(mAction); }
+
     inline void setAction(int32_t action) { mAction = action; }
 
     inline int32_t getFlags() const { return mFlags; }
@@ -575,9 +579,7 @@
 
     void setCursorPosition(float x, float y);
 
-    uint32_t getDisplayOrientation() const { return mDisplayOrientation; }
-
-    int2 getDisplaySize() const { return {mDisplayWidth, mDisplayHeight}; }
+    ui::Transform getRawTransform() const { return mRawTransform; }
 
     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, uint32_t displayOrientation, int32_t displayWidth,
-                    int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime, size_t pointerCount,
+                    float rawYCursorPosition, const ui::Transform& rawTransform, nsecs_t downTime,
+                    nsecs_t eventTime, size_t pointerCount,
                     const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
 
     void copyFrom(const MotionEvent* other, bool keepHistory);
@@ -812,9 +814,7 @@
     float mYPrecision;
     float mRawXCursorPosition;
     float mRawYCursorPosition;
-    uint32_t mDisplayOrientation;
-    int32_t mDisplayWidth;
-    int32_t mDisplayHeight;
+    ui::Transform mRawTransform;
     nsecs_t mDownTime;
     Vector<PointerProperties> mPointerProperties;
     std::vector<nsecs_t> mSampleEventTimes;
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 7632b30..d655b28 100644
--- a/include/input/InputTransport.h
+++ b/include/input/InputTransport.h
@@ -114,7 +114,7 @@
 
         struct Motion {
             int32_t eventId;
-            uint32_t empty1;
+            uint32_t pointerCount;
             nsecs_t eventTime __attribute__((aligned(8)));
             int32_t deviceId;
             int32_t source;
@@ -129,20 +129,22 @@
             uint8_t empty2[3];                   // 3 bytes to fill gap created by classification
             int32_t edgeFlags;
             nsecs_t downTime __attribute__((aligned(8)));
-            float dsdx;
-            float dtdx;
-            float dtdy;
-            float dsdy;
-            float tx;
-            float ty;
+            float dsdx; // Begin window transform
+            float dtdx; //
+            float dtdy; //
+            float dsdy; //
+            float tx;   //
+            float ty;   // End window transform
             float xPrecision;
             float yPrecision;
             float xCursorPosition;
             float yCursorPosition;
-            uint32_t displayOrientation;
-            int32_t displayWidth;
-            int32_t displayHeight;
-            uint32_t pointerCount;
+            float dsdxRaw; // Begin raw transform
+            float dtdxRaw; //
+            float dtdyRaw; //
+            float dsdyRaw; //
+            float txRaw;   //
+            float tyRaw;   // End raw transform
             /**
              * The "pointers" field must be the last field of the struct InputMessage.
              * When we send the struct InputMessage across the socket, we are not
@@ -367,9 +369,8 @@
                                 int32_t metaState, int32_t buttonState,
                                 MotionClassification classification, const ui::Transform& transform,
                                 float xPrecision, float yPrecision, float xCursorPosition,
-                                float yCursorPosition, uint32_t displayOrientation,
-                                int32_t displayWidth, int32_t displayHeight, nsecs_t downTime,
-                                nsecs_t eventTime, uint32_t pointerCount,
+                                float yCursorPosition, const ui::Transform& rawTransform,
+                                nsecs_t downTime, nsecs_t eventTime, uint32_t pointerCount,
                                 const PointerProperties* pointerProperties,
                                 const PointerCoords* pointerCoords);
 
diff --git a/libs/battery/LongArrayMultiStateCounter.cpp b/libs/battery/LongArrayMultiStateCounter.cpp
index 68e0883..125cfaf 100644
--- a/libs/battery/LongArrayMultiStateCounter.cpp
+++ b/libs/battery/LongArrayMultiStateCounter.cpp
@@ -62,7 +62,7 @@
 template <>
 std::string LongArrayMultiStateCounter::valueToString(const std::vector<uint64_t>& v) const {
     std::stringstream s;
-    s << "{ ";
+    s << "{";
     bool first = true;
     for (uint64_t n : v) {
         if (!first) {
diff --git a/libs/battery/LongArrayMultiStateCounterTest.cpp b/libs/battery/LongArrayMultiStateCounterTest.cpp
index 24cb437..e4e6b2a 100644
--- a/libs/battery/LongArrayMultiStateCounterTest.cpp
+++ b/libs/battery/LongArrayMultiStateCounterTest.cpp
@@ -24,7 +24,9 @@
 class LongArrayMultiStateCounterTest : public testing::Test {};
 
 TEST_F(LongArrayMultiStateCounterTest, stateChange) {
-    LongArrayMultiStateCounter testCounter(2, 0, std::vector<uint64_t>(4), 1000);
+    LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4));
+    testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000);
+    testCounter.setState(0, 1000);
     testCounter.setState(1, 2000);
     testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000);
 
@@ -34,7 +36,9 @@
 }
 
 TEST_F(LongArrayMultiStateCounterTest, accumulation) {
-    LongArrayMultiStateCounter testCounter(2, 0, std::vector<uint64_t>(4), 1000);
+    LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4));
+    testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000);
+    testCounter.setState(0, 1000);
     testCounter.setState(1, 2000);
     testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000);
     testCounter.setState(0, 4000);
@@ -50,5 +54,16 @@
     EXPECT_EQ(std::vector<uint64_t>({70, 120, 170, 220}), testCounter.getCount(1));
 }
 
+TEST_F(LongArrayMultiStateCounterTest, toString) {
+    LongArrayMultiStateCounter testCounter(2, std::vector<uint64_t>(4));
+    testCounter.updateValue(std::vector<uint64_t>({0, 0, 0, 0}), 1000);
+    testCounter.setState(0, 1000);
+    testCounter.setState(1, 2000);
+    testCounter.updateValue(std::vector<uint64_t>({100, 200, 300, 400}), 3000);
+
+    EXPECT_STREQ("[0: {50, 100, 150, 200}, 1: {50, 100, 150, 200}] updated: 3000 currentState: 1",
+                 testCounter.toString().c_str());
+}
+
 } // namespace battery
 } // namespace android
diff --git a/libs/battery/MultiStateCounter.h b/libs/battery/MultiStateCounter.h
index 9f56b29..e1ee07c 100644
--- a/libs/battery/MultiStateCounter.h
+++ b/libs/battery/MultiStateCounter.h
@@ -42,6 +42,7 @@
     T lastValue;
     time_t lastUpdateTimestamp;
     T deltaValue;
+    bool isEnabled;
 
     struct State {
         time_t timeInStateSinceUpdate;
@@ -51,15 +52,22 @@
     State* states;
 
 public:
-    MultiStateCounter(uint16_t stateCount, state_t initialState, const T& emptyValue,
-                      time_t timestamp);
+    MultiStateCounter(uint16_t stateCount, const T& emptyValue);
 
     virtual ~MultiStateCounter();
 
+    void setEnabled(bool enabled, time_t timestamp);
+
     void setState(state_t state, time_t timestamp);
 
+    void setValue(state_t state, const T& value);
+
     void updateValue(const T& value, time_t timestamp);
 
+    void reset();
+
+    uint16_t getStateCount();
+
     const T& getCount(state_t state);
 
     std::string toString();
@@ -86,15 +94,15 @@
 // Since MultiStateCounter is a template, the implementation must be inlined.
 
 template <class T>
-MultiStateCounter<T>::MultiStateCounter(uint16_t stateCount, state_t initialState,
-                                        const T& emptyValue, time_t timestamp)
+MultiStateCounter<T>::MultiStateCounter(uint16_t stateCount, const T& emptyValue)
       : stateCount(stateCount),
-        currentState(initialState),
-        lastStateChangeTimestamp(timestamp),
+        currentState(0),
+        lastStateChangeTimestamp(-1),
         emptyValue(emptyValue),
         lastValue(emptyValue),
-        lastUpdateTimestamp(timestamp),
-        deltaValue(emptyValue) {
+        lastUpdateTimestamp(-1),
+        deltaValue(emptyValue),
+        isEnabled(true) {
     states = new State[stateCount];
     for (int i = 0; i < stateCount; i++) {
         states[i].timeInStateSinceUpdate = 0;
@@ -108,18 +116,39 @@
 };
 
 template <class T>
+void MultiStateCounter<T>::setEnabled(bool enabled, time_t timestamp) {
+    if (enabled == isEnabled) {
+        return;
+    }
+
+    if (!enabled) {
+        // Confirm the current state for the side-effect of updating the time-in-state
+        // counter for the current state.
+        setState(currentState, timestamp);
+    }
+
+    isEnabled = enabled;
+
+    if (lastStateChangeTimestamp >= 0) {
+        lastStateChangeTimestamp = timestamp;
+    }
+}
+
+template <class T>
 void MultiStateCounter<T>::setState(state_t state, time_t timestamp) {
-    if (timestamp >= lastStateChangeTimestamp) {
-        states[currentState].timeInStateSinceUpdate += timestamp - lastStateChangeTimestamp;
-    } else {
-        ALOGE("setState is called with an earlier timestamp: %lu, previous timestamp: %lu\n",
-              (unsigned long)timestamp, (unsigned long)lastStateChangeTimestamp);
-        // The accumulated durations have become unreliable. For example, if the timestamp
-        // sequence was 1000, 2000, 1000, 3000, if we accumulated the positive deltas,
-        // we would get 4000, which is greater than (last - first). This could lead to
-        // counts exceeding 100%.
-        for (int i = 0; i < stateCount; i++) {
-            states[i].timeInStateSinceUpdate = 0;
+    if (isEnabled && lastStateChangeTimestamp >= 0) {
+        if (timestamp >= lastStateChangeTimestamp) {
+            states[currentState].timeInStateSinceUpdate += timestamp - lastStateChangeTimestamp;
+        } else {
+            ALOGE("setState is called with an earlier timestamp: %lu, previous timestamp: %lu\n",
+                  (unsigned long)timestamp, (unsigned long)lastStateChangeTimestamp);
+            // The accumulated durations have become unreliable. For example, if the timestamp
+            // sequence was 1000, 2000, 1000, 3000, if we accumulated the positive deltas,
+            // we would get 4000, which is greater than (last - first). This could lead to
+            // counts exceeding 100%.
+            for (int i = 0; i < stateCount; i++) {
+                states[i].timeInStateSinceUpdate = 0;
+            }
         }
     }
     currentState = state;
@@ -127,36 +156,63 @@
 }
 
 template <class T>
-void MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) {
-    // Confirm the current state for the side-effect of updating the time-in-state
-    // counter for the current state.
-    setState(currentState, timestamp);
+void MultiStateCounter<T>::setValue(state_t state, const T& value) {
+    states[state].counter = value;
+}
 
-    if (timestamp > lastUpdateTimestamp) {
-        if (delta(lastValue, value, &deltaValue)) {
-            time_t timeSinceUpdate = timestamp - lastUpdateTimestamp;
-            for (int i = 0; i < stateCount; i++) {
-                time_t timeInState = states[i].timeInStateSinceUpdate;
-                if (timeInState) {
-                    add(&states[i].counter, deltaValue, timeInState, timeSinceUpdate);
-                    states[i].timeInStateSinceUpdate = 0;
+template <class T>
+void MultiStateCounter<T>::updateValue(const T& value, time_t timestamp) {
+    // If the counter is disabled, we ignore the update, except when the counter got disabled after
+    // the previous update, in which case we still need to pick up the residual delta.
+    if (isEnabled || lastUpdateTimestamp < lastStateChangeTimestamp) {
+        // Confirm the current state for the side-effect of updating the time-in-state
+        // counter for the current state.
+        setState(currentState, timestamp);
+
+        if (lastUpdateTimestamp >= 0) {
+            if (timestamp > lastUpdateTimestamp) {
+                if (delta(lastValue, value, &deltaValue)) {
+                    time_t timeSinceUpdate = timestamp - lastUpdateTimestamp;
+                    for (int i = 0; i < stateCount; i++) {
+                        time_t timeInState = states[i].timeInStateSinceUpdate;
+                        if (timeInState) {
+                            add(&states[i].counter, deltaValue, timeInState, timeSinceUpdate);
+                            states[i].timeInStateSinceUpdate = 0;
+                        }
+                    }
+                } else {
+                    std::stringstream str;
+                    str << "updateValue is called with a value " << valueToString(value)
+                        << ", which is lower than the previous value " << valueToString(lastValue)
+                        << "\n";
+                    ALOGE("%s", str.str().c_str());
                 }
+            } else if (timestamp < lastUpdateTimestamp) {
+                ALOGE("updateValue is called with an earlier timestamp: %lu, previous: %lu\n",
+                      (unsigned long)timestamp, (unsigned long)lastUpdateTimestamp);
             }
-        } else {
-            std::stringstream str;
-            str << "updateValue is called with a value " << valueToString(value)
-                << ", which is lower than the previous value " << valueToString(lastValue) << "\n";
-            ALOGE("%s", str.str().c_str());
         }
-    } else if (timestamp < lastUpdateTimestamp) {
-        ALOGE("updateValue is called with an earlier timestamp: %lu, previous timestamp: %lu\n",
-              (unsigned long)timestamp, (unsigned long)lastUpdateTimestamp);
     }
     lastValue = value;
     lastUpdateTimestamp = timestamp;
 }
 
 template <class T>
+void MultiStateCounter<T>::reset() {
+    lastStateChangeTimestamp = -1;
+    lastUpdateTimestamp = -1;
+    for (int i = 0; i < stateCount; i++) {
+        states[i].timeInStateSinceUpdate = 0;
+        states[i].counter = emptyValue;
+    }
+}
+
+template <class T>
+uint16_t MultiStateCounter<T>::getStateCount() {
+    return stateCount;
+}
+
+template <class T>
 const T& MultiStateCounter<T>::getCount(state_t state) {
     return states[state].counter;
 }
@@ -164,17 +220,29 @@
 template <class T>
 std::string MultiStateCounter<T>::toString() {
     std::stringstream str;
-    str << "currentState: " << currentState
-        << " lastStateChangeTimestamp: " << lastStateChangeTimestamp
-        << " lastUpdateTimestamp: " << lastUpdateTimestamp << " states: [";
+    str << "[";
     for (int i = 0; i < stateCount; i++) {
         if (i != 0) {
             str << ", ";
         }
-        str << i << ": time: " << states[i].timeInStateSinceUpdate
-            << " counter: " << valueToString(states[i].counter);
+        str << i << ": " << valueToString(states[i].counter);
+        if (states[i].timeInStateSinceUpdate > 0) {
+            str << " timeInStateSinceUpdate: " << states[i].timeInStateSinceUpdate;
+        }
     }
     str << "]";
+    if (lastUpdateTimestamp >= 0) {
+        str << " updated: " << lastUpdateTimestamp;
+    }
+    if (lastStateChangeTimestamp >= 0) {
+        str << " currentState: " << currentState;
+        if (lastStateChangeTimestamp > lastUpdateTimestamp) {
+            str << " stateChanged: " << lastStateChangeTimestamp;
+        }
+    } else {
+        str << " currentState: none";
+    }
+
     return str.str();
 }
 
diff --git a/libs/battery/MultiStateCounterTest.cpp b/libs/battery/MultiStateCounterTest.cpp
index 942d5ca..319ba76 100644
--- a/libs/battery/MultiStateCounterTest.cpp
+++ b/libs/battery/MultiStateCounterTest.cpp
@@ -49,8 +49,9 @@
 class MultiStateCounterTest : public testing::Test {};
 
 TEST_F(MultiStateCounterTest, constructor) {
-    DoubleMultiStateCounter testCounter(3, 1, 0, 1000);
-    testCounter.setState(1, 2000);
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
     testCounter.updateValue(3.14, 3000);
 
     EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
@@ -59,7 +60,9 @@
 }
 
 TEST_F(MultiStateCounterTest, stateChange) {
-    DoubleMultiStateCounter testCounter(3, 1, 0, 0);
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
     testCounter.setState(2, 1000);
     testCounter.updateValue(6.0, 3000);
 
@@ -68,8 +71,87 @@
     EXPECT_DOUBLE_EQ(4.0, testCounter.getCount(2));
 }
 
+TEST_F(MultiStateCounterTest, setEnabled) {
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
+    testCounter.setEnabled(false, 1000);
+    testCounter.setState(2, 2000);
+    testCounter.updateValue(6.0, 3000);
+
+    // In state 1: accumulated 1000 before disabled, that's 6.0 * 1000/3000 = 2.0
+    // In state 2: 0, since it is still disabled
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+
+    // Should have no effect since the counter is disabled
+    testCounter.setState(0, 3500);
+
+    // Should have no effect since the counter is disabled
+    testCounter.updateValue(10.0, 4000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+
+    testCounter.setState(2, 4500);
+
+    // Enable the counter to partially accumulate deltas for the current state, 2
+    testCounter.setEnabled(true, 5000);
+    testCounter.setEnabled(false, 6000);
+    testCounter.setEnabled(true, 7000);
+    testCounter.updateValue(20.0, 8000);
+
+    // The delta is 10.0 over 5000-3000=2000.
+    // Counter has been enabled in state 2 for (6000-5000)+(8000-7000) = 2000,
+    // so its share is (20.0-10.0) * 2000/(8000-4000) = 5.0
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(2.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(5.0, testCounter.getCount(2));
+
+    testCounter.reset();
+    testCounter.setState(0, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 2000);
+    testCounter.setEnabled(false, 3000);
+    testCounter.updateValue(200, 5000);
+
+    // 200 over 5000 = 40 per second
+    // Counter was in state 0 from 0 to 2000, so 2 sec, so the count should be 40 * 2 = 80
+    // It stayed in state 1 from 2000 to 3000, at which point the counter was disabled,
+    // so the count for state 1 should be 40 * 1 = 40.
+    // The remaining 2 seconds from 3000 to 5000 don't count because the counter was disabled.
+    EXPECT_DOUBLE_EQ(80.0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(40.0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+}
+
+TEST_F(MultiStateCounterTest, reset) {
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
+    testCounter.updateValue(2.72, 3000);
+
+    testCounter.reset();
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+
+    // Assert that we can still continue accumulating after a reset
+    testCounter.updateValue(0, 4000);
+    testCounter.updateValue(3.14, 5000);
+
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(0));
+    EXPECT_DOUBLE_EQ(3.14, testCounter.getCount(1));
+    EXPECT_DOUBLE_EQ(0, testCounter.getCount(2));
+}
+
 TEST_F(MultiStateCounterTest, timeAdjustment_setState) {
-    DoubleMultiStateCounter testCounter(3, 1, 0, 0);
+    DoubleMultiStateCounter testCounter(3, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
     testCounter.setState(2, 2000);
 
     // Time moves back
@@ -88,7 +170,9 @@
 }
 
 TEST_F(MultiStateCounterTest, timeAdjustment_updateValue) {
-    DoubleMultiStateCounter testCounter(1, 0, 0, 0);
+    DoubleMultiStateCounter testCounter(1, 0);
+    testCounter.updateValue(0, 0);
+    testCounter.setState(0, 0);
     testCounter.updateValue(6.0, 2000);
 
     // Time moves back. The negative delta from 2000 to 1000 is ignored
@@ -101,5 +185,23 @@
     EXPECT_DOUBLE_EQ(9.0, testCounter.getCount(0));
 }
 
+TEST_F(MultiStateCounterTest, toString) {
+    DoubleMultiStateCounter testCounter(2, 0);
+
+    EXPECT_STREQ("[0: 0.000000, 1: 0.000000] currentState: none", testCounter.toString().c_str());
+
+    testCounter.updateValue(0, 0);
+    testCounter.setState(1, 0);
+    testCounter.setState(1, 2000);
+    EXPECT_STREQ("[0: 0.000000, 1: 0.000000 timeInStateSinceUpdate: 2000]"
+                 " updated: 0 currentState: 1 stateChanged: 2000",
+                 testCounter.toString().c_str());
+
+    testCounter.updateValue(3.14, 3000);
+
+    EXPECT_STREQ("[0: 0.000000, 1: 3.140000] updated: 3000 currentState: 1",
+                 testCounter.toString().c_str());
+}
+
 } // namespace battery
 } // namespace android
diff --git a/libs/binder/FdTrigger.cpp b/libs/binder/FdTrigger.cpp
index b197a6a..49f83ff 100644
--- a/libs/binder/FdTrigger.cpp
+++ b/libs/binder/FdTrigger.cpp
@@ -53,25 +53,10 @@
             continue;
         }
         if (pfd[1].revents & POLLHUP) {
-            return -ECANCELED;
+            return DEAD_OBJECT;
         }
         return pfd[0].revents & event ? OK : DEAD_OBJECT;
     }
 }
 
-android::base::Result<bool> FdTrigger::isTriggeredPolled() {
-    pollfd pfd{.fd = mRead.get(), .events = 0, .revents = 0};
-    int ret = TEMP_FAILURE_RETRY(poll(&pfd, 1, 0));
-    if (ret < 0) {
-        return android::base::ErrnoError() << "FdTrigger::isTriggeredPolled: Error in poll()";
-    }
-    if (ret == 0) {
-        return false;
-    }
-    if (pfd.revents & POLLHUP) {
-        return true;
-    }
-    return android::base::Error() << "FdTrigger::isTriggeredPolled: poll() returns " << pfd.revents;
-}
-
 } // namespace android
diff --git a/libs/binder/FdTrigger.h b/libs/binder/FdTrigger.h
index a428417..a545d6c 100644
--- a/libs/binder/FdTrigger.h
+++ b/libs/binder/FdTrigger.h
@@ -35,9 +35,13 @@
     void trigger();
 
     /**
-     * Check whether this has been triggered by checking the write end.
+     * Check whether this has been triggered by checking the write end. Note:
+     * this has no internal locking, and it is inherently racey, but this is
+     * okay, because if we accidentally return false when a trigger has already
+     * happened, we can imagine that instead, the scheduler actually executed
+     * the code which is polling isTriggered earlier.
      */
-    bool isTriggered();
+    [[nodiscard]] bool isTriggered();
 
     /**
      * Poll for a read event.
@@ -48,17 +52,7 @@
      *   true - time to read!
      *   false - trigger happened
      */
-    status_t triggerablePoll(base::borrowed_fd fd, int16_t event);
-
-    /**
-     * Check whether this has been triggered by poll()ing the read end.
-     *
-     * Return:
-     *   true - triggered
-     *   false - not triggered
-     *   error - error when polling
-     */
-    android::base::Result<bool> isTriggeredPolled();
+    [[nodiscard]] status_t triggerablePoll(base::borrowed_fd fd, int16_t event);
 
 private:
     base::unique_fd mWrite;
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 9e04ffe..55d3d70 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -1410,23 +1410,6 @@
         }
 }
 
-status_t IPCThreadState::getProcessFreezeInfo(pid_t pid, bool *sync_received, bool *async_received)
-{
-    int ret = 0;
-    binder_frozen_status_info info;
-    info.pid = pid;
-
-#if defined(__ANDROID__)
-    if (ioctl(self()->mProcess->mDriverFD, BINDER_GET_FROZEN_INFO, &info) < 0)
-        ret = -errno;
-#endif
-    *sync_received = info.sync_recv;
-    *async_received = info.async_recv;
-
-    return ret;
-}
-
-#ifndef __ANDROID_VNDK__
 status_t IPCThreadState::getProcessFreezeInfo(pid_t pid, uint32_t *sync_received,
                                               uint32_t *async_received)
 {
@@ -1443,7 +1426,6 @@
 
     return ret;
 }
-#endif
 
 status_t IPCThreadState::freeze(pid_t pid, bool enable, uint32_t timeout_ms) {
     struct binder_freeze_info info;
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index 11a083a..dcba837 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -323,6 +323,7 @@
         status != OK) {
         LOG_RPC_DETAIL("Failed to read %s (%zu bytes) on RpcTransport %p, error: %s", what, size,
                        connection->rpcTransport.get(), statusToString(status).c_str());
+        (void)session->shutdownAndWait(false);
         return status;
     }
 
@@ -531,8 +532,8 @@
                                 const sp<RpcSession>& session, Parcel* reply) {
     RpcWireHeader command;
     while (true) {
-        if (status_t status =
-                    rpcRec(connection, session, "command header", &command, sizeof(command));
+        if (status_t status = rpcRec(connection, session, "command header (for reply)", &command,
+                                     sizeof(command));
             status != OK)
             return status;
 
@@ -601,7 +602,8 @@
     LOG_RPC_DETAIL("getAndExecuteCommand on RpcTransport %p", connection->rpcTransport.get());
 
     RpcWireHeader command;
-    if (status_t status = rpcRec(connection, session, "command header", &command, sizeof(command));
+    if (status_t status = rpcRec(connection, session, "command header (for server)", &command,
+                                 sizeof(command));
         status != OK)
         return status;
 
diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp
index c012df8..41f4a9f 100644
--- a/libs/binder/RpcTransportRaw.cpp
+++ b/libs/binder/RpcTransportRaw.cpp
@@ -35,20 +35,6 @@
 class RpcTransportRaw : public RpcTransport {
 public:
     explicit RpcTransportRaw(android::base::unique_fd socket) : mSocket(std::move(socket)) {}
-    Result<size_t> send(const void* buf, size_t size) {
-        ssize_t ret = TEMP_FAILURE_RETRY(::send(mSocket.get(), buf, size, MSG_NOSIGNAL));
-        if (ret < 0) {
-            return ErrnoError() << "send()";
-        }
-        return ret;
-    }
-    Result<size_t> recv(void* buf, size_t size) {
-        ssize_t ret = TEMP_FAILURE_RETRY(::recv(mSocket.get(), buf, size, MSG_NOSIGNAL));
-        if (ret < 0) {
-            return ErrnoError() << "recv()";
-        }
-        return ret;
-    }
     Result<size_t> peek(void *buf, size_t size) override {
         ssize_t ret = TEMP_FAILURE_RETRY(::recv(mSocket.get(), buf, size, MSG_PEEK));
         if (ret < 0) {
@@ -65,15 +51,17 @@
 
         status_t status;
         while ((status = fdTrigger->triggerablePoll(mSocket.get(), POLLOUT)) == OK) {
-            auto writeSize = this->send(buffer, end - buffer);
-            if (!writeSize.ok()) {
-                LOG_RPC_DETAIL("RpcTransport::send(): %s", writeSize.error().message().c_str());
-                return writeSize.error().code() == 0 ? UNKNOWN_ERROR : -writeSize.error().code();
+            ssize_t writeSize =
+                    TEMP_FAILURE_RETRY(::send(mSocket.get(), buffer, end - buffer, MSG_NOSIGNAL));
+            if (writeSize < 0) {
+                int savedErrno = errno;
+                LOG_RPC_DETAIL("RpcTransport send(): %s", strerror(savedErrno));
+                return -savedErrno;
             }
 
-            if (*writeSize == 0) return DEAD_OBJECT;
+            if (writeSize == 0) return DEAD_OBJECT;
 
-            buffer += *writeSize;
+            buffer += writeSize;
             if (buffer == end) return OK;
         }
         return status;
@@ -87,15 +75,17 @@
 
         status_t status;
         while ((status = fdTrigger->triggerablePoll(mSocket.get(), POLLIN)) == OK) {
-            auto readSize = this->recv(buffer, end - buffer);
-            if (!readSize.ok()) {
-                LOG_RPC_DETAIL("RpcTransport::recv(): %s", readSize.error().message().c_str());
-                return readSize.error().code() == 0 ? UNKNOWN_ERROR : -readSize.error().code();
+            ssize_t readSize =
+                    TEMP_FAILURE_RETRY(::recv(mSocket.get(), buffer, end - buffer, MSG_NOSIGNAL));
+            if (readSize < 0) {
+                int savedErrno = errno;
+                LOG_RPC_DETAIL("RpcTransport recv(): %s", strerror(savedErrno));
+                return -savedErrno;
             }
 
-            if (*readSize == 0) return DEAD_OBJECT; // EOF
+            if (readSize == 0) return DEAD_OBJECT; // EOF
 
-            buffer += *readSize;
+            buffer += readSize;
             if (buffer == end) return OK;
         }
         return status;
diff --git a/libs/binder/RpcTransportTls.cpp b/libs/binder/RpcTransportTls.cpp
index 63f9339..79445d9 100644
--- a/libs/binder/RpcTransportTls.cpp
+++ b/libs/binder/RpcTransportTls.cpp
@@ -241,7 +241,7 @@
     status_t handlePoll(int event, android::base::borrowed_fd fd, FdTrigger* fdTrigger,
                         const char* fnString) {
         status_t ret = fdTrigger->triggerablePoll(fd, event);
-        if (ret != OK && ret != DEAD_OBJECT && ret != -ECANCELED) {
+        if (ret != OK && ret != DEAD_OBJECT) {
             ALOGE("triggerablePoll error while poll()-ing after %s(): %s", fnString,
                   statusToString(ret).c_str());
         }
@@ -319,8 +319,6 @@
 private:
     android::base::unique_fd mSocket;
     Ssl mSsl;
-
-    static status_t isTriggered(FdTrigger* fdTrigger);
 };
 
 // Error code is errno.
@@ -341,15 +339,6 @@
     return ret;
 }
 
-status_t RpcTransportTls::isTriggered(FdTrigger* fdTrigger) {
-    auto ret = fdTrigger->isTriggeredPolled();
-    if (!ret.ok()) {
-        ALOGE("%s: %s", __PRETTY_FUNCTION__, ret.error().message().c_str());
-        return ret.error().code() == 0 ? UNKNOWN_ERROR : -ret.error().code();
-    }
-    return *ret ? -ECANCELED : OK;
-}
-
 status_t RpcTransportTls::interruptableWriteFully(FdTrigger* fdTrigger, const void* data,
                                                   size_t size) {
     auto buffer = reinterpret_cast<const uint8_t*>(data);
@@ -359,7 +348,7 @@
 
     // Before doing any I/O, check trigger once. This ensures the trigger is checked at least
     // once. The trigger is also checked via triggerablePoll() after every SSL_write().
-    if (status_t status = isTriggered(fdTrigger); status != OK) return status;
+    if (fdTrigger->isTriggered()) return DEAD_OBJECT;
 
     while (buffer < end) {
         size_t todo = std::min<size_t>(end - buffer, std::numeric_limits<int>::max());
@@ -390,7 +379,7 @@
 
     // Before doing any I/O, check trigger once. This ensures the trigger is checked at least
     // once. The trigger is also checked via triggerablePoll() after every SSL_write().
-    if (status_t status = isTriggered(fdTrigger); status != OK) return status;
+    if (fdTrigger->isTriggered()) return DEAD_OBJECT;
 
     while (buffer < end) {
         size_t todo = std::min<size_t>(end - buffer, std::numeric_limits<int>::max());
diff --git a/libs/binder/ServiceManagerHost.cpp b/libs/binder/ServiceManagerHost.cpp
index 59334b7..27cc563 100644
--- a/libs/binder/ServiceManagerHost.cpp
+++ b/libs/binder/ServiceManagerHost.cpp
@@ -74,12 +74,12 @@
               result->toString().c_str());
         return std::nullopt;
     }
-    if (!result->stderr.empty()) {
+    if (!result->stderrStr.empty()) {
         LOG_HOST("`adb forward tcp:0 tcp:%d` writes to stderr: %s", devicePort,
-                 result->stderr.c_str());
+                 result->stderrStr.c_str());
     }
 
-    unsigned int hostPort = parsePortNumber(result->stdout, "host port");
+    unsigned int hostPort = parsePortNumber(result->stdoutStr, "host port");
     if (hostPort == 0) return std::nullopt;
 
     return AdbForwarder(hostPort);
@@ -105,9 +105,9 @@
               result->toString().c_str());
         return;
     }
-    if (!result->stderr.empty()) {
+    if (!result->stderrStr.empty()) {
         LOG_HOST("`adb forward --remove tcp:%d` writes to stderr: %s", *mPort,
-                 result->stderr.c_str());
+                 result->stderrStr.c_str());
     }
 
     LOG_HOST("Successfully run `adb forward --remove tcp:%d`", *mPort);
@@ -139,8 +139,8 @@
         ALOGE("Command exits with: %s", result->toString().c_str());
         return nullptr;
     }
-    if (!result->stderr.empty()) {
-        LOG_HOST("servicedispatcher writes to stderr: %s", result->stderr.c_str());
+    if (!result->stderrStr.empty()) {
+        LOG_HOST("servicedispatcher writes to stderr: %s", result->stderrStr.c_str());
     }
 
     if (!result->stdoutEndsWithNewLine()) {
@@ -148,7 +148,7 @@
         return nullptr;
     }
 
-    unsigned int devicePort = parsePortNumber(result->stdout, "device port");
+    unsigned int devicePort = parsePortNumber(result->stdoutStr, "device port");
     if (devicePort == 0) return nullptr;
 
     auto forwardResult = AdbForwarder::forward(devicePort);
diff --git a/libs/binder/UtilsHost.cpp b/libs/binder/UtilsHost.cpp
index d121ce2..52b8f69 100644
--- a/libs/binder/UtilsHost.cpp
+++ b/libs/binder/UtilsHost.cpp
@@ -63,7 +63,7 @@
     if (res.exitCode) os << "code=" << *res.exitCode;
     if (res.signal) os << "signal=" << *res.signal;
     if (res.pid) os << ", pid=" << *res.pid;
-    return os << ", stdout=" << res.stdout << ", stderr=" << res.stderr;
+    return os << ", stdout=" << res.stdoutStr << ", stderr=" << res.stderrStr;
 }
 
 std::string CommandResult::toString() const {
@@ -142,9 +142,9 @@
         int pollRet = poll(fds, nfds, 1000 /* ms timeout */);
         if (pollRet == -1) return android::base::ErrnoError() << "poll()";
 
-        if (!handlePoll(&ret.outPipe, outPollFd, &ret.stdout))
+        if (!handlePoll(&ret.outPipe, outPollFd, &ret.stdoutStr))
             return android::base::ErrnoError() << "read(stdout)";
-        if (!handlePoll(&ret.errPipe, errPollFd, &ret.stderr))
+        if (!handlePoll(&ret.errPipe, errPollFd, &ret.stderrStr))
             return android::base::ErrnoError() << "read(stderr)";
 
         if (end && end(ret)) return ret;
diff --git a/libs/binder/UtilsHost.h b/libs/binder/UtilsHost.h
index 0f29f60..98ac4e0 100644
--- a/libs/binder/UtilsHost.h
+++ b/libs/binder/UtilsHost.h
@@ -43,8 +43,8 @@
     std::optional<int32_t> exitCode;
     std::optional<int32_t> signal;
     std::optional<pid_t> pid;
-    std::string stdout;
-    std::string stderr;
+    std::string stdoutStr;
+    std::string stderrStr;
 
     android::base::unique_fd outPipe;
     android::base::unique_fd errPipe;
@@ -55,15 +55,15 @@
         std::swap(exitCode, other.exitCode);
         std::swap(signal, other.signal);
         std::swap(pid, other.pid);
-        std::swap(stdout, other.stdout);
-        std::swap(stderr, other.stderr);
+        std::swap(stdoutStr, other.stdoutStr);
+        std::swap(stderrStr, other.stderrStr);
         return *this;
     }
     ~CommandResult();
     [[nodiscard]] std::string toString() const;
 
     [[nodiscard]] bool stdoutEndsWithNewLine() const {
-        return !stdout.empty() && stdout.back() == '\n';
+        return !stdoutStr.empty() && stdoutStr.back() == '\n';
     }
 
 private:
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index 065e6e3..82bebc9 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -51,17 +51,11 @@
     static  status_t            freeze(pid_t pid, bool enabled, uint32_t timeout_ms);
 
     // Provide information about the state of a frozen process
-    static  status_t            getProcessFreezeInfo(pid_t pid, bool *sync_received,
-                                                    bool *async_received);
-
-    // TODO: Remove the above legacy duplicated function in next version
-#ifndef __ANDROID_VNDK__
     static  status_t            getProcessFreezeInfo(pid_t pid, uint32_t *sync_received,
                                                     uint32_t *async_received);
-#endif
 
             sp<ProcessState>    process();
-            
+
             status_t            clearLastError();
 
             /**
diff --git a/libs/binder/include/binder/RpcTransport.h b/libs/binder/include/binder/RpcTransport.h
index 1c0bb18..4fe2324 100644
--- a/libs/binder/include/binder/RpcTransport.h
+++ b/libs/binder/include/binder/RpcTransport.h
@@ -38,7 +38,7 @@
     virtual ~RpcTransport() = default;
 
     // replacement of ::recv(MSG_PEEK). Error code may not be set if TLS is enabled.
-    virtual android::base::Result<size_t> peek(void *buf, size_t size) = 0;
+    [[nodiscard]] virtual android::base::Result<size_t> peek(void *buf, size_t size) = 0;
 
     /**
      * Read (or write), but allow to be interrupted by a trigger.
@@ -47,9 +47,10 @@
      *   OK - succeeded in completely processing 'size'
      *   error - interrupted (failure or trigger)
      */
-    virtual status_t interruptableWriteFully(FdTrigger *fdTrigger, const void *buf,
-                                             size_t size) = 0;
-    virtual status_t interruptableReadFully(FdTrigger *fdTrigger, void *buf, size_t size) = 0;
+    [[nodiscard]] virtual status_t interruptableWriteFully(FdTrigger *fdTrigger, const void *buf,
+                                                           size_t size) = 0;
+    [[nodiscard]] virtual status_t interruptableReadFully(FdTrigger *fdTrigger, void *buf,
+                                                          size_t size) = 0;
 
 protected:
     RpcTransport() = default;
diff --git a/libs/binder/include/binder/RpcCertificateVerifier.h b/libs/binder/include_tls/binder/RpcCertificateVerifier.h
similarity index 100%
rename from libs/binder/include/binder/RpcCertificateVerifier.h
rename to libs/binder/include_tls/binder/RpcCertificateVerifier.h
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index f88896f..49c7b7c 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -373,6 +373,12 @@
     return clazz->getInterfaceDescriptorUtf8();
 }
 
+AIBinder_DeathRecipient::TransferDeathRecipient::~TransferDeathRecipient() {
+    if (mOnUnlinked != nullptr) {
+        mOnUnlinked(mCookie);
+    }
+}
+
 void AIBinder_DeathRecipient::TransferDeathRecipient::binderDied(const wp<IBinder>& who) {
     CHECK(who == mWho) << who.unsafe_get() << "(" << who.get_refs() << ") vs " << mWho.unsafe_get()
                        << " (" << mWho.get_refs() << ")";
@@ -394,7 +400,7 @@
 }
 
 AIBinder_DeathRecipient::AIBinder_DeathRecipient(AIBinder_DeathRecipient_onBinderDied onDied)
-    : mOnDied(onDied) {
+    : mOnDied(onDied), mOnUnlinked(nullptr) {
     CHECK(onDied != nullptr);
 }
 
@@ -412,10 +418,12 @@
     std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
 
     sp<TransferDeathRecipient> recipient =
-            new TransferDeathRecipient(binder, cookie, this, mOnDied);
+            new TransferDeathRecipient(binder, cookie, this, mOnDied, mOnUnlinked);
 
     status_t status = binder->linkToDeath(recipient, cookie, 0 /*flags*/);
     if (status != STATUS_OK) {
+        // When we failed to link, the destructor of TransferDeathRecipient runs here, which
+        // ensures that mOnUnlinked is called before we return with an error from this method.
         return PruneStatusT(status);
     }
 
@@ -448,6 +456,10 @@
     return STATUS_NAME_NOT_FOUND;
 }
 
+void AIBinder_DeathRecipient::setOnUnlinked(AIBinder_DeathRecipient_onBinderUnlinked onUnlinked) {
+    mOnUnlinked = onUnlinked;
+}
+
 // start of C-API methods
 
 AIBinder* AIBinder_new(const AIBinder_Class* clazz, void* args) {
@@ -689,6 +701,15 @@
     return ret;
 }
 
+void AIBinder_DeathRecipient_setOnUnlinked(AIBinder_DeathRecipient* recipient,
+                                           AIBinder_DeathRecipient_onBinderUnlinked onUnlinked) {
+    if (recipient == nullptr) {
+        return;
+    }
+
+    recipient->setOnUnlinked(onUnlinked);
+}
+
 void AIBinder_DeathRecipient_delete(AIBinder_DeathRecipient* recipient) {
     if (recipient == nullptr) {
         return;
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index 3cb95ea..730e51b 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -148,8 +148,14 @@
     struct TransferDeathRecipient : ::android::IBinder::DeathRecipient {
         TransferDeathRecipient(const ::android::wp<::android::IBinder>& who, void* cookie,
                                const ::android::wp<AIBinder_DeathRecipient>& parentRecipient,
-                               const AIBinder_DeathRecipient_onBinderDied onDied)
-            : mWho(who), mCookie(cookie), mParentRecipient(parentRecipient), mOnDied(onDied) {}
+                               const AIBinder_DeathRecipient_onBinderDied onDied,
+                               const AIBinder_DeathRecipient_onBinderUnlinked onUnlinked)
+            : mWho(who),
+              mCookie(cookie),
+              mParentRecipient(parentRecipient),
+              mOnDied(onDied),
+              mOnUnlinked(onUnlinked) {}
+        ~TransferDeathRecipient();
 
         void binderDied(const ::android::wp<::android::IBinder>& who) override;
 
@@ -165,11 +171,13 @@
         // This is kept separately from AIBinder_DeathRecipient in case the death recipient is
         // deleted while the death notification is fired
         const AIBinder_DeathRecipient_onBinderDied mOnDied;
+        const AIBinder_DeathRecipient_onBinderUnlinked mOnUnlinked;
     };
 
     explicit AIBinder_DeathRecipient(AIBinder_DeathRecipient_onBinderDied onDied);
     binder_status_t linkToDeath(const ::android::sp<::android::IBinder>&, void* cookie);
     binder_status_t unlinkToDeath(const ::android::sp<::android::IBinder>& binder, void* cookie);
+    void setOnUnlinked(AIBinder_DeathRecipient_onBinderUnlinked onUnlinked);
 
    private:
     // When the user of this API deletes a Bp object but not the death recipient, the
@@ -180,4 +188,5 @@
     std::mutex mDeathRecipientsMutex;
     std::vector<::android::sp<TransferDeathRecipient>> mDeathRecipients;
     AIBinder_DeathRecipient_onBinderDied mOnDied;
+    AIBinder_DeathRecipient_onBinderUnlinked mOnUnlinked;
 };
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index b881c2c..43533c5 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -319,9 +319,9 @@
 /**
  * Registers for notifications that the associated binder is dead. The same death recipient may be
  * associated with multiple different binders. If the binder is local, then no death recipient will
- * be given (since if the local process dies, then no recipient will exist to recieve a
+ * be given (since if the local process dies, then no recipient will exist to receive a
  * transaction). The cookie is passed to recipient in the case that this binder dies and can be
- * null. The exact cookie must also be used to unlink this transaction (see AIBinder_linkToDeath).
+ * null. The exact cookie must also be used to unlink this transaction (see AIBinder_unlinkToDeath).
  * This function may return a binder transaction failure. The cookie can be used both for
  * identification and holding user data.
  *
@@ -348,6 +348,10 @@
  * If the binder dies, it will automatically unlink. If the binder is deleted, it will be
  * automatically unlinked.
  *
+ * Be aware that it is not safe to immediately deallocate the cookie when this call returns. If you
+ * need to clean up the cookie, you should do so in the onUnlinked callback, which can be set using
+ * AIBinder_DeathRecipient_setOnUnlinked.
+ *
  * Available since API level 29.
  *
  * \param binder the binder object to remove a previously linked death recipient from.
@@ -568,6 +572,22 @@
 typedef void (*AIBinder_DeathRecipient_onBinderDied)(void* cookie) __INTRODUCED_IN(29);
 
 /**
+ * This function is intended for cleaning up the data in the provided cookie, and it is executed
+ * when the DeathRecipient is unlinked. When the DeathRecipient is unlinked due to a death receipt,
+ * this method is called after the call to onBinderDied.
+ *
+ * This method is called once for each binder that is unlinked. Hence, if the same cookie is passed
+ * to multiple binders, then the caller is responsible for reference counting the cookie.
+ *
+ * See also AIBinder_linkToDeath/AIBinder_unlinkToDeath.
+ *
+ * Available since API level 33.
+ *
+ * \param cookie the cookie passed to AIBinder_linkToDeath.
+ */
+typedef void (*AIBinder_DeathRecipient_onBinderUnlinked)(void* cookie) __INTRODUCED_IN(33);
+
+/**
  * Creates a new binder death recipient. This can be attached to multiple different binder objects.
  *
  * Available since API level 29.
@@ -580,9 +600,47 @@
         AIBinder_DeathRecipient_onBinderDied onBinderDied) __INTRODUCED_IN(29);
 
 /**
+ * Set the callback to be called when this DeathRecipient is unlinked from a binder. The callback is
+ * called in the following situations:
+ *
+ *  1. If the binder died, shortly after the call to onBinderDied.
+ *  2. If the binder is explicitly unlinked with AIBinder_unlinkToDeath or
+ *     AIBinder_DeathRecipient_delete.
+ *  3. During or shortly after the AIBinder_linkToDeath call if it returns an error.
+ *
+ * It is guaranteed that the callback is called exactly once for each call to linkToDeath unless the
+ * process is aborted before the binder is unlinked.
+ *
+ * Be aware that when the binder is explicitly unlinked, it is not guaranteed that onUnlinked has
+ * been called before the call to AIBinder_unlinkToDeath or AIBinder_DeathRecipient_delete returns.
+ * For example, if the binder dies concurrently with a call to AIBinder_unlinkToDeath, the binder is
+ * not unlinked until after the death notification is delivered, even if AIBinder_unlinkToDeath
+ * returns before that happens.
+ *
+ * This method should be called before linking the DeathRecipient to a binder because the function
+ * pointer is cached. If you change it after linking to a binder, it is unspecified whether the old
+ * binder will call the old or new onUnlinked callback.
+ *
+ * The onUnlinked argument may be null. In this case, no notification is given when the binder is
+ * unlinked.
+ *
+ * Available since API level 33.
+ *
+ * \param recipient the DeathRecipient to set the onUnlinked callback for.
+ * \param onUnlinked the callback to call when a binder is unlinked from recipient.
+ */
+void AIBinder_DeathRecipient_setOnUnlinked(AIBinder_DeathRecipient* recipient,
+                                           AIBinder_DeathRecipient_onBinderUnlinked onUnlinked)
+        __INTRODUCED_IN(33);
+
+/**
  * Deletes a binder death recipient. It is not necessary to call AIBinder_unlinkToDeath before
  * calling this as these will all be automatically unlinked.
  *
+ * Be aware that it is not safe to immediately deallocate the cookie when this call returns. If you
+ * need to clean up the cookie, you should do so in the onUnlinked callback, which can be set using
+ * AIBinder_DeathRecipient_setOnUnlinked.
+ *
  * Available since API level 29.
  *
  * \param recipient the binder to delete (previously created with AIBinder_DeathRecipient_new).
diff --git a/libs/binder/ndk/include_ndk/android/binder_parcel.h b/libs/binder/ndk/include_ndk/android/binder_parcel.h
index a2f5c93..8457581 100644
--- a/libs/binder/ndk/include_ndk/android/binder_parcel.h
+++ b/libs/binder/ndk/include_ndk/android/binder_parcel.h
@@ -1167,6 +1167,8 @@
 /**
  * Marshals the raw bytes of the Parcel to a buffer.
  *
+ * Available since API level 33.
+ *
  * The parcel must not contain any binders or file descriptors.
  *
  * The data you retrieve here must not be placed in any kind of persistent storage. (on local disk,
@@ -1189,6 +1191,8 @@
 /**
  * Set the data in the parcel to the raw bytes from the buffer.
  *
+ * Available since API level 33.
+ *
  * \param parcel The parcel to set data.
  * \param buffer The data buffer to set.
  * \param len The size of the data to set.
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index ac892db..8605686 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -144,6 +144,7 @@
 LIBBINDER_NDK33 { # introduced=33
   global:
     AIBinder_Class_disableInterfaceTokenHeader;
+    AIBinder_DeathRecipient_setOnUnlinked;
     AParcel_marshal;
     AParcel_unmarshal;
 };
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 4b36530..499f88e 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -395,9 +395,16 @@
             << "Service failed to shut down.";
 }
 
+struct DeathRecipientCookie {
+    std::function<void(void)>*onDeath, *onUnlink;
+};
 void LambdaOnDeath(void* cookie) {
-    auto onDeath = static_cast<std::function<void(void)>*>(cookie);
-    (*onDeath)();
+    auto funcs = static_cast<DeathRecipientCookie*>(cookie);
+    (*funcs->onDeath)();
+};
+void LambdaOnUnlink(void* cookie) {
+    auto funcs = static_cast<DeathRecipientCookie*>(cookie);
+    (*funcs->onUnlink)();
 };
 TEST(NdkBinder, DeathRecipient) {
     using namespace std::chrono_literals;
@@ -409,26 +416,46 @@
 
     std::mutex deathMutex;
     std::condition_variable deathCv;
-    bool deathRecieved = false;
+    bool deathReceived = false;
 
     std::function<void(void)> onDeath = [&] {
         std::cerr << "Binder died (as requested)." << std::endl;
-        deathRecieved = true;
+        deathReceived = true;
         deathCv.notify_one();
     };
 
-    AIBinder_DeathRecipient* recipient = AIBinder_DeathRecipient_new(LambdaOnDeath);
+    std::mutex unlinkMutex;
+    std::condition_variable unlinkCv;
+    bool unlinkReceived = false;
+    bool wasDeathReceivedFirst = false;
 
-    EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient, static_cast<void*>(&onDeath)));
+    std::function<void(void)> onUnlink = [&] {
+        std::cerr << "Binder unlinked (as requested)." << std::endl;
+        wasDeathReceivedFirst = deathReceived;
+        unlinkReceived = true;
+        unlinkCv.notify_one();
+    };
+
+    DeathRecipientCookie cookie = {&onDeath, &onUnlink};
+
+    AIBinder_DeathRecipient* recipient = AIBinder_DeathRecipient_new(LambdaOnDeath);
+    AIBinder_DeathRecipient_setOnUnlinked(recipient, LambdaOnUnlink);
+
+    EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient, static_cast<void*>(&cookie)));
 
     // the binder driver should return this if the service dies during the transaction
     EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die());
 
     foo = nullptr;
 
-    std::unique_lock<std::mutex> lock(deathMutex);
-    EXPECT_TRUE(deathCv.wait_for(lock, 1s, [&] { return deathRecieved; }));
-    EXPECT_TRUE(deathRecieved);
+    std::unique_lock<std::mutex> lockDeath(deathMutex);
+    EXPECT_TRUE(deathCv.wait_for(lockDeath, 1s, [&] { return deathReceived; }));
+    EXPECT_TRUE(deathReceived);
+
+    std::unique_lock<std::mutex> lockUnlink(unlinkMutex);
+    EXPECT_TRUE(deathCv.wait_for(lockUnlink, 1s, [&] { return unlinkReceived; }));
+    EXPECT_TRUE(unlinkReceived);
+    EXPECT_TRUE(wasDeathReceivedFirst);
 
     AIBinder_DeathRecipient_delete(recipient);
     AIBinder_decStrong(binder);
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index dd0c7b8..41ceee5 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -152,20 +152,46 @@
     /// available.
     fn get_extension(&mut self) -> Result<Option<SpIBinder>>;
 
+    /// Create a Parcel that can be used with `submit_transact`.
+    fn prepare_transact(&self) -> Result<Parcel>;
+
     /// Perform a generic operation with the object.
     ///
+    /// The provided [`Parcel`] must have been created by a call to
+    /// `prepare_transact` on the same binder.
+    ///
+    /// # Arguments
+    ///
+    /// * `code` - Transaction code for the operation.
+    /// * `data` - [`Parcel`] with input data.
+    /// * `flags` - Transaction flags, e.g. marking the transaction as
+    ///   asynchronous ([`FLAG_ONEWAY`](FLAG_ONEWAY)).
+    fn submit_transact(
+        &self,
+        code: TransactionCode,
+        data: Parcel,
+        flags: TransactionFlags,
+    ) -> Result<Parcel>;
+
+    /// Perform a generic operation with the object. This is a convenience
+    /// method that internally calls `prepare_transact` followed by
+    /// `submit_transact.
+    ///
     /// # Arguments
     /// * `code` - Transaction code for the operation
-    /// * `data` - [`Parcel`] with input data
-    /// * `reply` - Optional [`Parcel`] for reply data
     /// * `flags` - Transaction flags, e.g. marking the transaction as
     ///   asynchronous ([`FLAG_ONEWAY`](FLAG_ONEWAY))
+    /// * `input_callback` A callback for building the `Parcel`.
     fn transact<F: FnOnce(&mut Parcel) -> Result<()>>(
         &self,
         code: TransactionCode,
         flags: TransactionFlags,
         input_callback: F,
-    ) -> Result<Parcel>;
+    ) -> Result<Parcel> {
+        let mut parcel = self.prepare_transact()?;
+        input_callback(&mut parcel)?;
+        self.submit_transact(code, parcel, flags)
+    }
 }
 
 /// Interface of binder local or remote objects.
diff --git a/libs/binder/rust/src/parcel.rs b/libs/binder/rust/src/parcel.rs
index a0e991c..dad89ec 100644
--- a/libs/binder/rust/src/parcel.rs
+++ b/libs/binder/rust/src/parcel.rs
@@ -25,6 +25,7 @@
 use std::convert::TryInto;
 use std::mem::ManuallyDrop;
 use std::ptr;
+use std::fmt;
 
 mod file_descriptor;
 mod parcelable;
@@ -96,6 +97,13 @@
         let _ = ManuallyDrop::new(self);
         ptr
     }
+
+    pub(crate) fn is_owned(&self) -> bool {
+        match *self {
+            Self::Owned(_) => true,
+            Self::Borrowed(_) => false,
+        }
+    }
 }
 
 // Data serialization methods
@@ -412,6 +420,13 @@
     }
 }
 
+impl fmt::Debug for Parcel {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.debug_struct("Parcel")
+            .finish()
+    }
+}
+
 #[cfg(test)]
 impl Parcel {
     /// Create a new parcel tied to a bogus binder. TESTING ONLY!
diff --git a/libs/binder/rust/src/parcel/file_descriptor.rs b/libs/binder/rust/src/parcel/file_descriptor.rs
index 179b7c8..f71a686 100644
--- a/libs/binder/rust/src/parcel/file_descriptor.rs
+++ b/libs/binder/rust/src/parcel/file_descriptor.rs
@@ -23,7 +23,7 @@
 use crate::sys;
 
 use std::fs::File;
-use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
 
 /// Rust version of the Java class android.os.ParcelFileDescriptor
 #[derive(Debug)]
@@ -54,6 +54,12 @@
     }
 }
 
+impl IntoRawFd for ParcelFileDescriptor {
+    fn into_raw_fd(self) -> RawFd {
+        self.0.into_raw_fd()
+    }
+}
+
 impl Serialize for ParcelFileDescriptor {
     fn serialize(&self, parcel: &mut Parcel) -> Result<()> {
         let fd = self.0.as_raw_fd();
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index b03ed49..68fa34b 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -31,8 +31,10 @@
 use std::convert::TryInto;
 use std::ffi::{c_void, CString};
 use std::fmt;
+use std::mem;
 use std::os::unix::io::AsRawFd;
 use std::ptr;
+use std::sync::Arc;
 
 /// A strong reference to a Binder remote object.
 ///
@@ -233,13 +235,7 @@
 }
 
 impl<T: AsNative<sys::AIBinder>> IBinderInternal for T {
-    /// Perform a binder transaction
-    fn transact<F: FnOnce(&mut Parcel) -> Result<()>>(
-        &self,
-        code: TransactionCode,
-        flags: TransactionFlags,
-        input_callback: F,
-    ) -> Result<Parcel> {
+    fn prepare_transact(&self) -> Result<Parcel> {
         let mut input = ptr::null_mut();
         let status = unsafe {
             // Safety: `SpIBinder` guarantees that `self` always contains a
@@ -252,15 +248,25 @@
             // pointer, or null.
             sys::AIBinder_prepareTransaction(self.as_native() as *mut sys::AIBinder, &mut input)
         };
+
         status_result(status)?;
-        let mut input = unsafe {
+
+        unsafe {
             // Safety: At this point, `input` is either a valid, owned `AParcel`
             // pointer, or null. `Parcel::owned` safely handles both cases,
             // taking ownership of the parcel.
-            Parcel::owned(input).ok_or(StatusCode::UNEXPECTED_NULL)?
-        };
-        input_callback(&mut input)?;
+            Parcel::owned(input).ok_or(StatusCode::UNEXPECTED_NULL)
+        }
+    }
+
+    fn submit_transact(
+        &self,
+        code: TransactionCode,
+        data: Parcel,
+        flags: TransactionFlags,
+    ) -> Result<Parcel> {
         let mut reply = ptr::null_mut();
+        assert!(data.is_owned());
         let status = unsafe {
             // Safety: `SpIBinder` guarantees that `self` always contains a
             // valid pointer to an `AIBinder`. Although `IBinder::transact` is
@@ -275,13 +281,13 @@
             // only providing `on_transact` with an immutable reference to
             // `self`.
             //
-            // This call takes ownership of the `input` parcel pointer, and
+            // This call takes ownership of the `data` parcel pointer, and
             // passes ownership of the `reply` out parameter to its caller. It
             // does not affect ownership of the `binder` parameter.
             sys::AIBinder_transact(
                 self.as_native() as *mut sys::AIBinder,
                 code,
-                &mut input.into_raw(),
+                &mut data.into_raw(),
                 &mut reply,
                 flags,
             )
@@ -378,13 +384,17 @@
             // Safety: `SpIBinder` guarantees that `self` always contains a
             // valid pointer to an `AIBinder`. `recipient` can always be
             // converted into a valid pointer to an
-            // `AIBinder_DeathRecipient`. Any value is safe to pass as the
-            // cookie, although we depend on this value being set by
-            // `get_cookie` when the death recipient callback is called.
+            // `AIBinder_DeathRecipient`.
+            //
+            // The cookie is also the correct pointer, and by calling new_cookie,
+            // we have created a new ref-count to the cookie, which linkToDeath
+            // takes ownership of. Once the DeathRecipient is unlinked for any
+            // reason (including if this call fails), the onUnlinked callback
+            // will consume that ref-count.
             sys::AIBinder_linkToDeath(
                 self.as_native_mut(),
                 recipient.as_native_mut(),
-                recipient.get_cookie(),
+                recipient.new_cookie(),
             )
         })
     }
@@ -552,10 +562,20 @@
 }
 
 /// Rust wrapper around DeathRecipient objects.
+///
+/// The cookie in this struct represents an Arc<F> for the owned callback.
+/// This struct owns a ref-count of it, and so does every binder that we
+/// have been linked with.
 #[repr(C)]
 pub struct DeathRecipient {
     recipient: *mut sys::AIBinder_DeathRecipient,
-    callback: Box<dyn Fn() + Send + 'static>,
+    cookie: *mut c_void,
+    vtable: &'static DeathRecipientVtable,
+}
+
+struct DeathRecipientVtable {
+    cookie_incr_refcount: unsafe extern "C" fn(*mut c_void),
+    cookie_decr_refcount: unsafe extern "C" fn(*mut c_void),
 }
 
 impl DeathRecipient {
@@ -563,9 +583,9 @@
     /// associated object dies.
     pub fn new<F>(callback: F) -> DeathRecipient
     where
-        F: Fn() + Send + 'static,
+        F: Fn() + Send + Sync + 'static,
     {
-        let callback = Box::new(callback);
+        let callback: *const F = Arc::into_raw(Arc::new(callback));
         let recipient = unsafe {
             // Safety: The function pointer is a valid death recipient callback.
             //
@@ -574,34 +594,85 @@
             // no longer needed.
             sys::AIBinder_DeathRecipient_new(Some(Self::binder_died::<F>))
         };
+        unsafe {
+            // Safety: The function pointer is a valid onUnlinked callback.
+            //
+            // All uses of linkToDeath in this file correctly increment the
+            // ref-count that this onUnlinked callback will decrement.
+            sys::AIBinder_DeathRecipient_setOnUnlinked(recipient, Some(Self::cookie_decr_refcount::<F>));
+        }
         DeathRecipient {
             recipient,
-            callback,
+            cookie: callback as *mut c_void,
+            vtable: &DeathRecipientVtable {
+                cookie_incr_refcount: Self::cookie_incr_refcount::<F>,
+                cookie_decr_refcount: Self::cookie_decr_refcount::<F>,
+            },
         }
     }
 
+    /// Increment the ref-count for the cookie and return it.
+    ///
+    /// # Safety
+    ///
+    /// The caller must handle the returned ref-count correctly.
+    unsafe fn new_cookie(&self) -> *mut c_void {
+        (self.vtable.cookie_incr_refcount)(self.cookie);
+
+        // Return a raw pointer with ownership of a ref-count
+        self.cookie
+    }
+
     /// Get the opaque cookie that identifies this death recipient.
     ///
     /// This cookie will be used to link and unlink this death recipient to a
     /// binder object and will be passed to the `binder_died` callback as an
     /// opaque userdata pointer.
     fn get_cookie(&self) -> *mut c_void {
-        &*self.callback as *const _ as *mut c_void
+        self.cookie
     }
 
     /// Callback invoked from C++ when the binder object dies.
     ///
     /// # Safety
     ///
-    /// The `cookie` parameter must have been created with the `get_cookie`
-    /// method of this object.
+    /// The `cookie` parameter must be the cookie for an Arc<F> and
+    /// the caller must hold a ref-count to it.
     unsafe extern "C" fn binder_died<F>(cookie: *mut c_void)
     where
-        F: Fn() + Send + 'static,
+        F: Fn() + Send + Sync + 'static,
     {
-        let callback = (cookie as *mut F).as_ref().unwrap();
+        let callback = (cookie as *const F).as_ref().unwrap();
         callback();
     }
+
+    /// Callback that decrements the ref-count.
+    /// This is invoked from C++ when a binder is unlinked.
+    ///
+    /// # Safety
+    ///
+    /// The `cookie` parameter must be the cookie for an Arc<F> and
+    /// the owner must give up a ref-count to it.
+    unsafe extern "C" fn cookie_decr_refcount<F>(cookie: *mut c_void)
+    where
+        F: Fn() + Send + Sync + 'static,
+    {
+        drop(Arc::from_raw(cookie as *const F));
+    }
+
+    /// Callback that increments the ref-count.
+    ///
+    /// # Safety
+    ///
+    /// The `cookie` parameter must be the cookie for an Arc<F> and
+    /// the owner must handle the created ref-count properly.
+    unsafe extern "C" fn cookie_incr_refcount<F>(cookie: *mut c_void)
+    where
+        F: Fn() + Send + Sync + 'static,
+    {
+        let arc = mem::ManuallyDrop::new(Arc::from_raw(cookie as *const F));
+        mem::forget(Arc::clone(&arc));
+    }
 }
 
 /// # Safety
@@ -627,6 +698,12 @@
             // `AIBinder_DeathRecipient_new` when `self` was created. This
             // delete method can only be called once when `self` is dropped.
             sys::AIBinder_DeathRecipient_delete(self.recipient);
+
+            // Safety: We own a ref-count to the cookie, and so does every
+            // linked binder. This call gives up our ref-count. The linked
+            // binders should already have given up their ref-count, or should
+            // do so shortly.
+            (self.vtable.cookie_decr_refcount)(self.cookie)
         }
     }
 }
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index da8907d..335e8d8 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -363,13 +363,58 @@
         );
     }
 
-    fn register_death_notification(binder: &mut SpIBinder) -> (Arc<AtomicBool>, DeathRecipient) {
+    struct Bools {
+        binder_died: Arc<AtomicBool>,
+        binder_dealloc: Arc<AtomicBool>,
+    }
+
+    impl Bools {
+        fn is_dead(&self) -> bool {
+            self.binder_died.load(Ordering::Relaxed)
+        }
+        fn assert_died(&self) {
+            assert!(
+                self.is_dead(),
+                "Did not receive death notification"
+            );
+        }
+        fn assert_dropped(&self) {
+            assert!(
+                self.binder_dealloc.load(Ordering::Relaxed),
+                "Did not dealloc death notification"
+            );
+        }
+        fn assert_not_dropped(&self) {
+            assert!(
+                !self.binder_dealloc.load(Ordering::Relaxed),
+                "Dealloc death notification too early"
+            );
+        }
+    }
+
+    fn register_death_notification(binder: &mut SpIBinder) -> (Bools, DeathRecipient) {
         let binder_died = Arc::new(AtomicBool::new(false));
+        let binder_dealloc = Arc::new(AtomicBool::new(false));
+
+        struct SetOnDrop {
+            binder_dealloc: Arc<AtomicBool>,
+        }
+        impl Drop for SetOnDrop {
+            fn drop(&mut self) {
+                self.binder_dealloc.store(true, Ordering::Relaxed);
+            }
+        }
 
         let mut death_recipient = {
             let flag = binder_died.clone();
+            let set_on_drop = SetOnDrop {
+                binder_dealloc: binder_dealloc.clone(),
+            };
             DeathRecipient::new(move || {
                 flag.store(true, Ordering::Relaxed);
+                // Force the closure to take ownership of set_on_drop. When the closure is
+                // dropped, the destructor of `set_on_drop` will run.
+                let _ = &set_on_drop;
             })
         };
 
@@ -377,7 +422,12 @@
             .link_to_death(&mut death_recipient)
             .expect("link_to_death failed");
 
-        (binder_died, death_recipient)
+        let bools = Bools {
+            binder_died,
+            binder_dealloc,
+        };
+
+        (bools, death_recipient)
     }
 
     /// Killing a remote service should unregister the service and trigger
@@ -390,7 +440,7 @@
         let service_process = ScopedServiceProcess::new(service_name);
         let mut remote = binder::get_service(service_name).expect("Could not retrieve service");
 
-        let (binder_died, _recipient) = register_death_notification(&mut remote);
+        let (bools, recipient) = register_death_notification(&mut remote);
 
         drop(service_process);
         remote
@@ -400,10 +450,12 @@
         // Pause to ensure any death notifications get delivered
         thread::sleep(Duration::from_secs(1));
 
-        assert!(
-            binder_died.load(Ordering::Relaxed),
-            "Did not receive death notification"
-        );
+        bools.assert_died();
+        bools.assert_not_dropped();
+
+        drop(recipient);
+
+        bools.assert_dropped();
     }
 
     /// Test unregistering death notifications.
@@ -415,7 +467,7 @@
         let service_process = ScopedServiceProcess::new(service_name);
         let mut remote = binder::get_service(service_name).expect("Could not retrieve service");
 
-        let (binder_died, mut recipient) = register_death_notification(&mut remote);
+        let (bools, mut recipient) = register_death_notification(&mut remote);
 
         remote
             .unlink_to_death(&mut recipient)
@@ -430,9 +482,13 @@
         thread::sleep(Duration::from_secs(1));
 
         assert!(
-            !binder_died.load(Ordering::Relaxed),
+            !bools.is_dead(),
             "Received unexpected death notification after unlinking",
         );
+
+        bools.assert_not_dropped();
+        drop(recipient);
+        bools.assert_dropped();
     }
 
     /// Dropping a remote handle should unregister any death notifications.
@@ -444,7 +500,7 @@
         let service_process = ScopedServiceProcess::new(service_name);
         let mut remote = binder::get_service(service_name).expect("Could not retrieve service");
 
-        let (binder_died, _recipient) = register_death_notification(&mut remote);
+        let (bools, recipient) = register_death_notification(&mut remote);
 
         // This should automatically unregister our death notification.
         drop(remote);
@@ -457,9 +513,13 @@
         // We dropped the remote handle, so we should not receive the death
         // notification when the remote process dies here.
         assert!(
-            !binder_died.load(Ordering::Relaxed),
+            !bools.is_dead(),
             "Received unexpected death notification after dropping remote handle"
         );
+
+        bools.assert_not_dropped();
+        drop(recipient);
+        bools.assert_dropped();
     }
 
     /// Test IBinder interface methods not exercised elsewhere.
@@ -637,4 +697,27 @@
         assert!(!(service1 > service1));
         assert_eq!(service1 < service2, !(service2 < service1));
     }
+
+    #[test]
+    fn binder_parcel_mixup() {
+        let service1 = BnTest::new_binder(
+            TestService::new("testing_service1"),
+            BinderFeatures::default(),
+        );
+        let service2 = BnTest::new_binder(
+            TestService::new("testing_service2"),
+            BinderFeatures::default(),
+        );
+
+        let service1 = service1.as_binder();
+        let service2 = service2.as_binder();
+
+        let parcel = service1.prepare_transact().unwrap();
+        let res = service2.submit_transact(super::TestTransactionCode::Test as binder::TransactionCode, parcel, 0);
+
+        match res {
+            Ok(_) => panic!("submit_transact should fail"),
+            Err(err) => assert_eq!(err, binder::StatusCode::BAD_VALUE),
+        }
+    }
 }
diff --git a/libs/binder/tests/binderHostDeviceTest.cpp b/libs/binder/tests/binderHostDeviceTest.cpp
index 3f72b8f..eec3b44 100644
--- a/libs/binder/tests/binderHostDeviceTest.cpp
+++ b/libs/binder/tests/binderHostDeviceTest.cpp
@@ -75,10 +75,10 @@
         auto debuggableResult = execute(Split("adb shell getprop ro.debuggable", " "), nullptr);
         ASSERT_THAT(debuggableResult, Ok());
         ASSERT_EQ(0, debuggableResult->exitCode) << *debuggableResult;
-        auto debuggableBool = ParseBool(Trim(debuggableResult->stdout));
-        ASSERT_NE(ParseBoolResult::kError, debuggableBool) << Trim(debuggableResult->stdout);
+        auto debuggableBool = ParseBool(Trim(debuggableResult->stdoutStr));
+        ASSERT_NE(ParseBoolResult::kError, debuggableBool) << Trim(debuggableResult->stdoutStr);
         if (debuggableBool == ParseBoolResult::kFalse) {
-            GTEST_SKIP() << "ro.debuggable=" << Trim(debuggableResult->stdout);
+            GTEST_SKIP() << "ro.debuggable=" << Trim(debuggableResult->stdoutStr);
         }
 
         auto lsResult = execute(Split("adb shell which servicedispatcher", " "), nullptr);
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 639876f..c69203b 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -491,7 +491,7 @@
     EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, true, 1000));
     EXPECT_EQ(FAILED_TRANSACTION, m_server->transact(BINDER_LIB_TEST_NOP_TRANSACTION, data, &reply));
 
-    bool sync_received, async_received;
+    uint32_t sync_received, async_received;
 
     EXPECT_EQ(NO_ERROR, IPCThreadState::self()->getProcessFreezeInfo(pid, &sync_received,
                 &async_received));
@@ -499,15 +499,7 @@
     EXPECT_EQ(sync_received, 1);
     EXPECT_EQ(async_received, 0);
 
-    uint32_t sync_received2, async_received2;
-
-    EXPECT_EQ(NO_ERROR, IPCThreadState::self()->getProcessFreezeInfo(pid, &sync_received2,
-                &async_received2));
-
-    EXPECT_EQ(sync_received2, 1);
-    EXPECT_EQ(async_received2, 0);
-
-    EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, 0, 0));
+    EXPECT_EQ(NO_ERROR, IPCThreadState::self()->freeze(pid, false, 0));
     EXPECT_EQ(NO_ERROR, m_server->transact(BINDER_LIB_TEST_NOP_TRANSACTION, data, &reply));
 }
 
diff --git a/libs/binder/tests/binderRpcBenchmark.cpp b/libs/binder/tests/binderRpcBenchmark.cpp
index e430c28..55aa57b 100644
--- a/libs/binder/tests/binderRpcBenchmark.cpp
+++ b/libs/binder/tests/binderRpcBenchmark.cpp
@@ -211,10 +211,9 @@
     for (size_t tries = 0; tries < 5; tries++) {
         usleep(10000);
         status = gSession->setupUnixDomainClient(addr.c_str());
-        if (status == OK) goto success;
+        if (status == OK) break;
     }
-    LOG(FATAL) << "Could not connect: " << statusToString(status).c_str();
-success:
+    CHECK_EQ(status, OK) << "Could not connect: " << statusToString(status).c_str();
 
     ::benchmark::RunSpecifiedBenchmarks();
     return 0;
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index 2fd63a3..cc1d2fa 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -615,25 +615,20 @@
                     status = session->setupPreconnectedClient({}, [=]() {
                         return connectTo(UnixSocketAddress(addr.c_str()));
                     });
-                    if (status == OK) goto success;
                     break;
                 case SocketType::UNIX:
                     status = session->setupUnixDomainClient(addr.c_str());
-                    if (status == OK) goto success;
                     break;
                 case SocketType::VSOCK:
                     status = session->setupVsockClient(VMADDR_CID_LOCAL, vsockPort);
-                    if (status == OK) goto success;
                     break;
                 case SocketType::INET:
                     status = session->setupInetClient("127.0.0.1", serverInfo.port);
-                    if (status == OK) goto success;
                     break;
                 default:
                     LOG_ALWAYS_FATAL("Unknown socket type");
             }
-            LOG_ALWAYS_FATAL("Could not connect %s", statusToString(status).c_str());
-        success:
+            CHECK_EQ(status, OK) << "Could not connect: " << statusToString(status);
             ret.sessions.push_back({session, session->getRootObject()});
         }
         return ret;
@@ -1436,13 +1431,32 @@
                         BinderRpcSimple::PrintTestParam);
 
 class RpcTransportTest
-      : public ::testing::TestWithParam<std::tuple<SocketType, RpcSecurity, RpcCertificateFormat>> {
+      : public ::testing::TestWithParam<
+                std::tuple<SocketType, RpcSecurity, std::optional<RpcCertificateFormat>>> {
 public:
     using ConnectToServer = std::function<base::unique_fd()>;
     static inline std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
         auto [socketType, rpcSecurity, certificateFormat] = info.param;
-        return PrintToString(socketType) + "_" + newFactory(rpcSecurity)->toCString() + "_" +
-                PrintToString(certificateFormat);
+        auto ret = PrintToString(socketType) + "_" + newFactory(rpcSecurity)->toCString();
+        if (certificateFormat.has_value()) ret += "_" + PrintToString(*certificateFormat);
+        return ret;
+    }
+    static std::vector<ParamType> getRpcTranportTestParams() {
+        std::vector<RpcTransportTest::ParamType> ret;
+        for (auto socketType : testSocketTypes(false /* hasPreconnected */)) {
+            for (auto rpcSecurity : RpcSecurityValues()) {
+                switch (rpcSecurity) {
+                    case RpcSecurity::RAW: {
+                        ret.emplace_back(socketType, rpcSecurity, std::nullopt);
+                    } break;
+                    case RpcSecurity::TLS: {
+                        ret.emplace_back(socketType, rpcSecurity, RpcCertificateFormat::PEM);
+                        ret.emplace_back(socketType, rpcSecurity, RpcCertificateFormat::DER);
+                    } break;
+                }
+            }
+        }
+        return ret;
     }
     void TearDown() override {
         for (auto& server : mServers) server->shutdownAndWait();
@@ -1642,8 +1656,9 @@
     status_t trust(A* a, B* b) {
         auto [socketType, rpcSecurity, certificateFormat] = GetParam();
         if (rpcSecurity != RpcSecurity::TLS) return OK;
-        auto bCert = b->getCtx()->getCertificate(certificateFormat);
-        return a->getCertVerifier()->addTrustedPeerCertificate(certificateFormat, bCert);
+        LOG_ALWAYS_FATAL_IF(!certificateFormat.has_value());
+        auto bCert = b->getCtx()->getCertificate(*certificateFormat);
+        return a->getCertVerifier()->addTrustedPeerCertificate(*certificateFormat, bCert);
     }
 
     static constexpr const char* kMessage = "hello";
@@ -1779,9 +1794,9 @@
         }
 
         status = serverTransport->interruptableWriteFully(fdTrigger, msg2.data(), msg2.size());
-        if (status != -ECANCELED)
+        if (status != DEAD_OBJECT)
             return AssertionFailure() << "When FdTrigger is shut down, interruptableWriteFully "
-                                         "should return -ECANCELLED, but it is "
+                                         "should return DEAD_OBJECT, but it is "
                                       << statusToString(status);
         return AssertionSuccess();
     };
@@ -1799,40 +1814,29 @@
 
     server->setPostConnect(serverPostConnect);
 
-    // Start server
     server->start();
     // connect() to server and do handshake
     ASSERT_TRUE(client.setUpTransport());
-    // read the first message. This confirms that server has finished handshake and start handling
-    // client fd. Server thread should pause at waitForWriteBarrier.
+    // read the first message. This ensures that server has finished handshake and start handling
+    // client fd. Server thread should pause at writeCv.wait_for().
     ASSERT_TRUE(client.readMessage(kMessage));
     // Trigger server shutdown after server starts handling client FD. This ensures that the second
     // write is on an FdTrigger that has been shut down.
     server->shutdown();
     // Continues server thread to write the second message.
     {
-        std::unique_lock<std::mutex> lock(writeMutex);
+        std::lock_guard<std::mutex> lock(writeMutex);
         shouldContinueWriting = true;
-        lock.unlock();
-        writeCv.notify_all();
     }
+    writeCv.notify_all();
     // After this line, server thread unblocks and attempts to write the second message, but
-    // shutdown is triggered, so write should failed with -ECANCELLED. See |serverPostConnect|.
+    // shutdown is triggered, so write should failed with DEAD_OBJECT. See |serverPostConnect|.
     // On the client side, second read fails with DEAD_OBJECT
     ASSERT_FALSE(client.readMessage(msg2));
 }
 
-std::vector<RpcCertificateFormat> testRpcCertificateFormats() {
-    return {
-            RpcCertificateFormat::PEM,
-            RpcCertificateFormat::DER,
-    };
-}
-
 INSTANTIATE_TEST_CASE_P(BinderRpc, RpcTransportTest,
-                        ::testing::Combine(::testing::ValuesIn(testSocketTypes(false)),
-                                           ::testing::ValuesIn(RpcSecurityValues()),
-                                           ::testing::ValuesIn(testRpcCertificateFormats())),
+                        ::testing::ValuesIn(RpcTransportTest::getRpcTranportTestParams()),
                         RpcTransportTest::PrintParamInfo);
 
 } // namespace android
diff --git a/libs/binder/tests/binderUtilsHostTest.cpp b/libs/binder/tests/binderUtilsHostTest.cpp
index fb24836..4330e3e 100644
--- a/libs/binder/tests/binderUtilsHostTest.cpp
+++ b/libs/binder/tests/binderUtilsHostTest.cpp
@@ -34,7 +34,7 @@
     auto result = execute({"echo", "foo"}, nullptr);
     ASSERT_THAT(result, Ok());
     EXPECT_THAT(result->exitCode, Optional(EX_OK));
-    EXPECT_EQ(result->stdout, "foo\n");
+    EXPECT_EQ(result->stdoutStr, "foo\n");
 }
 
 TEST(UtilsHost, ExecuteLongRunning) {
@@ -44,7 +44,7 @@
         std::vector<std::string> args{"sh", "-c",
                                       "sleep 0.5 && echo -n f && sleep 0.5 && echo oo && sleep 1"};
         auto result = execute(std::move(args), [](const CommandResult& commandResult) {
-            return android::base::EndsWith(commandResult.stdout, "\n");
+            return android::base::EndsWith(commandResult.stdoutStr, "\n");
         });
         auto elapsed = std::chrono::system_clock::now() - now;
         auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
@@ -53,7 +53,7 @@
 
         ASSERT_THAT(result, Ok());
         EXPECT_EQ(std::nullopt, result->exitCode);
-        EXPECT_EQ(result->stdout, "foo\n");
+        EXPECT_EQ(result->stdoutStr, "foo\n");
     }
 
     // ~CommandResult() called, child process is killed.
@@ -70,7 +70,7 @@
         std::vector<std::string> args{"sh", "-c",
                                       "sleep 2 && echo -n f && sleep 2 && echo oo && sleep 2"};
         auto result = execute(std::move(args), [](const CommandResult& commandResult) {
-            return android::base::EndsWith(commandResult.stdout, "\n");
+            return android::base::EndsWith(commandResult.stdoutStr, "\n");
         });
         auto elapsed = std::chrono::system_clock::now() - now;
         auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
@@ -79,7 +79,7 @@
 
         ASSERT_THAT(result, Ok());
         EXPECT_EQ(std::nullopt, result->exitCode);
-        EXPECT_EQ(result->stdout, "foo\n");
+        EXPECT_EQ(result->stdoutStr, "foo\n");
     }
 
     // ~CommandResult() called, child process is killed.
diff --git a/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp b/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp
index 95582bf..20c5569 100644
--- a/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp
@@ -54,10 +54,9 @@
     for (size_t tries = 0; tries < 5; tries++) {
         usleep(10000);
         status = session->setupUnixDomainClient(addr.c_str());
-        if (status == OK) goto success;
+        if (status == OK) break;
     }
-    LOG(FATAL) << "Unable to connect";
-success:
+    CHECK_EQ(status, OK) << "Unable to connect";
 
     sp<BpBinder> bpBinder = session->getRootObject()->remoteBinder();
 
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 326da3a..8c359c7 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -55,6 +55,7 @@
 filegroup {
     name: "guiconstants_aidl",
     srcs: [
+        "android/gui/DropInputMode.aidl",
         "android/**/TouchOcclusionMode.aidl",
     ],
 }
@@ -65,11 +66,13 @@
     host_supported: true,
     srcs: [
         ":guiconstants_aidl",
+        "android/gui/DisplayInfo.aidl",
         "android/gui/FocusRequest.aidl",
         "android/gui/InputApplicationInfo.aidl",
         "android/gui/IWindowInfosListener.aidl",
         "android/gui/IWindowInfosReportedListener.aidl",
         "android/gui/WindowInfo.aidl",
+        "DisplayInfo.cpp",
         "WindowInfo.cpp",
     ],
 
@@ -90,7 +93,7 @@
     ],
 
     aidl: {
-        export_aidl_headers: true
+        export_aidl_headers: true,
     },
 
     include_dirs: [
@@ -135,8 +138,8 @@
     ],
 
     aidl: {
-        export_aidl_headers: true
-    }
+        export_aidl_headers: true,
+    },
 }
 
 cc_library_shared {
diff --git a/libs/gui/DisplayInfo.cpp b/libs/gui/DisplayInfo.cpp
new file mode 100644
index 0000000..52d9540
--- /dev/null
+++ b/libs/gui/DisplayInfo.cpp
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "DisplayInfo"
+
+#include <binder/Parcel.h>
+#include <gui/DisplayInfo.h>
+#include <private/gui/ParcelUtils.h>
+
+#include <log/log.h>
+
+namespace android::gui {
+
+// --- DisplayInfo ---
+
+status_t DisplayInfo::readFromParcel(const android::Parcel* parcel) {
+    if (parcel == nullptr) {
+        ALOGE("%s: Null parcel", __func__);
+        return BAD_VALUE;
+    }
+
+    float dsdx, dtdx, tx, dtdy, dsdy, ty;
+    SAFE_PARCEL(parcel->readInt32, &displayId);
+    SAFE_PARCEL(parcel->readInt32, &logicalWidth);
+    SAFE_PARCEL(parcel->readInt32, &logicalHeight);
+    SAFE_PARCEL(parcel->readFloat, &dsdx);
+    SAFE_PARCEL(parcel->readFloat, &dtdx);
+    SAFE_PARCEL(parcel->readFloat, &tx);
+    SAFE_PARCEL(parcel->readFloat, &dtdy);
+    SAFE_PARCEL(parcel->readFloat, &dsdy);
+    SAFE_PARCEL(parcel->readFloat, &ty);
+
+    transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
+
+    return OK;
+}
+
+status_t DisplayInfo::writeToParcel(android::Parcel* parcel) const {
+    if (parcel == nullptr) {
+        ALOGE("%s: Null parcel", __func__);
+        return BAD_VALUE;
+    }
+
+    SAFE_PARCEL(parcel->writeInt32, displayId);
+    SAFE_PARCEL(parcel->writeInt32, logicalWidth);
+    SAFE_PARCEL(parcel->writeInt32, logicalHeight);
+    SAFE_PARCEL(parcel->writeFloat, transform.dsdx());
+    SAFE_PARCEL(parcel->writeFloat, transform.dtdx());
+    SAFE_PARCEL(parcel->writeFloat, transform.tx());
+    SAFE_PARCEL(parcel->writeFloat, transform.dtdy());
+    SAFE_PARCEL(parcel->writeFloat, transform.dsdy());
+    SAFE_PARCEL(parcel->writeFloat, transform.ty());
+
+    return OK;
+}
+
+} // namespace android::gui
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 1fd9d13..a419a63 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -69,7 +69,8 @@
         isTrustedOverlay(false),
         bufferCrop(Rect::INVALID_RECT),
         destinationFrame(Rect::INVALID_RECT),
-        releaseBufferListener(nullptr) {
+        releaseBufferListener(nullptr),
+        dropInputMode(gui::DropInputMode::NONE) {
     matrix.dsdx = matrix.dtdy = 1.0f;
     matrix.dsdy = matrix.dtdx = 0.0f;
     hdrMetadata.validTypes = 0;
@@ -174,6 +175,7 @@
     SAFE_PARCEL(output.writeBool, isTrustedOverlay);
 
     SAFE_PARCEL(output.writeStrongBinder, releaseBufferEndpoint);
+    SAFE_PARCEL(output.writeUint32, static_cast<uint32_t>(dropInputMode));
     return NO_ERROR;
 }
 
@@ -304,6 +306,10 @@
     SAFE_PARCEL(input.readBool, &isTrustedOverlay);
 
     SAFE_PARCEL(input.readNullableStrongBinder, &releaseBufferEndpoint);
+
+    uint32_t mode;
+    SAFE_PARCEL(input.readUint32, &mode);
+    dropInputMode = static_cast<gui::DropInputMode>(mode);
     return NO_ERROR;
 }
 
@@ -558,6 +564,10 @@
     if (other.what & eProducerDisconnect) {
         what |= eProducerDisconnect;
     }
+    if (other.what & eDropInputModeChanged) {
+        what |= eDropInputModeChanged;
+        dropInputMode = other.dropInputMode;
+    }
     if ((other.what & what) != other.what) {
         ALOGE("Unmerged SurfaceComposer Transaction properties. LayerState::merge needs updating? "
               "other.what=0x%" PRIX64 " what=0x%" PRIX64 " unmerged flags=0x%" PRIX64,
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index 1bca6f9..64361db 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1739,6 +1739,21 @@
     return *this;
 }
 
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDropInputMode(
+        const sp<SurfaceControl>& sc, gui::DropInputMode mode) {
+    layer_state_t* s = getLayerState(sc);
+    if (!s) {
+        mStatus = BAD_INDEX;
+        return *this;
+    }
+
+    s->what |= layer_state_t::eDropInputModeChanged;
+    s->dropInputMode = mode;
+
+    registerSurfaceControlForCallback(sc);
+    return *this;
+}
+
 // ---------------------------------------------------------------------------
 
 DisplayState& SurfaceComposerClient::Transaction::getDisplayState(const sp<IBinder>& token) {
diff --git a/libs/gui/WindowInfo.cpp b/libs/gui/WindowInfo.cpp
index b2ef7aa..5f3a726 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -54,12 +54,11 @@
             info.frameLeft == frameLeft && info.frameTop == frameTop &&
             info.frameRight == frameRight && info.frameBottom == frameBottom &&
             info.surfaceInset == surfaceInset && info.globalScaleFactor == globalScaleFactor &&
-            info.transform == transform && info.displayOrientation == displayOrientation &&
-            info.displayWidth == displayWidth && info.displayHeight == displayHeight &&
-            info.touchableRegion.hasSameRects(touchableRegion) && info.visible == visible &&
-            info.trustedOverlay == trustedOverlay && info.focusable == focusable &&
-            info.touchOcclusionMode == touchOcclusionMode && info.hasWallpaper == hasWallpaper &&
-            info.paused == paused && info.ownerPid == ownerPid && info.ownerUid == ownerUid &&
+            info.transform == transform && info.touchableRegion.hasSameRects(touchableRegion) &&
+            info.visible == visible && info.trustedOverlay == trustedOverlay &&
+            info.focusable == focusable && info.touchOcclusionMode == touchOcclusionMode &&
+            info.hasWallpaper == hasWallpaper && info.paused == paused &&
+            info.ownerPid == ownerPid && info.ownerUid == ownerUid &&
             info.packageName == packageName && info.inputFeatures == inputFeatures &&
             info.displayId == displayId && info.portalToDisplayId == portalToDisplayId &&
             info.replaceTouchableRegionWithCrop == replaceTouchableRegionWithCrop &&
@@ -97,9 +96,6 @@
         parcel->writeFloat(transform.dtdy()) ?:
         parcel->writeFloat(transform.dsdy()) ?:
         parcel->writeFloat(transform.ty()) ?:
-        parcel->writeUint32(displayOrientation) ?:
-        parcel->writeInt32(displayWidth) ?:
-        parcel->writeInt32(displayHeight) ?:
         parcel->writeBool(visible) ?:
         parcel->writeBool(focusable) ?:
         parcel->writeBool(hasWallpaper) ?:
@@ -155,9 +151,6 @@
         parcel->readFloat(&dtdy) ?:
         parcel->readFloat(&dsdy) ?:
         parcel->readFloat(&ty) ?:
-        parcel->readUint32(&displayOrientation) ?:
-        parcel->readInt32(&displayWidth) ?:
-        parcel->readInt32(&displayHeight) ?:
         parcel->readBool(&visible) ?:
         parcel->readBool(&focusable) ?:
         parcel->readBool(&hasWallpaper) ?:
diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp
index c00a438..c32b9ab 100644
--- a/libs/gui/WindowInfosListenerReporter.cpp
+++ b/libs/gui/WindowInfosListenerReporter.cpp
@@ -19,6 +19,7 @@
 
 namespace android {
 
+using gui::DisplayInfo;
 using gui::IWindowInfosReportedListener;
 using gui::WindowInfo;
 using gui::WindowInfosListener;
@@ -65,7 +66,7 @@
 }
 
 binder::Status WindowInfosListenerReporter::onWindowInfosChanged(
-        const std::vector<WindowInfo>& windowInfos,
+        const std::vector<WindowInfo>& windowInfos, const std::vector<DisplayInfo>& displayInfos,
         const sp<IWindowInfosReportedListener>& windowInfosReportedListener) {
     std::unordered_set<sp<WindowInfosListener>, ISurfaceComposer::SpHash<WindowInfosListener>>
             windowInfosListeners;
@@ -78,7 +79,7 @@
     }
 
     for (auto listener : windowInfosListeners) {
-        listener->onWindowInfosChanged(windowInfos);
+        listener->onWindowInfosChanged(windowInfos, displayInfos);
     }
 
     if (windowInfosReportedListener) {
diff --git a/libs/gui/android/gui/DisplayInfo.aidl b/libs/gui/android/gui/DisplayInfo.aidl
new file mode 100644
index 0000000..30c0885
--- /dev/null
+++ b/libs/gui/android/gui/DisplayInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+parcelable DisplayInfo cpp_header "gui/DisplayInfo.h";
diff --git a/libs/gui/android/gui/DropInputMode.aidl b/libs/gui/android/gui/DropInputMode.aidl
new file mode 100644
index 0000000..2b31744
--- /dev/null
+++ b/libs/gui/android/gui/DropInputMode.aidl
@@ -0,0 +1,45 @@
+/**
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.gui;
+
+
+/**
+  * Input event drop modes: Input event drop options for windows and its children.
+  *
+  * @hide
+  */
+@Backing(type="int")
+enum DropInputMode {
+    /**
+      * Default mode, input events are sent to the target as usual.
+      */
+    NONE,
+
+    /**
+      * Window and its children will not receive any input even if it has a valid input channel.
+      * Touches and keys will be dropped. If a window is focused, it will remain focused but will
+      * not receive any keys. If the window has a touchable region and is the target of an input
+      * event, the event will be dropped and will not go to the window behind. ref: b/197296414
+      */
+    ALL,
+
+    /**
+      * Similar to DROP but input events are only dropped if the window is considered to be
+      * obscured. ref: b/197364677
+      */
+    OBSCURED
+}
diff --git a/libs/gui/android/gui/IWindowInfosListener.aidl b/libs/gui/android/gui/IWindowInfosListener.aidl
index d4553ca..a5b2762 100644
--- a/libs/gui/android/gui/IWindowInfosListener.aidl
+++ b/libs/gui/android/gui/IWindowInfosListener.aidl
@@ -16,11 +16,12 @@
 
 package android.gui;
 
+import android.gui.DisplayInfo;
 import android.gui.IWindowInfosReportedListener;
 import android.gui.WindowInfo;
 
 /** @hide */
 oneway interface IWindowInfosListener
 {
-    void onWindowInfosChanged(in WindowInfo[] windowInfos, in @nullable IWindowInfosReportedListener windowInfosReportedListener);
+    void onWindowInfosChanged(in WindowInfo[] windowInfos, in DisplayInfo[] displayInfos, in @nullable IWindowInfosReportedListener windowInfosReportedListener);
 }
diff --git a/libs/gui/include/gui/DisplayInfo.h b/libs/gui/include/gui/DisplayInfo.h
new file mode 100644
index 0000000..74f33a2
--- /dev/null
+++ b/libs/gui/include/gui/DisplayInfo.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <gui/constants.h>
+#include <ui/Transform.h>
+
+namespace android::gui {
+
+/*
+ * Describes information about a display that can have windows in it.
+ *
+ * This should only be used by InputFlinger to support raw coordinates in logical display space.
+ */
+struct DisplayInfo : public Parcelable {
+    int32_t displayId = ADISPLAY_ID_NONE;
+
+    // Logical display dimensions.
+    int32_t logicalWidth = 0;
+    int32_t logicalHeight = 0;
+
+    // The display transform. This takes display coordinates to logical display coordinates.
+    ui::Transform transform;
+
+    status_t writeToParcel(android::Parcel*) const override;
+
+    status_t readFromParcel(const android::Parcel*) override;
+};
+
+} // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index f14127c..b27102b 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -26,6 +26,7 @@
 #include <gui/ITransactionCompletedListener.h>
 #include <math/mat4.h>
 
+#include <android/gui/DropInputMode.h>
 #include <android/gui/FocusRequest.h>
 
 #include <gui/ISurfaceComposer.h>
@@ -118,6 +119,7 @@
         eAutoRefreshChanged = 0x1000'00000000,
         eStretchChanged = 0x2000'00000000,
         eTrustedOverlayChanged = 0x4000'00000000,
+        eDropInputModeChanged = 0x8000'00000000,
     };
 
     layer_state_t();
@@ -248,6 +250,9 @@
     // releaseCallbackId and release fence to all listeners so we store which listener the setBuffer
     // was called with.
     sp<IBinder> releaseBufferEndpoint;
+
+    // Force inputflinger to drop all input events for the layer and its children.
+    gui::DropInputMode dropInputMode;
 };
 
 struct ComposerState {
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index baa6878..ffd0244 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -562,6 +562,7 @@
         Transaction& setBufferCrop(const sp<SurfaceControl>& sc, const Rect& bufferCrop);
         Transaction& setDestinationFrame(const sp<SurfaceControl>& sc,
                                          const Rect& destinationFrame);
+        Transaction& setDropInputMode(const sp<SurfaceControl>& sc, gui::DropInputMode mode);
 
         status_t setDisplaySurface(const sp<IBinder>& token,
                 const sp<IGraphicBufferProducer>& bufferProducer);
diff --git a/libs/gui/include/gui/WindowInfo.h b/libs/gui/include/gui/WindowInfo.h
index 47f6c05..54a372c 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -132,9 +132,11 @@
     };
 
     enum class Feature : uint32_t {
-        DISABLE_TOUCH_PAD_GESTURES = 0x00000001,
-        NO_INPUT_CHANNEL = 0x00000002,
-        DISABLE_USER_ACTIVITY = 0x00000004,
+        DISABLE_TOUCH_PAD_GESTURES = 1u << 0,
+        NO_INPUT_CHANNEL = 1u << 1,
+        DISABLE_USER_ACTIVITY = 1u << 2,
+        DROP_INPUT = 1u << 3,
+        DROP_INPUT_IF_OBSCURED = 1u << 4,
     };
 
     /* These values are filled in by the WM and passed through SurfaceFlinger
@@ -179,13 +181,6 @@
     // Transform applied to individual windows.
     ui::Transform transform;
 
-    // Display orientation as ui::Transform::RotationFlags. 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;
-
     /*
      * This is filled in by the WM relative to the frame and then translated
      * to absolute coordinates by SurfaceFlinger once the frame is computed.
diff --git a/libs/gui/include/gui/WindowInfosListener.h b/libs/gui/include/gui/WindowInfosListener.h
index 8a70b9b..a18a498 100644
--- a/libs/gui/include/gui/WindowInfosListener.h
+++ b/libs/gui/include/gui/WindowInfosListener.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <gui/DisplayInfo.h>
 #include <gui/WindowInfo.h>
 #include <utils/RefBase.h>
 
@@ -23,6 +24,7 @@
 
 class WindowInfosListener : public virtual RefBase {
 public:
-    virtual void onWindowInfosChanged(const std::vector<WindowInfo>& /*windowInfos*/) = 0;
+    virtual void onWindowInfosChanged(const std::vector<WindowInfo>&,
+                                      const std::vector<DisplayInfo>&) = 0;
 };
 } // namespace android::gui
\ No newline at end of file
diff --git a/libs/gui/include/gui/WindowInfosListenerReporter.h b/libs/gui/include/gui/WindowInfosListenerReporter.h
index 7cb96e0..157a804 100644
--- a/libs/gui/include/gui/WindowInfosListenerReporter.h
+++ b/libs/gui/include/gui/WindowInfosListenerReporter.h
@@ -21,7 +21,6 @@
 #include <binder/IBinder.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/WindowInfosListener.h>
-#include <utils/Mutex.h>
 #include <unordered_set>
 
 namespace android {
@@ -30,7 +29,8 @@
 class WindowInfosListenerReporter : public gui::BnWindowInfosListener {
 public:
     static sp<WindowInfosListenerReporter> getInstance();
-    binder::Status onWindowInfosChanged(const std::vector<gui::WindowInfo>& windowInfos,
+    binder::Status onWindowInfosChanged(const std::vector<gui::WindowInfo>&,
+                                        const std::vector<gui::DisplayInfo>&,
                                         const sp<gui::IWindowInfosReportedListener>&) override;
 
     status_t addWindowInfosListener(const sp<gui::WindowInfosListener>& windowInfosListener,
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index 3d26c3d..6dd1073 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -27,6 +27,7 @@
         "BufferQueue_test.cpp",
         "CpuConsumer_test.cpp",
         "EndToEndNativeInputTest.cpp",
+        "DisplayInfo_test.cpp",
         "DisplayedContentSampling_test.cpp",
         "FillBuffer.cpp",
         "GLTest.cpp",
@@ -62,7 +63,7 @@
         "libinput",
         "libui",
         "libutils",
-        "libnativewindow"
+        "libnativewindow",
     ],
 
     header_libs: ["libsurfaceflinger_headers"],
@@ -117,7 +118,7 @@
         "libgui",
         "libui",
         "libutils",
-        "libbufferhubqueue",  // TODO(b/70046255): Remove these once BufferHub is integrated into libgui.
+        "libbufferhubqueue", // TODO(b/70046255): Remove these once BufferHub is integrated into libgui.
         "libpdx_default_transport",
     ],
 
@@ -146,5 +147,5 @@
         "liblog",
         "libui",
         "libutils",
-    ]
+    ],
 }
diff --git a/libs/gui/tests/DisplayInfo_test.cpp b/libs/gui/tests/DisplayInfo_test.cpp
new file mode 100644
index 0000000..df3329c
--- /dev/null
+++ b/libs/gui/tests/DisplayInfo_test.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <binder/Parcel.h>
+
+#include <gui/DisplayInfo.h>
+
+namespace android {
+
+using gui::DisplayInfo;
+
+namespace test {
+
+TEST(DisplayInfo, Parcelling) {
+    DisplayInfo info;
+    info.displayId = 42;
+    info.logicalWidth = 99;
+    info.logicalHeight = 78;
+    info.transform.set({0.4, -1, 100, 0.5, 0, 40, 0, 0, 1});
+
+    Parcel p;
+    info.writeToParcel(&p);
+    p.setDataPosition(0);
+
+    DisplayInfo info2;
+    info2.readFromParcel(&p);
+    ASSERT_EQ(info.displayId, info2.displayId);
+    ASSERT_EQ(info.logicalWidth, info2.logicalWidth);
+    ASSERT_EQ(info.logicalHeight, info2.logicalHeight);
+    ASSERT_EQ(info.transform, info2.transform);
+}
+
+} // namespace test
+} // namespace android
diff --git a/libs/gui/tests/WindowInfo_test.cpp b/libs/gui/tests/WindowInfo_test.cpp
index a4f436c..dcdf76f 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -60,9 +60,6 @@
     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;
     i.focusable = false;
     i.hasWallpaper = false;
@@ -100,8 +97,6 @@
     ASSERT_EQ(i.globalScaleFactor, i2.globalScaleFactor);
     ASSERT_EQ(i.alpha, i2.alpha);
     ASSERT_EQ(i.transform, i2.transform);
-    ASSERT_EQ(i.displayWidth, i2.displayWidth);
-    ASSERT_EQ(i.displayHeight, i2.displayHeight);
     ASSERT_EQ(i.visible, i2.visible);
     ASSERT_EQ(i.focusable, i2.focusable);
     ASSERT_EQ(i.hasWallpaper, i2.hasWallpaper);
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 1e8ff94..30e5d5b 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -76,36 +76,13 @@
     return result;
 }
 
-// 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(uint32_t orientation, float x, float y, int32_t displayWidth = 0,
-                 int32_t displayHeight = 0) {
-    if (orientation == ui::Transform::ROT_0) {
-        return {x, y};
-    }
-
-    vec2 xy(x, y);
-    if (orientation == ui::Transform::ROT_90) {
-        xy.x = displayHeight - y;
-        xy.y = x;
-    } else if (orientation == ui::Transform::ROT_180) {
-        xy.x = displayWidth - x;
-        xy.y = displayHeight - y;
-    } else if (orientation == ui::Transform::ROT_270) {
-        xy.x = y;
-        xy.y = displayWidth - x;
-    }
-    return xy;
-}
-
-vec2 applyTransformWithoutTranslation(const ui::Transform& transform, float x, float y) {
+vec2 transformWithoutTranslation(const ui::Transform& transform, float x, float y) {
     const vec2 transformedXy = transform.transform(x, y);
     const vec2 transformedOrigin = transform.transform(0, 0);
     return transformedXy - transformedOrigin;
 }
 
-bool shouldDisregardWindowTranslation(uint32_t source) {
+bool shouldDisregardTranslation(uint32_t source) {
     // Pointer events are the only type of events that refer to absolute coordinates on the display,
     // so we should apply the entire window transform. For other types of events, we should make
     // sure to not apply the window translation/offset.
@@ -431,8 +408,7 @@
                              int32_t buttonState, MotionClassification classification,
                              const ui::Transform& transform, float xPrecision, float yPrecision,
                              float rawXCursorPosition, float rawYCursorPosition,
-                             uint32_t displayOrientation, int32_t displayWidth,
-                             int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime,
+                             const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
                              size_t pointerCount, const PointerProperties* pointerProperties,
                              const PointerCoords* pointerCoords) {
     InputEvent::initialize(id, deviceId, source, displayId, hmac);
@@ -448,9 +424,7 @@
     mYPrecision = yPrecision;
     mRawXCursorPosition = rawXCursorPosition;
     mRawYCursorPosition = rawYCursorPosition;
-    mDisplayOrientation = displayOrientation;
-    mDisplayWidth = displayWidth;
-    mDisplayHeight = displayHeight;
+    mRawTransform = rawTransform;
     mDownTime = downTime;
     mPointerProperties.clear();
     mPointerProperties.appendArray(pointerProperties, pointerCount);
@@ -474,9 +448,7 @@
     mYPrecision = other->mYPrecision;
     mRawXCursorPosition = other->mRawXCursorPosition;
     mRawYCursorPosition = other->mRawYCursorPosition;
-    mDisplayOrientation = other->mDisplayOrientation;
-    mDisplayWidth = other->mDisplayWidth;
-    mDisplayHeight = other->mDisplayHeight;
+    mRawTransform = other->mRawTransform;
     mDownTime = other->mDownTime;
     mPointerProperties = other->mPointerProperties;
 
@@ -542,20 +514,19 @@
     if (!isPerWindowInputRotationEnabled()) return coords->getAxisValue(axis);
 
     if (axis == AMOTION_EVENT_AXIS_X || axis == AMOTION_EVENT_AXIS_Y) {
-        // 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(mDisplayOrientation, coords->getX(), coords->getY())
-                : rotatePoint(mDisplayOrientation, coords->getX(), coords->getY(), mDisplayWidth,
-                              mDisplayHeight);
+        // For compatibility, convert raw coordinates into logical display space.
+        const vec2 xy = shouldDisregardTranslation(mSource)
+                ? transformWithoutTranslation(mRawTransform, coords->getX(), coords->getY())
+                : mRawTransform.transform(coords->getX(), coords->getY());
         static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
         return xy[axis];
     }
 
     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
+        // For compatibility, since we report raw coordinates in logical display space, we
         // need to convert the relative axes into the same orientation for consistency.
-        const vec2 relativeXy = rotatePoint(mDisplayOrientation,
+        const vec2 relativeXy =
+                transformWithoutTranslation(mRawTransform,
                                             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;
@@ -569,8 +540,8 @@
     const PointerCoords* coords = getHistoricalRawPointerCoords(pointerIndex, historicalIndex);
 
     if (axis == AMOTION_EVENT_AXIS_X || axis == AMOTION_EVENT_AXIS_Y) {
-        const vec2 xy = shouldDisregardWindowTranslation(mSource)
-                ? applyTransformWithoutTranslation(mTransform, coords->getX(), coords->getY())
+        const vec2 xy = shouldDisregardTranslation(mSource)
+                ? transformWithoutTranslation(mTransform, coords->getX(), coords->getY())
                 : mTransform.transform(coords->getXYValue());
         static_assert(AMOTION_EVENT_AXIS_X == 0 && AMOTION_EVENT_AXIS_Y == 1);
         return xy[axis];
@@ -578,11 +549,9 @@
 
     if (axis == AMOTION_EVENT_AXIS_RELATIVE_X || axis == AMOTION_EVENT_AXIS_RELATIVE_Y) {
         const vec2 relativeXy =
-                applyTransformWithoutTranslation(mTransform,
-                                                 coords->getAxisValue(
-                                                         AMOTION_EVENT_AXIS_RELATIVE_X),
-                                                 coords->getAxisValue(
-                                                         AMOTION_EVENT_AXIS_RELATIVE_Y));
+                transformWithoutTranslation(mTransform,
+                                            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;
     }
 
@@ -607,6 +576,8 @@
 
 void MotionEvent::scale(float globalScaleFactor) {
     mTransform.set(mTransform.tx() * globalScaleFactor, mTransform.ty() * globalScaleFactor);
+    mRawTransform.set(mRawTransform.tx() * globalScaleFactor,
+                      mRawTransform.ty() * globalScaleFactor);
     mXPrecision *= globalScaleFactor;
     mYPrecision *= globalScaleFactor;
 
@@ -708,9 +679,11 @@
     mYPrecision = parcel->readFloat();
     mRawXCursorPosition = parcel->readFloat();
     mRawYCursorPosition = parcel->readFloat();
-    mDisplayOrientation = parcel->readUint32();
-    mDisplayWidth = parcel->readInt32();
-    mDisplayHeight = parcel->readInt32();
+
+    result = android::readFromParcel(mRawTransform, *parcel);
+    if (result != OK) {
+        return result;
+    }
     mDownTime = parcel->readInt64();
 
     mPointerProperties.clear();
@@ -770,9 +743,11 @@
     parcel->writeFloat(mYPrecision);
     parcel->writeFloat(mRawXCursorPosition);
     parcel->writeFloat(mRawYCursorPosition);
-    parcel->writeUint32(mDisplayOrientation);
-    parcel->writeInt32(mDisplayWidth);
-    parcel->writeInt32(mDisplayHeight);
+
+    result = android::writeToParcel(mRawTransform, *parcel);
+    if (result != OK) {
+        return result;
+    }
     parcel->writeInt64(mDownTime);
 
     for (size_t i = 0; i < pointerCount; i++) {
@@ -834,9 +809,9 @@
         case AMOTION_EVENT_ACTION_OUTSIDE:
             return "OUTSIDE";
         case AMOTION_EVENT_ACTION_POINTER_DOWN:
-            return "POINTER_DOWN";
+            return StringPrintf("POINTER_DOWN(%" PRId32 ")", MotionEvent::getActionIndex(action));
         case AMOTION_EVENT_ACTION_POINTER_UP:
-            return "POINTER_UP";
+            return StringPrintf("POINTER_UP(%" PRId32 ")", MotionEvent::getActionIndex(action));
         case AMOTION_EVENT_ACTION_HOVER_MOVE:
             return "HOVER_MOVE";
         case AMOTION_EVENT_ACTION_SCROLL:
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 91ab008..02a5a08 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -203,6 +203,8 @@
         case InputMessage::Type::MOTION: {
             // int32_t eventId
             msg->body.motion.eventId = body.motion.eventId;
+            // uint32_t pointerCount
+            msg->body.motion.pointerCount = body.motion.pointerCount;
             // nsecs_t eventTime
             msg->body.motion.eventTime = body.motion.eventTime;
             // int32_t deviceId
@@ -245,14 +247,14 @@
             msg->body.motion.xCursorPosition = body.motion.xCursorPosition;
             // float yCursorPosition
             msg->body.motion.yCursorPosition = body.motion.yCursorPosition;
-            // uint32_t displayOrientation
-            msg->body.motion.displayOrientation = body.motion.displayOrientation;
-            // int32_t displayWidth
-            msg->body.motion.displayWidth = body.motion.displayWidth;
-            // int32_t displayHeight
-            msg->body.motion.displayHeight = body.motion.displayHeight;
-            // uint32_t pointerCount
-            msg->body.motion.pointerCount = body.motion.pointerCount;
+
+            msg->body.motion.dsdxRaw = body.motion.dsdxRaw;
+            msg->body.motion.dtdxRaw = body.motion.dtdxRaw;
+            msg->body.motion.dtdyRaw = body.motion.dtdyRaw;
+            msg->body.motion.dsdyRaw = body.motion.dsdyRaw;
+            msg->body.motion.txRaw = body.motion.txRaw;
+            msg->body.motion.tyRaw = body.motion.tyRaw;
+
             //struct Pointer pointers[MAX_POINTERS]
             for (size_t i = 0; i < body.motion.pointerCount; i++) {
                 // PointerProperties properties
@@ -542,8 +544,8 @@
         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, uint32_t displayOrientation,
-        int32_t displayWidth, int32_t displayHeight, nsecs_t downTime, nsecs_t eventTime,
+        float yPrecision, float xCursorPosition, float yCursorPosition,
+        const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
         uint32_t pointerCount, const PointerProperties* pointerProperties,
         const PointerCoords* pointerCoords) {
     if (ATRACE_ENABLED()) {
@@ -603,9 +605,12 @@
     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.dsdxRaw = rawTransform.dsdx();
+    msg.body.motion.dtdxRaw = rawTransform.dtdx();
+    msg.body.motion.dtdyRaw = rawTransform.dtdy();
+    msg.body.motion.dsdyRaw = rawTransform.dsdy();
+    msg.body.motion.txRaw = rawTransform.tx();
+    msg.body.motion.tyRaw = rawTransform.ty();
     msg.body.motion.downTime = downTime;
     msg.body.motion.eventTime = eventTime;
     msg.body.motion.pointerCount = pointerCount;
@@ -1391,6 +1396,10 @@
     ui::Transform transform;
     transform.set({msg->body.motion.dsdx, msg->body.motion.dtdx, msg->body.motion.tx,
                    msg->body.motion.dtdy, msg->body.motion.dsdy, msg->body.motion.ty, 0, 0, 1});
+    ui::Transform displayTransform;
+    displayTransform.set({msg->body.motion.dsdxRaw, msg->body.motion.dtdxRaw,
+                          msg->body.motion.txRaw, msg->body.motion.dtdyRaw,
+                          msg->body.motion.dsdyRaw, msg->body.motion.tyRaw, 0, 0, 1});
     event->initialize(msg->body.motion.eventId, msg->body.motion.deviceId, msg->body.motion.source,
                       msg->body.motion.displayId, msg->body.motion.hmac, msg->body.motion.action,
                       msg->body.motion.actionButton, msg->body.motion.flags,
@@ -1398,9 +1407,8 @@
                       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.displayOrientation, msg->body.motion.displayWidth,
-                      msg->body.motion.displayHeight, msg->body.motion.downTime,
-                      msg->body.motion.eventTime, pointerCount, pointerProperties, pointerCoords);
+                      displayTransform, msg->body.motion.downTime, msg->body.motion.eventTime,
+                      pointerCount, pointerProperties, pointerCoords);
 }
 
 void InputConsumer::initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg) {
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index b1ef753..7e7dfd5 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -226,11 +226,16 @@
     static constexpr float Y_SCALE = 3.0;
     static constexpr float X_OFFSET = 1;
     static constexpr float Y_OFFSET = 1.1;
+    static constexpr float RAW_X_SCALE = 4.0;
+    static constexpr float RAW_Y_SCALE = -5.0;
+    static constexpr float RAW_X_OFFSET = 12;
+    static constexpr float RAW_Y_OFFSET = -41.1;
 
     static const std::optional<bool> INITIAL_PER_WINDOW_INPUT_ROTATION_FLAG_VALUE;
 
     int32_t mId;
     ui::Transform mTransform;
+    ui::Transform mRawTransform;
 
     void SetUp() override;
     void TearDown() override;
@@ -259,6 +264,7 @@
 void MotionEventTest::initializeEventWithHistory(MotionEvent* event) {
     mId = InputEvent::nextId();
     mTransform.set({X_SCALE, 0, X_OFFSET, 0, Y_SCALE, Y_OFFSET, 0, 0, 1});
+    mRawTransform.set({RAW_X_SCALE, 0, RAW_X_OFFSET, 0, RAW_Y_SCALE, RAW_Y_OFFSET, 0, 0, 1});
 
     PointerProperties pointerProperties[2];
     pointerProperties[0].clear();
@@ -294,9 +300,8 @@
                       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,
-                      ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                      ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2, pointerProperties,
-                      pointerCoords);
+                      mRawTransform, 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);
@@ -373,39 +378,37 @@
     ASSERT_EQ(ARBITRARY_EVENT_TIME + 1, event->getHistoricalEventTime(1));
     ASSERT_EQ(ARBITRARY_EVENT_TIME + 2, event->getEventTime());
 
-    ASSERT_EQ(11, event->getHistoricalRawPointerCoords(0, 0)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(21, event->getHistoricalRawPointerCoords(1, 0)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(111, event->getHistoricalRawPointerCoords(0, 1)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(121, event->getHistoricalRawPointerCoords(1, 1)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(211, event->getRawPointerCoords(0)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
-    ASSERT_EQ(221, event->getRawPointerCoords(1)->
-            getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(11, event->getHistoricalRawPointerCoords(0, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(21, event->getHistoricalRawPointerCoords(1, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(111, event->getHistoricalRawPointerCoords(0, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(121, event->getHistoricalRawPointerCoords(1, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(211, event->getRawPointerCoords(0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
+    ASSERT_EQ(221, event->getRawPointerCoords(1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
 
-    ASSERT_EQ(11, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0));
-    ASSERT_EQ(21, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0));
-    ASSERT_EQ(111, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1));
-    ASSERT_EQ(121, event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1));
-    ASSERT_EQ(211, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0));
-    ASSERT_EQ(221, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1));
+    ASSERT_EQ(RAW_Y_OFFSET + 11 * RAW_Y_SCALE,
+              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0));
+    ASSERT_EQ(RAW_Y_OFFSET + 21 * RAW_Y_SCALE,
+              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0));
+    ASSERT_EQ(RAW_Y_OFFSET + 111 * RAW_Y_SCALE,
+              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1));
+    ASSERT_EQ(RAW_Y_OFFSET + 121 * RAW_Y_SCALE,
+              event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1));
+    ASSERT_EQ(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0));
+    ASSERT_EQ(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1));
 
-    ASSERT_EQ(10, event->getHistoricalRawX(0, 0));
-    ASSERT_EQ(20, event->getHistoricalRawX(1, 0));
-    ASSERT_EQ(110, event->getHistoricalRawX(0, 1));
-    ASSERT_EQ(120, event->getHistoricalRawX(1, 1));
-    ASSERT_EQ(210, event->getRawX(0));
-    ASSERT_EQ(220, event->getRawX(1));
+    ASSERT_EQ(RAW_X_OFFSET + 10 * RAW_X_SCALE, event->getHistoricalRawX(0, 0));
+    ASSERT_EQ(RAW_X_OFFSET + 20 * RAW_X_SCALE, event->getHistoricalRawX(1, 0));
+    ASSERT_EQ(RAW_X_OFFSET + 110 * RAW_X_SCALE, event->getHistoricalRawX(0, 1));
+    ASSERT_EQ(RAW_X_OFFSET + 120 * RAW_X_SCALE, event->getHistoricalRawX(1, 1));
+    ASSERT_EQ(RAW_X_OFFSET + 210 * RAW_X_SCALE, event->getRawX(0));
+    ASSERT_EQ(RAW_X_OFFSET + 220 * RAW_X_SCALE, event->getRawX(1));
 
-    ASSERT_EQ(11, event->getHistoricalRawY(0, 0));
-    ASSERT_EQ(21, event->getHistoricalRawY(1, 0));
-    ASSERT_EQ(111, event->getHistoricalRawY(0, 1));
-    ASSERT_EQ(121, event->getHistoricalRawY(1, 1));
-    ASSERT_EQ(211, event->getRawY(0));
-    ASSERT_EQ(221, event->getRawY(1));
+    ASSERT_EQ(RAW_Y_OFFSET + 11 * RAW_Y_SCALE, event->getHistoricalRawY(0, 0));
+    ASSERT_EQ(RAW_Y_OFFSET + 21 * RAW_Y_SCALE, event->getHistoricalRawY(1, 0));
+    ASSERT_EQ(RAW_Y_OFFSET + 111 * RAW_Y_SCALE, event->getHistoricalRawY(0, 1));
+    ASSERT_EQ(RAW_Y_OFFSET + 121 * RAW_Y_SCALE, event->getHistoricalRawY(1, 1));
+    ASSERT_EQ(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawY(0));
+    ASSERT_EQ(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawY(1));
 
     ASSERT_EQ(X_OFFSET + 10 * X_SCALE, event->getHistoricalX(0, 0));
     ASSERT_EQ(X_OFFSET + 20 * X_SCALE, event->getHistoricalX(1, 0));
@@ -543,8 +546,8 @@
     ASSERT_EQ(X_OFFSET * 2, event.getXOffset());
     ASSERT_EQ(Y_OFFSET * 2, event.getYOffset());
 
-    ASSERT_EQ(210 * 2, event.getRawX(0));
-    ASSERT_EQ(211 * 2, event.getRawY(0));
+    ASSERT_EQ((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0));
+    ASSERT_EQ((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0));
     ASSERT_EQ((X_OFFSET + 210 * X_SCALE) * 2, event.getX(0));
     ASSERT_EQ((Y_OFFSET + 211 * Y_SCALE) * 2, event.getY(0));
     ASSERT_EQ(212, event.getPressure(0));
@@ -592,10 +595,10 @@
     // The geometrical representation is irrelevant to the test, it's just easy to generate
     // and check rotation.  We set the orientation to the same angle.
     // Coordinate system: down is increasing Y, right is increasing X.
-    const float PI_180 = float(M_PI / 180);
-    const float RADIUS = 10;
-    const float ARC = 36;
-    const float ROTATION = ARC * 2;
+    static constexpr float PI_180 = float(M_PI / 180);
+    static constexpr float RADIUS = 10;
+    static constexpr float ARC = 36;
+    static constexpr float ROTATION = ARC * 2;
 
     const size_t pointerCount = 11;
     PointerProperties pointerProperties[pointerCount];
@@ -616,9 +619,8 @@
                      AMOTION_EVENT_EDGE_FLAG_NONE, AMETA_NONE, 0 /*buttonState*/,
                      MotionClassification::NONE, identityTransform, 0 /*xPrecision*/,
                      0 /*yPrecision*/, 3 + RADIUS /*xCursorPosition*/, 2 /*yCursorPosition*/,
-                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                     0 /*downTime*/, 0 /*eventTime*/, pointerCount, pointerProperties,
-                     pointerCoords);
+                     identityTransform, 0 /*downTime*/, 0 /*eventTime*/, pointerCount,
+                     pointerProperties, pointerCoords);
     float originalRawX = 0 + 3;
     float originalRawY = -RADIUS + 2;
 
@@ -661,7 +663,7 @@
 
 MotionEvent createTouchDownEvent(float x, float y, float dx, float dy,
                                  const ui::Transform& transform,
-                                 uint32_t displayOrientation = ui::Transform::ROT_0) {
+                                 const ui::Transform& rawTransform) {
     std::vector<PointerProperties> pointerProperties;
     pointerProperties.push_back(PointerProperties{/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER});
     std::vector<PointerCoords> pointerCoords;
@@ -677,19 +679,18 @@
                      /* 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, displayOrientation,
-                     /* displayWidth */ 400,
-                     /* displayHeight */ 800, eventTime, eventTime, pointerCoords.size(),
-                     pointerProperties.data(), pointerCoords.data());
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, rawTransform, eventTime, eventTime,
+                     pointerCoords.size(), pointerProperties.data(), pointerCoords.data());
     return event;
 }
 
 TEST_F(MotionEventTest, ApplyTransform) {
     // Create a rotate-90 transform with an offset (like a window which isn't fullscreen).
     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, ui::Transform::ROT_90);
+    ui::Transform transform(ui::Transform::ROT_90, 800, 400);
+    transform.set(transform.tx() + 20, transform.ty() + 40);
+    ui::Transform rawTransform(ui::Transform::ROT_90, 800, 400);
+    MotionEvent event = createTouchDownEvent(60, 100, 42, 96, transform, rawTransform);
     ASSERT_EQ(700, event.getRawX(0));
     ASSERT_EQ(60, event.getRawY(0));
     ASSERT_NE(event.getRawX(0), event.getX(0));
@@ -698,10 +699,10 @@
     ASSERT_EQ(-96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
     ASSERT_EQ(42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
 
-    MotionEvent changedEvent = createTouchDownEvent(60, 100, 42, 96, identity);
-    const std::array<float, 9> rowMajor{xform[0][0], xform[1][0], xform[2][0],
-                                        xform[0][1], xform[1][1], xform[2][1],
-                                        xform[0][2], xform[1][2], xform[2][2]};
+    MotionEvent changedEvent = createTouchDownEvent(60, 100, 42, 96, identity, identity);
+    const std::array<float, 9> rowMajor{transform[0][0], transform[1][0], transform[2][0],
+                                        transform[0][1], transform[1][1], transform[2][1],
+                                        transform[0][2], transform[1][2], transform[2][2]};
     changedEvent.applyTransform(rowMajor);
 
     // transformContent effectively rotates the raw coordinates, so those should now include
@@ -727,9 +728,9 @@
                                                  AINPUT_SOURCE_JOYSTICK};
     for (uint32_t source : NON_POINTER_SOURCES) {
         // 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, ui::Transform::ROT_90);
+        ui::Transform transform(ui::Transform::ROT_90, 800, 400);
+        transform.set(transform.tx() + 20, transform.ty() + 40);
+        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, transform, transform);
         event.setSource(source);
 
         // Since this event comes from a non-pointer source, it should include rotation but not
@@ -741,72 +742,34 @@
     }
 }
 
-TEST_F(MotionEventTest, RawCompatTransform) {
-    {
-        // Make sure raw is raw regardless of transform translation.
-        ui::Transform xform;
-        xform.set(20, 40);
-        MotionEvent event = createTouchDownEvent(60, 100, 42, 96, xform);
-        ASSERT_EQ(60, event.getRawX(0));
-        ASSERT_EQ(100, event.getRawY(0));
-        ASSERT_NE(event.getRawX(0), event.getX(0));
-        ASSERT_NE(event.getRawY(0), event.getY(0));
-        // Relative values should not be modified.
-        ASSERT_EQ(42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
-        ASSERT_EQ(96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
-    }
+TEST_F(MotionEventTest, AxesAreCorrectlyTransformed) {
+    const ui::Transform identity;
+    ui::Transform transform;
+    transform.set({1.1, -2.2, 3.3, -4.4, 5.5, -6.6, 0, 0, 1});
+    ui::Transform rawTransform;
+    rawTransform.set({-6.6, 5.5, -4.4, 3.3, -2.2, 1.1, 0, 0, 1});
+    auto transformWithoutTranslation = [](const ui::Transform& t, float x, float y) {
+        auto newPoint = t.transform(x, y);
+        auto newOrigin = t.transform(0, 0);
+        return newPoint - newOrigin;
+    };
 
-    // Next check that getRaw contains rotation (for compatibility) but otherwise is still
-    // "Screen-space". The following tests check all 3 rotations.
-    {
-        // 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, ui::Transform::ROT_90);
-        ASSERT_EQ(700, event.getRawX(0));
-        ASSERT_EQ(60, event.getRawY(0));
-        ASSERT_NE(event.getRawX(0), event.getX(0));
-        ASSERT_NE(event.getRawY(0), event.getY(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));
-    }
+    const MotionEvent event = createTouchDownEvent(60, 100, 42, 96, transform, rawTransform);
 
-    {
-        // 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, ui::Transform::ROT_180);
-        ASSERT_EQ(340, event.getRawX(0));
-        ASSERT_EQ(700, event.getRawY(0));
-        // Relative values should be rotated but not translated.
-        ASSERT_EQ(-42, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
-        ASSERT_EQ(-96, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
-    }
+    // The x and y axes should have the window transform applied.
+    const auto newPoint = transform.transform(60, 100);
+    ASSERT_EQ(newPoint.x, event.getX(0));
+    ASSERT_EQ(newPoint.y, event.getY(0));
 
-    {
-        // 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, 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));
-    }
+    // The raw values should have the display transform applied.
+    const auto raw = rawTransform.transform(60, 100);
+    ASSERT_EQ(raw.x, event.getRawX(0));
+    ASSERT_EQ(raw.y, event.getRawY(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));
-    }
+    // Relative values should have the window transform applied without any translation.
+    const auto rel = transformWithoutTranslation(transform, 42, 96);
+    ASSERT_EQ(rel.x, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
+    ASSERT_EQ(rel.y, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
 }
 
 TEST_F(MotionEventTest, Initialize_SetsClassification) {
@@ -832,8 +795,7 @@
                          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, ui::Transform::ROT_0,
-                         INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 0 /*downTime*/,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 0 /*downTime*/,
                          0 /*eventTime*/, pointerCount, pointerProperties, pointerCoords);
         ASSERT_EQ(classification, event.getClassification());
     }
@@ -854,9 +816,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*/, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 0 /*downTime*/, 0 /*eventTime*/,
-                     pointerCount, pointerProperties, pointerCoords);
+                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, identityTransform,
+                     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 8db5bf1..d09f2ac 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -160,13 +160,14 @@
     constexpr float yScale = 3;
     constexpr float xOffset = -10;
     constexpr float yOffset = -20;
+    constexpr float rawXScale = 4;
+    constexpr float rawYScale = -5;
+    constexpr float rawXOffset = -11;
+    constexpr float rawYOffset = 42;
     constexpr float xPrecision = 0.25;
     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;
     constexpr size_t pointerCount = 3;
     constexpr nsecs_t eventTime = 4;
@@ -192,12 +193,14 @@
 
     ui::Transform transform;
     transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
+    ui::Transform rawTransform;
+    rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
     status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
                                             actionButton, flags, edgeFlags, metaState, buttonState,
                                             classification, transform, xPrecision, yPrecision,
-                                            xCursorPosition, yCursorPosition, displayOrientation,
-                                            displayWidth, displayHeight, downTime, eventTime,
-                                            pointerCount, pointerProperties, pointerCoords);
+                                            xCursorPosition, yCursorPosition, rawTransform,
+                                            downTime, eventTime, pointerCount, pointerProperties,
+                                            pointerCoords);
     ASSERT_EQ(OK, status)
             << "publisher publishMotionEvent should return OK";
 
@@ -234,9 +237,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(rawTransform, motionEvent->getRawTransform());
     EXPECT_EQ(downTime, motionEvent->getDownTime());
     EXPECT_EQ(eventTime, motionEvent->getEventTime());
     EXPECT_EQ(pointerCount, motionEvent->getPointerCount());
@@ -247,28 +248,18 @@
         EXPECT_EQ(pointerProperties[i].id, motionEvent->getPointerId(i));
         EXPECT_EQ(pointerProperties[i].toolType, motionEvent->getToolType(i));
 
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
-                motionEvent->getRawX(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
-                motionEvent->getRawY(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X) * xScale + xOffset,
-                  motionEvent->getX(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y) * yScale + yOffset,
-                  motionEvent->getY(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
-                motionEvent->getPressure(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
-                motionEvent->getSize(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
-                motionEvent->getTouchMajor(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
-                motionEvent->getTouchMinor(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
-                motionEvent->getToolMajor(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
-                motionEvent->getToolMinor(i));
-        EXPECT_EQ(pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION),
-                motionEvent->getOrientation(i));
+        const auto& pc = pointerCoords[i];
+        EXPECT_EQ(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i));
+        EXPECT_EQ(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i));
+        EXPECT_EQ(pc.getX() * xScale + xOffset, motionEvent->getX(i));
+        EXPECT_EQ(pc.getY() * yScale + yOffset, motionEvent->getY(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent->getPressure(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent->getSize(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent->getTouchMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR), motionEvent->getTouchMinor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR), motionEvent->getToolMajor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR), motionEvent->getToolMinor(i));
+        EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION), motionEvent->getOrientation(i));
     }
 
     status = mConsumer->sendFinishedSignal(seq, false);
@@ -505,12 +496,12 @@
     }
 
     ui::Transform identityTransform;
-    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,
-                                            ui::Transform::ROT_0, 0, 0, 0, 0, pointerCount,
-                                            pointerProperties, pointerCoords);
+    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, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
 }
@@ -522,12 +513,12 @@
     PointerCoords pointerCoords[pointerCount];
 
     ui::Transform identityTransform;
-    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,
-                                            ui::Transform::ROT_0, 0, 0, 0, 0, pointerCount,
-                                            pointerProperties, pointerCoords);
+    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, identityTransform,
+                                           0, 0, pointerCount, pointerProperties, pointerCoords);
     ASSERT_EQ(BAD_VALUE, status)
             << "publisher publishMotionEvent should return BAD_VALUE";
 }
@@ -544,12 +535,12 @@
     }
 
     ui::Transform identityTransform;
-    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,
-                                            ui::Transform::ROT_0, 0, 0, 0, 0, pointerCount,
-                                            pointerProperties, pointerCoords);
+    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, identityTransform,
+                                           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 18289a5..2f88704 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -49,7 +49,7 @@
   CHECK_OFFSET(InputMessage::Body::Key, downTime, 88);
 
   CHECK_OFFSET(InputMessage::Body::Motion, eventId, 0);
-  CHECK_OFFSET(InputMessage::Body::Motion, empty1, 4);
+  CHECK_OFFSET(InputMessage::Body::Motion, pointerCount, 4);
   CHECK_OFFSET(InputMessage::Body::Motion, eventTime, 8);
   CHECK_OFFSET(InputMessage::Body::Motion, deviceId, 16);
   CHECK_OFFSET(InputMessage::Body::Motion, source, 20);
@@ -74,11 +74,13 @@
   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, 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::Motion, dsdxRaw, 136);
+  CHECK_OFFSET(InputMessage::Body::Motion, dtdxRaw, 140);
+  CHECK_OFFSET(InputMessage::Body::Motion, dtdyRaw, 144);
+  CHECK_OFFSET(InputMessage::Body::Motion, dsdyRaw, 148);
+  CHECK_OFFSET(InputMessage::Body::Motion, txRaw, 152);
+  CHECK_OFFSET(InputMessage::Body::Motion, tyRaw, 156);
+  CHECK_OFFSET(InputMessage::Body::Motion, pointers, 160);
 
   CHECK_OFFSET(InputMessage::Body::Focus, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Focus, hasFocus, 4);
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 13e2b02..3039362 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -184,8 +184,7 @@
                          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, ui::Transform::ROT_0,
-                         INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 0 /*downTime*/,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, 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 b29c9a4..f2b59ea 100644
--- a/libs/input/tests/VerifiedInputEvent_test.cpp
+++ b/libs/input/tests/VerifiedInputEvent_test.cpp
@@ -43,12 +43,12 @@
 
     ui::Transform transform;
     transform.set({2, 0, 4, 0, 3, 5, 0, 0, 1});
+    ui::Transform identity;
     event.initialize(InputEvent::nextId(), 0 /*deviceId*/, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_DEFAULT,
                      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*/, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, 100 /*downTime*/,
+                     280 /*xCursorPosition*/, 540 /*yCursorPosition*/, identity, 100 /*downTime*/,
                      200 /*eventTime*/, pointerCount, pointerProperties, pointerCoords);
     return event;
 }
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index 4c07c22..f053568 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -30,7 +30,6 @@
 #include <input/InputTransport.h>
 
 #include <android/os/BnInputFlinger.h>
-#include <android/os/IInputFlinger.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
 #include <utils/Timers.h>
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 6ce0313..68d25f9 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -236,8 +236,8 @@
                      /* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
                      identityTransform, /* xPrecision */ 0,
                      /* yPrecision */ 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
-                     AMOTION_EVENT_INVALID_CURSOR_POSITION, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, currentTime, currentTime,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, currentTime,
+                     currentTime,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     return event;
 }
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 571c126..bcb0071 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -309,15 +309,14 @@
 volatile int32_t DispatchEntry::sNextSeqAtomic;
 
 DispatchEntry::DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                             ui::Transform transform, float globalScaleFactor,
-                             uint32_t displayOrientation, int2 displaySize)
+                             const ui::Transform& transform, const ui::Transform& rawTransform,
+                             float globalScaleFactor)
       : seq(nextSeq()),
         eventEntry(std::move(eventEntry)),
         targetFlags(targetFlags),
         transform(transform),
+        rawTransform(rawTransform),
         globalScaleFactor(globalScaleFactor),
-        displayOrientation(displayOrientation),
-        displaySize(displaySize),
         deliveryTime(0),
         resolvedAction(0),
         resolvedFlags(0) {}
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 5365a78..7a121ce 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -226,9 +226,8 @@
     std::shared_ptr<EventEntry> eventEntry; // the event to dispatch
     int32_t targetFlags;
     ui::Transform transform;
+    ui::Transform rawTransform;
     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.
     nsecs_t deliveryTime; // time when the event was actually delivered
@@ -241,8 +240,8 @@
     int32_t resolvedFlags;
 
     DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
-                  ui::Transform transform, float globalScaleFactor, uint32_t displayOrientation,
-                  int2 displaySize);
+                  const ui::Transform& transform, const ui::Transform& rawTransform,
+                  float globalScaleFactor);
 
     inline bool hasForegroundTarget() const { return targetFlags & InputTarget::FLAG_FOREGROUND; }
 
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 058e099..5f48c1d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -55,6 +55,7 @@
 using android::base::HwTimeoutMultiplier;
 using android::base::Result;
 using android::base::StringPrintf;
+using android::gui::DisplayInfo;
 using android::gui::FocusRequest;
 using android::gui::TouchOcclusionMode;
 using android::gui::WindowInfo;
@@ -345,18 +346,15 @@
             // Use identity transform for joystick and position-based (touchpad) events because they
             // don't depend on the window transform.
             return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, identityTransform,
-                                                   1.0f /*globalScaleFactor*/,
-                                                   inputTarget.displayOrientation,
-                                                   inputTarget.displaySize);
+                                                   identityTransform, 1.0f /*globalScaleFactor*/);
         }
     }
 
     if (inputTarget.useDefaultPointerTransform()) {
         const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
         return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform,
-                                               inputTarget.globalScaleFactor,
-                                               inputTarget.displayOrientation,
-                                               inputTarget.displaySize);
+                                               inputTarget.displayTransform,
+                                               inputTarget.globalScaleFactor);
     }
 
     ALOG_ASSERT(eventEntry->type == EventEntry::Type::MOTION);
@@ -407,24 +405,11 @@
 
     std::unique_ptr<DispatchEntry> dispatchEntry =
             std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags,
-                                            firstPointerTransform, inputTarget.globalScaleFactor,
-                                            inputTarget.displayOrientation,
-                                            inputTarget.displaySize);
+                                            firstPointerTransform, inputTarget.displayTransform,
+                                            inputTarget.globalScaleFactor);
     return dispatchEntry;
 }
 
-void addGestureMonitors(const std::vector<Monitor>& monitors,
-                        std::vector<TouchedMonitor>& outTouchedMonitors, float xOffset = 0,
-                        float yOffset = 0) {
-    if (monitors.empty()) {
-        return;
-    }
-    outTouchedMonitors.reserve(monitors.size() + outTouchedMonitors.size());
-    for (const Monitor& monitor : monitors) {
-        outTouchedMonitors.emplace_back(monitor, xOffset, yOffset);
-    }
-}
-
 status_t openInputChannelPair(const std::string& name, std::shared_ptr<InputChannel>& serverChannel,
                               std::unique_ptr<InputChannel>& clientChannel) {
     std::unique_ptr<InputChannel> uniqueServerChannel;
@@ -946,10 +931,9 @@
         }
 
         // Alternatively, maybe there's a gesture monitor that could handle this event
-        std::vector<TouchedMonitor> gestureMonitors = findTouchedGestureMonitorsLocked(displayId);
-        for (TouchedMonitor& gestureMonitor : gestureMonitors) {
+        for (const auto& monitor : getValueByKey(mGestureMonitorsByDisplay, displayId)) {
             sp<Connection> connection =
-                    getConnectionLocked(gestureMonitor.monitor.inputChannel->getConnectionToken());
+                    getConnectionLocked(monitor.inputChannel->getConnectionToken());
             if (connection != nullptr && connection->responsive) {
                 // This monitor could take more input. Drop all events preceding this
                 // event, so that gesture monitor could get a chance to receive the stream
@@ -1076,15 +1060,6 @@
     return nullptr;
 }
 
-std::vector<TouchedMonitor> InputDispatcher::findTouchedGestureMonitorsLocked(
-        int32_t displayId) const {
-    std::vector<TouchedMonitor> touchedMonitors;
-
-    std::vector<Monitor> monitors = getValueByKey(mGestureMonitorsByDisplay, displayId);
-    addGestureMonitors(monitors, touchedMonitors);
-    return touchedMonitors;
-}
-
 void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason dropReason) {
     const char* reason;
     switch (dropReason) {
@@ -1706,13 +1681,13 @@
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
         ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
               ", policyFlags=0x%x, "
-              "action=0x%x, actionButton=0x%x, flags=0x%x, "
+              "action=%s, actionButton=0x%x, flags=0x%x, "
               "metaState=0x%x, buttonState=0x%x,"
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
               prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId,
-              entry.policyFlags, entry.action, entry.actionButton, entry.flags, entry.metaState,
-              entry.buttonState, entry.edgeFlags, entry.xPrecision, entry.yPrecision,
-              entry.downTime);
+              entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
+              entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.edgeFlags,
+              entry.xPrecision, entry.yPrecision, entry.downTime);
 
         for (uint32_t i = 0; i < entry.pointerCount; i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%d, "
@@ -1868,6 +1843,11 @@
         return InputEventInjectionResult::FAILED;
     }
 
+    // Drop key events if requested by input feature
+    if (focusedWindowHandle != nullptr && shouldDropInput(entry, focusedWindowHandle)) {
+        return InputEventInjectionResult::FAILED;
+    }
+
     // Compatibility behavior: raise ANR if there is a focused application, but no focused window.
     // Only start counting when we have a focused event to dispatch. The ANR is canceled if we
     // start interacting with another application via touch (app switch). This code can be removed
@@ -1941,16 +1921,16 @@
  * Given a list of monitors, remove the ones we cannot find a connection for, and the ones
  * that are currently unresponsive.
  */
-std::vector<TouchedMonitor> InputDispatcher::selectResponsiveMonitorsLocked(
-        const std::vector<TouchedMonitor>& monitors) const {
-    std::vector<TouchedMonitor> responsiveMonitors;
+std::vector<Monitor> InputDispatcher::selectResponsiveMonitorsLocked(
+        const std::vector<Monitor>& monitors) const {
+    std::vector<Monitor> responsiveMonitors;
     std::copy_if(monitors.begin(), monitors.end(), std::back_inserter(responsiveMonitors),
-                 [this](const TouchedMonitor& monitor) REQUIRES(mLock) {
-                     sp<Connection> connection = getConnectionLocked(
-                             monitor.monitor.inputChannel->getConnectionToken());
+                 [this](const Monitor& monitor) REQUIRES(mLock) {
+                     sp<Connection> connection =
+                             getConnectionLocked(monitor.inputChannel->getConnectionToken());
                      if (connection == nullptr) {
                          ALOGE("Could not find connection for monitor %s",
-                               monitor.monitor.inputChannel->getName().c_str());
+                               monitor.inputChannel->getName().c_str());
                          return false;
                      }
                      if (!connection->responsive) {
@@ -2051,14 +2031,10 @@
             x = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X));
             y = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y));
         }
-        bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
+        const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
         newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
                                                            isDown /*addOutsideTargets*/);
 
-        std::vector<TouchedMonitor> newGestureMonitors = isDown
-                ? findTouchedGestureMonitorsLocked(displayId)
-                : std::vector<TouchedMonitor>{};
-
         // Figure out whether splitting will be allowed for this window.
         if (newTouchedWindowHandle != nullptr &&
             newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
@@ -2113,8 +2089,15 @@
             }
         }
 
-        // Also don't send the new touch event to unresponsive gesture monitors
-        newGestureMonitors = selectResponsiveMonitorsLocked(newGestureMonitors);
+        // Drop touch events if requested by input feature
+        if (newTouchedWindowHandle != nullptr && shouldDropInput(entry, newTouchedWindowHandle)) {
+            newTouchedWindowHandle = nullptr;
+        }
+
+        const std::vector<Monitor> newGestureMonitors = isDown
+                ? selectResponsiveMonitorsLocked(
+                          getValueByKey(mGestureMonitorsByDisplay, displayId))
+                : std::vector<Monitor>{};
 
         if (newTouchedWindowHandle == nullptr && newGestureMonitors.empty()) {
             ALOGI("Dropping event because there is no touchable window or gesture monitor at "
@@ -2178,6 +2161,13 @@
             sp<WindowInfoHandle> oldTouchedWindowHandle =
                     tempTouchState.getFirstForegroundWindowHandle();
             newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState);
+
+            // Drop touch events if requested by input feature
+            if (newTouchedWindowHandle != nullptr &&
+                shouldDropInput(entry, newTouchedWindowHandle)) {
+                newTouchedWindowHandle = nullptr;
+            }
+
             if (oldTouchedWindowHandle != newTouchedWindowHandle &&
                 oldTouchedWindowHandle != nullptr && newTouchedWindowHandle != nullptr) {
                 if (DEBUG_FOCUS) {
@@ -2327,9 +2317,8 @@
                               touchedWindow.pointerIds, inputTargets);
     }
 
-    for (const TouchedMonitor& touchedMonitor : tempTouchState.gestureMonitors) {
-        addMonitoringTargetLocked(touchedMonitor.monitor, touchedMonitor.xOffset,
-                                  touchedMonitor.yOffset, inputTargets);
+    for (const auto& monitor : tempTouchState.gestureMonitors) {
+        addMonitoringTargetLocked(monitor, displayId, inputTargets);
     }
 
     // Drop the outside or hover touch windows since we will not care about them
@@ -2506,9 +2495,13 @@
         inputTarget.inputChannel = inputChannel;
         inputTarget.flags = targetFlags;
         inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
-        inputTarget.displayOrientation = windowInfo->displayOrientation;
-        inputTarget.displaySize =
-                int2(windowHandle->getInfo()->displayWidth, windowHandle->getInfo()->displayHeight);
+        const auto& displayInfoIt = mDisplayInfos.find(windowInfo->displayId);
+        if (displayInfoIt != mDisplayInfos.end()) {
+            inputTarget.displayTransform = displayInfoIt->second.transform;
+        } else {
+            ALOGI_IF(isPerWindowInputRotationEnabled(),
+                     "DisplayInfo not found for window on display: %d", windowInfo->displayId);
+        }
         inputTargets.push_back(inputTarget);
         it = inputTargets.end() - 1;
     }
@@ -2520,27 +2513,44 @@
 }
 
 void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets,
-                                                       int32_t displayId, float xOffset,
-                                                       float yOffset) {
+                                                       int32_t displayId) {
     std::unordered_map<int32_t, std::vector<Monitor>>::const_iterator it =
             mGlobalMonitorsByDisplay.find(displayId);
 
     if (it != mGlobalMonitorsByDisplay.end()) {
         const std::vector<Monitor>& monitors = it->second;
         for (const Monitor& monitor : monitors) {
-            addMonitoringTargetLocked(monitor, xOffset, yOffset, inputTargets);
+            addMonitoringTargetLocked(monitor, displayId, inputTargets);
         }
     }
 }
 
-void InputDispatcher::addMonitoringTargetLocked(const Monitor& monitor, float xOffset,
-                                                float yOffset,
+void InputDispatcher::addMonitoringTargetLocked(const Monitor& monitor, int32_t displayId,
                                                 std::vector<InputTarget>& inputTargets) {
     InputTarget target;
     target.inputChannel = monitor.inputChannel;
     target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
     ui::Transform t;
-    t.set(xOffset, yOffset);
+    if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
+        // Input monitors always get un-rotated display coordinates. We undo the display
+        // rotation that is present in the display transform so that display rotation is not
+        // applied to these input targets.
+        const auto& displayInfo = it->second;
+        int32_t width = displayInfo.logicalWidth;
+        int32_t height = displayInfo.logicalHeight;
+        const auto orientation = displayInfo.transform.getOrientation();
+        uint32_t inverseOrientation = orientation;
+        if (orientation == ui::Transform::ROT_90) {
+            inverseOrientation = ui::Transform::ROT_270;
+            std::swap(width, height);
+        } else if (orientation == ui::Transform::ROT_270) {
+            inverseOrientation = ui::Transform::ROT_90;
+            std::swap(width, height);
+        }
+        target.displayTransform =
+                ui::Transform(inverseOrientation, width, height) * displayInfo.transform;
+        t = t * target.displayTransform;
+    }
     target.setDefaultPointerTransform(t);
     inputTargets.push_back(target);
 }
@@ -3226,9 +3236,7 @@
                                                      motionEntry.xPrecision, motionEntry.yPrecision,
                                                      motionEntry.xCursorPosition,
                                                      motionEntry.yCursorPosition,
-                                                     dispatchEntry->displayOrientation,
-                                                     dispatchEntry->displaySize.x,
-                                                     dispatchEntry->displaySize.y,
+                                                     dispatchEntry->rawTransform,
                                                      motionEntry.downTime, motionEntry.eventTime,
                                                      motionEntry.pointerCount,
                                                      motionEntry.pointerProperties, usingCoords);
@@ -3957,14 +3965,14 @@
             mLock.unlock();
 
             MotionEvent event;
-            ui::Transform transform;
+            ui::Transform identityTransform;
             event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC,
                              args->action, args->actionButton, args->flags, args->edgeFlags,
-                             args->metaState, args->buttonState, args->classification, transform,
-                             args->xPrecision, args->yPrecision, args->xCursorPosition,
-                             args->yCursorPosition, ui::Transform::ROT_0, INVALID_DISPLAY_SIZE,
-                             INVALID_DISPLAY_SIZE, args->downTime, args->eventTime,
-                             args->pointerCount, args->pointerProperties, args->pointerCoords);
+                             args->metaState, args->buttonState, args->classification,
+                             identityTransform, args->xPrecision, args->yPrecision,
+                             args->xCursorPosition, args->yCursorPosition, identityTransform,
+                             args->downTime, args->eventTime, args->pointerCount,
+                             args->pointerProperties, args->pointerCoords);
 
             policyFlags |= POLICY_FLAG_FILTERED;
             if (!mPolicy->filterInputEvent(&event, policyFlags)) {
@@ -4562,6 +4570,7 @@
 
 void InputDispatcher::setInputWindows(
         const std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>>& handlesPerDisplay) {
+    // TODO(b/198444055): Remove setInputWindows from InputDispatcher.
     { // acquire lock
         std::scoped_lock _l(mLock);
         for (const auto& [displayId, handles] : handlesPerDisplay) {
@@ -4642,6 +4651,18 @@
                     CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
                                                "touched window was removed");
                     synthesizeCancelationEventsForInputChannelLocked(touchedInputChannel, options);
+                    // Since we are about to drop the touch, cancel the events for the wallpaper as
+                    // well.
+                    if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND &&
+                        touchedWindow.windowHandle->getInfo()->hasWallpaper) {
+                        sp<WindowInfoHandle> wallpaper = state.getWallpaperWindow();
+                        if (wallpaper != nullptr) {
+                            sp<Connection> wallpaperConnection =
+                                    getConnectionLocked(wallpaper->getToken());
+                            synthesizeCancelationEventsForConnectionLocked(wallpaperConnection,
+                                                                           options);
+                        }
+                    }
                 }
                 state.windows.erase(state.windows.begin() + i);
             } else {
@@ -5082,9 +5103,17 @@
     }
 
     if (!mWindowHandlesByDisplay.empty()) {
-        for (auto& it : mWindowHandlesByDisplay) {
-            const std::vector<sp<WindowInfoHandle>> windowHandles = it.second;
-            dump += StringPrintf(INDENT "Display: %" PRId32 "\n", it.first);
+        for (const auto& [displayId, windowHandles] : mWindowHandlesByDisplay) {
+            dump += StringPrintf(INDENT "Display: %" PRId32 "\n", displayId);
+            if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
+                const auto& displayInfo = it->second;
+                dump += StringPrintf(INDENT2 "logicalSize=%dx%d\n", displayInfo.logicalWidth,
+                                     displayInfo.logicalHeight);
+                displayInfo.transform.dump(dump, "transform", INDENT4);
+            } else {
+                dump += INDENT2 "No DisplayInfo found!\n";
+            }
+
             if (!windowHandles.empty()) {
                 dump += INDENT2 "Windows:\n";
                 for (size_t i = 0; i < windowHandles.size(); i++) {
@@ -5116,13 +5145,12 @@
                                          windowInfo->inputFeatures.string().c_str());
                     dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%" PRId64
                                          "ms, trustedOverlay=%s, hasToken=%s, "
-                                         "touchOcclusionMode=%s, displayOrientation=%d\n",
+                                         "touchOcclusionMode=%s\n",
                                          windowInfo->ownerPid, windowInfo->ownerUid,
                                          millis(windowInfo->dispatchingTimeout),
                                          toString(windowInfo->trustedOverlay),
                                          toString(windowInfo->token != nullptr),
-                                         toString(windowInfo->touchOcclusionMode).c_str(),
-                                         windowInfo->displayOrientation);
+                                         toString(windowInfo->touchOcclusionMode).c_str());
                     windowInfo->transform.dump(dump, "transform", INDENT4);
                 }
             } else {
@@ -5435,9 +5463,9 @@
         TouchState& state = stateIt->second;
         std::shared_ptr<InputChannel> requestingChannel;
         std::optional<int32_t> foundDeviceId;
-        for (const TouchedMonitor& touchedMonitor : state.gestureMonitors) {
-            if (touchedMonitor.monitor.inputChannel->getConnectionToken() == token) {
-                requestingChannel = touchedMonitor.monitor.inputChannel;
+        for (const auto& monitor : state.gestureMonitors) {
+            if (monitor.inputChannel->getConnectionToken() == token) {
+                requestingChannel = monitor.inputChannel;
                 foundDeviceId = state.deviceId;
             }
         }
@@ -6168,16 +6196,44 @@
     mLooper->wake();
 }
 
-void InputDispatcher::onWindowInfosChanged(const std::vector<gui::WindowInfo>& windowInfos) {
+void InputDispatcher::onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
+                                           const std::vector<DisplayInfo>& displayInfos) {
     // The listener sends the windows as a flattened array. Separate the windows by display for
     // more convenient parsing.
     std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
-
     for (const auto& info : windowInfos) {
         handlesPerDisplay.emplace(info.displayId, std::vector<sp<WindowInfoHandle>>());
         handlesPerDisplay[info.displayId].push_back(new WindowInfoHandle(info));
     }
-    setInputWindows(handlesPerDisplay);
+
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+        mDisplayInfos.clear();
+        for (const auto& displayInfo : displayInfos) {
+            mDisplayInfos.emplace(displayInfo.displayId, displayInfo);
+        }
+
+        for (const auto& [displayId, handles] : handlesPerDisplay) {
+            setInputWindowsLocked(handles, displayId);
+        }
+    }
+    // Wake up poll loop since it may need to make new input dispatching choices.
+    mLooper->wake();
+}
+
+bool InputDispatcher::shouldDropInput(
+        const EventEntry& entry, const sp<android::gui::WindowInfoHandle>& windowHandle) const {
+    if (windowHandle->getInfo()->inputFeatures.test(WindowInfo::Feature::DROP_INPUT) ||
+        (windowHandle->getInfo()->inputFeatures.test(WindowInfo::Feature::DROP_INPUT_IF_OBSCURED) &&
+         isWindowObscuredLocked(windowHandle))) {
+        ALOGW("Dropping %s event targeting %s as requested by input feature %s on display "
+              "%" PRId32 ".",
+              ftl::enum_string(entry.type).c_str(), windowHandle->getName().c_str(),
+              windowHandle->getInfo()->inputFeatures.string().c_str(),
+              windowHandle->getInfo()->displayId);
+        return true;
+    }
+    return false;
 }
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index c09a89c..51ec551 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -143,7 +143,8 @@
 
     void displayRemoved(int32_t displayId) override;
 
-    void onWindowInfosChanged(const std::vector<gui::WindowInfo>& windowInfos) override;
+    void onWindowInfosChanged(const std::vector<gui::WindowInfo>&,
+                              const std::vector<gui::DisplayInfo>&) override;
 
 private:
     enum class DropReason {
@@ -337,8 +338,10 @@
     float mMaximumObscuringOpacityForTouch GUARDED_BY(mLock);
     android::os::BlockUntrustedTouchesMode mBlockUntrustedTouchesMode GUARDED_BY(mLock);
 
-    std::unordered_map<int32_t, std::vector<sp<android::gui::WindowInfoHandle>>>
+    std::unordered_map<int32_t /*displayId*/, std::vector<sp<android::gui::WindowInfoHandle>>>
             mWindowHandlesByDisplay GUARDED_BY(mLock);
+    std::unordered_map<int32_t /*displayId*/, android::gui::DisplayInfo> mDisplayInfos
+            GUARDED_BY(mLock);
     void setInputWindowsLocked(
             const std::vector<sp<android::gui::WindowInfoHandle>>& inputWindowHandles,
             int32_t displayId) REQUIRES(mLock);
@@ -513,18 +516,16 @@
     android::os::InputEventInjectionResult findTouchedWindowTargetsLocked(
             nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
             nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) REQUIRES(mLock);
-    std::vector<TouchedMonitor> findTouchedGestureMonitorsLocked(int32_t displayId) const
-            REQUIRES(mLock);
-    std::vector<TouchedMonitor> selectResponsiveMonitorsLocked(
-            const std::vector<TouchedMonitor>& gestureMonitors) const REQUIRES(mLock);
+    std::vector<Monitor> selectResponsiveMonitorsLocked(
+            const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
 
     void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                int32_t targetFlags, BitSet32 pointerIds,
                                std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
-    void addMonitoringTargetLocked(const Monitor& monitor, float xOffset, float yOffset,
+    void addMonitoringTargetLocked(const Monitor& monitor, int32_t displayId,
                                    std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
-    void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId,
-                                          float xOffset = 0, float yOffset = 0) REQUIRES(mLock);
+    void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
+            REQUIRES(mLock);
     void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
     bool checkInjectionPermission(const sp<android::gui::WindowInfoHandle>& windowHandle,
                                   const InjectionState* injectionState);
@@ -554,6 +555,10 @@
     std::string getApplicationWindowLabel(const InputApplicationHandle* applicationHandle,
                                           const sp<android::gui::WindowInfoHandle>& windowHandle);
 
+    bool shouldDropInput(const EventEntry& entry,
+                         const sp<android::gui::WindowInfoHandle>& windowHandle) const
+            REQUIRES(mLock);
+
     // Manage the dispatch cycle for a single connection.
     // These methods are deliberately not Interruptible because doing all of the work
     // with the mutex held makes it easier to ensure that connection invariants are maintained.
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 7c463c8..5b76eee 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -101,11 +101,8 @@
     // (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};
+    // Current display transform. Used for compatibility for raw coordinates.
+    ui::Transform displayTransform;
 
     // The subset of pointer ids to include in motion events dispatched to this input target
     // if FLAG_SPLIT is set.
diff --git a/services/inputflinger/dispatcher/Monitor.cpp b/services/inputflinger/dispatcher/Monitor.cpp
index bbce759..43a82d5 100644
--- a/services/inputflinger/dispatcher/Monitor.cpp
+++ b/services/inputflinger/dispatcher/Monitor.cpp
@@ -22,8 +22,4 @@
 Monitor::Monitor(const std::shared_ptr<InputChannel>& inputChannel, int32_t pid)
       : inputChannel(inputChannel), pid(pid) {}
 
-// --- TouchedMonitor ---
-TouchedMonitor::TouchedMonitor(const Monitor& monitor, float xOffset, float yOffset)
-      : monitor(monitor), xOffset(xOffset), yOffset(yOffset) {}
-
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Monitor.h b/services/inputflinger/dispatcher/Monitor.h
index 7be0760..365d5be 100644
--- a/services/inputflinger/dispatcher/Monitor.h
+++ b/services/inputflinger/dispatcher/Monitor.h
@@ -29,15 +29,6 @@
     explicit Monitor(const std::shared_ptr<InputChannel>& inputChannel, int32_t pid);
 };
 
-// For tracking the offsets we need to apply when adding gesture monitor targets.
-struct TouchedMonitor {
-    Monitor monitor;
-    float xOffset = 0.f;
-    float yOffset = 0.f;
-
-    explicit TouchedMonitor(const Monitor& monitor, float xOffset, float yOffset);
-};
-
 } // namespace android::inputdispatcher
 
 #endif // _UI_INPUT_INPUTDISPATCHER_MONITOR_H
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index b7ed658..759b3e7 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -75,7 +75,7 @@
     windows.push_back(touchedWindow);
 }
 
-void TouchState::addGestureMonitors(const std::vector<TouchedMonitor>& newMonitors) {
+void TouchState::addGestureMonitors(const std::vector<Monitor>& newMonitors) {
     const size_t newSize = gestureMonitors.size() + newMonitors.size();
     gestureMonitors.reserve(newSize);
     gestureMonitors.insert(std::end(gestureMonitors), std::begin(newMonitors),
@@ -134,4 +134,14 @@
     return haveSlipperyForegroundWindow;
 }
 
+sp<WindowInfoHandle> TouchState::getWallpaperWindow() const {
+    for (size_t i = 0; i < windows.size(); i++) {
+        const TouchedWindow& window = windows[i];
+        if (window.windowHandle->getInfo()->type == WindowInfo::Type::WALLPAPER) {
+            return window.windowHandle;
+        }
+    }
+    return nullptr;
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 579b868..4a62051 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -36,7 +36,7 @@
     int32_t displayId; // id to the display that currently has a touch, others are rejected
     std::vector<TouchedWindow> windows;
 
-    std::vector<TouchedMonitor> gestureMonitors;
+    std::vector<Monitor> gestureMonitors;
 
     TouchState();
     ~TouchState();
@@ -45,12 +45,13 @@
     void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
                            int32_t targetFlags, BitSet32 pointerIds);
     void addPortalWindow(const sp<android::gui::WindowInfoHandle>& windowHandle);
-    void addGestureMonitors(const std::vector<TouchedMonitor>& monitors);
+    void addGestureMonitors(const std::vector<Monitor>& monitors);
     void removeWindowByToken(const sp<IBinder>& token);
     void filterNonAsIsTouchWindows();
     void filterNonMonitors();
     sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
     bool isSlippery() const;
+    sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/reader/Macros.h b/services/inputflinger/reader/Macros.h
index 827e31a..0dfe7f1 100644
--- a/services/inputflinger/reader/Macros.h
+++ b/services/inputflinger/reader/Macros.h
@@ -31,7 +31,7 @@
 #define DEBUG_VIRTUAL_KEYS 0
 
 // Log debug messages about pointers.
-#define DEBUG_POINTERS 0
+static constexpr bool DEBUG_POINTERS = false;
 
 // Log debug messages about pointer assignment calculations.
 #define DEBUG_POINTER_ASSIGNMENT 0
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index fab7f4c..4bd1cd8 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -95,13 +95,13 @@
         }
 
         if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) {
-#if DEBUG_POINTERS
-            if (newSlot) {
-                ALOGW("MultiTouch device emitted invalid slot index %d but it "
-                      "should be between 0 and %zd; ignoring this slot.",
-                      mCurrentSlot, mSlotCount - 1);
+            if (DEBUG_POINTERS) {
+                if (newSlot) {
+                    ALOGW("MultiTouch device emitted invalid slot index %d but it "
+                          "should be between 0 and %zd; ignoring this slot.",
+                          mCurrentSlot, mSlotCount - 1);
+                }
             }
-#endif
         } else {
             Slot* slot = &mSlots[mCurrentSlot];
             // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of
@@ -273,19 +273,19 @@
             if (id) {
                 outState->rawPointerData.canceledIdBits.markBit(id.value());
             }
-#if DEBUG_POINTERS
-            ALOGI("Stop processing slot %zu for it received a palm event from device %s", inIndex,
-                  getDeviceName().c_str());
-#endif
+            if (DEBUG_POINTERS) {
+                ALOGI("Stop processing slot %zu for it received a palm event from device %s",
+                      inIndex, getDeviceName().c_str());
+            }
             continue;
         }
 
         if (outCount >= MAX_POINTERS) {
-#if DEBUG_POINTERS
-            ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
-                  "ignoring the rest.",
-                  getDeviceName().c_str(), MAX_POINTERS);
-#endif
+            if (DEBUG_POINTERS) {
+                ALOGD("MultiTouch device %s emitted more than maximum of %d pointers; "
+                      "ignoring the rest.",
+                      getDeviceName().c_str(), MAX_POINTERS);
+            }
             break; // too many fingers!
         }
 
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 0a91bde..e9d45b2 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -74,6 +74,12 @@
     return event;
 }
 
+static void assertMotionAction(int32_t expectedAction, int32_t receivedAction) {
+    ASSERT_EQ(expectedAction, receivedAction)
+            << "expected " << MotionEvent::actionToString(expectedAction) << ", got "
+            << MotionEvent::actionToString(receivedAction);
+}
+
 // --- FakeInputDispatcherPolicy ---
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
@@ -536,8 +542,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, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -550,8 +556,7 @@
                              (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,
-                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                     ARBITRARY_TIME, ARBITRARY_TIME,
+                     identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -563,8 +568,7 @@
                              (~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,
-                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                     ARBITRARY_TIME, ARBITRARY_TIME,
+                     identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -577,8 +581,7 @@
                              (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,
-                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                     ARBITRARY_TIME, ARBITRARY_TIME,
+                     identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -590,8 +593,7 @@
                              (~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,
-                     ui::Transform::ROT_0, INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE,
-                     ARBITRARY_TIME, ARBITRARY_TIME,
+                     identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -602,8 +604,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, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 0, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -613,8 +615,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, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -626,8 +628,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, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -638,8 +640,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, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 1, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -652,8 +654,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, ui::Transform::ROT_0,
-                     INVALID_DISPLAY_SIZE, INVALID_DISPLAY_SIZE, ARBITRARY_TIME, ARBITRARY_TIME,
+                     AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
+                     ARBITRARY_TIME,
                      /*pointerCount*/ 2, pointerProperties, pointerCoords);
     ASSERT_EQ(InputEventInjectionResult::FAILED,
               mDispatcher->injectInputEvent(&event, INJECTOR_PID, INJECTOR_UID,
@@ -801,7 +803,8 @@
             }
             case AINPUT_EVENT_TYPE_MOTION: {
                 const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-                EXPECT_EQ(expectedAction, motionEvent.getAction());
+                assertMotionAction(expectedAction, motionEvent.getAction());
+
                 if (expectedFlags.has_value()) {
                     EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
                 }
@@ -982,6 +985,10 @@
         mInfo.addTouchableRegion(frame);
     }
 
+    void setType(WindowInfo::Type type) { mInfo.type = type; }
+
+    void setHasWallpaper(bool hasWallpaper) { mInfo.hasWallpaper = hasWallpaper; }
+
     void addFlags(Flags<WindowInfo::Flag> flags) { mInfo.flags |= flags; }
 
     void setFlags(Flags<WindowInfo::Flag> flags) { mInfo.flags = flags; }
@@ -1285,9 +1292,8 @@
                          mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE,
                          mButtonState, MotionClassification::NONE, identityTransform,
                          /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
-                         mRawYCursorPosition, mDisplayOrientation, mDisplayWidth, mDisplayHeight,
-                         mEventTime, mEventTime, mPointers.size(), pointerProperties.data(),
-                         pointerCoords.data());
+                         mRawYCursorPosition, identityTransform, mEventTime, mEventTime,
+                         mPointers.size(), pointerProperties.data(), pointerCoords.data());
 
         return event;
     }
@@ -1302,9 +1308,6 @@
     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};
 
     std::vector<PointerBuilder> mPointers;
 };
@@ -1485,6 +1488,197 @@
     windowSecond->assertNoEvents();
 }
 
+/**
+ * Two windows: A top window, and a wallpaper behind the window.
+ * Touch goes to the top window, and then top window disappears. Ensure that wallpaper window
+ * gets ACTION_CANCEL.
+ * 1. foregroundWindow <-- has wallpaper (hasWallpaper=true)
+ * 2. wallpaperWindow <-- is wallpaper (type=InputWindowInfo::Type::WALLPAPER)
+ */
+TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCanceled) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> foregroundWindow =
+            new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+    foregroundWindow->setHasWallpaper(true);
+    sp<FakeWindowHandle> wallpaperWindow =
+            new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->setType(WindowInfo::Type::WALLPAPER);
+    constexpr int expectedWallpaperFlags =
+            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Both foreground window and its wallpaper should receive the touch down
+    foregroundWindow->consumeMotionDown();
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    foregroundWindow->consumeMotionMove();
+    wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // Now the foreground window goes away, but the wallpaper stays
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wallpaperWindow}}});
+    foregroundWindow->consumeMotionCancel();
+    // Since the "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
+    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+}
+
+/**
+ * A single window that receives touch (on top), and a wallpaper window underneath it.
+ * The top window gets a multitouch gesture.
+ * Ensure that wallpaper gets the same gesture.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+    window->setHasWallpaper(true);
+
+    sp<FakeWindowHandle> wallpaperWindow =
+            new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->setType(WindowInfo::Type::WALLPAPER);
+    constexpr int expectedWallpaperFlags =
+            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+
+    // Touch down on top window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 100}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Both top window and its wallpaper should receive the touch down
+    window->consumeMotionDown();
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // Second finger down on the top window
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                               AINPUT_SOURCE_TOUCHSCREEN)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(100))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(150)
+                                     .y(150))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    window->consumeMotionPointerDown(1 /* pointerIndex */);
+    wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
+                                              expectedWallpaperFlags);
+    window->assertNoEvents();
+    wallpaperWindow->assertNoEvents();
+}
+
+/**
+ * Two windows: a window on the left and window on the right.
+ * A third window, wallpaper, is behind both windows, and spans both top windows.
+ * The first touch down goes to the left window. A second pointer touches down on the right window.
+ * The touch is split, so both left and right windows should receive ACTION_DOWN.
+ * The wallpaper will get the full event, so it should receive ACTION_DOWN followed by
+ * ACTION_POINTER_DOWN(1).
+ */
+TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> leftWindow =
+            new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+    leftWindow->setFrame(Rect(0, 0, 200, 200));
+    leftWindow->setFlags(WindowInfo::Flag::SPLIT_TOUCH | WindowInfo::Flag::NOT_TOUCH_MODAL);
+    leftWindow->setHasWallpaper(true);
+
+    sp<FakeWindowHandle> rightWindow =
+            new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+    rightWindow->setFrame(Rect(200, 0, 400, 200));
+    rightWindow->setFlags(WindowInfo::Flag::SPLIT_TOUCH | WindowInfo::Flag::NOT_TOUCH_MODAL);
+    rightWindow->setHasWallpaper(true);
+
+    sp<FakeWindowHandle> wallpaperWindow =
+            new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->setFrame(Rect(0, 0, 400, 200));
+    wallpaperWindow->setType(WindowInfo::Type::WALLPAPER);
+    constexpr int expectedWallpaperFlags =
+            AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
+    mDispatcher->setInputWindows(
+            {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
+
+    // Touch down on left window
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 100}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Both foreground window and its wallpaper should receive the touch down
+    leftWindow->consumeMotionDown();
+    wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // Second finger down on the right window
+    const MotionEvent secondFingerDownEvent =
+            MotionEventBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                               AINPUT_SOURCE_TOUCHSCREEN)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(100))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(300)
+                                     .y(100))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    leftWindow->consumeMotionMove();
+    // Since the touch is split, right window gets ACTION_DOWN
+    rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
+                                              expectedWallpaperFlags);
+
+    // Now, leftWindow, which received the first finger, disappears.
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {rightWindow, wallpaperWindow}}});
+    leftWindow->consumeMotionCancel();
+    // Since a "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
+    wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+    // The pointer that's still down on the right window moves, and goes to the right window only.
+    // As far as the dispatcher's concerned though, both pointers are still present.
+    const MotionEvent secondFingerMoveEvent =
+            MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(100)
+                                     .y(100))
+                    .pointer(PointerBuilder(/* id */ 1, AMOTION_EVENT_TOOL_TYPE_FINGER)
+                                     .x(310)
+                                     .y(110))
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
+                                InputEventInjectionSync::WAIT_FOR_RESULT));
+    rightWindow->consumeMotionMove();
+
+    leftWindow->assertNoEvents();
+    rightWindow->assertNoEvents();
+    wallpaperWindow->assertNoEvents();
+}
+
 TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
     sp<FakeWindowHandle> windowLeft =
@@ -2301,11 +2495,21 @@
                                      expectedDisplayId, expectedFlags);
     }
 
+    void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+        mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_MOVE,
+                                     expectedDisplayId, expectedFlags);
+    }
+
     void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
         mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_UP,
                                      expectedDisplayId, expectedFlags);
     }
 
+    void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+        mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
+                                     expectedDisplayId, expectedFlags);
+    }
+
     MotionEvent* consumeMotion() {
         InputEvent* event = mInputReceiver->consume();
         if (!event) {
@@ -2325,6 +2529,57 @@
     std::unique_ptr<FakeInputReceiver> mInputReceiver;
 };
 
+/**
+ * Two entities that receive touch: A window, and a global monitor.
+ * The touch goes to the window, and then the window disappears.
+ * The monitor does not get cancel right away. But if more events come in, the touch gets canceled
+ * for the monitor, as well.
+ * 1. foregroundWindow
+ * 2. monitor <-- global monitor (doesn't observe z order, receives all events)
+ */
+TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_GlobalMonitorTouchIsCanceled) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+
+    FakeMonitorReceiver monitor =
+            FakeMonitorReceiver(mDispatcher, "GlobalMonitor", ADISPLAY_ID_DEFAULT,
+                                false /*isGestureMonitor*/);
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+                               {100, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    // Both the foreground window and the global monitor should receive the touch down
+    window->consumeMotionDown();
+    monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {110, 200}))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+    window->consumeMotionMove();
+    monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
+
+    // Now the foreground window goes away
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+    window->consumeMotionCancel();
+    monitor.assertNoEvents(); // Global monitor does not get a cancel yet
+
+    // If more events come in, there will be no more foreground window to send them to. This will
+    // cause a cancel for the monitor, as well.
+    ASSERT_EQ(InputEventInjectionResult::FAILED,
+              injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+                                ADISPLAY_ID_DEFAULT, {120, 200}))
+            << "Injection should fail because the window was removed";
+    window->assertNoEvents();
+    // Global monitor now gets the cancel
+    monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
+}
+
 // Tests for gesture monitors
 TEST_F(InputDispatcherTest, GestureMonitor_ReceivesMotionEvents) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
@@ -3359,8 +3614,7 @@
                          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, ui::Transform::ROT_0,
-                         0 /*INVALID_DISPLAY_SIZE*/, 0 /*INVALID_DISPLAY_SIZE*/, eventTime,
+                         AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, eventTime,
                          eventTime,
                          /*pointerCount*/ 1, pointerProperties, pointerCoords);
 
@@ -3567,7 +3821,7 @@
                 << " event, got " << inputEventTypeToString(event->getType()) << " event";
 
         const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
-        EXPECT_EQ(expectedAction, motionEvent.getAction());
+        assertMotionAction(expectedAction, motionEvent.getAction());
 
         for (size_t i = 0; i < points.size(); i++) {
             float expectedX = points[i].x;
@@ -5471,4 +5725,134 @@
     mSecondWindow->assertNoEvents();
 }
 
+class InputDispatcherDropInputFeatureTest : public InputDispatcherTest {};
+
+TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+    window->setInputFeatures(WindowInfo::Feature::DROP_INPUT);
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    window->setFocusable(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+
+    // With the flag set, window should not get any input
+    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(&keyArgs);
+    window->assertNoEvents();
+
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&motionArgs);
+    window->assertNoEvents();
+
+    // With the flag cleared, the window should get input
+    window->setInputFeatures({});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(&keyArgs);
+    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&motionArgs);
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->assertNoEvents();
+}
+
+TEST_F(InputDispatcherDropInputFeatureTest, ObscuredWindowDropsInput) {
+    std::shared_ptr<FakeApplicationHandle> obscuringApplication =
+            std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> obscuringWindow =
+            new FakeWindowHandle(obscuringApplication, mDispatcher, "obscuringWindow",
+                                 ADISPLAY_ID_DEFAULT);
+    obscuringWindow->setFrame(Rect(0, 0, 50, 50));
+    obscuringWindow->setOwnerInfo(111, 111);
+    obscuringWindow->setFlags(WindowInfo::Flag::NOT_TOUCHABLE);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+    window->setInputFeatures(WindowInfo::Feature::DROP_INPUT_IF_OBSCURED);
+    window->setOwnerInfo(222, 222);
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    window->setFocusable(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+
+    // With the flag set, window should not get any input
+    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(&keyArgs);
+    window->assertNoEvents();
+
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&motionArgs);
+    window->assertNoEvents();
+
+    // With the flag cleared, the window should get input
+    window->setInputFeatures({});
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+
+    keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(&keyArgs);
+    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&motionArgs);
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED);
+    window->assertNoEvents();
+}
+
+TEST_F(InputDispatcherDropInputFeatureTest, UnobscuredWindowGetsInput) {
+    std::shared_ptr<FakeApplicationHandle> obscuringApplication =
+            std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> obscuringWindow =
+            new FakeWindowHandle(obscuringApplication, mDispatcher, "obscuringWindow",
+                                 ADISPLAY_ID_DEFAULT);
+    obscuringWindow->setFrame(Rect(0, 0, 50, 50));
+    obscuringWindow->setOwnerInfo(111, 111);
+    obscuringWindow->setFlags(WindowInfo::Flag::NOT_TOUCHABLE);
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+    window->setInputFeatures(WindowInfo::Feature::DROP_INPUT_IF_OBSCURED);
+    window->setOwnerInfo(222, 222);
+    mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+    window->setFocusable(true);
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+    setFocusedWindow(window);
+    window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+
+    // With the flag set, window should not get any input
+    NotifyKeyArgs keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(&keyArgs);
+    window->assertNoEvents();
+
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                               ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&motionArgs);
+    window->assertNoEvents();
+
+    // When the window is no longer obscured because it went on top, it should get input
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, obscuringWindow}}});
+
+    keyArgs = generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyKey(&keyArgs);
+    window->consumeKeyUp(ADISPLAY_ID_DEFAULT);
+
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT);
+    mDispatcher->notifyMotion(&motionArgs);
+    window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+    window->assertNoEvents();
+}
+
 } // namespace android::inputdispatcher
diff --git a/services/surfaceflinger/BufferQueueLayer.cpp b/services/surfaceflinger/BufferQueueLayer.cpp
index e4f642e..d51db88 100644
--- a/services/surfaceflinger/BufferQueueLayer.cpp
+++ b/services/surfaceflinger/BufferQueueLayer.cpp
@@ -17,7 +17,6 @@
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wconversion"
-#pragma clang diagnostic ignored "-Wextra"
 
 #undef LOG_TAG
 #define LOG_TAG "BufferQueueLayer"
@@ -171,7 +170,7 @@
         expectedPresentTime = 0;
     }
 
-    for (int i = 1; i < mQueueItems.size(); i++) {
+    for (size_t i = 1; i < mQueueItems.size(); i++) {
         const bool fenceSignaled =
                 mQueueItems[i].item.mFenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
         if (!fenceSignaled) {
@@ -243,7 +242,7 @@
     uint64_t lastSignaledFrameNumber = mLastFrameNumberReceived;
     {
         Mutex::Autolock lock(mQueueItemLock);
-        for (int i = 0; i < mQueueItems.size(); i++) {
+        for (size_t i = 0; i < mQueueItems.size(); i++) {
             bool fenceSignaled =
                     mQueueItems[i].item.mFenceTime->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
             if (!fenceSignaled) {
@@ -629,4 +628,4 @@
 } // namespace android
 
 // TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
+#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 2f8a90f..3397118 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -139,6 +139,34 @@
     return mCompositionDisplay->getRenderSurface()->getPageFlipCount();
 }
 
+std::pair<gui::DisplayInfo, ui::Transform> DisplayDevice::getInputInfo() const {
+    gui::DisplayInfo info;
+    info.displayId = getLayerStack().id;
+
+    // The physical orientation is set when the orientation of the display panel is
+    // different than the default orientation of the device. Other services like
+    // InputFlinger do not know about this, so we do not need to expose the physical
+    // orientation of the panel outside of SurfaceFlinger.
+    const ui::Rotation inversePhysicalOrientation = ui::ROTATION_0 - mPhysicalOrientation;
+    auto width = getWidth();
+    auto height = getHeight();
+    if (inversePhysicalOrientation == ui::ROTATION_90 ||
+        inversePhysicalOrientation == ui::ROTATION_270) {
+        std::swap(width, height);
+    }
+    const ui::Transform undoPhysicalOrientation(ui::Transform::toRotationFlags(
+                                                        inversePhysicalOrientation),
+                                                width, height);
+    const auto& displayTransform = undoPhysicalOrientation * getTransform();
+    // Send the inverse display transform to input so it can convert display coordinates to
+    // logical display.
+    info.transform = displayTransform.inverse();
+
+    info.logicalWidth = getLayerStackSpaceRect().width();
+    info.logicalHeight = getLayerStackSpaceRect().height();
+    return {info, displayTransform};
+}
+
 // ----------------------------------------------------------------------------
 void DisplayDevice::setPowerMode(hal::PowerMode mode) {
     mPowerMode = mode;
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 9de578f..4a731bd 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -167,6 +167,10 @@
         return mDeviceProductInfo;
     }
 
+    // Get the DisplayInfo that will be sent to InputFlinger, and the display transform that should
+    // be applied to all the input windows on the display.
+    std::pair<gui::DisplayInfo, ui::Transform> getInputInfo() const;
+
     /* ------------------------------------------------------------------------
      * Display power mode management.
      */
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index f070d87..d549fe9 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -135,6 +135,7 @@
     mDrawingState.postTime = -1;
     mDrawingState.destinationFrame.makeInvalid();
     mDrawingState.isTrustedOverlay = false;
+    mDrawingState.dropInputMode = gui::DropInputMode::NONE;
 
     if (args.flags & ISurfaceComposerClient::eNoColorFill) {
         // Set an invalid color so there is no color fill.
@@ -2132,7 +2133,7 @@
     if (traceFlags & SurfaceTracing::TRACE_INPUT) {
         WindowInfo info;
         if (useDrawing) {
-            info = fillInputInfo(nullptr);
+            info = fillInputInfo(ui::Transform());
         } else {
             info = state.inputInfo;
         }
@@ -2266,7 +2267,7 @@
     }
 }
 
-WindowInfo Layer::fillInputInfo(const DisplayDevice* display) {
+WindowInfo Layer::fillInputInfo(const ui::Transform& displayTransform) {
     if (!hasInputInfo()) {
         mDrawingState.inputInfo.name = getName();
         mDrawingState.inputInfo.ownerUid = mOwnerUid;
@@ -2280,35 +2281,6 @@
     info.id = sequence;
     info.displayId = getLayerStack().id;
 
-    // Transform that maps from LayerStack space to display space, e.g. rotated to non-rotated.
-    // Used when InputFlinger operates in display space.
-    ui::Transform displayTransform;
-    if (display) {
-        // The physical orientation is set when the orientation of the display panel is different
-        // than the default orientation of the device. Other services like InputFlinger do not know
-        // about this, so we do not need to expose the physical orientation of the panel outside of
-        // SurfaceFlinger.
-        const ui::Rotation inversePhysicalOrientation =
-                ui::ROTATION_0 - display->getPhysicalOrientation();
-        auto width = display->getWidth();
-        auto height = display->getHeight();
-        if (inversePhysicalOrientation == ui::ROTATION_90 ||
-            inversePhysicalOrientation == ui::ROTATION_270) {
-            std::swap(width, height);
-        }
-        const ui::Transform undoPhysicalOrientation(ui::Transform::toRotationFlags(
-                                                            inversePhysicalOrientation),
-                                                    width, height);
-        displayTransform = undoPhysicalOrientation * display->getTransform();
-
-        // Send the inverse of the display orientation so that input can transform points back to
-        // the rotated display space.
-        const ui::Rotation inverseOrientation = ui::ROTATION_0 - display->getOrientation();
-        info.displayOrientation = ui::Transform::toRotationFlags(inverseOrientation);
-
-        info.displayWidth = width;
-        info.displayHeight = height;
-    }
     fillInputFrameInfo(info, displayTransform);
 
     // For compatibility reasons we let layers which can receive input
@@ -2570,6 +2542,14 @@
     return handle->owner;
 }
 
+bool Layer::setDropInputMode(gui::DropInputMode mode) {
+    if (mDrawingState.dropInputMode == mode) {
+        return false;
+    }
+    mDrawingState.dropInputMode = mode;
+    return true;
+}
+
 // ---------------------------------------------------------------------------
 
 std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) {
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 4200be4..b70d5d4 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -17,6 +17,7 @@
 
 #pragma once
 
+#include <android/gui/DropInputMode.h>
 #include <compositionengine/LayerFE.h>
 #include <gui/BufferQueue.h>
 #include <gui/ISurfaceComposerClient.h>
@@ -277,6 +278,8 @@
         Rect destinationFrame;
 
         sp<IBinder> releaseBufferEndpoint;
+
+        gui::DropInputMode dropInputMode;
     };
 
     /*
@@ -442,6 +445,8 @@
     virtual bool setFrameRateSelectionPriority(int32_t priority);
     virtual bool setFixedTransformHint(ui::Transform::RotationFlags fixedTransformHint);
     virtual void setAutoRefresh(bool /* autoRefresh */) {}
+    bool setDropInputMode(gui::DropInputMode);
+
     //  If the variable is not set on the layer, it traverses up the tree to inherit the frame
     //  rate priority from its parent.
     virtual int32_t getFrameRateSelectionPriority() const;
@@ -851,7 +856,7 @@
     bool getPremultipledAlpha() const;
     void setInputInfo(const gui::WindowInfo& info);
 
-    gui::WindowInfo fillInputInfo(const DisplayDevice*);
+    gui::WindowInfo fillInputInfo(const ui::Transform& displayTransform);
 
     /**
      * Returns whether this layer has an explicitly set input-info.
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
index e922d46..3f6bd6f 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp
@@ -133,27 +133,10 @@
     return true;
 }
 
-float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer,
-                                                    const RefreshRate& refreshRate,
-                                                    bool isSeamlessSwitch) const {
-    if (!isVoteAllowed(layer, refreshRate)) {
-        return 0;
-    }
-
+float RefreshRateConfigs::calculateNonExactMatchingLayerScoreLocked(
+        const LayerRequirement& layer, const RefreshRate& refreshRate) const {
     constexpr float kScoreForFractionalPairs = .8f;
 
-    // Slightly prefer seamless switches.
-    constexpr float kSeamedSwitchPenalty = 0.95f;
-    const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
-
-    // If the layer wants Max, give higher score to the higher refresh rate
-    if (layer.vote == LayerVoteType::Max) {
-        const auto ratio = refreshRate.getFps().getValue() /
-                mAppRequestRefreshRates.back()->getFps().getValue();
-        // use ratio^2 to get a lower score the more we get further from peak
-        return ratio * ratio;
-    }
-
     const auto displayPeriod = refreshRate.getVsyncPeriod();
     const auto layerPeriod = layer.desiredRefreshRate.getPeriodNsecs();
     if (layer.vote == LayerVoteType::ExplicitDefault) {
@@ -178,7 +161,7 @@
     if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
         layer.vote == LayerVoteType::Heuristic) {
         if (isFractionalPairOrMultiple(refreshRate.getFps(), layer.desiredRefreshRate)) {
-            return kScoreForFractionalPairs * seamlessness;
+            return kScoreForFractionalPairs;
         }
 
         // Calculate how many display vsyncs we need to present a single frame for this
@@ -188,7 +171,7 @@
         static constexpr size_t MAX_FRAMES_TO_FIT = 10; // Stop calculating when score < 0.1
         if (displayFramesRemainder == 0) {
             // Layer desired refresh rate matches the display rate.
-            return 1.0f * seamlessness;
+            return 1.0f;
         }
 
         if (displayFramesQuotient == 0) {
@@ -206,7 +189,29 @@
             iter++;
         }
 
-        return (1.0f / iter) * seamlessness;
+        return (1.0f / iter);
+    }
+
+    return 0;
+}
+
+float RefreshRateConfigs::calculateLayerScoreLocked(const LayerRequirement& layer,
+                                                    const RefreshRate& refreshRate,
+                                                    bool isSeamlessSwitch) const {
+    if (!isVoteAllowed(layer, refreshRate)) {
+        return 0;
+    }
+
+    // Slightly prefer seamless switches.
+    constexpr float kSeamedSwitchPenalty = 0.95f;
+    const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
+
+    // If the layer wants Max, give higher score to the higher refresh rate
+    if (layer.vote == LayerVoteType::Max) {
+        const auto ratio = refreshRate.getFps().getValue() /
+                mAppRequestRefreshRates.back()->getFps().getValue();
+        // use ratio^2 to get a lower score the more we get further from peak
+        return ratio * ratio;
     }
 
     if (layer.vote == LayerVoteType::ExplicitExact) {
@@ -221,7 +226,18 @@
         return divider == 1;
     }
 
-    return 0;
+    // If the layer frame rate is a divider of the refresh rate it should score
+    // the highest score.
+    if (getFrameRateDivider(refreshRate.getFps(), layer.desiredRefreshRate) > 0) {
+        return 1.0f * seamlessness;
+    }
+
+    // The layer frame rate is not a divider of the refresh rate,
+    // there is a small penalty attached to the score to favor the frame rates
+    // the exactly matches the display refresh rate or a multiple.
+    constexpr float kNonExactMatchingPenalty = 0.99f;
+    return calculateNonExactMatchingLayerScoreLocked(layer, refreshRate) * seamlessness *
+            kNonExactMatchingPenalty;
 }
 
 struct RefreshRateScore {
@@ -330,18 +346,28 @@
     const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 ||
             explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0;
 
+    const Policy* policy = getCurrentPolicyLocked();
+    const auto& defaultMode = mRefreshRates.at(policy->defaultMode);
+    // If the default mode group is different from the group of current mode,
+    // this means a layer requesting a seamed mode switch just disappeared and
+    // we should switch back to the default group.
+    // However if a seamed layer is still present we anchor around the group
+    // of the current mode, in order to prevent unnecessary seamed mode switches
+    // (e.g. when pausing a video playback).
+    const auto anchorGroup = seamedFocusedLayers > 0 ? mCurrentRefreshRate->getModeGroup()
+                                                     : defaultMode->getModeGroup();
+
     // Consider the touch event if there are no Explicit* layers. Otherwise wait until after we've
     // selected a refresh rate to see if we should apply touch boost.
     if (globalSignals.touch && !hasExplicitVoteLayers) {
         ALOGV("TouchBoost - choose %s", getMaxRefreshRateByPolicyLocked().getName().c_str());
         setTouchConsidered();
-        return getMaxRefreshRateByPolicyLocked();
+        return getMaxRefreshRateByPolicyLocked(anchorGroup);
     }
 
     // If the primary range consists of a single refresh rate then we can only
     // move out the of range if layers explicitly request a different refresh
     // rate.
-    const Policy* policy = getCurrentPolicyLocked();
     const bool primaryRangeIsSingleRate =
             policy->primaryRange.min.equalsWithMargin(policy->primaryRange.max);
 
@@ -353,7 +379,9 @@
     }
 
     if (layers.empty() || noVoteLayers == layers.size()) {
-        return getMaxRefreshRateByPolicyLocked();
+        const auto& refreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup);
+        ALOGV("no layers with votes - choose %s", refreshRate.getName().c_str());
+        return refreshRate;
     }
 
     // Only if all layers want Min we should return Min
@@ -370,8 +398,6 @@
         scores.emplace_back(RefreshRateScore{refreshRate, 0.0f});
     }
 
-    const auto& defaultMode = mRefreshRates.at(policy->defaultMode);
-
     for (const auto& layer : layers) {
         ALOGV("Calculating score for %s (%s, weight %.2f, desired %.2f) ", layer.name.c_str(),
               layerVoteTypeString(layer.vote).c_str(), layer.weight,
@@ -409,10 +435,7 @@
             // mode group otherwise. In second case, if the current mode group is different
             // from the default, this means a layer with seamlessness=SeamedAndSeamless has just
             // disappeared.
-            const bool isInPolicyForDefault = seamedFocusedLayers > 0
-                    ? scores[i].refreshRate->getModeGroup() == mCurrentRefreshRate->getModeGroup()
-                    : scores[i].refreshRate->getModeGroup() == defaultMode->getModeGroup();
-
+            const bool isInPolicyForDefault = scores[i].refreshRate->getModeGroup() == anchorGroup;
             if (layer.seamlessness == Seamlessness::Default && !isInPolicyForDefault) {
                 ALOGV("%s ignores %s. Current mode = %s", formatLayerInfo(layer, weight).c_str(),
                       scores[i].refreshRate->toString().c_str(),
@@ -451,9 +474,9 @@
         // range instead of picking a random score from the app range.
         if (std::all_of(scores.begin(), scores.end(),
                         [](RefreshRateScore score) { return score.score == 0; })) {
-            ALOGV("layers not scored - choose %s",
-                  getMaxRefreshRateByPolicyLocked().getName().c_str());
-            return getMaxRefreshRateByPolicyLocked();
+            const auto& refreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup);
+            ALOGV("layers not scored - choose %s", refreshRate.getName().c_str());
+            return refreshRate;
         } else {
             return *bestRefreshRate;
         }
@@ -463,7 +486,7 @@
     // interactive (as opposed to ExplicitExactOrMultiple) and therefore if those posted an explicit
     // vote we should not change it if we get a touch event. Only apply touch boost if it will
     // actually increase the refresh rate over the normal selection.
-    const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked();
+    const RefreshRate& touchRefreshRate = getMaxRefreshRateByPolicyLocked(anchorGroup);
 
     const bool touchBoostForExplicitExact = [&] {
         if (mSupportsFrameRateOverride) {
@@ -646,10 +669,10 @@
     return getMaxRefreshRateByPolicyLocked();
 }
 
-const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked() const {
+const RefreshRate& RefreshRateConfigs::getMaxRefreshRateByPolicyLocked(int anchorGroup) const {
     for (auto it = mPrimaryRefreshRates.rbegin(); it != mPrimaryRefreshRates.rend(); it++) {
         const auto& refreshRate = (**it);
-        if (mCurrentRefreshRate->getModeGroup() == refreshRate.getModeGroup()) {
+        if (anchorGroup == refreshRate.getModeGroup()) {
             return refreshRate;
         }
     }
diff --git a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
index 0d75689..ffaa029 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
@@ -388,7 +388,11 @@
 
     // Returns the highest refresh rate according to the current policy. May change at runtime. Only
     // uses the primary range, not the app request range.
-    const RefreshRate& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock);
+    const RefreshRate& getMaxRefreshRateByPolicyLocked() const REQUIRES(mLock) {
+        return getMaxRefreshRateByPolicyLocked(mCurrentRefreshRate->getModeGroup());
+    }
+
+    const RefreshRate& getMaxRefreshRateByPolicyLocked(int anchorGroup) const REQUIRES(mLock);
 
     // Returns the current refresh rate, if allowed. Otherwise the default that is allowed by
     // the policy.
@@ -405,6 +409,9 @@
     float calculateLayerScoreLocked(const LayerRequirement&, const RefreshRate&,
                                     bool isSeamlessSwitch) const REQUIRES(mLock);
 
+    float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&,
+                                                    const RefreshRate&) const REQUIRES(mLock);
+
     void updateDisplayModes(const DisplayModes& mode, DisplayModeId currentModeId) EXCLUDES(mLock);
 
     // The list of refresh rates, indexed by display modes ID. This may change after this
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 34b5cdc..ebb6b07 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -169,6 +169,7 @@
 
 using android::hardware::power::Boost;
 using base::StringAppendF;
+using gui::DisplayInfo;
 using gui::IWindowInfosListener;
 using gui::WindowInfo;
 using ui::ColorMode;
@@ -1709,7 +1710,15 @@
 
 void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
                                         std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
-    ATRACE_CALL();
+    const std::string tracePeriod = [vsyncPeriod]() {
+        if (ATRACE_ENABLED() && vsyncPeriod) {
+            std::stringstream ss;
+            ss << "(" << *vsyncPeriod << ")";
+            return ss.str();
+        }
+        return std::string();
+    }();
+    ATRACE_FORMAT("onComposerHalVsync%s", tracePeriod.c_str());
 
     Mutex::Autolock lock(mStateLock);
     const auto displayId = getHwComposer().toPhysicalDisplayId(hwcDisplayId);
@@ -3059,6 +3068,16 @@
 
 void SurfaceFlinger::notifyWindowInfos() {
     std::vector<WindowInfo> windowInfos;
+    std::vector<DisplayInfo> displayInfos;
+    std::unordered_map<const DisplayDevice*, const ui::Transform> displayTransforms;
+
+    if (enablePerWindowInputRotation()) {
+        for (const auto& [_, display] : ON_MAIN_THREAD(mDisplays)) {
+            const auto& [info, transform] = display->getInputInfo();
+            displayInfos.emplace_back(info);
+            displayTransforms.emplace(display.get(), transform);
+        }
+    }
 
     mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
         if (!layer->needsInputInfo()) return;
@@ -3070,9 +3089,11 @@
 
         // When calculating the screen bounds we ignore the transparent region since it may
         // result in an unwanted offset.
-        windowInfos.push_back(layer->fillInputInfo(display));
+        const auto it = displayTransforms.find(display);
+        windowInfos.push_back(
+                layer->fillInputInfo(it != displayTransforms.end() ? it->second : ui::Transform()));
     });
-    mWindowInfosListenerInvoker->windowInfosChanged(windowInfos,
+    mWindowInfosListenerInvoker->windowInfosChanged(windowInfos, displayInfos,
                                                     mInputWindowCommands.syncInputWindows);
 }
 
@@ -4134,6 +4155,16 @@
             flags |= eTraversalNeeded;
         }
     }
+    if (what & layer_state_t::eDropInputModeChanged) {
+        if (privileged) {
+            if (layer->setDropInputMode(s.dropInputMode)) {
+                flags |= eTraversalNeeded;
+                mInputInfoChanged = true;
+            }
+        } else {
+            ALOGE("Attempt to update DropInputMode without permission ACCESS_SURFACE_FLINGER");
+        }
+    }
     // This has to happen after we reparent children because when we reparent to null we remove
     // child layers from current state and remove its relative z. If the children are reparented in
     // the same transaction, then we have to make sure we reparent the children first so we do not
@@ -5133,7 +5164,8 @@
             continue;
         }
 
-        StringAppendF(&result, "Display %s HWC layers:\n", to_string(*displayId).c_str());
+        StringAppendF(&result, "Display %s (%s) HWC layers:\n", to_string(*displayId).c_str(),
+                      (isDisplayActiveLocked(display) ? "active" : "inactive"));
         Layer::miniDumpHeader(result);
 
         const DisplayDevice& ref = *display;
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 9dedfa7..6adad6d 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -1014,7 +1014,7 @@
 
     sp<DisplayDevice> getDisplayWithInputByLayer(Layer* layer) const REQUIRES(mStateLock);
 
-    bool isDisplayActiveLocked(const sp<const DisplayDevice>& display) REQUIRES(mStateLock) {
+    bool isDisplayActiveLocked(const sp<const DisplayDevice>& display) const REQUIRES(mStateLock) {
         return display->getDisplayToken() == mActiveDisplayToken;
     }
 
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index dc2aa58..b93d127 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -21,6 +21,7 @@
 
 namespace android {
 
+using gui::DisplayInfo;
 using gui::IWindowInfosListener;
 using gui::WindowInfo;
 
@@ -67,6 +68,7 @@
 }
 
 void WindowInfosListenerInvoker::windowInfosChanged(const std::vector<WindowInfo>& windowInfos,
+                                                    const std::vector<DisplayInfo>& displayInfos,
                                                     bool shouldSync) {
     std::unordered_set<sp<IWindowInfosListener>, ISurfaceComposer::SpHash<IWindowInfosListener>>
             windowInfosListeners;
@@ -81,7 +83,7 @@
     mCallbacksPending = windowInfosListeners.size();
 
     for (const auto& listener : windowInfosListeners) {
-        listener->onWindowInfosChanged(windowInfos,
+        listener->onWindowInfosChanged(windowInfos, displayInfos,
                                        shouldSync ? mWindowInfosReportedListener : nullptr);
     }
 }
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.h b/services/surfaceflinger/WindowInfosListenerInvoker.h
index 5e5796f..ecd797a 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -33,7 +33,8 @@
     void addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener);
     void removeWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener);
 
-    void windowInfosChanged(const std::vector<gui::WindowInfo>& windowInfos, bool shouldSync);
+    void windowInfosChanged(const std::vector<gui::WindowInfo>&,
+                            const std::vector<gui::DisplayInfo>&, bool shouldSync);
 
 protected:
     void binderDied(const wp<IBinder>& who) override;
diff --git a/services/surfaceflinger/tests/WindowInfosListener_test.cpp b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
index de116f2..0069111 100644
--- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp
+++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
@@ -22,6 +22,7 @@
 
 namespace android {
 using Transaction = SurfaceComposerClient::Transaction;
+using gui::DisplayInfo;
 using gui::WindowInfo;
 
 class WindowInfosListenerTest : public ::testing::Test {
@@ -40,7 +41,8 @@
 
     struct SyncWindowInfosListener : public gui::WindowInfosListener {
     public:
-        void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos) override {
+        void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
+                                  const std::vector<DisplayInfo>&) override {
             windowInfosPromise.set_value(windowInfos);
         }
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
index a6bfde7..a8e3e5e 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
@@ -175,7 +175,6 @@
     RefreshRate mExpected30Config = {mConfig30, RefreshRate::ConstructorTag(0)};
     RefreshRate mExpected120Config = {mConfig120, RefreshRate::ConstructorTag(0)};
 
-private:
     DisplayModePtr createDisplayMode(DisplayModeId modeId, int32_t group, int64_t vsyncPeriod,
                                      ui::Size resolution = ui::Size());
 };
@@ -349,10 +348,19 @@
     EXPECT_EQ(mExpected90Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 
-    ASSERT_GE(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
-              0);
+    ASSERT_EQ(refreshRateConfigs->setDisplayManagerPolicy({HWC_CONFIG_ID_60, {Fps(60), Fps(60)}}),
+              NO_ERROR);
     EXPECT_EQ(mExpected60Config,
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
+
+    // We select max even when this will cause a non-seamless switch.
+    refreshRateConfigs = std::make_unique<RefreshRateConfigs>(m60_90DeviceWithDifferentGroups,
+                                                              /*currentConfigId=*/HWC_CONFIG_ID_60);
+    ASSERT_EQ(refreshRateConfigs->setDisplayManagerPolicy(
+                      {HWC_CONFIG_ID_90, /*allowGroupSwitching*/ true, {Fps(0), Fps(90)}}),
+              NO_ERROR);
+    EXPECT_EQ(mExpected90DifferentGroupConfig,
+              refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
 
 TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) {
@@ -2170,6 +2178,44 @@
               refreshRateConfigs->getBestRefreshRate(layers, {.touch = false, .idle = false}));
 }
 
+// b/190578904
+TEST_F(RefreshRateConfigsTest, getBestRefreshRate_deviceWithCloseRefreshRates) {
+    constexpr int kMinRefreshRate = 10;
+    constexpr int kMaxRefreshRate = 240;
+
+    DisplayModes displayModes;
+    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+        constexpr int32_t kGroup = 0;
+        const auto refreshRate = Fps(static_cast<float>(fps));
+        displayModes.push_back(
+                createDisplayMode(DisplayModeId(fps), kGroup, refreshRate.getPeriodNsecs()));
+    }
+
+    const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false};
+    auto refreshRateConfigs =
+            std::make_unique<RefreshRateConfigs>(displayModes,
+                                                 /*currentConfigId=*/displayModes[0]->getId());
+
+    auto layers = std::vector<LayerRequirement>{LayerRequirement{.weight = 1.0f}};
+    const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
+        layers[0].desiredRefreshRate = fps;
+        layers[0].vote = vote;
+        EXPECT_EQ(fps.getIntValue(),
+                  refreshRateConfigs->getBestRefreshRate(layers, globalSignals)
+                          .getFps()
+                          .getIntValue())
+                << "Failed for " << RefreshRateConfigs::layerVoteTypeString(vote);
+    };
+
+    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+        const auto refreshRate = Fps(static_cast<float>(fps));
+        testRefreshRate(refreshRate, LayerVoteType::Heuristic);
+        testRefreshRate(refreshRate, LayerVoteType::ExplicitDefault);
+        testRefreshRate(refreshRate, LayerVoteType::ExplicitExactOrMultiple);
+        testRefreshRate(refreshRate, LayerVoteType::ExplicitExact);
+    }
+}
+
 TEST_F(RefreshRateConfigsTest, testComparisonOperator) {
     EXPECT_TRUE(mExpected60Config < mExpected90Config);
     EXPECT_FALSE(mExpected60Config < mExpected60Config);