Merge "Add comment clarifying ANGLE libs loading behaviors" into main
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 220fef6..d427ecf 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -170,6 +170,7 @@
 #define ALT_PSTORE_LAST_KMSG "/sys/fs/pstore/console-ramoops-0"
 #define BLK_DEV_SYS_DIR "/sys/block"
 
+#define AFLAGS "/system/bin/aflags"
 #define RECOVERY_DIR "/cache/recovery"
 #define RECOVERY_DATA_DIR "/data/misc/recovery"
 #define UPDATE_ENGINE_LOG_DIR "/data/misc/update_engine_log"
@@ -1792,6 +1793,10 @@
 
     RunCommand("ACONFIG FLAGS", {PRINT_FLAGS},
                CommandOptions::WithTimeout(10).Always().DropRoot().Build());
+    RunCommand("ACONFIG FLAGS DUMP", {AFLAGS, "list"},
+               CommandOptions::WithTimeout(10).Always().AsRootIfAvailable().Build());
+    RunCommand("WHICH ACONFIG FLAG STORAGE", {AFLAGS, "which-backing"},
+               CommandOptions::WithTimeout(10).Always().AsRootIfAvailable().Build());
 
     RunCommand("STORAGED IO INFO", {"storaged", "-u", "-p"});
 
@@ -3533,7 +3538,7 @@
             // the dumpstate's own activity which is irrelevant.
             RunCommand(
                 SERIALIZE_PERFETTO_TRACE_TASK, {"perfetto", "--save-for-bugreport"},
-                CommandOptions::WithTimeout(10).DropRoot().CloseAllFileDescriptorsOnExec().Build(),
+                CommandOptions::WithTimeout(30).DropRoot().CloseAllFileDescriptorsOnExec().Build(),
                 false, outFd);
             // MaybeAddSystemTraceToZip() will take care of copying the trace in the zip
             // file in the later stages.
diff --git a/data/etc/android.software.opengles.deqp.level-2023-03-01.xml b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml
index d0b594c..7aef9ec 100644
--- a/data/etc/android.software.opengles.deqp.level-2023-03-01.xml
+++ b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2021 The Android Open Source Project
+<!-- Copyright 2023 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.
diff --git a/data/etc/android.software.opengles.deqp.level-2024-03-01.xml b/data/etc/android.software.opengles.deqp.level-2024-03-01.xml
index 4eeed2a..3d8a178 100644
--- a/data/etc/android.software.opengles.deqp.level-2024-03-01.xml
+++ b/data/etc/android.software.opengles.deqp.level-2024-03-01.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2021 The Android Open Source Project
+<!-- Copyright 2024 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.
@@ -15,7 +15,7 @@
 -->
 
 <!-- This is the standard feature indicating that the device passes OpenGL ES
-     dEQP tests associated with date 2023-03-01 (0x07E70301). -->
+     dEQP tests associated with date 2024-03-01 (0x7e80301). -->
 <permissions>
     <feature name="android.software.opengles.deqp.level" version="132645633" />
 </permissions>
diff --git a/data/etc/android.software.opengles.deqp.level-2025-03-01.xml b/data/etc/android.software.opengles.deqp.level-2025-03-01.xml
new file mode 100644
index 0000000..e005356
--- /dev/null
+++ b/data/etc/android.software.opengles.deqp.level-2025-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes OpenGL ES
+     dEQP tests associated with date 2025-03-01 (0x7e90301). -->
+<permissions>
+    <feature name="android.software.opengles.deqp.level" version="132711169" />
+</permissions>
diff --git a/data/etc/android.software.opengles.deqp.level-latest.xml b/data/etc/android.software.opengles.deqp.level-latest.xml
index 62bb101..1ad35bc 100644
--- a/data/etc/android.software.opengles.deqp.level-latest.xml
+++ b/data/etc/android.software.opengles.deqp.level-latest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2023 The Android Open Source Project
+<!-- Copyright 2025 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.
@@ -17,5 +17,5 @@
 <!-- This is the standard feature indicating that the device passes OpenGL ES
      dEQP tests associated with the most recent level for this Android version. -->
 <permissions>
-    <feature name="android.software.opengles.deqp.level" version="132645633" />
+    <feature name="android.software.opengles.deqp.level" version="132711169" />
 </permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml
index 8b2b4c8..185edbf 100644
--- a/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml
+++ b/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2021 The Android Open Source Project
+<!-- Copyright 2024 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.
@@ -15,7 +15,7 @@
 -->
 
 <!-- This is the standard feature indicating that the device passes Vulkan dEQP
-     tests associated with date 2023-03-01 (0x07E70301). -->
+     tests associated with date 2024-03-01 (0x7e80301). -->
 <permissions>
     <feature name="android.software.vulkan.deqp.level" version="132645633" />
 </permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-2025-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2025-03-01.xml
new file mode 100644
index 0000000..b424667
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-2025-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes Vulkan dEQP
+     tests associated with date 2025-03-01 (0x7e90301). -->
+<permissions>
+    <feature name="android.software.vulkan.deqp.level" version="132711169" />
+</permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-latest.xml b/data/etc/android.software.vulkan.deqp.level-latest.xml
index 0fc12b3..4128a95 100644
--- a/data/etc/android.software.vulkan.deqp.level-latest.xml
+++ b/data/etc/android.software.vulkan.deqp.level-latest.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright 2023 The Android Open Source Project
+<!-- Copyright 2025 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.
@@ -17,5 +17,5 @@
 <!-- This is the standard feature indicating that the device passes Vulkan
      dEQP tests associated with the most recent level for this Android version. -->
 <permissions>
-    <feature name="android.software.vulkan.deqp.level" version="132645633" />
+    <feature name="android.software.vulkan.deqp.level" version="132711169" />
 </permissions>
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 82cacca..8d61e77 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -145,6 +145,9 @@
  * Buffers which are replaced or removed from the scene in the transaction invoking
  * this callback may be reused after this point.
  *
+ * Starting with API level 36, prefer using \a ASurfaceTransaction_OnBufferRelease to listen
+ * to when a buffer is ready to be reused.
+ *
  * \param context Optional context provided by the client that is passed into
  * the callback.
  *
@@ -153,12 +156,9 @@
  *
  * THREADING
  * The transaction completed callback can be invoked on any thread.
- *
- * Available since API level 29.
  */
 typedef void (*ASurfaceTransaction_OnComplete)(void* _Null_unspecified context,
-                                               ASurfaceTransactionStats* _Nonnull stats)
-        __INTRODUCED_IN(29);
+                                               ASurfaceTransactionStats* _Nonnull stats);
 
 /**
  * The ASurfaceTransaction_OnCommit callback is invoked when transaction is applied and the updates
@@ -182,12 +182,36 @@
  *
  * THREADING
  * The transaction committed callback can be invoked on any thread.
- *
- * Available since API level 31.
  */
 typedef void (*ASurfaceTransaction_OnCommit)(void* _Null_unspecified context,
-                                             ASurfaceTransactionStats* _Nonnull stats)
-        __INTRODUCED_IN(31);
+                                             ASurfaceTransactionStats* _Nonnull stats);
+
+/**
+ * The ASurfaceTransaction_OnBufferRelease callback is invoked when a buffer that was passed in
+ * ASurfaceTransaction_setBuffer is ready to be reused.
+ *
+ * This callback is guaranteed to be invoked if ASurfaceTransaction_setBuffer is called with a non
+ * null buffer. If the buffer in the transaction is replaced via another call to
+ * ASurfaceTransaction_setBuffer, the callback will be invoked immediately. Otherwise the callback
+ * will be invoked before the ASurfaceTransaction_OnComplete callback after the buffer was
+ * presented.
+ *
+ * If this callback is set, caller should not release the buffer using the
+ * ASurfaceTransaction_OnComplete.
+ *
+ * \param context Optional context provided by the client that is passed into the callback.
+ *
+ * \param release_fence_fd Returns the fence file descriptor used to signal the release of buffer
+ * associated with this callback. If this fence is valid (>=0), the buffer has not yet been released
+ * and the fence will signal when the buffer has been released. If the fence is -1 , the buffer is
+ * already released. The recipient of the callback takes ownership of the fence fd and is
+ * responsible for closing it.
+ *
+ * THREADING
+ * The callback can be invoked on any thread.
+ */
+typedef void (*ASurfaceTransaction_OnBufferRelease)(void* _Null_unspecified context,
+                                                    int release_fence_fd);
 
 /**
  * Returns the timestamp of when the frame was latched by the framework. Once a frame is
@@ -251,7 +275,7 @@
 /**
  * The returns the fence used to signal the release of the PREVIOUS buffer set on
  * this surface. If this fence is valid (>=0), the PREVIOUS buffer has not yet been released and the
- * fence will signal when the PREVIOUS buffer has been released. If the fence is -1 , the PREVIOUS
+ * fence will signal when the PREVIOUS buffer has been released. If the fence is -1, the PREVIOUS
  * buffer is already released. The recipient of the callback takes ownership of the
  * previousReleaseFenceFd and is responsible for closing it.
  *
@@ -353,6 +377,9 @@
  * Note that the buffer must be allocated with AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE
  * as the surface control might be composited using the GPU.
  *
+ * Starting with API level 36, prefer using \a ASurfaceTransaction_setBufferWithRelease to
+ * set a buffer and a callback which will be invoked when the buffer is ready to be reused.
+ *
  * Available since API level 29.
  */
 void ASurfaceTransaction_setBuffer(ASurfaceTransaction* _Nonnull transaction,
@@ -361,6 +388,29 @@
         __INTRODUCED_IN(29);
 
 /**
+ * Updates the AHardwareBuffer displayed for \a surface_control. If not -1, the
+ * acquire_fence_fd should be a file descriptor that is signaled when all pending work
+ * for the buffer is complete and the buffer can be safely read.
+ *
+ * The frameworks takes ownership of the \a acquire_fence_fd passed and is responsible
+ * for closing it.
+ *
+ * Note that the buffer must be allocated with AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE
+ * as the surface control might be composited using the GPU.
+ *
+ * When the buffer is ready to be reused, the ASurfaceTransaction_OnBufferRelease
+ * callback will be invoked. If the buffer is null, the callback will not be invoked.
+ *
+ * Available since API level 36.
+ */
+void ASurfaceTransaction_setBufferWithRelease(ASurfaceTransaction* _Nonnull transaction,
+                                              ASurfaceControl* _Nonnull surface_control,
+                                              AHardwareBuffer* _Nonnull buffer,
+                                              int acquire_fence_fd, void* _Null_unspecified context,
+                                              ASurfaceTransaction_OnBufferRelease _Nonnull func)
+        __INTRODUCED_IN(36);
+
+/**
  * Updates the color for \a surface_control.  This will make the background color for the
  * ASurfaceControl visible in transparent regions of the surface.  Colors \a r, \a g,
  * and \a b must be within the range that is valid for \a dataspace.  \a dataspace and \a alpha
diff --git a/include/ftl/non_null.h b/include/ftl/non_null.h
index 4a5d8bf..e684288 100644
--- a/include/ftl/non_null.h
+++ b/include/ftl/non_null.h
@@ -68,6 +68,15 @@
   constexpr NonNull(const NonNull&) = default;
   constexpr NonNull& operator=(const NonNull&) = default;
 
+  template <typename U, typename = std::enable_if_t<std::is_convertible_v<U, Pointer>>>
+  constexpr NonNull(const NonNull<U>& other) : pointer_(other.get()) {}
+
+  template <typename U, typename = std::enable_if_t<std::is_convertible_v<U, Pointer>>>
+  constexpr NonNull& operator=(const NonNull<U>& other) {
+    pointer_ = other.get();
+    return *this;
+  }
+
   [[nodiscard]] constexpr const Pointer& get() const { return pointer_; }
   [[nodiscard]] constexpr explicit operator const Pointer&() const { return get(); }
 
diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h
index 358a191..228347d 100644
--- a/include/input/InputConsumerNoResampling.h
+++ b/include/input/InputConsumerNoResampling.h
@@ -16,8 +16,13 @@
 
 #pragma once
 
+#include <functional>
+#include <map>
+#include <memory>
+#include <optional>
+
+#include <input/Input.h>
 #include <input/InputTransport.h>
-#include <input/LooperInterface.h>
 #include <input/Resampler.h>
 #include <utils/Looper.h>
 
@@ -36,7 +41,7 @@
     /**
      * When you receive this callback, you must (eventually) call "consumeBatchedInputEvents".
      * If you don't want batching, then call "consumeBatchedInputEvents" immediately with
-     * std::nullopt frameTime to receive the pending motion event(s).
+     * std::nullopt requestedFrameTime to receive the pending motion event(s).
      * @param pendingBatchSource the source of the pending batch.
      */
     virtual void onBatchedInputEventPending(int32_t pendingBatchSource) = 0;
@@ -67,26 +72,17 @@
 class InputConsumerNoResampling final {
 public:
     /**
-     * This constructor is exclusively for test code. Any real use of InputConsumerNoResampling must
-     * use the constructor that takes an sp<Looper> parameter instead of
-     * std::shared_ptr<LooperInterface>.
-     */
-    explicit InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
-                                       std::shared_ptr<LooperInterface> looper,
-                                       InputConsumerCallbacks& callbacks,
-                                       std::unique_ptr<Resampler> resampler);
-
-    /**
      * @param callbacks are used to interact with InputConsumerNoResampling. They're called whenever
      * the event is ready to consume.
      * @param looper needs to be sp and not shared_ptr because it inherits from
      * RefBase
-     * @param resampler the resampling strategy to use. If null, no resampling will be
-     * performed.
+     * @param resamplerCreator callable that returns the resampling strategy to be used. If null, no
+     * resampling will be performed. resamplerCreator must never return nullptr.
      */
-    explicit InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
-                                       sp<Looper> looper, InputConsumerCallbacks& callbacks,
-                                       std::unique_ptr<Resampler> resampler);
+    explicit InputConsumerNoResampling(
+            const std::shared_ptr<InputChannel>& channel, sp<Looper> looper,
+            InputConsumerCallbacks& callbacks,
+            std::function<std::unique_ptr<Resampler>()> resamplerCreator);
 
     ~InputConsumerNoResampling();
 
@@ -96,15 +92,17 @@
     void finishInputEvent(uint32_t seq, bool handled);
     void reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime);
     /**
-     * If you want to consume all events immediately (disable batching), the you still must call
-     * this. For frameTime, use a std::nullopt.
-     * @param frameTime the time up to which consume the events. When there's double (or triple)
-     * buffering, you may want to not consume all events currently available, because you could be
-     * still working on an older frame, but there could already have been events that arrived that
-     * are more recent.
+     * If you want to consume all events immediately (disable batching), then you still must call
+     * this. For requestedFrameTime, use a std::nullopt. It is not guaranteed that the consumption
+     * will occur at requestedFrameTime. The resampling strategy may modify it.
+     * @param requestedFrameTime the time up to which consume the events. When there's double (or
+     * triple) buffering, you may want to not consume all events currently available, because you
+     * could be still working on an older frame, but there could already have been events that
+     * arrived that are more recent.
      * @return whether any events were actually consumed
      */
-    bool consumeBatchedInputEvents(std::optional<nsecs_t> frameTime);
+    bool consumeBatchedInputEvents(std::optional<nsecs_t> requestedFrameTime);
+
     /**
      * Returns true when there is *likely* a pending batch or a pending event in the channel.
      *
@@ -119,9 +117,15 @@
 
 private:
     std::shared_ptr<InputChannel> mChannel;
-    std::shared_ptr<LooperInterface> mLooper;
+    sp<Looper> mLooper;
     InputConsumerCallbacks& mCallbacks;
-    std::unique_ptr<Resampler> mResampler;
+    const std::function<std::unique_ptr<Resampler>()> mResamplerCreator;
+
+    /**
+     * A map to manage multidevice resampling. Each contained resampler is never null. This map is
+     * only modified by handleMessages.
+     */
+    std::map<DeviceId, std::unique_ptr<Resampler>> mResamplers;
 
     // Looper-related infrastructure
     /**
@@ -194,26 +198,42 @@
     /**
      * Batch messages that can be batched. When an unbatchable message is encountered, send it
      * to the InputConsumerCallbacks immediately. If there are batches remaining,
-     * notify InputConsumerCallbacks.
+     * notify InputConsumerCallbacks. If a resampleable ACTION_DOWN message is received, then a
+     * resampler is inserted for that deviceId in mResamplers. If a resampleable ACTION_UP or
+     * ACTION_CANCEL message is received then the resampler associated to that deviceId is erased
+     * from mResamplers.
      */
     void handleMessages(std::vector<InputMessage>&& messages);
     /**
      * Batched InputMessages, per deviceId.
      * For each device, we are storing a queue of batched messages. These will all be collapsed into
-     * a single MotionEvent (up to a specific frameTime) when the consumer calls
+     * a single MotionEvent (up to a specific requestedFrameTime) when the consumer calls
      * `consumeBatchedInputEvents`.
      */
     std::map<DeviceId, std::queue<InputMessage>> mBatches;
     /**
      * Creates a MotionEvent by consuming samples from the provided queue. If one message has
-     * eventTime > frameTime, all subsequent messages in the queue will be skipped. It is assumed
-     * that messages are queued in chronological order. In other words, only events that occurred
-     * prior to the requested frameTime will be consumed.
-     * @param frameTime the time up to which to consume events
+     * eventTime > adjustedFrameTime, all subsequent messages in the queue will be skipped. It is
+     * assumed that messages are queued in chronological order. In other words, only events that
+     * occurred prior to the adjustedFrameTime will be consumed.
+     * @param requestedFrameTime the time up to which to consume events.
      * @param messages the queue of messages to consume from
      */
     std::pair<std::unique_ptr<MotionEvent>, std::optional<uint32_t>> createBatchedMotionEvent(
-            const nsecs_t frameTime, std::queue<InputMessage>& messages);
+            const nsecs_t requestedFrameTime, std::queue<InputMessage>& messages);
+
+    /**
+     * Consumes the batched input events, optionally allowing the caller to specify a device id
+     * and/or requestedFrameTime threshold. It is not guaranteed that consumption will occur at
+     * requestedFrameTime.
+     * @param deviceId The device id from which to consume events. If std::nullopt, consumes events
+     * from any device id.
+     * @param requestedFrameTime The time up to which consume the events. If std::nullopt, consumes
+     * input events with any timestamp.
+     * @return Whether or not any events were consumed.
+     */
+    bool consumeBatchedInputEvents(std::optional<DeviceId> deviceId,
+                                   std::optional<nsecs_t> requestedFrameTime);
     /**
      * A map from a single sequence number to several sequence numbers. This is needed because of
      * batching. When batching is enabled, a single MotionEvent will contain several samples. Each
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 7d8c19e..1a48239 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -389,6 +389,7 @@
     CONFIGURATION = 0,     /* .idc file */
     KEY_LAYOUT = 1,        /* .kl file */
     KEY_CHARACTER_MAP = 2, /* .kcm file */
+    ftl_last = KEY_CHARACTER_MAP,
 };
 
 /*
diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h
index 5bd5070..1899a66 100644
--- a/include/input/InputEventBuilders.h
+++ b/include/input/InputEventBuilders.h
@@ -21,6 +21,7 @@
 #include <input/Input.h>
 #include <input/InputTransport.h>
 #include <ui/LogicalDisplayId.h>
+#include <ui/Transform.h>
 #include <utils/Timers.h> // for nsecs_t, systemTime
 
 #include <vector>
@@ -94,16 +95,81 @@
         return *this;
     }
 
+    InputMessageBuilder& hmac(const std::array<uint8_t, 32>& hmac) {
+        mHmac = hmac;
+        return *this;
+    }
+
     InputMessageBuilder& action(int32_t action) {
         mAction = action;
         return *this;
     }
 
+    InputMessageBuilder& actionButton(int32_t actionButton) {
+        mActionButton = actionButton;
+        return *this;
+    }
+
+    InputMessageBuilder& flags(int32_t flags) {
+        mFlags = flags;
+        return *this;
+    }
+
+    InputMessageBuilder& metaState(int32_t metaState) {
+        mMetaState = metaState;
+        return *this;
+    }
+
+    InputMessageBuilder& buttonState(int32_t buttonState) {
+        mButtonState = buttonState;
+        return *this;
+    }
+
+    InputMessageBuilder& classification(MotionClassification classification) {
+        mClassification = classification;
+        return *this;
+    }
+
+    InputMessageBuilder& edgeFlags(int32_t edgeFlags) {
+        mEdgeFlags = edgeFlags;
+        return *this;
+    }
+
     InputMessageBuilder& downTime(nsecs_t downTime) {
         mDownTime = downTime;
         return *this;
     }
 
+    InputMessageBuilder& transform(const ui::Transform& transform) {
+        mTransform = transform;
+        return *this;
+    }
+
+    InputMessageBuilder& xPrecision(float xPrecision) {
+        mXPrecision = xPrecision;
+        return *this;
+    }
+
+    InputMessageBuilder& yPrecision(float yPrecision) {
+        mYPrecision = yPrecision;
+        return *this;
+    }
+
+    InputMessageBuilder& xCursorPosition(float xCursorPosition) {
+        mXCursorPosition = xCursorPosition;
+        return *this;
+    }
+
+    InputMessageBuilder& yCursorPosition(float yCursorPosition) {
+        mYCursorPosition = yCursorPosition;
+        return *this;
+    }
+
+    InputMessageBuilder& rawTransform(const ui::Transform& rawTransform) {
+        mRawTransform = rawTransform;
+        return *this;
+    }
+
     InputMessageBuilder& pointer(PointerBuilder pointerBuilder) {
         mPointers.push_back(pointerBuilder);
         return *this;
@@ -121,8 +187,30 @@
         message.body.motion.deviceId = mDeviceId;
         message.body.motion.source = mSource;
         message.body.motion.displayId = mDisplayId.val();
+        message.body.motion.hmac = std::move(mHmac);
         message.body.motion.action = mAction;
+        message.body.motion.actionButton = mActionButton;
+        message.body.motion.flags = mFlags;
+        message.body.motion.metaState = mMetaState;
+        message.body.motion.buttonState = mButtonState;
+        message.body.motion.edgeFlags = mEdgeFlags;
         message.body.motion.downTime = mDownTime;
+        message.body.motion.dsdx = mTransform.dsdx();
+        message.body.motion.dtdx = mTransform.dtdx();
+        message.body.motion.dtdy = mTransform.dtdy();
+        message.body.motion.dsdy = mTransform.dsdy();
+        message.body.motion.tx = mTransform.ty();
+        message.body.motion.ty = mTransform.tx();
+        message.body.motion.xPrecision = mXPrecision;
+        message.body.motion.yPrecision = mYPrecision;
+        message.body.motion.xCursorPosition = mXCursorPosition;
+        message.body.motion.yCursorPosition = mYCursorPosition;
+        message.body.motion.dsdxRaw = mRawTransform.dsdx();
+        message.body.motion.dtdxRaw = mRawTransform.dtdx();
+        message.body.motion.dtdyRaw = mRawTransform.dtdy();
+        message.body.motion.dsdyRaw = mRawTransform.dsdy();
+        message.body.motion.txRaw = mRawTransform.ty();
+        message.body.motion.tyRaw = mRawTransform.tx();
 
         for (size_t i = 0; i < mPointers.size(); ++i) {
             message.body.motion.pointers[i].properties = mPointers[i].buildProperties();
@@ -140,9 +228,21 @@
     DeviceId mDeviceId{DEFAULT_DEVICE_ID};
     int32_t mSource{AINPUT_SOURCE_TOUCHSCREEN};
     ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT};
+    std::array<uint8_t, 32> mHmac{INVALID_HMAC};
     int32_t mAction{AMOTION_EVENT_ACTION_MOVE};
+    int32_t mActionButton{0};
+    int32_t mFlags{0};
+    int32_t mMetaState{AMETA_NONE};
+    int32_t mButtonState{0};
+    MotionClassification mClassification{MotionClassification::NONE};
+    int32_t mEdgeFlags{0};
     nsecs_t mDownTime{mEventTime};
-
+    ui::Transform mTransform{};
+    float mXPrecision{1.0f};
+    float mYPrecision{1.0f};
+    float mXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+    float mYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
+    ui::Transform mRawTransform{};
     std::vector<PointerBuilder> mPointers;
 };
 
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index 92d5ec4..67b37b1 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -126,9 +126,9 @@
     bool getEvents(int32_t deviceId, const char16_t* chars, size_t numChars,
             Vector<KeyEvent>& outEvents) const;
 
-    /* Maps an Android key code to another Android key code. This mapping is applied after scanCode
-     * and usageCodes are mapped to corresponding Android Keycode */
-    void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode);
+    /* Maps some Android key code to another Android key code. This mapping is applied after
+     * scanCode and usageCodes are mapped to corresponding Android Keycode */
+    void setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping);
 
     /* Maps a scan code and usage code to a key code, in case this key map overrides
      * the mapping in some way. */
diff --git a/include/input/LooperInterface.h b/include/input/LooperInterface.h
deleted file mode 100644
index 2d6719c..0000000
--- a/include/input/LooperInterface.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/**
- * Copyright 2024 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 <utils/Looper.h>
-#include <utils/StrongPointer.h>
-
-namespace android {
-
-/**
- * LooperInterface allows the use of TestLooper in InputConsumerNoResampling without reassigning to
- * Looper. LooperInterface is needed to control how InputConsumerNoResampling consumes and batches
- * InputMessages.
- */
-class LooperInterface {
-public:
-    virtual ~LooperInterface() = default;
-
-    virtual int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback,
-                      void* data) = 0;
-    virtual int removeFd(int fd) = 0;
-
-    virtual sp<Looper> getLooper() const = 0;
-};
-} // namespace android
diff --git a/include/input/Resampler.h b/include/input/Resampler.h
index 2892137..4aaeddd 100644
--- a/include/input/Resampler.h
+++ b/include/input/Resampler.h
@@ -35,9 +35,9 @@
     virtual ~Resampler() = default;
 
     /**
-     * Tries to resample motionEvent at resampleTime. The provided resampleTime must be greater than
+     * Tries to resample motionEvent at frameTime. The provided frameTime must be greater than
      * the latest sample time of motionEvent. It is not guaranteed that resampling occurs at
-     * resampleTime. Interpolation may occur is futureSample is available. Otherwise, motionEvent
+     * frameTime. Interpolation may occur is futureSample is available. Otherwise, motionEvent
      * may be resampled by another method, or not resampled at all. Furthermore, it is the
      * implementer's responsibility to guarantee the following:
      * - If resampling occurs, a single additional sample should be added to motionEvent. That is,
@@ -45,15 +45,21 @@
      * samples by the end of the resampling. No other field of motionEvent should be modified.
      * - If resampling does not occur, then motionEvent must not be modified in any way.
      */
-    virtual void resampleMotionEvent(std::chrono::nanoseconds resampleTime,
-                                     MotionEvent& motionEvent,
+    virtual void resampleMotionEvent(std::chrono::nanoseconds frameTime, MotionEvent& motionEvent,
                                      const InputMessage* futureSample) = 0;
+
+    /**
+     * Returns resample latency. Resample latency is the time difference between frame time and
+     * resample time. More precisely, let frameTime and resampleTime be two timestamps, and
+     * frameTime > resampleTime. Resample latency is defined as frameTime - resampleTime.
+     */
+    virtual std::chrono::nanoseconds getResampleLatency() const = 0;
 };
 
 class LegacyResampler final : public Resampler {
 public:
     /**
-     * Tries to resample `motionEvent` at `resampleTime` by adding a resampled sample at the end of
+     * Tries to resample `motionEvent` at `frameTime` by adding a resampled sample at the end of
      * `motionEvent` with eventTime equal to `resampleTime` and pointer coordinates determined by
      * linear interpolation or linear extrapolation. An earlier `resampleTime` will be used if
      * extrapolation takes place and `resampleTime` is too far in the future. If `futureSample` is
@@ -61,9 +67,11 @@
      * data, LegacyResampler will extrapolate. Otherwise, no resampling takes place and
      * `motionEvent` is unmodified.
      */
-    void resampleMotionEvent(std::chrono::nanoseconds resampleTime, MotionEvent& motionEvent,
+    void resampleMotionEvent(std::chrono::nanoseconds frameTime, MotionEvent& motionEvent,
                              const InputMessage* futureSample) override;
 
+    std::chrono::nanoseconds getResampleLatency() const override;
+
 private:
     struct Pointer {
         PointerProperties properties;
@@ -84,12 +92,6 @@
     };
 
     /**
-     * Keeps track of the previous MotionEvent deviceId to enable comparison between the previous
-     * and the current deviceId.
-     */
-    std::optional<DeviceId> mPreviousDeviceId;
-
-    /**
      * Up to two latest samples from MotionEvent. Updated every time resampleMotionEvent is called.
      * Note: We store up to two samples in order to simplify the implementation. Although,
      * calculations are possible with only one previous sample.
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index 319716e..cbba711 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -40,6 +40,7 @@
 
 cc_library_headers {
     name: "libarect_headers",
+    host_supported: true,
     vendor_available: true,
     min_sdk_version: "29",
     // TODO(b/153609531): remove when no longer needed.
diff --git a/libs/binder/BackendUnifiedServiceManager.cpp b/libs/binder/BackendUnifiedServiceManager.cpp
index 5680798..52b485a 100644
--- a/libs/binder/BackendUnifiedServiceManager.cpp
+++ b/libs/binder/BackendUnifiedServiceManager.cpp
@@ -34,37 +34,47 @@
 using IAccessor = android::os::IAccessor;
 
 static const char* kStaticCachableList[] = {
+        // go/keep-sorted start
+        "accessibility",
+        "account",
         "activity",
-        "android.hardware.thermal.IThermal/default",
-        "android.hardware.power.IPower/default",
-        "android.frameworks.stats.IStats/default",
-        "android.system.suspend.ISystemSuspend/default",
+        "alarm",
+        "android.system.keystore2.IKeystoreService/default",
         "appops",
         "audio",
         "batterystats",
         "carrier_config",
         "connectivity",
+        "content",
         "content_capture",
         "device_policy",
         "display",
         "dropbox",
         "econtroller",
+        "graphicsstats",
+        "input",
+        "input_method",
         "isub",
+        "jobscheduler",
         "legacy_permission",
         "location",
         "media.extractor",
         "media.metrics",
         "media.player",
         "media.resource_manager",
+        "media_resource_monitor",
+        "mount",
         "netd_listener",
         "netstats",
         "network_management",
         "nfc",
+        "notification",
+        "package",
         "package_native",
         "performance_hint",
         "permission",
-        "permissionmgr",
         "permission_checker",
+        "permissionmgr",
         "phone",
         "platform_compat",
         "power",
@@ -76,9 +86,12 @@
         "time_detector",
         "trust",
         "uimode",
+        "user",
         "virtualdevice",
         "virtualdevice_native",
         "webviewupdate",
+        "window",
+        // go/keep-sorted end
 };
 
 bool BinderCacheWithInvalidation::isClientSideCachingEnabled(const std::string& serviceName) {
diff --git a/libs/binder/FdTrigger.cpp b/libs/binder/FdTrigger.cpp
index f0aa801..7263e23 100644
--- a/libs/binder/FdTrigger.cpp
+++ b/libs/binder/FdTrigger.cpp
@@ -108,7 +108,7 @@
 
     // POLLNVAL: invalid FD number, e.g. not opened.
     if (pfd[0].revents & POLLNVAL) {
-        ALOGE("Invalid FD number (%d) in FdTrigger (POLLNVAL)", pfd[0].fd);
+        LOG_ALWAYS_FATAL("Invalid FD number (%d) in FdTrigger (POLLNVAL)", pfd[0].fd);
         return BAD_VALUE;
     }
 
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 1d26d85..6698d0c 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -232,6 +232,15 @@
     return cmd;
 }
 
+static void printReturnCommandParcel(std::ostream& out, const Parcel& parcel) {
+    const void* cmds = parcel.data();
+    out << "\t" << HexDump(cmds, parcel.dataSize()) << "\n";
+    IF_LOG_COMMANDS() {
+        const void* end = parcel.data() + parcel.dataSize();
+        while (cmds < end) cmds = printReturnCommand(out, cmds);
+    }
+}
+
 static const void* printCommand(std::ostream& out, const void* _cmd) {
     static const size_t N = sizeof(kCommandStrings)/sizeof(kCommandStrings[0]);
     const int32_t* cmd = (const int32_t*)_cmd;
@@ -1235,13 +1244,15 @@
 
     if (err >= NO_ERROR) {
         if (bwr.write_consumed > 0) {
-            if (bwr.write_consumed < mOut.dataSize())
+            if (bwr.write_consumed < mOut.dataSize()) {
+                std::ostringstream logStream;
+                printReturnCommandParcel(logStream, mIn);
                 LOG_ALWAYS_FATAL("Driver did not consume write buffer. "
-                                 "err: %s consumed: %zu of %zu",
-                                 statusToString(err).c_str(),
-                                 (size_t)bwr.write_consumed,
-                                 mOut.dataSize());
-            else {
+                                 "err: %s consumed: %zu of %zu.\n"
+                                 "Return command: %s",
+                                 statusToString(err).c_str(), (size_t)bwr.write_consumed,
+                                 mOut.dataSize(), logStream.str().c_str());
+            } else {
                 mOut.setDataSize(0);
                 processPostWriteDerefs();
             }
@@ -1252,14 +1263,8 @@
         }
         IF_LOG_COMMANDS() {
             std::ostringstream logStream;
-            logStream << "Remaining data size: " << mOut.dataSize() << "\n";
-            logStream << "Received commands from driver: ";
-            const void* cmds = mIn.data();
-            const void* end = mIn.data() + mIn.dataSize();
-            logStream << "\t" << HexDump(cmds, mIn.dataSize()) << "\n";
-            while (cmds < end) cmds = printReturnCommand(logStream, cmds);
-            std::string message = logStream.str();
-            ALOGI("%s", message.c_str());
+            printReturnCommandParcel(logStream, mIn);
+            ALOGI("%s", logStream.str().c_str());
         }
         return NO_ERROR;
     }
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 88761d7..77b80ef 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -157,12 +157,21 @@
 
 class AccessorProvider {
 public:
-    AccessorProvider(RpcAccessorProvider&& provider) : mProvider(std::move(provider)) {}
-    sp<IBinder> provide(const String16& name) { return mProvider(name); }
+    AccessorProvider(std::set<std::string>&& instances, RpcAccessorProvider&& provider)
+          : mInstances(std::move(instances)), mProvider(std::move(provider)) {}
+    sp<IBinder> provide(const String16& name) {
+        if (mInstances.count(String8(name).c_str()) > 0) {
+            return mProvider(name);
+        } else {
+            return nullptr;
+        }
+    }
+    const std::set<std::string>& instances() { return mInstances; }
 
 private:
     AccessorProvider() = delete;
 
+    std::set<std::string> mInstances;
     RpcAccessorProvider mProvider;
 };
 
@@ -318,10 +327,32 @@
     return sp<CppBackendShim>::make(sp<BackendUnifiedServiceManager>::make(sm));
 }
 
-std::weak_ptr<AccessorProvider> addAccessorProvider(RpcAccessorProvider&& providerCallback) {
+// gAccessorProvidersMutex must be locked already
+static bool isInstanceProvidedLocked(const std::string& instance) {
+    return gAccessorProviders.end() !=
+            std::find_if(gAccessorProviders.begin(), gAccessorProviders.end(),
+                         [&instance](const AccessorProviderEntry& entry) {
+                             return entry.mProvider->instances().count(instance) > 0;
+                         });
+}
+
+std::weak_ptr<AccessorProvider> addAccessorProvider(std::set<std::string>&& instances,
+                                                    RpcAccessorProvider&& providerCallback) {
+    if (instances.empty()) {
+        ALOGE("Set of instances is empty! Need a non empty set of instances to provide for.");
+        return std::weak_ptr<AccessorProvider>();
+    }
     std::lock_guard<std::mutex> lock(gAccessorProvidersMutex);
+    for (const auto& instance : instances) {
+        if (isInstanceProvidedLocked(instance)) {
+            ALOGE("The instance %s is already provided for by a previously added "
+                  "RpcAccessorProvider.",
+                  instance.c_str());
+            return std::weak_ptr<AccessorProvider>();
+        }
+    }
     std::shared_ptr<AccessorProvider> provider =
-            std::make_shared<AccessorProvider>(std::move(providerCallback));
+            std::make_shared<AccessorProvider>(std::move(instances), std::move(providerCallback));
     std::weak_ptr<AccessorProvider> receipt = provider;
     gAccessorProviders.push_back(AccessorProviderEntry(std::move(provider)));
 
@@ -331,8 +362,9 @@
 status_t removeAccessorProvider(std::weak_ptr<AccessorProvider> wProvider) {
     std::shared_ptr<AccessorProvider> provider = wProvider.lock();
     if (provider == nullptr) {
-        ALOGE("The provider supplied to removeAccessorProvider has already been removed.");
-        return NAME_NOT_FOUND;
+        ALOGE("The provider supplied to removeAccessorProvider has already been removed or the "
+              "argument to this function was nullptr.");
+        return BAD_VALUE;
     }
     std::lock_guard<std::mutex> lock(gAccessorProvidersMutex);
     size_t sizeBefore = gAccessorProviders.size();
diff --git a/libs/binder/OS.h b/libs/binder/OS.h
index 04869a1..64b1fd4 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -27,6 +27,7 @@
 LIBBINDER_EXPORTED void trace_begin(uint64_t tag, const char* name);
 LIBBINDER_EXPORTED void trace_end(uint64_t tag);
 LIBBINDER_EXPORTED void trace_int(uint64_t tag, const char* name, int32_t value);
+LIBBINDER_EXPORTED uint64_t get_trace_enabled_tags();
 
 status_t setNonBlocking(borrowed_fd fd);
 
diff --git a/libs/binder/OS_android.cpp b/libs/binder/OS_android.cpp
index 893ee15..4e9230c 100644
--- a/libs/binder/OS_android.cpp
+++ b/libs/binder/OS_android.cpp
@@ -48,6 +48,10 @@
     atrace_int(tag, name, value);
 }
 
+uint64_t get_trace_enabled_tags() {
+    return atrace_enabled_tags;
+}
+
 } // namespace os
 
 // Legacy trace symbol. To be removed once all of downstream rebuilds.
diff --git a/libs/binder/OS_non_android_linux.cpp b/libs/binder/OS_non_android_linux.cpp
index 0c64eb6..6bba823 100644
--- a/libs/binder/OS_non_android_linux.cpp
+++ b/libs/binder/OS_non_android_linux.cpp
@@ -41,6 +41,10 @@
 
 void trace_int(uint64_t, const char*, int32_t) {}
 
+uint64_t get_trace_enabled_tags() {
+    return 0;
+}
+
 uint64_t GetThreadId() {
     return syscall(__NR_gettid);
 }
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 4b7af45..842ea77 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -180,7 +180,7 @@
         }
     }
 
-    ALOGD("Invalid object type 0x%08x", obj.hdr.type);
+    ALOGE("Invalid object type 0x%08x to acquire", obj.hdr.type);
 }
 
 static void release_object(const sp<ProcessState>& proc, const flat_binder_object& obj,
@@ -210,7 +210,7 @@
         }
     }
 
-    ALOGE("Invalid object type 0x%08x", obj.hdr.type);
+    ALOGE("Invalid object type 0x%08x to release", obj.hdr.type);
 }
 #endif // BINDER_WITH_KERNEL_IPC
 
@@ -683,7 +683,7 @@
     return err;
 }
 
-int Parcel::compareData(const Parcel& other) {
+int Parcel::compareData(const Parcel& other) const {
     size_t size = dataSize();
     if (size != other.dataSize()) {
         return size < other.dataSize() ? -1 : 1;
@@ -1150,31 +1150,6 @@
     return NO_ERROR;
 }
 
-status_t Parcel::writeUnpadded(const void* data, size_t len)
-{
-    if (len > INT32_MAX) {
-        // don't accept size_t values which may have come from an
-        // inadvertent conversion from a negative int.
-        return BAD_VALUE;
-    }
-
-    size_t end = mDataPos + len;
-    if (end < mDataPos) {
-        // integer overflow
-        return BAD_VALUE;
-    }
-
-    if (end <= mDataCapacity) {
-restart_write:
-        memcpy(mData+mDataPos, data, len);
-        return finishWrite(len);
-    }
-
-    status_t err = growData(len);
-    if (err == NO_ERROR) goto restart_write;
-    return err;
-}
-
 status_t Parcel::write(const void* data, size_t len)
 {
     if (len > INT32_MAX) {
@@ -1874,7 +1849,10 @@
                 if (mDataPos < kernelFields->mObjects[nextObject] + sizeof(flat_binder_object)) {
                     // Requested info overlaps with an object
                     if (!mServiceFuzzing) {
-                        ALOGE("Attempt to read from protected data in Parcel %p", this);
+                        ALOGE("Attempt to read or write from protected data in Parcel %p. pos: "
+                              "%zu, nextObject: %zu, object offset: %llu, object size: %zu",
+                              this, mDataPos, nextObject, kernelFields->mObjects[nextObject],
+                              sizeof(flat_binder_object));
                     }
                     return PERMISSION_DENIED;
                 }
@@ -2948,6 +2926,14 @@
         return BAD_VALUE;
     }
 
+    if (mDataPos > mDataSize) {
+        // b/370831157 - this case used to abort. We also don't expect mDataPos < mDataSize, but
+        // this would only waste a bit of memory, so it's okay.
+        ALOGE("growData only expected at the end of a Parcel. pos: %zu, size: %zu, capacity: %zu",
+              mDataPos, len, mDataCapacity);
+        return BAD_VALUE;
+    }
+
     if (len > SIZE_MAX - mDataSize) return NO_MEMORY; // overflow
     if (mDataSize + len > SIZE_MAX / 3) return NO_MEMORY; // overflow
     size_t newSize = ((mDataSize+len)*3)/2;
diff --git a/libs/binder/Status.cpp b/libs/binder/Status.cpp
index dba6587..9a98097 100644
--- a/libs/binder/Status.cpp
+++ b/libs/binder/Status.cpp
@@ -99,27 +99,28 @@
         return status;
     }
 
+    if (mException == EX_HAS_NOTED_APPOPS_REPLY_HEADER) {
+        status = skipUnusedHeader(parcel);
+        if (status != OK) {
+            setFromStatusT(status);
+            return status;
+        }
+        // Read next exception code.
+        status = parcel.readInt32(&mException);
+        if (status != OK) {
+            setFromStatusT(status);
+            return status;
+        }
+    }
+
     // Skip over fat response headers.  Not used (or propagated) in native code.
     if (mException == EX_HAS_REPLY_HEADER) {
-        // Note that the header size includes the 4 byte size field.
-        const size_t header_start = parcel.dataPosition();
-        // Get available size before reading more
-        const size_t header_avail = parcel.dataAvail();
-
-        int32_t header_size;
-        status = parcel.readInt32(&header_size);
+        status = skipUnusedHeader(parcel);
         if (status != OK) {
             setFromStatusT(status);
             return status;
         }
 
-        if (header_size < 0 || static_cast<size_t>(header_size) > header_avail) {
-            android_errorWriteLog(0x534e4554, "132650049");
-            setFromStatusT(UNKNOWN_ERROR);
-            return UNKNOWN_ERROR;
-        }
-
-        parcel.setDataPosition(header_start + header_size);
         // And fat response headers are currently only used when there are no
         // exceptions, so act like there was no error.
         mException = EX_NONE;
@@ -257,5 +258,28 @@
     return ret;
 }
 
+status_t Status::skipUnusedHeader(const Parcel& parcel) {
+    // Note that the header size includes the 4 byte size field.
+    const size_t header_start = parcel.dataPosition();
+    // Get available size before reading more
+    const size_t header_avail = parcel.dataAvail();
+
+    int32_t header_size;
+    status_t status = parcel.readInt32(&header_size);
+    ALOGD("Skip unused header. exception code: %d, start: %zu, size: %d.",
+           mException, header_start, header_size);
+    if (status != OK) {
+        return status;
+    }
+
+    if (header_size < 0 || static_cast<size_t>(header_size) > header_avail) {
+        android_errorWriteLog(0x534e4554, "132650049");
+        return UNKNOWN_ERROR;
+    }
+
+    parcel.setDataPosition(header_start + header_size);
+    return OK;
+}
+
 }  // namespace binder
 }  // namespace android
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 879f319..2b23276 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -24,6 +24,7 @@
 #include <utils/String16.h>
 #include <utils/Vector.h>
 #include <optional>
+#include <set>
 
 namespace android {
 
@@ -224,20 +225,36 @@
 typedef std::function<status_t(const String16& name, sockaddr* outAddr, socklen_t addrSize)>
         RpcSocketAddressProvider;
 
-typedef std::function<sp<IBinder>(const String16& name)> RpcAccessorProvider;
+/**
+ * This callback provides a way for clients to get access to remote services by
+ * providing an Accessor object from libbinder that can connect to the remote
+ * service over sockets.
+ *
+ * \param instance name of the service that the callback will provide an
+ *        Accessor for. The provided accessor will be used to set up a client
+ *        RPC connection in libbinder in order to return a binder for the
+ *        associated remote service.
+ *
+ * \return IBinder of the Accessor object that libbinder implements.
+ *         nullptr if the provider callback doesn't know how to reach the
+ *         service or doesn't want to provide access for any other reason.
+ */
+typedef std::function<sp<IBinder>(const String16& instance)> RpcAccessorProvider;
 
 class AccessorProvider;
 
 /**
- * Register an accessor provider for the service manager APIs.
+ * Register a RpcAccessorProvider for the service manager APIs.
  *
+ * \param instances that the RpcAccessorProvider knows about and can provide an
+ *        Accessor for.
  * \param provider callback that generates Accessors.
  *
  * \return A pointer used as a recept for the successful addition of the
  *         AccessorProvider. This is needed to unregister it later.
  */
 [[nodiscard]] LIBBINDER_EXPORTED std::weak_ptr<AccessorProvider> addAccessorProvider(
-        RpcAccessorProvider&& providerCallback);
+        std::set<std::string>&& instances, RpcAccessorProvider&& providerCallback);
 
 /**
  * Remove an accessor provider using the pointer provided by addAccessorProvider
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 5cc0830..ed4e211 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -92,7 +92,7 @@
 
     LIBBINDER_EXPORTED status_t appendFrom(const Parcel* parcel, size_t start, size_t len);
 
-    LIBBINDER_EXPORTED int compareData(const Parcel& other);
+    LIBBINDER_EXPORTED int compareData(const Parcel& other) const;
     LIBBINDER_EXPORTED status_t compareDataInRange(size_t thisOffset, const Parcel& other,
                                                    size_t otherOffset, size_t length,
                                                    int* result) const;
@@ -172,7 +172,6 @@
 
     LIBBINDER_EXPORTED status_t write(const void* data, size_t len);
     LIBBINDER_EXPORTED void* writeInplace(size_t len);
-    LIBBINDER_EXPORTED status_t writeUnpadded(const void* data, size_t len);
     LIBBINDER_EXPORTED status_t writeInt32(int32_t val);
     LIBBINDER_EXPORTED status_t writeUint32(uint32_t val);
     LIBBINDER_EXPORTED status_t writeInt64(int64_t val);
@@ -637,9 +636,6 @@
 
     LIBBINDER_EXPORTED const flat_binder_object* readObject(bool nullMetaData) const;
 
-    // Explicitly close all file descriptors in the parcel.
-    LIBBINDER_EXPORTED void closeFileDescriptors();
-
     // Debugging: get metrics on current allocations.
     LIBBINDER_EXPORTED static size_t getGlobalAllocSize();
     LIBBINDER_EXPORTED static size_t getGlobalAllocCount();
@@ -652,6 +648,9 @@
     LIBBINDER_EXPORTED void print(std::ostream& to, uint32_t flags = 0) const;
 
 private:
+    // Explicitly close all file descriptors in the parcel.
+    void closeFileDescriptors();
+
     // `objects` and `objectsSize` always 0 for RPC Parcels.
     typedef void (*release_func)(const uint8_t* data, size_t dataSize, const binder_size_t* objects,
                                  size_t objectsSize);
diff --git a/libs/binder/include/binder/Status.h b/libs/binder/include/binder/Status.h
index 49ccf7c..d69f662 100644
--- a/libs/binder/include/binder/Status.h
+++ b/libs/binder/include/binder/Status.h
@@ -67,6 +67,9 @@
         EX_SERVICE_SPECIFIC = -8,
         EX_PARCELABLE = -9,
 
+        // See android/os/Parcel.java. We need to handle this in native code.
+        EX_HAS_NOTED_APPOPS_REPLY_HEADER = -127,
+
         // This is special and Java specific; see Parcel.java.
         EX_HAS_REPLY_HEADER = -128,
         // This is special, and indicates to C++ binder proxies that the
@@ -150,6 +153,8 @@
     Status(int32_t exceptionCode, int32_t errorCode);
     Status(int32_t exceptionCode, int32_t errorCode, const String8& message);
 
+    status_t skipUnusedHeader(const Parcel& parcel);
+
     // If |mException| == EX_TRANSACTION_FAILED, generated code will return
     // |mErrorCode| as the result of the transaction rather than write an
     // exception to the reply parcel.
diff --git a/libs/binder/include/binder/Trace.h b/libs/binder/include/binder/Trace.h
index 2f450cb..a3e6c8a 100644
--- a/libs/binder/include/binder/Trace.h
+++ b/libs/binder/include/binder/Trace.h
@@ -42,6 +42,7 @@
 void trace_begin(uint64_t tag, const char* name);
 void trace_end(uint64_t tag);
 void trace_int(uint64_t tag, const char* name, int32_t value);
+uint64_t get_trace_enabled_tags();
 } // namespace os
 
 class LIBBINDER_EXPORTED ScopedTrace {
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index 5f45cb2..a7423b3 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -95,6 +95,7 @@
         "persistable_bundle.cpp",
         "process.cpp",
         "service_manager.cpp",
+        "binder_rpc.cpp",
     ],
 
     static_libs: [
diff --git a/libs/binder/ndk/binder_rpc.cpp b/libs/binder/ndk/binder_rpc.cpp
new file mode 100644
index 0000000..3c32a39
--- /dev/null
+++ b/libs/binder/ndk/binder_rpc.cpp
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2024 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 <android/binder_rpc.h>
+#include <arpa/inet.h>
+#include <binder/IServiceManager.h>
+#include <linux/vm_sockets.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <variant>
+
+#include "ibinder_internal.h"
+#include "status_internal.h"
+
+using ::android::defaultServiceManager;
+using ::android::IBinder;
+using ::android::IServiceManager;
+using ::android::OK;
+using ::android::sp;
+using ::android::status_t;
+using ::android::String16;
+using ::android::String8;
+using ::android::binder::Status;
+
+#define LOG_ACCESSOR_DEBUG(...)
+// #define LOG_ACCESSOR_DEBUG(...) ALOGW(__VA_ARGS__)
+
+struct ABinderRpc_ConnectionInfo {
+    std::variant<sockaddr_vm, sockaddr_un, sockaddr_in> addr;
+};
+
+struct ABinderRpc_Accessor final : public ::android::RefBase {
+    static ABinderRpc_Accessor* make(const char* instance, const sp<IBinder>& binder) {
+        LOG_ALWAYS_FATAL_IF(binder == nullptr, "ABinderRpc_Accessor requires a non-null binder");
+        status_t status = android::validateAccessor(String16(instance), binder);
+        if (status != OK) {
+            ALOGE("The given binder is not a valid IAccessor for %s. Status: %s", instance,
+                  android::statusToString(status).c_str());
+            return nullptr;
+        }
+        return new ABinderRpc_Accessor(binder);
+    }
+
+    sp<IBinder> asBinder() { return mAccessorBinder; }
+
+    ~ABinderRpc_Accessor() { LOG_ACCESSOR_DEBUG("ABinderRpc_Accessor dtor"); }
+
+   private:
+    ABinderRpc_Accessor(sp<IBinder> accessor) : mAccessorBinder(accessor) {}
+    ABinderRpc_Accessor() = delete;
+    sp<IBinder> mAccessorBinder;
+};
+
+struct ABinderRpc_AccessorProvider {
+   public:
+    static ABinderRpc_AccessorProvider* make(std::weak_ptr<android::AccessorProvider> cookie) {
+        if (cookie.expired()) {
+            ALOGE("Null AccessorProvider cookie from libbinder");
+            return nullptr;
+        }
+        return new ABinderRpc_AccessorProvider(cookie);
+    }
+    std::weak_ptr<android::AccessorProvider> mProviderCookie;
+
+   private:
+    ABinderRpc_AccessorProvider() = delete;
+
+    ABinderRpc_AccessorProvider(std::weak_ptr<android::AccessorProvider> provider)
+        : mProviderCookie(provider) {}
+};
+
+struct OnDeleteProviderHolder {
+    OnDeleteProviderHolder(void* data, ABinderRpc_AccessorProviderUserData_deleteCallback onDelete)
+        : mData(data), mOnDelete(onDelete) {}
+    ~OnDeleteProviderHolder() {
+        if (mOnDelete) {
+            mOnDelete(mData);
+        }
+    }
+    void* mData;
+    ABinderRpc_AccessorProviderUserData_deleteCallback mOnDelete;
+    // needs to be copy-able for std::function, but we will never copy it
+    OnDeleteProviderHolder(const OnDeleteProviderHolder&) {
+        LOG_ALWAYS_FATAL("This object can't be copied!");
+    }
+
+   private:
+    OnDeleteProviderHolder() = delete;
+};
+
+ABinderRpc_AccessorProvider* ABinderRpc_registerAccessorProvider(
+        ABinderRpc_AccessorProvider_getAccessorCallback provider, const char** instances,
+        size_t numInstances, void* data,
+        ABinderRpc_AccessorProviderUserData_deleteCallback onDelete) {
+    if (provider == nullptr) {
+        ALOGE("Null provider passed to ABinderRpc_registerAccessorProvider");
+        return nullptr;
+    }
+    if (data && onDelete == nullptr) {
+        ALOGE("If a non-null data ptr is passed to ABinderRpc_registerAccessorProvider, then a "
+              "ABinderRpc_AccessorProviderUserData_deleteCallback must also be passed to delete "
+              "the data object once the ABinderRpc_AccessorProvider is removed.");
+        return nullptr;
+    }
+    if (numInstances == 0 || instances == nullptr) {
+        ALOGE("No instances passed to ABinderRpc_registerAccessorProvider. numInstances: %zu",
+              numInstances);
+        return nullptr;
+    }
+    std::set<std::string> instanceStrings;
+    for (size_t i = 0; i < numInstances; i++) {
+        instanceStrings.emplace(instances[i]);
+    }
+    // call the onDelete when the last reference of this goes away (when the
+    // last reference to the generate std::function goes away).
+    std::shared_ptr<OnDeleteProviderHolder> onDeleteHolder =
+            std::make_shared<OnDeleteProviderHolder>(data, onDelete);
+    android::RpcAccessorProvider generate = [provider,
+                                             onDeleteHolder](const String16& name) -> sp<IBinder> {
+        ABinderRpc_Accessor* accessor = provider(String8(name).c_str(), onDeleteHolder->mData);
+        if (accessor == nullptr) {
+            ALOGE("The supplied ABinderRpc_AccessorProvider_getAccessorCallback returned nullptr");
+            return nullptr;
+        }
+        sp<IBinder> binder = accessor->asBinder();
+        ABinderRpc_Accessor_delete(accessor);
+        return binder;
+    };
+
+    std::weak_ptr<android::AccessorProvider> cookie =
+            android::addAccessorProvider(std::move(instanceStrings), std::move(generate));
+    return ABinderRpc_AccessorProvider::make(cookie);
+}
+
+void ABinderRpc_unregisterAccessorProvider(ABinderRpc_AccessorProvider* provider) {
+    if (provider == nullptr) {
+        LOG_ALWAYS_FATAL("Attempting to remove a null ABinderRpc_AccessorProvider");
+    }
+
+    status_t status = android::removeAccessorProvider(provider->mProviderCookie);
+    // There shouldn't be a way to get here because the caller won't have a
+    // ABinderRpc_AccessorProvider* without calling ABinderRpc_registerAccessorProvider
+    LOG_ALWAYS_FATAL_IF(status == android::BAD_VALUE, "Provider (%p) is not valid. Status: %s",
+                        provider, android::statusToString(status).c_str());
+    LOG_ALWAYS_FATAL_IF(status == android::NAME_NOT_FOUND,
+                        "Provider (%p) was already unregistered. Status: %s", provider,
+                        android::statusToString(status).c_str());
+    LOG_ALWAYS_FATAL_IF(status != OK,
+                        "Unknown error when attempting to unregister ABinderRpc_AccessorProvider "
+                        "(%p). Status: %s",
+                        provider, android::statusToString(status).c_str());
+
+    delete provider;
+}
+
+struct OnDeleteConnectionInfoHolder {
+    OnDeleteConnectionInfoHolder(void* data,
+                                 ABinderRpc_ConnectionInfoProviderUserData_delete onDelete)
+        : mData(data), mOnDelete(onDelete) {}
+    ~OnDeleteConnectionInfoHolder() {
+        if (mOnDelete) {
+            mOnDelete(mData);
+        }
+    }
+    void* mData;
+    ABinderRpc_ConnectionInfoProviderUserData_delete mOnDelete;
+    // needs to be copy-able for std::function, but we will never copy it
+    OnDeleteConnectionInfoHolder(const OnDeleteConnectionInfoHolder&) {
+        LOG_ALWAYS_FATAL("This object can't be copied!");
+    }
+
+   private:
+    OnDeleteConnectionInfoHolder() = delete;
+};
+
+ABinderRpc_Accessor* ABinderRpc_Accessor_new(
+        const char* instance, ABinderRpc_ConnectionInfoProvider provider, void* data,
+        ABinderRpc_ConnectionInfoProviderUserData_delete onDelete) {
+    if (instance == nullptr) {
+        ALOGE("Instance argument must be valid when calling ABinderRpc_Accessor_new");
+        return nullptr;
+    }
+    if (data && onDelete == nullptr) {
+        ALOGE("If a non-null data ptr is passed to ABinderRpc_Accessor_new, then a "
+              "ABinderRpc_ConnectionInfoProviderUserData_delete callback must also be passed to "
+              "delete "
+              "the data object once the ABinderRpc_Accessor is deleted.");
+        return nullptr;
+    }
+    std::shared_ptr<OnDeleteConnectionInfoHolder> onDeleteHolder =
+            std::make_shared<OnDeleteConnectionInfoHolder>(data, onDelete);
+    if (provider == nullptr) {
+        ALOGE("Can't create a new ABinderRpc_Accessor without a ABinderRpc_ConnectionInfoProvider "
+              "and it is "
+              "null");
+        return nullptr;
+    }
+    android::RpcSocketAddressProvider generate = [provider, onDeleteHolder](
+                                                         const String16& name, sockaddr* outAddr,
+                                                         size_t addrLen) -> status_t {
+        std::unique_ptr<ABinderRpc_ConnectionInfo> info(
+                provider(String8(name).c_str(), onDeleteHolder->mData));
+        if (info == nullptr) {
+            ALOGE("The supplied ABinderRpc_ConnectionInfoProvider returned nullptr");
+            return android::NAME_NOT_FOUND;
+        }
+        if (auto addr = std::get_if<sockaddr_vm>(&info->addr)) {
+            LOG_ALWAYS_FATAL_IF(addr->svm_family != AF_VSOCK,
+                                "ABinderRpc_ConnectionInfo invalid family");
+            if (addrLen < sizeof(sockaddr_vm)) {
+                ALOGE("Provided outAddr is too small! Expecting %zu, got %zu", sizeof(sockaddr_vm),
+                      addrLen);
+                return android::BAD_VALUE;
+            }
+            LOG_ACCESSOR_DEBUG(
+                    "Connection info provider found AF_VSOCK. family %d, port %d, cid %d",
+                    addr->svm_family, addr->svm_port, addr->svm_cid);
+            *reinterpret_cast<sockaddr_vm*>(outAddr) = *addr;
+        } else if (auto addr = std::get_if<sockaddr_un>(&info->addr)) {
+            LOG_ALWAYS_FATAL_IF(addr->sun_family != AF_UNIX,
+                                "ABinderRpc_ConnectionInfo invalid family");
+            if (addrLen < sizeof(sockaddr_un)) {
+                ALOGE("Provided outAddr is too small! Expecting %zu, got %zu", sizeof(sockaddr_un),
+                      addrLen);
+                return android::BAD_VALUE;
+            }
+            *reinterpret_cast<sockaddr_un*>(outAddr) = *addr;
+        } else if (auto addr = std::get_if<sockaddr_in>(&info->addr)) {
+            LOG_ALWAYS_FATAL_IF(addr->sin_family != AF_INET,
+                                "ABinderRpc_ConnectionInfo invalid family");
+            if (addrLen < sizeof(sockaddr_in)) {
+                ALOGE("Provided outAddr is too small! Expecting %zu, got %zu", sizeof(sockaddr_in),
+                      addrLen);
+                return android::BAD_VALUE;
+            }
+            *reinterpret_cast<sockaddr_in*>(outAddr) = *addr;
+        } else {
+            LOG_ALWAYS_FATAL(
+                    "Unsupported address family type when trying to get ARpcConnection info. A "
+                    "new variant was added to the ABinderRpc_ConnectionInfo and this needs to be "
+                    "updated.");
+        }
+        return OK;
+    };
+    sp<IBinder> accessorBinder = android::createAccessor(String16(instance), std::move(generate));
+    if (accessorBinder == nullptr) {
+        ALOGE("service manager did not get us an accessor");
+        return nullptr;
+    }
+    LOG_ACCESSOR_DEBUG("service manager found an accessor, so returning one now from _new");
+    return ABinderRpc_Accessor::make(instance, accessorBinder);
+}
+
+void ABinderRpc_Accessor_delete(ABinderRpc_Accessor* accessor) {
+    delete accessor;
+}
+
+AIBinder* ABinderRpc_Accessor_asBinder(ABinderRpc_Accessor* accessor) {
+    if (!accessor) {
+        ALOGE("ABinderRpc_Accessor argument is null.");
+        return nullptr;
+    }
+
+    sp<IBinder> binder = accessor->asBinder();
+    sp<AIBinder> aBinder = ABpBinder::lookupOrCreateFromBinder(binder);
+    AIBinder* ptr = aBinder.get();
+    if (ptr == nullptr) {
+        LOG_ALWAYS_FATAL("Failed to lookupOrCreateFromBinder");
+    }
+    ptr->incStrong(nullptr);
+    return ptr;
+}
+
+ABinderRpc_Accessor* ABinderRpc_Accessor_fromBinder(const char* instance, AIBinder* binder) {
+    if (!binder) {
+        ALOGE("binder argument is null");
+        return nullptr;
+    }
+    sp<IBinder> accessorBinder = binder->getBinder();
+    if (accessorBinder) {
+        return ABinderRpc_Accessor::make(instance, accessorBinder);
+    } else {
+        ALOGE("Attempting to get an ABinderRpc_Accessor for %s but AIBinder::getBinder returned "
+              "null",
+              instance);
+        return nullptr;
+    }
+}
+
+ABinderRpc_ConnectionInfo* ABinderRpc_ConnectionInfo_new(const sockaddr* addr, socklen_t len) {
+    if (addr == nullptr || len < 0 || static_cast<size_t>(len) < sizeof(sa_family_t)) {
+        ALOGE("Invalid arguments in ABinderRpc_Connection_new");
+        return nullptr;
+    }
+    // socklen_t was int32_t on 32-bit and uint32_t on 64 bit.
+    size_t socklen = len < 0 || static_cast<uintmax_t>(len) > SIZE_MAX ? 0 : len;
+
+    if (addr->sa_family == AF_VSOCK) {
+        if (len != sizeof(sockaddr_vm)) {
+            ALOGE("Incorrect size of %zu for AF_VSOCK sockaddr_vm. Expecting %zu", socklen,
+                  sizeof(sockaddr_vm));
+            return nullptr;
+        }
+        sockaddr_vm vm = *reinterpret_cast<const sockaddr_vm*>(addr);
+        LOG_ACCESSOR_DEBUG(
+                "ABinderRpc_ConnectionInfo_new found AF_VSOCK. family %d, port %d, cid %d",
+                vm.svm_family, vm.svm_port, vm.svm_cid);
+        return new ABinderRpc_ConnectionInfo(vm);
+    } else if (addr->sa_family == AF_UNIX) {
+        if (len != sizeof(sockaddr_un)) {
+            ALOGE("Incorrect size of %zu for AF_UNIX sockaddr_un. Expecting %zu", socklen,
+                  sizeof(sockaddr_un));
+            return nullptr;
+        }
+        sockaddr_un un = *reinterpret_cast<const sockaddr_un*>(addr);
+        LOG_ACCESSOR_DEBUG("ABinderRpc_ConnectionInfo_new found AF_UNIX. family %d, path %s",
+                           un.sun_family, un.sun_path);
+        return new ABinderRpc_ConnectionInfo(un);
+    } else if (addr->sa_family == AF_INET) {
+        if (len != sizeof(sockaddr_in)) {
+            ALOGE("Incorrect size of %zu for AF_INET sockaddr_in. Expecting %zu", socklen,
+                  sizeof(sockaddr_in));
+            return nullptr;
+        }
+        sockaddr_in in = *reinterpret_cast<const sockaddr_in*>(addr);
+        LOG_ACCESSOR_DEBUG(
+                "ABinderRpc_ConnectionInfo_new found AF_INET. family %d, address %s, port %d",
+                in.sin_family, inet_ntoa(in.sin_addr), ntohs(in.sin_port));
+        return new ABinderRpc_ConnectionInfo(in);
+    }
+
+    ALOGE("ABinderRpc APIs only support AF_VSOCK right now but the supplied sockaddr::sa_family "
+          "is: %hu",
+          addr->sa_family);
+    return nullptr;
+}
+
+void ABinderRpc_ConnectionInfo_delete(ABinderRpc_ConnectionInfo* info) {
+    delete info;
+}
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index af280d3..ff31dd0 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -18,8 +18,10 @@
 #include <android/binder_ibinder_platform.h>
 #include <android/binder_stability.h>
 #include <android/binder_status.h>
+#include <binder/Functional.h>
 #include <binder/IPCThreadState.h>
 #include <binder/IResultReceiver.h>
+#include <binder/Trace.h>
 #if __has_include(<private/android_filesystem_config.h>)
 #include <private/android_filesystem_config.h>
 #endif
@@ -40,6 +42,23 @@
 using ::android::String16;
 using ::android::String8;
 using ::android::wp;
+using ::android::binder::impl::make_scope_guard;
+using ::android::binder::impl::scope_guard;
+using ::android::binder::os::get_trace_enabled_tags;
+using ::android::binder::os::trace_begin;
+using ::android::binder::os::trace_end;
+
+// transaction codes for getInterfaceHash and getInterfaceVersion are defined
+// in file : system/tools/aidl/aidl.cpp
+static constexpr int kGetInterfaceVersionId = 0x00fffffe;
+static const char* kInterfaceVersion = "getInterfaceVersion";
+static constexpr int kGetInterfaceHashId = 0x00fffffd;
+static const char* kInterfaceHash = "getInterfaceHash";
+static const char* kNdkTrace = "AIDL::ndk::";
+static const char* kServerTrace = "::server";
+static const char* kClientTrace = "::client";
+static const char* kSeparator = "::";
+static const char* kUnknownCode = "Unknown_Transaction_Code:";
 
 namespace ABBinderTag {
 
@@ -90,6 +109,51 @@
     return sanitized;
 }
 
+const std::string getMethodName(const AIBinder_Class* clazz, transaction_code_t code) {
+    // TODO(b/150155678) - Move getInterfaceHash and getInterfaceVersion to libbinder and remove
+    // hardcoded cases.
+    if (code <= clazz->getTransactionCodeToFunctionLength() && code >= FIRST_CALL_TRANSACTION) {
+        // Codes have FIRST_CALL_TRANSACTION as added offset. Subtract to access function name
+        return clazz->getFunctionName(code);
+    } else if (code == kGetInterfaceVersionId) {
+        return kInterfaceVersion;
+    } else if (code == kGetInterfaceHashId) {
+        return kInterfaceHash;
+    }
+    return kUnknownCode + std::to_string(code);
+}
+
+const std::string getTraceSectionName(const AIBinder_Class* clazz, transaction_code_t code,
+                                      bool isServer) {
+    if (clazz == nullptr) {
+        ALOGE("class associated with binder is null. Class is needed to add trace with interface "
+              "name and function name");
+        return kNdkTrace;
+    }
+
+    const std::string descriptor = clazz->getInterfaceDescriptorUtf8();
+    const std::string methodName = getMethodName(clazz, code);
+
+    size_t traceSize =
+            strlen(kNdkTrace) + descriptor.size() + strlen(kSeparator) + methodName.size();
+    traceSize += isServer ? strlen(kServerTrace) : strlen(kClientTrace);
+
+    std::string trace;
+    // reserve to avoid repeated allocations
+    trace.reserve(traceSize);
+
+    trace += kNdkTrace;
+    trace += clazz->getInterfaceDescriptorUtf8();
+    trace += kSeparator;
+    trace += methodName;
+    trace += isServer ? kServerTrace : kClientTrace;
+
+    LOG_ALWAYS_FATAL_IF(trace.size() != traceSize, "Trace size mismatch. Expected %zu, got %zu",
+                        traceSize, trace.size());
+
+    return trace;
+}
+
 bool AIBinder::associateClass(const AIBinder_Class* clazz) {
     if (clazz == nullptr) return false;
 
@@ -203,6 +267,17 @@
 
 status_t ABBinder::onTransact(transaction_code_t code, const Parcel& data, Parcel* reply,
                               binder_flags_t flags) {
+    std::string sectionName;
+    bool tracingEnabled = get_trace_enabled_tags() & ATRACE_TAG_AIDL;
+    if (tracingEnabled) {
+        sectionName = getTraceSectionName(getClass(), code, true /*isServer*/);
+        trace_begin(ATRACE_TAG_AIDL, sectionName.c_str());
+    }
+
+    scope_guard guard = make_scope_guard([&]() {
+        if (tracingEnabled) trace_end(ATRACE_TAG_AIDL);
+    });
+
     if (isUserCommand(code)) {
         if (getClass()->writeHeader && !data.checkInterface(this)) {
             return STATUS_BAD_TYPE;
@@ -385,6 +460,31 @@
       mInterfaceDescriptor(interfaceDescriptor),
       mWideInterfaceDescriptor(interfaceDescriptor) {}
 
+bool AIBinder_Class::setTransactionCodeMap(const char** transactionCodeMap, size_t length) {
+    if (mTransactionCodeToFunction != nullptr) {
+        ALOGE("mTransactionCodeToFunction is already set!");
+        return false;
+    }
+    mTransactionCodeToFunction = transactionCodeMap;
+    mTransactionCodeToFunctionLength = length;
+    return true;
+}
+
+const char* AIBinder_Class::getFunctionName(transaction_code_t code) const {
+    if (mTransactionCodeToFunction == nullptr) {
+        ALOGE("mTransactionCodeToFunction is not set!");
+        return nullptr;
+    }
+
+    if (code < FIRST_CALL_TRANSACTION ||
+        code - FIRST_CALL_TRANSACTION >= mTransactionCodeToFunctionLength) {
+        ALOGE("Function name for requested code not found!");
+        return nullptr;
+    }
+
+    return mTransactionCodeToFunction[code - FIRST_CALL_TRANSACTION];
+}
+
 AIBinder_Class* AIBinder_Class_define(const char* interfaceDescriptor,
                                       AIBinder_Class_onCreate onCreate,
                                       AIBinder_Class_onDestroy onDestroy,
@@ -404,6 +504,24 @@
     clazz->onDump = onDump;
 }
 
+void AIBinder_Class_setTransactionCodeToFunctionNameMap(AIBinder_Class* clazz,
+                                                        const char** transactionCodeToFunction,
+                                                        size_t length) {
+    LOG_ALWAYS_FATAL_IF(clazz == nullptr || transactionCodeToFunction == nullptr,
+                        "Valid clazz and transactionCodeToFunction are needed to set code to "
+                        "function mapping.");
+    LOG_ALWAYS_FATAL_IF(!clazz->setTransactionCodeMap(transactionCodeToFunction, length),
+                        "Failed to set transactionCodeToFunction to clazz! Is "
+                        "transactionCodeToFunction already set?");
+}
+
+const char* AIBinder_Class_getFunctionName(AIBinder_Class* clazz, transaction_code_t code) {
+    LOG_ALWAYS_FATAL_IF(
+            clazz == nullptr,
+            "Valid clazz is needed to get function name for requested transaction code");
+    return clazz->getFunctionName(code);
+}
+
 void AIBinder_Class_disableInterfaceTokenHeader(AIBinder_Class* clazz) {
     LOG_ALWAYS_FATAL_IF(clazz == nullptr, "disableInterfaceTokenHeader requires non-null clazz");
 
@@ -734,6 +852,19 @@
 
 binder_status_t AIBinder_transact(AIBinder* binder, transaction_code_t code, AParcel** in,
                                   AParcel** out, binder_flags_t flags) {
+    const AIBinder_Class* clazz = binder ? binder->getClass() : nullptr;
+
+    std::string sectionName;
+    bool tracingEnabled = get_trace_enabled_tags() & ATRACE_TAG_AIDL;
+    if (tracingEnabled) {
+        sectionName = getTraceSectionName(clazz, code, false /*isServer*/);
+        trace_begin(ATRACE_TAG_AIDL, sectionName.c_str());
+    }
+
+    scope_guard guard = make_scope_guard([&]() {
+        if (tracingEnabled) trace_end(ATRACE_TAG_AIDL);
+    });
+
     if (in == nullptr) {
         ALOGE("%s: requires non-null in parameter", __func__);
         return STATUS_UNEXPECTED_NULL;
@@ -872,4 +1003,4 @@
                         "AIBinder_setInheritRt must be called on a local binder");
 
     localBinder->setInheritRt(inheritRt);
-}
+}
\ No newline at end of file
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index f5b738c..a93dc1f 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -132,6 +132,9 @@
 
     const ::android::String16& getInterfaceDescriptor() const { return mWideInterfaceDescriptor; }
     const char* getInterfaceDescriptorUtf8() const { return mInterfaceDescriptor.c_str(); }
+    bool setTransactionCodeMap(const char** transactionCodeMap, size_t transactionCodeMapSize);
+    const char* getFunctionName(transaction_code_t code) const;
+    size_t getTransactionCodeToFunctionLength() const { return mTransactionCodeToFunctionLength; }
 
     // whether a transaction header should be written
     bool writeHeader = true;
@@ -151,6 +154,10 @@
     // This must be a String16 since BBinder virtual getInterfaceDescriptor returns a reference to
     // one.
     const ::android::String16 mWideInterfaceDescriptor;
+    // Array which holds names of the functions
+    const char** mTransactionCodeToFunction = nullptr;
+    // length of mmTransactionCodeToFunctionLength array
+    size_t mTransactionCodeToFunctionLength = 0;
 };
 
 // Ownership is like this (when linked to death):
diff --git a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
index af56bf0..8296356 100644
--- a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
@@ -30,6 +30,17 @@
 #include <android/binder_auto_utils.h>
 #include <android/binder_ibinder.h>
 
+#if defined(__ANDROID_VENDOR__)
+#include <android/llndk-versioning.h>
+#elif !defined(API_LEVEL_AT_LEAST)
+#if defined(__BIONIC__)
+#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) \
+    (__builtin_available(android sdk_api_level, *))
+#else
+#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) (true)
+#endif  // __BIONIC__
+#endif  // __ANDROID_VENDOR__
+
 #if __has_include(<android/binder_shell.h>)
 #include <android/binder_shell.h>
 #define HAS_BINDER_SHELL_COMMAND
@@ -164,6 +175,10 @@
      * Helper method to create a class
      */
     static inline AIBinder_Class* defineClass(const char* interfaceDescriptor,
+                                              AIBinder_Class_onTransact onTransact,
+                                              const char** codeToFunction, size_t functionCount);
+
+    static inline AIBinder_Class* defineClass(const char* interfaceDescriptor,
                                               AIBinder_Class_onTransact onTransact);
 
    private:
@@ -256,6 +271,13 @@
 
 AIBinder_Class* ICInterface::defineClass(const char* interfaceDescriptor,
                                          AIBinder_Class_onTransact onTransact) {
+
+    return defineClass(interfaceDescriptor, onTransact, nullptr, 0);
+}
+
+AIBinder_Class* ICInterface::defineClass(const char* interfaceDescriptor,
+                                         AIBinder_Class_onTransact onTransact,
+                                         const char** codeToFunction, size_t functionCount) {
     AIBinder_Class* clazz = AIBinder_Class_define(interfaceDescriptor, ICInterfaceData::onCreate,
                                                   ICInterfaceData::onDestroy, onTransact);
     if (clazz == nullptr) {
@@ -274,6 +296,20 @@
         AIBinder_Class_setHandleShellCommand(clazz, ICInterfaceData::handleShellCommand);
     }
 #endif
+
+// TODO(b/368559337): fix versioning on product partition
+#if !defined(__ANDROID_PRODUCT__) && \
+        (defined(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__) || __ANDROID_API__ >= 36)
+    if API_LEVEL_AT_LEAST (36, 202504) {
+        if (codeToFunction != nullptr) {
+            AIBinder_Class_setTransactionCodeToFunctionNameMap(clazz, codeToFunction,
+                                                               functionCount);
+        }
+    }
+#else
+    (void)codeToFunction;
+    (void)functionCount;
+#endif  // defined(__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__) || __ANDROID_API__ >= 36
     return clazz;
 }
 
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index 72d255e..bd46c47 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -219,6 +219,50 @@
 void AIBinder_Class_setOnDump(AIBinder_Class* clazz, AIBinder_onDump onDump) __INTRODUCED_IN(29);
 
 /**
+ * Associates a mapping of transaction codes(transaction_code_t) to function names for the given
+ * class.
+ *
+ * Trace messages will use the provided names instead of bare integer codes when set. If not set by
+ * this function, trace messages will only be identified by the bare code. This should be called one
+ * time during clazz initialization. clazz is defined using AIBinder_Class_define and
+ * transactionCodeToFunctionMap should have same scope as clazz. Resetting/clearing the
+ * transactionCodeToFunctionMap is not allowed. Passing null for either clazz or
+ * transactionCodeToFunctionMap will abort.
+ *
+ * Available since API level 36.
+ *
+ * \param clazz class which should use this transaction to code function map.
+ * \param transactionCodeToFunctionMap array of function names indexed by transaction code.
+ * Transaction codes start from 1, functions with transaction code 1 will correspond to index 0 in
+ * transactionCodeToFunctionMap. When defining methods, transaction codes are expected to be
+ * contiguous, and this is required for maximum memory efficiency.
+ * You can use nullptr if certain transaction codes are not used. Lifetime should be same as clazz.
+ * \param length number of elements in the transactionCodeToFunctionMap
+ */
+void AIBinder_Class_setTransactionCodeToFunctionNameMap(AIBinder_Class* clazz,
+                                                        const char** transactionCodeToFunctionMap,
+                                                        size_t length) __INTRODUCED_IN(36);
+
+/**
+ * Get function name associated with transaction code for given class
+ *
+ * This function returns function name associated with provided transaction code for given class.
+ * AIBinder_Class_setTransactionCodeToFunctionNameMap should be called first to associate function
+ * to transaction code mapping.
+ *
+ * Available since API level 36.
+ *
+ * \param clazz class for which function name is requested
+ * \param transactionCode transaction_code_t for which function name is requested.
+ *
+ * \return function name in form of const char* if transaction code is valid for given class.
+ * The value returned is valid for the lifetime of clazz. if transaction code is invalid or
+ * transactionCodeToFunctionMap is not set, nullptr is returned.
+ */
+const char* AIBinder_Class_getFunctionName(AIBinder_Class* clazz, transaction_code_t code)
+        __INTRODUCED_IN(36);
+
+/**
  * This tells users of this class not to use a transaction header. By default, libbinder_ndk users
  * read/write transaction headers implicitly (in the SDK, this must be manually written by
  * android.os.Parcel#writeInterfaceToken, and it is read/checked with
diff --git a/libs/binder/ndk/include_ndk/android/persistable_bundle.h b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
index 5e0d4da..1d516ae 100644
--- a/libs/binder/ndk/include_ndk/android/persistable_bundle.h
+++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
@@ -17,13 +17,6 @@
 #pragma once
 
 #include <android/binder_parcel.h>
-#if defined(__ANDROID_VENDOR__)
-#include <android/llndk-versioning.h>
-#else
-#if !defined(__INTRODUCED_IN_LLNDK)
-#define __INTRODUCED_IN_LLNDK(level) __attribute__((annotate("introduced_in_llndk=" #level)))
-#endif
-#endif  // __ANDROID_VENDOR__
 #include <stdbool.h>
 #include <stdint.h>
 #include <sys/cdefs.h>
@@ -83,8 +76,7 @@
  *
  * \return Pointer to a new APersistableBundle
  */
-APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Create a new APersistableBundle based off an existing APersistableBundle.
@@ -98,7 +90,7 @@
  * \return Pointer to a new APersistableBundle
  */
 APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* _Nonnull pBundle)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Delete an APersistableBundle. This must always be called when finished using
@@ -109,7 +101,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_delete(APersistableBundle* _Nullable pBundle)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Check for equality of APersistableBundles.
@@ -123,7 +115,7 @@
  */
 bool APersistableBundle_isEqual(const APersistableBundle* _Nonnull lhs,
                                 const APersistableBundle* _Nonnull rhs)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Read an APersistableBundle from an AParcel.
@@ -142,7 +134,7 @@
  */
 binder_status_t APersistableBundle_readFromParcel(
         const AParcel* _Nonnull parcel, APersistableBundle* _Nullable* _Nonnull outPBundle)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Write an APersistableBundle to an AParcel.
@@ -162,7 +154,7 @@
  */
 binder_status_t APersistableBundle_writeToParcel(const APersistableBundle* _Nonnull pBundle,
                                                  AParcel* _Nonnull parcel)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get the size of an APersistableBundle. This is the number of mappings in the
@@ -175,7 +167,7 @@
  * \return number of mappings in the object
  */
 int32_t APersistableBundle_size(const APersistableBundle* _Nonnull pBundle)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Erase any entries added with the provided key.
@@ -188,7 +180,7 @@
  * \return number of entries erased. Either 0 or 1.
  */
 int32_t APersistableBundle_erase(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a boolean associated with the provided key.
@@ -201,8 +193,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_putBoolean(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                                   bool val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                   bool val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put an int32_t associated with the provided key.
@@ -215,8 +206,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_putInt(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                               int32_t val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                               int32_t val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put an int64_t associated with the provided key.
@@ -229,8 +219,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_putLong(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                                int64_t val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                int64_t val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a double associated with the provided key.
@@ -243,8 +232,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_putDouble(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                                  double val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                  double val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a string associated with the provided key.
@@ -258,8 +246,7 @@
  * Available since API level 202404.
  */
 void APersistableBundle_putString(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                                  const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                  const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a boolean vector associated with the provided key.
@@ -275,8 +262,7 @@
  */
 void APersistableBundle_putBooleanVector(APersistableBundle* _Nonnull pBundle,
                                          const char* _Nonnull key, const bool* _Nonnull vec,
-                                         int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                         int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put an int32_t vector associated with the provided key.
@@ -292,7 +278,7 @@
  */
 void APersistableBundle_putIntVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
                                      const int32_t* _Nonnull vec, int32_t num)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put an int64_t vector associated with the provided key.
@@ -308,8 +294,7 @@
  */
 void APersistableBundle_putLongVector(APersistableBundle* _Nonnull pBundle,
                                       const char* _Nonnull key, const int64_t* _Nonnull vec,
-                                      int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                      int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a double vector associated with the provided key.
@@ -325,8 +310,7 @@
  */
 void APersistableBundle_putDoubleVector(APersistableBundle* _Nonnull pBundle,
                                         const char* _Nonnull key, const double* _Nonnull vec,
-                                        int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                        int32_t num) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put a string vector associated with the provided key.
@@ -343,7 +327,7 @@
 void APersistableBundle_putStringVector(APersistableBundle* _Nonnull pBundle,
                                         const char* _Nonnull key,
                                         const char* _Nullable const* _Nullable vec, int32_t num)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Put an APersistableBundle associated with the provided key.
@@ -359,7 +343,7 @@
 void APersistableBundle_putPersistableBundle(APersistableBundle* _Nonnull pBundle,
                                              const char* _Nonnull key,
                                              const APersistableBundle* _Nonnull val)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a boolean associated with the provided key.
@@ -374,7 +358,7 @@
  */
 bool APersistableBundle_getBoolean(const APersistableBundle* _Nonnull pBundle,
                                    const char* _Nonnull key, bool* _Nonnull val)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get an int32_t associated with the provided key.
@@ -388,8 +372,7 @@
  * \return true if a value exists for the provided key
  */
 bool APersistableBundle_getInt(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
-                               int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                               int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get an int64_t associated with the provided key.
@@ -404,7 +387,7 @@
  */
 bool APersistableBundle_getLong(const APersistableBundle* _Nonnull pBundle,
                                 const char* _Nonnull key, int64_t* _Nonnull val)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a double associated with the provided key.
@@ -419,7 +402,7 @@
  */
 bool APersistableBundle_getDouble(const APersistableBundle* _Nonnull pBundle,
                                   const char* _Nonnull key, double* _Nonnull val)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a string associated with the provided key.
@@ -440,8 +423,7 @@
 int32_t APersistableBundle_getString(const APersistableBundle* _Nonnull pBundle,
                                      const char* _Nonnull key, char* _Nullable* _Nonnull val,
                                      APersistableBundle_stringAllocator stringAllocator,
-                                     void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                     void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a boolean vector associated with the provided key and place it in the
@@ -468,7 +450,7 @@
 int32_t APersistableBundle_getBooleanVector(const APersistableBundle* _Nonnull pBundle,
                                             const char* _Nonnull key, bool* _Nullable buffer,
                                             int32_t bufferSizeBytes)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get an int32_t vector associated with the provided key and place it in the
@@ -494,8 +476,7 @@
  */
 int32_t APersistableBundle_getIntVector(const APersistableBundle* _Nonnull pBundle,
                                         const char* _Nonnull key, int32_t* _Nullable buffer,
-                                        int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                        int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get an int64_t vector associated with the provided key and place it in the
@@ -521,8 +502,8 @@
  */
 int32_t APersistableBundle_getLongVector(const APersistableBundle* _Nonnull pBundle,
                                          const char* _Nonnull key, int64_t* _Nullable buffer,
-                                         int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                         int32_t bufferSizeBytes)
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a double vector associated with the provided key and place it in the
@@ -549,7 +530,7 @@
 int32_t APersistableBundle_getDoubleVector(const APersistableBundle* _Nonnull pBundle,
                                            const char* _Nonnull key, double* _Nullable buffer,
                                            int32_t bufferSizeBytes)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get a string vector associated with the provided key and place it in the
@@ -586,7 +567,7 @@
                                            int32_t bufferSizeBytes,
                                            APersistableBundle_stringAllocator stringAllocator,
                                            void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get an APersistableBundle* associated with the provided key.
@@ -605,7 +586,7 @@
 bool APersistableBundle_getPersistableBundle(const APersistableBundle* _Nonnull pBundle,
                                              const char* _Nonnull key,
                                              APersistableBundle* _Nullable* _Nonnull outBundle)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -638,7 +619,7 @@
                                           int32_t bufferSizeBytes,
                                           APersistableBundle_stringAllocator stringAllocator,
                                           void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -669,8 +650,7 @@
 int32_t APersistableBundle_getIntKeys(const APersistableBundle* _Nonnull pBundle,
                                       char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
                                       APersistableBundle_stringAllocator stringAllocator,
-                                      void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                      void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -701,8 +681,7 @@
 int32_t APersistableBundle_getLongKeys(const APersistableBundle* _Nonnull pBundle,
                                        char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
                                        APersistableBundle_stringAllocator stringAllocator,
-                                       void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                       void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -734,8 +713,8 @@
                                          char* _Nullable* _Nullable outKeys,
                                          int32_t bufferSizeBytes,
                                          APersistableBundle_stringAllocator stringAllocator,
-                                         void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                         void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -767,8 +746,8 @@
                                          char* _Nullable* _Nullable outKeys,
                                          int32_t bufferSizeBytes,
                                          APersistableBundle_stringAllocator stringAllocator,
-                                         void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                         void* _Nullable context)
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -801,7 +780,7 @@
                                                 int32_t bufferSizeBytes,
                                                 APersistableBundle_stringAllocator stringAllocator,
                                                 void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -834,7 +813,7 @@
                                             int32_t bufferSizeBytes,
                                             APersistableBundle_stringAllocator stringAllocator,
                                             void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -867,7 +846,7 @@
                                              int32_t bufferSizeBytes,
                                              APersistableBundle_stringAllocator stringAllocator,
                                              void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -899,7 +878,7 @@
                                                int32_t bufferSizeBytes,
                                                APersistableBundle_stringAllocator stringAllocator,
                                                void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -932,7 +911,7 @@
                                                int32_t bufferSizeBytes,
                                                APersistableBundle_stringAllocator stringAllocator,
                                                void* _Nullable context)
-        __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Get all of the keys associated with this specific type and place it in the
@@ -963,6 +942,6 @@
 int32_t APersistableBundle_getPersistableBundleKeys(
         const APersistableBundle* _Nonnull pBundle, char* _Nullable* _Nullable outKeys,
         int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator,
-        void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+        void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__);
 
 __END_DECLS
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index 41b30a0..cc4943b 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -18,7 +18,6 @@
 
 #include <android/binder_ibinder.h>
 #include <android/binder_status.h>
-#include <android/llndk-versioning.h>
 #include <sys/cdefs.h>
 
 __BEGIN_DECLS
@@ -257,8 +256,7 @@
  * \return the result of dlopen of the specified HAL
  */
 void* AServiceManager_openDeclaredPassthroughHal(const char* interface, const char* instance,
-                                                 int flag) __INTRODUCED_IN(__ANDROID_API_V__)
-        __INTRODUCED_IN_LLNDK(202404);
+                                                 int flag) __INTRODUCED_IN(__ANDROID_API_V__);
 
 /**
  * Prevent lazy services without client from shutting down their process
diff --git a/libs/binder/ndk/include_platform/android/binder_rpc.h b/libs/binder/ndk/include_platform/android/binder_rpc.h
new file mode 100644
index 0000000..7132554
--- /dev/null
+++ b/libs/binder/ndk/include_platform/android/binder_rpc.h
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2024 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 <android/binder_ibinder.h>
+#include <sys/socket.h>
+
+__BEGIN_DECLS
+
+/**
+ * @defgroup ABinderRpc Binder RPC
+ *
+ * This set of APIs makes it possible for a process to use the AServiceManager
+ * APIs to get binder objects for services that are available over sockets
+ * instead of the traditional kernel binder with the extra ServiceManager
+ * process.
+ *
+ * These APIs are used to supply libbinder with enough information to create
+ * and manage the socket connections underneath the ServiceManager APIs so the
+ * clients do not need to know the service implementation details or what
+ * transport they use for communication.
+ *
+ * @{
+ */
+
+/**
+ * This represents an IAccessor implementation from libbinder that is
+ * responsible for providing a pre-connected socket file descriptor for a
+ * specific service. The service is an RpcServer and the pre-connected socket is
+ * used to set up a client RpcSession underneath libbinder's IServiceManager APIs
+ * to provide the client with the service's binder for remote communication.
+ */
+typedef struct ABinderRpc_Accessor ABinderRpc_Accessor;
+
+/**
+ * This represents an object that supplies ABinderRpc_Accessors to libbinder
+ * when they are requested. They are requested any time a client is attempting
+ * to get a service through IServiceManager APIs when the services aren't known by
+ * servicemanager.
+ */
+typedef struct ABinderRpc_AccessorProvider ABinderRpc_AccessorProvider;
+
+/**
+ * This represents information necessary for libbinder to be able to connect to a
+ * remote service.
+ * It supports connecting to linux sockets and is created using sockaddr
+ * types for sockets supported by libbinder like sockaddr_in, sockaddr_un,
+ * sockaddr_vm.
+ */
+typedef struct ABinderRpc_ConnectionInfo ABinderRpc_ConnectionInfo;
+
+/**
+ * These APIs provide a way for clients of binder services to be able to get a
+ * binder object of that service through the existing libbinder/libbinder_ndk
+ * Service Manager APIs when that service is using RPC Binder over sockets
+ * instead kernel binder.
+ *
+ * Some of these APIs are used on Android hosts when kernel binder is supported
+ * and the usual servicemanager process is available. Some of these APIs are
+ * only required when there is no kernel binder or extra servicemanager process
+ * such as the case of microdroid or similar VMs.
+ */
+
+/**
+ * This callback is responsible for returning ABinderRpc_Accessor objects for a given
+ * service instance. These ABinderRpc_Accessor objects are implemented by
+ * libbinder_ndk and backed by implementations of android::os::IAccessor in
+ * libbinder.
+ *
+ * \param instance name of the service like
+ *        `android.hardware.vibrator.IVibrator/default`. This string must remain
+ *        valid and unchanged for the duration of this function call.
+ * \param data the data that was associated with this instance when the callback
+ *        was registered.
+ * \return The ABinderRpc_Accessor associated with the service `instance`. This
+ *        callback gives up ownership of the object once it returns it. The
+ *        caller of this callback (libbinder_ndk) is responsible for deleting it
+ *        with ABinderRpc_Accessor_delete.
+ */
+typedef ABinderRpc_Accessor* _Nullable (*ABinderRpc_AccessorProvider_getAccessorCallback)(
+        const char* _Nonnull instance, void* _Nullable data);
+
+/**
+ * This callback is responsible deleting the `void* data` object that is passed
+ * in to ABinderRpc_registerAccessorProvider for the ABinderRpc_AccessorProvider_getAccessorCallback
+ * to use. That object is owned by the ABinderRpc_AccessorProvider and must remain valid for the
+ * lifetime of the callback because it may be called and use the object.
+ * This _delete callback is called after the ABinderRpc_AccessorProvider is remove and
+ * is guaranteed never to be called again.
+ *
+ * \param data a pointer to data that the ABinderRpc_AccessorProvider_getAccessorCallback uses which
+ * is to be deleted by this call.
+ */
+typedef void (*ABinderRpc_AccessorProviderUserData_deleteCallback)(void* _Nullable data);
+
+/**
+ * Inject an ABinderRpc_AccessorProvider_getAccessorCallback into the process for
+ * the Service Manager APIs to use to retrieve ABinderRpc_Accessor objects associated
+ * with different RPC Binder services.
+ *
+ * \param provider callback that returns ABinderRpc_Accessors for libbinder to set up
+ *        RPC clients with.
+ * \param instances array of instances that are supported by this provider. It
+ *        will only be called if the client is looking for an instance that is
+ *        in this list. These instances must be unique per-process. If an
+ *        instance is being registered that was previously registered, this call
+ *        will fail and the ABinderRpc_AccessorProviderUserData_deleteCallback
+ *        will be called to clean up the data.
+ *        This array of strings must remain valid and unchanged for the duration
+ *        of this function call.
+ * \param number of instances in the instances array.
+ * \param data pointer that is passed to the ABinderRpc_AccessorProvider callback.
+ *        IMPORTANT: The ABinderRpc_AccessorProvider now OWNS that object that data
+ *        points to. It can be used as necessary in the callback. The data MUST
+ *        remain valid for the lifetime of the provider callback.
+ *        Do not attempt to give ownership of the same object to different
+ *        providers through multiple calls to this function because the first
+ *        one to be deleted will call the onDelete callback.
+ * \param onDelete callback used to delete the objects that `data` points to.
+ *        This is called after ABinderRpc_AccessorProvider is guaranteed to never be
+ *        called again. Before this callback is called, `data` must remain
+ *        valid.
+ * \return nullptr on error if the data pointer is non-null and the onDelete
+ *         callback is null or if an instance in the instances list was previously
+ *         registered. In the error case of duplicate instances, if data was
+ *         provided with a ABinderRpc_AccessorProviderUserData_deleteCallback,
+ *         the callback will be called to delete the data.
+ *         Otherwise returns a pointer to the ABinderRpc_AccessorProvider that
+ *         can be used to remove with ABinderRpc_unregisterAccessorProvider.
+ */
+ABinderRpc_AccessorProvider* _Nullable ABinderRpc_registerAccessorProvider(
+        ABinderRpc_AccessorProvider_getAccessorCallback _Nonnull provider,
+        const char* _Nullable* _Nonnull instances, size_t numInstances, void* _Nullable data,
+        ABinderRpc_AccessorProviderUserData_deleteCallback _Nullable onDelete) __INTRODUCED_IN(36);
+
+/**
+ * Remove an ABinderRpc_AccessorProvider from libbinder. This will remove references
+ *        from the ABinderRpc_AccessorProvider and will no longer call the
+ *        ABinderRpc_AccessorProvider_getAccessorCallback.
+ *
+ * Note: The `data` object that was used when adding the accessor will be
+ *       deleted by the ABinderRpc_AccessorProviderUserData_deleteCallback at some
+ *       point after this call. Do not use the object and do not try to delete
+ *       it through any other means.
+ * Note: This will abort when used incorrectly if this provider was never
+ *       registered or if it were already unregistered.
+ *
+ * \param provider to be removed and deleted
+ *
+ */
+void ABinderRpc_unregisterAccessorProvider(ABinderRpc_AccessorProvider* _Nonnull provider)
+        __INTRODUCED_IN(36);
+
+/**
+ * Callback which returns the RPC connection information for libbinder to use to
+ * connect to a socket that a given service is listening on. This is needed to
+ * create an ABinderRpc_Accessor so it can connect to these services.
+ *
+ * \param instance name of the service to connect to. This string must remain
+ *        valid and unchanged for the duration of this function call.
+ * \param data user data for this callback. The pointer is provided in
+ *        ABinderRpc_Accessor_new.
+ * \return ABinderRpc_ConnectionInfo with socket connection information for `instance`
+ */
+typedef ABinderRpc_ConnectionInfo* _Nullable (*ABinderRpc_ConnectionInfoProvider)(
+        const char* _Nonnull instance, void* _Nullable data) __INTRODUCED_IN(36);
+/**
+ * This callback is responsible deleting the `void* data` object that is passed
+ * in to ABinderRpc_Accessor_new for the ABinderRpc_ConnectionInfoProvider to use. That
+ * object is owned by the ABinderRpc_Accessor and must remain valid for the
+ * lifetime the Accessor because it may be used by the connection info provider
+ * callback.
+ * This _delete callback is called after the ABinderRpc_Accessor is removed and
+ * is guaranteed never to be called again.
+ *
+ * \param data a pointer to data that the ABinderRpc_AccessorProvider uses which is to
+ *        be deleted by this call.
+ */
+typedef void (*ABinderRpc_ConnectionInfoProviderUserData_delete)(void* _Nullable data);
+
+/**
+ * Create a new ABinderRpc_Accessor. This creates an IAccessor object in libbinder
+ * that can use the info from the ABinderRpc_ConnectionInfoProvider to connect to a
+ * socket that the service with `instance` name is listening to.
+ *
+ * \param instance name of the service that is listening on the socket. This
+ *        string must remain valid and unchanged for the duration of this
+ *        function call.
+ * \param provider callback that can get the socket connection information for the
+ *           instance. This connection information may be dynamic, so the
+ *           provider will be called any time a new connection is required.
+ * \param data pointer that is passed to the ABinderRpc_ConnectionInfoProvider callback.
+ *        IMPORTANT: The ABinderRpc_ConnectionInfoProvider now OWNS that object that data
+ *        points to. It can be used as necessary in the callback. The data MUST
+ *        remain valid for the lifetime of the provider callback.
+ *        Do not attempt to give ownership of the same object to different
+ *        providers through multiple calls to this function because the first
+ *        one to be deleted will call the onDelete callback.
+ * \param onDelete callback used to delete the objects that `data` points to.
+ *        This is called after ABinderRpc_ConnectionInfoProvider is guaranteed to never be
+ *        called again. Before this callback is called, `data` must remain
+ *        valid.
+ * \return an ABinderRpc_Accessor instance. This is deleted by the caller once it is
+ *         no longer needed.
+ */
+ABinderRpc_Accessor* _Nullable ABinderRpc_Accessor_new(
+        const char* _Nonnull instance, ABinderRpc_ConnectionInfoProvider _Nonnull provider,
+        void* _Nullable data, ABinderRpc_ConnectionInfoProviderUserData_delete _Nullable onDelete)
+        __INTRODUCED_IN(36);
+
+/**
+ * Delete an ABinderRpc_Accessor
+ *
+ * \param accessor to delete
+ */
+void ABinderRpc_Accessor_delete(ABinderRpc_Accessor* _Nonnull accessor) __INTRODUCED_IN(36);
+
+/**
+ * Return the AIBinder associated with an ABinderRpc_Accessor. This can be used to
+ * send the Accessor to another process or even register it with servicemanager.
+ *
+ * \param accessor to get the AIBinder for
+ * \return binder of the supplied accessor with one strong ref count
+ */
+AIBinder* _Nullable ABinderRpc_Accessor_asBinder(ABinderRpc_Accessor* _Nonnull accessor)
+        __INTRODUCED_IN(36);
+
+/**
+ * Return the ABinderRpc_Accessor associated with an AIBinder. The instance must match
+ * the ABinderRpc_Accessor implementation.
+ * This can be used when receiving an AIBinder from another process that the
+ * other process obtained from ABinderRpc_Accessor_asBinder.
+ *
+ * \param instance name of the service that the Accessor is responsible for.
+ *        This string must remain valid and unchanged for the duration of this
+ *        function call.
+ * \param accessorBinder proxy binder from another process's ABinderRpc_Accessor.
+ *        This function preserves the refcount of this binder object and the
+ *        caller still owns it.
+ * \return ABinderRpc_Accessor representing the other processes ABinderRpc_Accessor
+ *         implementation. The caller owns this ABinderRpc_Accessor instance and
+ *         is responsible for deleting it with ABinderRpc_Accessor_delete or
+ *         passing ownership of it elsewhere, like returning it through
+ *         ABinderRpc_AccessorProvider_getAccessorCallback.
+ *         nullptr on error when the accessorBinder is not a valid binder from
+ *         an IAccessor implementation or the IAccessor implementation is not
+ *         associated with the provided instance.
+ */
+ABinderRpc_Accessor* _Nullable ABinderRpc_Accessor_fromBinder(const char* _Nonnull instance,
+                                                              AIBinder* _Nonnull accessorBinder)
+        __INTRODUCED_IN(36);
+
+/**
+ * Create a new ABinderRpc_ConnectionInfo with sockaddr. This can be supported socket
+ * types like sockaddr_vm (vsock) and sockaddr_un (Unix Domain Sockets).
+ *
+ * \param addr sockaddr pointer that can come from supported socket
+ *        types like sockaddr_vm (vsock) and sockaddr_un (Unix Domain Sockets).
+ * \param len length of the concrete sockaddr type being used. Like
+ *        sizeof(sockaddr_vm) when sockaddr_vm is used.
+ * \return the connection info based on the given sockaddr
+ */
+ABinderRpc_ConnectionInfo* _Nullable ABinderRpc_ConnectionInfo_new(const sockaddr* _Nonnull addr,
+                                                                   socklen_t len)
+        __INTRODUCED_IN(36);
+
+/**
+ * Delete an ABinderRpc_ConnectionInfo object that was created with
+ * ABinderRpc_ConnectionInfo_new.
+ *
+ * \param info object to be deleted
+ */
+void ABinderRpc_ConnectionInfo_delete(ABinderRpc_ConnectionInfo* _Nonnull info) __INTRODUCED_IN(36);
+
+/** @} */
+
+__END_DECLS
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 826e199..c885816 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -248,6 +248,22 @@
     AServiceManager_openDeclaredPassthroughHal; # systemapi llndk=202404
 };
 
+LIBBINDER_NDK36 { # introduced=36
+  global:
+    AIBinder_Class_setTransactionCodeToFunctionNameMap;
+    AIBinder_Class_setTransactionCodeToFunctionNameMap; # llndk=202504
+    AIBinder_Class_getFunctionName;
+    AIBinder_Class_getFunctionName; # llndk=202504
+    ABinderRpc_registerAccessorProvider; # systemapi
+    ABinderRpc_unregisterAccessorProvider; # systemapi
+    ABinderRpc_Accessor_new; # systemapi
+    ABinderRpc_Accessor_delete; # systemapi
+    ABinderRpc_Accessor_asBinder; # systemapi
+    ABinderRpc_Accessor_fromBinder; # systemapi
+    ABinderRpc_ConnectionInfo_new; # systemapi
+    ABinderRpc_ConnectionInfo_delete; # systemapi
+};
+
 LIBBINDER_NDK_PLATFORM {
   global:
     AParcel_getAllowFds;
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index f518a22..e5a3da4 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -46,6 +46,7 @@
 #include "android/binder_ibinder.h"
 
 using namespace android;
+using namespace std::chrono_literals;
 
 constexpr char kExistingNonNdkService[] = "SurfaceFlinger";
 constexpr char kBinderNdkUnitTestService[] = "BinderNdkUnitTest";
@@ -54,7 +55,7 @@
 constexpr char kActiveServicesNdkUnitTestService[] = "ActiveServicesNdkUnitTestService";
 constexpr char kBinderNdkUnitTestServiceFlagged[] = "BinderNdkUnitTestFlagged";
 
-constexpr unsigned int kShutdownWaitTime = 11;
+constexpr auto kShutdownWaitTime = 30s;
 constexpr uint64_t kContextTestValue = 0xb4e42fb4d9a1d715;
 
 class MyTestFoo : public IFoo {
@@ -253,12 +254,22 @@
 }
 
 bool isServiceRunning(const char* serviceName) {
-    AIBinder* binder = AServiceManager_checkService(serviceName);
-    if (binder == nullptr) {
-        return false;
+    static const sp<android::IServiceManager> sm(android::defaultServiceManager());
+    const Vector<String16> services = sm->listServices();
+    for (const auto service : services) {
+        if (service == String16(serviceName)) return true;
     }
-    AIBinder_decStrong(binder);
+    return false;
+}
 
+bool isServiceShutdownWithWait(const char* serviceName) {
+    LOG(INFO) << "About to check and wait for shutdown of " << std::string(serviceName);
+    const auto before = std::chrono::steady_clock::now();
+    while (isServiceRunning(serviceName)) {
+        sleep(1);
+        const auto after = std::chrono::steady_clock::now();
+        if (after - before >= kShutdownWaitTime) return false;
+    }
     return true;
 }
 
@@ -450,8 +461,8 @@
     service = nullptr;
     IPCThreadState::self()->flushCommands();
     // Make sure the service is dead after some time of no use
-    sleep(kShutdownWaitTime);
-    ASSERT_EQ(nullptr, AServiceManager_checkService(kLazyBinderNdkUnitTestService));
+    ASSERT_TRUE(isServiceShutdownWithWait(kLazyBinderNdkUnitTestService))
+            << "Service failed to shut down";
 }
 
 TEST(NdkBinder, ForcedPersistenceTest) {
@@ -466,14 +477,12 @@
         service = nullptr;
         IPCThreadState::self()->flushCommands();
 
-        sleep(kShutdownWaitTime);
-
-        bool isRunning = isServiceRunning(kForcePersistNdkUnitTestService);
-
         if (i == 0) {
-            ASSERT_TRUE(isRunning) << "Service shut down when it shouldn't have.";
+            ASSERT_TRUE(isServiceRunning(kForcePersistNdkUnitTestService))
+                    << "Service shut down when it shouldn't have.";
         } else {
-            ASSERT_FALSE(isRunning) << "Service failed to shut down.";
+            ASSERT_TRUE(isServiceShutdownWithWait(kForcePersistNdkUnitTestService))
+                    << "Service failed to shut down";
         }
     }
 }
@@ -491,10 +500,7 @@
     service = nullptr;
     IPCThreadState::self()->flushCommands();
 
-    LOG(INFO) << "ActiveServicesCallbackTest about to sleep";
-    sleep(kShutdownWaitTime);
-
-    ASSERT_FALSE(isServiceRunning(kActiveServicesNdkUnitTestService))
+    ASSERT_TRUE(isServiceShutdownWithWait(kActiveServicesNdkUnitTestService))
             << "Service failed to shut down.";
 }
 
@@ -1102,6 +1108,37 @@
     EXPECT_EQ(deleteCount, 0);
 }
 
+void* EmptyOnCreate(void* args) {
+    return args;
+}
+void EmptyOnDestroy(void* /*userData*/) {}
+binder_status_t EmptyOnTransact(AIBinder* /*binder*/, transaction_code_t /*code*/,
+                                const AParcel* /*in*/, AParcel* /*out*/) {
+    return STATUS_OK;
+}
+
+TEST(NdkBinder_DeathTest, SetCodeMapTwice) {
+    const char* codeToFunction1[] = {"function-1", "function-2", "function-3"};
+    const char* codeToFunction2[] = {"function-4", "function-5"};
+    const char* interfaceName = "interface_descriptor";
+    AIBinder_Class* clazz =
+            AIBinder_Class_define(interfaceName, EmptyOnCreate, EmptyOnDestroy, EmptyOnTransact);
+    AIBinder_Class_setTransactionCodeToFunctionNameMap(clazz, codeToFunction1, 3);
+    // Reset/clear is not allowed
+    EXPECT_DEATH(AIBinder_Class_setTransactionCodeToFunctionNameMap(clazz, codeToFunction2, 2), "");
+}
+
+TEST(NdkBinder_DeathTest, SetNullCodeMap) {
+    const char* codeToFunction[] = {"function-1", "function-2", "function-3"};
+    const char* interfaceName = "interface_descriptor";
+    AIBinder_Class* clazz =
+            AIBinder_Class_define(interfaceName, EmptyOnCreate, EmptyOnDestroy, EmptyOnTransact);
+    EXPECT_DEATH(AIBinder_Class_setTransactionCodeToFunctionNameMap(nullptr, codeToFunction, 3),
+                 "");
+    EXPECT_DEATH(AIBinder_Class_setTransactionCodeToFunctionNameMap(clazz, nullptr, 0), "");
+    EXPECT_DEATH(AIBinder_Class_setTransactionCodeToFunctionNameMap(nullptr, nullptr, 0), "");
+}
+
 int main(int argc, char* argv[]) {
     ::testing::InitGoogleTest(&argc, argv);
 
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index 2deb254..4545d7b 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -15,6 +15,8 @@
         "libbinder_ndk_sys",
         "libdowncast_rs",
         "liblibc",
+        "liblog_rust",
+        "libnix",
     ],
     host_supported: true,
     vendor_available: true,
@@ -79,6 +81,9 @@
     shared_libs: [
         "libbinder_ndk",
     ],
+    rustlibs: [
+        "liblibc",
+    ],
     host_supported: true,
     vendor_available: true,
     product_available: true,
@@ -129,9 +134,18 @@
         // rustified
         "libbinder_ndk_bindgen_flags.txt",
     ],
+    bindgen_flags: [
+        "--blocklist-type",
+        "sockaddr",
+        "--raw-line",
+        "use libc::sockaddr;",
+    ],
     shared_libs: [
         "libbinder_ndk",
     ],
+    rustlibs: [
+        "liblibc",
+    ],
     host_supported: true,
     vendor_available: true,
     product_available: true,
@@ -185,6 +199,8 @@
         "libbinder_ndk_sys",
         "libdowncast_rs",
         "liblibc",
+        "liblog_rust",
+        "libnix",
     ],
 }
 
@@ -196,4 +212,7 @@
     auto_gen_config: true,
     clippy_lints: "none",
     lints: "none",
+    rustlibs: [
+        "liblibc",
+    ],
 }
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 9a252b8..23026e5 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -136,6 +136,31 @@
     }
 }
 
+/// Same as `Stability`, but in the form of a trait. Used when the stability should be encoded in
+/// the type.
+///
+/// When/if the `adt_const_params` Rust feature is stabilized, this could be replace by using
+/// `Stability` directly with const generics.
+pub trait StabilityType {
+    /// The `Stability` represented by this type.
+    const VALUE: Stability;
+}
+
+/// `Stability::Local`.
+#[derive(Debug)]
+pub enum LocalStabilityType {}
+/// `Stability::Vintf`.
+#[derive(Debug)]
+pub enum VintfStabilityType {}
+
+impl StabilityType for LocalStabilityType {
+    const VALUE: Stability = Stability::Local;
+}
+
+impl StabilityType for VintfStabilityType {
+    const VALUE: Stability = Stability::Vintf;
+}
+
 /// A local service that can be remotable via Binder.
 ///
 /// An object that implement this interface made be made into a Binder service
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index e70f4f0..0e8e388 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -104,6 +104,8 @@
 mod service;
 #[cfg(not(trusty))]
 mod state;
+#[cfg(not(any(android_vendor, android_vndk)))]
+mod system_only;
 
 use binder_ndk_sys as sys;
 
@@ -120,6 +122,8 @@
 };
 #[cfg(not(trusty))]
 pub use state::{ProcessState, ThreadState};
+#[cfg(not(any(android_vendor, android_vndk)))]
+pub use system_only::{Accessor, ConnectionInfo};
 
 /// Binder result containing a [`Status`] on error.
 pub type Result<T> = std::result::Result<T, Status>;
@@ -128,9 +132,10 @@
 /// without AIDL.
 pub mod binder_impl {
     pub use crate::binder::{
-        IBinderInternal, InterfaceClass, Remotable, Stability, ToAsyncInterface, ToSyncInterface,
-        TransactionCode, TransactionFlags, FIRST_CALL_TRANSACTION, FLAG_CLEAR_BUF, FLAG_ONEWAY,
-        FLAG_PRIVATE_LOCAL, LAST_CALL_TRANSACTION,
+        IBinderInternal, InterfaceClass, LocalStabilityType, Remotable, Stability, StabilityType,
+        ToAsyncInterface, ToSyncInterface, TransactionCode, TransactionFlags, VintfStabilityType,
+        FIRST_CALL_TRANSACTION, FLAG_CLEAR_BUF, FLAG_ONEWAY, FLAG_PRIVATE_LOCAL,
+        LAST_CALL_TRANSACTION,
     };
     pub use crate::binder_async::BinderAsyncRuntime;
     pub use crate::error::status_t;
diff --git a/libs/binder/rust/src/parcel/parcelable_holder.rs b/libs/binder/rust/src/parcel/parcelable_holder.rs
index f906113..87b42ab 100644
--- a/libs/binder/rust/src/parcel/parcelable_holder.rs
+++ b/libs/binder/rust/src/parcel/parcelable_holder.rs
@@ -15,6 +15,7 @@
  */
 
 use crate::binder::Stability;
+use crate::binder::StabilityType;
 use crate::error::StatusCode;
 use crate::parcel::{
     BorrowedParcel, Deserialize, Parcel, Parcelable, Serialize, NON_NULL_PARCELABLE_FLAG,
@@ -60,7 +61,7 @@
 /// `Send` nor `Sync`), mainly because it internally contains
 /// a `Parcel` which in turn is not thread-safe.
 #[derive(Debug)]
-pub struct ParcelableHolder {
+pub struct ParcelableHolder<STABILITY: StabilityType> {
     // This is a `Mutex` because of `get_parcelable`
     // which takes `&self` for consistency with C++.
     // We could make `get_parcelable` take a `&mut self`
@@ -68,13 +69,17 @@
     // improvement, but then callers would require a mutable
     // `ParcelableHolder` even for that getter method.
     data: Mutex<ParcelableHolderData>,
-    stability: Stability,
+
+    _stability_phantom: std::marker::PhantomData<STABILITY>,
 }
 
-impl ParcelableHolder {
+impl<STABILITY: StabilityType> ParcelableHolder<STABILITY> {
     /// Construct a new `ParcelableHolder` with the given stability.
-    pub fn new(stability: Stability) -> Self {
-        Self { data: Mutex::new(ParcelableHolderData::Empty), stability }
+    pub fn new() -> Self {
+        Self {
+            data: Mutex::new(ParcelableHolderData::Empty),
+            _stability_phantom: Default::default(),
+        }
     }
 
     /// Reset the contents of this `ParcelableHolder`.
@@ -91,7 +96,7 @@
     where
         T: Any + Parcelable + ParcelableMetadata + std::fmt::Debug + Send + Sync,
     {
-        if self.stability > p.get_stability() {
+        if STABILITY::VALUE > p.get_stability() {
             return Err(StatusCode::BAD_VALUE);
         }
 
@@ -157,30 +162,36 @@
 
     /// Return the stability value of this object.
     pub fn get_stability(&self) -> Stability {
-        self.stability
+        STABILITY::VALUE
     }
 }
 
-impl Clone for ParcelableHolder {
-    fn clone(&self) -> ParcelableHolder {
+impl<STABILITY: StabilityType> Default for ParcelableHolder<STABILITY> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<STABILITY: StabilityType> Clone for ParcelableHolder<STABILITY> {
+    fn clone(&self) -> Self {
         ParcelableHolder {
             data: Mutex::new(self.data.lock().unwrap().clone()),
-            stability: self.stability,
+            _stability_phantom: Default::default(),
         }
     }
 }
 
-impl Serialize for ParcelableHolder {
+impl<STABILITY: StabilityType> Serialize for ParcelableHolder<STABILITY> {
     fn serialize(&self, parcel: &mut BorrowedParcel<'_>) -> Result<(), StatusCode> {
         parcel.write(&NON_NULL_PARCELABLE_FLAG)?;
         self.write_to_parcel(parcel)
     }
 }
 
-impl Deserialize for ParcelableHolder {
+impl<STABILITY: StabilityType> Deserialize for ParcelableHolder<STABILITY> {
     type UninitType = Self;
     fn uninit() -> Self::UninitType {
-        Self::new(Default::default())
+        Self::new()
     }
     fn from_init(value: Self) -> Self::UninitType {
         value
@@ -191,16 +202,16 @@
         if status == NULL_PARCELABLE_FLAG {
             Err(StatusCode::UNEXPECTED_NULL)
         } else {
-            let mut parcelable = ParcelableHolder::new(Default::default());
+            let mut parcelable = Self::new();
             parcelable.read_from_parcel(parcel)?;
             Ok(parcelable)
         }
     }
 }
 
-impl Parcelable for ParcelableHolder {
+impl<STABILITY: StabilityType> Parcelable for ParcelableHolder<STABILITY> {
     fn write_to_parcel(&self, parcel: &mut BorrowedParcel<'_>) -> Result<(), StatusCode> {
-        parcel.write(&self.stability)?;
+        parcel.write(&STABILITY::VALUE)?;
 
         let mut data = self.data.lock().unwrap();
         match *data {
@@ -236,7 +247,7 @@
     }
 
     fn read_from_parcel(&mut self, parcel: &BorrowedParcel<'_>) -> Result<(), StatusCode> {
-        if self.stability != parcel.read()? {
+        if self.get_stability() != parcel.read()? {
             return Err(StatusCode::BAD_VALUE);
         }
 
diff --git a/libs/binder/rust/src/system_only.rs b/libs/binder/rust/src/system_only.rs
new file mode 100644
index 0000000..a91d84d
--- /dev/null
+++ b/libs/binder/rust/src/system_only.rs
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+use crate::proxy::SpIBinder;
+use crate::sys;
+
+use std::ffi::{c_void, CStr, CString};
+use std::os::raw::c_char;
+
+use libc::sockaddr;
+use nix::sys::socket::{SockaddrLike, UnixAddr, VsockAddr};
+use std::sync::Arc;
+use std::{fmt, ptr};
+
+/// Rust wrapper around ABinderRpc_Accessor objects for RPC binder service management.
+///
+/// Dropping the `Accessor` will drop the underlying object and the binder it owns.
+pub struct Accessor {
+    accessor: *mut sys::ABinderRpc_Accessor,
+}
+
+impl fmt::Debug for Accessor {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "ABinderRpc_Accessor({:p})", self.accessor)
+    }
+}
+
+/// Socket connection info required for libbinder to connect to a service.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum ConnectionInfo {
+    /// For vsock connection
+    Vsock(VsockAddr),
+    /// For unix domain socket connection
+    Unix(UnixAddr),
+}
+
+/// Safety: A `Accessor` is a wrapper around `ABinderRpc_Accessor` which is
+/// `Sync` and `Send`. As
+/// `ABinderRpc_Accessor` is threadsafe, this structure is too.
+/// The Fn owned the Accessor has `Sync` and `Send` properties
+unsafe impl Send for Accessor {}
+
+/// Safety: A `Accessor` is a wrapper around `ABinderRpc_Accessor` which is
+/// `Sync` and `Send`. As `ABinderRpc_Accessor` is threadsafe, this structure is too.
+/// The Fn owned the Accessor has `Sync` and `Send` properties
+unsafe impl Sync for Accessor {}
+
+impl Accessor {
+    /// Create a new accessor that will call the given callback when its
+    /// connection info is required.
+    /// The callback object and all objects it captures are owned by the Accessor
+    /// and will be deleted some time after the Accessor is Dropped. If the callback
+    /// is being called when the Accessor is Dropped, the callback will not be deleted
+    /// immediately.
+    pub fn new<F>(instance: &str, callback: F) -> Accessor
+    where
+        F: Fn(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
+    {
+        let callback: *mut c_void = Arc::into_raw(Arc::new(callback)) as *mut c_void;
+        let inst = CString::new(instance).unwrap();
+
+        // Safety: The function pointer is a valid connection_info callback.
+        // This call returns an owned `ABinderRpc_Accessor` pointer which
+        // must be destroyed via `ABinderRpc_Accessor_delete` when no longer
+        // needed.
+        // When the underlying ABinderRpc_Accessor is deleted, it will call
+        // the cookie_decr_refcount callback to release its strong ref.
+        let accessor = unsafe {
+            sys::ABinderRpc_Accessor_new(
+                inst.as_ptr(),
+                Some(Self::connection_info::<F>),
+                callback,
+                Some(Self::cookie_decr_refcount::<F>),
+            )
+        };
+
+        Accessor { accessor }
+    }
+
+    /// Get the underlying binder for this Accessor for when it needs to be either
+    /// registered with service manager or sent to another process.
+    pub fn as_binder(&self) -> Option<SpIBinder> {
+        // Safety: `ABinderRpc_Accessor_asBinder` returns either a null pointer or a
+        // valid pointer to an owned `AIBinder`. Either of these values is safe to
+        // pass to `SpIBinder::from_raw`.
+        unsafe { SpIBinder::from_raw(sys::ABinderRpc_Accessor_asBinder(self.accessor)) }
+    }
+
+    /// Callback invoked from C++ when the connection info is needed.
+    ///
+    /// # Safety
+    ///
+    /// The `instance` parameter must be a non-null pointer to a valid C string for
+    /// CStr::from_ptr. The memory must contain a valid null terminator at the end of
+    /// the string within isize::MAX from the pointer. The memory must not be mutated for
+    /// the duration of this function  call and must be valid for reads from the pointer
+    /// to the null terminator.
+    /// 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 connection_info<F>(
+        instance: *const c_char,
+        cookie: *mut c_void,
+    ) -> *mut binder_ndk_sys::ABinderRpc_ConnectionInfo
+    where
+        F: Fn(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
+    {
+        if cookie.is_null() || instance.is_null() {
+            log::error!("Cookie({cookie:p}) or instance({instance:p}) is null!");
+            return ptr::null_mut();
+        }
+        // Safety: The caller promises that `cookie` is for an Arc<F>.
+        let callback = unsafe { (cookie as *const F).as_ref().unwrap() };
+
+        // Safety: The caller in libbinder_ndk will have already verified this is a valid
+        // C string
+        let inst = unsafe {
+            match CStr::from_ptr(instance).to_str() {
+                Ok(s) => s,
+                Err(err) => {
+                    log::error!("Failed to get a valid C string! {err:?}");
+                    return ptr::null_mut();
+                }
+            }
+        };
+
+        let connection = match callback(inst) {
+            Some(con) => con,
+            None => {
+                return ptr::null_mut();
+            }
+        };
+
+        match connection {
+            ConnectionInfo::Vsock(addr) => {
+                // Safety: The sockaddr is being copied in the NDK API
+                unsafe { sys::ABinderRpc_ConnectionInfo_new(addr.as_ptr(), addr.len()) }
+            }
+            ConnectionInfo::Unix(addr) => {
+                // Safety: The sockaddr is being copied in the NDK API
+                // The cast is from sockaddr_un* to sockaddr*.
+                unsafe {
+                    sys::ABinderRpc_ConnectionInfo_new(addr.as_ptr() as *const sockaddr, addr.len())
+                }
+            }
+        }
+    }
+
+    /// 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(&str) -> Option<ConnectionInfo> + Send + Sync + 'static,
+    {
+        // Safety: The caller promises that `cookie` is for an Arc<F>.
+        unsafe { Arc::decrement_strong_count(cookie as *const F) };
+    }
+}
+
+impl Drop for Accessor {
+    fn drop(&mut self) {
+        // Safety: `self.accessor` is always a valid, owned
+        // `ABinderRpc_Accessor` pointer returned by
+        // `ABinderRpc_Accessor_new` when `self` was created. This delete
+        // method can only be called once when `self` is dropped.
+        unsafe {
+            sys::ABinderRpc_Accessor_delete(self.accessor);
+        }
+    }
+}
diff --git a/libs/binder/rust/sys/BinderBindings.hpp b/libs/binder/rust/sys/BinderBindings.hpp
index 65fa2ca..bd666fe 100644
--- a/libs/binder/rust/sys/BinderBindings.hpp
+++ b/libs/binder/rust/sys/BinderBindings.hpp
@@ -20,6 +20,7 @@
 #include <android/binder_parcel.h>
 #include <android/binder_parcel_platform.h>
 #include <android/binder_process.h>
+#include <android/binder_rpc.h>
 #include <android/binder_shell.h>
 #include <android/binder_stability.h>
 #include <android/binder_status.h>
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 5359832..bdb7e4a 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -384,8 +384,8 @@
     use std::time::Duration;
 
     use binder::{
-        BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder, StatusCode,
-        Strong,
+        Accessor, BinderFeatures, DeathRecipient, FromIBinder, IBinder, Interface, SpIBinder,
+        StatusCode, Strong,
     };
     // Import from impl API for testing only, should not be necessary as long as
     // you are using AIDL.
@@ -908,6 +908,43 @@
         assert_eq!(service.test().unwrap(), service_name);
     }
 
+    struct ToBeDeleted {
+        deleted: Arc<AtomicBool>,
+    }
+
+    impl Drop for ToBeDeleted {
+        fn drop(&mut self) {
+            assert!(!self.deleted.load(Ordering::Relaxed));
+            self.deleted.store(true, Ordering::Relaxed);
+        }
+    }
+
+    #[test]
+    fn test_accessor_callback_destruction() {
+        let deleted: Arc<AtomicBool> = Arc::new(AtomicBool::new(false));
+        {
+            let accessor: Accessor;
+            {
+                let helper = ToBeDeleted { deleted: deleted.clone() };
+                let get_connection_info = move |_instance: &str| {
+                    // Capture this object so we can see it get destructed
+                    // after the parent scope
+                    let _ = &helper;
+                    None
+                };
+                accessor = Accessor::new("foo.service", get_connection_info);
+            }
+
+            match accessor.as_binder() {
+                Some(_) => {
+                    assert!(!deleted.load(Ordering::Relaxed));
+                }
+                None => panic!("failed to get that accessor binder"),
+            }
+        }
+        assert!(deleted.load(Ordering::Relaxed));
+    }
+
     #[tokio::test]
     async fn reassociate_rust_binder_async() {
         let service_name = "testing_service";
diff --git a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
index ce0f742..ee20a22 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
+++ b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
@@ -21,7 +21,8 @@
 
 use crate::read_utils::READ_FUNCS;
 use binder::binder_impl::{
-    Binder, BorrowedParcel, IBinderInternal, Parcel, Stability, TransactionCode,
+    Binder, BorrowedParcel, IBinderInternal, LocalStabilityType, Parcel, TransactionCode,
+    VintfStabilityType,
 };
 use binder::{
     declare_binder_interface, BinderFeatures, Interface, Parcelable, ParcelableHolder, SpIBinder,
@@ -121,13 +122,15 @@
             }
 
             ReadOperation::ReadParcelableHolder { is_vintf } => {
-                let stability = if is_vintf { Stability::Vintf } else { Stability::Local };
-                let mut holder: ParcelableHolder = ParcelableHolder::new(stability);
-                match holder.read_from_parcel(parcel.borrowed_ref()) {
-                    Ok(result) => result,
-                    Err(err) => {
-                        println!("error occurred while reading from parcel: {:?}", err)
-                    }
+                let result = if is_vintf {
+                    ParcelableHolder::<VintfStabilityType>::new()
+                        .read_from_parcel(parcel.borrowed_ref())
+                } else {
+                    ParcelableHolder::<LocalStabilityType>::new()
+                        .read_from_parcel(parcel.borrowed_ref())
+                };
+                if let Err(e) = result {
+                    println!("error occurred while reading from parcel: {e:?}")
                 }
             }
 
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 0e653af..8b0dda3 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -532,6 +532,9 @@
     static_libs: [
         "libbinder_rpc_single_threaded",
     ],
+    shared_libs: [
+        "libbinder_ndk",
+    ],
 }
 
 cc_test {
diff --git a/libs/binder/tests/binderCacheUnitTest.cpp b/libs/binder/tests/binderCacheUnitTest.cpp
index 92dab19..c5ad793 100644
--- a/libs/binder/tests/binderCacheUnitTest.cpp
+++ b/libs/binder/tests/binderCacheUnitTest.cpp
@@ -137,9 +137,9 @@
     ASSERT_EQ(binder1, result);
 
     // Kill the server, this should remove from cache.
-    foo.killServer(binder1);
     pid_t pid;
     ASSERT_EQ(OK, binder1->getDebugPid(&pid));
+    foo.killServer(binder1);
     system(("kill -9 " + std::to_string(pid)).c_str());
 
     sp<IBinder> binder2 = sp<BBinder>::make();
@@ -149,7 +149,16 @@
     EXPECT_EQ(OK, mServiceManager->addService(kCachedServiceName, binder2));
 
     // Confirm that new service is returned instead of old.
-    sp<IBinder> result2 = mServiceManager->checkService(kCachedServiceName);
+    int retry_count = 20;
+    sp<IBinder> result2;
+    do {
+        std::this_thread::sleep_for(std::chrono::milliseconds(50));
+        if (retry_count-- == 0) {
+            break;
+        }
+        result2 = mServiceManager->checkService(kCachedServiceName);
+    } while (result2 != binder2);
+
     ASSERT_EQ(binder2, result2);
 }
 
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index fbca35e..077a33a 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -46,6 +46,13 @@
 #include "binderRpcTestCommon.h"
 #include "binderRpcTestFixture.h"
 
+// TODO need to add IServiceManager.cpp/.h to libbinder_no_kernel
+#ifdef BINDER_WITH_KERNEL_IPC
+#include "android-base/logging.h"
+#include "android/binder_manager.h"
+#include "android/binder_rpc.h"
+#endif // BINDER_WITH_KERNEL_IPC
+
 using namespace std::chrono_literals;
 using namespace std::placeholders;
 using android::binder::borrowed_fd;
@@ -454,7 +461,7 @@
         GTEST_SKIP() << "This test requires multiple threads";
     }
 
-    constexpr size_t kNumThreads = 10;
+    constexpr size_t kNumThreads = 5;
 
     auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
 
@@ -499,11 +506,11 @@
 
     EXPECT_GE(epochMsAfter, epochMsBefore + 2 * sleepMs);
 
-    // Potential flake, but make sure calls are handled in parallel. Due
-    // to past flakes, this only checks that the amount of time taken has
-    // some parallelism. Other tests such as ThreadPoolGreaterThanEqualRequested
-    // check this more exactly.
-    EXPECT_LE(epochMsAfter, epochMsBefore + (numCalls - 1) * sleepMs);
+    // b/272429574, b/365294257
+    // This flakes too much to test. Parallelization is tested
+    // in ThreadPoolGreaterThanEqualRequested and other tests.
+    // Test to make sure calls are handled in parallel.
+    // EXPECT_LE(epochMsAfter, epochMsBefore + (numCalls - 1) * sleepMs);
 }
 
 TEST_P(BinderRpc, ThreadPoolOverSaturated) {
@@ -515,8 +522,7 @@
     constexpr size_t kNumCalls = kNumThreads + 3;
     auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
 
-    // b/272429574 - below 500ms, the test fails
-    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 500 /*ms*/);
+    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 200 /*ms*/);
 }
 
 TEST_P(BinderRpc, ThreadPoolLimitOutgoing) {
@@ -530,8 +536,7 @@
     auto proc = createRpcTestSocketServerProcess(
             {.numThreads = kNumThreads, .numOutgoingConnections = kNumOutgoingConnections});
 
-    // b/272429574 - below 500ms, the test fails
-    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 500 /*ms*/);
+    testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 200 /*ms*/);
 }
 
 TEST_P(BinderRpc, ThreadingStressTest) {
@@ -1206,27 +1211,29 @@
     auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
     EXPECT_EQ(OK, proc.rootBinder->pingBinder());
 
-    auto receipt = addAccessorProvider([&](const String16& name) -> sp<IBinder> {
-        return createAccessor(name,
-                              [&](const String16& name, sockaddr* outAddr,
-                                  socklen_t addrSize) -> status_t {
-                                  if (outAddr == nullptr ||
-                                      addrSize < proc.proc->sessions[0].addrLen) {
-                                      return BAD_VALUE;
-                                  }
-                                  if (name == kInstanceName) {
-                                      if (proc.proc->sessions[0].addr.ss_family == AF_UNIX) {
-                                          sockaddr_un* un = reinterpret_cast<sockaddr_un*>(
-                                                  &proc.proc->sessions[0].addr);
-                                          ALOGE("inside callback: %s", un->sun_path);
-                                      }
-                                      std::memcpy(outAddr, &proc.proc->sessions[0].addr,
-                                                  proc.proc->sessions[0].addrLen);
-                                      return OK;
-                                  }
-                                  return NAME_NOT_FOUND;
-                              });
-    });
+    auto receipt = addAccessorProvider(
+            {String8(kInstanceName).c_str()}, [&](const String16& name) -> sp<IBinder> {
+                return createAccessor(name,
+                                      [&](const String16& name, sockaddr* outAddr,
+                                          socklen_t addrSize) -> status_t {
+                                          if (outAddr == nullptr ||
+                                              addrSize < proc.proc->sessions[0].addrLen) {
+                                              return BAD_VALUE;
+                                          }
+                                          if (name == kInstanceName) {
+                                              if (proc.proc->sessions[0].addr.ss_family ==
+                                                  AF_UNIX) {
+                                                  sockaddr_un* un = reinterpret_cast<sockaddr_un*>(
+                                                          &proc.proc->sessions[0].addr);
+                                                  ALOGE("inside callback: %s", un->sun_path);
+                                              }
+                                              std::memcpy(outAddr, &proc.proc->sessions[0].addr,
+                                                          proc.proc->sessions[0].addrLen);
+                                              return OK;
+                                          }
+                                          return NAME_NOT_FOUND;
+                                      });
+            });
 
     EXPECT_FALSE(receipt.expired());
 
@@ -1253,7 +1260,8 @@
 
     bool isProviderDeleted = false;
 
-    auto receipt = addAccessorProvider([&](const String16&) -> sp<IBinder> { return nullptr; });
+    auto receipt = addAccessorProvider({String8(kInstanceName).c_str()},
+                                       [&](const String16&) -> sp<IBinder> { return nullptr; });
     EXPECT_FALSE(receipt.expired());
 
     sp<IBinder> binder = defaultServiceManager()->checkService(kInstanceName);
@@ -1263,6 +1271,32 @@
     EXPECT_EQ(status, OK);
 }
 
+TEST_P(BinderRpcAccessor, InjectDuplicateAccessorProvider) {
+    const String16 kInstanceName("super.cool.service/better_than_default");
+    const String16 kInstanceName2("super.cool.service/better_than_default2");
+
+    auto receipt =
+            addAccessorProvider({String8(kInstanceName).c_str(), String8(kInstanceName2).c_str()},
+                                [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_FALSE(receipt.expired());
+    // reject this because it's associated with an already used instance name
+    auto receipt2 = addAccessorProvider({String8(kInstanceName).c_str()},
+                                        [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_TRUE(receipt2.expired());
+
+    // the first provider should still be usable
+    sp<IBinder> binder = defaultServiceManager()->checkService(kInstanceName);
+    EXPECT_EQ(binder, nullptr);
+
+    status_t status = removeAccessorProvider(receipt);
+    EXPECT_EQ(status, OK);
+}
+
+TEST_P(BinderRpcAccessor, InjectAccessorProviderNoInstance) {
+    auto receipt = addAccessorProvider({}, [&](const String16&) -> sp<IBinder> { return nullptr; });
+    EXPECT_TRUE(receipt.expired());
+}
+
 TEST_P(BinderRpcAccessor, InjectNoSockaddrProvided) {
     constexpr size_t kNumThreads = 10;
     const String16 kInstanceName("super.cool.service/better_than_default");
@@ -1273,12 +1307,15 @@
     bool isProviderDeleted = false;
     bool isAccessorDeleted = false;
 
-    auto receipt = addAccessorProvider([&](const String16& name) -> sp<IBinder> {
-        return createAccessor(name, [&](const String16&, sockaddr*, socklen_t) -> status_t {
-            // don't fill in outAddr
-            return NAME_NOT_FOUND;
-        });
-    });
+    auto receipt = addAccessorProvider({String8(kInstanceName).c_str()},
+                                       [&](const String16& name) -> sp<IBinder> {
+                                           return createAccessor(name,
+                                                                 [&](const String16&, sockaddr*,
+                                                                     socklen_t) -> status_t {
+                                                                     // don't fill in outAddr
+                                                                     return NAME_NOT_FOUND;
+                                                                 });
+                                       });
 
     EXPECT_FALSE(receipt.expired());
 
@@ -1289,6 +1326,260 @@
     EXPECT_EQ(status, OK);
 }
 
+constexpr const char* kARpcInstance = "some.instance.name.IFoo/default";
+const char* kARpcSupportedServices[] = {
+        kARpcInstance,
+};
+const uint32_t kARpcNumSupportedServices = 1;
+
+struct ConnectionInfoData {
+    sockaddr_storage addr;
+    socklen_t len;
+    bool* isDeleted;
+    ~ConnectionInfoData() {
+        if (isDeleted) *isDeleted = true;
+    }
+};
+
+struct AccessorProviderData {
+    sockaddr_storage addr;
+    socklen_t len;
+    bool* isDeleted;
+    ~AccessorProviderData() {
+        if (isDeleted) *isDeleted = true;
+    }
+};
+
+void accessorProviderDataOnDelete(void* data) {
+    delete reinterpret_cast<AccessorProviderData*>(data);
+}
+void infoProviderDataOnDelete(void* data) {
+    delete reinterpret_cast<ConnectionInfoData*>(data);
+}
+
+ABinderRpc_ConnectionInfo* infoProvider(const char* instance, void* cookie) {
+    if (instance == nullptr || cookie == nullptr) return nullptr;
+    ConnectionInfoData* data = reinterpret_cast<ConnectionInfoData*>(cookie);
+    return ABinderRpc_ConnectionInfo_new(reinterpret_cast<const sockaddr*>(&data->addr), data->len);
+}
+
+ABinderRpc_Accessor* getAccessor(const char* instance, void* cookie) {
+    if (instance == nullptr || cookie == nullptr) return nullptr;
+    if (0 != strcmp(instance, kARpcInstance)) return nullptr;
+
+    AccessorProviderData* data = reinterpret_cast<AccessorProviderData*>(cookie);
+
+    ConnectionInfoData* info = new ConnectionInfoData{
+            .addr = data->addr,
+            .len = data->len,
+            .isDeleted = nullptr,
+    };
+
+    return ABinderRpc_Accessor_new(instance, infoProvider, info, infoProviderDataOnDelete);
+}
+
+class BinderARpcNdk : public ::testing::Test {};
+
+TEST_F(BinderARpcNdk, ARpcProviderNewDelete) {
+    bool isDeleted = false;
+
+    AccessorProviderData* data = new AccessorProviderData{{}, 0, &isDeleted};
+
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices,
+                                                kARpcNumSupportedServices, data,
+                                                accessorProviderDataOnDelete);
+
+    ASSERT_NE(provider, nullptr);
+    EXPECT_FALSE(isDeleted);
+
+    ABinderRpc_unregisterAccessorProvider(provider);
+
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST_F(BinderARpcNdk, ARpcProviderDuplicateInstance) {
+    const char* instance = "some.instance.name.IFoo/default";
+    const uint32_t numInstances = 2;
+    const char* instances[numInstances] = {
+            instance,
+            "some.other.instance/default",
+    };
+
+    bool isDeleted = false;
+
+    AccessorProviderData* data = new AccessorProviderData{{}, 0, &isDeleted};
+
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, instances, numInstances, data,
+                                                accessorProviderDataOnDelete);
+
+    ASSERT_NE(provider, nullptr);
+    EXPECT_FALSE(isDeleted);
+
+    const uint32_t numInstances2 = 1;
+    const char* instances2[numInstances2] = {
+            instance,
+    };
+    bool isDeleted2 = false;
+    AccessorProviderData* data2 = new AccessorProviderData{{}, 0, &isDeleted2};
+    ABinderRpc_AccessorProvider* provider2 =
+            ABinderRpc_registerAccessorProvider(getAccessor, instances2, numInstances2, data2,
+                                                accessorProviderDataOnDelete);
+
+    EXPECT_EQ(provider2, nullptr);
+    // If it fails to be registered, the data is still cleaned up with
+    // accessorProviderDataOnDelete
+    EXPECT_TRUE(isDeleted2);
+
+    ABinderRpc_unregisterAccessorProvider(provider);
+
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST_F(BinderARpcNdk, ARpcProviderRegisterNoInstance) {
+    const uint32_t numInstances = 0;
+    const char* instances[numInstances] = {};
+
+    bool isDeleted = false;
+    AccessorProviderData* data = new AccessorProviderData{{}, 0, &isDeleted};
+
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, instances, numInstances, data,
+                                                accessorProviderDataOnDelete);
+    ASSERT_EQ(provider, nullptr);
+}
+
+TEST_F(BinderARpcNdk, ARpcAccessorNewDelete) {
+    bool isDeleted = false;
+
+    ConnectionInfoData* data = new ConnectionInfoData{{}, 0, &isDeleted};
+
+    ABinderRpc_Accessor* accessor =
+            ABinderRpc_Accessor_new("gshoe_service", infoProvider, data, infoProviderDataOnDelete);
+    ASSERT_NE(accessor, nullptr);
+    EXPECT_FALSE(isDeleted);
+
+    ABinderRpc_Accessor_delete(accessor);
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST_F(BinderARpcNdk, ARpcConnectionInfoNewDelete) {
+    sockaddr_vm addr{
+            .svm_family = AF_VSOCK,
+            .svm_port = VMADDR_PORT_ANY,
+            .svm_cid = VMADDR_CID_ANY,
+    };
+
+    ABinderRpc_ConnectionInfo* info =
+            ABinderRpc_ConnectionInfo_new(reinterpret_cast<sockaddr*>(&addr), sizeof(sockaddr_vm));
+    EXPECT_NE(info, nullptr);
+
+    ABinderRpc_ConnectionInfo_delete(info);
+}
+
+TEST_F(BinderARpcNdk, ARpcAsFromBinderAsBinder) {
+    bool isDeleted = false;
+
+    ConnectionInfoData* data = new ConnectionInfoData{{}, 0, &isDeleted};
+
+    ABinderRpc_Accessor* accessor =
+            ABinderRpc_Accessor_new("gshoe_service", infoProvider, data, infoProviderDataOnDelete);
+    ASSERT_NE(accessor, nullptr);
+    EXPECT_FALSE(isDeleted);
+
+    {
+        ndk::SpAIBinder binder = ndk::SpAIBinder(ABinderRpc_Accessor_asBinder(accessor));
+        EXPECT_NE(binder.get(), nullptr);
+
+        ABinderRpc_Accessor* accessor2 =
+                ABinderRpc_Accessor_fromBinder("wrong_service_name", binder.get());
+        // The API checks for the expected service name that is associated with
+        // the accessor!
+        EXPECT_EQ(accessor2, nullptr);
+
+        accessor2 = ABinderRpc_Accessor_fromBinder("gshoe_service", binder.get());
+        EXPECT_NE(accessor2, nullptr);
+
+        // this is a new ABinderRpc_Accessor object that wraps the underlying
+        // libbinder object.
+        EXPECT_NE(accessor, accessor2);
+
+        ndk::SpAIBinder binder2 = ndk::SpAIBinder(ABinderRpc_Accessor_asBinder(accessor2));
+        EXPECT_EQ(binder.get(), binder2.get());
+
+        ABinderRpc_Accessor_delete(accessor2);
+    }
+
+    EXPECT_FALSE(isDeleted);
+    ABinderRpc_Accessor_delete(accessor);
+    EXPECT_TRUE(isDeleted);
+}
+
+TEST_F(BinderARpcNdk, ARpcRequireProviderOnDeleteCallback) {
+    EXPECT_EQ(nullptr,
+              ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices,
+                                                  kARpcNumSupportedServices,
+                                                  reinterpret_cast<void*>(1), nullptr));
+}
+
+TEST_F(BinderARpcNdk, ARpcRequireInfoOnDeleteCallback) {
+    EXPECT_EQ(nullptr,
+              ABinderRpc_Accessor_new("the_best_service_name", infoProvider,
+                                      reinterpret_cast<void*>(1), nullptr));
+}
+
+TEST_F(BinderARpcNdk, ARpcNoDataNoProviderOnDeleteCallback) {
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices,
+                                                kARpcNumSupportedServices, nullptr, nullptr);
+    ASSERT_NE(nullptr, provider);
+    ABinderRpc_unregisterAccessorProvider(provider);
+}
+
+TEST_F(BinderARpcNdk, ARpcNoDataNoInfoOnDeleteCallback) {
+    ABinderRpc_Accessor* accessor =
+            ABinderRpc_Accessor_new("the_best_service_name", infoProvider, nullptr, nullptr);
+    ASSERT_NE(nullptr, accessor);
+    ABinderRpc_Accessor_delete(accessor);
+}
+
+TEST_F(BinderARpcNdk, ARpcNullArgs_ConnectionInfo_new) {
+    sockaddr_storage addr;
+    EXPECT_EQ(nullptr, ABinderRpc_ConnectionInfo_new(reinterpret_cast<const sockaddr*>(&addr), 0));
+}
+
+TEST_P(BinderRpcAccessor, ARpcGetService) {
+    constexpr size_t kNumThreads = 10;
+    bool isDeleted = false;
+
+    auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
+    EXPECT_EQ(OK, proc.rootBinder->pingBinder());
+
+    AccessorProviderData* data =
+            new AccessorProviderData{proc.proc->sessions[0].addr, proc.proc->sessions[0].addrLen,
+                                     &isDeleted};
+
+    ABinderRpc_AccessorProvider* provider =
+            ABinderRpc_registerAccessorProvider(getAccessor, kARpcSupportedServices,
+                                                kARpcNumSupportedServices, data,
+                                                accessorProviderDataOnDelete);
+
+    EXPECT_NE(provider, nullptr);
+    EXPECT_FALSE(isDeleted);
+
+    {
+        ndk::SpAIBinder binder = ndk::SpAIBinder(AServiceManager_checkService(kARpcInstance));
+        ASSERT_NE(binder.get(), nullptr);
+        EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
+    }
+
+    ABinderRpc_unregisterAccessorProvider(provider);
+    EXPECT_TRUE(isDeleted);
+
+    waitForExtraSessionCleanup(proc);
+}
+
 #endif // BINDER_WITH_KERNEL_IPC
 
 #ifdef BINDER_RPC_TO_TRUSTY_TEST
diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index fbab8f0..cac054e 100644
--- a/libs/binder/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/Android.bp
@@ -39,6 +39,7 @@
             "smoreland@google.com",
             "waghpawan@google.com",
         ],
+        triage_assignee: "smoreland@google.com",
         use_for_presubmit: true,
     },
 
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index e378b86..07f0143 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -25,6 +25,8 @@
 #include <binder/ParcelableHolder.h>
 #include <binder/PersistableBundle.h>
 #include <binder/Status.h>
+#include <fuzzbinder/random_binder.h>
+#include <fuzzbinder/random_fd.h>
 #include <utils/Flattenable.h>
 
 #include "../../Utils.h"
@@ -115,14 +117,6 @@
         p.setDataPosition(pos);
         FUZZ_LOG() << "setDataPosition done";
     },
-    [] (const ::android::Parcel& p, FuzzedDataProvider& provider) {
-        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
-        std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
-        FUZZ_LOG() << "about to setData: " <<(bytes.data() ? HexString(bytes.data(), bytes.size()) : "null");
-        // TODO: allow all read and write operations
-        (*const_cast<::android::Parcel*>(&p)).setData(bytes.data(), bytes.size());
-        FUZZ_LOG() << "setData done";
-    },
     PARCEL_READ_NO_STATUS(size_t, allowFds),
     PARCEL_READ_NO_STATUS(size_t, hasFileDescriptors),
     PARCEL_READ_NO_STATUS(std::vector<android::sp<android::IBinder>>, debugReadAllStrongBinders),
@@ -404,5 +398,113 @@
         FUZZ_LOG() << " toString() result: " << toString;
     },
 };
+
+std::vector<ParcelWrite<::android::Parcel>> BINDER_PARCEL_WRITE_FUNCTIONS {
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call setDataSize";
+        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+        p.setDataSize(len);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call setDataCapacity";
+        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+        p.setDataCapacity(len);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call setData";
+        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1024);
+        std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
+        p.setData(bytes.data(), bytes.size());
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+        FUZZ_LOG() << "about to call appendFrom";
+
+        std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(provider.ConsumeIntegralInRange<size_t>(0, 4096));
+        ::android::Parcel p2;
+        fillRandomParcel(&p2, FuzzedDataProvider(bytes.data(), bytes.size()), options);
+
+        int32_t start = provider.ConsumeIntegral<int32_t>();
+        int32_t len = provider.ConsumeIntegral<int32_t>();
+        p.appendFrom(&p2, start, len);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call pushAllowFds";
+        bool val = provider.ConsumeBool();
+        p.pushAllowFds(val);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call restoreAllowFds";
+        bool val = provider.ConsumeBool();
+        p.restoreAllowFds(val);
+    },
+    // markForBinder - covered by fillRandomParcel, aborts if called multiple times
+    // markForRpc - covered by fillRandomParcel, aborts if called multiple times
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call writeInterfaceToken";
+        std::string interface = provider.ConsumeRandomLengthString();
+        p.writeInterfaceToken(android::String16(interface.c_str()));
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call setEnforceNoDataAvail";
+        p.setEnforceNoDataAvail(provider.ConsumeBool());
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call setServiceFuzzing";
+        p.setServiceFuzzing();
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call freeData";
+        p.freeData();
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call write";
+        size_t len = provider.ConsumeIntegralInRange<size_t>(0, 256);
+        std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(len);
+        p.write(bytes.data(), bytes.size());
+    },
+    // write* - write functions all implemented by calling 'write' itself.
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+        FUZZ_LOG() << "about to call writeStrongBinder";
+
+        // TODO: this logic is somewhat duplicated with random parcel
+       android::sp<android::IBinder> binder;
+       if (provider.ConsumeBool() && options->extraBinders.size() > 0) {
+            binder = options->extraBinders.at(
+                    provider.ConsumeIntegralInRange<size_t>(0, options->extraBinders.size() - 1));
+        } else {
+            binder = android::getRandomBinder(&provider);
+            options->extraBinders.push_back(binder);
+        }
+
+        p.writeStrongBinder(binder);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call writeFileDescriptor (no ownership)";
+        p.writeFileDescriptor(STDERR_FILENO, false /* takeOwnership */);
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+        FUZZ_LOG() << "about to call writeFileDescriptor (take ownership)";
+        std::vector<unique_fd> fds = android::getRandomFds(&provider);
+        if (fds.size() == 0) return;
+
+        p.writeDupFileDescriptor(fds.at(0).get());
+        options->extraFds.insert(options->extraFds.end(),
+             std::make_move_iterator(fds.begin() + 1),
+             std::make_move_iterator(fds.end()));
+    },
+    // TODO: writeBlob
+    // TODO: writeDupImmutableBlobFileDescriptor
+    // TODO: writeObject (or make the API private more likely)
+    [] (::android::Parcel& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call writeNoException";
+        p.writeNoException();
+    },
+    [] (::android::Parcel& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+        FUZZ_LOG() << "about to call replaceCallingWorkSourceUid";
+        uid_t uid = provider.ConsumeIntegral<uid_t>();
+        p.replaceCallingWorkSourceUid(uid);
+    },
+};
+
 // clang-format on
 #pragma clang diagnostic pop
diff --git a/libs/binder/tests/parcel_fuzzer/binder.h b/libs/binder/tests/parcel_fuzzer/binder.h
index 0c51d68..b0ac140 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.h
+++ b/libs/binder/tests/parcel_fuzzer/binder.h
@@ -21,3 +21,4 @@
 #include "parcel_fuzzer.h"
 
 extern std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS;
+extern std::vector<ParcelWrite<::android::Parcel>> BINDER_PARCEL_WRITE_FUNCTIONS;
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
index 3a1471e..3f8d71d 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
@@ -20,8 +20,11 @@
 #include "aidl/parcelables/GenericDataParcelable.h"
 #include "aidl/parcelables/SingleDataParcelable.h"
 
+#include <android/binder_libbinder.h>
 #include <android/binder_parcel_utils.h>
 #include <android/binder_parcelable_utils.h>
+#include <fuzzbinder/random_binder.h>
+#include <fuzzbinder/random_fd.h>
 
 #include "util.h"
 
@@ -49,7 +52,8 @@
     return STATUS_UNKNOWN_TRANSACTION;
 }
 
-static AIBinder_Class* g_class = ::ndk::ICInterface::defineClass("ISomeInterface", onTransact);
+static AIBinder_Class* g_class =
+        ::ndk::ICInterface::defineClass("ISomeInterface", onTransact, nullptr, 0);
 
 class BpSomeInterface : public ::ndk::BpCInterface<ISomeInterface> {
 public:
@@ -210,16 +214,51 @@
             binder_status_t status = AParcel_marshal(p.aParcel(), buffer, start, len);
             FUZZ_LOG() << "status: " << status;
         },
-        [](const NdkParcelAdapter& /*p*/, FuzzedDataProvider& provider) {
-            FUZZ_LOG() << "about to unmarshal AParcel";
+};
+std::vector<ParcelWrite<NdkParcelAdapter>> BINDER_NDK_PARCEL_WRITE_FUNCTIONS{
+        [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+            FUZZ_LOG() << "about to call AParcel_writeStrongBinder";
+
+            // TODO: this logic is somewhat duplicated with random parcel
+            android::sp<android::IBinder> binder;
+            if (provider.ConsumeBool() && options->extraBinders.size() > 0) {
+                binder = options->extraBinders.at(
+                        provider.ConsumeIntegralInRange<size_t>(0, options->extraBinders.size() - 1));
+            } else {
+                binder = android::getRandomBinder(&provider);
+                options->extraBinders.push_back(binder);
+            }
+
+            ndk::SpAIBinder abinder = ndk::SpAIBinder(AIBinder_fromPlatformBinder(binder));
+            AParcel_writeStrongBinder(p.aParcel(), abinder.get());
+        },
+        [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* options) {
+            FUZZ_LOG() << "about to call AParcel_writeParcelFileDescriptor";
+
+            auto fds = android::getRandomFds(&provider);
+            if (fds.size() == 0) return;
+
+            AParcel_writeParcelFileDescriptor(p.aParcel(), fds.at(0).get());
+            options->extraFds.insert(options->extraFds.end(),
+                 std::make_move_iterator(fds.begin() + 1),
+                 std::make_move_iterator(fds.end()));
+        },
+        // all possible data writes can be done as a series of 4-byte reads
+        [] (NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+            FUZZ_LOG() << "about to call AParcel_writeInt32";
+            int32_t val = provider.ConsumeIntegral<int32_t>();
+            AParcel_writeInt32(p.aParcel(), val);
+        },
+        [] (NdkParcelAdapter& p, FuzzedDataProvider& /* provider */, android::RandomParcelOptions* /*options*/) {
+            FUZZ_LOG() << "about to call AParcel_reset";
+            AParcel_reset(p.aParcel());
+        },
+        [](NdkParcelAdapter& p, FuzzedDataProvider& provider, android::RandomParcelOptions* /*options*/) {
+            FUZZ_LOG() << "about to call AParcel_unmarshal";
             size_t len = provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes());
-            std::vector<uint8_t> parcelData = provider.ConsumeBytes<uint8_t>(len);
-            const uint8_t* buffer = parcelData.data();
-            const size_t bufferLen = parcelData.size();
-            NdkParcelAdapter adapter;
-            binder_status_t status = AParcel_unmarshal(adapter.aParcel(), buffer, bufferLen);
+            std::vector<uint8_t> data = provider.ConsumeBytes<uint8_t>(len);
+            binder_status_t status = AParcel_unmarshal(p.aParcel(), data.data(), data.size());
             FUZZ_LOG() << "status: " << status;
         },
-
 };
 // clang-format on
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.h b/libs/binder/tests/parcel_fuzzer/binder_ndk.h
index d19f25b..0c8b725 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.h
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.h
@@ -50,3 +50,4 @@
 };
 
 extern std::vector<ParcelRead<NdkParcelAdapter>> BINDER_NDK_PARCEL_READ_FUNCTIONS;
+extern std::vector<ParcelWrite<NdkParcelAdapter>> BINDER_NDK_PARCEL_WRITE_FUNCTIONS;
diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp
index a57d07f..192f9d5 100644
--- a/libs/binder/tests/parcel_fuzzer/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/main.cpp
@@ -80,6 +80,7 @@
     (void)binder->transact(code, data, &reply, flag);
 }
 
+// start with a Parcel full of data (e.g. like you get from another process)
 template <typename P>
 void doReadFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
                 FuzzedDataProvider&& provider) {
@@ -95,10 +96,10 @@
     RandomParcelOptions options;
 
     P p;
-    fillRandomParcel(&p, std::move(provider), &options);
+    fillRandomParcel(&p, std::move(provider), &options); // consumes provider
 
     // since we are only using a byte to index
-    CHECK(reads.size() <= 255) << reads.size();
+    CHECK_LE(reads.size(), 255u) << reads.size();
 
     FUZZ_LOG() << "backend: " << backend;
     FUZZ_LOG() << "input: " << HexString(p.data(), p.dataSize());
@@ -115,26 +116,29 @@
     }
 }
 
-// Append two random parcels.
 template <typename P>
-void doAppendFuzz(const char* backend, FuzzedDataProvider&& provider) {
-    int32_t start = provider.ConsumeIntegral<int32_t>();
-    int32_t len = provider.ConsumeIntegral<int32_t>();
-
-    std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(
-            provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes()));
-
-    // same options so that FDs and binders could be shared in both Parcels
+void doReadWriteFuzz(const char* backend, const std::vector<ParcelRead<P>>& reads,
+                     const std::vector<ParcelWrite<P>>& writes, FuzzedDataProvider&& provider) {
     RandomParcelOptions options;
+    P p;
 
-    P p0, p1;
-    fillRandomParcel(&p0, FuzzedDataProvider(bytes.data(), bytes.size()), &options);
-    fillRandomParcel(&p1, std::move(provider), &options);
+    // since we are only using a byte to index
+    CHECK_LE(reads.size() + writes.size(), 255u) << reads.size();
 
     FUZZ_LOG() << "backend: " << backend;
-    FUZZ_LOG() << "start: " << start << " len: " << len;
 
-    p0.appendFrom(&p1, start, len);
+    while (provider.remaining_bytes() > 0) {
+        uint8_t idx = provider.ConsumeIntegralInRange<uint8_t>(0, reads.size() + writes.size() - 1);
+
+        FUZZ_LOG() << "Instruction " << idx << " avail: " << p.dataAvail()
+                   << " pos: " << p.dataPosition() << " cap: " << p.dataCapacity();
+
+        if (idx < reads.size()) {
+            reads.at(idx)(p, provider);
+        } else {
+            writes.at(idx - reads.size())(p, provider, &options);
+        }
+    }
 }
 
 void* NothingClass_onCreate(void* args) {
@@ -156,7 +160,7 @@
 
     FuzzedDataProvider provider = FuzzedDataProvider(data, size);
 
-    const std::function<void(FuzzedDataProvider &&)> fuzzBackend[] = {
+    const std::function<void(FuzzedDataProvider&&)> fuzzBackend[] = {
             [](FuzzedDataProvider&& provider) {
                 doTransactFuzz<
                         ::android::hardware::Parcel>("hwbinder",
@@ -187,10 +191,14 @@
                                              std::move(provider));
             },
             [](FuzzedDataProvider&& provider) {
-                doAppendFuzz<::android::Parcel>("binder", std::move(provider));
+                doReadWriteFuzz<::android::Parcel>("binder", BINDER_PARCEL_READ_FUNCTIONS,
+                                                   BINDER_PARCEL_WRITE_FUNCTIONS,
+                                                   std::move(provider));
             },
             [](FuzzedDataProvider&& provider) {
-                doAppendFuzz<NdkParcelAdapter>("binder_ndk", std::move(provider));
+                doReadWriteFuzz<NdkParcelAdapter>("binder_ndk", BINDER_NDK_PARCEL_READ_FUNCTIONS,
+                                                  BINDER_NDK_PARCEL_WRITE_FUNCTIONS,
+                                                  std::move(provider));
             },
     };
 
diff --git a/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h b/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h
index 765a93e..dbd0cae 100644
--- a/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h
+++ b/libs/binder/tests/parcel_fuzzer/parcel_fuzzer.h
@@ -15,9 +15,13 @@
  */
 #pragma once
 
+#include <fuzzbinder/random_parcel.h>
 #include <fuzzer/FuzzedDataProvider.h>
 
 #include <functional>
 
 template <typename P>
 using ParcelRead = std::function<void(const P& p, FuzzedDataProvider& provider)>;
+template <typename P>
+using ParcelWrite = std::function<void(P& p, FuzzedDataProvider& provider,
+                                       android::RandomParcelOptions* options)>;
diff --git a/libs/binder/trusty/OS.cpp b/libs/binder/trusty/OS.cpp
index 157ab3c..ba9e457 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -42,6 +42,10 @@
 
 void trace_int(uint64_t, const char*, int32_t) {}
 
+uint64_t get_trace_enabled_tags() {
+    return 0;
+}
+
 uint64_t GetThreadId() {
     return 0;
 }
diff --git a/libs/binder/trusty/ndk/include/android/llndk-versioning.h b/libs/binder/trusty/ndk/include/android/llndk-versioning.h
index 3ae3d8f..e955a34 100644
--- a/libs/binder/trusty/ndk/include/android/llndk-versioning.h
+++ b/libs/binder/trusty/ndk/include/android/llndk-versioning.h
@@ -15,4 +15,5 @@
  */
 #pragma once
 
-#define __INTRODUCED_IN_LLNDK(x) /* nothing on Trusty */
+// TODO(b/349936395): set to true for Trusty
+#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) (false)
diff --git a/libs/binder/trusty/rust/binder_rpc_test/main.rs b/libs/binder/trusty/rust/binder_rpc_test/main.rs
index baea5a8..da1a86f 100644
--- a/libs/binder/trusty/rust/binder_rpc_test/main.rs
+++ b/libs/binder/trusty/rust/binder_rpc_test/main.rs
@@ -19,7 +19,7 @@
 use binder_rpc_test_aidl::aidl::IBinderRpcSession::{BnBinderRpcSession, IBinderRpcSession};
 use binder_rpc_test_aidl::aidl::IBinderRpcTest::{BnBinderRpcTest, IBinderRpcTest};
 use binder_rpc_test_session::MyBinderRpcSession;
-use libc::{clock_gettime, CLOCK_REALTIME};
+use libc::{clock_gettime, CLOCK_BOOTTIME};
 use rpcbinder::RpcSession;
 use trusty_std::ffi::{CString, FallibleCString};
 
@@ -56,7 +56,7 @@
     let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
 
     // Safety: Passing valid pointer to variable ts which lives past end of call
-    assert_eq!(unsafe { clock_gettime(CLOCK_REALTIME, &mut ts) }, 0);
+    assert_eq!(unsafe { clock_gettime(CLOCK_BOOTTIME, &mut ts) }, 0);
 
     ts.tv_sec as u64 * 1_000_000_000u64 + ts.tv_nsec as u64
 }
diff --git a/libs/binder/trusty/rust/rpcbinder/rules.mk b/libs/binder/trusty/rust/rpcbinder/rules.mk
index 97f5c03..04c63f7 100644
--- a/libs/binder/trusty/rust/rpcbinder/rules.mk
+++ b/libs/binder/trusty/rust/rpcbinder/rules.mk
@@ -29,8 +29,8 @@
 	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_server_bindgen \
-	external/rust/crates/cfg-if \
-	external/rust/crates/foreign-types \
+	$(call FIND_CRATE,cfg-if) \
+	$(call FIND_CRATE,foreign-types) \
 	trusty/user/base/lib/tipc/rust \
 	trusty/user/base/lib/trusty-sys \
 
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
index 36bd3a2..e622b22 100644
--- a/libs/binder/trusty/rust/rules.mk
+++ b/libs/binder/trusty/rust/rules.mk
@@ -27,8 +27,8 @@
 	$(LIBBINDER_DIR)/trusty/ndk \
 	$(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
 	$(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
-	external/rust/crates/downcast-rs \
-	external/rust/crates/libc \
+	$(call FIND_CRATE,downcast-rs) \
+	$(call FIND_CRATE,libc) \
 	trusty/user/base/lib/trusty-sys \
 
 MODULE_RUSTFLAGS += \
diff --git a/libs/debugstore/OWNERS b/libs/debugstore/OWNERS
index 428a1a2..c8e22b7 100644
--- a/libs/debugstore/OWNERS
+++ b/libs/debugstore/OWNERS
@@ -1,3 +1,2 @@
 benmiles@google.com
-gaillard@google.com
 mohamadmahmoud@google.com
diff --git a/libs/debugstore/rust/Android.bp b/libs/debugstore/rust/Android.bp
index 55ba3c3..9475333 100644
--- a/libs/debugstore/rust/Android.bp
+++ b/libs/debugstore/rust/Android.bp
@@ -23,7 +23,6 @@
     rustlibs: [
         "libcrossbeam_queue",
         "libparking_lot",
-        "libonce_cell",
         "libcxx",
     ],
     shared_libs: ["libutils"],
diff --git a/libs/debugstore/rust/src/core.rs b/libs/debugstore/rust/src/core.rs
index 1dfa512..6bf79d4 100644
--- a/libs/debugstore/rust/src/core.rs
+++ b/libs/debugstore/rust/src/core.rs
@@ -17,12 +17,14 @@
 use super::event_type::EventType;
 use super::storage::Storage;
 use crate::cxxffi::uptimeMillis;
-use once_cell::sync::Lazy;
 use std::fmt;
-use std::sync::atomic::{AtomicU64, Ordering};
+use std::sync::{
+    atomic::{AtomicU64, Ordering},
+    LazyLock,
+};
 
 //  Lazily initialized static instance of DebugStore.
-static INSTANCE: Lazy<DebugStore> = Lazy::new(DebugStore::new);
+static INSTANCE: LazyLock<DebugStore> = LazyLock::new(DebugStore::new);
 
 /// The `DebugStore` struct is responsible for managing debug events and data.
 pub struct DebugStore {
diff --git a/libs/ftl/non_null_test.cpp b/libs/ftl/non_null_test.cpp
index 367b398..457237c 100644
--- a/libs/ftl/non_null_test.cpp
+++ b/libs/ftl/non_null_test.cpp
@@ -81,6 +81,31 @@
 static_assert(std::is_same_v<decltype(ftl::as_non_null(std::declval<const int* const>())),
                              ftl::NonNull<const int*>>);
 
+class Base {};
+class Derived : public Base {};
+
+static_assert(std::is_constructible_v<ftl::NonNull<void*>, ftl::NonNull<int*>>);
+static_assert(!std::is_constructible_v<ftl::NonNull<int*>, ftl::NonNull<void*>>);
+static_assert(std::is_constructible_v<ftl::NonNull<const int*>, ftl::NonNull<int*>>);
+static_assert(!std::is_constructible_v<ftl::NonNull<int*>, ftl::NonNull<const int*>>);
+static_assert(std::is_constructible_v<ftl::NonNull<Base*>, ftl::NonNull<Derived*>>);
+static_assert(!std::is_constructible_v<ftl::NonNull<Derived*>, ftl::NonNull<Base*>>);
+static_assert(std::is_constructible_v<ftl::NonNull<std::unique_ptr<const int>>,
+                                      ftl::NonNull<std::unique_ptr<int>>>);
+static_assert(std::is_constructible_v<ftl::NonNull<std::unique_ptr<Base>>,
+                                      ftl::NonNull<std::unique_ptr<Derived>>>);
+
+static_assert(std::is_assignable_v<ftl::NonNull<void*>, ftl::NonNull<int*>>);
+static_assert(!std::is_assignable_v<ftl::NonNull<int*>, ftl::NonNull<void*>>);
+static_assert(std::is_assignable_v<ftl::NonNull<const int*>, ftl::NonNull<int*>>);
+static_assert(!std::is_assignable_v<ftl::NonNull<int*>, ftl::NonNull<const int*>>);
+static_assert(std::is_assignable_v<ftl::NonNull<Base*>, ftl::NonNull<Derived*>>);
+static_assert(!std::is_assignable_v<ftl::NonNull<Derived*>, ftl::NonNull<Base*>>);
+static_assert(std::is_assignable_v<ftl::NonNull<std::unique_ptr<const int>>,
+                                   ftl::NonNull<std::unique_ptr<int>>>);
+static_assert(std::is_assignable_v<ftl::NonNull<std::unique_ptr<Base>>,
+                                   ftl::NonNull<std::unique_ptr<Derived>>>);
+
 }  // namespace
 
 TEST(NonNull, SwapRawPtr) {
@@ -156,4 +181,14 @@
   EXPECT_TRUE(ftl::contains(spi, *spi.begin()));
 }
 
+TEST(NonNull, ImplicitConversion) {
+  int i = 123;
+  int j = 345;
+  auto ip = ftl::as_non_null(&i);
+  ftl::NonNull<void*> vp{ip};
+  EXPECT_EQ(vp.get(), &i);
+  vp = ftl::as_non_null(&j);
+  EXPECT_EQ(vp.get(), &j);
+}
+
 }  // namespace android::test
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 3c1971f..25e6a52 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -1124,6 +1124,17 @@
         AsyncWorker::getInstance().post(
                 [listener = mListener, slots = slots]() { listener->onBuffersDiscarded(slots); });
     }
+
+    void onBufferDetached(int slot) override {
+        AsyncWorker::getInstance().post(
+                [listener = mListener, slot = slot]() { listener->onBufferDetached(slot); });
+    }
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK)
+    void onBufferAttached() override {
+        AsyncWorker::getInstance().post([listener = mListener]() { listener->onBufferAttached(); });
+    }
+#endif
 };
 
 // Extends the BufferQueueProducer to create a wrapper around the listener so the listener calls
@@ -1255,6 +1266,11 @@
     mTransactionHangCallback = std::move(callback);
 }
 
+void BLASTBufferQueue::setApplyToken(sp<IBinder> applyToken) {
+    std::lock_guard _lock{mMutex};
+    mApplyToken = std::move(applyToken);
+}
+
 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BUFFER_RELEASE_CHANNEL)
 
 BLASTBufferQueue::BufferReleaseReader::BufferReleaseReader(
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index a4d105d..da74e9c 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -45,7 +45,10 @@
 
 #include <system/window.h>
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android {
+using namespace com::android::graphics::libgui;
 
 // Macros for include BufferQueueCore information in log messages
 #define BQ_LOGV(x, ...)                                                                           \
@@ -924,6 +927,7 @@
     uint64_t currentFrameNumber = 0;
     BufferItem item;
     int connectedApi;
+    bool enableEglCpuThrottling = true;
     sp<Fence> lastQueuedFence;
 
     { // Autolock scope
@@ -1097,6 +1101,9 @@
         VALIDATE_CONSISTENCY();
 
         connectedApi = mCore->mConnectedApi;
+        if (flags::bq_producer_throttles_only_async_mode()) {
+            enableEglCpuThrottling = mCore->mAsyncMode || mCore->mDequeueBufferCannotBlock;
+        }
         lastQueuedFence = std::move(mLastQueueBufferFence);
 
         mLastQueueBufferFence = std::move(acquireFence);
@@ -1142,7 +1149,7 @@
     }
 
     // Wait without lock held
-    if (connectedApi == NATIVE_WINDOW_API_EGL) {
+    if (connectedApi == NATIVE_WINDOW_API_EGL && enableEglCpuThrottling) {
         // Waiting here allows for two full buffers to be queued but not a
         // third. In the event that frames take varying time, this makes a
         // small trade-off in favor of latency rather than throughput.
diff --git a/libs/gui/IProducerListener.cpp b/libs/gui/IProducerListener.cpp
index 7700795..8b9b090 100644
--- a/libs/gui/IProducerListener.cpp
+++ b/libs/gui/IProducerListener.cpp
@@ -184,4 +184,10 @@
 void BnProducerListener::onBuffersDiscarded(const std::vector<int32_t>& /*discardedSlots*/) {
 }
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK)
+bool BnProducerListener::needsAttachNotify() {
+    return true;
+}
+#endif
+
 } // namespace android
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index b109969..422c57b 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -69,7 +69,7 @@
         color(0),
         bufferTransform(0),
         transformToDisplayInverse(false),
-        crop(Rect::INVALID_RECT),
+        crop({0, 0, -1, -1}),
         dataspace(ui::Dataspace::UNKNOWN),
         surfaceDamageRegion(),
         api(-1),
@@ -109,7 +109,10 @@
     SAFE_PARCEL(output.writeUint32, flags);
     SAFE_PARCEL(output.writeUint32, mask);
     SAFE_PARCEL(matrix.write, output);
-    SAFE_PARCEL(output.write, crop);
+    SAFE_PARCEL(output.writeFloat, crop.top);
+    SAFE_PARCEL(output.writeFloat, crop.left);
+    SAFE_PARCEL(output.writeFloat, crop.bottom);
+    SAFE_PARCEL(output.writeFloat, crop.right);
     SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, relativeLayerSurfaceControl);
     SAFE_PARCEL(SurfaceControl::writeNullableToParcel, output, parentSurfaceControlForChild);
     SAFE_PARCEL(output.writeFloat, color.r);
@@ -218,7 +221,10 @@
     SAFE_PARCEL(input.readUint32, &mask);
 
     SAFE_PARCEL(matrix.read, input);
-    SAFE_PARCEL(input.read, crop);
+    SAFE_PARCEL(input.readFloat, &crop.top);
+    SAFE_PARCEL(input.readFloat, &crop.left);
+    SAFE_PARCEL(input.readFloat, &crop.bottom);
+    SAFE_PARCEL(input.readFloat, &crop.right);
 
     SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &relativeLayerSurfaceControl);
     SAFE_PARCEL(SurfaceControl::readNullableFromParcel, input, &parentSurfaceControlForChild);
diff --git a/libs/gui/ScreenCaptureResults.cpp b/libs/gui/ScreenCaptureResults.cpp
index 601a5f9..2de023e 100644
--- a/libs/gui/ScreenCaptureResults.cpp
+++ b/libs/gui/ScreenCaptureResults.cpp
@@ -40,6 +40,13 @@
     SAFE_PARCEL(parcel->writeBool, capturedSecureLayers);
     SAFE_PARCEL(parcel->writeBool, capturedHdrLayers);
     SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(capturedDataspace));
+    if (optionalGainMap != nullptr) {
+        SAFE_PARCEL(parcel->writeBool, true);
+        SAFE_PARCEL(parcel->write, *optionalGainMap);
+    } else {
+        SAFE_PARCEL(parcel->writeBool, false);
+    }
+    SAFE_PARCEL(parcel->writeFloat, hdrSdrRatio);
     return NO_ERROR;
 }
 
@@ -68,6 +75,14 @@
     uint32_t dataspace = 0;
     SAFE_PARCEL(parcel->readUint32, &dataspace);
     capturedDataspace = static_cast<ui::Dataspace>(dataspace);
+
+    bool hasGainmap;
+    SAFE_PARCEL(parcel->readBool, &hasGainmap);
+    if (hasGainmap) {
+        optionalGainMap = new GraphicBuffer();
+        SAFE_PARCEL(parcel->read, *optionalGainMap);
+    }
+    SAFE_PARCEL(parcel->readFloat, &hdrSdrRatio);
     return NO_ERROR;
 }
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index df58df4..6bb80dc 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1539,14 +1539,7 @@
         mStatus = BAD_INDEX;
         return *this;
     }
-    if ((mask & layer_state_t::eLayerOpaque) || (mask & layer_state_t::eLayerHidden) ||
-        (mask & layer_state_t::eLayerSecure) || (mask & layer_state_t::eLayerSkipScreenshot) ||
-        (mask & layer_state_t::eEnableBackpressure) ||
-        (mask & layer_state_t::eIgnoreDestinationFrame) ||
-        (mask & layer_state_t::eLayerIsDisplayDecoration) ||
-        (mask & layer_state_t::eLayerIsRefreshRateIndicator)) {
-        s->what |= layer_state_t::eFlagsChanged;
-    }
+    s->what |= layer_state_t::eFlagsChanged;
     s->flags &= ~mask;
     s->flags |= (flags & mask);
     s->mask |= mask;
@@ -1652,6 +1645,11 @@
 
 SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCrop(
         const sp<SurfaceControl>& sc, const Rect& crop) {
+    return setCrop(sc, crop.toFloatRect());
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setCrop(
+        const sp<SurfaceControl>& sc, const FloatRect& crop) {
     layer_state_t* s = getLayerState(sc);
     if (!s) {
         mStatus = BAD_INDEX;
diff --git a/libs/gui/SurfaceControl.cpp b/libs/gui/SurfaceControl.cpp
index c5f9c38..f126c0b 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -139,9 +139,9 @@
     uint32_t ignore;
     auto flags = mCreateFlags & (ISurfaceComposerClient::eCursorWindow |
                                  ISurfaceComposerClient::eOpaque);
-    mBbqChild = mClient->createSurface(String8("bbq-wrapper"), 0, 0, mFormat,
+    mBbqChild = mClient->createSurface(String8::format("[BBQ] %s", mName.c_str()), 0, 0, mFormat,
                                        flags, mHandle, {}, &ignore);
-    mBbq = sp<BLASTBufferQueue>::make("bbq-adapter", mBbqChild, mWidth, mHeight, mFormat);
+    mBbq = sp<BLASTBufferQueue>::make("[BBQ]" + mName, mBbqChild, mWidth, mHeight, mFormat);
 
     // This surface is always consumed by SurfaceFlinger, so the
     // producerControlledByApp value doesn't matter; using false.
diff --git a/libs/gui/aidl/android/gui/CaptureArgs.aidl b/libs/gui/aidl/android/gui/CaptureArgs.aidl
index 2bbed2b..4920344 100644
--- a/libs/gui/aidl/android/gui/CaptureArgs.aidl
+++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl
@@ -69,5 +69,10 @@
     // exact colorspace is not an appropriate intermediate result.
     // Note that if the caller is requesting a specific dataspace, this hint does nothing.
     boolean hintForSeamlessTransition = false;
+
+    // Allows the screenshot to attach a gainmap, which allows for a per-pixel
+    // transformation of the screenshot to another luminance range, typically
+    // mapping an SDR base image into HDR.
+    boolean attachGainmap = false;
 }
 
diff --git a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
index 97a9035..f4ef16d 100644
--- a/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
+++ b/libs/gui/aidl/android/gui/ScreenCaptureResults.aidl
@@ -16,4 +16,4 @@
 
 package android.gui;
 
-parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h" rust_type "gui_aidl_types_rs::ScreenCaptureResults";
\ No newline at end of file
+parcelable ScreenCaptureResults cpp_header "gui/ScreenCaptureResults.h" rust_type "gui_aidl_types_rs::ScreenCaptureResults";
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index d787d6c..8592cff 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -142,7 +142,7 @@
      * indicates the reason for the hang.
      */
     void setTransactionHangCallback(std::function<void(const std::string&)> callback);
-
+    void setApplyToken(sp<IBinder>);
     virtual ~BLASTBufferQueue();
 
     void onFirstRef() override;
@@ -271,7 +271,7 @@
 
     // Queues up transactions using this token in SurfaceFlinger. This prevents queued up
     // transactions from other parts of the client from blocking this transaction.
-    const sp<IBinder> mApplyToken GUARDED_BY(mMutex) = sp<BBinder>::make();
+    sp<IBinder> mApplyToken GUARDED_BY(mMutex) = sp<BBinder>::make();
 
     // Guards access to mDequeueTimestamps since we cannot hold to mMutex in onFrameDequeued or
     // we will deadlock.
diff --git a/libs/gui/include/gui/Flags.h b/libs/gui/include/gui/Flags.h
new file mode 100644
index 0000000..735375a
--- /dev/null
+++ b/libs/gui/include/gui/Flags.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 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 <com_android_graphics_libgui_flags.h>
+
+#define WB_CAMERA3_AND_PROCESSORS_WITH_DEPENDENCIES                  \
+    (COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CAMERA3_AND_PROCESSORS) && \
+     COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_CONSUMER_BASE_OWNS_BQ) &&  \
+     COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(WB_PLATFORM_API_IMPROVEMENTS))
\ No newline at end of file
diff --git a/libs/gui/include/gui/IProducerListener.h b/libs/gui/include/gui/IProducerListener.h
index 3dcc6b6..43bf6a7 100644
--- a/libs/gui/include/gui/IProducerListener.h
+++ b/libs/gui/include/gui/IProducerListener.h
@@ -90,6 +90,9 @@
             Parcel* reply, uint32_t flags = 0);
     virtual bool needsReleaseNotify();
     virtual void onBuffersDiscarded(const std::vector<int32_t>& slots);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK)
+    virtual bool needsAttachNotify();
+#endif
 };
 
 #else
@@ -103,6 +106,9 @@
     virtual ~StubProducerListener();
     virtual void onBufferReleased() {}
     virtual bool needsReleaseNotify() { return false; }
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_CONSUMER_ATTACH_CALLBACK)
+    virtual bool needsAttachNotify() { return false; }
+#endif
 };
 
 } // namespace android
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 2cdde32..00065c8 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -330,7 +330,7 @@
     Region transparentRegion;
     uint32_t bufferTransform;
     bool transformToDisplayInverse;
-    Rect crop;
+    FloatRect crop;
     std::shared_ptr<BufferData> bufferData = nullptr;
     ui::Dataspace dataspace;
     HdrMetadata hdrMetadata;
diff --git a/libs/gui/include/gui/ScreenCaptureResults.h b/libs/gui/include/gui/ScreenCaptureResults.h
index 6e17791..f176f48 100644
--- a/libs/gui/include/gui/ScreenCaptureResults.h
+++ b/libs/gui/include/gui/ScreenCaptureResults.h
@@ -36,6 +36,11 @@
     bool capturedSecureLayers{false};
     bool capturedHdrLayers{false};
     ui::Dataspace capturedDataspace{ui::Dataspace::V0_SRGB};
+    // A gainmap that can be used to "lift" the screenshot into HDR
+    sp<GraphicBuffer> optionalGainMap;
+    // HDR/SDR ratio value that fully applies the gainmap.
+    // Note that we use 1/64 epsilon offsets to eliminate precision issues
+    float hdrSdrRatio{1.0f};
 };
 
 } // namespace android::gui
diff --git a/libs/gui/include/gui/Surface.h b/libs/gui/include/gui/Surface.h
index e74f9ad..14a3513 100644
--- a/libs/gui/include/gui/Surface.h
+++ b/libs/gui/include/gui/Surface.h
@@ -34,8 +34,6 @@
 #include <shared_mutex>
 #include <unordered_set>
 
-#include <com_android_graphics_libgui_flags.h>
-
 namespace android {
 
 class GraphicBuffer;
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index 4f9af16..2b99c0f 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -549,6 +549,7 @@
         Transaction& setMatrix(const sp<SurfaceControl>& sc,
                 float dsdx, float dtdx, float dtdy, float dsdy);
         Transaction& setCrop(const sp<SurfaceControl>& sc, const Rect& crop);
+        Transaction& setCrop(const sp<SurfaceControl>& sc, const FloatRect& crop);
         Transaction& setCornerRadius(const sp<SurfaceControl>& sc, float cornerRadius);
         Transaction& setBackgroundBlurRadius(const sp<SurfaceControl>& sc,
                                              int backgroundBlurRadius);
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
index df9b73b..d3f2899 100644
--- a/libs/gui/libgui_flags.aconfig
+++ b/libs/gui/libgui_flags.aconfig
@@ -106,4 +106,12 @@
   description: "Remove usage of IGBPs in the libcameraservice."
   bug: "342197849"
   is_fixed_read_only: true
-} # wb_libcameraservice
\ No newline at end of file
+} # wb_libcameraservice
+
+flag {
+  name: "bq_producer_throttles_only_async_mode"
+  namespace: "core_graphics"
+  description: "BufferQueueProducer only CPU throttle on queueBuffer() in async mode."
+  bug: "359252619"
+  is_fixed_read_only: true
+} # bq_producer_throttles_only_async_mode
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index eb2a61d..53f4a36 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -186,6 +186,10 @@
         mBlastBufferQueueAdapter->mergeWithNextTransaction(merge, frameNumber);
     }
 
+    void setApplyToken(sp<IBinder> applyToken) {
+        mBlastBufferQueueAdapter->setApplyToken(std::move(applyToken));
+    }
+
 private:
     sp<TestBLASTBufferQueue> mBlastBufferQueueAdapter;
 };
@@ -511,6 +515,69 @@
     adapter.waitForCallbacks();
 }
 
+class WaitForCommittedCallback {
+public:
+    WaitForCommittedCallback() = default;
+    ~WaitForCommittedCallback() = default;
+
+    void wait() {
+        std::unique_lock lock(mMutex);
+        cv.wait(lock, [this] { return mCallbackReceived; });
+    }
+
+    void notify() {
+        std::unique_lock lock(mMutex);
+        mCallbackReceived = true;
+        cv.notify_one();
+        mCallbackReceivedTimeStamp = std::chrono::system_clock::now();
+    }
+    auto getCallback() {
+        return [this](void* /* unused context */, nsecs_t /* latchTime */,
+                      const sp<Fence>& /* presentFence */,
+                      const std::vector<SurfaceControlStats>& /* stats */) { notify(); };
+    }
+    std::chrono::time_point<std::chrono::system_clock> mCallbackReceivedTimeStamp;
+
+private:
+    std::mutex mMutex;
+    std::condition_variable cv;
+    bool mCallbackReceived = false;
+};
+
+TEST_F(BLASTBufferQueueTest, setApplyToken) {
+    sp<IBinder> applyToken = sp<BBinder>::make();
+    WaitForCommittedCallback firstTransaction;
+    WaitForCommittedCallback secondTransaction;
+    {
+        BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+        adapter.setApplyToken(applyToken);
+        sp<IGraphicBufferProducer> igbProducer;
+        setUpProducer(adapter, igbProducer);
+
+        Transaction t;
+        t.addTransactionCommittedCallback(firstTransaction.getCallback(), nullptr);
+        adapter.mergeWithNextTransaction(&t, 1);
+        queueBuffer(igbProducer, 127, 127, 127,
+                    /*presentTimeDelay*/ std::chrono::nanoseconds(500ms).count());
+    }
+    {
+        BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
+        adapter.setApplyToken(applyToken);
+        sp<IGraphicBufferProducer> igbProducer;
+        setUpProducer(adapter, igbProducer);
+
+        Transaction t;
+        t.addTransactionCommittedCallback(secondTransaction.getCallback(), nullptr);
+        adapter.mergeWithNextTransaction(&t, 1);
+        queueBuffer(igbProducer, 127, 127, 127, /*presentTimeDelay*/ 0);
+    }
+
+    firstTransaction.wait();
+    secondTransaction.wait();
+    EXPECT_GT(secondTransaction.mCallbackReceivedTimeStamp,
+              firstTransaction.mCallbackReceivedTimeStamp);
+}
+
 TEST_F(BLASTBufferQueueTest, SetCrop_Item) {
     uint8_t r = 255;
     uint8_t g = 0;
diff --git a/libs/gui/tests/SamplingDemo.cpp b/libs/gui/tests/SamplingDemo.cpp
index f98437b..8fea689 100644
--- a/libs/gui/tests/SamplingDemo.cpp
+++ b/libs/gui/tests/SamplingDemo.cpp
@@ -46,7 +46,8 @@
 
         SurfaceComposerClient::Transaction{}
                 .setLayer(mButton, 0x7fffffff)
-                .setCrop(mButton, {0, 0, width - 2 * BUTTON_PADDING, height - 2 * BUTTON_PADDING})
+                .setCrop(mButton,
+                         Rect{0, 0, width - 2 * BUTTON_PADDING, height - 2 * BUTTON_PADDING})
                 .setPosition(mButton, samplingArea.left + BUTTON_PADDING,
                              samplingArea.top + BUTTON_PADDING)
                 .setColor(mButton, half3{1, 1, 1})
@@ -59,7 +60,8 @@
         SurfaceComposerClient::Transaction{}
                 .setLayer(mButtonBlend, 0x7ffffffe)
                 .setCrop(mButtonBlend,
-                         {0, 0, width - 2 * SAMPLE_AREA_PADDING, height - 2 * SAMPLE_AREA_PADDING})
+                         Rect{0, 0, width - 2 * SAMPLE_AREA_PADDING,
+                              height - 2 * SAMPLE_AREA_PADDING})
                 .setPosition(mButtonBlend, samplingArea.left + SAMPLE_AREA_PADDING,
                              samplingArea.top + SAMPLE_AREA_PADDING)
                 .setColor(mButtonBlend, half3{1, 1, 1})
@@ -75,7 +77,7 @@
 
             SurfaceComposerClient::Transaction{}
                     .setLayer(mSamplingArea, 0x7ffffffd)
-                    .setCrop(mSamplingArea, {0, 0, 100, 32})
+                    .setCrop(mSamplingArea, Rect{0, 0, 100, 32})
                     .setPosition(mSamplingArea, 490, 1606)
                     .setColor(mSamplingArea, half3{0, 1, 0})
                     .setAlpha(mSamplingArea, 0.1)
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index eb41918..ce8bb43 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -17,8 +17,6 @@
 #define LOG_TAG "InputConsumerNoResampling"
 #define ATRACE_TAG ATRACE_TAG_INPUT
 
-#include <chrono>
-
 #include <inttypes.h>
 
 #include <android-base/logging.h>
@@ -37,6 +35,10 @@
 
 namespace {
 
+using std::chrono::nanoseconds;
+
+using android::base::Result;
+
 /**
  * Log debug messages relating to the consumer end of the transport channel.
  * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
@@ -44,27 +46,6 @@
 const bool DEBUG_TRANSPORT_CONSUMER =
         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
 
-/**
- * RealLooper is a wrapper of Looper. All the member functions exclusively call the internal looper.
- * This class' behavior is the same as Looper.
- */
-class RealLooper final : public LooperInterface {
-public:
-    RealLooper(sp<Looper> looper) : mLooper{looper} {}
-
-    int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback,
-              void* data) override {
-        return mLooper->addFd(fd, ident, events, callback, data);
-    }
-
-    int removeFd(int fd) override { return mLooper->removeFd(fd); }
-
-    sp<Looper> getLooper() const override { return mLooper; }
-
-private:
-    sp<Looper> mLooper;
-};
-
 std::unique_ptr<KeyEvent> createKeyEvent(const InputMessage& msg) {
     std::unique_ptr<KeyEvent> event = std::make_unique<KeyEvent>();
     event->initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
@@ -188,24 +169,18 @@
     msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime;
     return msg;
 }
-
-bool isPointerEvent(const MotionEvent& motionEvent) {
-    return (motionEvent.getSource() & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
-}
 } // namespace
 
-using android::base::Result;
-
 // --- InputConsumerNoResampling ---
 
-InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
-                                                     std::shared_ptr<LooperInterface> looper,
-                                                     InputConsumerCallbacks& callbacks,
-                                                     std::unique_ptr<Resampler> resampler)
+InputConsumerNoResampling::InputConsumerNoResampling(
+        const std::shared_ptr<InputChannel>& channel, sp<Looper> looper,
+        InputConsumerCallbacks& callbacks,
+        std::function<std::unique_ptr<Resampler>()> resamplerCreator)
       : mChannel{channel},
         mLooper{looper},
-        mCallbacks(callbacks),
-        mResampler{std::move(resampler)},
+        mCallbacks{callbacks},
+        mResamplerCreator{std::move(resamplerCreator)},
         mFdEvents(0) {
     LOG_ALWAYS_FATAL_IF(mLooper == nullptr);
     mCallback = sp<LooperEventCallback>::make(
@@ -216,16 +191,9 @@
     setFdEvents(ALOOPER_EVENT_INPUT);
 }
 
-InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
-                                                     sp<Looper> looper,
-                                                     InputConsumerCallbacks& callbacks,
-                                                     std::unique_ptr<Resampler> resampler)
-      : InputConsumerNoResampling(channel, std::make_shared<RealLooper>(looper), callbacks,
-                                  std::move(resampler)) {}
-
 InputConsumerNoResampling::~InputConsumerNoResampling() {
     ensureCalledOnLooperThread(__func__);
-    consumeBatchedInputEvents(std::nullopt);
+    consumeBatchedInputEvents(/*requestedFrameTime=*/std::nullopt);
     while (!mOutboundQueue.empty()) {
         processOutboundEvents();
         // This is our last chance to ack the events. If we don't ack them here, we will get an ANR,
@@ -251,8 +219,7 @@
 
     int handledEvents = 0;
     if (events & ALOOPER_EVENT_INPUT) {
-        std::vector<InputMessage> messages = readAllMessages();
-        handleMessages(std::move(messages));
+        handleMessages(readAllMessages());
         handledEvents |= ALOOPER_EVENT_INPUT;
     }
 
@@ -346,7 +313,6 @@
 }
 
 void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messages) {
-    // TODO(b/297226446) : add resampling
     for (const InputMessage& msg : messages) {
         if (msg.header.type == InputMessage::Type::MOTION) {
             const int32_t action = msg.body.motion.action;
@@ -356,14 +322,31 @@
                                          action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
                     (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
                      isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
+
+            const bool canResample = (mResamplerCreator != nullptr) &&
+                    (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER));
+            if (canResample) {
+                if (action == AMOTION_EVENT_ACTION_DOWN) {
+                    if (std::unique_ptr<Resampler> resampler = mResamplerCreator();
+                        resampler != nullptr) {
+                        const auto [_, inserted] =
+                                mResamplers.insert(std::pair(deviceId, std::move(resampler)));
+                        LOG_IF(WARNING, !inserted) << deviceId << "already exists in mResamplers";
+                    }
+                }
+            }
+
             if (batchableEvent) {
                 // add it to batch
                 mBatches[deviceId].emplace(msg);
             } else {
-                // consume all pending batches for this event immediately
-                // TODO(b/329776327): figure out if this could be smarter by limiting the
-                // consumption only to the current device.
-                consumeBatchedInputEvents(std::nullopt);
+                // consume all pending batches for this device immediately
+                consumeBatchedInputEvents(deviceId, /*requestedFrameTime=*/std::nullopt);
+                if (canResample &&
+                    (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL)) {
+                    LOG_IF(INFO, mResamplers.erase(deviceId) == 0)
+                            << deviceId << "does not exist in mResamplers";
+                }
                 handleMessage(msg);
             }
         } else {
@@ -481,11 +464,21 @@
 }
 
 std::pair<std::unique_ptr<MotionEvent>, std::optional<uint32_t>>
-InputConsumerNoResampling::createBatchedMotionEvent(const nsecs_t frameTime,
+InputConsumerNoResampling::createBatchedMotionEvent(const nsecs_t requestedFrameTime,
                                                     std::queue<InputMessage>& messages) {
     std::unique_ptr<MotionEvent> motionEvent;
     std::optional<uint32_t> firstSeqForBatch;
-    while (!messages.empty() && !(messages.front().body.motion.eventTime > frameTime)) {
+
+    LOG_IF(FATAL, messages.empty()) << "messages queue is empty!";
+    const DeviceId deviceId = messages.front().body.motion.deviceId;
+    const auto resampler = mResamplers.find(deviceId);
+    const nanoseconds resampleLatency = (resampler != mResamplers.cend())
+            ? resampler->second->getResampleLatency()
+            : nanoseconds{0};
+    const nanoseconds adjustedFrameTime = nanoseconds{requestedFrameTime} - resampleLatency;
+
+    while (!messages.empty() &&
+           (messages.front().body.motion.eventTime <= adjustedFrameTime.count())) {
         if (motionEvent == nullptr) {
             motionEvent = createMotionEvent(messages.front());
             firstSeqForBatch = messages.front().header.seq;
@@ -498,46 +491,63 @@
         }
         messages.pop();
     }
+
     // Check if resampling should be performed.
-    if (motionEvent != nullptr && isPointerEvent(*motionEvent) && mResampler != nullptr) {
-        InputMessage* futureSample = nullptr;
-        if (!messages.empty()) {
-            futureSample = &messages.front();
-        }
-        mResampler->resampleMotionEvent(static_cast<std::chrono::nanoseconds>(frameTime),
-                                        *motionEvent, futureSample);
+    InputMessage* futureSample = nullptr;
+    if (!messages.empty()) {
+        futureSample = &messages.front();
     }
+    if ((motionEvent != nullptr) && (resampler != mResamplers.cend())) {
+        resampler->second->resampleMotionEvent(nanoseconds{requestedFrameTime}, *motionEvent,
+                                               futureSample);
+    }
+
     return std::make_pair(std::move(motionEvent), firstSeqForBatch);
 }
 
 bool InputConsumerNoResampling::consumeBatchedInputEvents(
-        std::optional<nsecs_t> requestedFrameTime) {
+        std::optional<DeviceId> deviceId, std::optional<nsecs_t> requestedFrameTime) {
     ensureCalledOnLooperThread(__func__);
     // When batching is not enabled, we want to consume all events. That's equivalent to having an
-    // infinite frameTime.
-    const nsecs_t frameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
+    // infinite requestedFrameTime.
+    requestedFrameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
     bool producedEvents = false;
-    for (auto& [_, messages] : mBatches) {
-        auto [motion, firstSeqForBatch] = createBatchedMotionEvent(frameTime, messages);
+
+    for (auto deviceIdIter = (deviceId.has_value()) ? (mBatches.find(*deviceId))
+                                                    : (mBatches.begin());
+         deviceIdIter != mBatches.cend(); ++deviceIdIter) {
+        std::queue<InputMessage>& messages = deviceIdIter->second;
+        auto [motion, firstSeqForBatch] = createBatchedMotionEvent(*requestedFrameTime, messages);
         if (motion != nullptr) {
             LOG_ALWAYS_FATAL_IF(!firstSeqForBatch.has_value());
             mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch);
             producedEvents = true;
         } else {
-            // This is OK, it just means that the frameTime is too old (all events that we have
-            // pending are in the future of the frametime). Maybe print a
-            // warning? If there are multiple devices active though, this might be normal and can
-            // just be ignored, unless none of them resulted in any consumption (in that case, this
-            // function would already return "false" so we could just leave it up to the caller).
+            // This is OK, it just means that the requestedFrameTime is too old (all events that we
+            // have pending are in the future of the requestedFrameTime). Maybe print a warning? If
+            // there are multiple devices active though, this might be normal and can just be
+            // ignored, unless none of them resulted in any consumption (in that case, this function
+            // would already return "false" so we could just leave it up to the caller).
+        }
+
+        if (deviceId.has_value()) {
+            // We already consumed events for this device. Break here to prevent iterating over the
+            // other devices.
+            break;
         }
     }
     std::erase_if(mBatches, [](const auto& pair) { return pair.second.empty(); });
     return producedEvents;
 }
 
+bool InputConsumerNoResampling::consumeBatchedInputEvents(
+        std::optional<nsecs_t> requestedFrameTime) {
+    return consumeBatchedInputEvents(/*deviceId=*/std::nullopt, requestedFrameTime);
+}
+
 void InputConsumerNoResampling::ensureCalledOnLooperThread(const char* func) const {
     sp<Looper> callingThreadLooper = Looper::getForThread();
-    if (callingThreadLooper != mLooper->getLooper()) {
+    if (callingThreadLooper != mLooper) {
         LOG(FATAL) << "The function " << func << " can only be called on the looper thread";
     }
 }
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 9333ab8..c903031 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -20,6 +20,7 @@
 #include <unistd.h>
 #include <ctype.h>
 
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <ftl/enum.h>
@@ -31,6 +32,9 @@
 
 namespace android {
 
+// Set to true to log detailed debugging messages about IDC file probing.
+static constexpr bool DEBUG_PROBE = false;
+
 static const char* CONFIGURATION_FILE_DIR[] = {
         "idc/",
         "keylayout/",
@@ -114,15 +118,18 @@
     for (const auto& prefix : pathPrefixes) {
         path = prefix;
         appendInputDeviceConfigurationFileRelativePath(path, name, type);
-#if DEBUG_PROBE
-        ALOGD("Probing for system provided input device configuration file: path='%s'",
-              path.c_str());
-#endif
         if (!access(path.c_str(), R_OK)) {
-#if DEBUG_PROBE
-            ALOGD("Found");
-#endif
+            LOG_IF(INFO, DEBUG_PROBE)
+                    << "Found system-provided input device configuration file at " << path;
             return path;
+        } else if (errno != ENOENT) {
+            LOG(WARNING) << "Couldn't find a system-provided input device configuration file at "
+                         << path << " due to error " << errno << " (" << strerror(errno)
+                         << "); there may be an IDC file there that cannot be loaded.";
+        } else {
+            LOG_IF(ERROR, DEBUG_PROBE)
+                    << "Didn't find system-provided input device configuration file at " << path
+                    << ": " << strerror(errno);
         }
     }
 
@@ -135,21 +142,22 @@
     }
     path += "/system/devices/";
     appendInputDeviceConfigurationFileRelativePath(path, name, type);
-#if DEBUG_PROBE
-    ALOGD("Probing for system user input device configuration file: path='%s'", path.c_str());
-#endif
     if (!access(path.c_str(), R_OK)) {
-#if DEBUG_PROBE
-        ALOGD("Found");
-#endif
+        LOG_IF(INFO, DEBUG_PROBE) << "Found system user input device configuration file at "
+                                  << path;
         return path;
+    } else if (errno != ENOENT) {
+        LOG(WARNING) << "Couldn't find a system user input device configuration file at " << path
+                     << " due to error " << errno << " (" << strerror(errno)
+                     << "); there may be an IDC file there that cannot be loaded.";
+    } else {
+        LOG_IF(ERROR, DEBUG_PROBE) << "Didn't find system user input device configuration file at "
+                                   << path << ": " << strerror(errno);
     }
 
     // Not found.
-#if DEBUG_PROBE
-    ALOGD("Probe failed to find input device configuration file: name='%s', type=%d",
-            name.c_str(), type);
-#endif
+    LOG_IF(INFO, DEBUG_PROBE) << "Probe failed to find input device configuration file with name '"
+                              << name << "' and type " << ftl::enum_string(type);
     return "";
 }
 
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index 1cf5612..b0563ab 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -317,19 +317,8 @@
     return true;
 }
 
-void KeyCharacterMap::addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) {
-    if (fromKeyCode == toKeyCode) {
-        mKeyRemapping.erase(fromKeyCode);
-#if DEBUG_MAPPING
-        ALOGD("addKeyRemapping: Cleared remapping forKeyCode=%d ~ Result Successful.", fromKeyCode);
-#endif
-        return;
-    }
-    mKeyRemapping.insert_or_assign(fromKeyCode, toKeyCode);
-#if DEBUG_MAPPING
-    ALOGD("addKeyRemapping: fromKeyCode=%d, toKeyCode=%d ~ Result Successful.", fromKeyCode,
-          toKeyCode);
-#endif
+void KeyCharacterMap::setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping) {
+    mKeyRemapping = keyRemapping;
 }
 
 status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const {
diff --git a/libs/input/Resampler.cpp b/libs/input/Resampler.cpp
index c663649..328fa68 100644
--- a/libs/input/Resampler.cpp
+++ b/libs/input/Resampler.cpp
@@ -241,12 +241,13 @@
                           motionEvent.getId());
 }
 
-void LegacyResampler::resampleMotionEvent(nanoseconds resampleTime, MotionEvent& motionEvent,
+nanoseconds LegacyResampler::getResampleLatency() const {
+    return RESAMPLE_LATENCY;
+}
+
+void LegacyResampler::resampleMotionEvent(nanoseconds frameTime, MotionEvent& motionEvent,
                                           const InputMessage* futureSample) {
-    if (mPreviousDeviceId && *mPreviousDeviceId != motionEvent.getDeviceId()) {
-        mLatestSamples.clear();
-    }
-    mPreviousDeviceId = motionEvent.getDeviceId();
+    const nanoseconds resampleTime = frameTime - RESAMPLE_LATENCY;
 
     updateLatestSamples(motionEvent);
 
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index e23fc94..31592cd 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -49,6 +49,12 @@
     const int POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY = 0x20000;
 
     /**
+     * The key event triggered a key gesture. Used in policy flag to notify that a key gesture was
+     * triggered using the event.
+     */
+    const int POLICY_FLAG_KEY_GESTURE_TRIGGERED = 0x40000;
+
+    /**
      * Common input event flag used for both motion and key events for a gesture or pointer being
      * canceled.
      */
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
index b8a8d76..701fb43 100644
--- a/libs/input/input_flags.aconfig
+++ b/libs/input/input_flags.aconfig
@@ -185,3 +185,32 @@
   description: "Collect quality metrics on framework palm rejection."
   bug: "341717757"
 }
+
+flag {
+  name: "enable_touchpad_no_focus_change"
+  namespace: "input"
+  description: "Prevents touchpad gesture changing window focus."
+  bug: "364460018"
+}
+
+flag {
+  name: "enable_input_policy_profile"
+  namespace: "input"
+  description: "Apply input policy profile for input threads."
+  bug: "347122505"
+  is_fixed_read_only: true
+}
+
+flag {
+  name: "keyboard_repeat_keys"
+  namespace: "input"
+  description: "Allow user to enable key repeats or configure timeout before key repeat and key repeat delay rates."
+  bug: "336585002"
+}
+
+flag {
+  name: "rotary_input_telemetry"
+  namespace: "wear_frameworks"
+  description: "Enable telemetry for rotary input"
+  bug: "370353565"
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 43bc894..81c6175 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -27,7 +27,6 @@
         "Resampler_test.cpp",
         "RingBuffer_test.cpp",
         "TestInputChannel.cpp",
-        "TestLooper.cpp",
         "TfLiteMotionPredictor_test.cpp",
         "TouchResampling_test.cpp",
         "TouchVideoFrame_test.cpp",
@@ -95,6 +94,7 @@
             },
         },
     },
+    native_coverage: false,
 }
 
 // NOTE: This is a compile time test, and does not need to be
diff --git a/libs/input/tests/InputConsumer_test.cpp b/libs/input/tests/InputConsumer_test.cpp
index c30f243..cbb332e 100644
--- a/libs/input/tests/InputConsumer_test.cpp
+++ b/libs/input/tests/InputConsumer_test.cpp
@@ -16,34 +16,81 @@
 
 #include <input/InputConsumerNoResampling.h>
 
+#include <gtest/gtest.h>
+
+#include <chrono>
 #include <memory>
 #include <optional>
-#include <utility>
 
+#include <TestEventMatchers.h>
 #include <TestInputChannel.h>
-#include <TestLooper.h>
 #include <android-base/logging.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/BlockingQueue.h>
+#include <input/Input.h>
 #include <input/InputEventBuilders.h>
+#include <input/Resampler.h>
+#include <utils/Looper.h>
 #include <utils/StrongPointer.h>
 
 namespace android {
 
+namespace {
+
+using std::chrono::nanoseconds;
+
+using ::testing::AllOf;
+using ::testing::Matcher;
+
+struct Pointer {
+    int32_t id{0};
+    ToolType toolType{ToolType::FINGER};
+    float x{0.0f};
+    float y{0.0f};
+    bool isResampled{false};
+
+    PointerBuilder asPointerBuilder() const {
+        return PointerBuilder{id, toolType}.x(x).y(y).isResampled(isResampled);
+    }
+};
+} // namespace
+
 class InputConsumerTest : public testing::Test, public InputConsumerCallbacks {
 protected:
     InputConsumerTest()
           : mClientTestChannel{std::make_shared<TestInputChannel>("TestChannel")},
-            mTestLooper{std::make_shared<TestLooper>()} {
-        Looper::setForThread(mTestLooper->getLooper());
-        mConsumer = std::make_unique<InputConsumerNoResampling>(mClientTestChannel, mTestLooper,
-                                                                *this, /*resampler=*/nullptr);
+            mLooper{sp<Looper>::make(/*allowNonCallbacks=*/false)} {
+        Looper::setForThread(mLooper);
+        mConsumer = std::make_unique<
+                InputConsumerNoResampling>(mClientTestChannel, mLooper, *this,
+                                           []() { return std::make_unique<LegacyResampler>(); });
     }
 
-    void assertOnBatchedInputEventPendingWasCalled();
+    void invokeLooperCallback() const {
+        sp<LooperCallback> callback;
+        ASSERT_TRUE(mLooper->getFdStateDebug(mClientTestChannel->getFd(), /*ident=*/nullptr,
+                                             /*events=*/nullptr, &callback, /*data=*/nullptr));
+        callback->handleEvent(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT, /*data=*/nullptr);
+    }
+
+    void assertOnBatchedInputEventPendingWasCalled() {
+        ASSERT_GT(mOnBatchedInputEventPendingInvocationCount, 0UL)
+                << "onBatchedInputEventPending has not been called.";
+        --mOnBatchedInputEventPendingInvocationCount;
+    }
+
+    void assertReceivedMotionEvent(const Matcher<MotionEvent>& matcher) {
+        std::unique_ptr<MotionEvent> motionEvent = mMotionEvents.pop();
+        ASSERT_NE(motionEvent, nullptr);
+        EXPECT_THAT(*motionEvent, matcher);
+    }
+
+    InputMessage nextPointerMessage(std::chrono::nanoseconds eventTime, DeviceId deviceId,
+                                    int32_t action, const Pointer& pointer);
 
     std::shared_ptr<TestInputChannel> mClientTestChannel;
-    std::shared_ptr<TestLooper> mTestLooper;
+    sp<Looper> mLooper;
     std::unique_ptr<InputConsumerNoResampling> mConsumer;
 
     BlockingQueue<std::unique_ptr<KeyEvent>> mKeyEvents;
@@ -54,7 +101,8 @@
     BlockingQueue<std::unique_ptr<TouchModeEvent>> mTouchModeEvents;
 
 private:
-    size_t onBatchedInputEventPendingInvocationCount{0};
+    uint32_t mLastSeq{0};
+    size_t mOnBatchedInputEventPendingInvocationCount{0};
 
     // InputConsumerCallbacks interface
     void onKeyEvent(std::unique_ptr<KeyEvent> event, uint32_t seq) override {
@@ -69,7 +117,7 @@
         if (!mConsumer->probablyHasInput()) {
             ADD_FAILURE() << "should deterministically have input because there is a batch";
         }
-        ++onBatchedInputEventPendingInvocationCount;
+        ++mOnBatchedInputEventPendingInvocationCount;
     };
     void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
         mFocusEvents.push(std::move(event));
@@ -89,35 +137,259 @@
     };
 };
 
-void InputConsumerTest::assertOnBatchedInputEventPendingWasCalled() {
-    ASSERT_GT(onBatchedInputEventPendingInvocationCount, 0UL)
-            << "onBatchedInputEventPending has not been called.";
-    --onBatchedInputEventPendingInvocationCount;
+InputMessage InputConsumerTest::nextPointerMessage(std::chrono::nanoseconds eventTime,
+                                                   DeviceId deviceId, int32_t action,
+                                                   const Pointer& pointer) {
+    ++mLastSeq;
+    return InputMessageBuilder{InputMessage::Type::MOTION, mLastSeq}
+            .eventTime(eventTime.count())
+            .deviceId(deviceId)
+            .source(AINPUT_SOURCE_TOUCHSCREEN)
+            .action(action)
+            .pointer(pointer.asPointerBuilder())
+            .build();
 }
 
 TEST_F(InputConsumerTest, MessageStreamBatchedInMotionEvent) {
-    mClientTestChannel->enqueueMessage(
-            InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}.build());
-    mClientTestChannel->enqueueMessage(
-            InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}.build());
-    mClientTestChannel->enqueueMessage(
-            InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2}.build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
+                                               .eventTime(nanoseconds{0ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_DOWN)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}
+                                               .eventTime(nanoseconds{5ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2}
+                                               .eventTime(nanoseconds{10ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
 
     mClientTestChannel->assertNoSentMessages();
 
-    mTestLooper->invokeCallback(mClientTestChannel->getFd(), ALOOPER_EVENT_INPUT);
+    invokeLooperCallback();
 
     assertOnBatchedInputEventPendingWasCalled();
 
-    mConsumer->consumeBatchedInputEvents(std::nullopt);
+    mConsumer->consumeBatchedInputEvents(/*frameTime=*/std::nullopt);
 
-    std::unique_ptr<MotionEvent> batchedMotionEvent = mMotionEvents.pop();
-    ASSERT_NE(batchedMotionEvent, nullptr);
+    std::unique_ptr<MotionEvent> downMotionEvent = mMotionEvents.pop();
+    ASSERT_NE(downMotionEvent, nullptr);
+
+    std::unique_ptr<MotionEvent> moveMotionEvent = mMotionEvents.pop();
+    ASSERT_NE(moveMotionEvent, nullptr);
+    EXPECT_EQ(moveMotionEvent->getHistorySize() + 1, 3UL);
 
     mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
     mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
     mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+}
 
-    EXPECT_EQ(batchedMotionEvent->getHistorySize() + 1, 3UL);
+TEST_F(InputConsumerTest, LastBatchedSampleIsLessThanResampleTime) {
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
+                                               .eventTime(nanoseconds{0ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_DOWN)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}
+                                               .eventTime(nanoseconds{5ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2}
+                                               .eventTime(nanoseconds{10ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/3}
+                                               .eventTime(nanoseconds{15ms}.count())
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+
+    assertOnBatchedInputEventPendingWasCalled();
+
+    mConsumer->consumeBatchedInputEvents(16'000'000 /*ns*/);
+
+    std::unique_ptr<MotionEvent> downMotionEvent = mMotionEvents.pop();
+    ASSERT_NE(downMotionEvent, nullptr);
+
+    std::unique_ptr<MotionEvent> moveMotionEvent = mMotionEvents.pop();
+    ASSERT_NE(moveMotionEvent, nullptr);
+    const size_t numSamples = moveMotionEvent->getHistorySize() + 1;
+    EXPECT_LT(moveMotionEvent->getHistoricalEventTime(numSamples - 2),
+              moveMotionEvent->getEventTime());
+
+    // Consume all remaining events before ending the test. Otherwise, the smart pointer that owns
+    // consumer is set to null before destroying consumer. This leads to a member function call on a
+    // null object.
+    // TODO(b/332613662): Remove this workaround.
+    mConsumer->consumeBatchedInputEvents(std::nullopt);
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/0, true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, true);
+}
+
+TEST_F(InputConsumerTest, BatchedEventsMultiDeviceConsumption) {
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/0}
+                                               .deviceId(0)
+                                               .action(AMOTION_EVENT_ACTION_DOWN)
+                                               .build());
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/1}
+                                               .deviceId(0)
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/2}
+                                               .deviceId(0)
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/3}
+                                               .deviceId(0)
+                                               .action(AMOTION_EVENT_ACTION_MOVE)
+                                               .build());
+
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/4}
+                                               .deviceId(1)
+                                               .action(AMOTION_EVENT_ACTION_DOWN)
+                                               .build());
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent(AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+    mClientTestChannel->enqueueMessage(InputMessageBuilder{InputMessage::Type::MOTION, /*seq=*/5}
+                                               .deviceId(0)
+                                               .action(AMOTION_EVENT_ACTION_UP)
+                                               .build());
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+    mClientTestChannel->assertFinishMessage(/*seq=*/0, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+}
+
+/**
+ * The test supposes a 60Hz Vsync rate and a 200Hz input rate. The InputMessages are intertwined as
+ * in a real use cases. The test's two devices should be resampled independently. Moreover, the
+ * InputMessage stream layout for the test is:
+ *
+ * DOWN(0, 0ms)
+ * MOVE(0, 5ms)
+ * MOVE(0, 10ms)
+ * DOWN(1, 15ms)
+ *
+ * CONSUME(16ms)
+ *
+ * MOVE(1, 20ms)
+ * MOVE(1, 25ms)
+ * MOVE(0, 30ms)
+ *
+ * CONSUME(32ms)
+ *
+ * MOVE(0, 35ms)
+ * UP(1, 40ms)
+ * UP(0, 45ms)
+ *
+ * CONSUME(48ms)
+ *
+ * The first field is device ID, and the second field is event time.
+ */
+TEST_F(InputConsumerTest, MultiDeviceResampling) {
+    mClientTestChannel->enqueueMessage(nextPointerMessage(0ms, /*deviceId=*/0,
+                                                          AMOTION_EVENT_ACTION_DOWN,
+                                                          Pointer{.x = 0, .y = 0}));
+
+    mClientTestChannel->assertNoSentMessages();
+
+    invokeLooperCallback();
+    assertReceivedMotionEvent(AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                    WithSampleCount(1)));
+
+    mClientTestChannel->enqueueMessage(nextPointerMessage(5ms, /*deviceId=*/0,
+                                                          AMOTION_EVENT_ACTION_MOVE,
+                                                          Pointer{.x = 1.0f, .y = 2.0f}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(10ms, /*deviceId=*/0,
+                                                          AMOTION_EVENT_ACTION_MOVE,
+                                                          Pointer{.x = 2.0f, .y = 4.0f}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(15ms, /*deviceId=*/1,
+                                                          AMOTION_EVENT_ACTION_DOWN,
+                                                          Pointer{.x = 10.0f, .y = 10.0f}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(16'000'000 /*ns*/);
+
+    assertReceivedMotionEvent(AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                    WithSampleCount(1)));
+    assertReceivedMotionEvent(
+            AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithSampleCount(3),
+                  WithSample(/*sampleIndex=*/2,
+                             Sample{11ms,
+                                    {PointerArgs{.x = 2.2f, .y = 4.4f, .isResampled = true}}})));
+
+    mClientTestChannel->enqueueMessage(nextPointerMessage(20ms, /*deviceId=*/1,
+                                                          AMOTION_EVENT_ACTION_MOVE,
+                                                          Pointer{.x = 11.0f, .y = 12.0f}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(25ms, /*deviceId=*/1,
+                                                          AMOTION_EVENT_ACTION_MOVE,
+                                                          Pointer{.x = 12.0f, .y = 14.0f}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(30ms, /*deviceId=*/0,
+                                                          AMOTION_EVENT_ACTION_MOVE,
+                                                          Pointer{.x = 5.0f, .y = 6.0f}));
+
+    invokeLooperCallback();
+    assertOnBatchedInputEventPendingWasCalled();
+    mConsumer->consumeBatchedInputEvents(32'000'000 /*ns*/);
+
+    assertReceivedMotionEvent(
+            AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithSampleCount(3),
+                  WithSample(/*sampleIndex=*/2,
+                             Sample{27ms,
+                                    {PointerArgs{.x = 12.4f, .y = 14.8f, .isResampled = true}}})));
+
+    mClientTestChannel->enqueueMessage(nextPointerMessage(35ms, /*deviceId=*/0,
+                                                          AMOTION_EVENT_ACTION_MOVE,
+                                                          Pointer{.x = 8.0f, .y = 9.0f}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(40ms, /*deviceId=*/1,
+                                                          AMOTION_EVENT_ACTION_UP,
+                                                          Pointer{.x = 12.0f, .y = 14.0f}));
+    mClientTestChannel->enqueueMessage(nextPointerMessage(45ms, /*deviceId=*/0,
+                                                          AMOTION_EVENT_ACTION_UP,
+                                                          Pointer{.x = 8.0f, .y = 9.0f}));
+
+    invokeLooperCallback();
+    mConsumer->consumeBatchedInputEvents(48'000'000 /*ns*/);
+
+    assertReceivedMotionEvent(
+            AllOf(WithDeviceId(1), WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSampleCount(1)));
+
+    assertReceivedMotionEvent(
+            AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithSampleCount(3),
+                  WithSample(/*sampleIndex=*/2,
+                             Sample{37'500'000ns,
+                                    {PointerArgs{.x = 9.5f, .y = 10.5f, .isResampled = true}}})));
+
+    assertReceivedMotionEvent(
+            AllOf(WithDeviceId(0), WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSampleCount(1)));
+
+    // The sequence order is based on the expected consumption. Each sequence number corresponds to
+    // one of the previously enqueued messages.
+    mClientTestChannel->assertFinishMessage(/*seq=*/1, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/4, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/2, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/3, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/5, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/6, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/9, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/7, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/8, /*handled=*/true);
+    mClientTestChannel->assertFinishMessage(/*seq=*/10, /*handled=*/true);
 }
 } // namespace android
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
index 467c3b4..1210f71 100644
--- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -364,7 +364,7 @@
         if (!mConsumer->probablyHasInput()) {
             ADD_FAILURE() << "should deterministically have input because there is a batch";
         }
-        mConsumer->consumeBatchedInputEvents(std::nullopt);
+        mConsumer->consumeBatchedInputEvents(/*frameTime=*/std::nullopt);
     };
     void onFocusEvent(std::unique_ptr<FocusEvent> event, uint32_t seq) override {
         mFocusEvents.push(std::move(event));
diff --git a/libs/input/tests/Resampler_test.cpp b/libs/input/tests/Resampler_test.cpp
index 7ae9a28..fae8518 100644
--- a/libs/input/tests/Resampler_test.cpp
+++ b/libs/input/tests/Resampler_test.cpp
@@ -87,7 +87,6 @@
 struct InputStream {
     std::vector<InputSample> samples{};
     int32_t action{0};
-    DeviceId deviceId{0};
     /**
      * Converts from InputStream to MotionEvent. Enables calling LegacyResampler methods only with
      * the relevant data for tests.
@@ -100,8 +99,8 @@
     MotionEventBuilder motionEventBuilder =
             MotionEventBuilder(action, AINPUT_SOURCE_CLASS_POINTER)
                     .downTime(0)
-                    .eventTime(static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count())
-                    .deviceId(deviceId);
+                    .eventTime(
+                            static_cast<std::chrono::nanoseconds>(firstSample.eventTime).count());
     for (const Pointer& pointer : firstSample.pointers) {
         const PointerBuilder pointerBuilder =
                 PointerBuilder(pointer.id, pointer.toolType).x(pointer.x).y(pointer.y);
@@ -120,6 +119,47 @@
 
 } // namespace
 
+/**
+ * The testing setup assumes an input rate of 200 Hz and a display rate of 60 Hz. This implies that
+ * input events are received every 5 milliseconds, while the display consumes batched events every
+ * ~16 milliseconds. The resampler's RESAMPLE_LATENCY constant determines the resample time, which
+ * is calculated as frameTime - RESAMPLE_LATENCY. resampleTime specifies the time used for
+ * resampling. For example, if the desired frame time consumption is ~16 milliseconds, the resample
+ * time would be ~11 milliseconds. Consequenly, the last added sample to the motion event has an
+ * event time of ~11 milliseconds. Note that there are specific scenarios where resampleMotionEvent
+ * is not called with a multiple of ~16 milliseconds. These cases are primarily for data addition
+ * or to test other functionalities of the resampler.
+ *
+ * Coordinates are calculated using linear interpolation (lerp) based on the last two available
+ * samples. Linear interpolation is defined as (a + alpha*(b - a)). Let t_b and t_a be the
+ * timestamps of samples a and b, respectively. The interpolation factor alpha is calculated as
+ * (resampleTime - t_a) / (t_b - t_a). The value of alpha determines whether the resampled
+ * coordinates are interpolated or extrapolated. If alpha falls within the semi-closed interval [0,
+ * 1), the coordinates are interpolated. If alpha is greater than or equal to 1, the coordinates are
+ * extrapolated.
+ *
+ * The timeline below depics an interpolation scenario
+ * -----------------------------------|---------|---------|---------|----------
+ *                                   10ms      11ms      15ms      16ms
+ *                                   MOVE       |        MOVE       |
+ *                                         resampleTime         frameTime
+ * Based on the timeline alpha is (11 - 10)/(15 - 10) = 1/5. Thus, coordinates are interpolated.
+ *
+ * The following timeline portrays an extrapolation scenario
+ * -------------------------|---------|---------|-------------------|----------
+ *                          5ms      10ms      11ms                16ms
+ *                          MOVE     MOVE       |                   |
+ *                                         resampleTime         frameTime
+ * Likewise, alpha = (11 - 5)/(10 - 5) = 6/5. Hence, coordinates are extrapolated.
+ *
+ * If a motion event was resampled, the tests will check that the following conditions are satisfied
+ * to guarantee resampling correctness:
+ * - The motion event metadata must not change.
+ * - The number of samples in the motion event must only increment by 1.
+ * - The resampled values must be at the end of motion event coordinates.
+ * - The rasamples values must be near the hand calculations.
+ * - The resampled time must be the most recent one in motion event.
+ */
 class ResamplerTest : public testing::Test {
 protected:
     ResamplerTest() : mResampler(std::make_unique<LegacyResampler>()) {}
@@ -225,7 +265,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     EXPECT_EQ(motionEvent.getTouchMajor(0), TOUCH_MAJOR_VALUE);
 
@@ -243,50 +283,11 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
 
-TEST_F(ResamplerTest, SinglePointerDifferentDeviceIdBetweenMotionEvents) {
-    MotionEvent motionFromFirstDevice =
-            InputStream{{InputSample{4ms, {{.id = 0, .x = 1.0f, .y = 1.0f, .isResampled = false}}},
-                         InputSample{8ms, {{.id = 0, .x = 2.0f, .y = 2.0f, .isResampled = false}}}},
-                        AMOTION_EVENT_ACTION_MOVE,
-                        .deviceId = 0};
-
-    mResampler->resampleMotionEvent(10ms, motionFromFirstDevice, nullptr);
-
-    MotionEvent motionFromSecondDevice =
-            InputStream{{InputSample{11ms,
-                                     {{.id = 0, .x = 3.0f, .y = 3.0f, .isResampled = false}}}},
-                        AMOTION_EVENT_ACTION_MOVE,
-                        .deviceId = 1};
-    const MotionEvent originalMotionEvent = motionFromSecondDevice;
-
-    mResampler->resampleMotionEvent(12ms, motionFromSecondDevice, nullptr);
-    // The MotionEvent should not be resampled because the second event came from a different device
-    // than the previous event.
-    assertMotionEventIsNotResampled(originalMotionEvent, motionFromSecondDevice);
-}
-
-// Increments of 16 ms for display refresh rate
-// Increments of 6 ms for input frequency
-// Resampling latency is known to be 5 ms
-// Therefore, first resampling time will be 11 ms
-
-/**
- * Timeline
- * ----+----------------------+---------+---------+---------+----------
- *     0ms                   10ms      11ms      15ms      16ms
- *    DOWN                   MOVE       |        MSG        |
- *                                  resample              frame
- * Resampling occurs at 11ms. It is possible to interpolate because there is a sample available
- * after the resample time. It is assumed that the InputMessage frequency is 100Hz, and the frame
- * frequency is 60Hz. This means the time between InputMessage samples is 10ms, and the time between
- * frames is ~16ms. Resample time is frameTime - RESAMPLE_LATENCY. The resampled sample must be the
- * last one in the batch to consume.
- */
 TEST_F(ResamplerTest, SinglePointerSingleSampleInterpolation) {
     MotionEvent motionEvent =
             InputStream{{InputSample{10ms,
@@ -297,7 +298,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.id = 0,
@@ -338,18 +339,13 @@
 
     const MotionEvent originalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, secondMotionEvent,
                                               {Pointer{.id = 0,
                                                        .x = 2.2f,
                                                        .y = 4.4f,
                                                        .isResampled = true}});
-    // Integrity of the whole motionEvent
-    // History size should increment by 1
-    // Check if the resampled value is the last one
-    // Check if the resampleTime is correct
-    // Check if the PointerCoords are consistent with the other computations
 }
 
 TEST_F(ResamplerTest, SinglePointerMultipleSampleInterpolation) {
@@ -364,7 +360,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.id = 0,
@@ -382,7 +378,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.id = 0,
@@ -400,7 +396,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, nullptr);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -414,7 +410,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(27ms, motionEvent, nullptr);
+    mResampler->resampleMotionEvent(32ms, motionEvent, nullptr);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -428,7 +424,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(43ms, motionEvent, nullptr);
+    mResampler->resampleMotionEvent(48ms, motionEvent, nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.id = 0,
@@ -451,7 +447,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.x = 2.2f, .y = 2.2f, .isResampled = true},
@@ -475,7 +471,7 @@
 
     const MotionEvent originalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, secondMotionEvent,
                                               {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
@@ -498,7 +494,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
@@ -517,7 +513,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
@@ -539,7 +535,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalMotionEvent, motionEvent,
                                               {Pointer{.x = 1.4f, .y = 1.4f, .isResampled = true},
@@ -560,7 +556,7 @@
 
     const MotionEvent originalSecondMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(27ms, secondMotionEvent, &secondFutureSample);
+    mResampler->resampleMotionEvent(32ms, secondMotionEvent, &secondFutureSample);
 
     assertMotionEventIsResampledAndCoordsNear(originalSecondMotionEvent, secondMotionEvent,
                                               {Pointer{.x = 3.8f, .y = 3.8f, .isResampled = true},
@@ -586,7 +582,7 @@
 
     const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
 }
@@ -606,7 +602,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -629,7 +625,7 @@
 
     const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsResampledAndCoordsNear(secondOriginalMotionEvent, secondMotionEvent,
                                               {Pointer{.x = 3.4f, .y = 3.4f, .isResampled = true},
@@ -650,7 +646,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -672,7 +668,7 @@
 
     const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
 }
@@ -691,7 +687,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -713,7 +709,7 @@
 
     const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
 }
@@ -746,7 +742,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, &futureSample);
+    mResampler->resampleMotionEvent(16ms, motionEvent, &futureSample);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -782,7 +778,7 @@
 
     const MotionEvent secondOriginalMotionEvent = secondMotionEvent;
 
-    mResampler->resampleMotionEvent(11ms, secondMotionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, secondMotionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(secondOriginalMotionEvent, secondMotionEvent);
 }
@@ -815,7 +811,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
@@ -847,7 +843,7 @@
 
     const MotionEvent originalMotionEvent = motionEvent;
 
-    mResampler->resampleMotionEvent(11ms, motionEvent, /*futureSample=*/nullptr);
+    mResampler->resampleMotionEvent(16ms, motionEvent, /*futureSample=*/nullptr);
 
     assertMotionEventIsNotResampled(originalMotionEvent, motionEvent);
 }
diff --git a/libs/input/tests/TestEventMatchers.h b/libs/input/tests/TestEventMatchers.h
new file mode 100644
index 0000000..3589de5
--- /dev/null
+++ b/libs/input/tests/TestEventMatchers.h
@@ -0,0 +1,187 @@
+/**
+ * Copyright 2024 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 <chrono>
+#include <ostream>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+
+namespace android {
+
+namespace {
+
+using ::testing::Matcher;
+
+} // namespace
+
+/**
+ * This file contains a copy of Matchers from .../inputflinger/tests/TestEventMatchers.h. Ideally,
+ * implementations must not be duplicated.
+ * TODO(b/365606513): Find a way to share TestEventMatchers.h between inputflinger and libinput.
+ */
+
+struct PointerArgs {
+    float x{0.0f};
+    float y{0.0f};
+    bool isResampled{false};
+};
+
+struct Sample {
+    std::chrono::nanoseconds eventTime{0};
+    std::vector<PointerArgs> pointers{};
+};
+
+class WithDeviceIdMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithDeviceIdMatcher(DeviceId deviceId) : mDeviceId(deviceId) {}
+
+    bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+        return mDeviceId == event.getDeviceId();
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "with device id " << mDeviceId; }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong device id"; }
+
+private:
+    const DeviceId mDeviceId;
+};
+
+inline WithDeviceIdMatcher WithDeviceId(int32_t deviceId) {
+    return WithDeviceIdMatcher(deviceId);
+}
+
+class WithMotionActionMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithMotionActionMatcher(int32_t action) : mAction(action) {}
+
+    bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
+        bool matches = mAction == event.getAction();
+        if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL) {
+            matches &= (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+        }
+        return matches;
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with motion action " << MotionEvent::actionToString(mAction);
+        if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+            *os << " and FLAG_CANCELED";
+        }
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong action"; }
+
+private:
+    const int32_t mAction;
+};
+
+inline WithMotionActionMatcher WithMotionAction(int32_t action) {
+    return WithMotionActionMatcher(action);
+}
+
+class WithSampleCountMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithSampleCountMatcher(size_t sampleCount) : mExpectedSampleCount{sampleCount} {}
+
+    bool MatchAndExplain(const MotionEvent& motionEvent, std::ostream*) const {
+        return (motionEvent.getHistorySize() + 1) == mExpectedSampleCount;
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "sample count " << mExpectedSampleCount; }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "different sample count"; }
+
+private:
+    const size_t mExpectedSampleCount;
+};
+
+inline WithSampleCountMatcher WithSampleCount(size_t sampleCount) {
+    return WithSampleCountMatcher(sampleCount);
+}
+
+class WithSampleMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithSampleMatcher(size_t sampleIndex, const Sample& sample)
+          : mSampleIndex{sampleIndex}, mSample{sample} {}
+
+    bool MatchAndExplain(const MotionEvent& motionEvent, std::ostream* os) const {
+        if (motionEvent.getHistorySize() < mSampleIndex) {
+            *os << "sample index out of bounds";
+            return false;
+        }
+
+        if (motionEvent.getHistoricalEventTime(mSampleIndex) != mSample.eventTime.count()) {
+            *os << "event time mismatch. sample: "
+                << motionEvent.getHistoricalEventTime(mSampleIndex)
+                << " expected: " << mSample.eventTime.count();
+            return false;
+        }
+
+        if (motionEvent.getPointerCount() != mSample.pointers.size()) {
+            *os << "pointer count mismatch. sample: " << motionEvent.getPointerCount()
+                << " expected: " << mSample.pointers.size();
+            return false;
+        }
+
+        for (size_t pointerIndex = 0; pointerIndex < motionEvent.getPointerCount();
+             ++pointerIndex) {
+            const PointerCoords& pointerCoords =
+                    *(motionEvent.getHistoricalRawPointerCoords(pointerIndex, mSampleIndex));
+            if ((pointerCoords.getX() != mSample.pointers[pointerIndex].x) ||
+                (pointerCoords.getY() != mSample.pointers[pointerIndex].y)) {
+                *os << "sample coordinates mismatch at pointer index " << pointerIndex
+                    << ". sample: (" << pointerCoords.getX() << ", " << pointerCoords.getY()
+                    << ") expected: (" << mSample.pointers[pointerIndex].x << ", "
+                    << mSample.pointers[pointerIndex].y << ")";
+                return false;
+            }
+            if (motionEvent.isResampled(pointerIndex, mSampleIndex) !=
+                mSample.pointers[pointerIndex].isResampled) {
+                *os << "resampling flag mismatch. sample: "
+                    << motionEvent.isResampled(pointerIndex, mSampleIndex)
+                    << " expected: " << mSample.pointers[pointerIndex].isResampled;
+                return false;
+            }
+        }
+        return true;
+    }
+
+    void DescribeTo(std::ostream* os) const { *os << "motion event sample properties match."; }
+
+    void DescribeNegationTo(std::ostream* os) const {
+        *os << "motion event sample properties do not match expected properties.";
+    }
+
+private:
+    const size_t mSampleIndex;
+    const Sample mSample;
+};
+
+inline WithSampleMatcher WithSample(size_t sampleIndex, const Sample& sample) {
+    return WithSampleMatcher(sampleIndex, sample);
+}
+
+} // namespace android
diff --git a/libs/input/tests/TestInputChannel.cpp b/libs/input/tests/TestInputChannel.cpp
index d5f00b6..26a0ca2 100644
--- a/libs/input/tests/TestInputChannel.cpp
+++ b/libs/input/tests/TestInputChannel.cpp
@@ -19,6 +19,11 @@
 
 #include <TestInputChannel.h>
 
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <array>
+
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 #include <binder/IBinder.h>
@@ -27,13 +32,25 @@
 namespace android {
 
 namespace {
-constexpr int FAKE_FD{-1};
+
+/**
+ * Returns a stub file descriptor by opening a socket pair and closing one of the fds. The returned
+ * fd can be used to construct an InputChannel.
+ */
+base::unique_fd generateFileDescriptor() {
+    std::array<int, 2> kFileDescriptors;
+    LOG_IF(FATAL, ::socketpair(AF_UNIX, SOCK_SEQPACKET, 0, kFileDescriptors.data()) != 0)
+            << "TestInputChannel. Failed to create socket pair.";
+    LOG_IF(FATAL, ::close(kFileDescriptors[1]) != 0)
+            << "TestInputChannel. Failed to close file descriptor.";
+    return base::unique_fd{kFileDescriptors[0]};
+}
 } // namespace
 
 // --- TestInputChannel ---
 
 TestInputChannel::TestInputChannel(const std::string& name)
-      : InputChannel{name, base::unique_fd(FAKE_FD), sp<BBinder>::make()} {}
+      : InputChannel{name, generateFileDescriptor(), sp<BBinder>::make()} {}
 
 void TestInputChannel::enqueueMessage(const InputMessage& message) {
     mReceivedMessages.push(message);
diff --git a/libs/input/tests/TestLooper.cpp b/libs/input/tests/TestLooper.cpp
deleted file mode 100644
index e0f01ed..0000000
--- a/libs/input/tests/TestLooper.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * Copyright 2024 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 <TestLooper.h>
-
-#include <android-base/logging.h>
-
-namespace android {
-
-TestLooper::TestLooper() : mLooper(sp<Looper>::make(/*allowNonCallbacks=*/false)) {}
-
-int TestLooper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback,
-                      void* data) {
-    mCallbacks[fd] = callback;
-    constexpr int SUCCESS{1};
-    return SUCCESS;
-}
-
-int TestLooper::removeFd(int fd) {
-    if (auto it = mCallbacks.find(fd); it != mCallbacks.cend()) {
-        mCallbacks.erase(fd);
-        constexpr int SUCCESS{1};
-        return SUCCESS;
-    }
-    constexpr int FAILURE{0};
-    return FAILURE;
-}
-
-void TestLooper::invokeCallback(int fd, int events) {
-    auto it = mCallbacks.find(fd);
-    LOG_IF(FATAL, it == mCallbacks.cend()) << "Fd does not exist in mCallbacks.";
-    mCallbacks[fd]->handleEvent(fd, events, /*data=*/nullptr);
-}
-
-sp<Looper> TestLooper::getLooper() const {
-    return mLooper;
-}
-} // namespace android
\ No newline at end of file
diff --git a/libs/input/tests/TestLooper.h b/libs/input/tests/TestLooper.h
deleted file mode 100644
index 3242bc7..0000000
--- a/libs/input/tests/TestLooper.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * Copyright 2024 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 <map>
-
-#include <input/LooperInterface.h>
-
-namespace android {
-/**
- * TestLooper provides a mechanism to directly trigger Looper's callback.
- */
-class TestLooper final : public LooperInterface {
-public:
-    TestLooper();
-
-    /**
-     * Adds a file descriptor to mCallbacks. Ident, events, and data parameters are ignored. If
-     * addFd is called with an existent file descriptor and a different callback, the previous
-     * callback is overwritten.
-     */
-    int addFd(int fd, int ident, int events, const sp<LooperCallback>& callback,
-              void* data) override;
-
-    /**
-     * Removes a file descriptor from mCallbacks. If fd is not in mCallbacks, returns FAILURE.
-     */
-    int removeFd(int fd) override;
-
-    /**
-     * Calls handleEvent of the file descriptor. Fd must be in mCallbacks. Otherwise, invokeCallback
-     * fatally logs.
-     */
-    void invokeCallback(int fd, int events);
-
-    sp<Looper> getLooper() const override;
-
-private:
-    std::map<int /*fd*/, sp<LooperCallback>> mCallbacks;
-    sp<Looper> mLooper;
-};
-} // namespace android
\ No newline at end of file
diff --git a/libs/nativedisplay/ADisplay.cpp b/libs/nativedisplay/ADisplay.cpp
index e3be3bc..d0ca78e 100644
--- a/libs/nativedisplay/ADisplay.cpp
+++ b/libs/nativedisplay/ADisplay.cpp
@@ -129,7 +129,7 @@
     std::vector<DisplayConfigImpl> modesPerDisplay[size];
     ui::DisplayConnectionType displayConnectionTypes[size];
     int numModes = 0;
-    for (int i = 0; i < size; ++i) {
+    for (size_t i = 0; i < size; ++i) {
         ui::StaticDisplayInfo staticInfo;
         if (const status_t status =
                     SurfaceComposerClient::getStaticDisplayInfo(ids[i].value, &staticInfo);
@@ -151,7 +151,7 @@
 
         numModes += modes.size();
         modesPerDisplay[i].reserve(modes.size());
-        for (int j = 0; j < modes.size(); ++j) {
+        for (size_t j = 0; j < modes.size(); ++j) {
             const ui::DisplayMode& mode = modes[j];
             modesPerDisplay[i].emplace_back(
                     DisplayConfigImpl{static_cast<size_t>(mode.id), mode.resolution.getWidth(),
@@ -224,7 +224,7 @@
     CHECK_NOT_NULL(display);
     DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
     float maxFps = 0.0;
-    for (int i = 0; i < impl->numConfigs; ++i) {
+    for (size_t i = 0; i < impl->numConfigs; ++i) {
         maxFps = std::max(maxFps, impl->configs[i].fps);
     }
     return maxFps;
@@ -261,7 +261,7 @@
 
     for (size_t i = 0; i < impl->numConfigs; i++) {
         auto* config = impl->configs + i;
-        if (config->id == info.activeDisplayModeId) {
+        if (info.activeDisplayModeId >= 0 && config->id == (size_t)info.activeDisplayModeId) {
             *outConfig = reinterpret_cast<ADisplayConfig*>(config);
             return OK;
         }
diff --git a/libs/nativewindow/rust/src/handle.rs b/libs/nativewindow/rust/src/handle.rs
index a3a9dc6..c41ab8d 100644
--- a/libs/nativewindow/rust/src/handle.rs
+++ b/libs/nativewindow/rust/src/handle.rs
@@ -12,7 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use std::{mem::forget, ptr::NonNull};
+use std::{
+    ffi::c_int,
+    mem::forget,
+    os::fd::{BorrowedFd, FromRawFd, IntoRawFd, OwnedFd},
+    ptr::NonNull,
+};
 
 /// Rust wrapper around `native_handle_t`.
 ///
@@ -22,6 +27,108 @@
 pub struct NativeHandle(NonNull<ffi::native_handle_t>);
 
 impl NativeHandle {
+    /// Creates a new `NativeHandle` with the given file descriptors and integer values.
+    ///
+    /// The `NativeHandle` will take ownership of the file descriptors and close them when it is
+    /// dropped.
+    pub fn new(fds: Vec<OwnedFd>, ints: &[c_int]) -> Option<Self> {
+        let fd_count = fds.len();
+        // SAFETY: native_handle_create doesn't have any safety requirements.
+        let handle = unsafe {
+            ffi::native_handle_create(fd_count.try_into().unwrap(), ints.len().try_into().unwrap())
+        };
+        let handle = NonNull::new(handle)?;
+        for (i, fd) in fds.into_iter().enumerate() {
+            // SAFETY: `handle` must be valid because it was just created, and the array offset is
+            // within the bounds of what we allocated above.
+            unsafe {
+                *(*handle.as_ptr()).data.as_mut_ptr().add(i) = fd.into_raw_fd();
+            }
+        }
+        for (i, value) in ints.iter().enumerate() {
+            // SAFETY: `handle` must be valid because it was just created, and the array offset is
+            // within the bounds of what we allocated above. Note that `data` is uninitialized
+            // until after this so we can't use `slice::from_raw_parts_mut` or similar to create a
+            // reference to it so we use raw pointers arithmetic instead.
+            unsafe {
+                *(*handle.as_ptr()).data.as_mut_ptr().add(fd_count + i) = *value;
+            }
+        }
+        // SAFETY: `handle` must be valid because it was just created.
+        unsafe {
+            ffi::native_handle_set_fdsan_tag(handle.as_ptr());
+        }
+        Some(Self(handle))
+    }
+
+    /// Returns a borrowed view of all the file descriptors in this native handle.
+    pub fn fds(&self) -> Vec<BorrowedFd> {
+        self.data()[..self.fd_count()]
+            .iter()
+            .map(|fd| {
+                // SAFETY: The `native_handle_t` maintains ownership of the file descriptor so it
+                // won't be closed until this `NativeHandle` is destroyed. The `BorrowedFd` will
+                // have a lifetime constrained to that of `&self`, so it can't outlive it.
+                unsafe { BorrowedFd::borrow_raw(*fd) }
+            })
+            .collect()
+    }
+
+    /// Returns the integer values in this native handle.
+    pub fn ints(&self) -> &[c_int] {
+        &self.data()[self.fd_count()..]
+    }
+
+    /// Destroys the `NativeHandle`, taking ownership of the file descriptors it contained.
+    pub fn into_fds(self) -> Vec<OwnedFd> {
+        let fds = self.data()[..self.fd_count()]
+            .iter()
+            .map(|fd| {
+                // SAFETY: The `native_handle_t` has ownership of the file descriptor, and
+                // after this we destroy it without closing the file descriptor so we can take over
+                // ownership of it.
+                unsafe { OwnedFd::from_raw_fd(*fd) }
+            })
+            .collect();
+
+        // SAFETY: Our wrapped `native_handle_t` pointer is always valid, and it won't be accessed
+        // after this because we own it and forget it.
+        unsafe {
+            assert_eq!(ffi::native_handle_delete(self.0.as_ptr()), 0);
+        }
+        // Don't drop self, as that would cause `native_handle_close` to be called and close the
+        // file descriptors.
+        forget(self);
+        fds
+    }
+
+    /// Returns a reference to the underlying `native_handle_t`.
+    fn as_ref(&self) -> &ffi::native_handle_t {
+        // SAFETY: All the ways of creating a `NativeHandle` ensure that the `native_handle_t` is
+        // valid and initialised, and lives as long as the `NativeHandle`. We enforce Rust's
+        // aliasing rules by giving the reference a lifetime matching that of `&self`.
+        unsafe { self.0.as_ref() }
+    }
+
+    /// Returns the number of file descriptors included in the native handle.
+    fn fd_count(&self) -> usize {
+        self.as_ref().numFds.try_into().unwrap()
+    }
+
+    /// Returns the number of integer values included in the native handle.
+    fn int_count(&self) -> usize {
+        self.as_ref().numInts.try_into().unwrap()
+    }
+
+    /// Returns a slice reference for all the used `data` field of the native handle, including both
+    /// file descriptors and integers.
+    fn data(&self) -> &[c_int] {
+        let total_count = self.fd_count() + self.int_count();
+        // SAFETY: The data must have been initialised with this number of elements when the
+        // `NativeHandle` was created.
+        unsafe { self.as_ref().data.as_slice(total_count) }
+    }
+
     /// Wraps a raw `native_handle_t` pointer, taking ownership of it.
     ///
     /// # Safety
@@ -90,3 +197,47 @@
 // SAFETY: A `NativeHandle` can be used from different threads simultaneously, as is is just
 // integers and file descriptors.
 unsafe impl Sync for NativeHandle {}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use std::fs::File;
+
+    #[test]
+    fn create_empty() {
+        let handle = NativeHandle::new(vec![], &[]).unwrap();
+        assert_eq!(handle.fds().len(), 0);
+        assert_eq!(handle.ints(), &[]);
+    }
+
+    #[test]
+    fn create_with_ints() {
+        let handle = NativeHandle::new(vec![], &[1, 2, 42]).unwrap();
+        assert_eq!(handle.fds().len(), 0);
+        assert_eq!(handle.ints(), &[1, 2, 42]);
+    }
+
+    #[test]
+    fn create_with_fd() {
+        let file = File::open("/dev/null").unwrap();
+        let handle = NativeHandle::new(vec![file.into()], &[]).unwrap();
+        assert_eq!(handle.fds().len(), 1);
+        assert_eq!(handle.ints(), &[]);
+    }
+
+    #[test]
+    fn clone() {
+        let file = File::open("/dev/null").unwrap();
+        let original = NativeHandle::new(vec![file.into()], &[42]).unwrap();
+        assert_eq!(original.ints(), &[42]);
+        assert_eq!(original.fds().len(), 1);
+
+        let cloned = original.clone();
+        drop(original);
+
+        assert_eq!(cloned.ints(), &[42]);
+        assert_eq!(cloned.fds().len(), 1);
+
+        drop(cloned);
+    }
+}
diff --git a/libs/renderengine/Android.bp b/libs/renderengine/Android.bp
index 7639fab..d248ea0 100644
--- a/libs/renderengine/Android.bp
+++ b/libs/renderengine/Android.bp
@@ -100,6 +100,7 @@
         "skia/debug/SkiaCapture.cpp",
         "skia/debug/SkiaMemoryReporter.cpp",
         "skia/filters/BlurFilter.cpp",
+        "skia/filters/GainmapFactory.cpp",
         "skia/filters/GaussianBlurFilter.cpp",
         "skia/filters/KawaseBlurDualFilter.cpp",
         "skia/filters/KawaseBlurFilter.cpp",
diff --git a/libs/renderengine/OWNERS b/libs/renderengine/OWNERS
index 66e1aa1..17ab29f 100644
--- a/libs/renderengine/OWNERS
+++ b/libs/renderengine/OWNERS
@@ -5,4 +5,6 @@
 djsollen@google.com
 jreck@google.com
 lpy@google.com
+nscobie@google.com
+sallyqi@google.com
 scroggo@google.com
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index bc3976d..907590a 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -21,6 +21,7 @@
 #include "skia/GraphiteVkRenderEngine.h"
 #include "skia/SkiaGLRenderEngine.h"
 #include "threaded/RenderEngineThreaded.h"
+#include "ui/GraphicTypes.h"
 
 #include <com_android_graphics_surfaceflinger_flags.h>
 #include <cutils/properties.h>
@@ -101,17 +102,34 @@
                                                   base::unique_fd&& bufferFence) {
     const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
     std::future<FenceResult> resultFuture = resultPromise->get_future();
-    updateProtectedContext(layers, buffer);
+    updateProtectedContext(layers, {buffer.get()});
     drawLayersInternal(std::move(resultPromise), display, layers, buffer, std::move(bufferFence));
     return resultFuture;
 }
 
+ftl::Future<FenceResult> RenderEngine::drawGainmap(
+        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+        const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+        float hdrSdrRatio, ui::Dataspace dataspace,
+        const std::shared_ptr<ExternalTexture>& gainmap) {
+    const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
+    std::future<FenceResult> resultFuture = resultPromise->get_future();
+    updateProtectedContext({}, {sdr.get(), hdr.get(), gainmap.get()});
+    drawGainmapInternal(std::move(resultPromise), sdr, std::move(sdrFence), hdr,
+                        std::move(hdrFence), hdrSdrRatio, dataspace, gainmap);
+    return resultFuture;
+}
+
 void RenderEngine::updateProtectedContext(const std::vector<LayerSettings>& layers,
-                                          const std::shared_ptr<ExternalTexture>& buffer) {
+                                          vector<const ExternalTexture*> buffers) {
     const bool needsProtectedContext =
-            (buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED)) ||
-            std::any_of(layers.begin(), layers.end(), [](const LayerSettings& layer) {
-                const std::shared_ptr<ExternalTexture>& buffer = layer.source.buffer.buffer;
+            std::any_of(layers.begin(), layers.end(),
+                        [](const LayerSettings& layer) {
+                            const std::shared_ptr<ExternalTexture>& buffer =
+                                    layer.source.buffer.buffer;
+                            return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
+                        }) ||
+            std::any_of(buffers.begin(), buffers.end(), [](const ExternalTexture* buffer) {
                 return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
             });
     useProtectedContext(needsProtectedContext);
diff --git a/libs/renderengine/benchmark/AndroidTest.xml b/libs/renderengine/benchmark/AndroidTest.xml
new file mode 100644
index 0000000..3b923cb
--- /dev/null
+++ b/libs/renderengine/benchmark/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 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.
+-->
+<configuration description="Config for librenderengine_bench.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-native-metric" />
+
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push" value="librenderengine_bench->/data/local/tmp/librenderengine_bench" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GoogleBenchmarkTest" >
+        <option name="native-benchmark-device-path" value="/data/local/tmp" />
+        <option name="benchmark-module-name" value="librenderengine_bench" />
+        <option name="file-exclusion-filter-regex" value=".*\.config$" />
+        <option name="file-exclusion-filter-regex" value=".*/resources/.*" />
+    </test>
+</configuration>
diff --git a/libs/renderengine/benchmark/RenderEngineBench.cpp b/libs/renderengine/benchmark/RenderEngineBench.cpp
index a9264b3..595573d 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -17,6 +17,7 @@
 #include <RenderEngineBench.h>
 #include <android-base/file.h>
 #include <benchmark/benchmark.h>
+#include <com_android_graphics_libgui_flags.h>
 #include <gui/SurfaceComposerClient.h>
 #include <log/log.h>
 #include <renderengine/ExternalTexture.h>
@@ -321,5 +322,7 @@
 BENCHMARK_CAPTURE(BM_homescreen, SkiaGLThreaded, RenderEngine::Threaded::YES,
                   RenderEngine::GraphicsApi::GL);
 
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS_EDGE_EXTENSION_SHADER
 BENCHMARK_CAPTURE(BM_homescreen_edgeExtension, SkiaGLThreaded, RenderEngine::Threaded::YES,
                   RenderEngine::GraphicsApi::GL);
+#endif
diff --git a/libs/renderengine/include/renderengine/DisplaySettings.h b/libs/renderengine/include/renderengine/DisplaySettings.h
index b640983..280ec19 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -102,6 +102,9 @@
         Local,
     };
     TonemapStrategy tonemapStrategy = TonemapStrategy::Libtonemap;
+
+    // For now, meaningful primarily when the TonemappingStrategy is Local
+    float targetHdrSdrRatio = 1.f;
 };
 
 static inline bool operator==(const DisplaySettings& lhs, const DisplaySettings& rhs) {
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 9bc2c48..0fd982e 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -97,6 +97,7 @@
     bool cacheImageDimmedLayers = true;
     bool cacheClippedLayers = true;
     bool cacheShadowLayers = true;
+    bool cacheEdgeExtension = true;
     bool cachePIPImageLayers = true;
     bool cacheTransparentImageDimmedLayers = true;
     bool cacheClippedDimmedImageLayers = true;
@@ -208,6 +209,13 @@
                                                 const std::shared_ptr<ExternalTexture>& buffer,
                                                 base::unique_fd&& bufferFence);
 
+    virtual ftl::Future<FenceResult> drawGainmap(const std::shared_ptr<ExternalTexture>& sdr,
+                                                 base::borrowed_fd&& sdrFence,
+                                                 const std::shared_ptr<ExternalTexture>& hdr,
+                                                 base::borrowed_fd&& hdrFence, float hdrSdrRatio,
+                                                 ui::Dataspace dataspace,
+                                                 const std::shared_ptr<ExternalTexture>& gainmap);
+
     // Clean-up method that should be called on the main thread after the
     // drawFence returned by drawLayers fires. This method will free up
     // resources used by the most recently drawn frame. If the frame is still
@@ -285,8 +293,7 @@
 
     // Update protectedContext mode depending on whether or not any layer has a protected buffer.
     void updateProtectedContext(const std::vector<LayerSettings>&,
-                                const std::shared_ptr<ExternalTexture>&);
-
+                                std::vector<const ExternalTexture*>);
     // Attempt to switch RenderEngine into and out of protectedContext mode
     virtual void useProtectedContext(bool useProtectedContext) = 0;
 
@@ -294,6 +301,13 @@
             const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
             const DisplaySettings& display, const std::vector<LayerSettings>& layers,
             const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) = 0;
+
+    virtual void drawGainmapInternal(
+            const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+            const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+            const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+            float hdrSdrRatio, ui::Dataspace dataspace,
+            const std::shared_ptr<ExternalTexture>& gainmap) = 0;
 };
 
 struct RenderEngineCreationArgs {
diff --git a/libs/renderengine/include/renderengine/mock/RenderEngine.h b/libs/renderengine/include/renderengine/mock/RenderEngine.h
index a8c242a..fb8331d 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -46,6 +46,17 @@
                  ftl::Future<FenceResult>(const DisplaySettings&, const std::vector<LayerSettings>&,
                                           const std::shared_ptr<ExternalTexture>&,
                                           base::unique_fd&&));
+    MOCK_METHOD7(drawGainmap,
+                 ftl::Future<FenceResult>(const std::shared_ptr<ExternalTexture>&,
+                                          base::borrowed_fd&&,
+                                          const std::shared_ptr<ExternalTexture>&,
+                                          base::borrowed_fd&&, float, ui::Dataspace,
+                                          const std::shared_ptr<ExternalTexture>&));
+    MOCK_METHOD8(drawGainmapInternal,
+                 void(const std::shared_ptr<std::promise<FenceResult>>&&,
+                      const std::shared_ptr<ExternalTexture>&, base::borrowed_fd&&,
+                      const std::shared_ptr<ExternalTexture>&, base::borrowed_fd&&, float,
+                      ui::Dataspace, const std::shared_ptr<ExternalTexture>&));
     MOCK_METHOD5(drawLayersInternal,
                  void(const std::shared_ptr<std::promise<FenceResult>>&&, const DisplaySettings&,
                       const std::vector<LayerSettings>&, const std::shared_ptr<ExternalTexture>&,
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index 59b0656..57041ee 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -27,6 +27,8 @@
 #include "ui/Rect.h"
 #include "utils/Timers.h"
 
+#include <com_android_graphics_libgui_flags.h>
+
 namespace android::renderengine::skia {
 
 namespace {
@@ -619,6 +621,32 @@
     }
 }
 
+static void drawEdgeExtensionLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+                                    const std::shared_ptr<ExternalTexture>& dstTexture,
+                                    const std::shared_ptr<ExternalTexture>& srcTexture) {
+    const Rect& displayRect = display.physicalDisplay;
+    // Make the layer
+    LayerSettings layer{
+            // Make the layer bigger than the texture
+            .geometry = Geometry{.boundaries = FloatRect(0, 0, displayRect.width(),
+                                                         displayRect.height())},
+            .source = PixelSource{.buffer =
+                                          Buffer{
+                                                  .buffer = srcTexture,
+                                                  .isOpaque = 1,
+                                          }},
+            // The type of effect does not affect the shader's uniforms, but the layer must have a
+            // valid EdgeExtensionEffect to apply the shader
+            .edgeExtensionEffect =
+                    EdgeExtensionEffect(true /* left */, false, false, true /* bottom */),
+    };
+    for (float alpha : {0.5, 0.0, 1.0}) {
+        layer.alpha = alpha;
+        auto layers = std::vector<LayerSettings>{layer};
+        renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+    }
+}
+
 //
 // The collection of shaders cached here were found by using perfetto to record shader compiles
 // during actions that involve RenderEngine, logging the layer settings, and the shader code
@@ -761,6 +789,12 @@
                 // Draw layers for b/185569240.
                 drawClippedLayers(renderengine, display, dstTexture, texture);
             }
+
+            if (com::android::graphics::libgui::flags::edge_extension_shader() &&
+                config.cacheEdgeExtension) {
+                drawEdgeExtensionLayers(renderengine, display, dstTexture, texture);
+                drawEdgeExtensionLayers(renderengine, p3Display, dstTexture, texture);
+            }
         }
 
         if (config.cachePIPImageLayers) {
diff --git a/libs/renderengine/skia/SkiaRenderEngine.cpp b/libs/renderengine/skia/SkiaRenderEngine.cpp
index 056e8fe..ec9d3ef 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -75,6 +75,7 @@
 #include "ColorSpaces.h"
 #include "compat/SkiaGpuContext.h"
 #include "filters/BlurFilter.h"
+#include "filters/GainmapFactory.h"
 #include "filters/GaussianBlurFilter.h"
 #include "filters/KawaseBlurDualFilter.h"
 #include "filters/KawaseBlurFilter.h"
@@ -238,12 +239,22 @@
 static inline SkPoint3 getSkPoint3(const android::vec3& vector) {
     return SkPoint3::Make(vector.x, vector.y, vector.z);
 }
+
 } // namespace
 
 namespace android {
 namespace renderengine {
 namespace skia {
 
+namespace {
+void trace(sp<Fence> fence) {
+    if (SFTRACE_ENABLED()) {
+        static gui::FenceMonitor sMonitor("RE Completion");
+        sMonitor.queueFence(std::move(fence));
+    }
+}
+} // namespace
+
 using base::StringAppendF;
 
 std::future<void> SkiaRenderEngine::primeCache(PrimeCacheConfig config) {
@@ -544,13 +555,15 @@
         const auto usingLocalTonemap =
                 parameters.display.tonemapStrategy == DisplaySettings::TonemapStrategy::Local &&
                 hdrType != HdrRenderType::SDR &&
-                shader->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr);
-
+                shader->isAImage((SkMatrix*)nullptr, (SkTileMode*)nullptr) &&
+                (hdrType != HdrRenderType::DISPLAY_HDR ||
+                 parameters.display.targetHdrSdrRatio < parameters.layerDimmingRatio);
         if (usingLocalTonemap) {
-            static MouriMap kMapper;
-            const float ratio =
+            const float inputRatio =
                     hdrType == HdrRenderType::GENERIC_HDR ? 1.0f : parameters.layerDimmingRatio;
-            shader = kMapper.mouriMap(getActiveContext(), shader, ratio);
+            static MouriMap kMapper;
+            shader = kMapper.mouriMap(getActiveContext(), shader, inputRatio,
+                                      parameters.display.targetHdrSdrRatio);
         }
 
         // disable tonemapping if we already locally tonemapped
@@ -1187,11 +1200,48 @@
 
     LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
     auto drawFence = sp<Fence>::make(flushAndSubmit(context, dstSurface));
+    trace(drawFence);
+    resultPromise->set_value(std::move(drawFence));
+}
 
-    if (SFTRACE_ENABLED()) {
-        static gui::FenceMonitor sMonitor("RE Completion");
-        sMonitor.queueFence(drawFence);
-    }
+void SkiaRenderEngine::drawGainmapInternal(
+        const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+        const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+        float hdrSdrRatio, ui::Dataspace dataspace,
+        const std::shared_ptr<ExternalTexture>& gainmap) {
+    std::lock_guard<std::mutex> lock(mRenderingMutex);
+    auto context = getActiveContext();
+    auto surfaceTextureRef = getOrCreateBackendTexture(gainmap->getBuffer(), true);
+    sk_sp<SkSurface> dstSurface =
+            surfaceTextureRef->getOrCreateSurface(ui::Dataspace::V0_SRGB_LINEAR);
+
+    waitFence(context, sdrFence);
+    const auto sdrTextureRef = getOrCreateBackendTexture(sdr->getBuffer(), false);
+    const auto sdrImage = sdrTextureRef->makeImage(dataspace, kPremul_SkAlphaType);
+    const auto sdrShader =
+            sdrImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
+                                 SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
+                                 nullptr);
+    waitFence(context, hdrFence);
+    const auto hdrTextureRef = getOrCreateBackendTexture(hdr->getBuffer(), false);
+    const auto hdrImage = hdrTextureRef->makeImage(dataspace, kPremul_SkAlphaType);
+    const auto hdrShader =
+            hdrImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
+                                 SkSamplingOptions({SkFilterMode::kLinear, SkMipmapMode::kNone}),
+                                 nullptr);
+
+    static GainmapFactory kGainmapFactory;
+    const auto gainmapShader = kGainmapFactory.createSkShader(sdrShader, hdrShader, hdrSdrRatio);
+
+    const auto canvas = dstSurface->getCanvas();
+    SkPaint paint;
+    paint.setShader(gainmapShader);
+    paint.setBlendMode(SkBlendMode::kSrc);
+    canvas->drawPaint(paint);
+
+    auto drawFence = sp<Fence>::make(flushAndSubmit(context, dstSurface));
+    trace(drawFence);
     resultPromise->set_value(std::move(drawFence));
 }
 
diff --git a/libs/renderengine/skia/SkiaRenderEngine.h b/libs/renderengine/skia/SkiaRenderEngine.h
index 721dbce..b5f8898 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -142,6 +142,13 @@
                             const std::vector<LayerSettings>& layers,
                             const std::shared_ptr<ExternalTexture>& buffer,
                             base::unique_fd&& bufferFence) override final;
+    void drawGainmapInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+                             const std::shared_ptr<ExternalTexture>& sdr,
+                             base::borrowed_fd&& sdrFence,
+                             const std::shared_ptr<ExternalTexture>& hdr,
+                             base::borrowed_fd&& hdrFence, float hdrSdrRatio,
+                             ui::Dataspace dataspace,
+                             const std::shared_ptr<ExternalTexture>& gainmap) override final;
 
     void dump(std::string& result) override final;
 
diff --git a/libs/renderengine/skia/filters/GainmapFactory.cpp b/libs/renderengine/skia/filters/GainmapFactory.cpp
new file mode 100644
index 0000000..e4d4fe9
--- /dev/null
+++ b/libs/renderengine/skia/filters/GainmapFactory.cpp
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2024 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 "GainmapFactory.h"
+
+#include <log/log.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+namespace {
+
+sk_sp<SkRuntimeEffect> makeEffect(const SkString& sksl) {
+    auto [effect, error] = SkRuntimeEffect::MakeForShader(sksl);
+    LOG_ALWAYS_FATAL_IF(!effect, "RuntimeShader error: %s", error.c_str());
+    return effect;
+}
+
+// Please refer to https://developer.android.com/media/platform/hdr-image-format#gain_map-generation
+static const SkString kGainmapShader = SkString(R"(
+    uniform shader sdr;
+    uniform shader hdr;
+    uniform float mapMaxLog2;
+
+    const float mapMinLog2 = 0.0;
+    const float mapGamma = 1.0;
+    const float offsetSdr = 0.015625;
+    const float offsetHdr = 0.015625;
+
+    float luminance(vec3 linearColor) {
+        return 0.2126 * linearColor.r + 0.7152 * linearColor.g + 0.0722 * linearColor.b;
+    }
+
+    vec4 main(vec2 xy) {
+        float sdrY = luminance(toLinearSrgb(sdr.eval(xy).rgb));
+        float hdrY = luminance(toLinearSrgb(hdr.eval(xy).rgb));
+        float pixelGain = (hdrY + offsetHdr) / (sdrY + offsetSdr);
+        float logRecovery = (log2(pixelGain) - mapMinLog2) / (mapMaxLog2 - mapMinLog2);
+        return vec4(pow(clamp(logRecovery, 0.0, 1.0), mapGamma));
+    }
+)");
+} // namespace
+
+const float INTERPOLATION_STRENGTH_VALUE = 0.7f;
+
+GainmapFactory::GainmapFactory() : mEffect(makeEffect(kGainmapShader)) {}
+
+sk_sp<SkShader> GainmapFactory::createSkShader(const sk_sp<SkShader>& sdr,
+                                               const sk_sp<SkShader>& hdr, float hdrSdrRatio) {
+    SkRuntimeShaderBuilder shaderBuilder(mEffect);
+    shaderBuilder.child("sdr") = sdr;
+    shaderBuilder.child("hdr") = hdr;
+    shaderBuilder.uniform("mapMaxLog2") = std::log2(hdrSdrRatio);
+    return shaderBuilder.makeShader();
+}
+
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/GainmapFactory.h b/libs/renderengine/skia/filters/GainmapFactory.h
new file mode 100644
index 0000000..7aea5e2
--- /dev/null
+++ b/libs/renderengine/skia/filters/GainmapFactory.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2024 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 <SkRuntimeEffect.h>
+#include <SkShader.h>
+
+namespace android {
+namespace renderengine {
+namespace skia {
+
+/**
+ * Generates a shader for computing a gainmap, given an SDR base image and its idealized HDR
+ * rendition. The shader follows the procedure in the UltraHDR spec:
+ * https://developer.android.com/media/platform/hdr-image-format#gain_map-generation, but makes some
+ * simplifying assumptions about metadata typical for RenderEngine's usage.
+ */
+class GainmapFactory {
+public:
+    GainmapFactory();
+    // Generates the gainmap shader. The hdrSdrRatio is the max_content_boost in the UltraHDR
+    // specification.
+    sk_sp<SkShader> createSkShader(const sk_sp<SkShader>& sdr, const sk_sp<SkShader>& hdr,
+                                   float hdrSdrRatio);
+
+private:
+    sk_sp<SkRuntimeEffect> mEffect;
+};
+} // namespace skia
+} // namespace renderengine
+} // namespace android
diff --git a/libs/renderengine/skia/filters/MouriMap.cpp b/libs/renderengine/skia/filters/MouriMap.cpp
index b458939..b099bcf 100644
--- a/libs/renderengine/skia/filters/MouriMap.cpp
+++ b/libs/renderengine/skia/filters/MouriMap.cpp
@@ -67,7 +67,7 @@
         float result = 0.0;
         for (int y = -2; y <= 2; y++) {
             for (int x = -2; x <= 2; x++) {
-            result += C[y + 2] * C[x + 2] * bitmap.eval(xy + vec2(x, y)).r;
+                result += C[y + 2] * C[x + 2] * bitmap.eval(xy + vec2(x, y)).r;
             }
         }
         return float4(float3(exp2(result)), 1.0);
@@ -78,18 +78,20 @@
     uniform shader lux;
     uniform float scaleFactor;
     uniform float hdrSdrRatio;
+    uniform float targetHdrSdrRatio;
     vec4 main(vec2 xy) {
         float localMax = lux.eval(xy * scaleFactor).r;
         float4 rgba = image.eval(xy);
         float3 linear = toLinearSrgb(rgba.rgb) * hdrSdrRatio;
 
-        if (localMax <= 1.0) {
+        if (localMax <= targetHdrSdrRatio) {
             return float4(fromLinearSrgb(linear), rgba.a);
         }
 
         float maxRGB = max(linear.r, max(linear.g, linear.b));
         localMax = max(localMax, maxRGB);
-        float gain = (1 + maxRGB / (localMax * localMax)) / (1 + maxRGB);
+        float gain = (1 + maxRGB * (targetHdrSdrRatio / (localMax * localMax)))
+                / (1 + maxRGB / targetHdrSdrRatio);
         return float4(fromLinearSrgb(linear * gain), rgba.a);
     }
 )");
@@ -114,10 +116,10 @@
         mTonemap(makeEffect(kTonemap)) {}
 
 sk_sp<SkShader> MouriMap::mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input,
-                                   float hdrSdrRatio) {
+                                   float hdrSdrRatio, float targetHdrSdrRatio) {
     auto downchunked = downchunk(context, input, hdrSdrRatio);
     auto localLux = blur(context, downchunked.get());
-    return tonemap(input, localLux.get(), hdrSdrRatio);
+    return tonemap(input, localLux.get(), hdrSdrRatio, targetHdrSdrRatio);
 }
 
 sk_sp<SkImage> MouriMap::downchunk(SkiaGpuContext* context, sk_sp<SkShader> input,
@@ -166,8 +168,8 @@
     LOG_ALWAYS_FATAL_IF(!blurSurface, "%s: Failed to create surface!", __func__);
     return makeImage(blurSurface.get(), blurBuilder);
 }
-sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux,
-                                  float hdrSdrRatio) const {
+sk_sp<SkShader> MouriMap::tonemap(sk_sp<SkShader> input, SkImage* localLux, float hdrSdrRatio,
+                                  float targetHdrSdrRatio) const {
     static constexpr float kScaleFactor = 1.0f / 128.0f;
     SkRuntimeShaderBuilder tonemapBuilder(mTonemap);
     tonemapBuilder.child("image") = input;
@@ -176,8 +178,9 @@
                                     SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone));
     tonemapBuilder.uniform("scaleFactor") = kScaleFactor;
     tonemapBuilder.uniform("hdrSdrRatio") = hdrSdrRatio;
+    tonemapBuilder.uniform("targetHdrSdrRatio") = targetHdrSdrRatio;
     return tonemapBuilder.makeShader();
 }
 } // namespace skia
 } // namespace renderengine
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/renderengine/skia/filters/MouriMap.h b/libs/renderengine/skia/filters/MouriMap.h
index 3c0df8a..9ba2b6f 100644
--- a/libs/renderengine/skia/filters/MouriMap.h
+++ b/libs/renderengine/skia/filters/MouriMap.h
@@ -64,13 +64,16 @@
     // Apply the MouriMap tonemmaping operator to the input.
     // The HDR/SDR ratio describes the luminace range of the input. 1.0 means SDR. Anything larger
     // then 1.0 means that there is headroom above the SDR region.
-    sk_sp<SkShader> mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, float hdrSdrRatio);
+    // Similarly, the target HDR/SDR ratio describes the luminance range of the output.
+    sk_sp<SkShader> mouriMap(SkiaGpuContext* context, sk_sp<SkShader> input, float inputHdrSdrRatio,
+                             float targetHdrSdrRatio);
 
 private:
     sk_sp<SkImage> downchunk(SkiaGpuContext* context, sk_sp<SkShader> input,
                              float hdrSdrRatio) const;
     sk_sp<SkImage> blur(SkiaGpuContext* context, SkImage* input) const;
-    sk_sp<SkShader> tonemap(sk_sp<SkShader> input, SkImage* localLux, float hdrSdrRatio) const;
+    sk_sp<SkShader> tonemap(sk_sp<SkShader> input, SkImage* localLux, float hdrSdrRatio,
+                            float targetHdrSdrRatio) const;
     const sk_sp<SkRuntimeEffect> mCrosstalkAndChunk16x16;
     const sk_sp<SkRuntimeEffect> mChunk8x8;
     const sk_sp<SkRuntimeEffect> mBlur;
@@ -78,4 +81,4 @@
 };
 } // namespace skia
 } // namespace renderengine
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.cpp b/libs/renderengine/threaded/RenderEngineThreaded.cpp
index f5a90fd..c187f93 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -249,6 +249,16 @@
     return;
 }
 
+void RenderEngineThreaded::drawGainmapInternal(
+        const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+        const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+        float hdrSdrRatio, ui::Dataspace dataspace,
+        const std::shared_ptr<ExternalTexture>& gainmap) {
+    resultPromise->set_value(Fence::NO_FENCE);
+    return;
+}
+
 ftl::Future<FenceResult> RenderEngineThreaded::drawLayers(
         const DisplaySettings& display, const std::vector<LayerSettings>& layers,
         const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
@@ -262,7 +272,7 @@
         mFunctionCalls.push(
                 [resultPromise, display, layers, buffer, fd](renderengine::RenderEngine& instance) {
                     SFTRACE_NAME("REThreaded::drawLayers");
-                    instance.updateProtectedContext(layers, buffer);
+                    instance.updateProtectedContext(layers, {buffer.get()});
                     instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
                                                 base::unique_fd(fd));
                 });
@@ -271,6 +281,30 @@
     return resultFuture;
 }
 
+ftl::Future<FenceResult> RenderEngineThreaded::drawGainmap(
+        const std::shared_ptr<ExternalTexture>& sdr, base::borrowed_fd&& sdrFence,
+        const std::shared_ptr<ExternalTexture>& hdr, base::borrowed_fd&& hdrFence,
+        float hdrSdrRatio, ui::Dataspace dataspace,
+        const std::shared_ptr<ExternalTexture>& gainmap) {
+    SFTRACE_CALL();
+    const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
+    std::future<FenceResult> resultFuture = resultPromise->get_future();
+    {
+        std::lock_guard lock(mThreadMutex);
+        mNeedsPostRenderCleanup = true;
+        mFunctionCalls.push([resultPromise, sdr, sdrFence = std::move(sdrFence), hdr,
+                             hdrFence = std::move(hdrFence), hdrSdrRatio, dataspace,
+                             gainmap](renderengine::RenderEngine& instance) mutable {
+            SFTRACE_NAME("REThreaded::drawGainmap");
+            instance.updateProtectedContext({}, {sdr.get(), hdr.get(), gainmap.get()});
+            instance.drawGainmapInternal(std::move(resultPromise), sdr, std::move(sdrFence), hdr,
+                                         std::move(hdrFence), hdrSdrRatio, dataspace, gainmap);
+        });
+    }
+    mCondition.notify_one();
+    return resultFuture;
+}
+
 int RenderEngineThreaded::getContextPriority() {
     std::promise<int> resultPromise;
     std::future<int> resultFuture = resultPromise.get_future();
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index d4997d6..cb6e924 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -55,6 +55,12 @@
                                         const std::vector<LayerSettings>& layers,
                                         const std::shared_ptr<ExternalTexture>& buffer,
                                         base::unique_fd&& bufferFence) override;
+    ftl::Future<FenceResult> drawGainmap(const std::shared_ptr<ExternalTexture>& sdr,
+                                         base::borrowed_fd&& sdrFence,
+                                         const std::shared_ptr<ExternalTexture>& hdr,
+                                         base::borrowed_fd&& hdrFence, float hdrSdrRatio,
+                                         ui::Dataspace dataspace,
+                                         const std::shared_ptr<ExternalTexture>& gainmap) override;
 
     int getContextPriority() override;
     bool supportsBackgroundBlur() override;
@@ -71,6 +77,13 @@
                             const std::vector<LayerSettings>& layers,
                             const std::shared_ptr<ExternalTexture>& buffer,
                             base::unique_fd&& bufferFence) override;
+    void drawGainmapInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
+                             const std::shared_ptr<ExternalTexture>& sdr,
+                             base::borrowed_fd&& sdrFence,
+                             const std::shared_ptr<ExternalTexture>& hdr,
+                             base::borrowed_fd&& hdrFence, float hdrSdrRatio,
+                             ui::Dataspace dataspace,
+                             const std::shared_ptr<ExternalTexture>& gainmap) override;
 
 private:
     void threadMain(CreateInstanceFactory factory);
diff --git a/libs/ui/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index e5af740..8b13d78 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -26,6 +26,7 @@
 #include <ftl/hash.h>
 #include <log/log.h>
 #include <ui/DisplayIdentification.h>
+#include <ui/Size.h>
 
 namespace android {
 namespace {
@@ -46,6 +47,10 @@
     return view[3];
 }
 
+bool isDetailedTimingDescriptor(const byte_view& view) {
+    return view[0] != 0 && view[1] != 0;
+}
+
 std::string_view parseEdidText(const byte_view& view) {
     std::string_view text(reinterpret_cast<const char*>(view.data()), view.size());
     text = text.substr(0, text.find('\n'));
@@ -219,6 +224,8 @@
     std::string_view displayName;
     std::string_view serialNumber;
     std::string_view asciiText;
+    ui::Size preferredDTDPixelSize;
+    ui::Size preferredDTDPhysicalSize;
 
     constexpr size_t kDescriptorCount = 4;
     constexpr size_t kDescriptorLength = 18;
@@ -243,6 +250,35 @@
                     serialNumber = parseEdidText(descriptor);
                     break;
             }
+        } else if (isDetailedTimingDescriptor(view)) {
+            static constexpr size_t kHorizontalPhysicalLsbOffset = 12;
+            static constexpr size_t kHorizontalPhysicalMsbOffset = 14;
+            static constexpr size_t kVerticalPhysicalLsbOffset = 13;
+            static constexpr size_t kVerticalPhysicalMsbOffset = 14;
+            const uint32_t hSize =
+                    static_cast<uint32_t>(view[kHorizontalPhysicalLsbOffset] |
+                                          ((view[kHorizontalPhysicalMsbOffset] >> 4) << 8));
+            const uint32_t vSize =
+                    static_cast<uint32_t>(view[kVerticalPhysicalLsbOffset] |
+                                          ((view[kVerticalPhysicalMsbOffset] & 0b1111) << 8));
+
+            static constexpr size_t kHorizontalPixelLsbOffset = 2;
+            static constexpr size_t kHorizontalPixelMsbOffset = 4;
+            static constexpr size_t kVerticalPixelLsbOffset = 5;
+            static constexpr size_t kVerticalPixelMsbOffset = 7;
+
+            const uint8_t hLsb = view[kHorizontalPixelLsbOffset];
+            const uint8_t hMsb = view[kHorizontalPixelMsbOffset];
+            const int32_t hPixel = hLsb + ((hMsb & 0xF0) << 4);
+
+            const uint8_t vLsb = view[kVerticalPixelLsbOffset];
+            const uint8_t vMsb = view[kVerticalPixelMsbOffset];
+            const int32_t vPixel = vLsb + ((vMsb & 0xF0) << 4);
+
+            preferredDTDPixelSize.setWidth(hPixel);
+            preferredDTDPixelSize.setHeight(vPixel);
+            preferredDTDPhysicalSize.setWidth(hSize);
+            preferredDTDPhysicalSize.setHeight(vSize);
         }
 
         view = view.subspan(kDescriptorLength);
@@ -297,14 +333,22 @@
         }
     }
 
-    return Edid{.manufacturerId = manufacturerId,
-                .productId = productId,
-                .pnpId = *pnpId,
-                .modelHash = modelHash,
-                .displayName = displayName,
-                .manufactureOrModelYear = manufactureOrModelYear,
-                .manufactureWeek = manufactureWeek,
-                .cea861Block = cea861Block};
+    DetailedTimingDescriptor preferredDetailedTimingDescriptor{
+            .pixelSizeCount = preferredDTDPixelSize,
+            .physicalSizeInMm = preferredDTDPhysicalSize,
+    };
+
+    return Edid{
+            .manufacturerId = manufacturerId,
+            .productId = productId,
+            .pnpId = *pnpId,
+            .modelHash = modelHash,
+            .displayName = displayName,
+            .manufactureOrModelYear = manufactureOrModelYear,
+            .manufactureWeek = manufactureWeek,
+            .cea861Block = cea861Block,
+            .preferredDetailedTimingDescriptor = preferredDetailedTimingDescriptor,
+    };
 }
 
 std::optional<PnpId> getPnpId(uint16_t manufacturerId) {
@@ -336,9 +380,12 @@
     }
 
     const auto displayId = PhysicalDisplayId::fromEdid(port, edid->manufacturerId, edid->modelHash);
-    return DisplayIdentificationInfo{.id = displayId,
-                                     .name = std::string(edid->displayName),
-                                     .deviceProductInfo = buildDeviceProductInfo(*edid)};
+    return DisplayIdentificationInfo{
+            .id = displayId,
+            .name = std::string(edid->displayName),
+            .deviceProductInfo = buildDeviceProductInfo(*edid),
+            .preferredDetailedTimingDescriptor = edid->preferredDetailedTimingDescriptor,
+    };
 }
 
 PhysicalDisplayId getVirtualDisplayId(uint32_t id) {
diff --git a/libs/ui/GraphicBuffer.cpp b/libs/ui/GraphicBuffer.cpp
index ffb6cdb..b0c6e44 100644
--- a/libs/ui/GraphicBuffer.cpp
+++ b/libs/ui/GraphicBuffer.cpp
@@ -388,8 +388,8 @@
         }
     }
 
-    const uint64_t usage = static_cast<uint64_t>(
-            android_convertGralloc1To0Usage(inProducerUsage, inConsumerUsage));
+    const uint64_t usage = static_cast<uint64_t>(ANDROID_NATIVE_UNSIGNED_CAST(
+            android_convertGralloc1To0Usage(inProducerUsage, inConsumerUsage)));
 
     auto result = getBufferMapper().lock(handle, usage, rect, base::unique_fd{fenceFd});
 
diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp
index b6ab2f5..7b5a27d 100644
--- a/libs/ui/GraphicBufferMapper.cpp
+++ b/libs/ui/GraphicBufferMapper.cpp
@@ -208,8 +208,10 @@
 status_t GraphicBufferMapper::lockAsync(buffer_handle_t handle, uint64_t producerUsage,
                                         uint64_t consumerUsage, const Rect& bounds, void** vaddr,
                                         int fenceFd) {
-    return lockAsync(handle, android_convertGralloc1To0Usage(producerUsage, consumerUsage), bounds,
-                     vaddr, fenceFd);
+    return lockAsync(handle,
+                     ANDROID_NATIVE_UNSIGNED_CAST(
+                             android_convertGralloc1To0Usage(producerUsage, consumerUsage)),
+                     bounds, vaddr, fenceFd);
 }
 
 status_t GraphicBufferMapper::lockAsyncYCbCr(buffer_handle_t handle, uint32_t usage,
diff --git a/libs/ui/include/ui/DisplayIdentification.h b/libs/ui/include/ui/DisplayIdentification.h
index 8bc2017..648e024 100644
--- a/libs/ui/include/ui/DisplayIdentification.h
+++ b/libs/ui/include/ui/DisplayIdentification.h
@@ -25,6 +25,7 @@
 
 #include <ui/DeviceProductInfo.h>
 #include <ui/DisplayId.h>
+#include <ui/Size.h>
 
 #define LEGACY_DISPLAY_TYPE_PRIMARY 0
 #define LEGACY_DISPLAY_TYPE_EXTERNAL 1
@@ -33,10 +34,16 @@
 
 using DisplayIdentificationData = std::vector<uint8_t>;
 
+struct DetailedTimingDescriptor {
+    ui::Size pixelSizeCount;
+    ui::Size physicalSizeInMm;
+};
+
 struct DisplayIdentificationInfo {
     PhysicalDisplayId id;
     std::string name;
     std::optional<DeviceProductInfo> deviceProductInfo;
+    std::optional<DetailedTimingDescriptor> preferredDetailedTimingDescriptor;
 };
 
 struct ExtensionBlock {
@@ -68,6 +75,7 @@
     uint8_t manufactureOrModelYear;
     uint8_t manufactureWeek;
     std::optional<Cea861ExtensionBlock> cea861Block;
+    std::optional<DetailedTimingDescriptor> preferredDetailedTimingDescriptor;
 };
 
 bool isEdid(const DisplayIdentificationData&);
diff --git a/libs/ui/include/ui/FloatRect.h b/libs/ui/include/ui/FloatRect.h
index 4c9c7b7..4366db5 100644
--- a/libs/ui/include/ui/FloatRect.h
+++ b/libs/ui/include/ui/FloatRect.h
@@ -51,6 +51,9 @@
     float bottom = 0.0f;
 
     constexpr bool isEmpty() const { return !(left < right && top < bottom); }
+
+    // a valid rectangle has a non negative width and height
+    inline bool isValid() const { return (getWidth() >= 0) && (getHeight() >= 0); }
 };
 
 inline bool operator==(const FloatRect& a, const FloatRect& b) {
diff --git a/libs/ui/include/ui/Rect.h b/libs/ui/include/ui/Rect.h
index 2eb9330..2307b44 100644
--- a/libs/ui/include/ui/Rect.h
+++ b/libs/ui/include/ui/Rect.h
@@ -74,12 +74,10 @@
     }
 
     inline explicit Rect(const FloatRect& floatRect) {
-        // Ideally we would use std::round, but we don't want to add an STL
-        // dependency here, so we use an approximation
-        left = static_cast<int32_t>(floatRect.left + 0.5f);
-        top = static_cast<int32_t>(floatRect.top + 0.5f);
-        right = static_cast<int32_t>(floatRect.right + 0.5f);
-        bottom = static_cast<int32_t>(floatRect.bottom + 0.5f);
+        left = static_cast<int32_t>(std::round(floatRect.left));
+        top = static_cast<int32_t>(std::round(floatRect.top));
+        right = static_cast<int32_t>(std::round(floatRect.right));
+        bottom = static_cast<int32_t>(std::round(floatRect.bottom));
     }
 
     inline explicit Rect(const ui::Size& size) {
diff --git a/libs/ui/tests/DisplayIdentification_test.cpp b/libs/ui/tests/DisplayIdentification_test.cpp
index 721b466..76e3f66 100644
--- a/libs/ui/tests/DisplayIdentification_test.cpp
+++ b/libs/ui/tests/DisplayIdentification_test.cpp
@@ -194,6 +194,10 @@
     EXPECT_EQ(21, edid->manufactureOrModelYear);
     EXPECT_EQ(0, edid->manufactureWeek);
     EXPECT_FALSE(edid->cea861Block);
+    EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(261, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(163, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 
     edid = parseEdid(getExternalEdid());
     ASSERT_TRUE(edid);
@@ -206,6 +210,10 @@
     EXPECT_EQ(22, edid->manufactureOrModelYear);
     EXPECT_EQ(2, edid->manufactureWeek);
     EXPECT_FALSE(edid->cea861Block);
+    EXPECT_EQ(1280, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(800, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(641, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(400, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 
     edid = parseEdid(getExternalEedid());
     ASSERT_TRUE(edid);
@@ -224,6 +232,10 @@
     EXPECT_EQ(0, physicalAddress.b);
     EXPECT_EQ(0, physicalAddress.c);
     EXPECT_EQ(0, physicalAddress.d);
+    EXPECT_EQ(1366, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(768, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(160, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(90, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 
     edid = parseEdid(getPanasonicTvEdid());
     ASSERT_TRUE(edid);
@@ -242,6 +254,10 @@
     EXPECT_EQ(0, physicalAddress.b);
     EXPECT_EQ(0, physicalAddress.c);
     EXPECT_EQ(0, physicalAddress.d);
+    EXPECT_EQ(1920, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(1080, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(698, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(392, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 
     edid = parseEdid(getHisenseTvEdid());
     ASSERT_TRUE(edid);
@@ -260,6 +276,10 @@
     EXPECT_EQ(2, physicalAddress.b);
     EXPECT_EQ(3, physicalAddress.c);
     EXPECT_EQ(4, physicalAddress.d);
+    EXPECT_EQ(1920, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(1080, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(575, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(323, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 
     edid = parseEdid(getCtlDisplayEdid());
     ASSERT_TRUE(edid);
@@ -273,6 +293,10 @@
     EXPECT_EQ(0xff, edid->manufactureWeek);
     ASSERT_TRUE(edid->cea861Block);
     EXPECT_FALSE(edid->cea861Block->hdmiVendorDataBlock);
+    EXPECT_EQ(1360, edid->preferredDetailedTimingDescriptor->pixelSizeCount.width);
+    EXPECT_EQ(768, edid->preferredDetailedTimingDescriptor->pixelSizeCount.height);
+    EXPECT_EQ(521, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.width);
+    EXPECT_EQ(293, edid->preferredDetailedTimingDescriptor->physicalSizeInMm.height);
 }
 
 TEST(DisplayIdentificationTest, parseInvalidEdid) {
diff --git a/libs/ui/tests/Rect_test.cpp b/libs/ui/tests/Rect_test.cpp
index 9cc36bb..c3c8bd9 100644
--- a/libs/ui/tests/Rect_test.cpp
+++ b/libs/ui/tests/Rect_test.cpp
@@ -99,6 +99,16 @@
         EXPECT_EQ(30, rect.right);
         EXPECT_EQ(40, rect.bottom);
     }
+
+    EXPECT_EQ(Rect(0, 1, -1, 0), Rect(FloatRect(0.f, 1.f, -1.f, 0.f)));
+    EXPECT_EQ(Rect(100000, 100000, -100000, -100000),
+              Rect(FloatRect(100000.f, 100000.f, -100000.f, -100000.f)));
+
+    // round down if < .5
+    EXPECT_EQ(Rect(0, 1, -1, 0), Rect(FloatRect(0.4f, 1.1f, -1.499f, 0.1f)));
+
+    // round up if >= .5
+    EXPECT_EQ(Rect(20, 20, -20, -20), Rect(FloatRect(19.5f, 19.9f, -19.5f, -19.9f)));
 }
 
 TEST(RectTest, makeInvalid) {
diff --git a/libs/vibrator/TEST_MAPPING b/libs/vibrator/TEST_MAPPING
index d782b43..e206761 100644
--- a/libs/vibrator/TEST_MAPPING
+++ b/libs/vibrator/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "libvibrator_test"
     }
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index 6e35041..6713a5c 100644
--- a/opengl/libs/EGL/egl_platform_entries.cpp
+++ b/opengl/libs/EGL/egl_platform_entries.cpp
@@ -2141,6 +2141,10 @@
     }
 
     egl_surface_t const* const s = get_surface(surface);
+    if (!s->getNativeWindow()) {
+        setError(EGL_BAD_SURFACE, EGL_FALSE);
+        return EGL_FALSE;
+    }
     native_window_set_buffers_timestamp(s->getNativeWindow(), time);
 
     return EGL_TRUE;
@@ -2405,7 +2409,7 @@
         case 0:
             return EGL_TRUE;
         case -ENOENT:
-            return setError(EGL_BAD_ACCESS, (EGLBoolean)EGL_FALSE);
+            return setErrorQuiet(EGL_BAD_ACCESS, (EGLBoolean)EGL_FALSE);
         case -ENOSYS:
             return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
         case -EINVAL:
diff --git a/services/gpuservice/vts/TEST_MAPPING b/services/gpuservice/vts/TEST_MAPPING
index b33e962..a809be1 100644
--- a/services/gpuservice/vts/TEST_MAPPING
+++ b/services/gpuservice/vts/TEST_MAPPING
@@ -1,7 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "GpuServiceVendorTests"
+      "name": "GpuServiceVendorTests",
+      "options": [
+        {
+          // Exclude test methods that require a physical device to run.
+          "exclude-annotation": "android.platform.test.annotations.RequiresDevice"
+        }
+      ]
     }
   ]
 }
diff --git a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
index 6c16335..5c12323 100644
--- a/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
+++ b/services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java
@@ -19,7 +19,7 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
-import android.platform.test.annotations.RestrictedBuildTest;
+import android.platform.test.annotations.RequiresDevice;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -63,7 +63,7 @@
     }
 
     @VsrTest(requirements={"VSR-3.3-004"})
-    @RestrictedBuildTest
+    @RequiresDevice
     @Test
     public void testGpuWorkPeriodTracepointFormat() throws Exception {
         CommandResult commandResult = getDevice().executeShellV2Command(
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index cb220ab..ca92ab5 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -217,6 +217,7 @@
         "libcutils",
         "libinput",
         "liblog",
+        "libprocessgroup",
         "libstatslog",
         "libutils",
     ],
diff --git a/services/inputflinger/InputFilterCallbacks.cpp b/services/inputflinger/InputFilterCallbacks.cpp
index 5fbdc84..493459a 100644
--- a/services/inputflinger/InputFilterCallbacks.cpp
+++ b/services/inputflinger/InputFilterCallbacks.cpp
@@ -44,7 +44,8 @@
     InputFilterThread(std::shared_ptr<IInputThreadCallback> callback) : mCallback(callback) {
         mLooper = sp<Looper>::make(/*allowNonCallbacks=*/false);
         mThread = std::make_unique<InputThread>(
-                "InputFilter", [this]() { loopOnce(); }, [this]() { mLooper->wake(); });
+                "InputFilter", [this]() { loopOnce(); }, [this]() { mLooper->wake(); },
+                /*isInCriticalPath=*/false);
     }
 
     ndk::ScopedAStatus finish() override {
diff --git a/services/inputflinger/InputThread.cpp b/services/inputflinger/InputThread.cpp
index e74f258..7cf4e39 100644
--- a/services/inputflinger/InputThread.cpp
+++ b/services/inputflinger/InputThread.cpp
@@ -16,10 +16,26 @@
 
 #include "InputThread.h"
 
+#include <android-base/logging.h>
+#include <com_android_input_flags.h>
+#include <processgroup/processgroup.h>
+
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+
 namespace {
 
+bool applyInputEventProfile(const Thread& thread) {
+#if defined(__ANDROID__)
+    return SetTaskProfiles(thread.getTid(), {"InputPolicy"});
+#else
+    // Since thread information is not available and there's no benefit of
+    // applying the task profile on host, return directly.
+    return true;
+#endif
+}
+
 // Implementation of Thread from libutils.
 class InputThreadImpl : public Thread {
 public:
@@ -39,10 +55,16 @@
 
 } // namespace
 
-InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake)
-      : mName(name), mThreadWake(wake) {
+InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake,
+                         bool isInCriticalPath)
+      : mThreadWake(wake) {
     mThread = sp<InputThreadImpl>::make(loop);
-    mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
+    mThread->run(name.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
+    if (input_flags::enable_input_policy_profile() && isInCriticalPath) {
+        if (!applyInputEventProfile(*mThread)) {
+            LOG(ERROR) << "Couldn't apply input policy profile for " << name;
+        }
+    }
 }
 
 InputThread::~InputThread() {
@@ -63,4 +85,4 @@
 #endif
 }
 
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 397feda..006d507 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -407,7 +407,8 @@
         // TODO(b/315815559): Do not fade and reset the icon if the hover exit will be followed
         //   immediately by a DOWN event.
         pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
-        pc.updatePointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED);
+        pc.updatePointerIcon(mShowTouchesEnabled ? PointerIconStyle::TYPE_SPOT_HOVER
+                                                 : PointerIconStyle::TYPE_NOT_SPECIFIED);
     } else if (canUnfadeOnDisplay(args.displayId)) {
         pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
     }
@@ -792,6 +793,13 @@
     if (isFromSource(sources, AINPUT_SOURCE_STYLUS)) {
         auto it = mStylusPointersByDevice.find(deviceId);
         if (it != mStylusPointersByDevice.end()) {
+            if (mShowTouchesEnabled) {
+                // If an app doesn't override the icon for the hovering stylus, show the hover icon.
+                auto* style = std::get_if<PointerIconStyle>(&icon);
+                if (style != nullptr && *style == PointerIconStyle::TYPE_NOT_SPECIFIED) {
+                    *style = PointerIconStyle::TYPE_SPOT_HOVER;
+                }
+            }
             setIconForController(icon, *it->second);
             return true;
         }
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index af9d2eb..10fec74 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -298,9 +298,14 @@
       "name": "CtsInputRootTestCases"
     }
   ],
-  "staged-platinum-postsubmit": [
+  "platinum-postsubmit": [
     {
       "name": "inputflinger_tests"
     }
+  ],
+  "staged-platinum-postsubmit": [
+    {
+      "name": "libinput_tests"
+    }
   ]
 }
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 1a0ec48..8b2b843 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -46,6 +46,7 @@
         "InputState.cpp",
         "InputTarget.cpp",
         "LatencyAggregator.cpp",
+        "LatencyAggregatorWithHistograms.cpp",
         "LatencyTracker.cpp",
         "Monitor.cpp",
         "TouchedWindow.cpp",
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index 4a0889f..568d348 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -32,7 +32,8 @@
         CANCEL_POINTER_EVENTS = 1,
         CANCEL_NON_POINTER_EVENTS = 2,
         CANCEL_FALLBACK_EVENTS = 3,
-        ftl_last = CANCEL_FALLBACK_EVENTS,
+        CANCEL_HOVER_EVENTS = 4,
+        ftl_last = CANCEL_HOVER_EVENTS
     };
 
     // The criterion to use to determine which events should be canceled.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 250e72c..4b43c27 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -155,6 +155,10 @@
 // Number of recent events to keep for debugging purposes.
 constexpr size_t RECENT_QUEUE_MAX_SIZE = 10;
 
+// Interval at which we should push the atom gathering input event latencies in
+// LatencyAggregatorWithHistograms
+constexpr nsecs_t LATENCY_STATISTICS_PUSH_INTERVAL = 6 * 3600 * 1000000000LL; // 6 hours
+
 // Event log tags. See EventLogTags.logtags for reference.
 constexpr int LOGTAG_INPUT_INTERACTION = 62000;
 constexpr int LOGTAG_INPUT_FOCUS = 62001;
@@ -748,7 +752,8 @@
             }
             touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS;
         }
-        touchedWindow.addHoveringPointer(entry.deviceId, pointer);
+        const auto [x, y] = resolveTouchedPosition(entry);
+        touchedWindow.addHoveringPointer(entry.deviceId, pointer, x, y);
         if (canReceiveForegroundTouches(*newWindow->getInfo())) {
             touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
         }
@@ -875,6 +880,8 @@
             return {false, true};
         case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS:
             return {false, true};
+        case CancelationOptions::Mode::CANCEL_HOVER_EVENTS:
+            return {true, false};
     }
 }
 
@@ -944,8 +951,13 @@
         mFocusedDisplayId(ui::LogicalDisplayId::DEFAULT),
         mWindowTokenWithPointerCapture(nullptr),
         mAwaitedApplicationDisplayId(ui::LogicalDisplayId::INVALID),
-        mLatencyAggregator(),
-        mLatencyTracker(&mLatencyAggregator) {
+        mInputEventTimelineProcessor(
+                input_flags::enable_per_device_input_latency_metrics()
+                        ? std::move(std::unique_ptr<InputEventTimelineProcessor>(
+                                  new LatencyAggregatorWithHistograms()))
+                        : std::move(std::unique_ptr<InputEventTimelineProcessor>(
+                                  new LatencyAggregator()))),
+        mLatencyTracker(*mInputEventTimelineProcessor) {
     mLooper = sp<Looper>::make(false);
     mReporter = createInputReporter();
 
@@ -981,7 +993,8 @@
         return ALREADY_EXISTS;
     }
     mThread = std::make_unique<InputThread>(
-            "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
+            "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); },
+            /*isInCriticalPath=*/true);
     return OK;
 }
 
@@ -1017,6 +1030,11 @@
         const nsecs_t nextAnrCheck = processAnrsLocked();
         nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);
 
+        if (mPerDeviceInputLatencyMetricsFlag) {
+            const nsecs_t nextStatisticsPush = processLatencyStatisticsLocked();
+            nextWakeupTime = std::min(nextWakeupTime, nextStatisticsPush);
+        }
+
         // We are about to enter an infinitely long sleep, because we have no commands or
         // pending or queued events
         if (nextWakeupTime == LLONG_MAX) {
@@ -1097,6 +1115,21 @@
     return LLONG_MIN;
 }
 
+/**
+ * Check if enough time has passed since the last latency statistics push.
+ * Return the time at which we should wake up next.
+ */
+nsecs_t InputDispatcher::processLatencyStatisticsLocked() {
+    const nsecs_t currentTime = now();
+    // Log the atom recording latency statistics if more than 6 hours passed from the last
+    // push
+    if (currentTime - mLastStatisticPushTime >= LATENCY_STATISTICS_PUSH_INTERVAL) {
+        mInputEventTimelineProcessor->pushLatencyStatistics();
+        mLastStatisticPushTime = currentTime;
+    }
+    return mLastStatisticPushTime + LATENCY_STATISTICS_PUSH_INTERVAL;
+}
+
 std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(
         const std::shared_ptr<Connection>& connection) {
     if (connection->monitor) {
@@ -2511,7 +2544,8 @@
 
             if (isHoverAction) {
                 // The "windowHandle" is the target of this hovering pointer.
-                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer);
+                tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointer, x,
+                                                          y);
             }
 
             // Set target flags.
@@ -4491,6 +4525,10 @@
     { // acquire lock
         mLock.lock();
 
+        if (input_flags::keyboard_repeat_keys() && !mConfig.keyRepeatEnabled) {
+            policyFlags |= POLICY_FLAG_DISABLE_KEY_REPEAT;
+        }
+
         if (shouldSendKeyToInputFilterLocked(args)) {
             mLock.unlock();
 
@@ -4511,6 +4549,14 @@
             newEntry->traceTracker = mTracer->traceInboundEvent(*newEntry);
         }
 
+        if (mPerDeviceInputLatencyMetricsFlag) {
+            if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
+                IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER &&
+                !mInputFilterEnabled) {
+                mLatencyTracker.trackNotifyKey(args);
+            }
+        }
+
         needWake = enqueueInboundEventLocked(std::move(newEntry));
         mLock.unlock();
     } // release lock
@@ -4643,9 +4689,7 @@
         if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
             IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER &&
             !mInputFilterEnabled) {
-            std::set<InputDeviceUsageSource> sources = getUsageSourcesForMotionArgs(args);
-            mLatencyTracker.trackListener(args.id, args.eventTime, args.readTime, args.deviceId,
-                                          sources, args.action, InputEventType::MOTION);
+            mLatencyTracker.trackNotifyMotion(args);
         }
 
         needWake = enqueueInboundEventLocked(std::move(newEntry));
@@ -5415,6 +5459,32 @@
         }
     }
 
+    // Check if the hovering should stop because the window is no longer eligible to receive it
+    // (for example, if the touchable region changed)
+    if (const auto& it = mTouchStatesByDisplay.find(displayId); it != mTouchStatesByDisplay.end()) {
+        TouchState& state = it->second;
+        for (TouchedWindow& touchedWindow : state.windows) {
+            std::vector<DeviceId> erasedDevices = touchedWindow.eraseHoveringPointersIf(
+                    [this, displayId, &touchedWindow](const PointerProperties& properties, float x,
+                                                      float y) REQUIRES(mLock) {
+                        const bool isStylus = properties.toolType == ToolType::STYLUS;
+                        const ui::Transform displayTransform = getTransformLocked(displayId);
+                        const bool stillAcceptsTouch =
+                                windowAcceptsTouchAt(*touchedWindow.windowHandle->getInfo(),
+                                                     displayId, x, y, isStylus, displayTransform);
+                        return !stillAcceptsTouch;
+                    });
+
+            for (DeviceId deviceId : erasedDevices) {
+                CancelationOptions options(CancelationOptions::Mode::CANCEL_HOVER_EVENTS,
+                                           "WindowInfo changed",
+                                           traceContext.getTracker());
+                options.deviceId = deviceId;
+                synthesizeCancelationEventsForWindowLocked(touchedWindow.windowHandle, options);
+            }
+        }
+    }
+
     // Release information for windows that are no longer present.
     // This ensures that unused input channels are released promptly.
     // Otherwise, they might stick around until the window handle is destroyed
@@ -6055,7 +6125,7 @@
     dump += StringPrintf(INDENT2 "KeyRepeatTimeout: %" PRId64 "ms\n",
                          ns2ms(mConfig.keyRepeatTimeout));
     dump += mLatencyTracker.dump(INDENT2);
-    dump += mLatencyAggregator.dump(INDENT2);
+    dump += mInputEventTimelineProcessor->dump(INDENT2);
     dump += INDENT "InputTracer: ";
     dump += mTracer == nullptr ? "Disabled" : "Enabled";
 }
@@ -7209,11 +7279,13 @@
 }
 
 void InputDispatcher::setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
-                                                std::chrono::nanoseconds delay) {
+                                                std::chrono::nanoseconds delay,
+                                                bool keyRepeatEnabled) {
     std::scoped_lock _l(mLock);
 
     mConfig.keyRepeatTimeout = timeout.count();
     mConfig.keyRepeatDelay = delay.count();
+    mConfig.keyRepeatEnabled = keyRepeatEnabled;
 }
 
 bool InputDispatcher::isPointerInWindow(const sp<android::IBinder>& token,
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 87dfd1d..24e36ae 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <com_android_input_flags.h>
+
 #include "AnrTracker.h"
 #include "CancelationOptions.h"
 #include "DragState.h"
@@ -28,6 +30,7 @@
 #include "InputTarget.h"
 #include "InputThread.h"
 #include "LatencyAggregator.h"
+#include "LatencyAggregatorWithHistograms.h"
 #include "LatencyTracker.h"
 #include "Monitor.h"
 #include "TouchState.h"
@@ -153,8 +156,8 @@
     // Public to allow tests to verify that a Monitor can get ANR.
     void setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout);
 
-    void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
-                                   std::chrono::nanoseconds delay) override;
+    void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout, std::chrono::nanoseconds delay,
+                                   bool keyRepeatEnabled) override;
 
     bool isPointerInWindow(const sp<IBinder>& token, ui::LogicalDisplayId displayId,
                            DeviceId deviceId, int32_t pointerId) override;
@@ -326,6 +329,7 @@
     std::chrono::nanoseconds mMonitorDispatchingTimeout GUARDED_BY(mLock);
 
     nsecs_t processAnrsLocked() REQUIRES(mLock);
+    nsecs_t processLatencyStatisticsLocked() REQUIRES(mLock);
     std::chrono::nanoseconds getDispatchingTimeoutLocked(
             const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
 
@@ -697,7 +701,8 @@
                                          DeviceId deviceId) const REQUIRES(mLock);
 
     // Statistics gathering.
-    LatencyAggregator mLatencyAggregator GUARDED_BY(mLock);
+    nsecs_t mLastStatisticPushTime = 0;
+    std::unique_ptr<InputEventTimelineProcessor> mInputEventTimelineProcessor GUARDED_BY(mLock);
     LatencyTracker mLatencyTracker GUARDED_BY(mLock);
     void traceInboundQueueLengthLocked() REQUIRES(mLock);
     void traceOutboundQueueLength(const Connection& connection);
@@ -738,6 +743,10 @@
 
     sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
             const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
+
+    /** Stores the value of the input flag for per device input latency metrics. */
+    const bool mPerDeviceInputLatencyMetricsFlag =
+            com::android::input::flags::enable_per_device_input_latency_metrics();
 };
 
 } // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.cpp b/services/inputflinger/dispatcher/InputEventTimeline.cpp
index 31ceb8d..6881964 100644
--- a/services/inputflinger/dispatcher/InputEventTimeline.cpp
+++ b/services/inputflinger/dispatcher/InputEventTimeline.cpp
@@ -66,12 +66,11 @@
     return !operator==(rhs);
 }
 
-InputEventTimeline::InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime,
-                                       uint16_t vendorId, uint16_t productId,
+InputEventTimeline::InputEventTimeline(nsecs_t eventTime, nsecs_t readTime, uint16_t vendorId,
+                                       uint16_t productId,
                                        const std::set<InputDeviceUsageSource>& sources,
                                        InputEventActionType inputEventActionType)
-      : isDown(isDown),
-        eventTime(eventTime),
+      : eventTime(eventTime),
         readTime(readTime),
         vendorId(vendorId),
         productId(productId),
@@ -91,8 +90,8 @@
             return false;
         }
     }
-    return isDown == rhs.isDown && eventTime == rhs.eventTime && readTime == rhs.readTime &&
-            vendorId == rhs.vendorId && productId == rhs.productId && sources == rhs.sources &&
+    return eventTime == rhs.eventTime && readTime == rhs.readTime && vendorId == rhs.vendorId &&
+            productId == rhs.productId && sources == rhs.sources &&
             inputEventActionType == rhs.inputEventActionType;
 }
 
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.h b/services/inputflinger/dispatcher/InputEventTimeline.h
index 6668399..4552708 100644
--- a/services/inputflinger/dispatcher/InputEventTimeline.h
+++ b/services/inputflinger/dispatcher/InputEventTimeline.h
@@ -97,10 +97,9 @@
 };
 
 struct InputEventTimeline {
-    InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime, uint16_t vendorId,
-                       uint16_t productId, const std::set<InputDeviceUsageSource>& sources,
+    InputEventTimeline(nsecs_t eventTime, nsecs_t readTime, uint16_t vendorId, uint16_t productId,
+                       const std::set<InputDeviceUsageSource>& sources,
                        InputEventActionType inputEventActionType);
-    const bool isDown; // True if this is an ACTION_DOWN event
     const nsecs_t eventTime;
     const nsecs_t readTime;
     const uint16_t vendorId;
@@ -122,13 +121,21 @@
 class InputEventTimelineProcessor {
 protected:
     InputEventTimelineProcessor() {}
-    virtual ~InputEventTimelineProcessor() {}
 
 public:
+    virtual ~InputEventTimelineProcessor() {}
+
     /**
      * Process the provided timeline
      */
     virtual void processTimeline(const InputEventTimeline& timeline) = 0;
+
+    /**
+     * Trigger a push for the input event latency statistics
+     */
+    virtual void pushLatencyStatistics() = 0;
+
+    virtual std::string dump(const char* prefix) const = 0;
 };
 
 } // namespace inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index 4df23c5..9b5a79b 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -638,6 +638,8 @@
             return memento.source & AINPUT_SOURCE_CLASS_POINTER;
         case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS:
             return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
+        case CancelationOptions::Mode::CANCEL_HOVER_EVENTS:
+            return memento.hovering;
         default:
             return false;
     }
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.cpp b/services/inputflinger/dispatcher/LatencyAggregator.cpp
index e09d97a..d0e9d7c 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.cpp
+++ b/services/inputflinger/dispatcher/LatencyAggregator.cpp
@@ -28,6 +28,8 @@
 using dist_proc::aggregation::KllQuantile;
 using std::chrono_literals::operator""ms;
 
+namespace {
+
 // Convert the provided nanoseconds into hundreds of microseconds.
 // Use hundreds of microseconds (as opposed to microseconds) to preserve space.
 static inline int64_t ns2hus(nsecs_t nanos) {
@@ -74,6 +76,8 @@
     return std::chrono::milliseconds(std::stoi(millis));
 }
 
+} // namespace
+
 namespace android::inputdispatcher {
 
 /**
@@ -125,6 +129,9 @@
     processSlowEvent(timeline);
 }
 
+// This version of LatencyAggregator doesn't push any atoms
+void LatencyAggregator::pushLatencyStatistics() {}
+
 void LatencyAggregator::processStatistics(const InputEventTimeline& timeline) {
     std::scoped_lock lock(mLock);
     // Before we do any processing, check that we have not yet exceeded MAX_SIZE
@@ -134,7 +141,9 @@
     mNumSketchEventsProcessed++;
 
     std::array<std::unique_ptr<KllQuantile>, SketchIndex::SIZE>& sketches =
-            timeline.isDown ? mDownSketches : mMoveSketches;
+            timeline.inputEventActionType == InputEventActionType::MOTION_ACTION_DOWN
+            ? mDownSketches
+            : mMoveSketches;
 
     // Process common ones first
     const nsecs_t eventToRead = timeline.readTime - timeline.eventTime;
@@ -242,7 +251,9 @@
         const nsecs_t consumeToGpuComplete = gpuCompletedTime - connectionTimeline.consumeTime;
         const nsecs_t gpuCompleteToPresent = presentTime - gpuCompletedTime;
 
-        android::util::stats_write(android::util::SLOW_INPUT_EVENT_REPORTED, timeline.isDown,
+        android::util::stats_write(android::util::SLOW_INPUT_EVENT_REPORTED,
+                                   timeline.inputEventActionType ==
+                                           InputEventActionType::MOTION_ACTION_DOWN,
                                    static_cast<int32_t>(ns2us(eventToRead)),
                                    static_cast<int32_t>(ns2us(readToDeliver)),
                                    static_cast<int32_t>(ns2us(deliverToConsume)),
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.h b/services/inputflinger/dispatcher/LatencyAggregator.h
index d6d1686..468add1 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.h
+++ b/services/inputflinger/dispatcher/LatencyAggregator.h
@@ -57,6 +57,8 @@
      */
     void processTimeline(const InputEventTimeline& timeline) override;
 
+    void pushLatencyStatistics() override;
+
     std::string dump(const char* prefix) const;
 
     ~LatencyAggregator();
diff --git a/services/inputflinger/dispatcher/LatencyAggregatorWithHistograms.cpp b/services/inputflinger/dispatcher/LatencyAggregatorWithHistograms.cpp
new file mode 100644
index 0000000..881a96b
--- /dev/null
+++ b/services/inputflinger/dispatcher/LatencyAggregatorWithHistograms.cpp
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2024 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 "LatencyAggregatorWithHistograms"
+#include "../InputDeviceMetricsSource.h"
+#include "InputDispatcher.h"
+
+#include <inttypes.h>
+#include <log/log_event_list.h>
+#include <statslog.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <input/Input.h>
+#include <log/log.h>
+#include <server_configurable_flags/get_flags.h>
+
+using android::base::StringPrintf;
+using std::chrono_literals::operator""ms;
+
+namespace {
+
+// Convert the provided nanoseconds into hundreds of microseconds.
+// Use hundreds of microseconds (as opposed to microseconds) to preserve space.
+static inline int64_t ns2hus(nsecs_t nanos) {
+    return ns2us(nanos) / 100;
+}
+
+// Category (=namespace) name for the input settings that are applied at boot time
+static const char* INPUT_NATIVE_BOOT = "input_native_boot";
+// Feature flag name for the threshold of end-to-end touch latency that would trigger
+// SlowEventReported atom to be pushed
+static const char* SLOW_EVENT_MIN_REPORTING_LATENCY_MILLIS =
+        "slow_event_min_reporting_latency_millis";
+// Feature flag name for the minimum delay before reporting a slow event after having just reported
+// a slow event. This helps limit the amount of data sent to the server
+static const char* SLOW_EVENT_MIN_REPORTING_INTERVAL_MILLIS =
+        "slow_event_min_reporting_interval_millis";
+
+// If an event has end-to-end latency > 200 ms, it will get reported as a slow event.
+std::chrono::milliseconds DEFAULT_SLOW_EVENT_MIN_REPORTING_LATENCY = 200ms;
+// If we receive two slow events less than 1 min apart, we will only report 1 of them.
+std::chrono::milliseconds DEFAULT_SLOW_EVENT_MIN_REPORTING_INTERVAL = 60000ms;
+
+static std::chrono::milliseconds getSlowEventMinReportingLatency() {
+    std::string millis = server_configurable_flags::
+            GetServerConfigurableFlag(INPUT_NATIVE_BOOT, SLOW_EVENT_MIN_REPORTING_LATENCY_MILLIS,
+                                      std::to_string(
+                                              DEFAULT_SLOW_EVENT_MIN_REPORTING_LATENCY.count()));
+    return std::chrono::milliseconds(std::stoi(millis));
+}
+
+static std::chrono::milliseconds getSlowEventMinReportingInterval() {
+    std::string millis = server_configurable_flags::
+            GetServerConfigurableFlag(INPUT_NATIVE_BOOT, SLOW_EVENT_MIN_REPORTING_INTERVAL_MILLIS,
+                                      std::to_string(
+                                              DEFAULT_SLOW_EVENT_MIN_REPORTING_INTERVAL.count()));
+    return std::chrono::milliseconds(std::stoi(millis));
+}
+
+} // namespace
+
+namespace android::inputdispatcher {
+
+int32_t LatencyStageIndexToAtomEnum(LatencyStageIndex latencyStageIndex) {
+    switch (latencyStageIndex) {
+        case LatencyStageIndex::EVENT_TO_READ:
+            return util::INPUT_EVENT_LATENCY_REPORTED__LATENCY_STAGE__EVENT_TO_READ;
+        case LatencyStageIndex::READ_TO_DELIVER:
+            return util::INPUT_EVENT_LATENCY_REPORTED__LATENCY_STAGE__READ_TO_DELIVER;
+        case LatencyStageIndex::DELIVER_TO_CONSUME:
+            return util::INPUT_EVENT_LATENCY_REPORTED__LATENCY_STAGE__DELIVER_TO_CONSUME;
+        case LatencyStageIndex::CONSUME_TO_FINISH:
+            return util::INPUT_EVENT_LATENCY_REPORTED__LATENCY_STAGE__CONSUME_TO_FINISH;
+        case LatencyStageIndex::CONSUME_TO_GPU_COMPLETE:
+            return util::INPUT_EVENT_LATENCY_REPORTED__LATENCY_STAGE__CONSUME_TO_GPU_COMPLETE;
+        case LatencyStageIndex::GPU_COMPLETE_TO_PRESENT:
+            return util::INPUT_EVENT_LATENCY_REPORTED__LATENCY_STAGE__GPU_COMPLETE_TO_PRESENT;
+        case LatencyStageIndex::END_TO_END:
+            return util::INPUT_EVENT_LATENCY_REPORTED__LATENCY_STAGE__END_TO_END;
+        default:
+            return util::INPUT_EVENT_LATENCY_REPORTED__LATENCY_STAGE__UNKNOWN_LATENCY_STAGE;
+    }
+}
+
+int32_t InputEventTypeEnumToAtomEnum(InputEventActionType inputEventActionType) {
+    switch (inputEventActionType) {
+        case InputEventActionType::UNKNOWN_INPUT_EVENT:
+            return util::INPUT_EVENT_LATENCY_REPORTED__INPUT_EVENT_TYPE__UNKNOWN_INPUT_EVENT;
+        case InputEventActionType::MOTION_ACTION_DOWN:
+            return util::INPUT_EVENT_LATENCY_REPORTED__INPUT_EVENT_TYPE__MOTION_ACTION_DOWN;
+        case InputEventActionType::MOTION_ACTION_MOVE:
+            return util::INPUT_EVENT_LATENCY_REPORTED__INPUT_EVENT_TYPE__MOTION_ACTION_MOVE;
+        case InputEventActionType::MOTION_ACTION_UP:
+            return util::INPUT_EVENT_LATENCY_REPORTED__INPUT_EVENT_TYPE__MOTION_ACTION_UP;
+        case InputEventActionType::MOTION_ACTION_HOVER_MOVE:
+            return util::INPUT_EVENT_LATENCY_REPORTED__INPUT_EVENT_TYPE__MOTION_ACTION_HOVER_MOVE;
+        case InputEventActionType::MOTION_ACTION_SCROLL:
+            return util::INPUT_EVENT_LATENCY_REPORTED__INPUT_EVENT_TYPE__MOTION_ACTION_SCROLL;
+        case InputEventActionType::KEY:
+            return util::INPUT_EVENT_LATENCY_REPORTED__INPUT_EVENT_TYPE__KEY;
+    }
+}
+
+void LatencyAggregatorWithHistograms::processTimeline(const InputEventTimeline& timeline) {
+    processStatistics(timeline);
+    processSlowEvent(timeline);
+}
+
+void LatencyAggregatorWithHistograms::addSampleToHistogram(
+        const InputEventLatencyIdentifier& identifier, LatencyStageIndex latencyStageIndex,
+        nsecs_t latency) {
+    // Only record positive values for the statistics
+    if (latency > 0) {
+        auto it = mHistograms.find(identifier);
+        if (it != mHistograms.end()) {
+            it->second[static_cast<size_t>(latencyStageIndex)].addSample(ns2hus(latency));
+        }
+    }
+}
+
+void LatencyAggregatorWithHistograms::processStatistics(const InputEventTimeline& timeline) {
+    // Only gather data for Down, Move and Up motion events and Key events
+    if (!(timeline.inputEventActionType == InputEventActionType::MOTION_ACTION_DOWN ||
+          timeline.inputEventActionType == InputEventActionType::MOTION_ACTION_MOVE ||
+          timeline.inputEventActionType == InputEventActionType::MOTION_ACTION_UP ||
+          timeline.inputEventActionType == InputEventActionType::KEY))
+        return;
+
+    // Don't collect data for unidentified devices. This situation can occur for the first few input
+    // events produced when an input device is first connected
+    if (timeline.vendorId == 0xFFFF && timeline.productId == 0xFFFF) return;
+
+    InputEventLatencyIdentifier identifier = {timeline.vendorId, timeline.productId,
+                                              timeline.sources, timeline.inputEventActionType};
+    // Check if there's a value in mHistograms map associated to identifier.
+    // If not, add an array with 7 empty histograms as an entry
+    if (mHistograms.count(identifier) == 0) {
+        if (static_cast<int32_t>(timeline.inputEventActionType) - 1 < 0) {
+            LOG(FATAL) << "Action index is smaller than 0. Action type: "
+                       << ftl::enum_string(timeline.inputEventActionType);
+            return;
+        }
+        size_t actionIndex =
+                static_cast<size_t>(static_cast<int32_t>(timeline.inputEventActionType) - 1);
+        if (actionIndex >= NUM_INPUT_EVENT_TYPES) {
+            LOG(FATAL) << "Action index greater than the number of input event types. Action Type: "
+                       << ftl::enum_string(timeline.inputEventActionType)
+                       << "; Action Type Index: " << actionIndex;
+            return;
+        }
+
+        std::array<Histogram, 7> histograms =
+                {Histogram(allBinSizes[binSizesMappings[0][actionIndex]]),
+                 Histogram(allBinSizes[binSizesMappings[1][actionIndex]]),
+                 Histogram(allBinSizes[binSizesMappings[2][actionIndex]]),
+                 Histogram(allBinSizes[binSizesMappings[3][actionIndex]]),
+                 Histogram(allBinSizes[binSizesMappings[4][actionIndex]]),
+                 Histogram(allBinSizes[binSizesMappings[5][actionIndex]]),
+                 Histogram(allBinSizes[binSizesMappings[6][actionIndex]])};
+        mHistograms.insert({identifier, histograms});
+    }
+
+    // Process common ones first
+    const nsecs_t eventToRead = timeline.readTime - timeline.eventTime;
+    addSampleToHistogram(identifier, LatencyStageIndex::EVENT_TO_READ, eventToRead);
+
+    // Now process per-connection ones
+    for (const auto& [connectionToken, connectionTimeline] : timeline.connectionTimelines) {
+        if (!connectionTimeline.isComplete()) {
+            continue;
+        }
+        const nsecs_t readToDeliver = connectionTimeline.deliveryTime - timeline.readTime;
+        const nsecs_t deliverToConsume =
+                connectionTimeline.consumeTime - connectionTimeline.deliveryTime;
+        const nsecs_t consumeToFinish =
+                connectionTimeline.finishTime - connectionTimeline.consumeTime;
+        const nsecs_t gpuCompletedTime =
+                connectionTimeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+        const nsecs_t presentTime =
+                connectionTimeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+        const nsecs_t consumeToGpuComplete = gpuCompletedTime - connectionTimeline.consumeTime;
+        const nsecs_t gpuCompleteToPresent = presentTime - gpuCompletedTime;
+        const nsecs_t endToEnd = presentTime - timeline.eventTime;
+
+        addSampleToHistogram(identifier, LatencyStageIndex::READ_TO_DELIVER, readToDeliver);
+        addSampleToHistogram(identifier, LatencyStageIndex::DELIVER_TO_CONSUME, deliverToConsume);
+        addSampleToHistogram(identifier, LatencyStageIndex::CONSUME_TO_FINISH, consumeToFinish);
+        addSampleToHistogram(identifier, LatencyStageIndex::CONSUME_TO_GPU_COMPLETE,
+                             consumeToGpuComplete);
+        addSampleToHistogram(identifier, LatencyStageIndex::GPU_COMPLETE_TO_PRESENT,
+                             gpuCompleteToPresent);
+        addSampleToHistogram(identifier, LatencyStageIndex::END_TO_END, endToEnd);
+    }
+}
+
+void LatencyAggregatorWithHistograms::pushLatencyStatistics() {
+    for (auto& [id, histograms] : mHistograms) {
+        auto [vendorId, productId, sources, action] = id;
+        for (size_t latencyStageIndex = static_cast<size_t>(LatencyStageIndex::EVENT_TO_READ);
+             latencyStageIndex < static_cast<size_t>(LatencyStageIndex::SIZE);
+             ++latencyStageIndex) {
+            // Convert sources set to vector for atom logging:
+            std::vector<int32_t> sourcesVector = {};
+            for (auto& elem : sources) {
+                sourcesVector.push_back(static_cast<int32_t>(elem));
+            }
+
+            // convert histogram bin counts array to vector for atom logging:
+            std::array arr = histograms[latencyStageIndex].getBinCounts();
+            std::vector<int32_t> binCountsVector(arr.begin(), arr.end());
+
+            if (static_cast<int32_t>(action) - 1 < 0) {
+                ALOGW("Action index is smaller than 0. Action type: %s",
+                      ftl::enum_string(action).c_str());
+                continue;
+            }
+            size_t actionIndex = static_cast<size_t>(static_cast<int32_t>(action) - 1);
+            if (actionIndex >= NUM_INPUT_EVENT_TYPES) {
+                ALOGW("Action index greater than the number of input event types. Action Type: %s; "
+                      "Action Type Index: %zu",
+                      ftl::enum_string(action).c_str(), actionIndex);
+                continue;
+            }
+
+            stats_write(android::util::INPUT_EVENT_LATENCY_REPORTED, vendorId, productId,
+                        sourcesVector, InputEventTypeEnumToAtomEnum(action),
+                        LatencyStageIndexToAtomEnum(
+                                static_cast<LatencyStageIndex>(latencyStageIndex)),
+                        histogramVersions[latencyStageIndex][actionIndex], binCountsVector);
+        }
+    }
+    mHistograms.clear();
+}
+
+// TODO (b/270049345): For now, we just copied the code from LatencyAggregator to populate the old
+// atom, but eventually we should migrate this to use the new SlowEventReported atom
+void LatencyAggregatorWithHistograms::processSlowEvent(const InputEventTimeline& timeline) {
+    static const std::chrono::duration sSlowEventThreshold = getSlowEventMinReportingLatency();
+    static const std::chrono::duration sSlowEventReportingInterval =
+            getSlowEventMinReportingInterval();
+    for (const auto& [token, connectionTimeline] : timeline.connectionTimelines) {
+        if (!connectionTimeline.isComplete()) {
+            continue;
+        }
+        mNumEventsSinceLastSlowEventReport++;
+        const nsecs_t presentTime =
+                connectionTimeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+        const std::chrono::nanoseconds endToEndLatency =
+                std::chrono::nanoseconds(presentTime - timeline.eventTime);
+        if (endToEndLatency < sSlowEventThreshold) {
+            continue;
+        }
+        // This is a slow event. Before we report it, check if we are reporting too often
+        const std::chrono::duration elapsedSinceLastReport =
+                std::chrono::nanoseconds(timeline.eventTime - mLastSlowEventTime);
+        if (elapsedSinceLastReport < sSlowEventReportingInterval) {
+            mNumSkippedSlowEvents++;
+            continue;
+        }
+
+        const nsecs_t eventToRead = timeline.readTime - timeline.eventTime;
+        const nsecs_t readToDeliver = connectionTimeline.deliveryTime - timeline.readTime;
+        const nsecs_t deliverToConsume =
+                connectionTimeline.consumeTime - connectionTimeline.deliveryTime;
+        const nsecs_t consumeToFinish =
+                connectionTimeline.finishTime - connectionTimeline.consumeTime;
+        const nsecs_t gpuCompletedTime =
+                connectionTimeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+        const nsecs_t consumeToGpuComplete = gpuCompletedTime - connectionTimeline.consumeTime;
+        const nsecs_t gpuCompleteToPresent = presentTime - gpuCompletedTime;
+
+        android::util::stats_write(android::util::SLOW_INPUT_EVENT_REPORTED,
+                                   timeline.inputEventActionType ==
+                                           InputEventActionType::MOTION_ACTION_DOWN,
+                                   static_cast<int32_t>(ns2us(eventToRead)),
+                                   static_cast<int32_t>(ns2us(readToDeliver)),
+                                   static_cast<int32_t>(ns2us(deliverToConsume)),
+                                   static_cast<int32_t>(ns2us(consumeToFinish)),
+                                   static_cast<int32_t>(ns2us(consumeToGpuComplete)),
+                                   static_cast<int32_t>(ns2us(gpuCompleteToPresent)),
+                                   static_cast<int32_t>(ns2us(endToEndLatency.count())),
+                                   static_cast<int32_t>(mNumEventsSinceLastSlowEventReport),
+                                   static_cast<int32_t>(mNumSkippedSlowEvents));
+        mNumEventsSinceLastSlowEventReport = 0;
+        mNumSkippedSlowEvents = 0;
+        mLastSlowEventTime = timeline.readTime;
+    }
+}
+
+std::string LatencyAggregatorWithHistograms::dump(const char* prefix) const {
+    std::string statisticsStr = StringPrintf("%s Histograms:\n", prefix);
+    for (const auto& [id, histograms] : mHistograms) {
+        auto [vendorId, productId, sources, action] = id;
+
+        std::string identifierStr =
+                StringPrintf("%s  Identifier: vendor %d, product %d, sources: {", prefix, vendorId,
+                             productId);
+        bool firstSource = true;
+        for (const auto& source : sources) {
+            if (!firstSource) {
+                identifierStr += ", ";
+            }
+            identifierStr += StringPrintf("%d", static_cast<int32_t>(source));
+            firstSource = false;
+        }
+        identifierStr += StringPrintf("}, action: %d\n", static_cast<int32_t>(action));
+
+        std::string histogramsStr;
+        for (size_t stageIndex = 0; stageIndex < static_cast<size_t>(LatencyStageIndex::SIZE);
+             stageIndex++) {
+            const auto& histogram = histograms[stageIndex];
+            const std::array<int, NUM_BINS>& binCounts = histogram.getBinCounts();
+
+            histogramsStr += StringPrintf("%s   %zu: ", prefix, stageIndex);
+            histogramsStr += StringPrintf("%d", binCounts[0]);
+            for (size_t bin = 1; bin < NUM_BINS; bin++) {
+                histogramsStr += StringPrintf(", %d", binCounts[bin]);
+            }
+            histogramsStr += StringPrintf("\n");
+        }
+        statisticsStr += identifierStr + histogramsStr;
+    }
+
+    return StringPrintf("%sLatencyAggregatorWithHistograms:\n", prefix) + statisticsStr +
+            StringPrintf("%s  mLastSlowEventTime=%" PRId64 "\n", prefix, mLastSlowEventTime) +
+            StringPrintf("%s  mNumEventsSinceLastSlowEventReport = %zu\n", prefix,
+                         mNumEventsSinceLastSlowEventReport) +
+            StringPrintf("%s  mNumSkippedSlowEvents = %zu\n", prefix, mNumSkippedSlowEvents);
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyAggregatorWithHistograms.h b/services/inputflinger/dispatcher/LatencyAggregatorWithHistograms.h
new file mode 100644
index 0000000..2ceb0e7
--- /dev/null
+++ b/services/inputflinger/dispatcher/LatencyAggregatorWithHistograms.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2024 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 <android-base/thread_annotations.h>
+#include <utils/Timers.h>
+
+#include "InputEventTimeline.h"
+
+namespace android::inputdispatcher {
+
+static constexpr size_t NUM_BINS = 20;
+static constexpr size_t NUM_INPUT_EVENT_TYPES = 6;
+
+enum class LatencyStageIndex : size_t {
+    EVENT_TO_READ = 0,
+    READ_TO_DELIVER = 1,
+    DELIVER_TO_CONSUME = 2,
+    CONSUME_TO_FINISH = 3,
+    CONSUME_TO_GPU_COMPLETE = 4,
+    GPU_COMPLETE_TO_PRESENT = 5,
+    END_TO_END = 6,
+    SIZE = 7, // must be last
+};
+
+// Let's create a full timeline here:
+// eventTime
+// readTime
+// <---- after this point, the data becomes per-connection
+// deliveryTime // time at which the event was sent to the receiver
+// consumeTime  // time at which the receiver read the event
+// finishTime   // time at which the dispatcher reads the response from the receiver that the event
+// was processed
+// GraphicsTimeline::GPU_COMPLETED_TIME
+// GraphicsTimeline::PRESENT_TIME
+
+/**
+ * Keep histograms with latencies of the provided events
+ */
+class LatencyAggregatorWithHistograms final : public InputEventTimelineProcessor {
+public:
+    /**
+     * Record a complete event timeline
+     */
+    void processTimeline(const InputEventTimeline& timeline) override;
+
+    void pushLatencyStatistics() override;
+
+    std::string dump(const char* prefix) const override;
+
+private:
+    // ---------- Slow event handling ----------
+    void processSlowEvent(const InputEventTimeline& timeline);
+    nsecs_t mLastSlowEventTime = 0;
+    // How many slow events have been skipped due to rate limiting
+    size_t mNumSkippedSlowEvents = 0;
+    // How many events have been received since the last time we reported a slow event
+    size_t mNumEventsSinceLastSlowEventReport = 0;
+
+    // ---------- Statistics handling ----------
+    /**
+     * Data structure to gather time samples into NUM_BINS buckets
+     */
+    class Histogram {
+    public:
+        Histogram(const std::array<int, NUM_BINS - 1>& binSizes) : mBinSizes(binSizes) {
+            mBinCounts.fill(0);
+        }
+
+        // Increments binCounts of the appropriate bin when adding a new sample
+        void addSample(int64_t sample) {
+            size_t binIndex = getSampleBinIndex(sample);
+            mBinCounts[binIndex]++;
+        }
+
+        const std::array<int32_t, NUM_BINS>& getBinCounts() const { return mBinCounts; }
+
+    private:
+        // reference to an array that represents the range of values each bin holds.
+        // in bin i+1 live samples such that *mBinSizes[i] <= sample < *mBinSizes[i+1]
+        const std::array<int, NUM_BINS - 1>& mBinSizes;
+        std::array<int32_t, NUM_BINS>
+                mBinCounts; // the number of samples that currently live in each bin
+
+        size_t getSampleBinIndex(int64_t sample) {
+            auto it = std::upper_bound(mBinSizes.begin(), mBinSizes.end(), sample);
+            return std::distance(mBinSizes.begin(), it);
+        }
+    };
+
+    void processStatistics(const InputEventTimeline& timeline);
+
+    // Identifier for the an input event. If two input events have the same identifiers we
+    // want to use the same histograms to count the latency samples
+    using InputEventLatencyIdentifier =
+            std::tuple<uint16_t /*vendorId*/, uint16_t /*productId*/,
+                       const std::set<InputDeviceUsageSource> /*sources*/,
+                       InputEventActionType /*inputEventActionType*/>;
+
+    // Maps an input event identifier to an array of 7 histograms, one for each latency
+    // stage. It is cleared after an atom push
+    std::map<InputEventLatencyIdentifier, std::array<Histogram, 7>> mHistograms;
+
+    void addSampleToHistogram(const InputEventLatencyIdentifier& identifier,
+                              LatencyStageIndex latencyStageIndex, nsecs_t time);
+
+    // Stores all possible arrays of bin sizes. The order in the vector does not matter, as long
+    // as binSizesMappings points to the right index
+    static constexpr std::array<std::array<int, NUM_BINS - 1>, 6> allBinSizes = {
+            {{10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100},
+             {1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32},
+             {15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, 210, 225, 240, 255, 270,
+              285},
+             {40, 80, 120, 160, 200, 240, 280, 320, 360, 400, 440, 480, 520, 560, 600, 640, 680,
+              720, 760},
+             {20, 40, 60, 80, 100, 120, 140, 160, 180, 200, 220, 240, 260, 280, 300, 320, 340, 360,
+              380},
+             {200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600,
+              1700, 1800, 1900, 2000}}};
+
+    // Stores indexes in allBinSizes to use with each {LatencyStage, InputEventType} pair.
+    // Bin sizes for a certain latencyStage and inputEventType are at:
+    // *(allBinSizes[binSizesMappings[latencyStageIndex][inputEventTypeIndex]])
+    // inputEventTypeIndex is the int value of InputEventActionType enum decreased by 1 since we
+    // don't want to record latencies for unknown events.
+    // e.g. MOTION_ACTION_DOWN is 0, MOTION_ACTION_MOVE is 1...
+    static constexpr std::array<std::array<int8_t, NUM_INPUT_EVENT_TYPES>,
+                                static_cast<size_t>(LatencyStageIndex::SIZE)>
+            binSizesMappings = {{{0, 0, 0, 0, 0, 0},
+                                 {1, 1, 1, 1, 1, 1},
+                                 {1, 1, 1, 1, 1, 1},
+                                 {2, 2, 2, 2, 2, 2},
+                                 {3, 3, 3, 3, 3, 3},
+                                 {4, 4, 4, 4, 4, 4},
+                                 {5, 5, 5, 5, 5, 5}}};
+
+    // Similar to binSizesMappings, but holds the index of the array of bin ranges to use on the
+    // server. The index gets pushed with the atom within the histogram_version field.
+    static constexpr std::array<std::array<int8_t, NUM_INPUT_EVENT_TYPES>,
+                                static_cast<size_t>(LatencyStageIndex::SIZE)>
+            histogramVersions = {{{0, 0, 0, 0, 0, 0},
+                                  {1, 1, 1, 1, 1, 1},
+                                  {1, 1, 1, 1, 1, 1},
+                                  {2, 2, 2, 2, 2, 2},
+                                  {3, 3, 3, 3, 3, 3},
+                                  {4, 4, 4, 4, 4, 4},
+                                  {5, 5, 5, 5, 5, 5}}};
+};
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp
index 721d009..0852026 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.cpp
+++ b/services/inputflinger/dispatcher/LatencyTracker.cpp
@@ -62,15 +62,33 @@
     }
 }
 
-LatencyTracker::LatencyTracker(InputEventTimelineProcessor* processor)
-      : mTimelineProcessor(processor) {
-    LOG_ALWAYS_FATAL_IF(processor == nullptr);
+LatencyTracker::LatencyTracker(InputEventTimelineProcessor& processor)
+      : mTimelineProcessor(&processor) {}
+
+void LatencyTracker::trackNotifyMotion(const NotifyMotionArgs& args) {
+    std::set<InputDeviceUsageSource> sources = getUsageSourcesForMotionArgs(args);
+    trackListener(args.id, args.eventTime, args.readTime, args.deviceId, sources, args.action,
+                  InputEventType::MOTION);
+}
+
+void LatencyTracker::trackNotifyKey(const NotifyKeyArgs& args) {
+    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+    for (auto& inputDevice : mInputDevices) {
+        if (args.deviceId == inputDevice.getId()) {
+            keyboardType = inputDevice.getKeyboardType();
+            break;
+        }
+    }
+    std::set<InputDeviceUsageSource> sources =
+            std::set{getUsageSourceForKeyArgs(keyboardType, args)};
+    trackListener(args.id, args.eventTime, args.readTime, args.deviceId, sources, args.action,
+                  InputEventType::KEY);
 }
 
 void LatencyTracker::trackListener(int32_t inputEventId, nsecs_t eventTime, nsecs_t readTime,
                                    DeviceId deviceId,
                                    const std::set<InputDeviceUsageSource>& sources,
-                                   int inputEventAction, InputEventType inputEventType) {
+                                   int32_t inputEventAction, InputEventType inputEventType) {
     reportAndPruneMatureRecords(eventTime);
     const auto it = mTimelines.find(inputEventId);
     if (it != mTimelines.end()) {
@@ -105,7 +123,7 @@
     const InputEventActionType inputEventActionType = [&]() {
         switch (inputEventType) {
             case InputEventType::MOTION: {
-                switch (inputEventAction) {
+                switch (MotionEvent::getActionMasked(inputEventAction)) {
                     case AMOTION_EVENT_ACTION_DOWN:
                         return InputEventActionType::MOTION_ACTION_DOWN;
                     case AMOTION_EVENT_ACTION_MOVE:
@@ -134,10 +152,8 @@
         }
     }();
 
-    bool isDown = inputEventType == InputEventType::MOTION &&
-            inputEventAction == AMOTION_EVENT_ACTION_DOWN;
     mTimelines.emplace(inputEventId,
-                       InputEventTimeline(isDown, eventTime, readTime, identifier->vendor,
+                       InputEventTimeline(eventTime, readTime, identifier->vendor,
                                           identifier->product, sources, inputEventActionType));
     mEventTimes.emplace(eventTime, inputEventId);
 }
diff --git a/services/inputflinger/dispatcher/LatencyTracker.h b/services/inputflinger/dispatcher/LatencyTracker.h
index 532f422..eb58222 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.h
+++ b/services/inputflinger/dispatcher/LatencyTracker.h
@@ -42,7 +42,7 @@
      * Create a LatencyTracker.
      * param reportingFunction: the function that will be called in order to report full latency.
      */
-    LatencyTracker(InputEventTimelineProcessor* processor);
+    LatencyTracker(InputEventTimelineProcessor& processor);
     /**
      * Start keeping track of an event identified by inputEventId. This must be called first.
      * If duplicate events are encountered (events that have the same eventId), none of them will be
@@ -53,12 +53,19 @@
      * must drop all duplicate data.
      */
     void trackListener(int32_t inputEventId, nsecs_t eventTime, nsecs_t readTime, DeviceId deviceId,
-                       const std::set<InputDeviceUsageSource>& sources, int inputEventActionType,
+                       const std::set<InputDeviceUsageSource>& sources, int32_t inputEventAction,
                        InputEventType inputEventType);
     void trackFinishedEvent(int32_t inputEventId, const sp<IBinder>& connectionToken,
                             nsecs_t deliveryTime, nsecs_t consumeTime, nsecs_t finishTime);
     void trackGraphicsLatency(int32_t inputEventId, const sp<IBinder>& connectionToken,
                               std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
+    /**
+     * trackNotifyMotion and trackNotifyKeys are intermediates between InputDispatcher and
+     * trackListener. They compute the InputDeviceUsageSource set and call trackListener with
+     * the relevant parameters for latency computation.
+     */
+    void trackNotifyMotion(const NotifyMotionArgs& args);
+    void trackNotifyKey(const NotifyKeyArgs& args);
 
     std::string dump(const char* prefix) const;
     void setInputDevices(const std::vector<InputDeviceInfo>& inputDevices);
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 0c9ad3c..2bf63be 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -112,17 +112,18 @@
 }
 
 void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
-                                            DeviceId deviceId, const PointerProperties& pointer) {
+                                            DeviceId deviceId, const PointerProperties& pointer,
+                                            float x, float y) {
     for (TouchedWindow& touchedWindow : windows) {
         if (touchedWindow.windowHandle == windowHandle) {
-            touchedWindow.addHoveringPointer(deviceId, pointer);
+            touchedWindow.addHoveringPointer(deviceId, pointer, x, y);
             return;
         }
     }
 
     TouchedWindow touchedWindow;
     touchedWindow.windowHandle = windowHandle;
-    touchedWindow.addHoveringPointer(deviceId, pointer);
+    touchedWindow.addHoveringPointer(deviceId, pointer, x, y);
     windows.push_back(touchedWindow);
 }
 
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 5a70dd5..451d917 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -49,7 +49,8 @@
             DeviceId deviceId, const std::vector<PointerProperties>& touchingPointers,
             std::optional<nsecs_t> firstDownTimeInTarget);
     void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                    DeviceId deviceId, const PointerProperties& pointer);
+                                    DeviceId deviceId, const PointerProperties& pointer, float x,
+                                    float y);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
     void clearHoveringPointers(DeviceId deviceId);
 
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
index 1f86f66..fa5be1a 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.cpp
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -36,6 +36,13 @@
                         }) != pointers.end();
 }
 
+bool hasPointerId(const std::vector<TouchedWindow::HoveringPointer>& pointers, int32_t pointerId) {
+    return std::find_if(pointers.begin(), pointers.end(),
+                        [&pointerId](const TouchedWindow::HoveringPointer& pointer) {
+                            return pointer.properties.id == pointerId;
+                        }) != pointers.end();
+}
+
 } // namespace
 
 bool TouchedWindow::hasHoveringPointers() const {
@@ -78,16 +85,18 @@
     return hasPointerId(state.hoveringPointers, pointerId);
 }
 
-void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer) {
-    std::vector<PointerProperties>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
+void TouchedWindow::addHoveringPointer(DeviceId deviceId, const PointerProperties& properties,
+                                       float x, float y) {
+    std::vector<HoveringPointer>& hoveringPointers = mDeviceStates[deviceId].hoveringPointers;
     const size_t initialSize = hoveringPointers.size();
-    std::erase_if(hoveringPointers, [&pointer](const PointerProperties& properties) {
-        return properties.id == pointer.id;
+    std::erase_if(hoveringPointers, [&properties](const HoveringPointer& pointer) {
+        return pointer.properties.id == properties.id;
     });
     if (hoveringPointers.size() != initialSize) {
-        LOG(ERROR) << __func__ << ": " << pointer << ", device " << deviceId << " was in " << *this;
+        LOG(ERROR) << __func__ << ": " << properties << ", device " << deviceId << " was in "
+                   << *this;
     }
-    hoveringPointers.push_back(pointer);
+    hoveringPointers.push_back({properties, x, y});
 }
 
 Result<void> TouchedWindow::addTouchingPointers(DeviceId deviceId,
@@ -173,8 +182,8 @@
                 return true;
             }
         }
-        for (const PointerProperties& properties : state.hoveringPointers) {
-            if (properties.toolType == ToolType::STYLUS) {
+        for (const HoveringPointer& pointer : state.hoveringPointers) {
+            if (pointer.properties.toolType == ToolType::STYLUS) {
                 return true;
             }
         }
@@ -270,8 +279,8 @@
     }
     DeviceState& state = stateIt->second;
 
-    std::erase_if(state.hoveringPointers, [&pointerId](const PointerProperties& properties) {
-        return properties.id == pointerId;
+    std::erase_if(state.hoveringPointers, [&pointerId](const HoveringPointer& pointer) {
+        return pointer.properties.id == pointerId;
     });
 
     if (!state.hasPointers()) {
@@ -279,6 +288,22 @@
     }
 }
 
+std::vector<DeviceId> TouchedWindow::eraseHoveringPointersIf(
+        std::function<bool(const PointerProperties&, float /*x*/, float /*y*/)> condition) {
+    std::vector<DeviceId> erasedDevices;
+    for (auto& [deviceId, state] : mDeviceStates) {
+        std::erase_if(state.hoveringPointers, [&](const HoveringPointer& pointer) {
+            if (condition(pointer.properties, pointer.x, pointer.y)) {
+                erasedDevices.push_back(deviceId);
+                return true;
+            }
+            return false;
+        });
+    }
+
+    return erasedDevices;
+}
+
 void TouchedWindow::removeAllHoveringPointersForDevice(DeviceId deviceId) {
     const auto stateIt = mDeviceStates.find(deviceId);
     if (stateIt == mDeviceStates.end()) {
@@ -312,6 +337,11 @@
     return out;
 }
 
+std::ostream& operator<<(std::ostream& out, const TouchedWindow::HoveringPointer& pointer) {
+    out << pointer.properties << " at (" << pointer.x << ", " << pointer.y << ")";
+    return out;
+}
+
 std::ostream& operator<<(std::ostream& out, const TouchedWindow& window) {
     out << window.dump();
     return out;
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 4f0ad16..c38681e 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -38,7 +38,7 @@
     bool hasHoveringPointers() const;
     bool hasHoveringPointers(DeviceId deviceId) const;
     bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const;
-    void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer);
+    void addHoveringPointer(DeviceId deviceId, const PointerProperties& pointer, float x, float y);
     void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
 
     // Touching
@@ -69,6 +69,15 @@
     void clearHoveringPointers(DeviceId deviceId);
     std::string dump() const;
 
+    struct HoveringPointer {
+        PointerProperties properties;
+        float x;
+        float y;
+    };
+
+    std::vector<DeviceId> eraseHoveringPointersIf(
+            std::function<bool(const PointerProperties&, float /*x*/, float /*y*/)> condition);
+
 private:
     struct DeviceState {
         std::vector<PointerProperties> touchingPointers;
@@ -78,7 +87,7 @@
         // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE
         // scenario.
         std::optional<nsecs_t> downTimeInTarget;
-        std::vector<PointerProperties> hoveringPointers;
+        std::vector<HoveringPointer> hoveringPointers;
 
         bool hasPointers() const { return !touchingPointers.empty() || !hoveringPointers.empty(); };
     };
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h b/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h
index 5eb3a32..ba197d4 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h
@@ -34,8 +34,13 @@
     // The key repeat inter-key delay.
     nsecs_t keyRepeatDelay;
 
+    // Whether key repeat is enabled.
+    bool keyRepeatEnabled;
+
     InputDispatcherConfiguration()
-          : keyRepeatTimeout(500 * 1000000LL), keyRepeatDelay(50 * 1000000LL) {}
+          : keyRepeatTimeout(500 * 1000000LL),
+            keyRepeatDelay(50 * 1000000LL),
+            keyRepeatEnabled(true) {}
 };
 
 } // namespace android
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 653f595..463a952 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -227,10 +227,11 @@
     virtual void cancelCurrentTouch() = 0;
 
     /*
-     * Updates key repeat configuration timeout and delay.
+     * Updates whether key repeat is enabled and key repeat configuration timeout and delay.
      */
     virtual void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
-                                           std::chrono::nanoseconds delay) = 0;
+                                           std::chrono::nanoseconds delay,
+                                           bool keyRepeatEnabled) = 0;
 
     /*
      * Determine if a pointer from a device is being dispatched to the given window.
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
index 3c3c15a..4f61885 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -41,7 +41,7 @@
       : mBackend(std::move(innerBackend)),
         mTracerThread(
                 "InputTracer", [this]() { threadLoop(); },
-                [this]() { mThreadWakeCondition.notify_all(); }) {}
+                [this]() { mThreadWakeCondition.notify_all(); }, /*isInCriticalPath=*/false) {}
 
 template <typename Backend>
 ThreadedBackend<Backend>::~ThreadedBackend() {
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 7bec94e..2f6c6d7 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -93,6 +93,9 @@
         // The touchpad settings changed.
         TOUCHPAD_SETTINGS = 1u << 13,
 
+        // The key remapping has changed.
+        KEY_REMAPPING = 1u << 14,
+
         // All devices must be reopened.
         MUST_REOPEN = 1u << 31,
     };
@@ -246,6 +249,9 @@
     // True if a pointer icon should be shown for direct stylus pointers.
     bool stylusPointerIconEnabled;
 
+    // Keycodes to be remapped.
+    std::map<int32_t /* fromKeyCode */, int32_t /* toKeyCode */> keyRemapping;
+
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
             defaultPointerDisplayId(ui::LogicalDisplayId::DEFAULT),
@@ -333,9 +339,6 @@
     virtual int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) = 0;
     virtual int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) = 0;
 
-    virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode,
-                                 int32_t toKeyCode) const = 0;
-
     virtual int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const = 0;
 
     /* Toggle Caps Lock */
@@ -466,6 +469,9 @@
     virtual void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
                                              int32_t deviceId) = 0;
 
+    /* Sends the Info of gestures that happen on the touchpad. */
+    virtual void notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) = 0;
+
     /* Gets the keyboard layout for a particular input device. */
     virtual std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
diff --git a/services/inputflinger/include/InputThread.h b/services/inputflinger/include/InputThread.h
index 5e75027..ed92b8f 100644
--- a/services/inputflinger/include/InputThread.h
+++ b/services/inputflinger/include/InputThread.h
@@ -28,14 +28,13 @@
  */
 class InputThread {
 public:
-    explicit InputThread(std::string name, std::function<void()> loop,
-                         std::function<void()> wake = nullptr);
+    explicit InputThread(std::string name, std::function<void()> loop, std::function<void()> wake,
+                         bool isInCriticalPath);
     virtual ~InputThread();
 
     bool isCallingThread();
 
 private:
-    std::string mName;
     std::function<void()> mThreadWake;
     sp<Thread> mThread;
 };
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index e34ed0f..8f3d9ca 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -72,10 +72,6 @@
     /* Dumps the state of the pointer controller. */
     virtual std::string dump() = 0;
 
-    /* Gets the bounds of the region that the pointer can traverse.
-     * Returns true if the bounds are available. */
-    virtual std::optional<FloatRect> getBounds() const = 0;
-
     /* Move the pointer. */
     virtual void move(float deltaX, float deltaY) = 0;
 
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index b76e8c5..b3cd35c 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -90,10 +90,14 @@
         "libstatslog",
         "libstatspull",
         "libutils",
+        "libstatssocket",
     ],
     static_libs: [
         "libchrome-gestures",
         "libui-types",
+        "libexpresslog",
+        "libtextclassifier_hash_static",
+        "libstatslog_express",
     ],
     header_libs: [
         "libbatteryservice_headers",
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index e11adb8..0865eed 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -1177,7 +1177,8 @@
     return false;
 }
 
-void EventHub::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const {
+void EventHub::setKeyRemapping(int32_t deviceId,
+                               const std::map<int32_t, int32_t>& keyRemapping) const {
     std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device == nullptr) {
@@ -1185,7 +1186,7 @@
     }
     const std::shared_ptr<KeyCharacterMap> kcm = device->getKeyCharacterMap();
     if (kcm) {
-        kcm->addKeyRemapping(fromKeyCode, toKeyCode);
+        kcm->setKeyRemapping(keyRemapping);
     }
 }
 
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 70f024e..6185f1a 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -365,6 +365,18 @@
             // so update the enabled state when there is a change in display info.
             out += updateEnableState(when, readerConfig, forceEnable);
         }
+
+        if (!changes.any() || changes.test(InputReaderConfiguration::Change::KEY_REMAPPING)) {
+            const bool isFullKeyboard =
+                    (mSources & AINPUT_SOURCE_KEYBOARD) == AINPUT_SOURCE_KEYBOARD &&
+                    mKeyboardType == KeyboardType::ALPHABETIC;
+            if (isFullKeyboard) {
+                for_each_subdevice([&readerConfig](auto& context) {
+                    context.setKeyRemapping(readerConfig.keyRemapping);
+                });
+                bumpGeneration();
+            }
+        }
     }
     return out;
 }
@@ -689,12 +701,6 @@
     });
 }
 
-void InputDevice::addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) {
-    for_each_subdevice([fromKeyCode, toKeyCode](auto& context) {
-        context.addKeyRemapping(fromKeyCode, toKeyCode);
-    });
-}
-
 void InputDevice::bumpGeneration() {
     mGeneration = mContext->bumpGeneration();
 }
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index a5b1249..8b664d5 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -122,7 +122,8 @@
         return ALREADY_EXISTS;
     }
     mThread = std::make_unique<InputThread>(
-            "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
+            "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); },
+            /*isInCriticalPath=*/true);
     return OK;
 }
 
@@ -625,15 +626,6 @@
     return result;
 }
 
-void InputReader::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const {
-    std::scoped_lock _l(mLock);
-
-    InputDevice* device = findInputDeviceLocked(deviceId);
-    if (device != nullptr) {
-        device->addKeyRemapping(fromKeyCode, toKeyCode);
-    }
-}
-
 int32_t InputReader::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
     std::scoped_lock _l(mLock);
 
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 657126a..edc3037 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -281,8 +281,8 @@
 
     virtual bool hasMscEvent(int32_t deviceId, int mscEvent) const = 0;
 
-    virtual void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode,
-                                 int32_t toKeyCode) const = 0;
+    virtual void setKeyRemapping(int32_t deviceId,
+                                 const std::map<int32_t, int32_t>& keyRemapping) const = 0;
 
     virtual status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
                             int32_t metaState, int32_t* outKeycode, int32_t* outMetaState,
@@ -513,8 +513,8 @@
 
     bool hasMscEvent(int32_t deviceId, int mscEvent) const override final;
 
-    void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode,
-                         int32_t toKeyCode) const override final;
+    void setKeyRemapping(int32_t deviceId,
+                         const std::map<int32_t, int32_t>& keyRemapping) const override final;
 
     status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
                     int32_t* outKeycode, int32_t* outMetaState,
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 021978d..62cc4da 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -124,8 +124,6 @@
     int32_t getMetaState();
     void updateMetaState(int32_t keyCode);
 
-    void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode);
-
     void setKeyboardType(KeyboardType keyboardType);
 
     void bumpGeneration();
@@ -329,8 +327,8 @@
 
     inline bool hasMscEvent(int mscEvent) const { return mEventHub->hasMscEvent(mId, mscEvent); }
 
-    inline void addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) const {
-        mEventHub->addKeyRemapping(mId, fromKeyCode, toKeyCode);
+    inline void setKeyRemapping(const std::map<int32_t, int32_t>& keyRemapping) const {
+        mEventHub->setKeyRemapping(mId, keyRemapping);
     }
 
     inline status_t mapKey(int32_t scanCode, int32_t usageCode, int32_t metaState,
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 2cc0a00..1003871 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -65,8 +65,6 @@
     int32_t getKeyCodeState(int32_t deviceId, uint32_t sourceMask, int32_t keyCode) override;
     int32_t getSwitchState(int32_t deviceId, uint32_t sourceMask, int32_t sw) override;
 
-    void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const override;
-
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override;
 
     void toggleCapsLockState(int32_t deviceId) override;
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
index c8e7790..dd46bbc 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
@@ -20,14 +20,19 @@
 #include <sstream>
 
 #include <android-base/stringprintf.h>
+#include <com_android_input_flags.h>
 #include <input/PrintTools.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 namespace {
 
+static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD;
+
 int32_t actionWithIndex(int32_t action, int32_t index) {
     return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
 }
@@ -43,6 +48,12 @@
     return i;
 }
 
+void addRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis,
+                       RawAbsoluteAxisInfo& evdevAxis) {
+    deviceInfo.addMotionRange(androidAxis, SOURCE, evdevAxis.minValue, evdevAxis.maxValue,
+                              evdevAxis.flat, evdevAxis.fuzz, evdevAxis.resolution);
+}
+
 } // namespace
 
 CapturedTouchpadEventConverter::CapturedTouchpadEventConverter(
@@ -108,8 +119,15 @@
 }
 
 void CapturedTouchpadEventConverter::populateMotionRanges(InputDeviceInfo& info) const {
-    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X);
-    tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y);
+    if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+        tryAddRawMotionRangeWithRelative(/*byref*/ info, AMOTION_EVENT_AXIS_X,
+                                         AMOTION_EVENT_AXIS_RELATIVE_X, ABS_MT_POSITION_X);
+        tryAddRawMotionRangeWithRelative(/*byref*/ info, AMOTION_EVENT_AXIS_Y,
+                                         AMOTION_EVENT_AXIS_RELATIVE_Y, ABS_MT_POSITION_Y);
+    } else {
+        tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X);
+        tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y);
+    }
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MAJOR, ABS_MT_TOUCH_MAJOR);
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MINOR, ABS_MT_TOUCH_MINOR);
     tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR);
@@ -135,8 +153,23 @@
                                                           int32_t evdevAxis) const {
     std::optional<RawAbsoluteAxisInfo> info = mDeviceContext.getAbsoluteAxisInfo(evdevAxis);
     if (info) {
-        deviceInfo.addMotionRange(androidAxis, SOURCE, info->minValue, info->maxValue, info->flat,
-                                  info->fuzz, info->resolution);
+        addRawMotionRange(/*byref*/ deviceInfo, androidAxis, *info);
+    }
+}
+
+void CapturedTouchpadEventConverter::tryAddRawMotionRangeWithRelative(InputDeviceInfo& deviceInfo,
+                                                                      int32_t androidAxis,
+                                                                      int32_t androidRelativeAxis,
+                                                                      int32_t evdevAxis) const {
+    std::optional<RawAbsoluteAxisInfo> axisInfo = mDeviceContext.getAbsoluteAxisInfo(evdevAxis);
+    if (axisInfo) {
+        addRawMotionRange(/*byref*/ deviceInfo, androidAxis, *axisInfo);
+
+        // The largest movement we could possibly report on a relative axis is from the minimum to
+        // the maximum (or vice versa) of the absolute axis.
+        float range = axisInfo->maxValue - axisInfo->minValue;
+        deviceInfo.addMotionRange(androidRelativeAxis, SOURCE, -range, range, axisInfo->flat,
+                                  axisInfo->fuzz, axisInfo->resolution);
     }
 }
 
@@ -163,7 +196,7 @@
     std::list<NotifyArgs> out;
     std::vector<PointerCoords> coords;
     std::vector<PointerProperties> properties;
-    std::map<size_t, size_t> coordsIndexForSlotNumber;
+    std::map<size_t /*slotNumber*/, size_t /*coordsIndex*/> coordsIndexForSlotNumber;
 
     // For all the touches that were already down, send a MOVE event with their updated coordinates.
     // A convention of the MotionEvent API is that pointer coordinates in UP events match the
@@ -175,11 +208,19 @@
             // to stay perfectly still between frames, and if it does the worst that can happen is
             // an extra MOVE event, so it's not worth the overhead of checking for changes.
             coordsIndexForSlotNumber[slotNumber] = coords.size();
-            coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
+            coords.push_back(makePointerCoordsForSlot(slotNumber));
             properties.push_back({.id = pointerId, .toolType = ToolType::FINGER});
         }
         out.push_back(
                 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties));
+        if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+            // For any further events we send from this sync, the pointers won't have moved relative
+            // to the positions we just reported in this MOVE event, so zero out the relative axes.
+            for (PointerCoords& pointer : coords) {
+                pointer.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
+                pointer.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
+            }
+        }
     }
 
     std::vector<size_t> upSlots, downSlots;
@@ -234,6 +275,9 @@
                                      /*flags=*/cancel ? AMOTION_EVENT_FLAG_CANCELED : 0));
 
         freePointerIdForSlot(slotNumber);
+        if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+            mPreviousCoordsForSlotNumber.erase(slotNumber);
+        }
         coords.erase(coords.begin() + indexToRemove);
         properties.erase(properties.begin() + indexToRemove);
         // Now that we've removed some coords and properties, we might have to update the slot
@@ -254,7 +298,7 @@
                 : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex);
 
         coordsIndexForSlotNumber[slotNumber] = coordsIndex;
-        coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
+        coords.push_back(makePointerCoordsForSlot(slotNumber));
         properties.push_back(
                 {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER});
 
@@ -286,12 +330,22 @@
                             AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{});
 }
 
-PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(
-        const MultiTouchMotionAccumulator::Slot& slot) const {
+PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(size_t slotNumber) {
+    const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(slotNumber);
     PointerCoords coords;
     coords.clear();
     coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX());
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY());
+    if (input_flags::include_relative_axis_values_for_captured_touchpads()) {
+        if (auto it = mPreviousCoordsForSlotNumber.find(slotNumber);
+            it != mPreviousCoordsForSlotNumber.end()) {
+            auto [oldX, oldY] = it->second;
+            coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, slot.getX() - oldX);
+            coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, slot.getY() - oldY);
+        }
+        mPreviousCoordsForSlotNumber[slotNumber] = std::make_pair(slot.getX(), slot.getY());
+    }
+
     coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor());
     coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor());
     coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor());
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
index 9b6df7a..d6c0708 100644
--- a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
@@ -21,6 +21,7 @@
 #include <map>
 #include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <android/input.h>
@@ -49,12 +50,14 @@
 private:
     void tryAddRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis,
                               int32_t evdevAxis) const;
+    void tryAddRawMotionRangeWithRelative(InputDeviceInfo& deviceInfo, int32_t androidAxis,
+                                          int32_t androidRelativeAxis, int32_t evdevAxis) const;
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
     [[nodiscard]] NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                                   const std::vector<PointerCoords>& coords,
                                                   const std::vector<PointerProperties>& properties,
                                                   int32_t actionButton = 0, int32_t flags = 0);
-    PointerCoords makePointerCoordsForSlot(const MultiTouchMotionAccumulator::Slot& slot) const;
+    PointerCoords makePointerCoordsForSlot(size_t slotNumber);
     int32_t allocatePointerIdToSlot(size_t slotNumber);
     void freePointerIdForSlot(size_t slotNumber);
 
@@ -76,8 +79,7 @@
 
     std::bitset<MAX_POINTER_ID + 1> mPointerIdsInUse;
     std::map<size_t, int32_t> mPointerIdForSlotNumber;
-
-    static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD;
+    std::map<size_t, std::pair<float, float>> mPreviousCoordsForSlotNumber;
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
index b72cc6e..c633b49 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -20,6 +20,8 @@
 
 #include "RotaryEncoderInputMapper.h"
 
+#include <Counter.h>
+#include <com_android_input_flags.h>
 #include <utils/Timers.h>
 #include <optional>
 
@@ -27,14 +29,26 @@
 
 namespace android {
 
+using android::expresslog::Counter;
+
+constexpr float kDefaultResolution = 0;
 constexpr float kDefaultScaleFactor = 1.0f;
+constexpr int32_t kDefaultMinRotationsToLog = 3;
 
 RotaryEncoderInputMapper::RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
                                                    const InputReaderConfiguration& readerConfig)
+      : RotaryEncoderInputMapper(deviceContext, readerConfig,
+                                 Counter::logIncrement /* telemetryLogCounter */) {}
+
+RotaryEncoderInputMapper::RotaryEncoderInputMapper(
+        InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig,
+        std::function<void(const char*, int64_t)> telemetryLogCounter)
       : InputMapper(deviceContext, readerConfig),
         mSource(AINPUT_SOURCE_ROTARY_ENCODER),
         mScalingFactor(kDefaultScaleFactor),
-        mOrientation(ui::ROTATION_0) {}
+        mResolution(kDefaultResolution),
+        mOrientation(ui::ROTATION_0),
+        mTelemetryLogCounter(telemetryLogCounter) {}
 
 RotaryEncoderInputMapper::~RotaryEncoderInputMapper() {}
 
@@ -51,6 +65,7 @@
         if (!res.has_value()) {
             ALOGW("Rotary Encoder device configuration file didn't specify resolution!\n");
         }
+        mResolution = res.value_or(kDefaultResolution);
         std::optional<float> scalingFactor = config.getFloat("device.scalingFactor");
         if (!scalingFactor.has_value()) {
             ALOGW("Rotary Encoder device configuration file didn't specify scaling factor,"
@@ -59,7 +74,22 @@
         }
         mScalingFactor = scalingFactor.value_or(kDefaultScaleFactor);
         info.addMotionRange(AMOTION_EVENT_AXIS_SCROLL, mSource, -1.0f, 1.0f, 0.0f, 0.0f,
-                            res.value_or(0.0f) * mScalingFactor);
+                            mResolution * mScalingFactor);
+
+        if (com::android::input::flags::rotary_input_telemetry()) {
+            mMinRotationsToLog = config.getInt("rotary_encoder.min_rotations_to_log");
+            if (!mMinRotationsToLog.has_value()) {
+                ALOGI("Rotary Encoder device configuration file didn't specify min log rotation.");
+            } else if (*mMinRotationsToLog <= 0) {
+                ALOGE("Rotary Encoder device configuration specified non-positive min log rotation "
+                      ": %d. Telemetry logging of rotations disabled.",
+                      *mMinRotationsToLog);
+                mMinRotationsToLog = {};
+            } else {
+                ALOGD("Rotary Encoder telemetry enabled. mMinRotationsToLog=%d",
+                      *mMinRotationsToLog);
+            }
+        }
     }
 }
 
@@ -121,10 +151,29 @@
     return out;
 }
 
+void RotaryEncoderInputMapper::logScroll(float scroll) {
+    if (mResolution <= 0 || !mMinRotationsToLog) return;
+
+    mUnloggedScrolls += fabs(scroll);
+
+    // unitsPerRotation = (2 * PI * radians) * (units per radian (i.e. resolution))
+    const float unitsPerRotation = 2 * M_PI * mResolution;
+    const float scrollsPerMinRotationsToLog = *mMinRotationsToLog * unitsPerRotation;
+    const int32_t numMinRotationsToLog =
+            static_cast<int32_t>(mUnloggedScrolls / scrollsPerMinRotationsToLog);
+    mUnloggedScrolls = std::fmod(mUnloggedScrolls, scrollsPerMinRotationsToLog);
+    if (numMinRotationsToLog) {
+        mTelemetryLogCounter("input.value_rotary_input_device_full_rotation_count",
+                             numMinRotationsToLog * (*mMinRotationsToLog));
+    }
+}
+
 std::list<NotifyArgs> RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) {
     std::list<NotifyArgs> out;
 
     float scroll = mRotaryEncoderScrollAccumulator.getRelativeVWheel();
+    logScroll(scroll);
+
     if (mSlopController) {
         scroll = mSlopController->consumeEvent(when, scroll);
     }
diff --git a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
index 7e80415..d74ced1 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -46,13 +46,39 @@
 
     int32_t mSource;
     float mScalingFactor;
+    /** Units per rotation, provided via the `device.res` IDC property. */
+    float mResolution;
     ui::Rotation mOrientation;
+    /**
+     * The minimum number of rotations to log for telemetry.
+     * Provided via `rotary_encoder.min_rotations_to_log` IDC property. If no value is provided in
+     * the IDC file, or if a non-positive value is provided, the telemetry will be disabled, and
+     * this value is set to the empty optional.
+     */
+    std::optional<int32_t> mMinRotationsToLog;
+    /**
+     * A function to log count for telemetry.
+     * The char* is the logging key, and the int64_t is the value to log.
+     * Abstracting the actual logging APIs via this function is helpful for simple unit testing.
+     */
+    std::function<void(const char*, int64_t)> mTelemetryLogCounter;
     ui::LogicalDisplayId mDisplayId = ui::LogicalDisplayId::INVALID;
     std::unique_ptr<SlopController> mSlopController;
 
+    /** Amount of raw scrolls (pre-slop) not yet logged for telemetry. */
+    float mUnloggedScrolls = 0;
+
     explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
                                       const InputReaderConfiguration& readerConfig);
+
+    /** This is a test constructor that allows injecting the expresslog Counter logic. */
+    RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
+                             const InputReaderConfiguration& readerConfig,
+                             std::function<void(const char*, int64_t)> expressLogCounter);
     [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
+
+    /** Logs a given amount of scroll for telemetry. */
+    void logScroll(float scroll);
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index b17e79a..9a36bfb 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -480,6 +480,9 @@
         return;
     }
     mGesturesToProcess.push_back(*gesture);
+    if (mTouchpadHardwareStateNotificationsEnabled) {
+        getPolicy()->notifyTouchpadGestureInfo(gesture->type, getDeviceId());
+    }
 }
 
 std::list<NotifyArgs> TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) {
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 9924d0d..da2c683 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -60,6 +60,21 @@
     }
 }
 
+bool isGestureNoFocusChange(MotionClassification classification) {
+    switch (classification) {
+        case MotionClassification::TWO_FINGER_SWIPE:
+        case MotionClassification::MULTI_FINGER_SWIPE:
+        case MotionClassification::PINCH:
+            // Most gestures can be performed on an unfocused window, so they should not
+            // not affect window focus.
+            return true;
+        case MotionClassification::NONE:
+        case MotionClassification::AMBIGUOUS_GESTURE:
+        case MotionClassification::DEEP_PRESS:
+            return false;
+    }
+}
+
 } // namespace
 
 GestureConverter::GestureConverter(InputReaderContext& readerContext,
@@ -67,6 +82,7 @@
       : mDeviceId(deviceId),
         mReaderContext(readerContext),
         mEnableFlingStop(input_flags::enable_touchpad_fling_stop()),
+        mEnableNoFocusChange(input_flags::enable_touchpad_no_focus_change()),
         // We can safely assume that ABS_MT_POSITION_X and _Y axes will be available, as EventHub
         // won't classify a device as a touchpad if they're not present.
         mXAxisInfo(deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X).value()),
@@ -338,7 +354,6 @@
         NotifyMotionArgs args =
                 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN, /* actionButton= */ 0,
                                mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
-        args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
         out.push_back(args);
     }
     float deltaX = gesture.details.scroll.dx;
@@ -353,7 +368,6 @@
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
                            mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
-    args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
     out.push_back(args);
     return out;
 }
@@ -427,7 +441,6 @@
     NotifyMotionArgs args =
             makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
                            mButtonState, /* pointerCount= */ 1, mFakeFingerCoords.data());
-    args.flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
     out.push_back(args);
     mCurrentClassification = MotionClassification::NONE;
     out += enterHover(when, readTime);
@@ -624,6 +637,18 @@
                                                   int32_t actionButton, int32_t buttonState,
                                                   uint32_t pointerCount,
                                                   const PointerCoords* pointerCoords) {
+    int32_t flags = 0;
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        flags |= AMOTION_EVENT_FLAG_CANCELED;
+    }
+    if (mEnableNoFocusChange && isGestureNoFocusChange(mCurrentClassification)) {
+        flags |= AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+    }
+    if (mCurrentClassification == MotionClassification::TWO_FINGER_SWIPE) {
+        // This helps to make GestureDetector responsive.
+        flags |= AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE;
+    }
+
     return {mReaderContext.getNextId(),
             when,
             readTime,
@@ -633,7 +658,7 @@
             /* policyFlags= */ POLICY_FLAG_WAKE,
             action,
             /* actionButton= */ actionButton,
-            /* flags= */ action == AMOTION_EVENT_ACTION_CANCEL ? AMOTION_EVENT_FLAG_CANCELED : 0,
+            flags,
             mReaderContext.getGlobalMetaState(),
             buttonState,
             mCurrentClassification,
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index 829fb92..c9a35c1 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -99,6 +99,7 @@
     const int32_t mDeviceId;
     InputReaderContext& mReaderContext;
     const bool mEnableFlingStop;
+    const bool mEnableNoFocusChange;
 
     std::optional<ui::LogicalDisplayId> mDisplayId;
     FloatRect mBoundsInLogicalDisplay{};
diff --git a/services/inputflinger/rust/bounce_keys_filter.rs b/services/inputflinger/rust/bounce_keys_filter.rs
index e05e8e5..a8959d9 100644
--- a/services/inputflinger/rust/bounce_keys_filter.rs
+++ b/services/inputflinger/rust/bounce_keys_filter.rs
@@ -25,6 +25,7 @@
 };
 use input::KeyboardType;
 use log::debug;
+use std::any::Any;
 use std::collections::{HashMap, HashSet};
 
 #[derive(Debug)]
@@ -134,6 +135,17 @@
         self.next.destroy();
     }
 
+    fn save(
+        &mut self,
+        state: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
+    ) -> HashMap<&'static str, Box<dyn Any + Send + Sync>> {
+        self.next.save(state)
+    }
+
+    fn restore(&mut self, state: &HashMap<&'static str, Box<dyn Any + Send + Sync>>) {
+        self.next.restore(state);
+    }
+
     fn dump(&mut self, dump_str: String) -> String {
         let mut result = "Bounce Keys filter: \n".to_string();
         result += &format!("\tthreshold = {:?}ns\n", self.bounce_key_threshold_ns);
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
index e221244..39c3465 100644
--- a/services/inputflinger/rust/input_filter.rs
+++ b/services/inputflinger/rust/input_filter.rs
@@ -33,6 +33,8 @@
 use crate::sticky_keys_filter::StickyKeysFilter;
 use input::ModifierState;
 use log::{error, info};
+use std::any::Any;
+use std::collections::HashMap;
 use std::sync::{Arc, Mutex, RwLock};
 
 /// Virtual keyboard device ID
@@ -43,6 +45,11 @@
     fn notify_key(&mut self, event: &KeyEvent);
     fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]);
     fn destroy(&mut self);
+    fn save(
+        &mut self,
+        state: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
+    ) -> HashMap<&'static str, Box<dyn Any + Send + Sync>>;
+    fn restore(&mut self, state: &HashMap<&'static str, Box<dyn Any + Send + Sync>>);
     fn dump(&mut self, dump_str: String) -> String;
 }
 
@@ -105,6 +112,7 @@
     fn notifyConfigurationChanged(&self, config: &InputFilterConfiguration) -> binder::Result<()> {
         {
             let mut state = self.state.lock().unwrap();
+            let saved_state = state.first_filter.save(HashMap::new());
             state.first_filter.destroy();
             let mut first_filter: Box<dyn Filter + Send + Sync> =
                 Box::new(BaseFilter::new(self.callbacks.clone()));
@@ -138,6 +146,7 @@
                 );
             }
             state.first_filter = first_filter;
+            state.first_filter.restore(&saved_state);
         }
         Result::Ok(())
     }
@@ -175,6 +184,18 @@
         // do nothing
     }
 
+    fn save(
+        &mut self,
+        state: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
+    ) -> HashMap<&'static str, Box<dyn Any + Send + Sync>> {
+        // do nothing
+        state
+    }
+
+    fn restore(&mut self, _state: &HashMap<&'static str, Box<dyn Any + Send + Sync>>) {
+        // do nothing
+    }
+
     fn dump(&mut self, dump_str: String) -> String {
         // do nothing
         dump_str
@@ -367,6 +388,8 @@
     use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
         DeviceInfo::DeviceInfo, KeyEvent::KeyEvent,
     };
+    use std::any::Any;
+    use std::collections::HashMap;
     use std::sync::{Arc, RwLock, RwLockWriteGuard};
 
     #[derive(Default)]
@@ -415,6 +438,16 @@
         fn destroy(&mut self) {
             self.inner().is_destroy_called = true;
         }
+        fn save(
+            &mut self,
+            state: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
+        ) -> HashMap<&'static str, Box<dyn Any + Send + Sync>> {
+            // do nothing
+            state
+        }
+        fn restore(&mut self, _state: &HashMap<&'static str, Box<dyn Any + Send + Sync>>) {
+            // do nothing
+        }
         fn dump(&mut self, dump_str: String) -> String {
             // do nothing
             dump_str
diff --git a/services/inputflinger/rust/slow_keys_filter.rs b/services/inputflinger/rust/slow_keys_filter.rs
index 8830aac..085e80e 100644
--- a/services/inputflinger/rust/slow_keys_filter.rs
+++ b/services/inputflinger/rust/slow_keys_filter.rs
@@ -26,7 +26,8 @@
 };
 use input::KeyboardType;
 use log::debug;
-use std::collections::HashSet;
+use std::any::Any;
+use std::collections::{HashMap, HashSet};
 use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
 
 // Policy flags from Input.h
@@ -187,6 +188,19 @@
         slow_filter.next.destroy();
     }
 
+    fn save(
+        &mut self,
+        state: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
+    ) -> HashMap<&'static str, Box<dyn Any + Send + Sync>> {
+        let mut slow_filter = self.write_inner();
+        slow_filter.next.save(state)
+    }
+
+    fn restore(&mut self, state: &HashMap<&'static str, Box<dyn Any + Send + Sync>>) {
+        let mut slow_filter = self.write_inner();
+        slow_filter.next.restore(state);
+    }
+
     fn dump(&mut self, dump_str: String) -> String {
         let mut slow_filter = self.write_inner();
         let mut result = "Slow Keys filter: \n".to_string();
diff --git a/services/inputflinger/rust/sticky_keys_filter.rs b/services/inputflinger/rust/sticky_keys_filter.rs
index 161a5fc..7a1d0ec 100644
--- a/services/inputflinger/rust/sticky_keys_filter.rs
+++ b/services/inputflinger/rust/sticky_keys_filter.rs
@@ -24,7 +24,8 @@
     DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
 };
 use input::ModifierState;
-use std::collections::HashSet;
+use std::any::Any;
+use std::collections::{HashMap, HashSet};
 
 // Modifier keycodes: values are from /frameworks/native/include/android/keycodes.h
 const KEYCODE_ALT_LEFT: i32 = 57;
@@ -40,10 +41,17 @@
 const KEYCODE_META_RIGHT: i32 = 118;
 const KEYCODE_FUNCTION: i32 = 119;
 const KEYCODE_NUM_LOCK: i32 = 143;
+static STICKY_KEYS_DATA: &str = "sticky_keys_data";
 
 pub struct StickyKeysFilter {
     next: Box<dyn Filter + Send + Sync>,
     listener: ModifierStateListener,
+    data: Data,
+}
+
+#[derive(Default)]
+/// Data that will be saved and restored across configuration changes
+struct Data {
     /// Tracking devices that contributed to the modifier state.
     contributing_devices: HashSet<i32>,
     /// State describing the current enabled modifiers. This contain both locked and non-locked
@@ -61,21 +69,15 @@
         next: Box<dyn Filter + Send + Sync>,
         listener: ModifierStateListener,
     ) -> StickyKeysFilter {
-        Self {
-            next,
-            listener,
-            contributing_devices: HashSet::new(),
-            modifier_state: ModifierState::None,
-            locked_modifier_state: ModifierState::None,
-        }
+        Self { next, listener, data: Default::default() }
     }
 }
 
 impl Filter for StickyKeysFilter {
     fn notify_key(&mut self, event: &KeyEvent) {
         let up = event.action == KeyEventAction::UP;
-        let mut modifier_state = self.modifier_state;
-        let mut locked_modifier_state = self.locked_modifier_state;
+        let mut modifier_state = self.data.modifier_state;
+        let mut locked_modifier_state = self.data.locked_modifier_state;
         if !is_ephemeral_modifier_key(event.keyCode) {
             // If non-ephemeral modifier key (i.e. non-modifier keys + toggle modifier keys like
             // CAPS_LOCK, NUM_LOCK etc.), don't block key and pass in the sticky modifier state with
@@ -93,7 +95,7 @@
             }
         } else if up {
             // Update contributing devices to track keyboards
-            self.contributing_devices.insert(event.deviceId);
+            self.data.contributing_devices.insert(event.deviceId);
             // If ephemeral modifier key, capture the key and update the sticky modifier states
             let modifier_key_mask = get_ephemeral_modifier_key_mask(event.keyCode);
             let symmetrical_modifier_key_mask = get_symmetrical_modifier_key_mask(event.keyCode);
@@ -108,38 +110,62 @@
                 modifier_state |= modifier_key_mask;
             }
         }
-        if self.modifier_state != modifier_state
-            || self.locked_modifier_state != locked_modifier_state
+        if self.data.modifier_state != modifier_state
+            || self.data.locked_modifier_state != locked_modifier_state
         {
-            self.modifier_state = modifier_state;
-            self.locked_modifier_state = locked_modifier_state;
+            self.data.modifier_state = modifier_state;
+            self.data.locked_modifier_state = locked_modifier_state;
             self.listener.modifier_state_changed(modifier_state, locked_modifier_state);
         }
     }
 
     fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
         // Clear state if all contributing devices removed
-        self.contributing_devices.retain(|id| device_infos.iter().any(|x| *id == x.deviceId));
-        if self.contributing_devices.is_empty()
-            && (self.modifier_state != ModifierState::None
-                || self.locked_modifier_state != ModifierState::None)
+        self.data.contributing_devices.retain(|id| device_infos.iter().any(|x| *id == x.deviceId));
+        if self.data.contributing_devices.is_empty()
+            && (self.data.modifier_state != ModifierState::None
+                || self.data.locked_modifier_state != ModifierState::None)
         {
-            self.modifier_state = ModifierState::None;
-            self.locked_modifier_state = ModifierState::None;
+            self.data.modifier_state = ModifierState::None;
+            self.data.locked_modifier_state = ModifierState::None;
             self.listener.modifier_state_changed(ModifierState::None, ModifierState::None);
         }
         self.next.notify_devices_changed(device_infos);
     }
 
+    fn save(
+        &mut self,
+        mut state: HashMap<&'static str, Box<dyn Any + Send + Sync>>,
+    ) -> HashMap<&'static str, Box<dyn Any + Send + Sync>> {
+        let data = Data {
+            contributing_devices: self.data.contributing_devices.clone(),
+            modifier_state: self.data.modifier_state,
+            locked_modifier_state: self.data.locked_modifier_state,
+        };
+        state.insert(STICKY_KEYS_DATA, Box::new(data));
+        self.next.save(state)
+    }
+
+    fn restore(&mut self, state: &HashMap<&'static str, Box<dyn Any + Send + Sync>>) {
+        if let Some(value) = state.get(STICKY_KEYS_DATA) {
+            if let Some(data) = value.downcast_ref::<Data>() {
+                self.data.contributing_devices = data.contributing_devices.clone();
+                self.data.modifier_state = data.modifier_state;
+                self.data.locked_modifier_state = data.locked_modifier_state;
+            }
+        }
+        self.next.restore(state)
+    }
+
     fn destroy(&mut self) {
         self.next.destroy();
     }
 
     fn dump(&mut self, dump_str: String) -> String {
         let mut result = "Sticky Keys filter: \n".to_string();
-        result += &format!("\tmodifier_state = {:?}\n", self.modifier_state);
-        result += &format!("\tlocked_modifier_state = {:?}\n", self.locked_modifier_state);
-        result += &format!("\tcontributing_devices = {:?}\n", self.contributing_devices);
+        result += &format!("\tmodifier_state = {:?}\n", self.data.modifier_state);
+        result += &format!("\tlocked_modifier_state = {:?}\n", self.data.locked_modifier_state);
+        result += &format!("\tcontributing_devices = {:?}\n", self.data.contributing_devices);
         self.next.dump(dump_str + &result)
     }
 }
@@ -245,6 +271,7 @@
     };
     use input::KeyboardType;
     use input::ModifierState;
+    use std::collections::HashMap;
     use std::sync::{Arc, RwLock};
 
     static DEVICE_ID: i32 = 1;
@@ -452,6 +479,45 @@
     }
 
     #[test]
+    fn test_modifier_state_restored_on_recreation() {
+        let test_filter = TestFilter::new();
+        let test_callbacks = TestCallbacks::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+
+        let saved_state = sticky_keys_filter.save(HashMap::new());
+        sticky_keys_filter.destroy();
+
+        // Create a new Sticky keys filter
+        let test_filter = TestFilter::new();
+        let mut sticky_keys_filter = setup_filter(
+            Box::new(test_filter.clone()),
+            Arc::new(RwLock::new(Strong::new(Box::new(test_callbacks.clone())))),
+        );
+        sticky_keys_filter.restore(&saved_state);
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
+        assert_eq!(test_callbacks.get_last_locked_modifier_state(), ModifierState::None);
+
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_DOWN });
+        sticky_keys_filter.notify_key(&KeyEvent { keyCode: KEYCODE_CTRL_LEFT, ..BASE_KEY_UP });
+        assert_eq!(
+            test_callbacks.get_last_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
+        assert_eq!(
+            test_callbacks.get_last_locked_modifier_state(),
+            ModifierState::CtrlLeftOn | ModifierState::CtrlOn
+        );
+    }
+
+    #[test]
     fn test_key_events_have_sticky_modifier_state() {
         let test_filter = TestFilter::new();
         let test_callbacks = TestCallbacks::new();
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 95283ba..744cf4a 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -123,4 +123,5 @@
         "device-tests",
         "device-platinum-tests",
     ],
+    native_coverage: false,
 }
diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
index f20c43c..353011a 100644
--- a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
+++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
@@ -20,6 +20,7 @@
 #include <memory>
 
 #include <EventHub.h>
+#include <com_android_input_flags.h>
 #include <gtest/gtest.h>
 #include <linux/input-event-codes.h>
 #include <linux/input.h>
@@ -32,6 +33,8 @@
 #include "TestEventMatchers.h"
 #include "TestInputListener.h"
 
+namespace input_flags = com::android::input::flags;
+
 namespace android {
 
 using testing::AllOf;
@@ -47,6 +50,8 @@
             mReader(mFakeEventHub, mFakePolicy, mFakeListener),
             mDevice(newDevice()),
             mDeviceContext(*mDevice, EVENTHUB_ID) {
+        input_flags::include_relative_axis_values_for_captured_touchpads(true);
+
         const size_t slotCount = 8;
         mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0);
         mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true);
@@ -126,7 +131,7 @@
 
 TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_allAxesPresent_populatedCorrectly) {
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
-    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+    mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, -500, 2000, 0, 0, 40);
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1100, 0, 0, 35);
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 30);
     mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 900, 0, 0, 25);
@@ -150,8 +155,8 @@
     const InputDeviceInfo::MotionRange* posY =
             info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD);
     ASSERT_NE(nullptr, posY);
-    EXPECT_NEAR(0, posY->min, EPSILON);
-    EXPECT_NEAR(2500, posY->max, EPSILON);
+    EXPECT_NEAR(-500, posY->min, EPSILON);
+    EXPECT_NEAR(2000, posY->max, EPSILON);
     EXPECT_NEAR(40, posY->resolution, EPSILON);
 
     const InputDeviceInfo::MotionRange* touchMajor =
@@ -182,8 +187,22 @@
     EXPECT_NEAR(800, toolMinor->max, EPSILON);
     EXPECT_NEAR(20, toolMinor->resolution, EPSILON);
 
-    // ...except orientation and pressure, which get scaled, and size, which is generated from other
-    // values.
+    // ...except for the relative motion axes, derived from the corresponding absolute ones:
+    const InputDeviceInfo::MotionRange* relX =
+            info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, relX);
+    EXPECT_NEAR(-4000, relX->min, EPSILON);
+    EXPECT_NEAR(4000, relX->max, EPSILON);
+    EXPECT_NEAR(45, relX->resolution, EPSILON);
+
+    const InputDeviceInfo::MotionRange* relY =
+            info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD);
+    ASSERT_NE(nullptr, relY);
+    EXPECT_NEAR(-2500, relY->min, EPSILON);
+    EXPECT_NEAR(2500, relY->max, EPSILON);
+    EXPECT_NEAR(40, relY->resolution, EPSILON);
+
+    // ...orientation and pressure, which get scaled:
     const InputDeviceInfo::MotionRange* orientation =
             info.getMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, AINPUT_SOURCE_TOUCHPAD);
     ASSERT_NE(nullptr, orientation);
@@ -198,6 +217,7 @@
     EXPECT_NEAR(1, pressure->max, EPSILON);
     EXPECT_NEAR(0, pressure->resolution, EPSILON);
 
+    // ... and size, which is generated from other values.
     const InputDeviceInfo::MotionRange* size =
             info.getMotionRange(AMOTION_EVENT_AXIS_SIZE, AINPUT_SOURCE_TOUCHPAD);
     ASSERT_NE(nullptr, size);
@@ -219,7 +239,9 @@
     // present, since it's generated from axes that aren't provided by this device).
     EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD));
     EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD));
-    EXPECT_EQ(2u, info.getMotionRanges().size());
+    EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_X, AINPUT_SOURCE_TOUCHPAD));
+    EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_RELATIVE_Y, AINPUT_SOURCE_TOUCHPAD));
+    EXPECT_EQ(4u, info.getMotionRanges().size());
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, OneFinger_motionReportedCorrectly) {
@@ -235,14 +257,16 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithCoords(50, 100), WithToolType(ToolType::FINGER)));
+                      WithCoords(50, 100), WithRelativeMotion(0, 0),
+                      WithToolType(ToolType::FINGER)));
 
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
     processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99);
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+                      WithCoords(52, 99), WithRelativeMotion(2, -1),
+                      WithToolType(ToolType::FINGER)));
 
     processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
     processAxis(conv, EV_KEY, BTN_TOUCH, 0);
@@ -255,8 +279,9 @@
                             VariantWith<NotifyMotionArgs>(
                                     WithMotionAction(AMOTION_EVENT_ACTION_UP))));
     EXPECT_THAT(args,
-                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(52, 99), WithPointerCount(1u),
-                                                         WithToolType(ToolType::FINGER)))));
+                Each(VariantWith<NotifyMotionArgs>(
+                        AllOf(WithCoords(52, 99), WithRelativeMotion(0, 0), WithPointerCount(1u),
+                              WithToolType(ToolType::FINGER)))));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, OneFinger_touchDimensionsPassedThrough) {
@@ -507,13 +532,13 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithCoords(51, 100)));
+                      WithCoords(51, 100), WithRelativeMotion(0, 0)));
 
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(52, 100)));
+                      WithCoords(52, 100), WithRelativeMotion(1, 0)));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, FingerArrivingAfterPalm_onlyFingerReported) {
@@ -553,7 +578,7 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
-                      WithCoords(98, 148)));
+                      WithCoords(98, 148), WithRelativeMotion(-2, -2)));
 }
 
 TEST_F(CapturedTouchpadEventConverterTest, FingerAndFingerTurningIntoPalm_partiallyCancelled) {
@@ -660,7 +685,8 @@
 
     EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
                 AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
-                      WithCoords(50, 100), WithToolType(ToolType::FINGER)));
+                      WithCoords(50, 100), WithRelativeMotion(0, 0),
+                      WithToolType(ToolType::FINGER)));
 
     processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
     processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
@@ -678,13 +704,16 @@
                 ElementsAre(VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
                                           WithPointerCount(1u), WithCoords(52, 99),
+                                          WithRelativeMotion(2, -1),
                                           WithToolType(ToolType::FINGER))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(
                                                   AMOTION_EVENT_ACTION_POINTER_DOWN |
                                                   1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                           WithPointerCount(2u), WithPointerCoords(0, 52, 99),
+                                          WithPointerRelativeMotion(0, 0, 0),
                                           WithPointerCoords(1, 250, 200),
+                                          WithPointerRelativeMotion(1, 0, 0),
                                           WithPointerToolType(0, ToolType::FINGER),
                                           WithPointerToolType(1, ToolType::FINGER)))));
 
@@ -700,14 +729,17 @@
     std::list<NotifyArgs> args = processSync(conv);
     EXPECT_THAT(args,
                 ElementsAre(VariantWith<NotifyMotionArgs>(
-                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
-                            VariantWith<NotifyMotionArgs>(WithMotionAction(
-                                    AMOTION_EVENT_ACTION_POINTER_UP |
-                                    0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT))));
+                                    AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                                          WithPointerRelativeMotion(1, 5, 2))),
+                            VariantWith<NotifyMotionArgs>(
+                                    AllOf(WithMotionAction(
+                                                  AMOTION_EVENT_ACTION_POINTER_UP |
+                                                  0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                          WithPointerRelativeMotion(1, 0, 0)))));
     EXPECT_THAT(args,
                 Each(VariantWith<NotifyMotionArgs>(
                         AllOf(WithPointerCount(2u), WithPointerCoords(0, 52, 99),
-                              WithPointerCoords(1, 255, 202),
+                              WithPointerRelativeMotion(0, 0, 0), WithPointerCoords(1, 255, 202),
                               WithPointerToolType(1, ToolType::FINGER),
                               WithPointerToolType(0, ToolType::FINGER)))));
 
@@ -723,9 +755,69 @@
                                     WithMotionAction(AMOTION_EVENT_ACTION_UP))));
     EXPECT_THAT(args,
                 Each(VariantWith<NotifyMotionArgs>(AllOf(WithPointerCount(1u), WithCoords(255, 202),
+                                                         WithRelativeMotion(0, 0),
                                                          WithToolType(ToolType::FINGER)))));
 }
 
+TEST_F(CapturedTouchpadEventConverterTest, RelativeMotionAxesClearedForNewFingerInSlot) {
+    CapturedTouchpadEventConverter conv = createConverter();
+    // Put down one finger.
+    processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithCoords(50, 100), WithRelativeMotion(0, 0)));
+
+    // Move it in negative X and Y directions.
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 47);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 97);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(47, 97),
+                      WithRelativeMotion(-3, -3)));
+
+    // Lift it.
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+    processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+
+    std::list<NotifyArgs> args = processSync(conv);
+    EXPECT_THAT(args,
+                ElementsAre(VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_MOVE)),
+                            VariantWith<NotifyMotionArgs>(
+                                    WithMotionAction(AMOTION_EVENT_ACTION_UP))));
+    EXPECT_THAT(args,
+                Each(VariantWith<NotifyMotionArgs>(AllOf(WithCoords(47, 97),
+                                                         WithRelativeMotion(0, 0),
+                                                         WithPointerCount(1u)))));
+
+    // Put down another finger using the same slot. Relative axis values should be cleared.
+    processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 60);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 60);
+
+    processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+    processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+                      WithCoords(60, 60), WithRelativeMotion(0, 0)));
+
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 64);
+    processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 58);
+
+    EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+                      WithCoords(64, 58), WithRelativeMotion(4, -2)));
+}
+
 // Pointer IDs max out at 31, and so must be reused once a touch is lifted to avoid running out.
 TEST_F(CapturedTouchpadEventConverterTest, PointerIdsReusedAfterLift) {
     CapturedTouchpadEventConverter conv = createConverter();
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index 31fbf20..943de6e 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -151,9 +151,10 @@
     getDevice(deviceId)->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode);
 }
 
-void FakeEventHub::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const {
+void FakeEventHub::setKeyRemapping(int32_t deviceId,
+                                   const std::map<int32_t, int32_t>& keyRemapping) const {
     Device* device = getDevice(deviceId);
-    device->keyRemapping.insert_or_assign(fromKeyCode, toKeyCode);
+    device->keyRemapping = keyRemapping;
 }
 
 void FakeEventHub::addLed(int32_t deviceId, int32_t led, bool initialState) {
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index 3d8dddd..2dfbb23 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -55,7 +55,7 @@
         KeyedVector<int32_t, int32_t> absoluteAxisValue;
         KeyedVector<int32_t, KeyInfo> keysByScanCode;
         KeyedVector<int32_t, KeyInfo> keysByUsageCode;
-        std::unordered_map<int32_t, int32_t> keyRemapping;
+        std::map<int32_t, int32_t> keyRemapping;
         KeyedVector<int32_t, bool> leds;
         // fake mapping which would normally come from keyCharacterMap
         std::unordered_map<int32_t, int32_t> keyCodeMapping;
@@ -129,7 +129,7 @@
     void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t keyCode,
                 uint32_t flags);
     void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode);
-    void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const;
+    void setKeyRemapping(int32_t deviceId, const std::map<int32_t, int32_t>& keyRemapping) const;
     void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition);
 
     void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType,
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index e1f844c..f373cac 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -256,6 +256,10 @@
     mTouchpadHardwareStateNotified.notify_all();
 }
 
+void FakeInputReaderPolicy::notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) {
+    std::scoped_lock lock(mLock);
+}
+
 std::shared_ptr<KeyCharacterMap> FakeInputReaderPolicy::getKeyboardLayoutOverlay(
         const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) {
     return nullptr;
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 61bb9fc..3a2b4e9 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -85,6 +85,7 @@
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
     void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
                                      int32_t deviceId) override;
+    void notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) override;
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) override;
     std::string getDeviceAlias(const InputDeviceIdentifier&) override;
diff --git a/services/inputflinger/tests/FakePointerController.cpp b/services/inputflinger/tests/FakePointerController.cpp
index d0998ba..887a939 100644
--- a/services/inputflinger/tests/FakePointerController.cpp
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -148,12 +148,6 @@
     return mIsPointerShown;
 }
 
-std::optional<FloatRect> FakePointerController::getBounds() const {
-    if (!mEnabled) return std::nullopt;
-
-    return mHaveBounds ? std::make_optional<FloatRect>(mMinX, mMinY, mMaxX, mMaxY) : std::nullopt;
-}
-
 void FakePointerController::move(float deltaX, float deltaY) {
     if (!mEnabled) return;
 
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index 2c76c62..9b773a7 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -65,7 +65,6 @@
 
 private:
     std::string dump() override { return ""; }
-    std::optional<FloatRect> getBounds() const override;
     void move(float deltaX, float deltaY) override;
     void unfade(Transition) override;
     void setPresentation(Presentation) override {}
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index d0cd677..225ae0f 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -279,6 +279,8 @@
 }
 
 TEST_F(GestureConverterTest, Scroll) {
+    input_flags::enable_touchpad_no_focus_change(true);
+
     const nsecs_t downTime = 12345;
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
@@ -300,7 +302,8 @@
     ASSERT_THAT(args,
                 Each(VariantWith<NotifyMotionArgs>(
                         AllOf(WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
-                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE |
+                                        AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE),
                               WithToolType(ToolType::FINGER),
                               WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
@@ -312,7 +315,8 @@
                               WithGestureScrollDistance(0, 5, EPSILON),
                               WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
                               WithToolType(ToolType::FINGER),
-                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+                              WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE |
+                                        AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE),
                               WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
 
     Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
@@ -325,7 +329,8 @@
                                           WithGestureScrollDistance(0, 0, EPSILON),
                                           WithMotionClassification(
                                                   MotionClassification::TWO_FINGER_SWIPE),
-                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE |
+                                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithCoords(0, 0),
@@ -845,6 +850,8 @@
 }
 
 TEST_F(GestureConverterTest, Pinch_Inwards) {
+    input_flags::enable_touchpad_no_focus_change(true);
+
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
@@ -867,7 +874,8 @@
                         AllOf(WithMotionClassification(MotionClassification::PINCH),
                               WithGesturePinchScaleFactor(1.0f, EPSILON),
                               WithToolType(ToolType::FINGER),
-                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
@@ -879,7 +887,8 @@
                               WithGesturePinchScaleFactor(0.8f, EPSILON),
                               WithPointerCoords(0, -80, 0), WithPointerCoords(1, 80, 0),
                               WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
@@ -891,12 +900,14 @@
                                                   1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(2u))),
+                                          WithPointerCount(2u),
+                                          WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(1u))),
+                                          WithPointerCount(1u),
+                                          WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithCoords(0, 0),
@@ -908,6 +919,8 @@
 }
 
 TEST_F(GestureConverterTest, Pinch_Outwards) {
+    input_flags::enable_touchpad_no_focus_change(true);
+
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
@@ -930,7 +943,8 @@
                         AllOf(WithMotionClassification(MotionClassification::PINCH),
                               WithGesturePinchScaleFactor(1.0f, EPSILON),
                               WithToolType(ToolType::FINGER),
-                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)))));
 
     Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
                           /* dz= */ 1.1, GESTURES_ZOOM_UPDATE);
@@ -942,7 +956,8 @@
                               WithGesturePinchScaleFactor(1.1f, EPSILON),
                               WithPointerCoords(0, -110, 0), WithPointerCoords(1, 110, 0),
                               WithPointerCount(2u), WithToolType(ToolType::FINGER),
-                              WithDisplayId(ui::LogicalDisplayId::DEFAULT)))));
+                              WithDisplayId(ui::LogicalDisplayId::DEFAULT),
+                              WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)))));
 
     Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
                        GESTURES_ZOOM_END);
@@ -954,12 +969,14 @@
                                                   1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(2u))),
+                                          WithPointerCount(2u),
+                                          WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                                           WithMotionClassification(MotionClassification::PINCH),
                                           WithGesturePinchScaleFactor(1.0f, EPSILON),
-                                          WithPointerCount(1u))),
+                                          WithPointerCount(1u),
+                                          WithFlags(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithCoords(0, 0),
@@ -1055,6 +1072,8 @@
 }
 
 TEST_F(GestureConverterTest, ResetDuringScroll) {
+    input_flags::enable_touchpad_no_focus_change(true);
+
     InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
     GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
     converter.setDisplayId(ui::LogicalDisplayId::DEFAULT);
@@ -1070,7 +1089,8 @@
                                           WithGestureScrollDistance(0, 0, EPSILON),
                                           WithMotionClassification(
                                                   MotionClassification::TWO_FINGER_SWIPE),
-                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE))),
+                                          WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE |
+                                                    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE))),
                             VariantWith<NotifyMotionArgs>(
                                     AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
                                           WithCoords(0, 0),
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index c2f174f..c5702e9 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -1913,6 +1913,99 @@
     window->consumeMotionEvent(WithMotionAction(ACTION_SCROLL));
 }
 
+/**
+ * Two windows: a trusted overlay and a regular window underneath. Both windows are visible.
+ * Mouse is hovered, and the hover event should only go to the overlay.
+ * However, next, the touchable region of the trusted overlay shrinks. The mouse position hasn't
+ * changed, but the cursor would now end up hovering above the regular window underneatch.
+ * If the mouse is now clicked, this would generate an ACTION_DOWN event, which would go to the
+ * regular window. However, the trusted overlay is also watching for outside touch.
+ * The trusted overlay should get two events:
+ * 1) The ACTION_OUTSIDE event, since the click is now not inside its touchable region
+ * 2) The HOVER_EXIT event, since the mouse pointer is no longer hovering inside this window
+ *
+ * This test reproduces a crash where there is an overlap between dispatch modes for the trusted
+ * overlay touch target, since the event is causing both an ACTION_OUTSIDE, and as a HOVER_EXIT.
+ */
+TEST_F(InputDispatcherTest, MouseClickUnderShrinkingTrustedOverlay) {
+    std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(app, mDispatcher, "Trusted overlay",
+                                                              ui::LogicalDisplayId::DEFAULT);
+    overlay->setTrustedOverlay(true);
+    overlay->setWatchOutsideTouch(true);
+    overlay->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(app, mDispatcher, "Regular window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+    // Hover the mouse into the overlay
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Now, shrink the touchable region of the overlay! This will cause the cursor to suddenly have
+    // the regular window as the touch target
+    overlay->setTouchableRegion(Region({0, 0, 0, 0}));
+    mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Now we can click with the mouse. The click should go into the regular window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_OUTSIDE));
+    window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+}
+
+/**
+ * Similar to above, but also has a spy on top that also catches the HOVER
+ * events. Also, instead of ACTION_DOWN, we are continuing to send the hovering
+ * stream to ensure that the spy receives hover events correctly.
+ */
+TEST_F(InputDispatcherTest, MouseClickUnderShrinkingTrustedOverlayWithSpy) {
+    std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> spyWindow =
+            sp<FakeWindowHandle>::make(app, mDispatcher, "Spy", ui::LogicalDisplayId::DEFAULT);
+    spyWindow->setFrame(Rect(0, 0, 200, 200));
+    spyWindow->setTrustedOverlay(true);
+    spyWindow->setSpy(true);
+    sp<FakeWindowHandle> overlay = sp<FakeWindowHandle>::make(app, mDispatcher, "Trusted overlay",
+                                                              ui::LogicalDisplayId::DEFAULT);
+    overlay->setTrustedOverlay(true);
+    overlay->setWatchOutsideTouch(true);
+    overlay->setFrame(Rect(0, 0, 200, 200));
+
+    sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(app, mDispatcher, "Regular window",
+                                                             ui::LogicalDisplayId::DEFAULT);
+    window->setFrame(Rect(0, 0, 200, 200));
+
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+    // Hover the mouse into the overlay
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+                                      .build());
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+    // Now, shrink the touchable region of the overlay! This will cause the cursor to suddenly have
+    // the regular window as the touch target
+    overlay->setTouchableRegion(Region({0, 0, 0, 0}));
+    mDispatcher->onWindowInfosChanged(
+            {{*spyWindow->getInfo(), *overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+    // Now we can click with the mouse. The click should go into the regular window
+    mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+                                      .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(110))
+                                      .build());
+    spyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+    overlay->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+    window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+}
+
 using InputDispatcherMultiDeviceTest = InputDispatcherTest;
 
 /**
@@ -9091,6 +9184,7 @@
 protected:
     static constexpr std::chrono::nanoseconds KEY_REPEAT_TIMEOUT = 40ms;
     static constexpr std::chrono::nanoseconds KEY_REPEAT_DELAY = 40ms;
+    static constexpr bool KEY_REPEAT_ENABLED = true;
 
     std::shared_ptr<FakeApplicationHandle> mApp;
     sp<FakeWindowHandle> mWindow;
@@ -9098,7 +9192,8 @@
     virtual void SetUp() override {
         InputDispatcherTest::SetUp();
 
-        mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
+        mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY,
+                                               KEY_REPEAT_ENABLED);
         setUpWindow();
     }
 
@@ -9247,6 +9342,24 @@
     expectKeyRepeatOnce(3);
 }
 
+TEST_F(InputDispatcherKeyRepeatTest, FocusedWindow_NoRepeatWhenKeyRepeatDisabled) {
+    SCOPED_FLAG_OVERRIDE(keyboard_repeat_keys, true);
+    static constexpr std::chrono::milliseconds KEY_NO_REPEAT_ASSERTION_TIMEOUT = 100ms;
+
+    mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY,
+                                           /*repeatKeyEnabled=*/false);
+    sendAndConsumeKeyDown(/*deviceId=*/1);
+
+    ASSERT_GT(KEY_NO_REPEAT_ASSERTION_TIMEOUT, KEY_REPEAT_TIMEOUT)
+            << "Ensure the check for no key repeats extends beyond the repeat timeout duration.";
+    ASSERT_GT(KEY_NO_REPEAT_ASSERTION_TIMEOUT, KEY_REPEAT_DELAY)
+            << "Ensure the check for no key repeats extends beyond the repeat delay duration.";
+
+    // No events should be returned if key repeat is turned off.
+    // Wait for KEY_NO_REPEAT_ASSERTION_TIMEOUT to return no events to ensure key repeat disabled.
+    mWindow->assertNoEvents(KEY_NO_REPEAT_ASSERTION_TIMEOUT);
+}
+
 /* Test InputDispatcher for MultiDisplay */
 class InputDispatcherFocusOnTwoDisplaysTest : public InputDispatcherTest {
 public:
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 4a9e893..17c37d5 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -3330,11 +3330,11 @@
 TEST_F(KeyboardInputMapperTest, Process_KeyRemapping) {
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
     mFakeEventHub->addKey(EVENTHUB_ID, KEY_B, 0, AKEYCODE_B, 0);
-    mFakeEventHub->addKeyRemapping(EVENTHUB_ID, AKEYCODE_A, AKEYCODE_B);
 
     KeyboardInputMapper& mapper =
             constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD);
 
+    mFakeEventHub->setKeyRemapping(EVENTHUB_ID, {{AKEYCODE_A, AKEYCODE_B}});
     // Key down by scan code.
     process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_A, 1);
     NotifyKeyArgs args;
diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp
index 2ccd93e..3cc4bdd 100644
--- a/services/inputflinger/tests/InputTracingTest.cpp
+++ b/services/inputflinger/tests/InputTracingTest.cpp
@@ -133,8 +133,8 @@
         mDispatcher->setFocusedWindow(request);
     }
 
-    void tapAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
-                      Level inboundTraceLevel, Level dispatchTraceLevel, InputTraceSession& s) {
+    void tapAndExpect(const std::vector<sp<FakeWindowHandle>>& windows, Level inboundTraceLevel,
+                      Level dispatchTraceLevel, InputTraceSession& s) {
         const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
                                   .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110))
                                   .build();
@@ -156,7 +156,7 @@
         }
     }
 
-    void keypressAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows,
+    void keypressAndExpect(const std::vector<sp<FakeWindowHandle>>& windows,
                            Level inboundTraceLevel, Level dispatchTraceLevel,
                            InputTraceSession& s) {
         const auto down = KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build();
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 5a3d79d..f41b39a 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -97,7 +97,8 @@
     MOCK_METHOD(bool, hasRelativeAxis, (int32_t deviceId, int axis), (const));
     MOCK_METHOD(bool, hasInputProperty, (int32_t deviceId, int property), (const));
     MOCK_METHOD(bool, hasMscEvent, (int32_t deviceId, int mscEvent), (const));
-    MOCK_METHOD(void, addKeyRemapping, (int32_t deviceId, int fromKeyCode, int toKeyCode), (const));
+    MOCK_METHOD(void, setKeyRemapping,
+                (int32_t deviceId, (const std::map<int32_t, int32_t>& keyRemapping)), (const));
     MOCK_METHOD(status_t, mapKey,
                 (int32_t deviceId, int scanCode, int usageCode, int32_t metaState,
                  int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags),
@@ -247,8 +248,6 @@
     MOCK_METHOD(int32_t, getMetaState, (), ());
     MOCK_METHOD(void, updateMetaState, (int32_t keyCode), ());
 
-    MOCK_METHOD(void, addKeyRemapping, (int32_t fromKeyCode, int32_t toKeyCode), ());
-
     MOCK_METHOD(void, setKeyboardType, (KeyboardType keyboardType), ());
 
     MOCK_METHOD(void, bumpGeneration, (), ());
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
index 5650286..3f14c23 100644
--- a/services/inputflinger/tests/LatencyTracker_test.cpp
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -61,7 +61,6 @@
 
 InputEventTimeline getTestTimeline() {
     InputEventTimeline t(
-            /*isDown=*/false,
             /*eventTime=*/2,
             /*readTime=*/3,
             /*vendorId=*/0,
@@ -88,7 +87,7 @@
         connection1 = sp<BBinder>::make();
         connection2 = sp<BBinder>::make();
 
-        mTracker = std::make_unique<LatencyTracker>(this);
+        mTracker = std::make_unique<LatencyTracker>(*this);
         setDefaultInputDeviceInfo(*mTracker);
     }
     void TearDown() override {}
@@ -107,6 +106,8 @@
     void processTimeline(const InputEventTimeline& timeline) override {
         mReceivedTimelines.push_back(timeline);
     }
+    void pushLatencyStatistics() override {}
+    std::string dump(const char* prefix) const { return ""; };
     std::deque<InputEventTimeline> mReceivedTimelines;
 };
 
@@ -174,7 +175,7 @@
                             AMOTION_EVENT_ACTION_CANCEL, InputEventType::MOTION);
     triggerEventReporting(/*eventTime=*/2);
     assertReceivedTimeline(
-            InputEventTimeline{/*isDown=*/false, /*eventTime=*/2,
+            InputEventTimeline{/*eventTime=*/2,
                                /*readTime=*/3, /*vendorId=*/0, /*productID=*/0,
                                /*sources=*/{InputDeviceUsageSource::UNKNOWN},
                                /*inputEventActionType=*/InputEventActionType::UNKNOWN_INPUT_EVENT});
@@ -226,7 +227,6 @@
 TEST_F(LatencyTrackerTest, WhenDuplicateEventsAreReported_DoesNotCrash) {
     constexpr nsecs_t inputEventId = 1;
     constexpr nsecs_t readTime = 3; // does not matter for this test
-    constexpr bool isDown = false;  // does not matter for this test
 
     // In the following 2 calls to trackListener, the inputEventId's are the same, but event times
     // are different.
@@ -246,7 +246,6 @@
 TEST_F(LatencyTrackerTest, MultipleEvents_AreReportedConsistently) {
     constexpr int32_t inputEventId1 = 1;
     InputEventTimeline timeline1(
-            /*isDown*/ false,
             /*eventTime*/ 2,
             /*readTime*/ 3,
             /*vendorId=*/0,
@@ -264,7 +263,6 @@
 
     constexpr int32_t inputEventId2 = 10;
     InputEventTimeline timeline2(
-            /*isDown=*/false,
             /*eventTime=*/20,
             /*readTime=*/30,
             /*vendorId=*/0,
@@ -317,9 +315,9 @@
                                 /*deviceId=*/DEVICE_ID,
                                 /*sources=*/{InputDeviceUsageSource::UNKNOWN},
                                 AMOTION_EVENT_ACTION_CANCEL, InputEventType::MOTION);
-        expectedTimelines.push_back(InputEventTimeline{timeline.isDown, timeline.eventTime,
-                                                       timeline.readTime, timeline.vendorId,
-                                                       timeline.productId, timeline.sources,
+        expectedTimelines.push_back(InputEventTimeline{timeline.eventTime, timeline.readTime,
+                                                       timeline.vendorId, timeline.productId,
+                                                       timeline.sources,
                                                        timeline.inputEventActionType});
     }
     // Now, complete the first event that was sent.
@@ -350,10 +348,9 @@
                             {InputDeviceUsageSource::UNKNOWN}, AMOTION_EVENT_ACTION_CANCEL,
                             InputEventType::MOTION);
     triggerEventReporting(expected.eventTime);
-    assertReceivedTimeline(InputEventTimeline{expected.isDown, expected.eventTime,
-                                              expected.readTime, expected.vendorId,
-                                              expected.productId, expected.sources,
-                                              expected.inputEventActionType});
+    assertReceivedTimeline(InputEventTimeline{expected.eventTime, expected.readTime,
+                                              expected.vendorId, expected.productId,
+                                              expected.sources, expected.inputEventActionType});
 }
 
 /**
@@ -364,7 +361,7 @@
 TEST_F(LatencyTrackerTest, TrackListenerCheck_DeviceInfoFieldsInputEventTimeline) {
     constexpr int32_t inputEventId = 1;
     InputEventTimeline timeline(
-            /*isDown*/ false, /*eventTime*/ 2, /*readTime*/ 3,
+            /*eventTime*/ 2, /*readTime*/ 3,
             /*vendorId=*/50, /*productId=*/60,
             /*sources=*/
             {InputDeviceUsageSource::TOUCHSCREEN, InputDeviceUsageSource::STYLUS_DIRECT},
@@ -390,37 +387,37 @@
     constexpr int32_t inputEventId = 1;
     // Create timelines for different event types (Motion, Key)
     InputEventTimeline motionDownTimeline(
-            /*isDown*/ true, /*eventTime*/ 2, /*readTime*/ 3,
+            /*eventTime*/ 2, /*readTime*/ 3,
             /*vendorId*/ 0, /*productId*/ 0,
             /*sources*/ {InputDeviceUsageSource::UNKNOWN},
             /*inputEventActionType*/ InputEventActionType::MOTION_ACTION_DOWN);
 
     InputEventTimeline motionMoveTimeline(
-            /*isDown*/ false, /*eventTime*/ 4, /*readTime*/ 5,
+            /*eventTime*/ 4, /*readTime*/ 5,
             /*vendorId*/ 0, /*productId*/ 0,
             /*sources*/ {InputDeviceUsageSource::UNKNOWN},
             /*inputEventActionType*/ InputEventActionType::MOTION_ACTION_MOVE);
 
     InputEventTimeline motionUpTimeline(
-            /*isDown*/ false, /*eventTime*/ 6, /*readTime*/ 7,
+            /*eventTime*/ 6, /*readTime*/ 7,
             /*vendorId*/ 0, /*productId*/ 0,
             /*sources*/ {InputDeviceUsageSource::UNKNOWN},
             /*inputEventActionType*/ InputEventActionType::MOTION_ACTION_UP);
 
     InputEventTimeline keyDownTimeline(
-            /*isDown*/ false, /*eventTime*/ 8, /*readTime*/ 9,
+            /*eventTime*/ 8, /*readTime*/ 9,
             /*vendorId*/ 0, /*productId*/ 0,
             /*sources*/ {InputDeviceUsageSource::UNKNOWN},
             /*inputEventActionType*/ InputEventActionType::KEY);
 
     InputEventTimeline keyUpTimeline(
-            /*isDown*/ false, /*eventTime*/ 10, /*readTime*/ 11,
+            /*eventTime*/ 10, /*readTime*/ 11,
             /*vendorId*/ 0, /*productId*/ 0,
             /*sources*/ {InputDeviceUsageSource::UNKNOWN},
             /*inputEventActionType*/ InputEventActionType::KEY);
 
     InputEventTimeline unknownTimeline(
-            /*isDown*/ false, /*eventTime*/ 12, /*readTime*/ 13,
+            /*eventTime*/ 12, /*readTime*/ 13,
             /*vendorId*/ 0, /*productId*/ 0,
             /*sources*/ {InputDeviceUsageSource::UNKNOWN},
             /*inputEventActionType*/ InputEventActionType::UNKNOWN_INPUT_EVENT);
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 18222dd..411c7ba 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -978,6 +978,36 @@
     assertPointerControllerRemoved(pc);
 }
 
+/**
+ * When both "show touches" and "stylus hover icons" are enabled, if the app doesn't specify an
+ * icon for the hovering stylus, fall back to using the spot hover icon.
+ */
+TEST_F(PointerChoreographerTest, ShowTouchesOverridesUnspecifiedStylusIcon) {
+    mChoreographer.setShowTouchesEnabled(true);
+    mChoreographer.setStylusPointerIconEnabled(true);
+    mChoreographer.notifyInputDevicesChanged(
+            {/*id=*/0,
+             {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+                                     DISPLAY_ID)}});
+
+    mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+                                                  AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+                                        .pointer(STYLUS_POINTER)
+                                        .deviceId(DEVICE_ID)
+                                        .displayId(DISPLAY_ID)
+                                        .build());
+    auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+    mChoreographer.setPointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED, DISPLAY_ID, DEVICE_ID);
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_SPOT_HOVER);
+
+    mChoreographer.setPointerIcon(PointerIconStyle::TYPE_ARROW, DISPLAY_ID, DEVICE_ID);
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_ARROW);
+
+    mChoreographer.setPointerIcon(PointerIconStyle::TYPE_NOT_SPECIFIED, DISPLAY_ID, DEVICE_ID);
+    pc->assertPointerIconSet(PointerIconStyle::TYPE_SPOT_HOVER);
+}
+
 using StylusFixtureParam =
         std::tuple</*name*/ std::string_view, /*source*/ uint32_t, ControllerType>;
 
diff --git a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
index 6607bc7..157bee3 100644
--- a/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
+++ b/services/inputflinger/tests/RotaryEncoderInputMapper_test.cpp
@@ -23,6 +23,8 @@
 
 #include <android-base/logging.h>
 #include <android_companion_virtualdevice_flags.h>
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gtest/gtest.h>
 #include <input/DisplayViewport.h>
 #include <linux/input-event-codes.h>
@@ -100,6 +102,15 @@
         EXPECT_CALL(mMockEventHub, hasRelativeAxis(EVENTHUB_ID, REL_HWHEEL_HI_RES))
                 .WillRepeatedly(Return(false));
     }
+
+    std::map<std::string, int64_t> mTelemetryLogCounts;
+
+    /**
+     * A fake function for telemetry logging.
+     * Records the log counts in the `mTelemetryLogCounts` map.
+     */
+    std::function<void(const char*, int64_t)> mTelemetryLogCounter =
+            [this](const char* key, int64_t value) { mTelemetryLogCounts[key] += value; };
 };
 
 TEST_F(RotaryEncoderInputMapperTest, ConfigureDisplayIdWithAssociatedViewport) {
@@ -187,4 +198,142 @@
                               WithMotionAction(AMOTION_EVENT_ACTION_SCROLL), WithScroll(0.5f)))));
 }
 
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, RotaryInputTelemetryFlagOff_NoRotationLogging,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                       rotary_input_telemetry))) {
+    mPropertyMap.addProperty("device.res", "3");
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 70);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+}
+
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, ZeroResolution_NoRotationLogging,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      rotary_input_telemetry))) {
+    mPropertyMap.addProperty("device.res", "-3");
+    mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "2");
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+}
+
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, NegativeMinLogRotation_NoRotationLogging,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      rotary_input_telemetry))) {
+    mPropertyMap.addProperty("device.res", "3");
+    mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "-2");
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+}
+
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, ZeroMinLogRotation_NoRotationLogging,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      rotary_input_telemetry))) {
+    mPropertyMap.addProperty("device.res", "3");
+    mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "0");
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+}
+
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, NoMinLogRotation_NoRotationLogging,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      rotary_input_telemetry))) {
+    // 3 units per radian, 2 * M_PI * 3 = ~18.85 units per rotation.
+    mPropertyMap.addProperty("device.res", "3");
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 700);
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+}
+
+TEST_F_WITH_FLAGS(RotaryEncoderInputMapperTest, RotationLogging,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(com::android::input::flags,
+                                                      rotary_input_telemetry))) {
+    // 3 units per radian, 2 * M_PI * 3 = ~18.85 units per rotation.
+    // Multiples of `unitsPerRoation`, to easily follow the assertions below.
+    // [18.85, 37.7, 56.55, 75.4, 94.25, 113.1, 131.95, 150.8]
+    mPropertyMap.addProperty("device.res", "3");
+    mPropertyMap.addProperty("rotary_encoder.min_rotations_to_log", "2");
+
+    mMapper = createInputMapper<RotaryEncoderInputMapper>(*mDeviceContext, mReaderConfiguration,
+                                                          mTelemetryLogCounter);
+    InputDeviceInfo info;
+    mMapper->populateDeviceInfo(info);
+
+    std::list<NotifyArgs> args;
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 15); // total scroll = 15
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 13); // total scroll = 28
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    // Expect 0 since `min_rotations_to_log` = 2, and total scroll 28 only has 1 rotation.
+    ASSERT_EQ(mTelemetryLogCounts.find("input.value_rotary_input_device_full_rotation_count"),
+              mTelemetryLogCounts.end());
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, 10); // total scroll = 38
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    // Total scroll includes >= `min_rotations_to_log` (2), expect log.
+    ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 2);
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -22); // total scroll = 60
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    // Expect no additional telemetry. Total rotation is 3, and total unlogged rotation is 1, which
+    // is less than `min_rotations_to_log`.
+    ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 2);
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -16); // total scroll = 76
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    // Total unlogged rotation >= `min_rotations_to_log` (2), so expect 2 more logged rotation.
+    ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 4);
+
+    args += process(ARBITRARY_TIME, EV_REL, REL_WHEEL, -76); // total scroll = 152
+    args += process(ARBITRARY_TIME, EV_SYN, SYN_REPORT, 0);
+    // Total unlogged scroll >= 4*`min_rotations_to_log`. Expect *all* unlogged rotations to be
+    // logged, even if that's more than multiple of `min_rotations_to_log`.
+    ASSERT_EQ(mTelemetryLogCounts["input.value_rotary_input_device_full_rotation_count"], 8);
+}
+
 } // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
index cfedc6e..6fa3365 100644
--- a/services/inputflinger/tests/TestEventMatchers.h
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -615,7 +615,12 @@
     explicit WithPointerIdMatcher(size_t index, int32_t pointerId)
           : mIndex(index), mPointerId(pointerId) {}
 
-    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+    bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream* os) const {
+        if (mIndex >= args.pointerCoords.size()) {
+            *os << "Pointer index " << mIndex << " is out of bounds";
+            return false;
+        }
+
         return args.pointerProperties[mIndex].id == mPointerId;
     }
 
@@ -646,12 +651,51 @@
     return (isnan(x) ? isnan(argX) : x == argX) && (isnan(y) ? isnan(argY) : y == argY);
 }
 
-MATCHER_P2(WithRelativeMotion, x, y, "InputEvent with specified relative motion") {
-    const auto argX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
-    const auto argY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
-    *result_listener << "expected relative motion (" << x << ", " << y << "), but got (" << argX
-                     << ", " << argY << ")";
-    return argX == x && argY == y;
+/// Relative motion matcher
+class WithRelativeMotionMatcher {
+public:
+    using is_gtest_matcher = void;
+    explicit WithRelativeMotionMatcher(size_t pointerIndex, float relX, float relY)
+          : mPointerIndex(pointerIndex), mRelX(relX), mRelY(relY) {}
+
+    bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+        if (mPointerIndex >= event.pointerCoords.size()) {
+            *os << "Pointer index " << mPointerIndex << " is out of bounds";
+            return false;
+        }
+
+        const PointerCoords& coords = event.pointerCoords[mPointerIndex];
+        bool matches = mRelX == coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X) &&
+                mRelY == coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+        if (!matches) {
+            *os << "expected relative motion (" << mRelX << ", " << mRelY << ") at pointer index "
+                << mPointerIndex << ", but got ("
+                << coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X) << ", "
+                << coords.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y) << ")";
+        }
+        return matches;
+    }
+
+    void DescribeTo(std::ostream* os) const {
+        *os << "with relative motion (" << mRelX << ", " << mRelY << ") at pointer index "
+            << mPointerIndex;
+    }
+
+    void DescribeNegationTo(std::ostream* os) const { *os << "wrong relative motion"; }
+
+private:
+    const size_t mPointerIndex;
+    const float mRelX;
+    const float mRelY;
+};
+
+inline WithRelativeMotionMatcher WithRelativeMotion(float relX, float relY) {
+    return WithRelativeMotionMatcher(0, relX, relY);
+}
+
+inline WithRelativeMotionMatcher WithPointerRelativeMotion(size_t pointerIndex, float relX,
+                                                           float relY) {
+    return WithRelativeMotionMatcher(pointerIndex, relX, relY);
 }
 
 MATCHER_P3(WithGestureOffset, dx, dy, epsilon,
@@ -758,10 +802,14 @@
     return argToolType == toolType;
 }
 
-MATCHER_P2(WithPointerToolType, pointer, toolType,
+MATCHER_P2(WithPointerToolType, pointerIndex, toolType,
            "InputEvent with specified tool type for pointer") {
-    const auto argToolType = arg.pointerProperties[pointer].toolType;
-    *result_listener << "expected pointer " << pointer << " to have tool type "
+    if (std::cmp_greater_equal(pointerIndex, arg.getPointerCount())) {
+        *result_listener << "Pointer index " << pointerIndex << " is out of bounds";
+        return false;
+    }
+    const auto argToolType = arg.pointerProperties[pointerIndex].toolType;
+    *result_listener << "expected pointer " << pointerIndex << " to have tool type "
                      << ftl::enum_string(toolType) << ", but got " << ftl::enum_string(argToolType);
     return argToolType == toolType;
 }
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
index 3e4a19b..5442a65 100644
--- a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
@@ -155,10 +155,6 @@
         return reader->getLightPlayerId(deviceId, lightId);
     }
 
-    void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const {
-        reader->addKeyRemapping(deviceId, fromKeyCode, toKeyCode);
-    }
-
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
         return reader->getKeyCodeForKeyLocation(deviceId, locationKeyCode);
     }
diff --git a/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp b/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
index 80c2213..908fa40 100644
--- a/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
@@ -38,6 +38,10 @@
             connectionTimeline.isComplete();
         }
     };
+
+    void pushLatencyStatistics() override {}
+
+    std::string dump(const char* prefix) const { return ""; };
 };
 
 static sp<IBinder> getConnectionToken(FuzzedDataProvider& fdp,
@@ -53,7 +57,7 @@
     FuzzedDataProvider fdp(data, size);
 
     EmptyProcessor emptyProcessor;
-    LatencyTracker tracker(&emptyProcessor);
+    LatencyTracker tracker(emptyProcessor);
 
     // Make some pre-defined tokens to ensure that some timelines are complete.
     std::array<sp<IBinder> /*token*/, 10> predefinedTokens;
@@ -71,7 +75,7 @@
                     const DeviceId deviceId = fdp.ConsumeIntegral<int32_t>();
                     std::set<InputDeviceUsageSource> sources = {
                             fdp.ConsumeEnum<InputDeviceUsageSource>()};
-                    int32_t inputEventActionType = fdp.ConsumeIntegral<int32_t>();
+                    const int32_t inputEventActionType = fdp.ConsumeIntegral<int32_t>();
                     const InputEventType inputEventType = fdp.ConsumeEnum<InputEventType>();
                     tracker.trackListener(inputEventId, eventTime, readTime, deviceId, sources,
                                           inputEventActionType, inputEventType);
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index ddc3310..fa8270a 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -202,7 +202,8 @@
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override {
         return mFdp->ConsumeIntegral<int32_t>();
     }
-    void addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const override {}
+    void setKeyRemapping(int32_t deviceId,
+                         const std::map<int32_t, int32_t>& keyRemapping) const override {}
     int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override {
         return mFdp->ConsumeIntegral<int32_t>();
     }
@@ -283,6 +284,7 @@
     void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {}
     void notifyTouchpadHardwareState(const SelfContainedHardwareState& schs,
                                      int32_t deviceId) override {}
+    void notifyTouchpadGestureInfo(GestureType type, int32_t deviceId) override {}
     std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
             const InputDeviceIdentifier& identifier,
             const std::optional<KeyboardLayoutInfo> layoutInfo) override {
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index f4b0265..7b2596a 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -52,6 +52,7 @@
         "-Wall",
         "-Werror",
         "-Wextra",
+        "-Wthread-safety",
         "-fvisibility=hidden",
     ],
 
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index f56642b..0d00642 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -711,14 +711,17 @@
         if (err == OK && isSensorCapped) {
             if ((requestedSamplingPeriodNs >= SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS) ||
                 !isRateCappedBasedOnPermission()) {
+                Mutex::Autolock _l(mConnectionLock);
                 mMicSamplingPeriodBackup[handle] = requestedSamplingPeriodNs;
             } else {
+                Mutex::Autolock _l(mConnectionLock);
                 mMicSamplingPeriodBackup[handle] = SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS;
             }
         }
 
     } else {
         err = mService->disable(this, handle);
+        Mutex::Autolock _l(mConnectionLock);
         mMicSamplingPeriodBackup.erase(handle);
     }
     return err;
@@ -750,8 +753,10 @@
     if (ret == OK && isSensorCapped) {
         if ((requestedSamplingPeriodNs >= SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS) ||
             !isRateCappedBasedOnPermission()) {
+            Mutex::Autolock _l(mConnectionLock);
             mMicSamplingPeriodBackup[handle] = requestedSamplingPeriodNs;
         } else {
+            Mutex::Autolock _l(mConnectionLock);
             mMicSamplingPeriodBackup[handle] = SENSOR_SERVICE_CAPPED_SAMPLING_PERIOD_NS;
         }
     }
diff --git a/services/sensorservice/SensorEventConnection.h b/services/sensorservice/SensorEventConnection.h
index 6a98a40..bb8733d 100644
--- a/services/sensorservice/SensorEventConnection.h
+++ b/services/sensorservice/SensorEventConnection.h
@@ -199,7 +199,8 @@
     // valid mapping for sensors that require a permission in order to reduce the lookup time.
     std::unordered_map<int32_t, int32_t> mHandleToAppOp;
     // Mapping of sensor handles to its rate before being capped by the mic toggle.
-    std::unordered_map<int, nsecs_t> mMicSamplingPeriodBackup;
+    std::unordered_map<int, nsecs_t> mMicSamplingPeriodBackup
+        GUARDED_BY(mConnectionLock);
     userid_t mUserId;
 
     std::optional<bool> mIsRateCappedBasedOnPermission;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index d1429a2..14a8fd6 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -155,7 +155,7 @@
     uint32_t geomBufferTransform{0};
     Rect geomBufferSize;
     Rect geomContentCrop;
-    Rect geomCrop;
+    FloatRect geomCrop;
 
     GenericLayerMetadataMap metadata;
 
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
index 091c207..e6f0d2d 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -24,6 +24,7 @@
 #include <compositionengine/impl/OutputLayerCompositionState.h>
 #include <cstdint>
 #include "system/graphics-base-v1.0.h"
+#include "ui/FloatRect.h"
 
 #include <ui/HdrRenderTypeUtils.h>
 
@@ -185,35 +186,35 @@
     const auto& layerState = *getLayerFE().getCompositionState();
     const auto& outputState = getOutput().getState();
 
+    // Convert from layer space to layerStackSpace
     // apply the layer's transform, followed by the display's global transform
     // here we're guaranteed that the layer's transform preserves rects
-    Region activeTransparentRegion = layerState.transparentRegionHint;
     const ui::Transform& layerTransform = layerState.geomLayerTransform;
-    const ui::Transform& inverseLayerTransform = layerState.geomInverseLayerTransform;
-    const Rect& bufferSize = layerState.geomBufferSize;
-    Rect activeCrop = layerState.geomCrop;
-    if (!activeCrop.isEmpty() && bufferSize.isValid()) {
-        activeCrop = layerTransform.transform(activeCrop);
-        if (!activeCrop.intersect(outputState.layerStackSpace.getContent(), &activeCrop)) {
-            activeCrop.clear();
-        }
-        activeCrop = inverseLayerTransform.transform(activeCrop, true);
-        // This needs to be here as transform.transform(Rect) computes the
-        // transformed rect and then takes the bounding box of the result before
-        // returning. This means
-        // transform.inverse().transform(transform.transform(Rect)) != Rect
-        // in which case we need to make sure the final rect is clipped to the
-        // display bounds.
-        if (!activeCrop.intersect(bufferSize, &activeCrop)) {
-            activeCrop.clear();
-        }
+    Region activeTransparentRegion = layerTransform.transform(layerState.transparentRegionHint);
+    if (!layerState.geomCrop.isEmpty() && layerState.geomBufferSize.isValid()) {
+        FloatRect activeCrop = layerTransform.transform(layerState.geomCrop);
+        activeCrop = activeCrop.intersect(outputState.layerStackSpace.getContent().toFloatRect());
+        const FloatRect& bufferSize =
+                layerTransform.transform(layerState.geomBufferSize.toFloatRect());
+        activeCrop = activeCrop.intersect(bufferSize);
+
         // mark regions outside the crop as transparent
-        activeTransparentRegion.orSelf(Rect(0, 0, bufferSize.getWidth(), activeCrop.top));
-        activeTransparentRegion.orSelf(
-                Rect(0, activeCrop.bottom, bufferSize.getWidth(), bufferSize.getHeight()));
-        activeTransparentRegion.orSelf(Rect(0, activeCrop.top, activeCrop.left, activeCrop.bottom));
-        activeTransparentRegion.orSelf(
-                Rect(activeCrop.right, activeCrop.top, bufferSize.getWidth(), activeCrop.bottom));
+        Rect topRegion = Rect(layerTransform.transform(
+                FloatRect(0, 0, layerState.geomBufferSize.getWidth(), layerState.geomCrop.top)));
+        Rect bottomRegion = Rect(layerTransform.transform(
+                FloatRect(0, layerState.geomCrop.bottom, layerState.geomBufferSize.getWidth(),
+                          layerState.geomBufferSize.getHeight())));
+        Rect leftRegion = Rect(layerTransform.transform(FloatRect(0, layerState.geomCrop.top,
+                                                                 layerState.geomCrop.left,
+                                                                 layerState.geomCrop.bottom)));
+        Rect rightRegion = Rect(layerTransform.transform(
+                FloatRect(layerState.geomCrop.right, layerState.geomCrop.top,
+                          layerState.geomBufferSize.getWidth(), layerState.geomCrop.bottom)));
+
+        activeTransparentRegion.orSelf(topRegion);
+        activeTransparentRegion.orSelf(bottomRegion);
+        activeTransparentRegion.orSelf(leftRegion);
+        activeTransparentRegion.orSelf(rightRegion);
     }
 
     // reduce uses a FloatRect to provide more accuracy during the
@@ -223,19 +224,22 @@
     // Some HWCs may clip client composited input to its displayFrame. Make sure
     // that this does not cut off the shadow.
     if (layerState.forceClientComposition && layerState.shadowSettings.length > 0.0f) {
-        const auto outset = layerState.shadowSettings.length;
+        // RenderEngine currently blurs shadows to smooth out edges, so outset by
+        // 2x the length instead of 1x to compensate
+        const auto outset = layerState.shadowSettings.length * 2;
         geomLayerBounds.left -= outset;
         geomLayerBounds.top -= outset;
         geomLayerBounds.right += outset;
         geomLayerBounds.bottom += outset;
     }
-    Rect frame{layerTransform.transform(reduce(geomLayerBounds, activeTransparentRegion))};
-    if (!frame.intersect(outputState.layerStackSpace.getContent(), &frame)) {
-        frame.clear();
-    }
-    const ui::Transform displayTransform{outputState.transform};
 
-    return displayTransform.transform(frame);
+    geomLayerBounds = layerTransform.transform(geomLayerBounds);
+    FloatRect frame = reduce(geomLayerBounds, activeTransparentRegion);
+    frame = frame.intersect(outputState.layerStackSpace.getContent().toFloatRect());
+
+    // convert from layerStackSpace to displaySpace
+    const ui::Transform displayTransform{outputState.transform};
+    return Rect(displayTransform.transform(frame));
 }
 
 uint32_t OutputLayer::calculateOutputRelativeBufferTransform(
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 839fb7d..e910c72 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -51,7 +51,8 @@
     MOCK_CONST_METHOD0(getMaxVirtualDisplayCount, size_t());
     MOCK_CONST_METHOD0(getMaxVirtualDisplayDimension, size_t());
     MOCK_METHOD3(allocateVirtualDisplay, bool(HalVirtualDisplayId, ui::Size, ui::PixelFormat*));
-    MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId));
+    MOCK_METHOD3(allocatePhysicalDisplay,
+                 void(hal::HWDisplayId, PhysicalDisplayId, std::optional<ui::Size>));
 
     MOCK_METHOD1(createLayer, std::shared_ptr<HWC2::Layer>(HalDisplayId));
     MOCK_METHOD(status_t, getDeviceCompositionChanges,
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
index 1c54469..b21533a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp
@@ -30,6 +30,7 @@
 #include "MockHWC2.h"
 #include "MockHWComposer.h"
 #include "RegionMatcher.h"
+#include "ui/FloatRect.h"
 
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
 
@@ -270,7 +271,7 @@
         mLayerFEState.geomLayerTransform = ui::Transform{TR_IDENT};
         mLayerFEState.geomBufferSize = Rect{0, 0, 1920, 1080};
         mLayerFEState.geomBufferUsesDisplayInverseTransform = false;
-        mLayerFEState.geomCrop = Rect{0, 0, 1920, 1080};
+        mLayerFEState.geomCrop = FloatRect{0, 0, 1920, 1080};
         mLayerFEState.geomLayerBounds = FloatRect{0.f, 0.f, 1920.f, 1080.f};
 
         mOutputState.layerStackSpace.setContent(Rect{0, 0, 1920, 1080});
@@ -296,20 +297,20 @@
 }
 
 TEST_F(OutputLayerDisplayFrameTest, cropAffectsDisplayFrame) {
-    mLayerFEState.geomCrop = Rect{100, 200, 300, 500};
+    mLayerFEState.geomCrop = FloatRect{100, 200, 300, 500};
     const Rect expected{100, 200, 300, 500};
     EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, cropAffectsDisplayFrameRotated) {
-    mLayerFEState.geomCrop = Rect{100, 200, 300, 500};
+    mLayerFEState.geomCrop = FloatRect{100, 200, 300, 500};
     mLayerFEState.geomLayerTransform.set(HAL_TRANSFORM_ROT_90, 1920, 1080);
     const Rect expected{1420, 100, 1720, 300};
     EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
 TEST_F(OutputLayerDisplayFrameTest, emptyGeomCropIsNotUsedToComputeFrame) {
-    mLayerFEState.geomCrop = Rect{};
+    mLayerFEState.geomCrop = FloatRect{};
     const Rect expected{0, 0, 1920, 1080};
     EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
@@ -339,7 +340,7 @@
 
     mLayerFEState.geomLayerBounds = FloatRect{100.f, 100.f, 200.f, 200.f};
     Rect expected{mLayerFEState.geomLayerBounds};
-    expected.inset(-kShadowRadius, -kShadowRadius, -kShadowRadius, -kShadowRadius);
+    expected.inset(-2 * kShadowRadius, -2 * kShadowRadius, -2 * kShadowRadius, -2 * kShadowRadius);
     EXPECT_THAT(calculateOutputDisplayFrame(), expected);
 }
 
diff --git a/services/surfaceflinger/Display/DisplayModeController.cpp b/services/surfaceflinger/Display/DisplayModeController.cpp
index 0e9218c..f8b6c6e 100644
--- a/services/surfaceflinger/Display/DisplayModeController.cpp
+++ b/services/surfaceflinger/Display/DisplayModeController.cpp
@@ -283,6 +283,8 @@
     }
 }
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-value" // b/369277774
 auto DisplayModeController::getKernelIdleTimerState(PhysicalDisplayId displayId) const
         -> KernelIdleTimerState {
     std::lock_guard lock(mDisplayLock);
@@ -298,4 +300,5 @@
     return {desiredModeIdOpt, displayPtr->isKernelIdleTimerEnabled};
 }
 
+#pragma clang diagnostic pop
 } // namespace android::display
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 8e78af4..f1fa938 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -675,6 +675,11 @@
         }
     });
 }
+
+void Display::setPhysicalSizeInMm(std::optional<ui::Size> size) {
+    mPhysicalSize = size;
+}
+
 } // namespace impl
 
 // Layer methods
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.h b/services/surfaceflinger/DisplayHardware/HWC2.h
index d426bca..8e2aeaf 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -105,6 +105,7 @@
     virtual bool isVsyncPeriodSwitchSupported() const = 0;
     virtual bool hasDisplayIdleTimerCapability() const = 0;
     virtual void onLayerDestroyed(hal::HWLayerId layerId) = 0;
+    virtual std::optional<ui::Size> getPhysicalSizeInMm() const = 0;
 
     [[nodiscard]] virtual hal::Error acceptChanges() = 0;
     [[nodiscard]] virtual base::expected<std::shared_ptr<HWC2::Layer>, hal::Error>
@@ -285,6 +286,8 @@
     bool hasDisplayIdleTimerCapability() const override;
     void onLayerDestroyed(hal::HWLayerId layerId) override;
     hal::Error getPhysicalDisplayOrientation(Hwc2::AidlTransform* outTransform) const override;
+    void setPhysicalSizeInMm(std::optional<ui::Size> size);
+    std::optional<ui::Size> getPhysicalSizeInMm() const override { return mPhysicalSize; }
 
 private:
     void loadDisplayCapabilities();
@@ -318,6 +321,8 @@
     std::optional<
             std::unordered_set<aidl::android::hardware::graphics::composer3::DisplayCapability>>
             mDisplayCapabilities GUARDED_BY(mDisplayCapabilitiesMutex);
+    // Physical size in mm.
+    std::optional<ui::Size> mPhysicalSize;
 };
 
 } // namespace impl
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index c83e0da..d08e261 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -76,6 +76,7 @@
 using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
+using aidl::android::hardware::graphics::composer3::DisplayConfiguration;
 using namespace std::string_literals;
 
 namespace android {
@@ -222,8 +223,8 @@
     return true;
 }
 
-void HWComposer::allocatePhysicalDisplay(hal::HWDisplayId hwcDisplayId,
-                                         PhysicalDisplayId displayId) {
+void HWComposer::allocatePhysicalDisplay(hal::HWDisplayId hwcDisplayId, PhysicalDisplayId displayId,
+                                         std::optional<ui::Size> physicalSize) {
     mPhysicalDisplayIdMap[hwcDisplayId] = displayId;
 
     if (!mPrimaryHwcDisplayId) {
@@ -235,6 +236,7 @@
             std::make_unique<HWC2::impl::Display>(*mComposer.get(), mCapabilities, hwcDisplayId,
                                                   hal::DisplayType::PHYSICAL);
     newDisplay->setConnected(true);
+    newDisplay->setPhysicalSizeInMm(physicalSize);
     displayData.hwcDisplay = std::move(newDisplay);
 }
 
@@ -276,6 +278,47 @@
     return getModesFromLegacyDisplayConfigs(hwcDisplayId);
 }
 
+DisplayConfiguration::Dpi HWComposer::getEstimatedDotsPerInchFromSize(
+        uint64_t hwcDisplayId, const HWCDisplayMode& hwcMode) const {
+    if (!FlagManager::getInstance().correct_dpi_with_display_size()) {
+        return {-1, -1};
+    }
+    if (const auto displayId = toPhysicalDisplayId(hwcDisplayId)) {
+        if (const auto it = mDisplayData.find(displayId.value());
+            it != mDisplayData.end() && it->second.hwcDisplay->getPhysicalSizeInMm()) {
+            ui::Size size = it->second.hwcDisplay->getPhysicalSizeInMm().value();
+            if (hwcMode.width > 0 && hwcMode.height > 0 && size.width > 0 && size.height > 0) {
+                static constexpr float kMmPerInch = 25.4f;
+                return {hwcMode.width * kMmPerInch / size.width,
+                        hwcMode.height * kMmPerInch / size.height};
+            }
+        }
+    }
+    return {-1, -1};
+}
+
+DisplayConfiguration::Dpi HWComposer::correctedDpiIfneeded(
+        DisplayConfiguration::Dpi dpi, DisplayConfiguration::Dpi estimatedDpi) const {
+    // hwc can be unreliable when it comes to dpi. A rough estimated dpi may yield better
+    // results. For instance, libdrm and bad edid may result in a dpi of {350, 290} for a
+    // 16:9 3840x2160 display, which would match a 4:3 aspect ratio.
+    // The logic here checks if hwc was able to provide some dpi, and if so if the dpi
+    // disparity between the axes is more reasonable than a rough estimate, otherwise use
+    // the estimated dpi as a corrected value.
+    if (estimatedDpi.x == -1 || estimatedDpi.y == -1) {
+        return dpi;
+    }
+    if (dpi.x == -1 || dpi.y == -1) {
+        return estimatedDpi;
+    }
+    if (std::min(dpi.x, dpi.y) != 0 && std::min(estimatedDpi.x, estimatedDpi.y) != 0 &&
+        abs(dpi.x - dpi.y) / std::min(dpi.x, dpi.y) >
+                abs(estimatedDpi.x - estimatedDpi.y) / std::min(estimatedDpi.x, estimatedDpi.y)) {
+        return estimatedDpi;
+    }
+    return dpi;
+}
+
 std::vector<HWComposer::HWCDisplayMode> HWComposer::getModesFromDisplayConfigurations(
         uint64_t hwcDisplayId, int32_t maxFrameIntervalNs) const {
     std::vector<hal::DisplayConfiguration> configs;
@@ -294,9 +337,16 @@
                                       .configGroup = config.configGroup,
                                       .vrrConfig = config.vrrConfig};
 
+        const DisplayConfiguration::Dpi estimatedDPI =
+                getEstimatedDotsPerInchFromSize(hwcDisplayId, hwcMode);
         if (config.dpi) {
-            hwcMode.dpiX = config.dpi->x;
-            hwcMode.dpiY = config.dpi->y;
+            const DisplayConfiguration::Dpi dpi =
+                    correctedDpiIfneeded(config.dpi.value(), estimatedDPI);
+            hwcMode.dpiX = dpi.x;
+            hwcMode.dpiY = dpi.y;
+        } else if (estimatedDPI.x != -1 && estimatedDPI.y != -1) {
+            hwcMode.dpiX = estimatedDPI.x;
+            hwcMode.dpiY = estimatedDPI.y;
         }
 
         if (!mEnableVrrTimeout) {
@@ -328,12 +378,14 @@
 
         const int32_t dpiX = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_X);
         const int32_t dpiY = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_Y);
-        if (dpiX != -1) {
-            hwcMode.dpiX = static_cast<float>(dpiX) / 1000.f;
-        }
-        if (dpiY != -1) {
-            hwcMode.dpiY = static_cast<float>(dpiY) / 1000.f;
-        }
+        const DisplayConfiguration::Dpi hwcDpi =
+                DisplayConfiguration::Dpi{dpiX == -1 ? dpiY : dpiX / 1000.f,
+                                          dpiY == -1 ? dpiY : dpiY / 1000.f};
+        const DisplayConfiguration::Dpi estimatedDPI =
+                getEstimatedDotsPerInchFromSize(hwcDisplayId, hwcMode);
+        const DisplayConfiguration::Dpi dpi = correctedDpiIfneeded(hwcDpi, estimatedDPI);
+        hwcMode.dpiX = dpi.x;
+        hwcMode.dpiY = dpi.y;
 
         modes.push_back(hwcMode);
     }
@@ -1072,6 +1124,8 @@
             getDisplayIdentificationData(hwcDisplayId, &port, &data);
             if (auto newInfo = parseDisplayIdentificationData(port, data)) {
                 info->deviceProductInfo = std::move(newInfo->deviceProductInfo);
+                info->preferredDetailedTimingDescriptor =
+                        std::move(newInfo->preferredDetailedTimingDescriptor);
             } else {
                 ALOGE("Failed to parse identification data for display %" PRIu64, hwcDisplayId);
             }
@@ -1114,7 +1168,11 @@
     }
 
     if (!isConnected(info->id)) {
-        allocatePhysicalDisplay(hwcDisplayId, info->id);
+        std::optional<ui::Size> size = std::nullopt;
+        if (info->preferredDetailedTimingDescriptor) {
+            size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
+        }
+        allocatePhysicalDisplay(hwcDisplayId, info->id, size);
     }
     return info;
 }
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index dec4bfe..b95c619 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -134,7 +134,8 @@
     // supported by the HWC can be queried in advance, but allocation may fail for other reasons.
     virtual bool allocateVirtualDisplay(HalVirtualDisplayId, ui::Size, ui::PixelFormat*) = 0;
 
-    virtual void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId) = 0;
+    virtual void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId,
+                                         std::optional<ui::Size> physicalSize) = 0;
 
     // Attempts to create a new layer on this display
     virtual std::shared_ptr<HWC2::Layer> createLayer(HalDisplayId) = 0;
@@ -349,7 +350,8 @@
     bool allocateVirtualDisplay(HalVirtualDisplayId, ui::Size, ui::PixelFormat*) override;
 
     // Called from SurfaceFlinger, when the state for a new physical display needs to be recreated.
-    void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId) override;
+    void allocatePhysicalDisplay(hal::HWDisplayId, PhysicalDisplayId,
+                                 std::optional<ui::Size> physicalSize) override;
 
     // Attempts to create a new layer on this display
     std::shared_ptr<HWC2::Layer> createLayer(HalDisplayId) override;
@@ -532,6 +534,13 @@
     std::optional<DisplayIdentificationInfo> onHotplugDisconnect(hal::HWDisplayId);
     bool shouldIgnoreHotplugConnect(hal::HWDisplayId, bool hasDisplayIdentificationData) const;
 
+    aidl::android::hardware::graphics::composer3::DisplayConfiguration::Dpi
+    getEstimatedDotsPerInchFromSize(uint64_t hwcDisplayId, const HWCDisplayMode& hwcMode) const;
+
+    aidl::android::hardware::graphics::composer3::DisplayConfiguration::Dpi correctedDpiIfneeded(
+            aidl::android::hardware::graphics::composer3::DisplayConfiguration::Dpi dpi,
+            aidl::android::hardware::graphics::composer3::DisplayConfiguration::Dpi estimatedDpi)
+            const;
     std::vector<HWCDisplayMode> getModesFromDisplayConfigurations(uint64_t hwcDisplayId,
                                                                   int32_t maxFrameIntervalNs) const;
     std::vector<HWCDisplayMode> getModesFromLegacyDisplayConfigs(uint64_t hwcDisplayId) const;
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 2311360..47b811b 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -38,6 +38,8 @@
 using FrameTimelineEvent = perfetto::protos::pbzero::FrameTimelineEvent;
 using FrameTimelineDataSource = impl::FrameTimeline::FrameTimelineDataSource;
 
+namespace {
+
 void dumpTable(std::string& result, TimelineItem predictions, TimelineItem actuals,
                const std::string& indent, PredictionState predictionState, nsecs_t baseTime) {
     StringAppendF(&result, "%s", indent.c_str());
@@ -319,6 +321,16 @@
     return minTime;
 }
 
+bool shouldTraceForDataSource(const FrameTimelineDataSource::TraceContext& ctx, nsecs_t timestamp) {
+    if (auto ds = ctx.GetDataSourceLocked(); ds && ds->getStartTime() > timestamp) {
+        return false;
+    }
+
+    return true;
+}
+
+} // namespace
+
 int64_t TraceCookieCounter::getCookieForTracing() {
     return ++mTraceCookie;
 }
@@ -726,15 +738,24 @@
     classifyJankLocked(JankType::None, refreshRate, displayFrameRenderRate, nullptr);
 }
 
-void SurfaceFrame::tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
+void SurfaceFrame::tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset,
+                                    bool filterFramesBeforeTraceStarts) const {
     int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+    bool traced = false;
 
     // Expected timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+        const auto timestamp = mPredictions.startTime;
+        if (filterFramesBeforeTraceStarts && !shouldTraceForDataSource(ctx, timestamp)) {
+            // Do not trace packets started before tracing starts.
+            return;
+        }
+        traced = true;
+
         std::scoped_lock lock(mMutex);
         auto packet = ctx.NewTracePacket();
         packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(static_cast<uint64_t>(mPredictions.startTime + monoBootOffset));
+        packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* expectedSurfaceFrameStartEvent = event->set_expected_surface_frame_start();
@@ -748,42 +769,54 @@
         expectedSurfaceFrameStartEvent->set_layer_name(mDebugName);
     });
 
-    // Expected timeline end
-    FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
-        std::scoped_lock lock(mMutex);
-        auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(static_cast<uint64_t>(mPredictions.endTime + monoBootOffset));
+    if (traced) {
+        // Expected timeline end
+        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            std::scoped_lock lock(mMutex);
+            auto packet = ctx.NewTracePacket();
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+            packet->set_timestamp(static_cast<uint64_t>(mPredictions.endTime + monoBootOffset));
 
-        auto* event = packet->set_frame_timeline_event();
-        auto* expectedSurfaceFrameEndEvent = event->set_frame_end();
+            auto* event = packet->set_frame_timeline_event();
+            auto* expectedSurfaceFrameEndEvent = event->set_frame_end();
 
-        expectedSurfaceFrameEndEvent->set_cookie(expectedTimelineCookie);
-    });
+            expectedSurfaceFrameEndEvent->set_cookie(expectedTimelineCookie);
+        });
+    }
 }
 
-void SurfaceFrame::traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
+void SurfaceFrame::traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset,
+                                bool filterFramesBeforeTraceStarts) const {
     int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+    bool traced = false;
 
     // Actual timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+        const auto timestamp = [&]() {
+            std::scoped_lock lock(mMutex);
+            // Actual start time is not yet available, so use expected start instead
+            if (mPredictionState == PredictionState::Expired) {
+                // If prediction is expired, we can't use the predicted start time. Instead, just
+                // use a start time a little earlier than the end time so that we have some info
+                // about this frame in the trace.
+                nsecs_t endTime =
+                        (mPresentState == PresentState::Dropped ? mDropTime : mActuals.endTime);
+                return endTime - kPredictionExpiredStartTimeDelta;
+            }
+
+            return mActuals.startTime == 0 ? mPredictions.startTime : mActuals.startTime;
+        }();
+
+        if (filterFramesBeforeTraceStarts && !shouldTraceForDataSource(ctx, timestamp)) {
+            // Do not trace packets started before tracing starts.
+            return;
+        }
+        traced = true;
+
         std::scoped_lock lock(mMutex);
         auto packet = ctx.NewTracePacket();
         packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        // Actual start time is not yet available, so use expected start instead
-        if (mPredictionState == PredictionState::Expired) {
-            // If prediction is expired, we can't use the predicted start time. Instead, just use a
-            // start time a little earlier than the end time so that we have some info about this
-            // frame in the trace.
-            nsecs_t endTime =
-                    (mPresentState == PresentState::Dropped ? mDropTime : mActuals.endTime);
-            const auto timestamp = endTime - kPredictionExpiredStartTimeDelta;
-            packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
-        } else {
-            const auto timestamp =
-                    mActuals.startTime == 0 ? mPredictions.startTime : mActuals.startTime;
-            packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
-        }
+        packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* actualSurfaceFrameStartEvent = event->set_actual_surface_frame_start();
@@ -812,28 +845,31 @@
         actualSurfaceFrameStartEvent->set_jank_severity_type(toProto(mJankSeverityType));
     });
 
-    // Actual timeline end
-    FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
-        std::scoped_lock lock(mMutex);
-        auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        if (mPresentState == PresentState::Dropped) {
-            packet->set_timestamp(static_cast<uint64_t>(mDropTime + monoBootOffset));
-        } else {
-            packet->set_timestamp(static_cast<uint64_t>(mActuals.endTime + monoBootOffset));
-        }
+    if (traced) {
+        // Actual timeline end
+        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            std::scoped_lock lock(mMutex);
+            auto packet = ctx.NewTracePacket();
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+            if (mPresentState == PresentState::Dropped) {
+                packet->set_timestamp(static_cast<uint64_t>(mDropTime + monoBootOffset));
+            } else {
+                packet->set_timestamp(static_cast<uint64_t>(mActuals.endTime + monoBootOffset));
+            }
 
-        auto* event = packet->set_frame_timeline_event();
-        auto* actualSurfaceFrameEndEvent = event->set_frame_end();
+            auto* event = packet->set_frame_timeline_event();
+            auto* actualSurfaceFrameEndEvent = event->set_frame_end();
 
-        actualSurfaceFrameEndEvent->set_cookie(actualTimelineCookie);
-    });
+            actualSurfaceFrameEndEvent->set_cookie(actualTimelineCookie);
+        });
+    }
 }
 
 /**
  * TODO(b/178637512): add inputEventId to the perfetto trace.
  */
-void SurfaceFrame::trace(int64_t displayFrameToken, nsecs_t monoBootOffset) const {
+void SurfaceFrame::trace(int64_t displayFrameToken, nsecs_t monoBootOffset,
+                         bool filterFramesBeforeTraceStarts) const {
     if (mToken == FrameTimelineInfo::INVALID_VSYNC_ID ||
         displayFrameToken == FrameTimelineInfo::INVALID_VSYNC_ID) {
         // No packets can be traced with a missing token.
@@ -842,9 +878,9 @@
     if (getPredictionState() != PredictionState::Expired) {
         // Expired predictions have zeroed timestamps. This cannot be used in any meaningful way in
         // a trace.
-        tracePredictions(displayFrameToken, monoBootOffset);
+        tracePredictions(displayFrameToken, monoBootOffset, filterFramesBeforeTraceStarts);
     }
-    traceActuals(displayFrameToken, monoBootOffset);
+    traceActuals(displayFrameToken, monoBootOffset, filterFramesBeforeTraceStarts);
 }
 
 namespace impl {
@@ -870,8 +906,12 @@
 }
 
 FrameTimeline::FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
-                             JankClassificationThresholds thresholds, bool useBootTimeClock)
+                             JankClassificationThresholds thresholds, bool useBootTimeClock,
+                             bool filterFramesBeforeTraceStarts)
       : mUseBootTimeClock(useBootTimeClock),
+        mFilterFramesBeforeTraceStarts(
+                FlagManager::getInstance().filter_frames_before_trace_starts() &&
+                filterFramesBeforeTraceStarts),
         mMaxDisplayFrames(kDefaultMaxDisplayFrames),
         mTimeStats(std::move(timeStats)),
         mSurfaceFlingerPid(surfaceFlingerPid),
@@ -1154,16 +1194,23 @@
     }
 }
 
-void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid,
-                                                   nsecs_t monoBootOffset) const {
+void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                                                   bool filterFramesBeforeTraceStarts) const {
     int64_t expectedTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+    bool traced = false;
 
     // Expected timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+        const auto timestamp = mSurfaceFlingerPredictions.startTime;
+        if (filterFramesBeforeTraceStarts && !shouldTraceForDataSource(ctx, timestamp)) {
+            // Do not trace packets started before tracing starts.
+            return;
+        }
+        traced = true;
+
         auto packet = ctx.NewTracePacket();
         packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(
-                static_cast<uint64_t>(mSurfaceFlingerPredictions.startTime + monoBootOffset));
+        packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* expectedDisplayFrameStartEvent = event->set_expected_display_frame_start();
@@ -1174,22 +1221,25 @@
         expectedDisplayFrameStartEvent->set_pid(surfaceFlingerPid);
     });
 
-    // Expected timeline end
-    FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
-        auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(
-                static_cast<uint64_t>(mSurfaceFlingerPredictions.endTime + monoBootOffset));
+    if (traced) {
+        // Expected timeline end
+        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            auto packet = ctx.NewTracePacket();
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+            packet->set_timestamp(
+                    static_cast<uint64_t>(mSurfaceFlingerPredictions.endTime + monoBootOffset));
 
-        auto* event = packet->set_frame_timeline_event();
-        auto* expectedDisplayFrameEndEvent = event->set_frame_end();
+            auto* event = packet->set_frame_timeline_event();
+            auto* expectedDisplayFrameEndEvent = event->set_frame_end();
 
-        expectedDisplayFrameEndEvent->set_cookie(expectedTimelineCookie);
-    });
+            expectedDisplayFrameEndEvent->set_cookie(expectedTimelineCookie);
+        });
+    }
 }
 
 void FrameTimeline::DisplayFrame::addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                                                  nsecs_t previousPredictionPresentTime) const {
+                                                  nsecs_t previousPredictionPresentTime,
+                                                  bool filterFramesBeforeTraceStarts) const {
     nsecs_t skippedFrameStartTime = 0, skippedFramePresentTime = 0;
     const constexpr float kThresh = 0.5f;
     const constexpr float kRange = 1.5f;
@@ -1215,9 +1265,17 @@
     // add slice
     if (skippedFrameStartTime != 0 && skippedFramePresentTime != 0) {
         int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+        bool traced = false;
 
         // Actual timeline start
         FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            if (filterFramesBeforeTraceStarts &&
+                !shouldTraceForDataSource(ctx, skippedFrameStartTime)) {
+                // Do not trace packets started before tracing starts.
+                return;
+            }
+            traced = true;
+
             auto packet = ctx.NewTracePacket();
             packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
             packet->set_timestamp(static_cast<uint64_t>(skippedFrameStartTime + monoBootOffset));
@@ -1238,30 +1296,40 @@
             actualDisplayFrameStartEvent->set_jank_severity_type(toProto(JankSeverityType::None));
         });
 
-        // Actual timeline end
-        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
-            auto packet = ctx.NewTracePacket();
-            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-            packet->set_timestamp(static_cast<uint64_t>(skippedFramePresentTime + monoBootOffset));
+        if (traced) {
+            // Actual timeline end
+            FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+                auto packet = ctx.NewTracePacket();
+                packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+                packet->set_timestamp(
+                        static_cast<uint64_t>(skippedFramePresentTime + monoBootOffset));
 
-            auto* event = packet->set_frame_timeline_event();
-            auto* actualDisplayFrameEndEvent = event->set_frame_end();
+                auto* event = packet->set_frame_timeline_event();
+                auto* actualDisplayFrameEndEvent = event->set_frame_end();
 
-            actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
-        });
+                actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
+            });
+        }
     }
 }
 
-void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid,
-                                               nsecs_t monoBootOffset) const {
+void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                                               bool filterFramesBeforeTraceStarts) const {
     int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+    bool traced = false;
 
     // Actual timeline start
     FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+        const auto timestamp = mSurfaceFlingerActuals.startTime;
+        if (filterFramesBeforeTraceStarts && !shouldTraceForDataSource(ctx, timestamp)) {
+            // Do not trace packets started before tracing starts.
+            return;
+        }
+        traced = true;
+
         auto packet = ctx.NewTracePacket();
         packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(
-                static_cast<uint64_t>(mSurfaceFlingerActuals.startTime + monoBootOffset));
+        packet->set_timestamp(static_cast<uint64_t>(timestamp + monoBootOffset));
 
         auto* event = packet->set_frame_timeline_event();
         auto* actualDisplayFrameStartEvent = event->set_actual_display_frame_start();
@@ -1280,22 +1348,25 @@
         actualDisplayFrameStartEvent->set_jank_severity_type(toProto(mJankSeverityType));
     });
 
-    // Actual timeline end
-    FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
-        auto packet = ctx.NewTracePacket();
-        packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
-        packet->set_timestamp(
-                static_cast<uint64_t>(mSurfaceFlingerActuals.presentTime + monoBootOffset));
+    if (traced) {
+        // Actual timeline end
+        FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+            auto packet = ctx.NewTracePacket();
+            packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+            packet->set_timestamp(
+                    static_cast<uint64_t>(mSurfaceFlingerActuals.presentTime + monoBootOffset));
 
-        auto* event = packet->set_frame_timeline_event();
-        auto* actualDisplayFrameEndEvent = event->set_frame_end();
+            auto* event = packet->set_frame_timeline_event();
+            auto* actualDisplayFrameEndEvent = event->set_frame_end();
 
-        actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
-    });
+            actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
+        });
+    }
 }
 
 nsecs_t FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                                           nsecs_t previousPredictionPresentTime) const {
+                                           nsecs_t previousPredictionPresentTime,
+                                           bool filterFramesBeforeTraceStarts) const {
     if (mSurfaceFrames.empty()) {
         // We don't want to trace display frames without any surface frames updates as this cannot
         // be janky
@@ -1311,16 +1382,17 @@
     if (mPredictionState == PredictionState::Valid) {
         // Expired and unknown predictions have zeroed timestamps. This cannot be used in any
         // meaningful way in a trace.
-        tracePredictions(surfaceFlingerPid, monoBootOffset);
+        tracePredictions(surfaceFlingerPid, monoBootOffset, filterFramesBeforeTraceStarts);
     }
-    traceActuals(surfaceFlingerPid, monoBootOffset);
+    traceActuals(surfaceFlingerPid, monoBootOffset, filterFramesBeforeTraceStarts);
 
     for (auto& surfaceFrame : mSurfaceFrames) {
-        surfaceFrame->trace(mToken, monoBootOffset);
+        surfaceFrame->trace(mToken, monoBootOffset, filterFramesBeforeTraceStarts);
     }
 
     if (FlagManager::getInstance().add_sf_skipped_frames_to_trace()) {
-        addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousPredictionPresentTime);
+        addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousPredictionPresentTime,
+                        filterFramesBeforeTraceStarts);
     }
     return mSurfaceFlingerPredictions.presentTime;
 }
@@ -1414,8 +1486,9 @@
         const nsecs_t signalTime = Fence::SIGNAL_TIME_INVALID;
         auto& displayFrame = pendingPresentFence.second;
         displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
-        mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
-                                                             mPreviousPredictionPresentTime);
+        mPreviousPredictionPresentTime =
+                displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
+                                    mPreviousPredictionPresentTime, mFilterFramesBeforeTraceStarts);
         mPendingPresentFences.erase(mPendingPresentFences.begin());
     }
 
@@ -1431,8 +1504,9 @@
 
         auto& displayFrame = pendingPresentFence.second;
         displayFrame->onPresent(signalTime, mPreviousActualPresentTime);
-        mPreviousPredictionPresentTime = displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
-                                                             mPreviousPredictionPresentTime);
+        mPreviousPredictionPresentTime =
+                displayFrame->trace(mSurfaceFlingerPid, monoBootOffset,
+                                    mPreviousPredictionPresentTime, mFilterFramesBeforeTraceStarts);
         mPreviousActualPresentTime = signalTime;
 
         mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i));
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 94cfcb4..cffb61e 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -214,7 +214,8 @@
     // enabled. The displayFrameToken is needed to link the SurfaceFrame to the corresponding
     // DisplayFrame at the trace processor side. monoBootOffset is the difference
     // between SYSTEM_TIME_BOOTTIME and SYSTEM_TIME_MONOTONIC.
-    void trace(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
+    void trace(int64_t displayFrameToken, nsecs_t monoBootOffset,
+               bool filterFramesBeforeTraceStarts) const;
 
     // Getter functions used only by FrameTimelineTests and SurfaceFrame internally
     TimelineItem getActuals() const;
@@ -234,8 +235,10 @@
             std::chrono::duration_cast<std::chrono::nanoseconds>(2ms).count();
 
 private:
-    void tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
-    void traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
+    void tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset,
+                          bool filterFramesBeforeTraceStarts) const;
+    void traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset,
+                      bool filterFramesBeforeTraceStarts) const;
     void classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
                             Fps displayFrameRenderRate, nsecs_t* outDeadlineDelta) REQUIRES(mMutex);
 
@@ -367,9 +370,15 @@
 class FrameTimeline : public android::frametimeline::FrameTimeline {
 public:
     class FrameTimelineDataSource : public perfetto::DataSource<FrameTimelineDataSource> {
-        void OnSetup(const SetupArgs&) override{};
-        void OnStart(const StartArgs&) override{};
-        void OnStop(const StopArgs&) override{};
+    public:
+        nsecs_t getStartTime() const { return mTraceStartTime; }
+
+    private:
+        void OnSetup(const SetupArgs&) override {};
+        void OnStart(const StartArgs&) override { mTraceStartTime = systemTime(); };
+        void OnStop(const StopArgs&) override {};
+
+        nsecs_t mTraceStartTime = 0;
     };
 
     /*
@@ -390,7 +399,8 @@
         // is enabled. monoBootOffset is the difference between SYSTEM_TIME_BOOTTIME
         // and SYSTEM_TIME_MONOTONIC.
         nsecs_t trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                      nsecs_t previousPredictionPresentTime) const;
+                      nsecs_t previousPredictionPresentTime,
+                      bool filterFramesBeforeTraceStarts) const;
         // Sets the token, vsyncPeriod, predictions and SF start time.
         void onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
                         std::optional<TimelineItem> predictions, nsecs_t wakeUpTime);
@@ -424,10 +434,13 @@
 
     private:
         void dump(std::string& result, nsecs_t baseTime) const;
-        void tracePredictions(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
-        void traceActuals(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
+        void tracePredictions(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                              bool filterFramesBeforeTraceStarts) const;
+        void traceActuals(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+                          bool filterFramesBeforeTraceStarts) const;
         void addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
-                             nsecs_t previousActualPresentTime) const;
+                             nsecs_t previousActualPresentTime,
+                             bool filterFramesBeforeTraceStarts) const;
         void classifyJank(nsecs_t& deadlineDelta, nsecs_t& deltaToVsync,
                           nsecs_t previousPresentTime);
 
@@ -471,7 +484,8 @@
     };
 
     FrameTimeline(std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid,
-                  JankClassificationThresholds thresholds = {}, bool useBootTimeClock = true);
+                  JankClassificationThresholds thresholds = {}, bool useBootTimeClock = true,
+                  bool filterFramesBeforeTraceStarts = true);
     ~FrameTimeline() = default;
 
     frametimeline::TokenManager* getTokenManager() override { return &mTokenManager; }
@@ -516,6 +530,7 @@
     TraceCookieCounter mTraceCookieCounter;
     mutable std::mutex mMutex;
     const bool mUseBootTimeClock;
+    const bool mFilterFramesBeforeTraceStarts;
     uint32_t mMaxDisplayFrames;
     std::shared_ptr<TimeStats> mTimeStats;
     const pid_t mSurfaceFlingerPid;
diff --git a/services/surfaceflinger/FrontEnd/LayerHandle.cpp b/services/surfaceflinger/FrontEnd/LayerHandle.cpp
index 75e4e3a..ffd51a4 100644
--- a/services/surfaceflinger/FrontEnd/LayerHandle.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerHandle.cpp
@@ -28,7 +28,7 @@
 
 LayerHandle::~LayerHandle() {
     if (mFlinger) {
-        mFlinger->onHandleDestroyed(this, mLayer, mLayerId);
+        mFlinger->onHandleDestroyed(mLayer, mLayerId);
     }
 }
 
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
index 398e64a..b7d4cc5 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshot.h
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -72,7 +72,7 @@
     bool premultipliedAlpha;
     ui::Transform parentTransform;
     Rect bufferSize;
-    Rect croppedBufferSize;
+    FloatRect croppedBufferSize;
     std::shared_ptr<renderengine::ExternalTexture> externalTexture;
     gui::LayerMetadata layerMetadata;
     gui::LayerMetadata relativeLayerMetadata;
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
index ac15b92..10e212e 100644
--- a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -116,7 +116,7 @@
  * that's already included.
  */
 std::pair<FloatRect, bool> getInputBounds(const LayerSnapshot& snapshot, bool fillParentBounds) {
-    FloatRect inputBounds = snapshot.croppedBufferSize.toFloatRect();
+    FloatRect inputBounds = snapshot.croppedBufferSize;
     if (snapshot.hasBufferOrSidebandStream() && snapshot.croppedBufferSize.isValid() &&
         snapshot.localTransform.getType() != ui::Transform::IDENTITY) {
         inputBounds = snapshot.localTransform.transform(inputBounds);
@@ -220,7 +220,7 @@
     }
 
     // Check if the parent has cropped the buffer
-    Rect bufferSize = snapshot.croppedBufferSize;
+    FloatRect bufferSize = snapshot.croppedBufferSize;
     if (!bufferSize.isValid()) {
         snapshot.inputInfo.inputConfig |= gui::WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED;
         return;
@@ -279,24 +279,6 @@
           snapshot.getDebugString().c_str());
 }
 
-bool needsInputInfo(const LayerSnapshot& snapshot, const RequestedLayerState& requested) {
-    if (requested.potentialCursor) {
-        return false;
-    }
-
-    if (snapshot.inputInfo.token != nullptr) {
-        return true;
-    }
-
-    if (snapshot.hasBufferOrSidebandStream()) {
-        return true;
-    }
-
-    return requested.windowInfoHandle &&
-            requested.windowInfoHandle->getInfo()->inputConfig.test(
-                    gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL);
-}
-
 void updateMetadata(LayerSnapshot& snapshot, const RequestedLayerState& requested,
                     const LayerSnapshotBuilder::Args& args) {
     snapshot.metadata.clear();
@@ -988,7 +970,7 @@
         parentRoundedCorner.radius.y *= t.getScaleY();
     }
 
-    FloatRect layerCropRect = snapshot.croppedBufferSize.toFloatRect();
+    FloatRect layerCropRect = snapshot.croppedBufferSize;
     const vec2 radius(requested.cornerRadius, requested.cornerRadius);
     RoundedCornerState layerSettings(layerCropRect, radius);
     const bool layerSettingsValid = layerSettings.hasRoundedCorners() && !layerCropRect.isEmpty();
@@ -1079,7 +1061,7 @@
             requested.externalTexture ? snapshot.bufferSize.toFloatRect() : parentBounds;
     snapshot.geomLayerCrop = parentBounds;
     if (!requested.crop.isEmpty()) {
-        snapshot.geomLayerCrop = snapshot.geomLayerCrop.intersect(requested.crop.toFloatRect());
+        snapshot.geomLayerCrop = snapshot.geomLayerCrop.intersect(requested.crop);
     }
     snapshot.geomLayerBounds = snapshot.geomLayerBounds.intersect(snapshot.geomLayerCrop);
     snapshot.transformedBounds = snapshot.geomLayerTransform.transform(snapshot.geomLayerBounds);
@@ -1090,10 +1072,10 @@
             snapshot.geomLayerTransform.transform(geomLayerBoundsWithoutTransparentRegion);
     snapshot.parentTransform = parentSnapshot.geomLayerTransform;
 
-    // Subtract the transparent region and snap to the bounds
-    const Rect bounds =
-            RequestedLayerState::reduce(snapshot.croppedBufferSize, requested.transparentRegion);
     if (requested.potentialCursor) {
+        // Subtract the transparent region and snap to the bounds
+        const Rect bounds = RequestedLayerState::reduce(Rect(snapshot.croppedBufferSize),
+                                                        requested.transparentRegion);
         snapshot.cursorFrame = snapshot.geomLayerTransform.transform(bounds);
     }
 }
@@ -1162,7 +1144,7 @@
     }
 
     updateVisibility(snapshot, snapshot.isVisible);
-    if (!needsInputInfo(snapshot, requested)) {
+    if (!requested.needsInputInfo()) {
         return;
     }
 
@@ -1172,7 +1154,7 @@
     bool noValidDisplay = !displayInfoOpt.has_value();
     auto displayInfo = displayInfoOpt.value_or(sDefaultInfo);
 
-    if (!requested.windowInfoHandle) {
+    if (!requested.hasInputInfo()) {
         snapshot.inputInfo.inputConfig = InputConfig::NO_INPUT_CHANNEL;
     }
     fillInputFrameInfo(snapshot.inputInfo, displayInfo.transform, snapshot);
@@ -1210,7 +1192,8 @@
         snapshot.inputInfo.inputConfig |= InputConfig::TRUSTED_OVERLAY;
     }
 
-    snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize();
+    snapshot.inputInfo.contentSize = {snapshot.croppedBufferSize.getHeight(),
+                                      snapshot.croppedBufferSize.getWidth()};
 
     // If the layer is a clone, we need to crop the input region to cloned root to prevent
     // touches from going outside the cloned area.
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
index 17d2610..1eff9d6 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -62,6 +62,8 @@
     metadata.merge(args.metadata);
     changes |= RequestedLayerState::Changes::Metadata;
     handleAlive = true;
+    // TODO: b/305254099 remove once we don't pass invisible windows to input
+    windowInfoHandle = nullptr;
     if (parentId != UNASSIGNED_LAYER_ID) {
         canBeRoot = false;
     }
@@ -94,7 +96,7 @@
     LLOGV(layerId, "Created %s flags=%d", getDebugString().c_str(), flags);
     color.a = 1.0f;
 
-    crop.makeInvalid();
+    crop = {0, 0, -1, -1};
     z = 0;
     layerStack = ui::DEFAULT_LAYER_STACK;
     transformToDisplayInverse = false;
@@ -471,10 +473,10 @@
     return Rect(0, 0, static_cast<int32_t>(bufWidth), static_cast<int32_t>(bufHeight));
 }
 
-Rect RequestedLayerState::getCroppedBufferSize(const Rect& bufferSize) const {
-    Rect size = bufferSize;
+FloatRect RequestedLayerState::getCroppedBufferSize(const Rect& bufferSize) const {
+    FloatRect size = bufferSize.toFloatRect();
     if (!crop.isEmpty() && size.isValid()) {
-        size.intersect(crop, &size);
+        size = size.intersect(crop);
     } else if (!crop.isEmpty()) {
         size = crop;
     }
@@ -553,6 +555,24 @@
             windowInfo->inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL);
 }
 
+bool RequestedLayerState::needsInputInfo() const {
+    if (potentialCursor) {
+        return false;
+    }
+
+    if ((sidebandStream != nullptr) || (externalTexture != nullptr)) {
+        return true;
+    }
+
+    if (!windowInfoHandle) {
+        return false;
+    }
+
+    const auto windowInfo = windowInfoHandle->getInfo();
+    return windowInfo->token != nullptr ||
+            windowInfo->inputConfig.test(gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL);
+}
+
 bool RequestedLayerState::hasBlur() const {
     return backgroundBlurRadius > 0 || blurRegions.size() > 0;
 }
diff --git a/services/surfaceflinger/FrontEnd/RequestedLayerState.h b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
index 48b9640..3220e86 100644
--- a/services/surfaceflinger/FrontEnd/RequestedLayerState.h
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -79,7 +79,7 @@
     bool isHiddenByPolicy() const;
     half4 getColor() const;
     Rect getBufferSize(uint32_t displayRotationFlags) const;
-    Rect getCroppedBufferSize(const Rect& bufferSize) const;
+    FloatRect getCroppedBufferSize(const Rect& bufferSize) const;
     Rect getBufferCrop() const;
     std::string getDebugString() const;
     std::string getDebugStringShort() const;
@@ -87,6 +87,7 @@
     aidl::android::hardware::graphics::composer3::Composition getCompositionType() const;
     bool hasValidRelativeParent() const;
     bool hasInputInfo() const;
+    bool needsInputInfo() const;
     bool hasBlur() const;
     bool hasFrameUpdate() const;
     bool hasReadyFrame() const;
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index c8bb068..9fdcbd0 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -138,7 +138,7 @@
                 args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))) {
     ALOGV("Creating Layer %s", getDebugName());
 
-    mDrawingState.crop.makeInvalid();
+    mDrawingState.crop = {0, 0, -1, -1};
     mDrawingState.sequence = 0;
     mDrawingState.transform.set(0, 0);
     mDrawingState.frameNumber = 0;
@@ -316,7 +316,7 @@
 
 Rect Layer::getCroppedBufferSize(const State& s) const {
     Rect size = getBufferSize(s);
-    Rect crop = getCrop(s);
+    Rect crop = Rect(getCrop(s));
     if (!crop.isEmpty() && size.isValid()) {
         size.intersect(crop, &size);
     } else if (!crop.isEmpty()) {
@@ -355,26 +355,6 @@
 // transaction
 // ----------------------------------------------------------------------------
 
-uint32_t Layer::doTransaction(uint32_t flags) {
-    SFTRACE_CALL();
-
-    const State& s(getDrawingState());
-
-    if (s.sequence != mLastCommittedTxSequence) {
-        // invalidate and recompute the visible regions if needed
-        mLastCommittedTxSequence = s.sequence;
-        flags |= eVisibleRegion;
-    }
-
-    if (!mPotentialCursor && (flags & Layer::eVisibleRegion)) {
-        mFlinger->mUpdateInputInfo = true;
-    }
-
-    commitTransaction();
-
-    return flags;
-}
-
 void Layer::commitTransaction() {
     // Set the present state for all bufferlessSurfaceFramesTX to Presented. The
     // bufferSurfaceFrameTX will be presented in latchBuffer.
@@ -389,17 +369,11 @@
     mDrawingState.bufferlessSurfaceFramesTX.clear();
 }
 
-uint32_t Layer::clearTransactionFlags(uint32_t mask) {
-    const auto flags = mTransactionFlags & mask;
-    mTransactionFlags &= ~mask;
-    return flags;
-}
-
 void Layer::setTransactionFlags(uint32_t mask) {
     mTransactionFlags |= mask;
 }
 
-bool Layer::setCrop(const Rect& crop) {
+bool Layer::setCrop(const FloatRect& crop) {
     if (mDrawingState.crop == crop) return false;
     mDrawingState.sequence++;
     mDrawingState.crop = crop;
@@ -867,16 +841,6 @@
     return true;
 }
 
-bool Layer::setBufferCrop(const Rect& bufferCrop) {
-    if (mDrawingState.bufferCrop == bufferCrop) return false;
-
-    mDrawingState.sequence++;
-    mDrawingState.bufferCrop = bufferCrop;
-
-    setTransactionFlags(eTransactionNeeded);
-    return true;
-}
-
 void Layer::releasePreviousBuffer() {
     mReleasePreviousBuffer = true;
     if (!mBufferInfo.mBuffer ||
@@ -1491,10 +1455,6 @@
     mBufferInfo.mFrameLatencyNeeded = false;
 }
 
-bool Layer::willReleaseBufferOnLatch() const {
-    return !mDrawingState.buffer && mBufferInfo.mBuffer;
-}
-
 bool Layer::latchBufferImpl(bool& recomputeVisibleRegions, nsecs_t latchTime, bool bgColorOnly) {
     SFTRACE_FORMAT_INSTANT("latchBuffer %s - %" PRIu64, getDebugName(),
                            getDrawingState().frameNumber);
@@ -1555,34 +1515,10 @@
     return true;
 }
 
-bool Layer::isProtected() const {
-    return (mBufferInfo.mBuffer != nullptr) &&
-            (mBufferInfo.mBuffer->getUsage() & GRALLOC_USAGE_PROTECTED);
-}
-
 bool Layer::getTransformToDisplayInverse() const {
     return mBufferInfo.mTransformToDisplayInverse;
 }
 
-Rect Layer::getBufferCrop() const {
-    // this is the crop rectangle that applies to the buffer
-    // itself (as opposed to the window)
-    if (!mBufferInfo.mCrop.isEmpty()) {
-        // if the buffer crop is defined, we use that
-        return mBufferInfo.mCrop;
-    } else if (mBufferInfo.mBuffer != nullptr) {
-        // otherwise we use the whole buffer
-        return mBufferInfo.mBuffer->getBounds();
-    } else {
-        // if we don't have a buffer yet, we use an empty/invalid crop
-        return Rect();
-    }
-}
-
-uint32_t Layer::getBufferTransform() const {
-    return mBufferInfo.mTransform;
-}
-
 ui::Dataspace Layer::translateDataspace(ui::Dataspace dataspace) {
     ui::Dataspace updatedDataspace = dataspace;
     // translate legacy dataspaces to modern dataspaces
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index 9caa20c..57093ae 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -88,12 +88,6 @@
     // Windows that are not in focus, but voted for a specific mode ID.
     static constexpr int32_t PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
 
-    enum { // flags for doTransaction()
-        eDontUpdateGeometryState = 0x00000001,
-        eVisibleRegion = 0x00000002,
-        eInputInfoChanged = 0x00000004
-    };
-
     using FrameRate = scheduler::LayerInfo::FrameRate;
     using FrameRateCompatibility = scheduler::FrameRateCompatibility;
     using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
@@ -101,7 +95,7 @@
     struct State {
         int32_t sequence; // changes when visible regions can change
         // Crop is expressed in layer space coordinate.
-        Rect crop;
+        FloatRect crop;
         LayerMetadata metadata;
 
         ui::Dataspace dataspace;
@@ -171,9 +165,6 @@
     static bool isLayerFocusedBasedOnPriority(int32_t priority);
     static void miniDumpHeader(std::string& result);
 
-    // Provide unique string for each class type in the Layer hierarchy
-    const char* getType() const { return "Layer"; }
-
     // This second set of geometry attributes are controlled by
     // setGeometryAppliesWithResize, and their default mode is to be
     // immediate. If setGeometryAppliesWithResize is specified
@@ -181,7 +172,7 @@
     // be delayed until the resize completes.
 
     // Buffer space
-    bool setCrop(const Rect& crop);
+    bool setCrop(const FloatRect& crop);
 
     bool setTransform(uint32_t /*transform*/);
     bool setTransformToDisplayInverse(bool /*transformToDisplayInverse*/);
@@ -200,7 +191,6 @@
                                           bool willPresent);
 
     sp<LayerFE> getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
-    sp<LayerFE> getOrCreateCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
 
     // If we have received a new buffer this frame, we will pass its surface
     // damage down to hardware composer. Otherwise, we must send a region with
@@ -208,18 +198,7 @@
     Region getVisibleRegion(const DisplayDevice*) const;
     void updateLastLatchTime(nsecs_t latchtime);
 
-    /*
-     * isProtected - true if the layer may contain protected contents in the
-     * GRALLOC_USAGE_PROTECTED sense.
-     */
-    bool isProtected() const;
-    /*
-     * usesSourceCrop - true if content should use a source crop
-     */
-    bool usesSourceCrop() const { return hasBufferOrSidebandStream(); }
-
-    Rect getCrop(const Layer::State& s) const { return s.crop; }
-    bool needsFiltering(const DisplayDevice*) const;
+    Rect getCrop(const Layer::State& s) const { return Rect(s.crop); }
 
     // from graphics API
     static ui::Dataspace translateDataspace(ui::Dataspace dataspace);
@@ -242,23 +221,6 @@
     bool latchBufferImpl(bool& /*recomputeVisibleRegions*/, nsecs_t /*latchTime*/,
                          bool bgColorOnly);
 
-    /*
-     * Returns true if the currently presented buffer will be released when this layer state
-     * is latched. This will return false if there is no buffer currently presented.
-     */
-    bool willReleaseBufferOnLatch() const;
-
-    /*
-     * returns the rectangle that crops the content of the layer and scales it
-     * to the layer's size.
-     */
-    Rect getBufferCrop() const;
-
-    /*
-     * Returns the transform applied to the buffer.
-     */
-    uint32_t getBufferTransform() const;
-
     sp<GraphicBuffer> getBuffer() const;
     /**
      * Returns active buffer size in the correct orientation. Buffer size is determined by undoing
@@ -313,8 +275,6 @@
 
     const char* getDebugName() const;
 
-    uint32_t getTransactionFlags() const { return mTransactionFlags; }
-
     static bool computeTrustedPresentationState(const FloatRect& bounds,
                                                 const FloatRect& sourceBounds,
                                                 const Region& coveredRegion,
@@ -332,9 +292,6 @@
     // Sets the masked bits.
     void setTransactionFlags(uint32_t mask);
 
-    // Clears and returns the masked bits.
-    uint32_t clearTransactionFlags(uint32_t mask);
-
     int32_t getSequence() const { return sequence; }
 
     // For tracing.
@@ -348,14 +305,6 @@
     void writeCompositionStateToProto(perfetto::protos::LayerProto* layerProto,
                                       ui::LayerStack layerStack);
 
-    gui::WindowInfo::Type getWindowType() const { return mWindowType; }
-
-    /*
-     * doTransaction - process the transaction. This is a good place to figure
-     * out which attributes of the surface have changed.
-     */
-    uint32_t doTransaction(uint32_t transactionFlags);
-
     inline const State& getDrawingState() const { return mDrawingState; }
     inline State& getDrawingState() { return mDrawingState; }
 
@@ -366,13 +315,6 @@
     void getFrameStats(FrameStats* outStats) const;
     void onDisconnect();
 
-    ui::Transform getTransform() const;
-
-    half4 getColor() const;
-    int32_t getBackgroundBlurRadius() const;
-    bool drawShadows() const { return mEffectiveShadowRadius > 0.f; };
-
-    bool isHandleAlive() const { return mHandleAlive; }
     bool onHandleDestroyed() { return mHandleAlive = false; }
 
     /**
@@ -383,7 +325,6 @@
      */
     Rect getCroppedBufferSize(const Layer::State& s) const;
 
-    void setFrameTimelineInfoForBuffer(const FrameTimelineInfo& /*info*/) {}
     void setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info, nsecs_t postTime,
                                                    gui::GameMode gameMode);
     void setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInfo& info,
@@ -412,14 +353,9 @@
     // this to be called once.
     sp<IBinder> getHandle();
     const std::string& getName() const { return mName; }
-    void setInputInfo(const gui::WindowInfo& info);
 
     virtual uid_t getOwnerUid() const { return mOwnerUid; }
 
-    pid_t getOwnerPid() { return mOwnerPid; }
-
-    int32_t getOwnerAppId() { return mOwnerAppId; }
-
     // Used to check if mUsedVsyncIdForRefreshRateSelection should be expired when it stop updating.
     nsecs_t mMaxTimeForUseVsyncId = 0;
     // True when DrawState.useVsyncIdForRefreshRateSelection previously set to true during updating
@@ -431,9 +367,6 @@
     // the same.
     const int32_t sequence;
 
-    bool mPendingHWCDestroy{false};
-
-    bool setBufferCrop(const Rect& /* bufferCrop */);
     // See mPendingBufferTransactions
     void decrementPendingBufferCount();
     std::atomic<int32_t>* getPendingBufferCounter() { return &mPendingBufferTransactions; }
@@ -513,10 +446,6 @@
     int64_t mEnteredTrustedPresentationStateTime = -1;
 
     uint32_t mTransactionFlags{0};
-    // Updated in doTransaction, used to track the last sequence number we
-    // committed. Currently this is really only used for updating visible
-    // regions.
-    int32_t mLastCommittedTxSequence = -1;
 
     // Timestamp history for UIAutomation. Thread safe.
     FrameTracker mFrameTracker;
@@ -632,7 +561,7 @@
     // You can understand the trace this way:
     //     - If the integer increases, a buffer arrived at the server.
     //     - If the integer decreases in latchBuffer, that buffer was latched
-    //     - If the integer decreases in setBuffer or doTransaction, a buffer was dropped
+    //     - If the integer decreases in setBuffer, a buffer was dropped
     std::atomic<int32_t> mPendingBufferTransactions{0};
 
     // Contains requested position and matrix updates. This will be applied if the client does
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index 5eea45b..a0fbab0 100644
--- a/services/surfaceflinger/LayerProtoHelper.cpp
+++ b/services/surfaceflinger/LayerProtoHelper.cpp
@@ -106,6 +106,13 @@
     outRect.right = proto.right();
 }
 
+void LayerProtoHelper::readFromProto(const perfetto::protos::RectProto& proto, FloatRect& outRect) {
+    outRect.left = proto.left();
+    outRect.top = proto.top();
+    outRect.bottom = proto.bottom();
+    outRect.right = proto.right();
+}
+
 void LayerProtoHelper::writeToProto(
         const FloatRect& rect,
         std::function<perfetto::protos::FloatRectProto*()> getFloatRectProto) {
@@ -427,7 +434,7 @@
                                        layerInfo->mutable_color_transform());
     }
 
-    LayerProtoHelper::writeToProto(snapshot.croppedBufferSize.toFloatRect(),
+    LayerProtoHelper::writeToProto(snapshot.croppedBufferSize,
                                    [&]() { return layerInfo->mutable_source_bounds(); });
     LayerProtoHelper::writeToProto(snapshot.transformedBounds,
                                    [&]() { return layerInfo->mutable_screen_bounds(); });
@@ -455,7 +462,7 @@
         return layerInfo->mutable_requested_position();
     });
 
-    LayerProtoHelper::writeToProto(requestedState.crop,
+    LayerProtoHelper::writeToProto(Rect(requestedState.crop),
                                    [&]() { return layerInfo->mutable_crop(); });
 
     layerInfo->set_is_opaque(snapshot.contentOpaque);
diff --git a/services/surfaceflinger/LayerProtoHelper.h b/services/surfaceflinger/LayerProtoHelper.h
index 41ea684..3ca553a 100644
--- a/services/surfaceflinger/LayerProtoHelper.h
+++ b/services/surfaceflinger/LayerProtoHelper.h
@@ -44,6 +44,7 @@
                              std::function<perfetto::protos::RectProto*()> getRectProto);
     static void writeToProto(const Rect& rect, perfetto::protos::RectProto* rectProto);
     static void readFromProto(const perfetto::protos::RectProto& proto, Rect& outRect);
+    static void readFromProto(const perfetto::protos::RectProto& proto, FloatRect& outRect);
     static void writeToProto(const FloatRect& rect,
                              std::function<perfetto::protos::FloatRectProto*()> getFloatRectProto);
     static void writeToProto(const Region& region,
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index ffc1dd7..fa0ecee 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -5,6 +5,8 @@
 domlaskowski@google.com
 jreck@google.com
 lpy@google.com
+mattbuckley@google.com
+melodymhsu@google.com
 pdwilliams@google.com
 racarr@google.com
 ramindani@google.com
@@ -12,3 +14,4 @@
 sallyqi@google.com
 scroggo@google.com
 vishnun@google.com
+xwxw@google.com
diff --git a/services/surfaceflinger/PowerAdvisor/OWNERS b/services/surfaceflinger/PowerAdvisor/OWNERS
new file mode 100644
index 0000000..9f40e27
--- /dev/null
+++ b/services/surfaceflinger/PowerAdvisor/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:/ADPF_OWNERS
\ No newline at end of file
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 7712d38..06c2f26 100644
--- a/services/surfaceflinger/RegionSamplingThread.cpp
+++ b/services/surfaceflinger/RegionSamplingThread.cpp
@@ -346,6 +346,7 @@
     constexpr bool kRegionSampling = true;
     constexpr bool kGrayscale = false;
     constexpr bool kIsProtected = false;
+    constexpr bool kAttachGainmap = false;
 
     SurfaceFlinger::RenderAreaBuilderVariant
             renderAreaBuilder(std::in_place_type<DisplayRenderAreaBuilder>, sampledBounds,
@@ -358,15 +359,15 @@
         std::vector<sp<LayerFE>> layerFEs;
         auto displayState = mFlinger.getSnapshotsFromMainThread(renderAreaBuilder,
                                                                 getLayerSnapshotsFn, layerFEs);
-        fenceResult =
-                mFlinger.captureScreenshot(renderAreaBuilder, buffer, kRegionSampling, kGrayscale,
-                                           kIsProtected, nullptr, displayState, layerFEs)
-                        .get();
+        fenceResult = mFlinger.captureScreenshot(renderAreaBuilder, buffer, kRegionSampling,
+                                                 kGrayscale, kIsProtected, kAttachGainmap, nullptr,
+                                                 displayState, layerFEs)
+                              .get();
     } else {
-        fenceResult =
-                mFlinger.captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn, buffer,
-                                                 kRegionSampling, kGrayscale, kIsProtected, nullptr)
-                        .get();
+        fenceResult = mFlinger.captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn,
+                                                       buffer, kRegionSampling, kGrayscale,
+                                                       kIsProtected, kAttachGainmap, nullptr)
+                              .get();
     }
     if (fenceResult.ok()) {
         fenceResult.value()->waitForever(LOG_TAG);
diff --git a/services/surfaceflinger/Scheduler/EventThread.cpp b/services/surfaceflinger/Scheduler/EventThread.cpp
index 218c56e..e385f18 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -426,8 +426,8 @@
 
     LOG_FATAL_IF(!mVSyncState);
     mVsyncTracer = (mVsyncTracer + 1) % 2;
-    mPendingEvents.push_back(makeVSync(mVSyncState->displayId, wakeupTime, ++mVSyncState->count,
-                                       vsyncTime, readyTime));
+    mPendingEvents.push_back(makeVSync(mVsyncSchedule->getPhysicalDisplayId(), wakeupTime,
+                                       ++mVSyncState->count, vsyncTime, readyTime));
     mCondition.notify_all();
 }
 
@@ -486,9 +486,9 @@
             if (event->header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG) {
                 if (event->hotplug.connectionError == 0) {
                     if (event->hotplug.connected && !mVSyncState) {
-                        mVSyncState.emplace(event->header.displayId);
-                    } else if (!event->hotplug.connected && mVSyncState &&
-                               mVSyncState->displayId == event->header.displayId) {
+                        mVSyncState.emplace();
+                    } else if (!event->hotplug.connected &&
+                               mVsyncSchedule->getPhysicalDisplayId() == event->header.displayId) {
                         mVSyncState.reset();
                     }
                 } else {
@@ -559,7 +559,7 @@
                 const auto now = systemTime(SYSTEM_TIME_MONOTONIC);
                 const auto deadlineTimestamp = now + timeout.count();
                 const auto expectedVSyncTime = deadlineTimestamp + timeout.count();
-                mPendingEvents.push_back(makeVSync(mVSyncState->displayId, now,
+                mPendingEvents.push_back(makeVSync(mVsyncSchedule->getPhysicalDisplayId(), now,
                                                    ++mVSyncState->count, expectedVSyncTime,
                                                    deadlineTimestamp));
             }
@@ -739,7 +739,7 @@
     StringAppendF(&result, "%s: state=%s VSyncState=", mThreadName, toCString(mState));
     if (mVSyncState) {
         StringAppendF(&result, "{displayId=%s, count=%u%s}\n",
-                      to_string(mVSyncState->displayId).c_str(), mVSyncState->count,
+                      to_string(mVsyncSchedule->getPhysicalDisplayId()).c_str(), mVSyncState->count,
                       mVSyncState->synthetic ? ", synthetic" : "");
     } else {
         StringAppendF(&result, "none\n");
diff --git a/services/surfaceflinger/Scheduler/EventThread.h b/services/surfaceflinger/Scheduler/EventThread.h
index bbe4f9d..c3c7eb0 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -235,10 +235,6 @@
 
     // VSYNC state of connected display.
     struct VSyncState {
-        explicit VSyncState(PhysicalDisplayId displayId) : displayId(displayId) {}
-
-        const PhysicalDisplayId displayId;
-
         // Number of VSYNC events since display was connected.
         uint32_t count = 0;
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index be00079..5e13154 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -38,7 +38,6 @@
 #include <FrameTimeline/FrameTimeline.h>
 #include <scheduler/interface/ICompositor.h>
 
-#include <algorithm>
 #include <cinttypes>
 #include <cstdint>
 #include <functional>
@@ -46,16 +45,15 @@
 #include <numeric>
 
 #include <common/FlagManager.h>
-#include "../Layer.h"
 #include "EventThread.h"
 #include "FrameRateOverrideMappings.h"
 #include "FrontEnd/LayerHandle.h"
+#include "Layer.h"
 #include "OneShotTimer.h"
 #include "RefreshRateStats.h"
 #include "SurfaceFlingerFactory.h"
 #include "SurfaceFlingerProperties.h"
 #include "TimeStats/TimeStats.h"
-#include "VSyncTracker.h"
 #include "VsyncConfiguration.h"
 #include "VsyncController.h"
 #include "VsyncSchedule.h"
@@ -361,10 +359,8 @@
 
     if (cycle == Cycle::Render) {
         mRenderEventThread = std::move(eventThread);
-        mRenderEventConnection = mRenderEventThread->createEventConnection();
     } else {
         mLastCompositeEventThread = std::move(eventThread);
-        mLastCompositeEventConnection = mLastCompositeEventThread->createEventConnection();
     }
 }
 
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index 1367ec3..c88b563 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -145,10 +145,6 @@
             Cycle, EventRegistrationFlags eventRegistration = {},
             const sp<IBinder>& layerHandle = nullptr) EXCLUDES(mChoreographerLock);
 
-    const sp<EventThreadConnection>& getEventConnection(Cycle cycle) const {
-        return cycle == Cycle::Render ? mRenderEventConnection : mLastCompositeEventConnection;
-    }
-
     enum class Hotplug { Connected, Disconnected };
     void dispatchHotplug(PhysicalDisplayId, Hotplug);
 
@@ -467,10 +463,7 @@
     void onExpectedPresentTimePosted(TimePoint expectedPresentTime) override EXCLUDES(mDisplayLock);
 
     std::unique_ptr<EventThread> mRenderEventThread;
-    sp<EventThreadConnection> mRenderEventConnection;
-
     std::unique_ptr<EventThread> mLastCompositeEventThread;
-    sp<EventThreadConnection> mLastCompositeEventConnection;
 
     std::atomic<nsecs_t> mLastResyncTime = 0;
 
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.cpp b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
index fa377e9..3c5f68c 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.cpp
@@ -21,7 +21,6 @@
 
 #include "VsyncModulator.h"
 
-#include <android-base/properties.h>
 #include <common/trace.h>
 #include <log/log.h>
 
@@ -37,8 +36,7 @@
 
 VsyncModulator::VsyncModulator(const VsyncConfigSet& config, Now now)
       : mVsyncConfigSet(config),
-        mNow(now),
-        mTraceDetailedInfo(base::GetBoolProperty("debug.sf.vsync_trace_detailed_info", false)) {}
+        mNow(now) {}
 
 VsyncConfig VsyncModulator::setVsyncConfigSet(const VsyncConfigSet& config) {
     std::lock_guard<std::mutex> lock(mMutex);
@@ -71,10 +69,6 @@
             break;
     }
 
-    if (mTraceDetailedInfo) {
-        SFTRACE_INT("mEarlyWakeup", static_cast<int>(mEarlyWakeupRequests.size()));
-    }
-
     if (mEarlyWakeupRequests.empty() && schedule == Schedule::EarlyEnd) {
         mEarlyTransactionFrames = MIN_EARLY_TRANSACTION_FRAMES;
         mEarlyTransactionStartTime = mNow();
@@ -167,15 +161,19 @@
     const VsyncConfig& offsets = getNextVsyncConfig();
     mVsyncConfig = offsets;
 
-    if (mTraceDetailedInfo) {
-        const bool isEarly = &offsets == &mVsyncConfigSet.early;
-        const bool isEarlyGpu = &offsets == &mVsyncConfigSet.earlyGpu;
-        const bool isLate = &offsets == &mVsyncConfigSet.late;
+    // Trace config type
+    SFTRACE_INT("Vsync-Early",  &mVsyncConfig == &mVsyncConfigSet.early);
+    SFTRACE_INT("Vsync-EarlyGpu", &mVsyncConfig == &mVsyncConfigSet.earlyGpu);
+    SFTRACE_INT("Vsync-Late", &mVsyncConfig == &mVsyncConfigSet.late);
 
-        SFTRACE_INT("Vsync-EarlyOffsetsOn", isEarly);
-        SFTRACE_INT("Vsync-EarlyGpuOffsetsOn", isEarlyGpu);
-        SFTRACE_INT("Vsync-LateOffsetsOn", isLate);
-    }
+    // Trace early vsync conditions
+    SFTRACE_INT("EarlyWakeupRequests",
+                                 static_cast<int>(mEarlyWakeupRequests.size()));
+    SFTRACE_INT("EarlyTransactionFrames", mEarlyTransactionFrames);
+    SFTRACE_INT("RefreshRateChangePending", mRefreshRateChangePending);
+
+    // Trace early gpu conditions
+    SFTRACE_INT("EarlyGpuFrames", mEarlyGpuFrames);
 
     return offsets;
 }
@@ -183,7 +181,6 @@
 void VsyncModulator::binderDied(const wp<IBinder>& who) {
     std::lock_guard<std::mutex> lock(mMutex);
     mEarlyWakeupRequests.erase(who);
-
     static_cast<void>(updateVsyncConfigLocked());
 }
 
diff --git a/services/surfaceflinger/Scheduler/VsyncModulator.h b/services/surfaceflinger/Scheduler/VsyncModulator.h
index be0d334..d0a7935 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.h
@@ -105,7 +105,6 @@
     std::atomic<TimePoint> mLastTransactionCommitTime = TimePoint();
 
     const Now mNow;
-    const bool mTraceDetailedInfo;
 };
 
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.h b/services/surfaceflinger/Scheduler/VsyncSchedule.h
index 881d678..e63cbb2 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -112,6 +112,8 @@
 
     bool getPendingHardwareVsyncState() const REQUIRES(kMainThreadContext);
 
+    PhysicalDisplayId getPhysicalDisplayId() const { return mId; }
+
 protected:
     using ControllerPtr = std::unique_ptr<VsyncController>;
 
diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp
index 8bb72b8..41a9a1b 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.cpp
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -93,6 +93,12 @@
     if (mEnableLocalTonemapping) {
         clientCompositionDisplay.tonemapStrategy =
                 renderengine::DisplaySettings::TonemapStrategy::Local;
+        if (static_cast<ui::PixelFormat>(buffer->getPixelFormat()) == ui::PixelFormat::RGBA_FP16) {
+            clientCompositionDisplay.targetHdrSdrRatio =
+                    getState().displayBrightnessNits / getState().sdrWhitePointNits;
+        } else {
+            clientCompositionDisplay.targetHdrSdrRatio = 1.f;
+        }
     }
 
     return clientCompositionDisplay;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index aeca824..8d16b9f 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -946,16 +946,20 @@
                         }));
                     }));
 
-    mLayerTracing.setTakeLayersSnapshotProtoFunction([&](uint32_t traceFlags) {
-        auto snapshot = perfetto::protos::LayersSnapshotProto{};
-        mScheduler
-                ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
-                    snapshot = takeLayersSnapshotProto(traceFlags, TimePoint::now(),
-                                                       mLastCommittedVsyncId, true);
-                })
-                .wait();
-        return snapshot;
-    });
+    mLayerTracing.setTakeLayersSnapshotProtoFunction(
+            [&](uint32_t traceFlags,
+                const LayerTracing::OnLayersSnapshotCallback& onLayersSnapshot) {
+                // Do not wait the future to avoid deadlocks
+                // between main and Perfetto threads (b/313130597)
+                static_cast<void>(mScheduler->schedule(
+                        [&, traceFlags, onLayersSnapshot]() FTL_FAKE_GUARD(mStateLock)
+                                FTL_FAKE_GUARD(kMainThreadContext) {
+                                    auto snapshot =
+                                            takeLayersSnapshotProto(traceFlags, TimePoint::now(),
+                                                                    mLastCommittedVsyncId, true);
+                                    onLayersSnapshot(std::move(snapshot));
+                                }));
+            });
 
     // Commit secondary display(s).
     processDisplayChangesLocked();
@@ -1003,6 +1007,8 @@
             // which we maintain for backwards compatibility.
             config.cacheUltraHDR =
                     base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false);
+            config.cacheEdgeExtension =
+                    base::GetBoolProperty("debug.sf.edge_extension_shader"s, true);
             return getRenderEngine().primeCache(config);
         });
 
@@ -3817,7 +3823,8 @@
         mDisplays.erase(displayToken);
 
         if (const auto& physical = currentState.physical) {
-            getHwComposer().allocatePhysicalDisplay(physical->hwcDisplayId, physical->id);
+            getHwComposer().allocatePhysicalDisplay(physical->hwcDisplayId, physical->id,
+                                                    /*physicalSize=*/std::nullopt);
         }
 
         processDisplayAdded(displayToken, currentState);
@@ -4716,6 +4723,7 @@
     for (auto& state : states) {
         resolvedStates.emplace_back(std::move(state));
         auto& resolvedState = resolvedStates.back();
+        resolvedState.layerId = LayerHandle::getLayerId(resolvedState.state.surface);
         if (resolvedState.state.hasBufferChanges() && resolvedState.state.hasValidBuffer() &&
             resolvedState.state.surface) {
             sp<Layer> layer = LayerHandle::getLayer(resolvedState.state.surface);
@@ -4727,9 +4735,8 @@
             if (resolvedState.externalTexture) {
                 resolvedState.state.bufferData->buffer = resolvedState.externalTexture->getBuffer();
             }
-            mBufferCountTracker.increment(resolvedState.state.surface->localBinder());
+            mBufferCountTracker.increment(resolvedState.layerId);
         }
-        resolvedState.layerId = LayerHandle::getLayerId(resolvedState.state.surface);
         if (resolvedState.state.what & layer_state_t::eReparent) {
             resolvedState.parentId =
                     getLayerIdFromSurfaceControl(resolvedState.state.parentSurfaceControlForChild);
@@ -5172,7 +5179,7 @@
             std::atomic<int32_t>* pendingBufferCounter = layer->getPendingBufferCounter();
             if (pendingBufferCounter) {
                 std::string counterName = layer->getPendingBufferCounterName();
-                mBufferCountTracker.add(outResult.handle->localBinder(), counterName,
+                mBufferCountTracker.add(LayerHandle::getLayerId(outResult.handle), counterName,
                                         pendingBufferCounter);
             }
         } break;
@@ -5220,7 +5227,7 @@
     return NO_ERROR;
 }
 
-void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId) {
+void SurfaceFlinger::onHandleDestroyed(sp<Layer>& layer, uint32_t layerId) {
     {
         // Used to remove stalled transactions which uses an internal lock.
         ftl::FakeGuard guard(kMainThreadContext);
@@ -5233,7 +5240,7 @@
 
     Mutex::Autolock stateLock(mStateLock);
     layer->onHandleDestroyed();
-    mBufferCountTracker.remove(handle);
+    mBufferCountTracker.remove(layerId);
     layer.clear();
     setTransactionFlags(eTransactionFlushNeeded | eTransactionNeeded);
 }
@@ -6963,7 +6970,8 @@
                                                  displayWeak, options),
                         getLayerSnapshotsFn, reqSize,
                         static_cast<ui::PixelFormat>(captureArgs.pixelFormat),
-                        captureArgs.allowProtected, captureArgs.grayscale, captureListener);
+                        captureArgs.allowProtected, captureArgs.grayscale,
+                        captureArgs.attachGainmap, captureListener);
 }
 
 void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args,
@@ -7020,7 +7028,7 @@
                                                  static_cast<ui::Dataspace>(args.dataspace),
                                                  displayWeak, options),
                         getLayerSnapshotsFn, size, static_cast<ui::PixelFormat>(args.pixelFormat),
-                        kAllowProtected, kGrayscale, captureListener);
+                        kAllowProtected, kGrayscale, args.attachGainmap, captureListener);
 }
 
 ScreenCaptureResults SurfaceFlinger::captureLayersSync(const LayerCaptureArgs& args) {
@@ -7131,7 +7139,8 @@
                                                  options),
                         getLayerSnapshotsFn, reqSize,
                         static_cast<ui::PixelFormat>(captureArgs.pixelFormat),
-                        captureArgs.allowProtected, captureArgs.grayscale, captureListener);
+                        captureArgs.allowProtected, captureArgs.grayscale,
+                        captureArgs.attachGainmap, captureListener);
 }
 
 // Creates a Future release fence for a layer and keeps track of it in a list to
@@ -7182,7 +7191,7 @@
 void SurfaceFlinger::captureScreenCommon(RenderAreaBuilderVariant renderAreaBuilder,
                                          GetLayerSnapshotsFunction getLayerSnapshotsFn,
                                          ui::Size bufferSize, ui::PixelFormat reqPixelFormat,
-                                         bool allowProtected, bool grayscale,
+                                         bool allowProtected, bool grayscale, bool attachGainmap,
                                          const sp<IScreenCaptureListener>& captureListener) {
     SFTRACE_CALL();
 
@@ -7228,9 +7237,9 @@
                 renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
                                                      renderengine::impl::ExternalTexture::Usage::
                                                              WRITEABLE);
-        auto futureFence =
-                captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */, grayscale,
-                                  isProtected, captureListener, displayState, layerFEs);
+        auto futureFence = captureScreenshot(renderAreaBuilder, texture, false /* regionSampling */,
+                                             grayscale, isProtected, attachGainmap, captureListener,
+                                             displayState, layerFEs);
         futureFence.get();
 
     } else {
@@ -7265,7 +7274,7 @@
                                                              WRITEABLE);
         auto futureFence = captureScreenshotLegacy(renderAreaBuilder, getLayerSnapshotsFn, texture,
                                                    false /* regionSampling */, grayscale,
-                                                   isProtected, captureListener);
+                                                   isProtected, attachGainmap, captureListener);
         futureFence.get();
     }
 }
@@ -7319,7 +7328,8 @@
 ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshot(
         const RenderAreaBuilderVariant& renderAreaBuilder,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener,
+        bool grayscale, bool isProtected, bool attachGainmap,
+        const sp<IScreenCaptureListener>& captureListener,
         std::optional<OutputCompositionState>& displayState, std::vector<sp<LayerFE>>& layerFEs) {
     SFTRACE_CALL();
 
@@ -7336,19 +7346,87 @@
         }
         return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
     }
+    float displayBrightnessNits = displayState.value().displayBrightnessNits;
+    float sdrWhitePointNits = displayState.value().sdrWhitePointNits;
 
     // Empty vector needed to pass into renderScreenImpl for legacy path
     std::vector<std::pair<Layer*, sp<android::LayerFE>>> layers;
     ftl::SharedFuture<FenceResult> renderFuture =
-            renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale, isProtected,
-                             captureResults, displayState, layers, layerFEs);
+            renderScreenImpl(renderArea.get(), buffer, regionSampling, grayscale, isProtected,
+                             attachGainmap, captureResults, displayState, layers, layerFEs);
+
+    if (captureResults.capturedHdrLayers && attachGainmap &&
+        FlagManager::getInstance().true_hdr_screenshots()) {
+        sp<GraphicBuffer> hdrBuffer =
+                getFactory().createGraphicBuffer(buffer->getWidth(), buffer->getHeight(),
+                                                 HAL_PIXEL_FORMAT_RGBA_FP16, 1 /* layerCount */,
+                                                 buffer->getUsage(), "screenshot-hdr");
+        sp<GraphicBuffer> gainmapBuffer =
+                getFactory().createGraphicBuffer(buffer->getWidth(), buffer->getHeight(),
+                                                 buffer->getPixelFormat(), 1 /* layerCount */,
+                                                 buffer->getUsage(), "screenshot-gainmap");
+
+        const status_t bufferStatus = hdrBuffer->initCheck();
+        const status_t gainmapBufferStatus = gainmapBuffer->initCheck();
+
+        if (bufferStatus != OK) {
+            ALOGW("%s: Buffer failed to allocate for hdr: %d. Screenshoting SDR instead.", __func__,
+                  bufferStatus);
+        } else if (gainmapBufferStatus != OK) {
+            ALOGW("%s: Buffer failed to allocate for gainmap: %d. Screenshoting SDR instead.",
+                  __func__, gainmapBufferStatus);
+        } else {
+            captureResults.optionalGainMap = gainmapBuffer;
+            const auto hdrTexture = std::make_shared<
+                    renderengine::impl::ExternalTexture>(hdrBuffer, getRenderEngine(),
+                                                         renderengine::impl::ExternalTexture::
+                                                                 Usage::WRITEABLE);
+            const auto gainmapTexture = std::make_shared<
+                    renderengine::impl::ExternalTexture>(gainmapBuffer, getRenderEngine(),
+                                                         renderengine::impl::ExternalTexture::
+                                                                 Usage::WRITEABLE);
+            ScreenCaptureResults unusedResults;
+            ftl::SharedFuture<FenceResult> hdrRenderFuture =
+                    renderScreenImpl(renderArea.get(), hdrTexture, regionSampling, grayscale,
+                                     isProtected, attachGainmap, unusedResults, displayState,
+                                     layers, layerFEs);
+
+            renderFuture =
+                    ftl::Future(std::move(renderFuture))
+                            .then([&, hdrRenderFuture = std::move(hdrRenderFuture),
+                                   displayBrightnessNits, sdrWhitePointNits,
+                                   dataspace = captureResults.capturedDataspace, buffer, hdrTexture,
+                                   gainmapTexture](FenceResult fenceResult) -> FenceResult {
+                                if (!fenceResult.ok()) {
+                                    return fenceResult;
+                                }
+
+                                auto hdrFenceResult = hdrRenderFuture.get();
+
+                                if (!hdrFenceResult.ok()) {
+                                    return hdrFenceResult;
+                                }
+
+                                return getRenderEngine()
+                                        .drawGainmap(buffer, fenceResult.value()->get(), hdrTexture,
+                                                     hdrFenceResult.value()->get(),
+                                                     displayBrightnessNits / sdrWhitePointNits,
+                                                     static_cast<ui::Dataspace>(dataspace),
+                                                     gainmapTexture)
+                                        .get();
+                            })
+                            .share();
+        };
+    }
 
     if (captureListener) {
         // Defer blocking on renderFuture back to the Binder thread.
         return ftl::Future(std::move(renderFuture))
-                .then([captureListener, captureResults = std::move(captureResults)](
-                              FenceResult fenceResult) mutable -> FenceResult {
+                .then([captureListener, captureResults = std::move(captureResults),
+                       displayBrightnessNits,
+                       sdrWhitePointNits](FenceResult fenceResult) mutable -> FenceResult {
                     captureResults.fenceResult = std::move(fenceResult);
+                    captureResults.hdrSdrRatio = displayBrightnessNits / sdrWhitePointNits;
                     captureListener->onScreenCaptureCompleted(captureResults);
                     return base::unexpected(NO_ERROR);
                 })
@@ -7360,7 +7438,8 @@
 ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenshotLegacy(
         RenderAreaBuilderVariant renderAreaBuilder, GetLayerSnapshotsFunction getLayerSnapshotsFn,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener) {
+        bool grayscale, bool isProtected, bool attachGainmap,
+        const sp<IScreenCaptureListener>& captureListener) {
     SFTRACE_CALL();
 
     auto takeScreenshotFn = [=, this, renderAreaBuilder = std::move(renderAreaBuilder)]() REQUIRES(
@@ -7389,8 +7468,8 @@
 
         auto layerFEs = extractLayerFEs(layers);
         ftl::SharedFuture<FenceResult> renderFuture =
-                renderScreenImpl(std::move(renderArea), buffer, regionSampling, grayscale,
-                                 isProtected, captureResults, displayState, layers, layerFEs);
+                renderScreenImpl(renderArea.get(), buffer, regionSampling, grayscale, isProtected,
+                                 attachGainmap, captureResults, displayState, layers, layerFEs);
 
         if (captureListener) {
             // Defer blocking on renderFuture back to the Binder thread.
@@ -7420,10 +7499,9 @@
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
-        std::unique_ptr<const RenderArea> renderArea,
-        const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-        bool grayscale, bool isProtected, ScreenCaptureResults& captureResults,
-        std::optional<OutputCompositionState>& displayState,
+        const RenderArea* renderArea, const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+        bool regionSampling, bool grayscale, bool isProtected, bool attachGainmap,
+        ScreenCaptureResults& captureResults, std::optional<OutputCompositionState>& displayState,
         std::vector<std::pair<Layer*, sp<LayerFE>>>& layers, std::vector<sp<LayerFE>>& layerFEs) {
     SFTRACE_CALL();
 
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index e46ad64..db0e15e 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -296,7 +296,7 @@
     // Called when all clients have released all their references to
     // this layer. The layer may still be kept alive by its parents but
     // the client can no longer modify this layer directly.
-    void onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId);
+    void onHandleDestroyed(sp<Layer>& layer, uint32_t layerId);
 
     TransactionCallbackInvoker& getTransactionCallbackInvoker() {
         return mTransactionCallbackInvoker;
@@ -433,32 +433,32 @@
     // This is done to avoid lock contention with the main thread.
     class BufferCountTracker {
     public:
-        void increment(BBinder* layerHandle) {
+        void increment(uint32_t layerId) {
             std::lock_guard<std::mutex> lock(mLock);
-            auto it = mCounterByLayerHandle.find(layerHandle);
-            if (it != mCounterByLayerHandle.end()) {
+            auto it = mCounterByLayerId.find(layerId);
+            if (it != mCounterByLayerId.end()) {
                 auto [name, pendingBuffers] = it->second;
                 int32_t count = ++(*pendingBuffers);
                 SFTRACE_INT(name.c_str(), count);
             } else {
-                ALOGW("Handle not found! %p", layerHandle);
+                ALOGW("Layer ID not found! %d", layerId);
             }
         }
 
-        void add(BBinder* layerHandle, const std::string& name, std::atomic<int32_t>* counter) {
+        void add(uint32_t layerId, const std::string& name, std::atomic<int32_t>* counter) {
             std::lock_guard<std::mutex> lock(mLock);
-            mCounterByLayerHandle[layerHandle] = std::make_pair(name, counter);
+            mCounterByLayerId[layerId] = std::make_pair(name, counter);
         }
 
-        void remove(BBinder* layerHandle) {
+        void remove(uint32_t layerId) {
             std::lock_guard<std::mutex> lock(mLock);
-            mCounterByLayerHandle.erase(layerHandle);
+            mCounterByLayerId.erase(layerId);
         }
 
     private:
         std::mutex mLock;
-        std::unordered_map<BBinder*, std::pair<std::string, std::atomic<int32_t>*>>
-                mCounterByLayerHandle GUARDED_BY(mLock);
+        std::unordered_map<uint32_t, std::pair<std::string, std::atomic<int32_t>*>>
+                mCounterByLayerId GUARDED_BY(mLock);
     };
 
     enum class BootStage {
@@ -861,7 +861,7 @@
 
     void captureScreenCommon(RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
                              ui::Size bufferSize, ui::PixelFormat, bool allowProtected,
-                             bool grayscale, const sp<IScreenCaptureListener>&);
+                             bool grayscale, bool attachGainmap, const sp<IScreenCaptureListener>&);
 
     std::optional<OutputCompositionState> getDisplayStateFromRenderAreaBuilder(
             RenderAreaBuilderVariant& renderAreaBuilder) REQUIRES(kMainThreadContext);
@@ -875,20 +875,21 @@
     ftl::SharedFuture<FenceResult> captureScreenshot(
             const RenderAreaBuilderVariant& renderAreaBuilder,
             const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
-            bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener,
+            bool grayscale, bool isProtected, bool attachGainmap,
+            const sp<IScreenCaptureListener>& captureListener,
             std::optional<OutputCompositionState>& displayState,
             std::vector<sp<LayerFE>>& layerFEs);
 
     ftl::SharedFuture<FenceResult> captureScreenshotLegacy(
             RenderAreaBuilderVariant, GetLayerSnapshotsFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
-            bool grayscale, bool isProtected, const sp<IScreenCaptureListener>&);
+            bool grayscale, bool isProtected, bool attachGainmap,
+            const sp<IScreenCaptureListener>&);
 
     ftl::SharedFuture<FenceResult> renderScreenImpl(
-            std::unique_ptr<const RenderArea>,
-            const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
-            bool grayscale, bool isProtected, ScreenCaptureResults&,
-            std::optional<OutputCompositionState>& displayState,
+            const RenderArea*, const std::shared_ptr<renderengine::ExternalTexture>&,
+            bool regionSampling, bool grayscale, bool isProtected, bool attachGainmap,
+            ScreenCaptureResults&, std::optional<OutputCompositionState>& displayState,
             std::vector<std::pair<Layer*, sp<LayerFE>>>& layers,
             std::vector<sp<LayerFE>>& layerFEs);
 
diff --git a/services/surfaceflinger/Tracing/LayerDataSource.cpp b/services/surfaceflinger/Tracing/LayerDataSource.cpp
index ed1e2ec..cc0063c 100644
--- a/services/surfaceflinger/Tracing/LayerDataSource.cpp
+++ b/services/surfaceflinger/Tracing/LayerDataSource.cpp
@@ -82,10 +82,13 @@
     }
 }
 
-void LayerDataSource::OnStop(const LayerDataSource::StopArgs&) {
+void LayerDataSource::OnStop(const LayerDataSource::StopArgs& args) {
     ALOGD("Received OnStop event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
     if (auto* p = mLayerTracing.load()) {
-        p->onStop(mMode);
+        // In dump mode we need to defer the stop (through HandleStopAsynchronously()) till
+        // the layers snapshot has been captured and written to perfetto. We must avoid writing
+        // to perfetto within the OnStop callback to prevent deadlocks (b/313130597).
+        p->onStop(mMode, mFlags, args.HandleStopAsynchronously());
     }
 }
 
diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp
index d40b888..d78f9bb 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.cpp
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -32,7 +32,7 @@
 namespace android {
 
 LayerTracing::LayerTracing() {
-    mTakeLayersSnapshotProto = [](uint32_t) { return perfetto::protos::LayersSnapshotProto{}; };
+    mTakeLayersSnapshotProto = [](uint32_t, const OnLayersSnapshotCallback&) {};
     LayerDataSource::Initialize(*this);
 }
 
@@ -45,7 +45,7 @@
 }
 
 void LayerTracing::setTakeLayersSnapshotProtoFunction(
-        const std::function<perfetto::protos::LayersSnapshotProto(uint32_t)>& callback) {
+        const std::function<void(uint32_t, const OnLayersSnapshotCallback&)>& callback) {
     mTakeLayersSnapshotProto = callback;
 }
 
@@ -62,7 +62,10 @@
             // It might take a while before a layers change occurs and a "spontaneous" snapshot is
             // taken. Let's manually take a snapshot, so that the trace's first entry will contain
             // the current layers state.
-            addProtoSnapshotToOstream(mTakeLayersSnapshotProto(flags), Mode::MODE_ACTIVE);
+            auto onLayersSnapshot = [this](perfetto::protos::LayersSnapshotProto&& snapshot) {
+                addProtoSnapshotToOstream(std::move(snapshot), Mode::MODE_ACTIVE);
+            };
+            mTakeLayersSnapshotProto(flags, onLayersSnapshot);
             ALOGD("Started active tracing (traced initial snapshot)");
             break;
         }
@@ -89,9 +92,7 @@
             break;
         }
         case Mode::MODE_DUMP: {
-            auto snapshot = mTakeLayersSnapshotProto(flags);
-            addProtoSnapshotToOstream(std::move(snapshot), Mode::MODE_DUMP);
-            ALOGD("Started dump tracing (dumped single snapshot)");
+            ALOGD("Started dump tracing");
             break;
         }
         default: {
@@ -125,10 +126,27 @@
     ALOGD("Flushed generated tracing");
 }
 
-void LayerTracing::onStop(Mode mode) {
-    if (mode == Mode::MODE_ACTIVE) {
-        mIsActiveTracingStarted.store(false);
-        ALOGD("Stopped active tracing");
+void LayerTracing::onStop(Mode mode, uint32_t flags, std::function<void()>&& deferredStopDone) {
+    switch (mode) {
+        case Mode::MODE_ACTIVE: {
+            mIsActiveTracingStarted.store(false);
+            deferredStopDone();
+            ALOGD("Stopped active tracing");
+            break;
+        }
+        case Mode::MODE_DUMP: {
+            auto onLayersSnapshot = [this, deferredStopDone = std::move(deferredStopDone)](
+                                            perfetto::protos::LayersSnapshotProto&& snapshot) {
+                addProtoSnapshotToOstream(std::move(snapshot), Mode::MODE_DUMP);
+                deferredStopDone();
+                ALOGD("Stopped dump tracing (written single snapshot)");
+            };
+            mTakeLayersSnapshotProto(flags, onLayersSnapshot);
+            break;
+        }
+        default: {
+            deferredStopDone();
+        }
     }
 }
 
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
index 2895ba7..e99fe4c 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -87,6 +87,7 @@
 class LayerTracing {
 public:
     using Mode = perfetto::protos::pbzero::SurfaceFlingerLayersConfig::Mode;
+    using OnLayersSnapshotCallback = std::function<void(perfetto::protos::LayersSnapshotProto&&)>;
 
     enum Flag : uint32_t {
         TRACE_INPUT = 1 << 1,
@@ -102,7 +103,7 @@
     LayerTracing(std::ostream&);
     ~LayerTracing();
     void setTakeLayersSnapshotProtoFunction(
-            const std::function<perfetto::protos::LayersSnapshotProto(uint32_t)>&);
+            const std::function<void(uint32_t, const OnLayersSnapshotCallback&)>&);
     void setTransactionTracing(TransactionTracing&);
 
     // Start event from perfetto data source
@@ -110,7 +111,7 @@
     // Flush event from perfetto data source
     void onFlush(Mode mode, uint32_t flags, bool isBugreport);
     // Stop event from perfetto data source
-    void onStop(Mode mode);
+    void onStop(Mode mode, uint32_t flags, std::function<void()>&& deferredStopDone);
 
     void addProtoSnapshotToOstream(perfetto::protos::LayersSnapshotProto&& snapshot, Mode mode);
     bool isActiveTracingStarted() const;
@@ -123,7 +124,7 @@
     void writeSnapshotToPerfetto(const perfetto::protos::LayersSnapshotProto& snapshot, Mode mode);
     bool checkAndUpdateLastVsyncIdWrittenToPerfetto(Mode mode, std::int64_t vsyncId);
 
-    std::function<perfetto::protos::LayersSnapshotProto(uint32_t)> mTakeLayersSnapshotProto;
+    std::function<void(uint32_t, const OnLayersSnapshotCallback&)> mTakeLayersSnapshotProto;
     TransactionTracing* mTransactionTracing;
 
     std::atomic<bool> mIsActiveTracingStarted{false};
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index b189598..f39a4d2 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -147,7 +147,7 @@
         proto.set_transform_to_display_inverse(layer.transformToDisplayInverse);
     }
     if (layer.what & layer_state_t::eCropChanged) {
-        LayerProtoHelper::writeToProto(layer.crop, proto.mutable_crop());
+        LayerProtoHelper::writeToProto(Rect(layer.crop), proto.mutable_crop());
     }
     if (layer.what & layer_state_t::eBufferChanged) {
         perfetto::protos::LayerState_BufferData* bufferProto = proto.mutable_buffer_data();
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
index 82aa557..12d6138 100644
--- a/services/surfaceflinger/common/FlagManager.cpp
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -143,11 +143,13 @@
     DUMP_READ_ONLY_FLAG(ce_fence_promise);
     DUMP_READ_ONLY_FLAG(idle_screen_refresh_rate_timeout);
     DUMP_READ_ONLY_FLAG(graphite_renderengine);
+    DUMP_READ_ONLY_FLAG(filter_frames_before_trace_starts);
     DUMP_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed);
     DUMP_READ_ONLY_FLAG(deprecate_vsync_sf);
     DUMP_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter);
     DUMP_READ_ONLY_FLAG(detached_mirror);
     DUMP_READ_ONLY_FLAG(commit_not_composited);
+    DUMP_READ_ONLY_FLAG(correct_dpi_with_display_size);
     DUMP_READ_ONLY_FLAG(local_tonemap_screenshots);
     DUMP_READ_ONLY_FLAG(override_trusted_overlay);
     DUMP_READ_ONLY_FLAG(flush_buffer_slots_to_uncache);
@@ -247,11 +249,13 @@
 FLAG_MANAGER_READ_ONLY_FLAG(vrr_bugfix_dropped_frame, "")
 FLAG_MANAGER_READ_ONLY_FLAG(ce_fence_promise, "");
 FLAG_MANAGER_READ_ONLY_FLAG(graphite_renderengine, "debug.renderengine.graphite")
+FLAG_MANAGER_READ_ONLY_FLAG(filter_frames_before_trace_starts, "")
 FLAG_MANAGER_READ_ONLY_FLAG(latch_unsignaled_with_auto_refresh_changed, "");
 FLAG_MANAGER_READ_ONLY_FLAG(deprecate_vsync_sf, "");
 FLAG_MANAGER_READ_ONLY_FLAG(allow_n_vsyncs_in_targeter, "");
 FLAG_MANAGER_READ_ONLY_FLAG(detached_mirror, "");
 FLAG_MANAGER_READ_ONLY_FLAG(commit_not_composited, "");
+FLAG_MANAGER_READ_ONLY_FLAG(correct_dpi_with_display_size, "");
 FLAG_MANAGER_READ_ONLY_FLAG(local_tonemap_screenshots, "debug.sf.local_tonemap_screenshots");
 FLAG_MANAGER_READ_ONLY_FLAG(override_trusted_overlay, "");
 FLAG_MANAGER_READ_ONLY_FLAG(flush_buffer_slots_to_uncache, "");
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
index 6619975..a1be194 100644
--- a/services/surfaceflinger/common/include/common/FlagManager.h
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -81,11 +81,13 @@
     bool ce_fence_promise() const;
     bool idle_screen_refresh_rate_timeout() const;
     bool graphite_renderengine() const;
+    bool filter_frames_before_trace_starts() const;
     bool latch_unsignaled_with_auto_refresh_changed() const;
     bool deprecate_vsync_sf() const;
     bool allow_n_vsyncs_in_targeter() const;
     bool detached_mirror() const;
     bool commit_not_composited() const;
+    bool correct_dpi_with_display_size() const;
     bool local_tonemap_screenshots() const;
     bool override_trusted_overlay() const;
     bool flush_buffer_slots_to_uncache() const;
diff --git a/services/surfaceflinger/surfaceflinger_flags_new.aconfig b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
index 0ff846e7..102e2b6 100644
--- a/services/surfaceflinger/surfaceflinger_flags_new.aconfig
+++ b/services/surfaceflinger/surfaceflinger_flags_new.aconfig
@@ -4,10 +4,10 @@
 container: "system"
 
 flag {
-    name: "adpf_gpu_sf"
-    namespace: "game"
-    description: "Guards use of the sending ADPF GPU duration hint and load hints from SurfaceFlinger to Power HAL"
-    bug: "284324521"
+  name: "adpf_gpu_sf"
+  namespace: "game"
+  description: "Guards use of the sending ADPF GPU duration hint and load hints from SurfaceFlinger to Power HAL"
+  bug: "284324521"
 } # adpf_gpu_sf
 
 flag {
@@ -21,18 +21,29 @@
   }
  } # ce_fence_promise
 
- flag {
-   name: "commit_not_composited"
-   namespace: "core_graphics"
-   description: "mark frames as non janky if the transaction resulted in no composition"
-   bug: "340633280"
-   is_fixed_read_only: true
-   metadata {
-     purpose: PURPOSE_BUGFIX
-   }
-  } # commit_not_composited
+flag {
+  name: "commit_not_composited"
+  namespace: "core_graphics"
+  description: "mark frames as non janky if the transaction resulted in no composition"
+  bug: "340633280"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # commit_not_composited
 
- flag {
+flag {
+  name: "correct_dpi_with_display_size"
+  namespace: "core_graphics"
+  description: "indicate whether missing or likely incorrect dpi should be corrected using the display size."
+  bug: "328425848"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # correct_dpi_with_display_size
+
+flag {
   name: "deprecate_vsync_sf"
   namespace: "core_graphics"
   description: "Depracate eVsyncSourceSurfaceFlinger and use vsync_app everywhere"
@@ -43,7 +54,7 @@
   }
 } # deprecate_vsync_sf
 
- flag {
+flag {
   name: "detached_mirror"
   namespace: "window_surfaces"
   description: "Ignore local transform when mirroring a partial hierarchy"
@@ -55,6 +66,17 @@
 } # detached_mirror
 
 flag {
+  name: "filter_frames_before_trace_starts"
+  namespace: "core_graphics"
+  description: "Do not trace FrameTimeline events for frames started before the trace started"
+  bug: "364194637"
+  is_fixed_read_only: true
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+} # filter_frames_before_trace_starts
+
+flag {
   name: "flush_buffer_slots_to_uncache"
   namespace: "core_graphics"
   description: "Flush DisplayCommands for disabled displays in order to uncache requested buffers."
@@ -96,11 +118,11 @@
 } # latch_unsignaled_with_auto_refresh_changed
 
 flag {
-    name: "local_tonemap_screenshots"
-    namespace: "core_graphics"
-    description: "Enables local tonemapping when capturing screenshots"
-    bug: "329464641"
-    is_fixed_read_only: true
+  name: "local_tonemap_screenshots"
+  namespace: "core_graphics"
+  description: "Enables local tonemapping when capturing screenshots"
+  bug: "329464641"
+  is_fixed_read_only: true
 } # local_tonemap_screenshots
 
 flag {
@@ -115,11 +137,11 @@
  } # single_hop_screenshot
 
 flag {
-    name: "true_hdr_screenshots"
-    namespace: "core_graphics"
-    description: "Enables screenshotting display content in HDR, sans tone mapping"
-    bug: "329470026"
-    is_fixed_read_only: true
+  name: "true_hdr_screenshots"
+  namespace: "core_graphics"
+  description: "Enables screenshotting display content in HDR, sans tone mapping"
+  bug: "329470026"
+  is_fixed_read_only: true
 } # true_hdr_screenshots
 
  flag {
@@ -134,11 +156,11 @@
 } # override_trusted_overlay
 
 flag {
-    name: "view_set_requested_frame_rate_mrr"
-    namespace: "core_graphics"
-    description: "Enable to use frame rate category NoPreference with fixed frame rate vote on MRR devices"
-    bug: "352206100"
-    is_fixed_read_only: true
+  name: "view_set_requested_frame_rate_mrr"
+  namespace: "core_graphics"
+  description: "Enable to use frame rate category NoPreference with fixed frame rate vote on MRR devices"
+  bug: "352206100"
+  is_fixed_read_only: true
 } # view_set_requested_frame_rate_mrr
 
 flag {
diff --git a/services/surfaceflinger/tests/benchmarks/Android.bp b/services/surfaceflinger/tests/benchmarks/Android.bp
index 1c47be34..22fca08 100644
--- a/services/surfaceflinger/tests/benchmarks/Android.bp
+++ b/services/surfaceflinger/tests/benchmarks/Android.bp
@@ -22,7 +22,6 @@
     static_libs: [
         "libgmock",
         "libgtest",
-        "libc++fs",
     ],
     header_libs: [
         "libsurfaceflinger_mocks_headers",
diff --git a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
index ae380ad..b472047 100644
--- a/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
+++ b/services/surfaceflinger/tests/common/LayerLifecycleManagerHelper.h
@@ -182,7 +182,7 @@
         mLifecycleManager.applyTransactions(setZTransaction(id, z));
     }
 
-    void setCrop(uint32_t id, const Rect& crop) {
+    void setCrop(uint32_t id, const FloatRect& crop) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
         transactions.back().states.push_back({});
@@ -193,6 +193,8 @@
         mLifecycleManager.applyTransactions(transactions);
     }
 
+    void setCrop(uint32_t id, const Rect& crop) { setCrop(id, crop.toFloatRect()); }
+
     void setFlags(uint32_t id, uint32_t mask, uint32_t flags) {
         std::vector<TransactionState> transactions;
         transactions.emplace_back();
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index 23d3c16..4f72424 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -467,7 +467,7 @@
                                                          LayerProperties::FORMAT,
                                                          LayerProperties::USAGE |
                                                                  GraphicBuffer::USAGE_HW_TEXTURE);
-        layer.crop = Rect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
+        layer.crop = FloatRect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
         layer.externalTexture = buffer;
         layer.bufferData->acquireFence = Fence::NO_FENCE;
         layer.dataspace = ui::Dataspace::UNKNOWN;
@@ -664,7 +664,8 @@
                 NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
                                      false);
         layer.sidebandStream = stream;
-        layer.crop = Rect(0, 0, SidebandLayerProperties::HEIGHT, SidebandLayerProperties::WIDTH);
+        layer.crop =
+                FloatRect(0, 0, SidebandLayerProperties::HEIGHT, SidebandLayerProperties::WIDTH);
     }
 
     static void setupHwcSetSourceCropBufferCallExpectations(CompositionTest* test) {
@@ -828,7 +829,7 @@
             return frontend::RequestedLayerState(args);
         });
 
-        layer.crop = Rect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
+        layer.crop = FloatRect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
         return layer;
     }
 
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index fa31643..9b10c94 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -64,17 +64,6 @@
 
 void DisplayTransactionTest::injectMockScheduler(PhysicalDisplayId displayId) {
     LOG_ALWAYS_FATAL_IF(mFlinger.scheduler());
-
-    EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*mEventThread, createEventConnection(_, _))
-            .WillOnce(Return(
-                    sp<EventThreadConnection>::make(mEventThread, mock::EventThread::kCallingUid)));
-
-    EXPECT_CALL(*mSFEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*mSFEventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(mSFEventThread,
-                                                             mock::EventThread::kCallingUid)));
-
     mFlinger.setupScheduler(std::make_unique<mock::VsyncController>(),
                             std::make_shared<mock::VSyncTracker>(),
                             std::unique_ptr<EventThread>(mEventThread),
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index 9be0fc3..0dfbd61 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -84,9 +84,11 @@
 
     void SetUp() override {
         constexpr bool kUseBootTimeClock = true;
+        constexpr bool kFilterFramesBeforeTraceStarts = false;
         mTimeStats = std::make_shared<mock::TimeStats>();
         mFrameTimeline = std::make_unique<impl::FrameTimeline>(mTimeStats, kSurfaceFlingerPid,
-                                                               kTestThresholds, !kUseBootTimeClock);
+                                                               kTestThresholds, !kUseBootTimeClock,
+                                                               kFilterFramesBeforeTraceStarts);
         mFrameTimeline->registerDataSource();
         mTokenManager = &mFrameTimeline->mTokenManager;
         mTraceCookieCounter = &mFrameTimeline->mTraceCookieCounter;
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 6030427..e0753a3 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -166,6 +166,7 @@
     expectHotplugConnect(kHwcDisplayId);
     const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
     ASSERT_TRUE(info);
+    ASSERT_TRUE(info->preferredDetailedTimingDescriptor.has_value());
 
     EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(false));
 
@@ -179,6 +180,10 @@
         constexpr int32_t kHeight = 720;
         constexpr int32_t kConfigGroup = 1;
         constexpr int32_t kVsyncPeriod = 16666667;
+        constexpr float kMmPerInch = 25.4f;
+        const ui::Size size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
+        const float expectedDpiX = (kWidth * kMmPerInch / size.width);
+        const float expectedDpiY = (kHeight * kMmPerInch / size.height);
 
         EXPECT_CALL(*mHal,
                     getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::WIDTH,
@@ -218,8 +223,13 @@
         EXPECT_EQ(modes.front().height, kHeight);
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
-        EXPECT_EQ(modes.front().dpiX, -1);
-        EXPECT_EQ(modes.front().dpiY, -1);
+        if (!FlagManager::getInstance().correct_dpi_with_display_size()) {
+            EXPECT_EQ(modes.front().dpiX, -1);
+            EXPECT_EQ(modes.front().dpiY, -1);
+        } else {
+            EXPECT_EQ(modes.front().dpiX, expectedDpiX);
+            EXPECT_EQ(modes.front().dpiY, expectedDpiY);
+        }
 
         // Optional parameters are supported
         constexpr int32_t kDpi = 320;
@@ -271,6 +281,10 @@
         constexpr int32_t kHeight = 720;
         constexpr int32_t kConfigGroup = 1;
         constexpr int32_t kVsyncPeriod = 16666667;
+        constexpr float kMmPerInch = 25.4f;
+        const ui::Size size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
+        const float expectedDpiX = (kWidth * kMmPerInch / size.width);
+        const float expectedDpiY = (kHeight * kMmPerInch / size.height);
 
         EXPECT_CALL(*mHal,
                     getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::WIDTH,
@@ -310,8 +324,13 @@
         EXPECT_EQ(modes.front().height, kHeight);
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
-        EXPECT_EQ(modes.front().dpiX, -1);
-        EXPECT_EQ(modes.front().dpiY, -1);
+        if (!FlagManager::getInstance().correct_dpi_with_display_size()) {
+            EXPECT_EQ(modes.front().dpiX, -1);
+            EXPECT_EQ(modes.front().dpiY, -1);
+        } else {
+            EXPECT_EQ(modes.front().dpiX, expectedDpiX);
+            EXPECT_EQ(modes.front().dpiY, expectedDpiY);
+        }
 
         // Optional parameters are supported
         constexpr int32_t kDpi = 320;
@@ -361,6 +380,10 @@
         constexpr int32_t kHeight = 720;
         constexpr int32_t kConfigGroup = 1;
         constexpr int32_t kVsyncPeriod = 16666667;
+        constexpr float kMmPerInch = 25.4f;
+        const ui::Size size = info->preferredDetailedTimingDescriptor->physicalSizeInMm;
+        const float expectedDpiX = (kWidth * kMmPerInch / size.width);
+        const float expectedDpiY = (kHeight * kMmPerInch / size.height);
         const hal::VrrConfig vrrConfig =
                 hal::VrrConfig{.minFrameIntervalNs = static_cast<Fps>(120_Hz).getPeriodNsecs(),
                                .notifyExpectedPresentConfig = hal::VrrConfig::
@@ -387,8 +410,13 @@
         EXPECT_EQ(modes.front().configGroup, kConfigGroup);
         EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
         EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
-        EXPECT_EQ(modes.front().dpiX, -1);
-        EXPECT_EQ(modes.front().dpiY, -1);
+        if (!FlagManager::getInstance().correct_dpi_with_display_size()) {
+            EXPECT_EQ(modes.front().dpiX, -1);
+            EXPECT_EQ(modes.front().dpiY, -1);
+        } else {
+            EXPECT_EQ(modes.front().dpiX, expectedDpiX);
+            EXPECT_EQ(modes.front().dpiY, expectedDpiY);
+        }
 
         // Supports optional dpi parameter
         constexpr int32_t kDpi = 320;
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
index b4efe0f..c7cc21c 100644
--- a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -619,4 +619,14 @@
     }
 }
 
+TEST_F(LayerLifecycleManagerTest, testInputInfoOfRequestedLayerState) {
+    // By default the layer has no buffer, so it doesn't need an input info
+    EXPECT_FALSE(getRequestedLayerState(mLifecycleManager, 111)->needsInputInfo());
+
+    setBuffer(111);
+    mLifecycleManager.commitChanges();
+
+    EXPECT_TRUE(getRequestedLayerState(mLifecycleManager, 111)->needsInputInfo());
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
index 9020723..a35ae15 100644
--- a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -162,12 +162,12 @@
     info.info.logicalHeight = 100;
     info.info.logicalWidth = 200;
     mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), info);
-    Rect layerCrop(0, 0, 10, 20);
+    FloatRect layerCrop(0, 0, 10, 20);
     setCrop(11, layerCrop);
     EXPECT_TRUE(mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Geometry));
     UPDATE_AND_VERIFY_WITH_DISPLAY_CHANGES(mSnapshotBuilder, STARTING_ZORDER);
     EXPECT_EQ(getSnapshot(11)->geomCrop, layerCrop);
-    EXPECT_EQ(getSnapshot(111)->geomLayerBounds, layerCrop.toFloatRect());
+    EXPECT_EQ(getSnapshot(111)->geomLayerBounds, layerCrop);
     float maxHeight = static_cast<float>(info.info.logicalHeight * 10);
     float maxWidth = static_cast<float>(info.info.logicalWidth * 10);
 
@@ -1762,6 +1762,7 @@
     UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
     EXPECT_TRUE(getSnapshot(1)->isHiddenByPolicy());
 }
+
 TEST_F(LayerSnapshotTest, edgeExtensionPropagatesInHierarchy) {
     if (!com::android::graphics::libgui::flags::edge_extension_shader()) {
         GTEST_SKIP() << "Skipping test because edge_extension_shader is off";
@@ -1920,4 +1921,18 @@
     EXPECT_GE(getSnapshot({.id = 1221})->transformedBounds.top, 0);
 }
 
+TEST_F(LayerSnapshotTest, shouldUpdateInputWhenNoInputInfo) {
+    // By default the layer has no buffer, so we don't expect it to have an input info
+    EXPECT_FALSE(getSnapshot(111)->hasInputInfo());
+
+    setBuffer(111);
+
+    UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+    EXPECT_TRUE(getSnapshot(111)->hasInputInfo());
+    EXPECT_TRUE(getSnapshot(111)->inputInfo.inputConfig.test(
+            gui::WindowInfo::InputConfig::NO_INPUT_CHANNEL));
+    EXPECT_FALSE(getSnapshot(2)->hasInputInfo());
+}
+
 } // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 45ca7e2..ac09cbc 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -124,7 +124,7 @@
 
     // createConnection call to scheduler makes a createEventConnection call to EventThread. Make
     // sure that call gets executed and returns an EventThread::Connection object.
-    EXPECT_CALL(*mEventThread, createEventConnection(_, _))
+    EXPECT_CALL(*mEventThread, createEventConnection(_))
             .WillRepeatedly(Return(mEventThreadConnection));
 
     mScheduler->setEventThread(Cycle::Render, std::move(eventThread));
@@ -797,7 +797,7 @@
 
     const auto mockConnection1 = sp<MockEventThreadConnection>::make(mEventThread);
     const auto mockConnection2 = sp<MockEventThreadConnection>::make(mEventThread);
-    EXPECT_CALL(*mEventThread, createEventConnection(_, _))
+    EXPECT_CALL(*mEventThread, createEventConnection(_))
             .WillOnce(Return(mockConnection1))
             .WillOnce(Return(mockConnection2));
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 4b0a7c3..8699621 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -187,16 +187,6 @@
     mAppEventThread = eventThread.get();
     auto sfEventThread = std::make_unique<mock::EventThread>();
 
-    EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*eventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
-                                                             mock::EventThread::kCallingUid)));
-
-    EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
-    EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-            .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
-                                                             mock::EventThread::kCallingUid)));
-
     auto vsyncController = std::make_unique<mock::VsyncController>();
     auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
 
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
index 933d03d..352000e 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -239,7 +239,7 @@
         ASSERT_TRUE(displayId);
         const auto hwcDisplayId = Case::Display::HWC_DISPLAY_ID_OPT::value;
         ASSERT_TRUE(hwcDisplayId);
-        mFlinger.getHwComposer().allocatePhysicalDisplay(*hwcDisplayId, *displayId);
+        mFlinger.getHwComposer().allocatePhysicalDisplay(*hwcDisplayId, *displayId, std::nullopt);
         DisplayModePtr activeMode = DisplayMode::Builder(Case::Display::HWC_ACTIVE_CONFIG_ID)
                                             .setResolution(Case::Display::RESOLUTION)
                                             .setVsyncPeriod(DEFAULT_VSYNC_PERIOD)
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index df16b2e..9de3346 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -74,10 +74,8 @@
     void setEventThread(Cycle cycle, std::unique_ptr<EventThread> eventThreadPtr) {
         if (cycle == Cycle::Render) {
             mRenderEventThread = std::move(eventThreadPtr);
-            mRenderEventConnection = mRenderEventThread->createEventConnection();
         } else {
             mLastCompositeEventThread = std::move(eventThreadPtr);
-            mLastCompositeEventConnection = mLastCompositeEventThread->createEventConnection();
         }
     }
 
@@ -133,7 +131,9 @@
     using Scheduler::resyncAllToHardwareVsync;
 
     auto& mutableLayerHistory() { return mLayerHistory; }
-    auto& mutableAttachedChoreographers() { return mAttachedChoreographers; }
+    auto& mutableAttachedChoreographers() NO_THREAD_SAFETY_ANALYSIS {
+        return mAttachedChoreographers;
+    }
 
     size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
         return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size();
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 2c63881..4dec5f6 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include <algorithm>
 #include <chrono>
 #include <memory>
 #include <variant>
@@ -44,7 +43,6 @@
 #include "Layer.h"
 #include "NativeWindowSurface.h"
 #include "RenderArea.h"
-#include "Scheduler/MessageQueue.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "SurfaceFlinger.h"
 #include "TestableScheduler.h"
@@ -60,7 +58,6 @@
 
 #include "Scheduler/VSyncTracker.h"
 #include "Scheduler/VsyncController.h"
-#include "mock/MockVSyncDispatch.h"
 #include "mock/MockVSyncTracker.h"
 #include "mock/MockVsyncController.h"
 
@@ -88,9 +85,7 @@
 public:
     ~Factory() = default;
 
-    std::unique_ptr<HWComposer> createHWComposer(const std::string&) override {
-        return nullptr;
-    }
+    std::unique_ptr<HWComposer> createHWComposer(const std::string&) override { return nullptr; }
 
     std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
             Fps /*currentRefreshRate*/) override {
@@ -276,17 +271,6 @@
 
         auto eventThread = makeMock<mock::EventThread>(options.useNiceMock);
         auto sfEventThread = makeMock<mock::EventThread>(options.useNiceMock);
-
-        EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
-        EXPECT_CALL(*eventThread, createEventConnection(_, _))
-                .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
-                                                                 mock::EventThread::kCallingUid)));
-
-        EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
-        EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
-                .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
-                                                                 mock::EventThread::kCallingUid)));
-
         auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
         auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
 
@@ -490,9 +474,10 @@
         auto layers = getLayerSnapshotsFn();
         auto layerFEs = mFlinger->extractLayerFEs(layers);
 
-        return mFlinger->renderScreenImpl(std::move(renderArea), buffer, regionSampling,
+        return mFlinger->renderScreenImpl(renderArea.get(), buffer, regionSampling,
                                           false /* grayscale */, false /* isProtected */,
-                                          captureResults, displayState, layers, layerFEs);
+                                          false /* attachGainmap */, captureResults, displayState,
+                                          layers, layerFEs);
     }
 
     auto getLayerSnapshotsForScreenshotsFn(ui::LayerStack layerStack, uint32_t uid) {
@@ -501,12 +486,14 @@
     }
 
     auto getDisplayNativePrimaries(const sp<IBinder>& displayToken,
-                                   ui::DisplayPrimaries &primaries) {
+                                   ui::DisplayPrimaries& primaries) {
         return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries);
     }
 
-    auto& getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; }
-    auto& getPendingTransactionQueue() {
+    auto& getTransactionQueue() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->mTransactionHandler.mLocklessTransactionQueue;
+    }
+    auto& getPendingTransactionQueue() NO_THREAD_SAFETY_ANALYSIS {
         ftl::FakeGuard guard(kMainThreadContext);
         return mFlinger->mTransactionHandler.mPendingTransactionQueues;
     }
@@ -650,7 +637,7 @@
     void destroyAllLayerHandles() {
         ftl::FakeGuard guard(kMainThreadContext);
         for (auto [layerId, legacyLayer] : mFlinger->mLegacyLayers) {
-            mFlinger->onHandleDestroyed(nullptr, legacyLayer, layerId);
+            mFlinger->onHandleDestroyed(legacyLayer, layerId);
         }
     }
 
@@ -674,8 +661,10 @@
      * post-conditions.
      */
 
-    const auto& displays() const { return mFlinger->mDisplays; }
-    const auto& physicalDisplays() const { return mFlinger->mPhysicalDisplays; }
+    const auto& displays() const NO_THREAD_SAFETY_ANALYSIS { return mFlinger->mDisplays; }
+    const auto& physicalDisplays() const NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->mPhysicalDisplays;
+    }
     const auto& currentState() const { return mFlinger->mCurrentState; }
     const auto& drawingState() const { return mFlinger->mDrawingState; }
     const auto& transactionFlags() const { return mFlinger->mTransactionFlags; }
@@ -688,13 +677,17 @@
     auto& mutableDisplayModeController() { return mFlinger->mDisplayModeController; }
     auto& mutableCurrentState() { return mFlinger->mCurrentState; }
     auto& mutableDisplayColorSetting() { return mFlinger->mDisplayColorSetting; }
-    auto& mutableDisplays() { return mFlinger->mDisplays; }
-    auto& mutablePhysicalDisplays() { return mFlinger->mPhysicalDisplays; }
+    auto& mutableDisplays() NO_THREAD_SAFETY_ANALYSIS { return mFlinger->mDisplays; }
+    auto& mutablePhysicalDisplays() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->mPhysicalDisplays;
+    }
     auto& mutableDrawingState() { return mFlinger->mDrawingState; }
     auto& mutableGeometryDirty() { return mFlinger->mGeometryDirty; }
     auto& mutableVisibleRegionsDirty() { return mFlinger->mVisibleRegionsDirty; }
     auto& mutableMainThreadId() { return mFlinger->mMainThreadId; }
-    auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; }
+    auto& mutablePendingHotplugEvents() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->mPendingHotplugEvents;
+    }
     auto& mutableTransactionFlags() { return mFlinger->mTransactionFlags; }
     auto& mutableDebugDisableHWC() { return mFlinger->mDebugDisableHWC; }
     auto& mutableMaxRenderTargetSize() { return mFlinger->mMaxRenderTargetSize; }
@@ -702,7 +695,7 @@
     auto& mutableHwcDisplayData() { return getHwComposer().mDisplayData; }
     auto& mutableHwcPhysicalDisplayIdMap() { return getHwComposer().mPhysicalDisplayIdMap; }
     auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; }
-    auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; }
+    auto& mutableActiveDisplayId() NO_THREAD_SAFETY_ANALYSIS { return mFlinger->mActiveDisplayId; }
     auto& mutablePreviouslyComposedLayers() { return mFlinger->mPreviouslyComposedLayers; }
 
     auto& mutableActiveDisplayRotationFlags() {
@@ -710,7 +703,9 @@
     }
 
     auto& mutableMinAcquiredBuffers() { return SurfaceFlinger::minAcquiredBuffers; }
-    auto& mutableLayerSnapshotBuilder() { return mFlinger->mLayerSnapshotBuilder; };
+    auto& mutableLayerSnapshotBuilder() NO_THREAD_SAFETY_ANALYSIS {
+        return mFlinger->mLayerSnapshotBuilder;
+    }
 
     auto fromHandle(const sp<IBinder>& handle) { return LayerHandle::getLayer(handle); }
 
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index fab1f6d..1e8cd0a 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -387,7 +387,7 @@
 
         state.state.what = what;
         if (what & layer_state_t::eCropChanged) {
-            state.state.crop = Rect(1, 2, 3, 4);
+            state.state.crop = FloatRect(1, 2, 3, 4);
         }
         if (what & layer_state_t::eFlagsChanged) {
             state.state.flags = layer_state_t::eEnableBackpressure;
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index 2cc1987..5edd2cd 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -35,6 +35,7 @@
                 (const, override));
     MOCK_METHOD(bool, isVsyncPeriodSwitchSupported, (), (const, override));
     MOCK_METHOD(void, onLayerDestroyed, (hal::HWLayerId), (override));
+    MOCK_METHOD(std::optional<ui::Size>, getPhysicalSizeInMm, (), (const override));
 
     MOCK_METHOD(hal::Error, acceptChanges, (), (override));
     MOCK_METHOD((base::expected<std::shared_ptr<HWC2::Layer>, hal::Error>), createLayer, (),
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index 8dd1a34..7398cbe 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -24,21 +24,11 @@
 
 class EventThread : public android::EventThread {
 public:
-    static constexpr auto kCallingUid = static_cast<uid_t>(0);
-
     EventThread();
     ~EventThread() override;
 
-    // TODO(b/302035909): workaround otherwise gtest complains about
-    //  error: no viable conversion from
-    //  'tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration> &&>' to 'const
-    //  tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration>>'
-    sp<EventThreadConnection> createEventConnection(EventRegistrationFlags flags) const override {
-        return createEventConnection(false, flags);
-    }
-    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, (bool, EventRegistrationFlags),
-                (const));
-
+    MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, (EventRegistrationFlags),
+                (const, override));
     MOCK_METHOD(void, enableSyntheticVsync, (bool), (override));
     MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override));
     MOCK_METHOD(void, onHotplugConnectionError, (int32_t), (override));
diff --git a/services/vibratorservice/TEST_MAPPING b/services/vibratorservice/TEST_MAPPING
index af48673..1eb3a58 100644
--- a/services/vibratorservice/TEST_MAPPING
+++ b/services/vibratorservice/TEST_MAPPING
@@ -4,11 +4,6 @@
       "name": "libvibratorservice_test"
     }
   ],
-  "postsubmit": [
-    {
-      "name": "libvibratorservice_test"
-    }
-  ],
   "imports": [
     {
       "path": "cts/tests/vibrator"
diff --git a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
index 764d9be..ca13c0b 100644
--- a/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
+++ b/services/vibratorservice/test/VibratorManagerHalWrapperAidlTest.cpp
@@ -31,10 +31,12 @@
 using aidl::android::hardware::vibrator::CompositePrimitive;
 using aidl::android::hardware::vibrator::Effect;
 using aidl::android::hardware::vibrator::EffectStrength;
+using aidl::android::hardware::vibrator::IVibrationSession;
 using aidl::android::hardware::vibrator::IVibrator;
 using aidl::android::hardware::vibrator::IVibratorCallback;
 using aidl::android::hardware::vibrator::IVibratorManager;
 using aidl::android::hardware::vibrator::PrimitivePwle;
+using aidl::android::hardware::vibrator::VibrationSessionConfig;
 
 using namespace android;
 using namespace testing;
@@ -55,6 +57,12 @@
     MOCK_METHOD(ndk::ScopedAStatus, triggerSynced, (const std::shared_ptr<IVibratorCallback>& cb),
                 (override));
     MOCK_METHOD(ndk::ScopedAStatus, cancelSynced, (), (override));
+    MOCK_METHOD(ndk::ScopedAStatus, startSession,
+                (const std::vector<int32_t>& ids, const VibrationSessionConfig& s,
+                 const std::shared_ptr<IVibratorCallback>& cb,
+                 std::shared_ptr<IVibrationSession>* ret),
+                (override));
+    MOCK_METHOD(ndk::ScopedAStatus, clearSessions, (), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t*), (override));
     MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string*), (override));
     MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));