Merge "[res] Add the NDK api for grammatical gender qualifier"
diff --git a/.clang-format b/.clang-format
index 6725a1f..f63f670 100644
--- a/.clang-format
+++ b/.clang-format
@@ -12,3 +12,6 @@
 PenaltyBreakBeforeFirstCallParameter: 100000
 SpacesBeforeTrailingComments: 1
 IncludeBlocks: Preserve
+
+DerivePointerAlignment: false
+PointerAlignment: Left
diff --git a/cmds/dumpstate/dumpstate.cpp b/cmds/dumpstate/dumpstate.cpp
index 69a1df2..fdee3e5 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -74,6 +74,7 @@
 #include <android/hidl/manager/1.0/IServiceManager.h>
 #include <android/os/IIncidentCompanion.h>
 #include <binder/IServiceManager.h>
+#include <cutils/multiuser.h>
 #include <cutils/native_handle.h>
 #include <cutils/properties.h>
 #include <cutils/sockets.h>
@@ -2619,10 +2620,13 @@
     return true;
 }
 
-static void SendBroadcast(const std::string& action, const std::vector<std::string>& args) {
+static void SendBroadcast(const std::string& action,
+                          const std::vector<std::string>& args,
+                          int32_t user_id) {
     // clang-format off
-    std::vector<std::string> am = {"/system/bin/cmd", "activity", "broadcast", "--user", "0",
-                    "--receiver-foreground", "--receiver-include-background", "-a", action};
+    std::vector<std::string> am = {"/system/bin/cmd", "activity", "broadcast", "--user",
+                        std::to_string(user_id), "--receiver-foreground",
+                        "--receiver-include-background", "-a", action};
     // clang-format on
 
     am.insert(am.end(), args.begin(), args.end());
@@ -3057,7 +3061,8 @@
         };
         // clang-format on
         // Send STARTED broadcast for apps that listen to bugreport generation events
-        SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED", am_args);
+        SendBroadcast("com.android.internal.intent.action.BUGREPORT_STARTED",
+                      am_args, multiuser_get_user_id(calling_uid));
         if (options_->progress_updates_to_socket) {
             dprintf(control_socket_fd_, "BEGIN:%s\n", path_.c_str());
         }
@@ -3305,7 +3310,7 @@
 }
 
 void Dumpstate::onUiIntensiveBugreportDumpsFinished(int32_t calling_uid) {
-    if (calling_uid == AID_SHELL || !CalledByApi()) {
+    if (multiuser_get_app_id(calling_uid) == AID_SHELL || !CalledByApi()) {
         return;
     }
     if (listener_ != nullptr) {
@@ -3316,7 +3321,7 @@
 }
 
 void Dumpstate::MaybeCheckUserConsent(int32_t calling_uid, const std::string& calling_package) {
-    if (calling_uid == AID_SHELL || !CalledByApi()) {
+    if (multiuser_get_app_id(calling_uid) == AID_SHELL || !CalledByApi()) {
         // No need to get consent for shell triggered dumpstates, or not through
         // bugreporting API (i.e. no fd to copy back).
         return;
@@ -3398,7 +3403,7 @@
     // If the caller has asked to copy the bugreport over to their directory, we need explicit
     // user consent (unless the caller is Shell).
     UserConsentResult consent_result;
-    if (calling_uid == AID_SHELL) {
+    if (multiuser_get_app_id(calling_uid) == AID_SHELL) {
         consent_result = UserConsentResult::APPROVED;
     } else {
         consent_result = consent_callback_->getResult();
diff --git a/cmds/dumpstate/dumpstate.rc b/cmds/dumpstate/dumpstate.rc
index a80da4e..12a7cff 100644
--- a/cmds/dumpstate/dumpstate.rc
+++ b/cmds/dumpstate/dumpstate.rc
@@ -8,6 +8,7 @@
     socket dumpstate stream 0660 shell log
     disabled
     oneshot
+    capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
 
 # dumpstatez generates a zipped bugreport but also uses a socket to print the file location once
 # it is finished.
@@ -16,9 +17,11 @@
     class main
     disabled
     oneshot
+    capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
 
 # bugreportd starts dumpstate binder service and makes it wait for a listener to connect.
 service bugreportd /system/bin/dumpstate -w
     class main
     disabled
     oneshot
+    capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL NET_ADMIN NET_RAW SETGID SETUID SYS_PTRACE SYS_RESOURCE BLOCK_SUSPEND SYSLOG
diff --git a/include/android/input.h b/include/android/input.h
index 5d19c5c..d6f9d63 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -778,6 +778,9 @@
      *   proportion of the touch pad's size. For example, if a touch pad is 1000 units wide, and a
      *   swipe gesture starts at X = 500 then moves to X = 400, this axis would have a value of
      *   -0.1.
+     *
+     * These values are relative to the state from the last event, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical events.
      */
     AMOTION_EVENT_AXIS_GESTURE_X_OFFSET = 48,
     /**
@@ -786,6 +789,34 @@
      * The same as {@link AMOTION_EVENT_AXIS_GESTURE_X_OFFSET}, but for the Y axis.
      */
     AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET = 49,
+    /**
+     * Axis constant: X scroll distance axis of a motion event.
+     *
+     * - For a touch pad, reports the distance that should be scrolled in the X axis as a result of
+     *   the user's two-finger scroll gesture, in display pixels.
+     *
+     * These values are relative to the state from the last event, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical events.
+     */
+    AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE = 50,
+    /**
+     * Axis constant: Y scroll distance axis of a motion event.
+     *
+     * The same as {@link AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE}, but for the Y axis.
+     */
+    AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE = 51,
+    /**
+     * Axis constant: pinch scale factor of a motion event.
+     *
+     * - For a touch pad, reports the change in distance between the fingers when the user is making
+     *   a pinch gesture, as a proportion of that distance when the gesture was last reported. For
+     *   example, if the fingers were 50 units apart and are now 52 units apart, the scale factor
+     *   would be 1.04.
+     *
+     * These values are relative to the state from the last event, not accumulated, so developers
+     * should make sure to process this axis value for all batched historical events.
+     */
+    AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR = 52,
 
     /**
      * Note: This is not an "Axis constant". It does not represent any axis, nor should it be used
@@ -793,7 +824,7 @@
      * to make some computations (like iterating through all possible axes) cleaner.
      * Please update the value accordingly if you add a new axis.
      */
-    AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET,
+    AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE = AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR,
 
     // NOTE: If you add a new axis here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
@@ -870,6 +901,21 @@
      * The current event stream represents the user swiping with two fingers on a touchpad.
      */
     AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE = 3,
+    /**
+     * Classification constant: multi-finger swipe.
+     *
+     * The current event stream represents the user swiping with three or more fingers on a
+     * touchpad. Unlike two-finger swipes, these are only to be handled by the system UI, which is
+     * why they have a separate constant from two-finger swipes.
+     */
+    AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE = 4,
+    /**
+     * Classification constant: pinch.
+     *
+     * The current event stream represents the user pinching with two fingers on a touchpad. The
+     * gesture is centered around the current cursor position.
+     */
+    AMOTION_EVENT_CLASSIFICATION_PINCH = 5,
 };
 
 /**
diff --git a/include/input/Input.h b/include/input/Input.h
index 1a35196..62d84e1 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -302,6 +302,17 @@
      * The current gesture represents the user swiping with two fingers on a touchpad.
      */
     TWO_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_TWO_FINGER_SWIPE,
+    /**
+     * The current gesture represents the user swiping with three or more fingers on a touchpad.
+     * Unlike two-finger swipes, these are only to be handled by the system UI, which is why they
+     * have a separate constant from two-finger swipes.
+     */
+    MULTI_FINGER_SWIPE = AMOTION_EVENT_CLASSIFICATION_MULTI_FINGER_SWIPE,
+    /**
+     * The current gesture represents the user pinching with two fingers on a touchpad. The gesture
+     * is centered around the current cursor position.
+     */
+    PINCH = AMOTION_EVENT_CLASSIFICATION_PINCH,
 };
 
 /**
@@ -368,7 +379,7 @@
  * Pointer coordinate data.
  */
 struct PointerCoords {
-    enum { MAX_AXES = 30 }; // 30 so that sizeof(PointerCoords) == 128
+    enum { MAX_AXES = 30 }; // 30 so that sizeof(PointerCoords) == 136
 
     // Bitfield of axes that are present in this structure.
     uint64_t bits __attribute__((aligned(8)));
@@ -377,8 +388,15 @@
     // for each axis that is present in the structure according to 'bits'.
     std::array<float, MAX_AXES> values;
 
+    // Whether these coordinate data were generated by resampling.
+    bool isResampled;
+
+    static_assert(sizeof(bool) == 1); // Ensure padding is correctly sized.
+    uint8_t empty[7];
+
     inline void clear() {
         BitSet64::clear(bits);
+        isResampled = false;
     }
 
     bool isEmpty() const {
@@ -769,6 +787,10 @@
                 AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex);
     }
 
+    inline bool isResampled(size_t pointerIndex, size_t historicalIndex) const {
+        return getHistoricalRawPointerCoords(pointerIndex, historicalIndex)->isResampled;
+    }
+
     ssize_t findPointerIndex(int32_t pointerId) const;
 
     void initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 5fa9fda..09933d3 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -24,7 +24,6 @@
 #include <vector>
 
 #include <android/os/IInputConstants.h>
-#include "android/hardware/input/InputDeviceCountryCode.h"
 
 namespace android {
 
@@ -236,9 +235,7 @@
 
     void initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                     const InputDeviceIdentifier& identifier, const std::string& alias,
-                    bool isExternal, bool hasMic,
-                    hardware::input::InputDeviceCountryCode countryCode =
-                            hardware::input::InputDeviceCountryCode::INVALID);
+                    bool isExternal, bool hasMic);
 
     inline int32_t getId() const { return mId; }
     inline int32_t getControllerNumber() const { return mControllerNumber; }
@@ -250,7 +247,6 @@
     }
     inline bool isExternal() const { return mIsExternal; }
     inline bool hasMic() const { return mHasMic; }
-    inline hardware::input::InputDeviceCountryCode getCountryCode() const { return mCountryCode; }
     inline uint32_t getSources() const { return mSources; }
 
     const MotionRange* getMotionRange(int32_t axis, uint32_t source) const;
@@ -310,7 +306,6 @@
     std::string mAlias;
     bool mIsExternal;
     bool mHasMic;
-    hardware::input::InputDeviceCountryCode mCountryCode;
     std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
     uint32_t mSources;
     int32_t mKeyboardType;
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index 867a089..b5e6f65 100644
--- a/include/input/KeyCharacterMap.h
+++ b/include/input/KeyCharacterMap.h
@@ -87,6 +87,9 @@
     /* Combines this key character map with the provided overlay. */
     void combine(const KeyCharacterMap& overlay);
 
+    /* Clears already applied layout overlay */
+    void clearLayoutOverlay();
+
     /* Gets the keyboard type. */
     KeyboardType getKeyboardType() const;
 
diff --git a/include/input/MotionPredictor.h b/include/input/MotionPredictor.h
index 6c07849..045e61b 100644
--- a/include/input/MotionPredictor.h
+++ b/include/input/MotionPredictor.h
@@ -63,14 +63,10 @@
     MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
                     std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled);
     void record(const MotionEvent& event);
-    std::vector<std::unique_ptr<MotionEvent>> predict();
+    std::vector<std::unique_ptr<MotionEvent>> predict(nsecs_t timestamp);
     bool isPredictionAvailable(int32_t deviceId, int32_t source);
-    void setExpectedPresentationTimeNanos(int64_t expectedPresentationTimeNanos);
 
 private:
-    std::mutex mLock;
-    int64_t mExpectedPresentationTimeNanos GUARDED_BY(mLock) = 0;
-    int64_t getExpectedPresentationTimeNanos();
     std::vector<MotionEvent> mEvents;
     const nsecs_t mPredictionTimestampOffsetNanos;
     const std::function<bool()> mCheckMotionPredictionEnabled;
diff --git a/include/input/VelocityTracker.h b/include/input/VelocityTracker.h
index 62c3ae1..da97c3e 100644
--- a/include/input/VelocityTracker.h
+++ b/include/input/VelocityTracker.h
@@ -51,33 +51,24 @@
         static const size_t MAX_DEGREE = 4;
 
         // Estimator time base.
-        nsecs_t time;
+        nsecs_t time = 0;
 
         // Polynomial coefficients describing motion.
-        float coeff[MAX_DEGREE + 1];
+        std::array<float, MAX_DEGREE + 1> coeff{};
 
         // Polynomial degree (number of coefficients), or zero if no information is
         // available.
-        uint32_t degree;
+        uint32_t degree = 0;
 
         // Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
-        float confidence;
-
-        inline void clear() {
-            time = 0;
-            degree = 0;
-            confidence = 0;
-            for (size_t i = 0; i <= MAX_DEGREE; i++) {
-                coeff[i] = 0;
-            }
-        }
+        float confidence = 0;
     };
 
     /*
      * Contains all available velocity data from a VelocityTracker.
      */
     struct ComputedVelocity {
-        inline std::optional<float> getVelocity(int32_t axis, uint32_t id) const {
+        inline std::optional<float> getVelocity(int32_t axis, int32_t id) const {
             const auto& axisVelocities = mVelocities.find(axis);
             if (axisVelocities == mVelocities.end()) {
                 return {};
@@ -91,7 +82,7 @@
             return axisIdVelocity->second;
         }
 
-        inline void addVelocity(int32_t axis, uint32_t id, float velocity) {
+        inline void addVelocity(int32_t axis, int32_t id, float velocity) {
             mVelocities[axis][id] = velocity;
         }
 
@@ -112,19 +103,13 @@
     // Resets the velocity tracker state.
     void clear();
 
-    // Resets the velocity tracker state for specific pointers.
+    // Resets the velocity tracker state for a specific pointer.
     // Call this method when some pointers have changed and may be reusing
     // an id that was assigned to a different pointer earlier.
-    void clearPointers(BitSet32 idBits);
+    void clearPointer(int32_t pointerId);
 
-    // Adds movement information for a set of pointers.
-    // The idBits bitfield specifies the pointer ids of the pointers whose data points
-    // are included in the movement.
-    // The positions map contains a mapping of an axis to positions array.
-    // The positions arrays contain information for each pointer in order by increasing id.
-    // Each array's size should be equal to the number of one bits in idBits.
-    void addMovement(nsecs_t eventTime, BitSet32 idBits,
-                     const std::map<int32_t, std::vector<float>>& positions);
+    // Adds movement information for a pointer for a specific axis
+    void addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis, float position);
 
     // Adds movement information for all pointers in a MotionEvent, including historical samples.
     void addMovement(const MotionEvent* event);
@@ -132,7 +117,7 @@
     // Returns the velocity of the specified pointer id and axis in position units per second.
     // Returns empty optional if there is insufficient movement information for the pointer, or if
     // the given axis is not supported for velocity tracking.
-    std::optional<float> getVelocity(int32_t axis, uint32_t id) const;
+    std::optional<float> getVelocity(int32_t axis, int32_t pointerId) const;
 
     // Returns a ComputedVelocity instance with all available velocity data, using the given units
     // (reference: units == 1 means "per millisecond"), and clamping each velocity between
@@ -142,15 +127,15 @@
     // Gets an estimator for the recent movements of the specified pointer id for the given axis.
     // Returns false and clears the estimator if there is no information available
     // about the pointer.
-    bool getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const;
+    std::optional<Estimator> getEstimator(int32_t axis, int32_t pointerId) const;
 
     // Gets the active pointer id, or -1 if none.
-    inline int32_t getActivePointerId() const { return mActivePointerId; }
+    inline int32_t getActivePointerId() const { return mActivePointerId.value_or(-1); }
 
 private:
     nsecs_t mLastEventTime;
     BitSet32 mCurrentPointerIdBits;
-    int32_t mActivePointerId;
+    std::optional<int32_t> mActivePointerId;
 
     // An override strategy passed in the constructor to be used for all axes.
     // This strategy will apply to all axes, unless the default strategy is specified here.
@@ -182,10 +167,9 @@
 public:
     virtual ~VelocityTrackerStrategy() { }
 
-    virtual void clearPointers(BitSet32 idBits) = 0;
-    virtual void addMovement(nsecs_t eventTime, BitSet32 idBits,
-                             const std::vector<float>& positions) = 0;
-    virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const = 0;
+    virtual void clearPointer(int32_t pointerId) = 0;
+    virtual void addMovement(nsecs_t eventTime, int32_t pointerId, float position) = 0;
+    virtual std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const = 0;
 };
 
 
@@ -194,29 +178,28 @@
  */
 class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {
 public:
-    enum Weighting {
+    enum class Weighting {
         // No weights applied.  All data points are equally reliable.
-        WEIGHTING_NONE,
+        NONE,
 
         // Weight by time delta.  Data points clustered together are weighted less.
-        WEIGHTING_DELTA,
+        DELTA,
 
         // Weight such that points within a certain horizon are weighed more than those
         // outside of that horizon.
-        WEIGHTING_CENTRAL,
+        CENTRAL,
 
         // Weight such that points older than a certain amount are weighed less.
-        WEIGHTING_RECENT,
+        RECENT,
     };
 
     // Degree must be no greater than Estimator::MAX_DEGREE.
-    LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = WEIGHTING_NONE);
-    virtual ~LeastSquaresVelocityTrackerStrategy();
+    LeastSquaresVelocityTrackerStrategy(uint32_t degree, Weighting weighting = Weighting::NONE);
+    ~LeastSquaresVelocityTrackerStrategy() override;
 
-    virtual void clearPointers(BitSet32 idBits);
-    void addMovement(nsecs_t eventTime, BitSet32 idBits,
-                     const std::vector<float>& positions) override;
-    virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+    void clearPointer(int32_t pointerId) override;
+    void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
+    std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
 
 private:
     // Sample horizon.
@@ -229,18 +212,15 @@
 
     struct Movement {
         nsecs_t eventTime;
-        BitSet32 idBits;
-        float positions[MAX_POINTERS];
-
-        inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; }
+        float position;
     };
 
-    float chooseWeight(uint32_t index) const;
+    float chooseWeight(int32_t pointerId, uint32_t index) const;
 
     const uint32_t mDegree;
     const Weighting mWeighting;
-    uint32_t mIndex;
-    Movement mMovements[HISTORY_SIZE];
+    std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex;
+    std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements;
 };
 
 
@@ -251,12 +231,11 @@
 public:
     // Degree must be 1 or 2.
     IntegratingVelocityTrackerStrategy(uint32_t degree);
-    ~IntegratingVelocityTrackerStrategy();
+    ~IntegratingVelocityTrackerStrategy() override;
 
-    virtual void clearPointers(BitSet32 idBits);
-    void addMovement(nsecs_t eventTime, BitSet32 idBits,
-                     const std::vector<float>& positions) override;
-    virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+    void clearPointer(int32_t pointerId) override;
+    void addMovement(nsecs_t eventTime, int32_t pointerId, float positions) override;
+    std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
 
 private:
     // Current state estimate for a particular pointer.
@@ -283,12 +262,11 @@
 class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy {
 public:
     LegacyVelocityTrackerStrategy();
-    virtual ~LegacyVelocityTrackerStrategy();
+    ~LegacyVelocityTrackerStrategy() override;
 
-    virtual void clearPointers(BitSet32 idBits);
-    void addMovement(nsecs_t eventTime, BitSet32 idBits,
-                     const std::vector<float>& positions) override;
-    virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+    void clearPointer(int32_t pointerId) override;
+    void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
+    std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
 
 private:
     // Oldest sample to consider when calculating the velocity.
@@ -302,25 +280,21 @@
 
     struct Movement {
         nsecs_t eventTime;
-        BitSet32 idBits;
-        float positions[MAX_POINTERS];
-
-        inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; }
+        float position;
     };
 
-    uint32_t mIndex;
-    Movement mMovements[HISTORY_SIZE];
+    std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex;
+    std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements;
 };
 
 class ImpulseVelocityTrackerStrategy : public VelocityTrackerStrategy {
 public:
     ImpulseVelocityTrackerStrategy(bool deltaValues);
-    virtual ~ImpulseVelocityTrackerStrategy();
+    ~ImpulseVelocityTrackerStrategy() override;
 
-    virtual void clearPointers(BitSet32 idBits);
-    void addMovement(nsecs_t eventTime, BitSet32 idBits,
-                     const std::vector<float>& positions) override;
-    virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+    void clearPointer(int32_t pointerId) override;
+    void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
+    std::optional<VelocityTracker::Estimator> getEstimator(int32_t pointerId) const override;
 
 private:
     // Sample horizon.
@@ -333,10 +307,7 @@
 
     struct Movement {
         nsecs_t eventTime;
-        BitSet32 idBits;
-        float positions[MAX_POINTERS];
-
-        inline float getPosition(uint32_t id) const { return positions[idBits.getIndexOfBit(id)]; }
+        float position;
     };
 
     // Whether or not the input movement values for the strategy come in the form of delta values.
@@ -344,8 +315,8 @@
     // velocity calculation.
     const bool mDeltaValues;
 
-    size_t mIndex;
-    Movement mMovements[HISTORY_SIZE];
+    std::map<int32_t /*pointerId*/, size_t /*positionInArray*/> mIndex;
+    std::map<int32_t /*pointerId*/, std::array<Movement, HISTORY_SIZE>> mMovements;
 };
 
 } // namespace android
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index f17bb7d..6bf7049 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -511,6 +511,7 @@
     visibility: [
         ":__subpackages__",
         "//packages/modules/Virtualization:__subpackages__",
+        "//device/google/cuttlefish/shared/minidroid:__subpackages__",
     ],
 }
 
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index 5e725a9..da5affb 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -407,8 +407,10 @@
         AutoMutex lock(e->mLock);
         if (mRecordingOn) {
             Parcel emptyReply;
+            timespec ts;
+            timespec_get(&ts, TIME_UTC);
             auto transaction =
-                    android::binder::debug::RecordedTransaction::fromDetails(code, flags, data,
+                    android::binder::debug::RecordedTransaction::fromDetails(code, flags, ts, data,
                                                                              reply ? *reply
                                                                                    : emptyReply,
                                                                              err);
diff --git a/libs/binder/BinderRecordReplay.cpp b/libs/binder/BinderRecordReplay.cpp
index 90c02a8..58bb106 100644
--- a/libs/binder/BinderRecordReplay.cpp
+++ b/libs/binder/BinderRecordReplay.cpp
@@ -16,10 +16,13 @@
 
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/unique_fd.h>
 #include <binder/BinderRecordReplay.h>
+#include <sys/mman.h>
 #include <algorithm>
 
 using android::Parcel;
+using android::base::borrowed_fd;
 using android::base::unique_fd;
 using android::binder::debug::RecordedTransaction;
 
@@ -30,123 +33,278 @@
 static_assert(PADDING8(7) == 1);
 static_assert(PADDING8(8) == 0);
 
-// Transactions are sequentially recorded to the file descriptor in the following format:
+// Transactions are sequentially recorded to a file descriptor.
 //
-// RecordedTransaction.TransactionHeader  (32 bytes)
-// Sent Parcel data                       (getDataSize() bytes)
-// padding                                (enough bytes to align the reply Parcel data to 8 bytes)
-// Reply Parcel data                      (getReplySize() bytes)
-// padding                                (enough bytes to align the next header to 8 bytes)
-// [repeats with next transaction]
+// An individual RecordedTransaction is written with the following format:
 //
-// Warning: This format is non-stable
+// WARNING: Though the following format is designed to be stable and
+// extensible, it is under active development and should be considered
+// unstable until this warning is removed.
+//
+// A RecordedTransaction is written to a file as a sequence of Chunks.
+//
+// A Chunk consists of a ChunkDescriptor, Data, Padding, and a Checksum.
+//
+// The ChunkDescriptor identifies the type of Data in the chunk, and the size
+// of the Data.
+//
+// The Data may be any uint32 number of bytes in length in [0-0xfffffff0].
+//
+// Padding is between [0-7] zero-bytes after the Data such that the Chunk ends
+// on an 8-byte boundary. The ChunkDescriptor's dataSize does not include the
+// size of Padding.
+//
+// The checksum is a 64-bit wide XOR of all previous data from the start of the
+// ChunkDescriptor to the end of Padding.
+//
+// ┌───────────────────────────┐
+// │Chunk                      │
+// │┌────────────────────────┐ │
+// ││ChunkDescriptor         │ │
+// ││┌───────────┬──────────┐│ │
+// │││chunkType  │dataSize  ├┼─┼─┐
+// │││uint32_t   │uint32_t  ││ │ │
+// ││└───────────┴──────────┘│ │ │
+// │└────────────────────────┘ │ │
+// │┌─────────────────────────┐│ │
+// ││Data                     ││ │
+// ││bytes * dataSize         │◀─┘
+// ││   ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤│
+// ││           Padding       ││
+// │└───┴─────────────────────┘│
+// │┌─────────────────────────┐│
+// ││checksum                 ││
+// ││uint64_t                 ││
+// │└─────────────────────────┘│
+// └───────────────────────────┘
+//
+// A RecordedTransaction is written as a Header Chunk with fields about the
+// transaction, a Data Parcel chunk, a Reply Parcel Chunk, and an End Chunk.
+// ┌──────────────────────┐
+// │     Header Chunk     │
+// ├──────────────────────┤
+// │  Sent Parcel Chunk   │
+// ├──────────────────────┤
+// │  Reply Parcel Chunk  │
+// ├──────────────────────┤
+// ║      End Chunk       ║
+// ╚══════════════════════╝
+//
+// On reading a RecordedTransaction, an unrecognized chunk is checksummed
+// then skipped according to size information in the ChunkDescriptor. Chunks
+// are read and either assimilated or skipped until an End Chunk is
+// encountered. This has three notable implications:
+//
+// 1. Older and newer implementations should be able to read one another's
+//    Transactions, though there will be loss of information.
+// 2. With the exception of the End Chunk, Chunks can appear in any order
+//    and even repeat, though this is not recommended.
+// 3. If any Chunk is repeated, old values will be overwritten by versions
+//    encountered later in the file.
+//
+// No effort is made to ensure the expected chunks are present. A single
+// End Chunk may therefore produce an empty, meaningless RecordedTransaction.
 
 RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept {
-    mHeader = {t.getCode(),      t.getFlags(),          t.getDataSize(),
-               t.getReplySize(), t.getReturnedStatus(), t.getVersion()};
-    mSent.setData(t.getDataParcel().data(), t.getDataSize());
-    mReply.setData(t.getReplyParcel().data(), t.getReplySize());
+    mHeader = t.mHeader;
+    mSent.setData(t.getDataParcel().data(), t.getDataParcel().dataSize());
+    mReply.setData(t.getReplyParcel().data(), t.getReplyParcel().dataSize());
 }
 
 std::optional<RecordedTransaction> RecordedTransaction::fromDetails(uint32_t code, uint32_t flags,
+                                                                    timespec timestamp,
                                                                     const Parcel& dataParcel,
                                                                     const Parcel& replyParcel,
                                                                     status_t err) {
     RecordedTransaction t;
     t.mHeader = {code,
                  flags,
-                 static_cast<uint64_t>(dataParcel.dataSize()),
-                 static_cast<uint64_t>(replyParcel.dataSize()),
                  static_cast<int32_t>(err),
-                 dataParcel.isForRpc() ? static_cast<uint32_t>(1) : static_cast<uint32_t>(0)};
+                 dataParcel.isForRpc() ? static_cast<uint32_t>(1) : static_cast<uint32_t>(0),
+                 static_cast<int64_t>(timestamp.tv_sec),
+                 static_cast<int32_t>(timestamp.tv_nsec),
+                 0};
 
-    if (t.mSent.setData(dataParcel.data(), t.getDataSize()) != android::NO_ERROR) {
-        LOG(INFO) << "Failed to set sent parcel data.";
+    if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) {
+        LOG(ERROR) << "Failed to set sent parcel data.";
         return std::nullopt;
     }
 
-    if (t.mReply.setData(replyParcel.data(), t.getReplySize()) != android::NO_ERROR) {
-        LOG(INFO) << "Failed to set reply parcel data.";
+    if (t.mReply.setData(replyParcel.data(), replyParcel.dataSize()) != android::NO_ERROR) {
+        LOG(ERROR) << "Failed to set reply parcel data.";
         return std::nullopt;
     }
 
     return std::optional<RecordedTransaction>(std::move(t));
 }
 
+enum {
+    HEADER_CHUNK = 1,
+    DATA_PARCEL_CHUNK = 2,
+    REPLY_PARCEL_CHUNK = 3,
+    END_CHUNK = 0x00ffffff,
+};
+
+struct ChunkDescriptor {
+    uint32_t chunkType = 0;
+    uint32_t dataSize = 0;
+};
+static_assert(sizeof(ChunkDescriptor) % 8 == 0);
+
+constexpr uint32_t kMaxChunkDataSize = 0xfffffff0;
+typedef uint64_t transaction_checksum_t;
+
+static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut,
+                                             transaction_checksum_t* sum) {
+    if (!android::base::ReadFully(fd, chunkOut, sizeof(ChunkDescriptor))) {
+        LOG(ERROR) << "Failed to read Chunk Descriptor from fd " << fd.get();
+        return android::UNKNOWN_ERROR;
+    }
+
+    *sum ^= *reinterpret_cast<transaction_checksum_t*>(chunkOut);
+    return android::NO_ERROR;
+}
+
 std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) {
     RecordedTransaction t;
-    if (!android::base::ReadFully(fd, &t.mHeader, sizeof(mHeader))) {
-        LOG(INFO) << "Failed to read transactionHeader from fd " << fd.get();
-        return std::nullopt;
-    }
-    if (t.getVersion() != 0) {
-        LOG(INFO) << "File corrupted: transaction version is not 0.";
-        return std::nullopt;
-    }
+    ChunkDescriptor chunk;
+    const long pageSize = sysconf(_SC_PAGE_SIZE);
+    do {
+        transaction_checksum_t checksum = 0;
+        if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) {
+            LOG(ERROR) << "Failed to read chunk descriptor.";
+            return std::nullopt;
+        }
+        off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
+        off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize;
+        off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart;
 
-    std::vector<uint8_t> bytes;
-    bytes.resize(t.getDataSize());
-    if (!android::base::ReadFully(fd, bytes.data(), t.getDataSize())) {
-        LOG(INFO) << "Failed to read sent parcel data from fd " << fd.get();
-        return std::nullopt;
-    }
-    if (t.mSent.setData(bytes.data(), t.getDataSize()) != android::NO_ERROR) {
-        LOG(INFO) << "Failed to set sent parcel data.";
-        return std::nullopt;
-    }
+        if (chunk.dataSize > kMaxChunkDataSize) {
+            LOG(ERROR) << "Chunk data exceeds maximum size.";
+            return std::nullopt;
+        }
 
-    uint8_t padding[7];
-    if (!android::base::ReadFully(fd, padding, PADDING8(t.getDataSize()))) {
-        LOG(INFO) << "Failed to read sent parcel padding from fd " << fd.get();
-        return std::nullopt;
-    }
-    if (std::any_of(padding, padding + 7, [](uint8_t i) { return i != 0; })) {
-        LOG(INFO) << "File corrupted: padding isn't 0.";
-        return std::nullopt;
-    }
+        size_t chunkPayloadSize =
+                chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t);
 
-    bytes.resize(t.getReplySize());
-    if (!android::base::ReadFully(fd, bytes.data(), t.getReplySize())) {
-        LOG(INFO) << "Failed to read reply parcel data from fd " << fd.get();
-        return std::nullopt;
-    }
-    if (t.mReply.setData(bytes.data(), t.getReplySize()) != android::NO_ERROR) {
-        LOG(INFO) << "Failed to set reply parcel data.";
-        return std::nullopt;
-    }
+        if (PADDING8(chunkPayloadSize) != 0) {
+            LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize;
+            return std::nullopt;
+        }
 
-    if (!android::base::ReadFully(fd, padding, PADDING8(t.getReplySize()))) {
-        LOG(INFO) << "Failed to read parcel padding from fd " << fd.get();
-        return std::nullopt;
-    }
-    if (std::any_of(padding, padding + 7, [](uint8_t i) { return i != 0; })) {
-        LOG(INFO) << "File corrupted: padding isn't 0.";
-        return std::nullopt;
-    }
+        transaction_checksum_t* payloadMap = reinterpret_cast<transaction_checksum_t*>(
+                mmap(NULL, chunkPayloadSize + mmapPayloadStartOffset, PROT_READ, MAP_SHARED,
+                     fd.get(), mmapPageAlignedStart));
+        payloadMap += mmapPayloadStartOffset /
+                sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap
+                                                // page-alignment
+        if (payloadMap == MAP_FAILED) {
+            LOG(ERROR) << "Memory mapping failed for fd " << fd.get() << ": " << errno << " "
+                       << strerror(errno);
+            return std::nullopt;
+        }
+        for (size_t checksumIndex = 0;
+             checksumIndex < chunkPayloadSize / sizeof(transaction_checksum_t); checksumIndex++) {
+            checksum ^= payloadMap[checksumIndex];
+        }
+        if (checksum != 0) {
+            LOG(ERROR) << "Checksum failed.";
+            return std::nullopt;
+        }
+        lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
+
+        switch (chunk.chunkType) {
+            case HEADER_CHUNK: {
+                if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) {
+                    LOG(ERROR) << "Header Chunk indicated size " << chunk.dataSize << "; Expected "
+                               << sizeof(TransactionHeader) << ".";
+                    return std::nullopt;
+                }
+                t.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
+                break;
+            }
+            case DATA_PARCEL_CHUNK: {
+                if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap),
+                                    chunk.dataSize) != android::NO_ERROR) {
+                    LOG(ERROR) << "Failed to set sent parcel data.";
+                    return std::nullopt;
+                }
+                break;
+            }
+            case REPLY_PARCEL_CHUNK: {
+                if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap),
+                                     chunk.dataSize) != android::NO_ERROR) {
+                    LOG(ERROR) << "Failed to set reply parcel data.";
+                    return std::nullopt;
+                }
+                break;
+            }
+            case END_CHUNK:
+                break;
+            default:
+                LOG(INFO) << "Unrecognized chunk.";
+                continue;
+        }
+    } while (chunk.chunkType != END_CHUNK);
 
     return std::optional<RecordedTransaction>(std::move(t));
 }
 
+android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType,
+                                                  size_t byteCount, const uint8_t* data) const {
+    if (byteCount > kMaxChunkDataSize) {
+        LOG(ERROR) << "Chunk data exceeds maximum size";
+        return BAD_VALUE;
+    }
+    ChunkDescriptor descriptor = {.chunkType = chunkType,
+                                  .dataSize = static_cast<uint32_t>(byteCount)};
+    // Prepare Chunk content as byte *
+    const std::byte* descriptorBytes = reinterpret_cast<const std::byte*>(&descriptor);
+    const std::byte* dataBytes = reinterpret_cast<const std::byte*>(data);
+
+    // Add Chunk to intermediate buffer, except checksum
+    std::vector<std::byte> buffer;
+    buffer.insert(buffer.end(), descriptorBytes, descriptorBytes + sizeof(ChunkDescriptor));
+    buffer.insert(buffer.end(), dataBytes, dataBytes + byteCount);
+    std::byte zero{0};
+    buffer.insert(buffer.end(), PADDING8(byteCount), zero);
+
+    // Calculate checksum from buffer
+    transaction_checksum_t* checksumData = reinterpret_cast<transaction_checksum_t*>(buffer.data());
+    transaction_checksum_t checksumValue = 0;
+    for (size_t idx = 0; idx < (buffer.size() / sizeof(transaction_checksum_t)); idx++) {
+        checksumValue ^= checksumData[idx];
+    }
+
+    // Write checksum to buffer
+    std::byte* checksumBytes = reinterpret_cast<std::byte*>(&checksumValue);
+    buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t));
+
+    // Write buffer to file
+    if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) {
+        LOG(ERROR) << "Failed to write chunk fd " << fd.get();
+        return UNKNOWN_ERROR;
+    }
+    return NO_ERROR;
+}
+
 android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const {
-    if (!android::base::WriteFully(fd, &mHeader, sizeof(mHeader))) {
-        LOG(INFO) << "Failed to write transactionHeader to fd " << fd.get();
+    if (NO_ERROR !=
+        writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader),
+                   reinterpret_cast<const uint8_t*>(&mHeader))) {
+        LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
-    if (!android::base::WriteFully(fd, mSent.data(), getDataSize())) {
-        LOG(INFO) << "Failed to write sent parcel data to fd " << fd.get();
+    if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) {
+        LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
-    const uint8_t zeros[7] = {0};
-    if (!android::base::WriteFully(fd, zeros, PADDING8(getDataSize()))) {
-        LOG(INFO) << "Failed to write sent parcel padding to fd " << fd.get();
+    if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataSize(), mReply.data())) {
+        LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
-    if (!android::base::WriteFully(fd, mReply.data(), getReplySize())) {
-        LOG(INFO) << "Failed to write reply parcel data to fd " << fd.get();
-        return UNKNOWN_ERROR;
-    }
-    if (!android::base::WriteFully(fd, zeros, PADDING8(getReplySize()))) {
-        LOG(INFO) << "Failed to write reply parcel padding to fd " << fd.get();
+    if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) {
+        LOG(ERROR) << "Failed to write end chunk to fd " << fd.get();
         return UNKNOWN_ERROR;
     }
     return NO_ERROR;
@@ -160,18 +318,16 @@
     return mHeader.flags;
 }
 
-uint64_t RecordedTransaction::getDataSize() const {
-    return mHeader.dataSize;
-}
-
-uint64_t RecordedTransaction::getReplySize() const {
-    return mHeader.replySize;
-}
-
 int32_t RecordedTransaction::getReturnedStatus() const {
     return mHeader.statusReturned;
 }
 
+timespec RecordedTransaction::getTimestamp() const {
+    time_t sec = mHeader.timestampSeconds;
+    int32_t nsec = mHeader.timestampNanoseconds;
+    return (timespec){.tv_sec = sec, .tv_nsec = nsec};
+}
+
 uint32_t RecordedTransaction::getVersion() const {
     return mHeader.version;
 }
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 6d64e1e..da58251 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -1221,6 +1221,10 @@
         return NO_ERROR;
     }
 
+    ALOGE_IF(mProcess->mDriverFD >= 0,
+             "Driver returned error (%s). This is a bug in either libbinder or the driver. This "
+             "thread's connection to %s will no longer work.",
+             statusToString(err).c_str(), mProcess->mDriverName.c_str());
     return err;
 }
 
diff --git a/libs/binder/include/binder/BinderRecordReplay.h b/libs/binder/include/binder/BinderRecordReplay.h
index 25ed5e5..ff983f0 100644
--- a/libs/binder/include/binder/BinderRecordReplay.h
+++ b/libs/binder/include/binder/BinderRecordReplay.h
@@ -34,17 +34,16 @@
     static std::optional<RecordedTransaction> fromFile(const android::base::unique_fd& fd);
     // Filled with the arguments.
     static std::optional<RecordedTransaction> fromDetails(uint32_t code, uint32_t flags,
-                                                          const Parcel& data, const Parcel& reply,
-                                                          status_t err);
+                                                          timespec timestamp, const Parcel& data,
+                                                          const Parcel& reply, status_t err);
     RecordedTransaction(RecordedTransaction&& t) noexcept;
 
     [[nodiscard]] status_t dumpToFile(const android::base::unique_fd& fd) const;
 
     uint32_t getCode() const;
     uint32_t getFlags() const;
-    uint64_t getDataSize() const;
-    uint64_t getReplySize() const;
     int32_t getReturnedStatus() const;
+    timespec getTimestamp() const;
     uint32_t getVersion() const;
     const Parcel& getDataParcel() const;
     const Parcel& getReplyParcel() const;
@@ -52,15 +51,19 @@
 private:
     RecordedTransaction() = default;
 
+    android::status_t writeChunk(const android::base::borrowed_fd, uint32_t chunkType,
+                                 size_t byteCount, const uint8_t* data) const;
+
 #pragma clang diagnostic push
 #pragma clang diagnostic error "-Wpadded"
     struct TransactionHeader {
         uint32_t code = 0;
         uint32_t flags = 0;
-        uint64_t dataSize = 0;
-        uint64_t replySize = 0;
         int32_t statusReturned = 0;
         uint32_t version = 0; // !0 iff Rpc
+        int64_t timestampSeconds = 0;
+        int32_t timestampNanoseconds = 0;
+        int32_t reserved = 0;
     };
 #pragma clang diagnostic pop
     static_assert(sizeof(TransactionHeader) == 32);
@@ -69,10 +72,6 @@
     TransactionHeader mHeader;
     Parcel mSent;
     Parcel mReply;
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wunused-private-field"
-    uint8_t mReserved[40];
-#pragma clang diagnostic pop
 };
 
 } // namespace binder::debug
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index 8cc8105..dc572ac 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -230,6 +230,7 @@
         "android.graphicsenv.IGpuService",
         "android.gui.IConsumerListener",
         "android.gui.IGraphicBufferConsumer",
+        "android.gui.ITransactionComposerListener",
         "android.gui.SensorEventConnection",
         "android.gui.SensorServer",
         "android.hardware.ICamera",
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index 2af512e..c78f870 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -198,7 +198,10 @@
 {
     const sp<IServiceManager> sm = defaultServiceManager();
     if (sm != nullptr) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
         *outService = interface_cast<INTERFACE>(sm->getService(name));
+#pragma clang diagnostic pop // getService deprecation
         if ((*outService) != nullptr) return NO_ERROR;
     }
     return NAME_NOT_FOUND;
diff --git a/libs/binder/include/binder/ParcelFileDescriptor.h b/libs/binder/include/binder/ParcelFileDescriptor.h
index 9896fd7..08d8e43 100644
--- a/libs/binder/include/binder/ParcelFileDescriptor.h
+++ b/libs/binder/include/binder/ParcelFileDescriptor.h
@@ -42,6 +42,7 @@
     android::status_t writeToParcel(android::Parcel* parcel) const override;
     android::status_t readFromParcel(const android::Parcel* parcel) override;
 
+    inline std::string toString() const { return "ParcelFileDescriptor:" + std::to_string(get()); }
     inline bool operator!=(const ParcelFileDescriptor& rhs) const {
         return mFd.get() != rhs.mFd.get();
     }
diff --git a/libs/binder/include/binder/ParcelableHolder.h b/libs/binder/include/binder/ParcelableHolder.h
index 88790a8..40fd30a 100644
--- a/libs/binder/include/binder/ParcelableHolder.h
+++ b/libs/binder/include/binder/ParcelableHolder.h
@@ -111,6 +111,11 @@
 
     Stability getStability() const override { return mStability; }
 
+    inline std::string toString() const {
+        return "ParcelableHolder:" +
+                (mParcelableName ? std::string(String8(mParcelableName.value()).c_str())
+                                 : "<parceled>");
+    }
     inline bool operator!=(const ParcelableHolder& rhs) const {
         return this != &rhs;
     }
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index 78dae4b..e7943dd 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -112,6 +112,13 @@
         LOG(ERROR) << "Failed to get fd for the socket:" << name;
         return nullptr;
     }
+    // Control socket fds are inherited from init, so they don't have O_CLOEXEC set.
+    // But we don't want any child processes to inherit the socket we are running
+    // the server on, so attempt to set the flag now.
+    if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) {
+        LOG(WARNING) << "Failed to set CLOEXEC on control socket with name " << name
+                     << " error: " << errno;
+    }
     if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
         LOG(ERROR) << "Failed to set up Unix Domain RPC server with name " << name
                    << " error: " << statusToString(status).c_str();
diff --git a/libs/binder/ndk/include_cpp/android/binder_to_string.h b/libs/binder/ndk/include_cpp/android/binder_to_string.h
index 2a00736..9b0d222 100644
--- a/libs/binder/ndk/include_cpp/android/binder_to_string.h
+++ b/libs/binder/ndk/include_cpp/android/binder_to_string.h
@@ -49,12 +49,17 @@
 #include <android/binder_interface_utils.h>
 #include <android/binder_parcelable_utils.h>
 #define HAS_NDK_INTERFACE
-#else
+#endif
+
+// TODO: some things include libbinder without having access to libbase. This is
+// due to frameworks/native/include, which symlinks to libbinder headers, so even
+// though we don't use it here, we detect a different header, so that we are more
+// confident libbase will be included
+#if __has_include(<binder/RpcSession.h>)
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
-#include <binder/ParcelFileDescriptor.h>
-#include <binder/ParcelableHolder.h>
-#endif  //_has_include
+#define HAS_CPP_INTERFACE
+#endif
 
 namespace android {
 namespace internal {
@@ -104,10 +109,12 @@
             IsInstantiationOf<_U, sp>::value ||  // for IBinder and interface types in the C++
                                                  // backend
 #endif
-                    IsInstantiationOf<_U, std::optional>::value ||  // for @nullable types in the
-                                                                    // C++/NDK backends
-                    IsInstantiationOf<_U, std::shared_ptr>::value,  // for interface types in the
-                                                                    // NDK backends
+                    IsInstantiationOf<_U, std::optional>::value ||    // for @nullable types in the
+                                                                      // C++/NDK backends
+                    IsInstantiationOf<_U, std::unique_ptr>::value ||  // for @nullable(heap=true)
+                                                                      // in C++/NDK backends
+                    IsInstantiationOf<_U, std::shared_ptr>::value,    // for interface types in the
+                                                                      // NDK backends
 
             std::true_type>
     _test(int);
@@ -134,19 +141,19 @@
 template <typename _T>
 class ToEmptyString {
     template <typename _U>
-    static std::enable_if_t<
+    static std::enable_if_t<false
 #ifdef HAS_NDK_INTERFACE
-            std::is_base_of_v<::ndk::ICInterface, _U>
+                                    || std::is_base_of_v<::ndk::ICInterface, _U>
 #if __ANDROID_API__ >= 31
-                    || std::is_same_v<::ndk::AParcelableHolder, _U>
+                                    || std::is_same_v<::ndk::AParcelableHolder, _U>
 #endif
-#else
-            std::is_base_of_v<IInterface, _U> || std::is_same_v<IBinder, _U> ||
-                    std::is_same_v<os::ParcelFileDescriptor, _U> ||
-                    std::is_same_v<os::ParcelableHolder, _U>
+#endif  // HAS_NDK_INTERFACE
+#ifdef HAS_CPP_INTERFACE
+                                    || std::is_base_of_v<IInterface, _U> ||
+                                    std::is_same_v<IBinder, _U>
 #endif
-            ,
-            std::true_type>
+                            ,
+                            std::true_type>
     _test(int);
     template <typename _U>
     static std::false_type _test(...);
@@ -155,6 +162,11 @@
     enum { value = decltype(_test<_T>(0))::value };
 };
 
+template <typename _T>
+struct TypeDependentFalse {
+    enum { value = false };
+};
+
 }  // namespace details
 
 template <typename _T>
@@ -214,11 +226,27 @@
         out << "]";
         return out.str();
     } else {
-        return "{no toString() implemented}";
+        static_assert(details::TypeDependentFalse<_T>::value, "no toString implemented, huh?");
     }
 }
 
 }  // namespace internal
 }  // namespace android
 
+#ifdef HAS_STRONG_POINTER
+#undef HAS_STRONG_POINTER
+#endif
+
+#ifdef HAS_STRING16
+#undef HAS_STRING16
+#endif
+
+#ifdef HAS_NDK_INTERFACE
+#undef HAS_NDK_INTERFACE
+#endif
+
+#ifdef HAS_CPP_INTERFACE
+#undef HAS_CPP_INTERFACE
+#endif
+
 /** @} */
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index a999d59..5db3187 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -100,6 +100,7 @@
         "binderBinderUnitTest.cpp",
         "binderStatusUnitTest.cpp",
         "binderMemoryHeapBaseUnitTest.cpp",
+        "binderRecordedTransactionTest.cpp",
     ],
     shared_libs: [
         "libbinder",
diff --git a/libs/binder/tests/binderAllocationLimits.cpp b/libs/binder/tests/binderAllocationLimits.cpp
index 6a6e008..bc40864 100644
--- a/libs/binder/tests/binderAllocationLimits.cpp
+++ b/libs/binder/tests/binderAllocationLimits.cpp
@@ -180,7 +180,11 @@
         mallocs++;
         // Happens to be SM package length. We could switch to forking
         // and registering our own service if it became an issue.
+#if defined(__LP64__)
         EXPECT_EQ(bytes, 78);
+#else
+        EXPECT_EQ(bytes, 70);
+#endif
     });
 
     a_binder->getInterfaceDescriptor();
diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp
new file mode 100644
index 0000000..67553fc
--- /dev/null
+++ b/libs/binder/tests/binderRecordedTransactionTest.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 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 <binder/BinderRecordReplay.h>
+#include <gtest/gtest.h>
+#include <utils/Errors.h>
+
+using android::Parcel;
+using android::status_t;
+using android::base::unique_fd;
+using android::binder::debug::RecordedTransaction;
+
+TEST(BinderRecordedTransaction, RoundTripEncoding) {
+    Parcel d;
+    d.writeInt32(12);
+    d.writeInt64(2);
+    Parcel r;
+    r.writeInt32(99);
+    timespec ts = {1232456, 567890};
+    auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+    auto file = std::tmpfile();
+    auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
+
+    status_t status = transaction->dumpToFile(fd);
+    ASSERT_EQ(android::NO_ERROR, status);
+
+    std::rewind(file);
+
+    auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+
+    EXPECT_EQ(retrievedTransaction->getCode(), 1);
+    EXPECT_EQ(retrievedTransaction->getFlags(), 42);
+    EXPECT_EQ(retrievedTransaction->getTimestamp().tv_sec, ts.tv_sec);
+    EXPECT_EQ(retrievedTransaction->getTimestamp().tv_nsec, ts.tv_nsec);
+    EXPECT_EQ(retrievedTransaction->getDataParcel().dataSize(), 12);
+    EXPECT_EQ(retrievedTransaction->getReplyParcel().dataSize(), 4);
+    EXPECT_EQ(retrievedTransaction->getReturnedStatus(), 0);
+    EXPECT_EQ(retrievedTransaction->getVersion(), 0);
+
+    EXPECT_EQ(retrievedTransaction->getDataParcel().readInt32(), 12);
+    EXPECT_EQ(retrievedTransaction->getDataParcel().readInt64(), 2);
+    EXPECT_EQ(retrievedTransaction->getReplyParcel().readInt32(), 99);
+}
+
+TEST(BinderRecordedTransaction, Checksum) {
+    Parcel d;
+    d.writeInt32(12);
+    d.writeInt64(2);
+    Parcel r;
+    r.writeInt32(99);
+    timespec ts = {1232456, 567890};
+    auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+    auto file = std::tmpfile();
+    auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
+
+    status_t status = transaction->dumpToFile(fd);
+    ASSERT_EQ(android::NO_ERROR, status);
+
+    lseek(fd.get(), 9, SEEK_SET);
+    uint32_t badData = 0xffffffff;
+    write(fd.get(), &badData, sizeof(uint32_t));
+    std::rewind(file);
+
+    auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+
+    EXPECT_FALSE(retrievedTransaction.has_value());
+}
+
+TEST(BinderRecordedTransaction, PayloadsExceedPageBoundaries) {
+    // File contents are read with mmap.
+    // This test verifies that transactions are read from portions
+    // of files that cross page boundaries and don't start at a
+    // page boundary offset of the fd.
+    const size_t pageSize = sysconf(_SC_PAGE_SIZE);
+    const size_t largeDataSize = pageSize + 100;
+    std::vector<uint8_t> largePayload;
+    uint8_t filler = 0xaa;
+    largePayload.insert(largePayload.end(), largeDataSize, filler);
+    Parcel d;
+    d.writeInt32(12);
+    d.writeInt64(2);
+    d.writeByteVector(largePayload);
+    Parcel r;
+    r.writeInt32(99);
+    timespec ts = {1232456, 567890};
+    auto transaction = RecordedTransaction::fromDetails(1, 42, ts, d, r, 0);
+
+    auto file = std::tmpfile();
+    auto fd = unique_fd(fcntl(fileno(file), F_DUPFD, 1));
+
+    // Write to file twice
+    status_t status = transaction->dumpToFile(fd);
+    ASSERT_EQ(android::NO_ERROR, status);
+    status = transaction->dumpToFile(fd);
+    ASSERT_EQ(android::NO_ERROR, status);
+
+    std::rewind(file);
+
+    for (int i = 0; i < 2; i++) {
+        auto retrievedTransaction = RecordedTransaction::fromFile(fd);
+
+        EXPECT_EQ(retrievedTransaction->getCode(), 1);
+        EXPECT_EQ(retrievedTransaction->getFlags(), 42);
+        EXPECT_EQ(retrievedTransaction->getTimestamp().tv_sec, ts.tv_sec);
+        EXPECT_EQ(retrievedTransaction->getTimestamp().tv_nsec, ts.tv_nsec);
+        EXPECT_EQ(retrievedTransaction->getDataParcel().dataSize(), d.dataSize());
+        EXPECT_EQ(retrievedTransaction->getReplyParcel().dataSize(), 4);
+        EXPECT_EQ(retrievedTransaction->getReturnedStatus(), 0);
+        EXPECT_EQ(retrievedTransaction->getVersion(), 0);
+
+        EXPECT_EQ(retrievedTransaction->getDataParcel().readInt32(), 12);
+        EXPECT_EQ(retrievedTransaction->getDataParcel().readInt64(), 2);
+        std::optional<std::vector<uint8_t>> payloadOut;
+        EXPECT_EQ(retrievedTransaction->getDataParcel().readByteVector(&payloadOut), android::OK);
+        EXPECT_EQ(payloadOut.value(), largePayload);
+
+        EXPECT_EQ(retrievedTransaction->getReplyParcel().readInt32(), 99);
+    }
+}
diff --git a/libs/binder/tests/binderUtilsHostTest.cpp b/libs/binder/tests/binderUtilsHostTest.cpp
index 4330e3e..25e286c 100644
--- a/libs/binder/tests/binderUtilsHostTest.cpp
+++ b/libs/binder/tests/binderUtilsHostTest.cpp
@@ -37,17 +37,24 @@
     EXPECT_EQ(result->stdoutStr, "foo\n");
 }
 
+template <typename T>
+auto millisSince(std::chrono::time_point<T> now) {
+    auto elapsed = std::chrono::system_clock::now() - now;
+    return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+}
+
 TEST(UtilsHost, ExecuteLongRunning) {
-    auto now = std::chrono::system_clock::now();
+    auto start = std::chrono::system_clock::now();
 
     {
-        std::vector<std::string> args{"sh", "-c",
-                                      "sleep 0.5 && echo -n f && sleep 0.5 && echo oo && sleep 1"};
-        auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+        std::vector<std::string>
+                args{"sh", "-c", "sleep 0.5 && echo -n f && sleep 0.5 && echo oo && sleep 100"};
+        auto result = execute(std::move(args), [&](const CommandResult& commandResult) {
+            std::cout << millisSince(start)
+                      << "ms: GOT PARTIAL COMMAND RESULT:" << commandResult.stdoutStr << std::endl;
             return android::base::EndsWith(commandResult.stdoutStr, "\n");
         });
-        auto elapsed = std::chrono::system_clock::now() - now;
-        auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+        auto elapsedMs = millisSince(start);
         EXPECT_GE(elapsedMs, 1000);
         EXPECT_LT(elapsedMs, 2000);
 
@@ -58,22 +65,21 @@
 
     // ~CommandResult() called, child process is killed.
     // Assert that the second sleep does not finish.
-    auto elapsed = std::chrono::system_clock::now() - now;
-    auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
-    EXPECT_LT(elapsedMs, 2000);
+    EXPECT_LT(millisSince(start), 2000);
 }
 
 TEST(UtilsHost, ExecuteLongRunning2) {
-    auto now = std::chrono::system_clock::now();
+    auto start = std::chrono::system_clock::now();
 
     {
         std::vector<std::string> args{"sh", "-c",
-                                      "sleep 2 && echo -n f && sleep 2 && echo oo && sleep 2"};
-        auto result = execute(std::move(args), [](const CommandResult& commandResult) {
+                                      "sleep 2 && echo -n f && sleep 2 && echo oo && sleep 100"};
+        auto result = execute(std::move(args), [&](const CommandResult& commandResult) {
+            std::cout << millisSince(start)
+                      << "ms: GOT PARTIAL COMMAND RESULT:" << commandResult.stdoutStr << std::endl;
             return android::base::EndsWith(commandResult.stdoutStr, "\n");
         });
-        auto elapsed = std::chrono::system_clock::now() - now;
-        auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
+        auto elapsedMs = millisSince(start);
         EXPECT_GE(elapsedMs, 4000);
         EXPECT_LT(elapsedMs, 6000);
 
@@ -84,9 +90,7 @@
 
     // ~CommandResult() called, child process is killed.
     // Assert that the second sleep does not finish.
-    auto elapsed = std::chrono::system_clock::now() - now;
-    auto elapsedMs = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
-    EXPECT_LT(elapsedMs, 6000);
+    EXPECT_LT(millisSince(start), 6000);
 }
 
 TEST(UtilsHost, KillWithSigKill) {
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
index 53e7de4..08eb27a 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
@@ -199,5 +199,25 @@
             binder_status_t status = genericDataParcelable.readFromParcel(p.aParcel());
             FUZZ_LOG() << "status: " << status;
         },
+        [](const NdkParcelAdapter& p, FuzzedDataProvider& provider) {
+            FUZZ_LOG() << "about to marshal AParcel";
+            size_t start = provider.ConsumeIntegral<size_t>();
+            // limit 1MB to avoid OOM issues
+            size_t len = provider.ConsumeIntegralInRange<size_t>(0, 1000000);
+            uint8_t buffer[len];
+            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";
+            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);
+            FUZZ_LOG() << "status: " << status;
+        },
+
 };
 // clang-format on
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 97e45c6..797d6ae 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -483,20 +483,18 @@
     mSyncedFrameNumbers.erase(callbackId.framenumber);
 }
 
-void BLASTBufferQueue::acquireNextBufferLocked(
+status_t BLASTBufferQueue::acquireNextBufferLocked(
         const std::optional<SurfaceComposerClient::Transaction*> transaction) {
     // If the next transaction is set, we want to guarantee the our acquire will not fail, so don't
     // include the extra buffer when checking if we can acquire the next buffer.
-    const bool includeExtraAcquire = !transaction;
-    const bool maxAcquired = maxBuffersAcquired(includeExtraAcquire);
-    if (mNumFrameAvailable == 0 || maxAcquired) {
-        BQA_LOGV("Can't process next buffer maxBuffersAcquired=%s", boolToString(maxAcquired));
-        return;
+    if (mNumFrameAvailable == 0) {
+        BQA_LOGV("Can't process next buffer. No available frames");
+        return NOT_ENOUGH_DATA;
     }
 
     if (mSurfaceControl == nullptr) {
         BQA_LOGE("ERROR : surface control is null");
-        return;
+        return NAME_NOT_FOUND;
     }
 
     SurfaceComposerClient::Transaction localTransaction;
@@ -513,10 +511,10 @@
             mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false);
     if (status == BufferQueue::NO_BUFFER_AVAILABLE) {
         BQA_LOGV("Failed to acquire a buffer, err=NO_BUFFER_AVAILABLE");
-        return;
+        return status;
     } else if (status != OK) {
         BQA_LOGE("Failed to acquire a buffer, err=%s", statusToString(status).c_str());
-        return;
+        return status;
     }
 
     auto buffer = bufferItem.mGraphicBuffer;
@@ -526,7 +524,7 @@
     if (buffer == nullptr) {
         mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
         BQA_LOGE("Buffer was empty");
-        return;
+        return BAD_VALUE;
     }
 
     if (rejectBuffer(bufferItem)) {
@@ -535,8 +533,7 @@
                  mSize.width, mSize.height, mRequestedSize.width, mRequestedSize.height,
                  buffer->getWidth(), buffer->getHeight(), bufferItem.mTransform);
         mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
-        acquireNextBufferLocked(transaction);
-        return;
+        return acquireNextBufferLocked(transaction);
     }
 
     mNumAcquired++;
@@ -624,6 +621,7 @@
              bufferItem.mTimestamp, bufferItem.mIsAutoTimestamp ? "(auto)" : "",
              static_cast<uint32_t>(mPendingTransactions.size()), bufferItem.mGraphicBuffer->getId(),
              bufferItem.mAutoRefresh ? " mAutoRefresh" : "", bufferItem.mTransform);
+    return OK;
 }
 
 Rect BLASTBufferQueue::computeCrop(const BufferItem& item) {
@@ -647,32 +645,6 @@
     mBufferItemConsumer->releaseBuffer(bufferItem, bufferItem.mFence);
 }
 
-void BLASTBufferQueue::flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock) {
-    BBQ_TRACE();
-    if (!mSyncedFrameNumbers.empty() && mNumFrameAvailable > 0) {
-        // We are waiting on a previous sync's transaction callback so allow another sync
-        // transaction to proceed.
-        //
-        // We need to first flush out the transactions that were in between the two syncs.
-        // We do this by merging them into mSyncTransaction so any buffer merging will get
-        // a release callback invoked. The release callback will be async so we need to wait
-        // on max acquired to make sure we have the capacity to acquire another buffer.
-        if (maxBuffersAcquired(false /* includeExtraAcquire */)) {
-            BQA_LOGD("waiting to flush shadow queue...");
-            mCallbackCV.wait(lock);
-        }
-        while (mNumFrameAvailable > 0) {
-            // flush out the shadow queue
-            acquireAndReleaseBuffer();
-        }
-    }
-
-    while (maxBuffersAcquired(false /* includeExtraAcquire */)) {
-        BQA_LOGD("waiting for free buffer.");
-        mCallbackCV.wait(lock);
-    }
-}
-
 void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
     std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
     SurfaceComposerClient::Transaction* prevTransaction = nullptr;
@@ -685,7 +657,6 @@
         BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet));
 
         if (syncTransactionSet) {
-            bool mayNeedToWaitForBuffer = true;
             // If we are going to re-use the same mSyncTransaction, release the buffer that may
             // already be set in the Transaction. This is to allow us a free slot early to continue
             // processing a new buffer.
@@ -696,14 +667,20 @@
                              bufferData->frameNumber);
                     releaseBuffer(bufferData->generateReleaseCallbackId(),
                                   bufferData->acquireFence);
-                    // Because we just released a buffer, we know there's no need to wait for a free
-                    // buffer.
-                    mayNeedToWaitForBuffer = false;
                 }
             }
 
-            if (mayNeedToWaitForBuffer) {
-                flushAndWaitForFreeBuffer(_lock);
+            if (waitForTransactionCallback) {
+                // We are waiting on a previous sync's transaction callback so allow another sync
+                // transaction to proceed.
+                //
+                // We need to first flush out the transactions that were in between the two syncs.
+                // We do this by merging them into mSyncTransaction so any buffer merging will get
+                // a release callback invoked.
+                while (mNumFrameAvailable > 0) {
+                    // flush out the shadow queue
+                    acquireAndReleaseBuffer();
+                }
             }
         }
 
@@ -719,7 +696,12 @@
                  item.mFrameNumber, boolToString(syncTransactionSet));
 
         if (syncTransactionSet) {
-            acquireNextBufferLocked(mSyncTransaction);
+            // If there's no available buffer and we're in a sync transaction, we need to wait
+            // instead of returning since we guarantee a buffer will be acquired for the sync.
+            while (acquireNextBufferLocked(mSyncTransaction) == BufferQueue::NO_BUFFER_AVAILABLE) {
+                BQA_LOGD("waiting for available buffer");
+                mCallbackCV.wait(_lock);
+            }
 
             // Only need a commit callback when syncing to ensure the buffer that's synced has been
             // sent to SF
@@ -829,15 +811,6 @@
     return mSize != bufferSize;
 }
 
-// Check if we have acquired the maximum number of buffers.
-// Consumer can acquire an additional buffer if that buffer is not droppable. Set
-// includeExtraAcquire is true to include this buffer to the count. Since this depends on the state
-// of the buffer, the next acquire may return with NO_BUFFER_AVAILABLE.
-bool BLASTBufferQueue::maxBuffersAcquired(bool includeExtraAcquire) const {
-    int maxAcquiredBuffers = mMaxAcquiredBuffers + (includeExtraAcquire ? 2 : 1);
-    return mNumAcquired >= maxAcquiredBuffers;
-}
-
 class BBQSurface : public Surface {
 private:
     std::mutex mMutex;
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index a0e75ff..a77ca04 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -42,7 +42,6 @@
 
 namespace android {
 
-using gui::CallbackId;
 using gui::DisplayCaptureArgs;
 using gui::IDisplayEventConnection;
 using gui::IRegionSamplingListener;
diff --git a/libs/gui/ITransactionCompletedListener.cpp b/libs/gui/ITransactionCompletedListener.cpp
index 23d7d50..2b25b61 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -21,11 +21,22 @@
 #include <optional>
 
 #include <gui/ISurfaceComposer.h>
+#include <gui/ITransactionCompletedListener.h>
 #include <gui/LayerState.h>
-#include <gui/ListenerStats.h>
 #include <private/gui/ParcelUtils.h>
 
-namespace android::gui {
+namespace android {
+
+namespace { // Anonymous
+
+enum class Tag : uint32_t {
+    ON_TRANSACTION_COMPLETED = IBinder::FIRST_CALL_TRANSACTION,
+    ON_RELEASE_BUFFER,
+    ON_TRANSACTION_QUEUE_STALLED,
+    LAST = ON_TRANSACTION_QUEUE_STALLED,
+};
+
+} // Anonymous namespace
 
 status_t FrameEventHistoryStats::writeToParcel(Parcel* output) const {
     status_t err = output->writeUint64(frameNumber);
@@ -263,6 +274,60 @@
     return listenerStats;
 }
 
+class BpTransactionCompletedListener : public SafeBpInterface<ITransactionCompletedListener> {
+public:
+    explicit BpTransactionCompletedListener(const sp<IBinder>& impl)
+          : SafeBpInterface<ITransactionCompletedListener>(impl, "BpTransactionCompletedListener") {
+    }
+
+    ~BpTransactionCompletedListener() override;
+
+    void onTransactionCompleted(ListenerStats stats) override {
+        callRemoteAsync<decltype(&ITransactionCompletedListener::
+                                         onTransactionCompleted)>(Tag::ON_TRANSACTION_COMPLETED,
+                                                                  stats);
+    }
+
+    void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
+                         uint32_t currentMaxAcquiredBufferCount) override {
+        callRemoteAsync<decltype(&ITransactionCompletedListener::
+                                         onReleaseBuffer)>(Tag::ON_RELEASE_BUFFER, callbackId,
+                                                           releaseFence,
+                                                           currentMaxAcquiredBufferCount);
+    }
+
+    void onTransactionQueueStalled(const String8& reason) override {
+        callRemoteAsync<
+                decltype(&ITransactionCompletedListener::
+                                 onTransactionQueueStalled)>(Tag::ON_TRANSACTION_QUEUE_STALLED,
+                                                             reason);
+    }
+};
+
+// Out-of-line virtual method definitions to trigger vtable emission in this translation unit (see
+// clang warning -Wweak-vtables)
+BpTransactionCompletedListener::~BpTransactionCompletedListener() = default;
+
+IMPLEMENT_META_INTERFACE(TransactionCompletedListener, "android.gui.ITransactionComposerListener");
+
+status_t BnTransactionCompletedListener::onTransact(uint32_t code, const Parcel& data,
+                                                    Parcel* reply, uint32_t flags) {
+    if (code < IBinder::FIRST_CALL_TRANSACTION || code > static_cast<uint32_t>(Tag::LAST)) {
+        return BBinder::onTransact(code, data, reply, flags);
+    }
+    auto tag = static_cast<Tag>(code);
+    switch (tag) {
+        case Tag::ON_TRANSACTION_COMPLETED:
+            return callLocalAsync(data, reply,
+                                  &ITransactionCompletedListener::onTransactionCompleted);
+        case Tag::ON_RELEASE_BUFFER:
+            return callLocalAsync(data, reply, &ITransactionCompletedListener::onReleaseBuffer);
+        case Tag::ON_TRANSACTION_QUEUE_STALLED:
+            return callLocalAsync(data, reply,
+                                  &ITransactionCompletedListener::onTransactionQueueStalled);
+    }
+}
+
 ListenerCallbacks ListenerCallbacks::filter(CallbackId::Type type) const {
     std::vector<CallbackId> filteredCallbackIds;
     for (const auto& callbackId : callbackIds) {
@@ -301,4 +366,4 @@
 
 const ReleaseCallbackId ReleaseCallbackId::INVALID_ID = ReleaseCallbackId(0, 0);
 
-}; // namespace android::gui
+}; // namespace android
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 0d1a69b..59b62fe 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -51,7 +51,6 @@
 
 namespace android {
 
-using gui::CallbackId;
 using gui::FocusRequest;
 using gui::WindowInfoHandle;
 
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index a2ed8aa..92125ea 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -314,8 +314,7 @@
     }
 }
 
-binder::Status TransactionCompletedListener::onTransactionCompleted(
-        const ListenerStats& listenerStats) {
+void TransactionCompletedListener::onTransactionCompleted(ListenerStats listenerStats) {
     std::unordered_map<CallbackId, CallbackTranslation, CallbackIdHash> callbacksMap;
     std::multimap<int32_t, sp<JankDataListener>> jankListenersMap;
     {
@@ -455,10 +454,9 @@
             }
         }
     }
-    return binder::Status::ok();
 }
 
-binder::Status TransactionCompletedListener::onTransactionQueueStalled(const std::string& reason) {
+void TransactionCompletedListener::onTransactionQueueStalled(const String8& reason) {
     std::unordered_map<void*, std::function<void(const std::string&)>> callbackCopy;
     {
         std::scoped_lock<std::mutex> lock(mMutex);
@@ -467,7 +465,6 @@
     for (auto const& it : callbackCopy) {
         it.second(reason.c_str());
     }
-    return binder::Status::ok();
 }
 
 void TransactionCompletedListener::addQueueStallListener(
@@ -481,12 +478,9 @@
     mQueueStallListeners.erase(id);
 }
 
-binder::Status TransactionCompletedListener::onReleaseBuffer(
-        const ReleaseCallbackId& callbackId,
-        const std::optional<os::ParcelFileDescriptor>& releaseFenceFd,
-        int32_t currentMaxAcquiredBufferCount) {
-    sp<Fence> releaseFence(releaseFenceFd ? new Fence(::dup(releaseFenceFd->get()))
-                                          : Fence::NO_FENCE);
+void TransactionCompletedListener::onReleaseBuffer(ReleaseCallbackId callbackId,
+                                                   sp<Fence> releaseFence,
+                                                   uint32_t currentMaxAcquiredBufferCount) {
     ReleaseBufferCallback callback;
     {
         std::scoped_lock<std::mutex> lock(mMutex);
@@ -495,14 +489,13 @@
     if (!callback) {
         ALOGE("Could not call release buffer callback, buffer not found %s",
               callbackId.to_string().c_str());
-        return binder::Status::fromExceptionCode(binder::Status::EX_ILLEGAL_ARGUMENT);
+        return;
     }
     std::optional<uint32_t> optionalMaxAcquiredBufferCount =
-            static_cast<uint32_t>(currentMaxAcquiredBufferCount) == UINT_MAX
+            currentMaxAcquiredBufferCount == UINT_MAX
             ? std::nullopt
             : std::make_optional<uint32_t>(currentMaxAcquiredBufferCount);
     callback(callbackId, releaseFence, optionalMaxAcquiredBufferCount);
-    return binder::Status::ok();
 }
 
 ReleaseBufferCallback TransactionCompletedListener::popReleaseBufferCallbackLocked(
@@ -832,11 +825,7 @@
                 ->mReleaseCallbackThread
                 .addReleaseCallback(state.bufferData->generateReleaseCallbackId(), fence);
     } else {
-        std::optional<os::ParcelFileDescriptor> fenceFd;
-        if (fence != Fence::NO_FENCE) {
-            fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(fence->get())));
-        }
-        listener->onReleaseBuffer(state.bufferData->generateReleaseCallbackId(), fenceFd, UINT_MAX);
+        listener->onReleaseBuffer(state.bufferData->generateReleaseCallbackId(), fence, UINT_MAX);
     }
 }
 
@@ -2496,6 +2485,20 @@
     return statusTFromBinderStatus(status);
 }
 
+status_t SurfaceComposerClient::getHdrConversionCapabilities(
+        std::vector<gui::HdrConversionCapability>* hdrConversionCapabilities) {
+    binder::Status status = ComposerServiceAIDL::getComposerService()->getHdrConversionCapabilities(
+            hdrConversionCapabilities);
+    return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::setHdrConversionStrategy(
+        gui::HdrConversionStrategy hdrConversionStrategy) {
+    binder::Status status = ComposerServiceAIDL::getComposerService()->setHdrConversionStrategy(
+            hdrConversionStrategy);
+    return statusTFromBinderStatus(status);
+}
+
 status_t SurfaceComposerClient::setOverrideFrameRate(uid_t uid, float frameRate) {
     binder::Status status =
             ComposerServiceAIDL::getComposerService()->setOverrideFrameRate(uid, frameRate);
@@ -2866,11 +2869,7 @@
 
         while (!callbackInfos.empty()) {
             auto [callbackId, releaseFence] = callbackInfos.front();
-            std::optional<os::ParcelFileDescriptor> fenceFd;
-            if (releaseFence != Fence::NO_FENCE) {
-                fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(releaseFence->get())));
-            }
-            listener->onReleaseBuffer(callbackId, fenceFd, UINT_MAX);
+            listener->onReleaseBuffer(callbackId, std::move(releaseFence), UINT_MAX);
             callbackInfos.pop();
         }
 
diff --git a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl b/libs/gui/aidl/android/gui/HdrConversionCapability.aidl
similarity index 75%
rename from libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
rename to libs/gui/aidl/android/gui/HdrConversionCapability.aidl
index c86de34..1bcfd38 100644
--- a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
+++ b/libs/gui/aidl/android/gui/HdrConversionCapability.aidl
@@ -16,4 +16,10 @@
 
 package android.gui;
 
-parcelable ReleaseCallbackId cpp_header "gui/ReleaseCallbackId.h";
+// TODO(b/265277221): use android.hardware.graphics.common.HdrConversionCapability.aidl
+/** @hide */
+parcelable HdrConversionCapability {
+    int sourceType;
+    int outputType;
+    boolean addsLatency;
+}
\ No newline at end of file
diff --git a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl b/libs/gui/aidl/android/gui/HdrConversionStrategy.aidl
similarity index 74%
copy from libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
copy to libs/gui/aidl/android/gui/HdrConversionStrategy.aidl
index c86de34..1be74b4 100644
--- a/libs/gui/aidl/android/gui/ReleaseCallbackId.aidl
+++ b/libs/gui/aidl/android/gui/HdrConversionStrategy.aidl
@@ -16,4 +16,10 @@
 
 package android.gui;
 
-parcelable ReleaseCallbackId cpp_header "gui/ReleaseCallbackId.h";
+// TODO(b/265277221): use android.hardware.graphics.common.HdrConversionStrategy.aidl
+/** @hide */
+union HdrConversionStrategy {
+    boolean passthrough = true;
+    int[] autoAllowedHdrTypes;
+    int forceHdrConversion;
+}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
index a0b613c..c08a7c6 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -30,6 +30,8 @@
 import android.gui.DynamicDisplayInfo;
 import android.gui.FrameEvent;
 import android.gui.FrameStats;
+import android.gui.HdrConversionCapability;
+import android.gui.HdrConversionStrategy;
 import android.gui.IDisplayEventConnection;
 import android.gui.IFpsListener;
 import android.gui.IHdrLayerInfoListener;
@@ -162,6 +164,26 @@
     boolean getBootDisplayModeSupport();
 
     /**
+     * Gets the HDR conversion capabilities of the device. The conversion capability defines whether
+     * conversion from sourceType to outputType is possible (with or without latency).
+     *
+     * Requires the ACCESS_SURFACE_FLINGER permission.
+     */
+     List<HdrConversionCapability> getHdrConversionCapabilities();
+
+     /**
+      * Sets the HDR conversion strategy of the device.
+      *
+      * Requires the ACCESS_SURFACE_FLINGER permission.
+      */
+     void setHdrConversionStrategy(in HdrConversionStrategy hdrConversionStrategy);
+
+     /**
+      * Gets whether HDR output conversion operations are supported on the device.
+      */
+     boolean getHdrOutputConversionSupport();
+
+    /**
      * Switches Auto Low Latency Mode on/off on the connected display, if it is
      * available. This should only be called if the display supports Auto Low
      * Latency Mode as reported in #getDynamicDisplayInfo.
diff --git a/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl b/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl
deleted file mode 100644
index dde4d38..0000000
--- a/libs/gui/aidl/android/gui/ITransactionCompletedListener.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.gui;
-
-import android.gui.ListenerStats;
-import android.gui.ReleaseCallbackId;
-
-/** @hide */
-oneway interface ITransactionCompletedListener {
-   void onTransactionCompleted(in ListenerStats stats);
-
-   void onReleaseBuffer(in ReleaseCallbackId callbackId,
-                        in @nullable ParcelFileDescriptor releaseFenceFd,
-                        int currentMaxAcquiredBufferCount);
-
-   void onTransactionQueueStalled(@utf8InCpp String name);
-}
diff --git a/libs/gui/aidl/android/gui/ListenerStats.aidl b/libs/gui/aidl/android/gui/ListenerStats.aidl
deleted file mode 100644
index 63248b2..0000000
--- a/libs/gui/aidl/android/gui/ListenerStats.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.gui;
-
-parcelable ListenerStats cpp_header "gui/ListenerStats.h";
diff --git a/libs/gui/aidl/android/gui/OverlayProperties.aidl b/libs/gui/aidl/android/gui/OverlayProperties.aidl
index 75cea15..1af5746 100644
--- a/libs/gui/aidl/android/gui/OverlayProperties.aidl
+++ b/libs/gui/aidl/android/gui/OverlayProperties.aidl
@@ -23,4 +23,6 @@
         int[] dataspaces;
     }
     SupportedBufferCombinations[] combinations;
+
+    boolean supportMixedColorSpaces;
 }
diff --git a/libs/gui/fuzzer/libgui_fuzzer_utils.h b/libs/gui/fuzzer/libgui_fuzzer_utils.h
index 8810e4e..685bd92 100644
--- a/libs/gui/fuzzer/libgui_fuzzer_utils.h
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -91,6 +91,11 @@
     MOCK_METHOD(binder::Status, setBootDisplayMode, (const sp<IBinder>&, int), (override));
     MOCK_METHOD(binder::Status, clearBootDisplayMode, (const sp<IBinder>&), (override));
     MOCK_METHOD(binder::Status, getBootDisplayModeSupport, (bool*), (override));
+    MOCK_METHOD(binder::Status, getHdrConversionCapabilities,
+                (std::vector<gui::HdrConversionCapability>*), (override));
+    MOCK_METHOD(binder::Status, setHdrConversionStrategy, (const gui::HdrConversionStrategy&),
+                (override));
+    MOCK_METHOD(binder::Status, getHdrOutputConversionSupport, (bool*), (override));
     MOCK_METHOD(binder::Status, setAutoLowLatencyMode, (const sp<IBinder>&, bool), (override));
     MOCK_METHOD(binder::Status, setGameContentType, (const sp<IBinder>&, bool), (override));
     MOCK_METHOD(binder::Status, captureDisplay,
diff --git a/libs/gui/include/gui/BLASTBufferQueue.h b/libs/gui/include/gui/BLASTBufferQueue.h
index 47dcc42..001d8e5 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -130,12 +130,11 @@
     void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
                            sp<IGraphicBufferConsumer>* outConsumer);
 
-    void acquireNextBufferLocked(
+    status_t acquireNextBufferLocked(
             const std::optional<SurfaceComposerClient::Transaction*> transaction) REQUIRES(mMutex);
     Rect computeCrop(const BufferItem& item) REQUIRES(mMutex);
     // Return true if we need to reject the buffer based on the scaling mode and the buffer size.
     bool rejectBuffer(const BufferItem& item) REQUIRES(mMutex);
-    bool maxBuffersAcquired(bool includeExtraAcquire) const REQUIRES(mMutex);
     static PixelFormat convertBufferFormat(PixelFormat& format);
     void mergePendingTransactions(SurfaceComposerClient::Transaction* t, uint64_t frameNumber)
             REQUIRES(mMutex);
@@ -144,7 +143,6 @@
     void acquireAndReleaseBuffer() REQUIRES(mMutex);
     void releaseBuffer(const ReleaseCallbackId& callbackId, const sp<Fence>& releaseFence)
             REQUIRES(mMutex);
-    void flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock);
 
     std::string mName;
     // Represents the queued buffer count from buffer queue,
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index 06a246e..045cc2a 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -23,11 +23,11 @@
 #include <android/gui/IHdrLayerInfoListener.h>
 #include <android/gui/IRegionSamplingListener.h>
 #include <android/gui/IScreenCaptureListener.h>
-#include <android/gui/ITransactionCompletedListener.h>
 #include <android/gui/ITunnelModeEnabledListener.h>
 #include <android/gui/IWindowInfosListener.h>
 #include <binder/IBinder.h>
 #include <binder/IInterface.h>
+#include <gui/ITransactionCompletedListener.h>
 #include <gui/SpHash.h>
 #include <math/vec4.h>
 #include <stdint.h>
@@ -66,7 +66,6 @@
 using gui::IDisplayEventConnection;
 using gui::IRegionSamplingListener;
 using gui::IScreenCaptureListener;
-using gui::ListenerCallbacks;
 using gui::SpHash;
 
 namespace gui {
diff --git a/libs/gui/include/gui/ListenerStats.h b/libs/gui/include/gui/ITransactionCompletedListener.h
similarity index 81%
rename from libs/gui/include/gui/ListenerStats.h
rename to libs/gui/include/gui/ITransactionCompletedListener.h
index 3a12802..453e8f3 100644
--- a/libs/gui/include/gui/ListenerStats.h
+++ b/libs/gui/include/gui/ITransactionCompletedListener.h
@@ -24,8 +24,6 @@
 #include <binder/SafeInterface.h>
 
 #include <gui/FrameTimestamps.h>
-#include <gui/ReleaseCallbackId.h>
-
 #include <ui/Fence.h>
 #include <utils/Timers.h>
 
@@ -34,7 +32,10 @@
 #include <unordered_set>
 #include <variant>
 
-namespace android::gui {
+namespace android {
+
+class ITransactionCompletedListener;
+class ListenerCallbacks;
 
 class CallbackId : public Parcelable {
 public:
@@ -53,6 +54,30 @@
     std::size_t operator()(const CallbackId& key) const { return std::hash<int64_t>()(key.id); }
 };
 
+class ReleaseCallbackId : public Parcelable {
+public:
+    static const ReleaseCallbackId INVALID_ID;
+
+    uint64_t bufferId;
+    uint64_t framenumber;
+    ReleaseCallbackId() {}
+    ReleaseCallbackId(uint64_t bufferId, uint64_t framenumber)
+          : bufferId(bufferId), framenumber(framenumber) {}
+    status_t writeToParcel(Parcel* output) const override;
+    status_t readFromParcel(const Parcel* input) override;
+
+    bool operator==(const ReleaseCallbackId& rhs) const {
+        return bufferId == rhs.bufferId && framenumber == rhs.framenumber;
+    }
+    bool operator!=(const ReleaseCallbackId& rhs) const { return !operator==(rhs); }
+    std::string to_string() const {
+        if (*this == INVALID_ID) return "INVALID_ID";
+
+        return "bufferId:" + std::to_string(bufferId) +
+                " framenumber:" + std::to_string(framenumber);
+    }
+};
+
 struct ReleaseBufferCallbackIdHash {
     std::size_t operator()(const ReleaseCallbackId& key) const {
         return std::hash<uint64_t>()(key.bufferId);
@@ -161,6 +186,27 @@
     std::vector<TransactionStats> transactionStats;
 };
 
+class ITransactionCompletedListener : public IInterface {
+public:
+    DECLARE_META_INTERFACE(TransactionCompletedListener)
+
+    virtual void onTransactionCompleted(ListenerStats stats) = 0;
+
+    virtual void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
+                                 uint32_t currentMaxAcquiredBufferCount) = 0;
+
+    virtual void onTransactionQueueStalled(const String8& name) = 0;
+};
+
+class BnTransactionCompletedListener : public SafeBnInterface<ITransactionCompletedListener> {
+public:
+    BnTransactionCompletedListener()
+          : SafeBnInterface<ITransactionCompletedListener>("BnTransactionCompletedListener") {}
+
+    status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+                        uint32_t flags = 0) override;
+};
+
 class ListenerCallbacks {
 public:
     ListenerCallbacks(const sp<IBinder>& listener,
@@ -222,4 +268,4 @@
     }
 };
 
-} // namespace android::gui
+} // namespace android
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index c5fdf82..45a84f6 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -21,10 +21,10 @@
 #include <stdint.h>
 #include <sys/types.h>
 
-#include <android/gui/ITransactionCompletedListener.h>
 #include <android/gui/IWindowInfosReportedListener.h>
 #include <android/native_window.h>
 #include <gui/IGraphicBufferProducer.h>
+#include <gui/ITransactionCompletedListener.h>
 #include <math/mat4.h>
 
 #include <android/gui/DropInputMode.h>
@@ -35,7 +35,6 @@
 #include <gui/ISurfaceComposer.h>
 #include <gui/LayerCaptureArgs.h>
 #include <gui/LayerMetadata.h>
-#include <gui/ReleaseCallbackId.h>
 #include <gui/SpHash.h>
 #include <gui/SurfaceControl.h>
 #include <gui/WindowInfo.h>
@@ -57,9 +56,6 @@
 using gui::ISurfaceComposerClient;
 using gui::LayerMetadata;
 
-using gui::ITransactionCompletedListener;
-using gui::ReleaseCallbackId;
-
 struct client_cache_t {
     wp<IBinder> token = nullptr;
     uint64_t id;
diff --git a/libs/gui/include/gui/ReleaseCallbackId.h b/libs/gui/include/gui/ReleaseCallbackId.h
deleted file mode 100644
index 142ee5a..0000000
--- a/libs/gui/include/gui/ReleaseCallbackId.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <binder/Parcel.h>
-#include <binder/Parcelable.h>
-
-#include <cstdint>
-
-namespace android::gui {
-
-class ReleaseCallbackId : public Parcelable {
-public:
-    static const ReleaseCallbackId INVALID_ID;
-
-    uint64_t bufferId;
-    uint64_t framenumber;
-    ReleaseCallbackId() {}
-    ReleaseCallbackId(uint64_t bufferId, uint64_t framenumber)
-          : bufferId(bufferId), framenumber(framenumber) {}
-    status_t writeToParcel(Parcel* output) const override;
-    status_t readFromParcel(const Parcel* input) override;
-
-    bool operator==(const ReleaseCallbackId& rhs) const {
-        return bufferId == rhs.bufferId && framenumber == rhs.framenumber;
-    }
-    bool operator!=(const ReleaseCallbackId& rhs) const { return !operator==(rhs); }
-    std::string to_string() const {
-        if (*this == INVALID_ID) return "INVALID_ID";
-
-        return "bufferId:" + std::to_string(bufferId) +
-                " framenumber:" + std::to_string(framenumber);
-    }
-};
-
-} // namespace android::gui
diff --git a/libs/gui/include/gui/SurfaceComposerClient.h b/libs/gui/include/gui/SurfaceComposerClient.h
index cc459c5..2458a40 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -42,13 +42,10 @@
 
 #include <android/gui/ISurfaceComposerClient.h>
 
-#include <android/gui/BnTransactionCompletedListener.h>
-
 #include <gui/CpuConsumer.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/ITransactionCompletedListener.h>
 #include <gui/LayerState.h>
-#include <gui/ListenerStats.h>
-#include <gui/ReleaseCallbackId.h>
 #include <gui/SurfaceControl.h>
 #include <gui/WindowInfosListenerReporter.h>
 #include <math/vec3.h>
@@ -62,21 +59,11 @@
 class ITunnelModeEnabledListener;
 class Region;
 
-using gui::BnTransactionCompletedListener;
-using gui::CallbackId;
-using gui::CallbackIdHash;
 using gui::DisplayCaptureArgs;
-using gui::FrameEventHistoryStats;
 using gui::IRegionSamplingListener;
 using gui::ISurfaceComposerClient;
-using gui::ITransactionCompletedListener;
-using gui::JankData;
 using gui::LayerCaptureArgs;
 using gui::LayerMetadata;
-using gui::ListenerStats;
-using gui::ReleaseBufferCallbackIdHash;
-using gui::ReleaseCallbackId;
-using gui::SurfaceStats;
 
 struct SurfaceControlStats {
     SurfaceControlStats(const sp<SurfaceControl>& sc, nsecs_t latchTime,
@@ -200,6 +187,11 @@
     // Clears the user-preferred display mode
     static status_t clearBootDisplayMode(const sp<IBinder>& display);
 
+    // Gets the HDR conversion capabilities of the device
+    static status_t getHdrConversionCapabilities(std::vector<gui::HdrConversionCapability>*);
+    // Sets the HDR conversion strategy for the device
+    static status_t setHdrConversionStrategy(gui::HdrConversionStrategy hdrConversionStrategy);
+
     // Sets the frame rate of a particular app (uid). This is currently called
     // by GameManager.
     static status_t setOverrideFrameRate(uid_t uid, float frameRate);
@@ -841,17 +833,17 @@
     void setReleaseBufferCallback(const ReleaseCallbackId&, ReleaseBufferCallback);
 
     // BnTransactionCompletedListener overrides
-    binder::Status onTransactionCompleted(const ListenerStats& stats) override;
-    binder::Status onReleaseBuffer(const ReleaseCallbackId& callbackId,
-                                   const std::optional<os::ParcelFileDescriptor>& releaseFenceFd,
-                                   int32_t currentMaxAcquiredBufferCount) override;
-    binder::Status onTransactionQueueStalled(const std::string& reason) override;
+    void onTransactionCompleted(ListenerStats stats) override;
+    void onReleaseBuffer(ReleaseCallbackId, sp<Fence> releaseFence,
+                         uint32_t currentMaxAcquiredBufferCount) override;
 
     void removeReleaseBufferCallback(const ReleaseCallbackId& callbackId);
 
     // For Testing Only
     static void setInstance(const sp<TransactionCompletedListener>&);
 
+    void onTransactionQueueStalled(const String8& reason) override;
+
 private:
     ReleaseBufferCallback popReleaseBufferCallbackLocked(const ReleaseCallbackId&);
     static sp<TransactionCompletedListener> sInstance;
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 55242df..3014804 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -819,6 +819,20 @@
         return binder::Status::ok();
     }
 
+    binder::Status getHdrConversionCapabilities(
+            std::vector<gui::HdrConversionCapability>*) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status setHdrConversionStrategy(
+            const gui::HdrConversionStrategy& /*hdrConversionStrategy*/) override {
+        return binder::Status::ok();
+    }
+
+    binder::Status getHdrOutputConversionSupport(bool* /*outSupport*/) override {
+        return binder::Status::ok();
+    }
+
     binder::Status setAutoLowLatencyMode(const sp<IBinder>& /*display*/, bool /*on*/) override {
         return binder::Status::ok();
     }
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 8ddd18d..8f41cc1 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -26,7 +26,6 @@
 filegroup {
     name: "inputconstants_aidl",
     srcs: [
-        "android/hardware/input/InputDeviceCountryCode.aidl",
         "android/os/IInputConstants.aidl",
         "android/os/InputEventInjectionResult.aidl",
         "android/os/InputEventInjectionSync.aidl",
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index d893cb9..c796439 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -72,6 +72,10 @@
             return "DEEP_PRESS";
         case MotionClassification::TWO_FINGER_SWIPE:
             return "TWO_FINGER_SWIPE";
+        case MotionClassification::MULTI_FINGER_SWIPE:
+            return "MULTI_FINGER_SWIPE";
+        case MotionClassification::PINCH:
+            return "PINCH";
     }
 }
 
@@ -411,6 +415,8 @@
     for (uint32_t i = 0; i < count; i++) {
         values[i] = parcel->readFloat();
     }
+
+    isResampled = parcel->readBool();
     return OK;
 }
 
@@ -421,6 +427,8 @@
     for (uint32_t i = 0; i < count; i++) {
         parcel->writeFloat(values[i]);
     }
+
+    parcel->writeBool(isResampled);
     return OK;
 }
 #endif
@@ -440,6 +448,9 @@
             return false;
         }
     }
+    if (isResampled != other.isResampled) {
+        return false;
+    }
     return true;
 }
 
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index fb6c590..87333f2 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -26,7 +26,6 @@
 #include <input/InputEventLabels.h>
 
 using android::base::StringPrintf;
-using android::hardware::input::InputDeviceCountryCode;
 
 namespace android {
 
@@ -178,7 +177,6 @@
         mAlias(other.mAlias),
         mIsExternal(other.mIsExternal),
         mHasMic(other.mHasMic),
-        mCountryCode(other.mCountryCode),
         mKeyboardLayoutInfo(other.mKeyboardLayoutInfo),
         mSources(other.mSources),
         mKeyboardType(other.mKeyboardType),
@@ -197,7 +195,7 @@
 
 void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber,
                                  const InputDeviceIdentifier& identifier, const std::string& alias,
-                                 bool isExternal, bool hasMic, InputDeviceCountryCode countryCode) {
+                                 bool isExternal, bool hasMic) {
     mId = id;
     mGeneration = generation;
     mControllerNumber = controllerNumber;
@@ -205,7 +203,6 @@
     mAlias = alias;
     mIsExternal = isExternal;
     mHasMic = hasMic;
-    mCountryCode = countryCode;
     mSources = 0;
     mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
     mHasVibrator = false;
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index dd7cbb5..7159e27 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -394,7 +394,10 @@
     DEFINE_AXIS(GENERIC_15), \
     DEFINE_AXIS(GENERIC_16), \
     DEFINE_AXIS(GESTURE_X_OFFSET), \
-    DEFINE_AXIS(GESTURE_Y_OFFSET)
+    DEFINE_AXIS(GESTURE_Y_OFFSET), \
+    DEFINE_AXIS(GESTURE_SCROLL_X_DISTANCE), \
+    DEFINE_AXIS(GESTURE_SCROLL_Y_DISTANCE), \
+    DEFINE_AXIS(GESTURE_PINCH_SCALE_FACTOR)
 
 // NOTE: If you add new LEDs here, you must also add them to Input.h
 #define LEDS_SEQUENCE \
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 8d8433b..9f0a314 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -267,6 +267,8 @@
                 memcpy(&msg->body.motion.pointers[i].coords.values[0],
                         &body.motion.pointers[i].coords.values[0],
                         count * (sizeof(body.motion.pointers[i].coords.values[0])));
+                msg->body.motion.pointers[i].coords.isResampled =
+                        body.motion.pointers[i].coords.isResampled;
             }
             break;
         }
@@ -1079,6 +1081,7 @@
 #endif
                 msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
                 msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
+                msgCoords.isResampled = true;
             } else {
                 state.lastResample.idBits.clearBit(id);
             }
@@ -1191,6 +1194,8 @@
             // We maintain the previously resampled value for this pointer (stored in
             // oldLastResample) when the coordinates for this pointer haven't changed since then.
             // This way we don't introduce artificial jitter when pointers haven't actually moved.
+            // The isResampled flag isn't cleared as the values don't reflect what the device is
+            // actually reporting.
 
             // We know here that the coordinates for the pointer haven't changed because we
             // would've cleared the resampled bit in rewriteMessage if they had. We can't modify
@@ -1209,6 +1214,7 @@
                     lerp(currentCoords.getX(), otherCoords.getX(), alpha));
             resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
                     lerp(currentCoords.getY(), otherCoords.getY(), alpha));
+            resampledCoords.isResampled = true;
 #if DEBUG_RESAMPLING
             ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
                     "other (%0.3f, %0.3f), alpha %0.3f",
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index fa5c41f..6bfac40 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -255,6 +255,13 @@
     mLayoutOverlayApplied = true;
 }
 
+void KeyCharacterMap::clearLayoutOverlay() {
+    if (mLayoutOverlayApplied) {
+        reloadBaseFromFile();
+        mLayoutOverlayApplied = false;
+    }
+}
+
 KeyCharacterMap::KeyboardType KeyCharacterMap::getKeyboardType() const {
     return mType;
 }
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
index 0719fe5..0fa0f12 100644
--- a/libs/input/MotionPredictor.cpp
+++ b/libs/input/MotionPredictor.cpp
@@ -55,7 +55,7 @@
  * The returned event should not contain any of the real, existing data. It should only
  * contain the predicted samples.
  */
-std::vector<std::unique_ptr<MotionEvent>> MotionPredictor::predict() {
+std::vector<std::unique_ptr<MotionEvent>> MotionPredictor::predict(nsecs_t timestamp) {
     if (mEvents.size() < 2) {
         return {};
     }
@@ -67,7 +67,7 @@
 
     std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
     std::vector<PointerCoords> futureCoords;
-    const int64_t futureTime = getExpectedPresentationTimeNanos() + mPredictionTimestampOffsetNanos;
+    const nsecs_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
     const nsecs_t currentTime = event.getEventTime();
     const MotionEvent& previous = mEvents.rbegin()[1];
     const nsecs_t oldTime = previous.getEventTime();
@@ -78,16 +78,19 @@
 
     for (size_t i = 0; i < event.getPointerCount(); i++) {
         const int32_t pointerId = event.getPointerId(i);
+        const PointerCoords* currentPointerCoords = event.getRawPointerCoords(i);
+        const float currentX = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X);
+        const float currentY = currentPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
+
         PointerCoords coords;
         coords.clear();
 
         ssize_t index = previous.findPointerIndex(pointerId);
         if (index >= 0) {
             // We have old data for this pointer. Compute the prediction.
-            const float oldX = previous.getRawX(index);
-            const float oldY = previous.getRawY(index);
-            const float currentX = event.getRawX(i);
-            const float currentY = event.getRawY(i);
+            const PointerCoords* oldPointerCoords = previous.getRawPointerCoords(index);
+            const float oldX = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_X);
+            const float oldY = oldPointerCoords->getAxisValue(AMOTION_EVENT_AXIS_Y);
 
             // Let's do a linear interpolation while waiting for a real model
             const float scale =
@@ -97,13 +100,15 @@
 
             coords.setAxisValue(AMOTION_EVENT_AXIS_X, futureX);
             coords.setAxisValue(AMOTION_EVENT_AXIS_Y, futureY);
+            ALOGD_IF(isDebug(),
+                     "Prediction by %.1f ms, (%.1f, %.1f), (%.1f, %.1f) --> (%.1f, %.1f)",
+                     (futureTime - event.getEventTime()) * 1E-6, oldX, oldY, currentX, currentY,
+                     futureX, futureY);
         }
 
         futureCoords.push_back(coords);
     }
 
-    ALOGD_IF(isDebug(), "Prediction is %.1f ms away from the event",
-             (futureTime - event.getEventTime()) * 1E-6);
     /**
      * The process of adding samples is different for the first and subsequent samples:
      * 1. Add the first sample via 'initialize' as below
@@ -143,14 +148,4 @@
     return true;
 }
 
-int64_t MotionPredictor::getExpectedPresentationTimeNanos() {
-    std::scoped_lock lock(mLock);
-    return mExpectedPresentationTimeNanos;
-}
-
-void MotionPredictor::setExpectedPresentationTimeNanos(int64_t expectedPresentationTimeNanos) {
-    std::scoped_lock lock(mLock);
-    mExpectedPresentationTimeNanos = expectedPresentationTimeNanos;
-}
-
 } // namespace android
diff --git a/libs/input/VelocityControl.cpp b/libs/input/VelocityControl.cpp
index 5c008b1..5720099 100644
--- a/libs/input/VelocityControl.cpp
+++ b/libs/input/VelocityControl.cpp
@@ -70,9 +70,10 @@
         if (deltaY) {
             mRawPositionY += *deltaY;
         }
-        mVelocityTracker.addMovement(eventTime, BitSet32(BitSet32::valueForBit(0)),
-                                     {{AMOTION_EVENT_AXIS_X, {mRawPositionX}},
-                                      {AMOTION_EVENT_AXIS_Y, {mRawPositionY}}});
+        mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_X,
+                                     mRawPositionX);
+        mVelocityTracker.addMovement(eventTime, /*pointerId=*/0, AMOTION_EVENT_AXIS_Y,
+                                     mRawPositionY);
 
         std::optional<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
         std::optional<float> vy = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_Y, 0);
diff --git a/libs/input/VelocityTracker.cpp b/libs/input/VelocityTracker.cpp
index 19b4684..3632914 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -23,6 +23,7 @@
 #include <optional>
 
 #include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
 #include <input/VelocityTracker.h>
 #include <utils/BitSet.h>
 #include <utils/Timers.h>
@@ -142,10 +143,7 @@
 // --- VelocityTracker ---
 
 VelocityTracker::VelocityTracker(const Strategy strategy)
-      : mLastEventTime(0),
-        mCurrentPointerIdBits(0),
-        mActivePointerId(-1),
-        mOverrideStrategy(strategy) {}
+      : mLastEventTime(0), mCurrentPointerIdBits(0), mOverrideStrategy(strategy) {}
 
 VelocityTracker::~VelocityTracker() {
 }
@@ -191,17 +189,17 @@
             return std::make_unique<
                     LeastSquaresVelocityTrackerStrategy>(2,
                                                          LeastSquaresVelocityTrackerStrategy::
-                                                                 WEIGHTING_DELTA);
+                                                                 Weighting::DELTA);
         case VelocityTracker::Strategy::WLSQ2_CENTRAL:
             return std::make_unique<
                     LeastSquaresVelocityTrackerStrategy>(2,
                                                          LeastSquaresVelocityTrackerStrategy::
-                                                                 WEIGHTING_CENTRAL);
+                                                                 Weighting::CENTRAL);
         case VelocityTracker::Strategy::WLSQ2_RECENT:
             return std::make_unique<
                     LeastSquaresVelocityTrackerStrategy>(2,
                                                          LeastSquaresVelocityTrackerStrategy::
-                                                                 WEIGHTING_RECENT);
+                                                                 Weighting::RECENT);
 
         case VelocityTracker::Strategy::INT1:
             return std::make_unique<IntegratingVelocityTrackerStrategy>(1);
@@ -220,30 +218,30 @@
 
 void VelocityTracker::clear() {
     mCurrentPointerIdBits.clear();
-    mActivePointerId = -1;
+    mActivePointerId = std::nullopt;
     mConfiguredStrategies.clear();
 }
 
-void VelocityTracker::clearPointers(BitSet32 idBits) {
-    BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value);
-    mCurrentPointerIdBits = remainingIdBits;
+void VelocityTracker::clearPointer(int32_t pointerId) {
+    mCurrentPointerIdBits.clearBit(pointerId);
 
-    if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
-        mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
+    if (mActivePointerId && *mActivePointerId == pointerId) {
+        // The active pointer id is being removed. Mark it invalid and try to find a new one
+        // from the remaining pointers.
+        mActivePointerId = std::nullopt;
+        if (!mCurrentPointerIdBits.isEmpty()) {
+            mActivePointerId = mCurrentPointerIdBits.firstMarkedBit();
+        }
     }
 
     for (const auto& [_, strategy] : mConfiguredStrategies) {
-        strategy->clearPointers(idBits);
+        strategy->clearPointer(pointerId);
     }
 }
 
-void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits,
-                                  const std::map<int32_t /*axis*/, std::vector<float>>& positions) {
-    while (idBits.count() > MAX_POINTERS) {
-        idBits.clearLastMarkedBit();
-    }
-
-    if ((mCurrentPointerIdBits.value & idBits.value) &&
+void VelocityTracker::addMovement(nsecs_t eventTime, int32_t pointerId, int32_t axis,
+                                  float position) {
+    if (mCurrentPointerIdBits.hasBit(pointerId) &&
         std::chrono::nanoseconds(eventTime - mLastEventTime) > ASSUME_POINTER_STOPPED_TIME) {
         ALOGD_IF(DEBUG_VELOCITY, "VelocityTracker: stopped for %s, clearing state.",
                  toString(std::chrono::nanoseconds(eventTime - mLastEventTime)).c_str());
@@ -254,39 +252,28 @@
     }
     mLastEventTime = eventTime;
 
-    mCurrentPointerIdBits = idBits;
-    if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
-        mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit();
+    mCurrentPointerIdBits.markBit(pointerId);
+    if (!mActivePointerId) {
+        // Let this be the new active pointer if no active pointer is currently set
+        mActivePointerId = pointerId;
     }
 
-    for (const auto& [axis, positionValues] : positions) {
-        LOG_ALWAYS_FATAL_IF(idBits.count() != positionValues.size(),
-                            "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu",
-                            idBits.count(), positionValues.size());
-        if (mConfiguredStrategies.find(axis) == mConfiguredStrategies.end()) {
-            configureStrategy(axis);
-        }
-        mConfiguredStrategies[axis]->addMovement(eventTime, idBits, positionValues);
+    if (mConfiguredStrategies.find(axis) == mConfiguredStrategies.end()) {
+        configureStrategy(axis);
     }
+    mConfiguredStrategies[axis]->addMovement(eventTime, pointerId, position);
 
     if (DEBUG_VELOCITY) {
-        ALOGD("VelocityTracker: addMovement eventTime=%" PRId64
-              ", idBits=0x%08x, activePointerId=%d",
-              eventTime, idBits.value, mActivePointerId);
-        for (const auto& positionsEntry : positions) {
-            for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) {
-                uint32_t id = iterBits.firstMarkedBit();
-                uint32_t index = idBits.getIndexOfBit(id);
-                iterBits.clearBit(id);
-                Estimator estimator;
-                getEstimator(positionsEntry.first, id, &estimator);
-                ALOGD("  %d: axis=%d, position=%0.3f, "
-                      "estimator (degree=%d, coeff=%s, confidence=%f)",
-                      id, positionsEntry.first, positionsEntry.second[index], int(estimator.degree),
-                      vectorToString(estimator.coeff, estimator.degree + 1).c_str(),
-                      estimator.confidence);
-            }
-        }
+        ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", pointerId=%" PRId32
+              ", activePointerId=%s",
+              eventTime, pointerId, toString(mActivePointerId).c_str());
+
+        std::optional<Estimator> estimator = getEstimator(axis, pointerId);
+        ALOGD("  %d: axis=%d, position=%0.3f, "
+              "estimator (degree=%d, coeff=%s, confidence=%f)",
+              pointerId, axis, position, int((*estimator).degree),
+              vectorToString((*estimator).coeff.data(), (*estimator).degree + 1).c_str(),
+              (*estimator).confidence);
     }
 }
 
@@ -296,94 +283,75 @@
     int32_t actionMasked = event->getActionMasked();
 
     switch (actionMasked) {
-    case AMOTION_EVENT_ACTION_DOWN:
-    case AMOTION_EVENT_ACTION_HOVER_ENTER:
-        // Clear all pointers on down before adding the new movement.
-        clear();
-        axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
-        break;
-    case AMOTION_EVENT_ACTION_POINTER_DOWN: {
-        // Start a new movement trace for a pointer that just went down.
-        // We do this on down instead of on up because the client may want to query the
-        // final velocity for a pointer that just went up.
-        BitSet32 downIdBits;
-        downIdBits.markBit(event->getPointerId(event->getActionIndex()));
-        clearPointers(downIdBits);
-        axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
-        break;
-    }
-    case AMOTION_EVENT_ACTION_MOVE:
-    case AMOTION_EVENT_ACTION_HOVER_MOVE:
-        axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
-        break;
-    case AMOTION_EVENT_ACTION_POINTER_UP:
-    case AMOTION_EVENT_ACTION_UP: {
-        std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime);
-        if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) {
-            ALOGD_IF(DEBUG_VELOCITY,
-                     "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.",
-                     toString(delaySinceLastEvent).c_str());
-            // We have not received any movements for too long.  Assume that all pointers
-            // have stopped.
-            for (int32_t axis : PLANAR_AXES) {
-                mConfiguredStrategies.erase(axis);
-            }
+        case AMOTION_EVENT_ACTION_DOWN:
+        case AMOTION_EVENT_ACTION_HOVER_ENTER:
+            // Clear all pointers on down before adding the new movement.
+            clear();
+            axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
+            break;
+        case AMOTION_EVENT_ACTION_POINTER_DOWN: {
+            // Start a new movement trace for a pointer that just went down.
+            // We do this on down instead of on up because the client may want to query the
+            // final velocity for a pointer that just went up.
+            clearPointer(event->getPointerId(event->getActionIndex()));
+            axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
+            break;
         }
-        // These actions because they do not convey any new information about
-        // pointer movement.  We also want to preserve the last known velocity of the pointers.
-        // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
-        // of the pointers that went up.  ACTION_POINTER_UP does include the new position of
-        // pointers that remained down but we will also receive an ACTION_MOVE with this
-        // information if any of them actually moved.  Since we don't know how many pointers
-        // will be going up at once it makes sense to just wait for the following ACTION_MOVE
-        // before adding the movement.
-        return;
-    }
-    case AMOTION_EVENT_ACTION_SCROLL:
-        axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL);
-        break;
-    default:
-        // Ignore all other actions.
-        return;
+        case AMOTION_EVENT_ACTION_MOVE:
+        case AMOTION_EVENT_ACTION_HOVER_MOVE:
+            axesToProcess.insert(PLANAR_AXES.begin(), PLANAR_AXES.end());
+            break;
+        case AMOTION_EVENT_ACTION_POINTER_UP:
+        case AMOTION_EVENT_ACTION_UP: {
+            std::chrono::nanoseconds delaySinceLastEvent(event->getEventTime() - mLastEventTime);
+            if (delaySinceLastEvent > ASSUME_POINTER_STOPPED_TIME) {
+                ALOGD_IF(DEBUG_VELOCITY,
+                         "VelocityTracker: stopped for %s, clearing state upon pointer liftoff.",
+                         toString(delaySinceLastEvent).c_str());
+                // We have not received any movements for too long.  Assume that all pointers
+                // have stopped.
+                for (int32_t axis : PLANAR_AXES) {
+                    mConfiguredStrategies.erase(axis);
+                }
+            }
+            // These actions because they do not convey any new information about
+            // pointer movement.  We also want to preserve the last known velocity of the pointers.
+            // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
+            // of the pointers that went up.  ACTION_POINTER_UP does include the new position of
+            // pointers that remained down but we will also receive an ACTION_MOVE with this
+            // information if any of them actually moved.  Since we don't know how many pointers
+            // will be going up at once it makes sense to just wait for the following ACTION_MOVE
+            // before adding the movement.
+            return;
+        }
+        case AMOTION_EVENT_ACTION_SCROLL:
+            axesToProcess.insert(AMOTION_EVENT_AXIS_SCROLL);
+            break;
+        default:
+            // Ignore all other actions.
+            return;
     }
 
-    size_t pointerCount = event->getPointerCount();
-    if (pointerCount > MAX_POINTERS) {
-        pointerCount = MAX_POINTERS;
-    }
-
-    BitSet32 idBits;
-    for (size_t i = 0; i < pointerCount; i++) {
-        idBits.markBit(event->getPointerId(i));
-    }
-
-    uint32_t pointerIndex[MAX_POINTERS];
-    for (size_t i = 0; i < pointerCount; i++) {
-        pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i));
-    }
-
-    std::map<int32_t, std::vector<float>> positions;
-    for (int32_t axis : axesToProcess) {
-        positions[axis].resize(pointerCount);
-    }
-
-    size_t historySize = event->getHistorySize();
+    const size_t historySize = event->getHistorySize();
     for (size_t h = 0; h <= historySize; h++) {
-        nsecs_t eventTime = event->getHistoricalEventTime(h);
-        for (int32_t axis : axesToProcess) {
-            for (size_t i = 0; i < pointerCount; i++) {
-                positions[axis][pointerIndex[i]] = event->getHistoricalAxisValue(axis, i, h);
+        const nsecs_t eventTime = event->getHistoricalEventTime(h);
+        for (size_t i = 0; i < event->getPointerCount(); i++) {
+            if (event->isResampled(i, h)) {
+                continue; // skip resampled samples
+            }
+            const int32_t pointerId = event->getPointerId(i);
+            for (int32_t axis : axesToProcess) {
+                const float position = event->getHistoricalAxisValue(axis, i, h);
+                addMovement(eventTime, pointerId, axis, position);
             }
         }
-        addMovement(eventTime, idBits, positions);
     }
 }
 
-std::optional<float> VelocityTracker::getVelocity(int32_t axis, uint32_t id) const {
-    Estimator estimator;
-    bool validVelocity = getEstimator(axis, id, &estimator) && estimator.degree >= 1;
-    if (validVelocity) {
-        return estimator.coeff[1];
+std::optional<float> VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const {
+    std::optional<Estimator> estimator = getEstimator(axis, pointerId);
+    if (estimator && (*estimator).degree >= 1) {
+        return (*estimator).coeff[1];
     }
     return {};
 }
@@ -406,50 +374,55 @@
     return computedVelocity;
 }
 
-bool VelocityTracker::getEstimator(int32_t axis, uint32_t id, Estimator* outEstimator) const {
+std::optional<VelocityTracker::Estimator> VelocityTracker::getEstimator(int32_t axis,
+                                                                        int32_t pointerId) const {
     const auto& it = mConfiguredStrategies.find(axis);
     if (it == mConfiguredStrategies.end()) {
-        return false;
+        return std::nullopt;
     }
-    return it->second->getEstimator(id, outEstimator);
+    return it->second->getEstimator(pointerId);
 }
 
 // --- LeastSquaresVelocityTrackerStrategy ---
 
 LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree,
                                                                          Weighting weighting)
-      : mDegree(degree), mWeighting(weighting), mIndex(0) {}
+      : mDegree(degree), mWeighting(weighting) {}
 
 LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
 }
 
-void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
-    BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
-    mMovements[mIndex].idBits = remainingIdBits;
+void LeastSquaresVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
+    mIndex.erase(pointerId);
+    mMovements.erase(pointerId);
 }
 
-void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
-                                                      const std::vector<float>& positions) {
-    if (mMovements[mIndex].eventTime != eventTime) {
+void LeastSquaresVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+                                                      float position) {
+    // If data for this pointer already exists, we have a valid entry at the position of
+    // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
+    // to the next position in the circular buffer and write the new Movement there. Otherwise,
+    // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
+    // for this pointer and write to the first position.
+    auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
+    auto [indexIt, _] = mIndex.insert({pointerId, 0});
+    size_t& index = indexIt->second;
+    if (!inserted && movementIt->second[index].eventTime != eventTime) {
         // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
         // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
         // the new pointer. If the eventtimes for both events are identical, just update the data
         // for this time.
         // We only compare against the last value, as it is likely that addMovement is called
         // in chronological order as events occur.
-        mIndex++;
+        index++;
     }
-    if (mIndex == HISTORY_SIZE) {
-        mIndex = 0;
+    if (index == HISTORY_SIZE) {
+        index = 0;
     }
 
-    Movement& movement = mMovements[mIndex];
+    Movement& movement = movementIt->second[index];
     movement.eventTime = eventTime;
-    movement.idBits = idBits;
-    uint32_t count = idBits.count();
-    for (uint32_t i = 0; i < count; i++) {
-        movement.positions[i] = positions[i];
-    }
+    movement.position = position;
 }
 
 /**
@@ -502,7 +475,9 @@
  * http://en.wikipedia.org/wiki/Gram-Schmidt
  */
 static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y,
-                              const std::vector<float>& w, uint32_t n, float* outB, float* outDet) {
+                              const std::vector<float>& w, uint32_t n,
+                              std::array<float, VelocityTracker::Estimator::MAX_DEGREE + 1>& outB,
+                              float* outDet) {
     const size_t m = x.size();
 
     ALOGD_IF(DEBUG_STRATEGY, "solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
@@ -583,7 +558,7 @@
         outB[i] /= r[i][i];
     }
 
-    ALOGD_IF(DEBUG_STRATEGY, "  - b=%s", vectorToString(outB, n).c_str());
+    ALOGD_IF(DEBUG_STRATEGY, "  - b=%s", vectorToString(outB.data(), n).c_str());
 
     // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
     // SSerr is the residual sum of squares (variance of the error),
@@ -671,37 +646,47 @@
     return std::make_optional(std::array<float, 3>({c, b, a}));
 }
 
-bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
-        VelocityTracker::Estimator* outEstimator) const {
-    outEstimator->clear();
-
+std::optional<VelocityTracker::Estimator> LeastSquaresVelocityTrackerStrategy::getEstimator(
+        int32_t pointerId) const {
+    const auto movementIt = mMovements.find(pointerId);
+    if (movementIt == mMovements.end()) {
+        return std::nullopt; // no data
+    }
     // Iterate over movement samples in reverse time order and collect samples.
     std::vector<float> positions;
     std::vector<float> w;
     std::vector<float> time;
 
-    uint32_t index = mIndex;
-    const Movement& newestMovement = mMovements[mIndex];
+    uint32_t index = mIndex.at(pointerId);
+    const Movement& newestMovement = movementIt->second[index];
     do {
-        const Movement& movement = mMovements[index];
-        if (!movement.idBits.hasBit(id)) {
-            break;
-        }
+        const Movement& movement = movementIt->second[index];
 
         nsecs_t age = newestMovement.eventTime - movement.eventTime;
         if (age > HORIZON) {
             break;
         }
-
-        positions.push_back(movement.getPosition(id));
-        w.push_back(chooseWeight(index));
+        if (movement.eventTime == 0 && index != 0) {
+            // All eventTime's are initialized to 0. In this fixed-width circular buffer, it's
+            // possible that not all entries are valid. We use a time=0 as a signal for those
+            // uninitialized values. If we encounter a time of 0 in a position
+            // that's > 0, it means that we hit the block where the data wasn't initialized.
+            // We still don't know whether the value at index=0, with eventTime=0 is valid.
+            // However, that's only possible when the value is by itself. So there's no hard in
+            // processing it anyways, since the velocity for a single point is zero, and this
+            // situation will only be encountered in artificial circumstances (in tests).
+            // In practice, time will never be 0.
+            break;
+        }
+        positions.push_back(movement.position);
+        w.push_back(chooseWeight(pointerId, index));
         time.push_back(-age * 0.000000001f);
         index = (index == 0 ? HISTORY_SIZE : index) - 1;
     } while (positions.size() < HISTORY_SIZE);
 
     const size_t m = positions.size();
     if (m == 0) {
-        return false; // no data
+        return std::nullopt; // no data
     }
 
     // Calculate a least squares polynomial fit.
@@ -710,112 +695,116 @@
         degree = m - 1;
     }
 
-    if (degree == 2 && mWeighting == WEIGHTING_NONE) {
+    if (degree == 2 && mWeighting == Weighting::NONE) {
         // Optimize unweighted, quadratic polynomial fit
         std::optional<std::array<float, 3>> coeff =
                 solveUnweightedLeastSquaresDeg2(time, positions);
         if (coeff) {
-            outEstimator->time = newestMovement.eventTime;
-            outEstimator->degree = 2;
-            outEstimator->confidence = 1;
-            for (size_t i = 0; i <= outEstimator->degree; i++) {
-                outEstimator->coeff[i] = (*coeff)[i];
+            VelocityTracker::Estimator estimator;
+            estimator.time = newestMovement.eventTime;
+            estimator.degree = 2;
+            estimator.confidence = 1;
+            for (size_t i = 0; i <= estimator.degree; i++) {
+                estimator.coeff[i] = (*coeff)[i];
             }
-            return true;
+            return estimator;
         }
     } else if (degree >= 1) {
         // General case for an Nth degree polynomial fit
         float det;
         uint32_t n = degree + 1;
-        if (solveLeastSquares(time, positions, w, n, outEstimator->coeff, &det)) {
-            outEstimator->time = newestMovement.eventTime;
-            outEstimator->degree = degree;
-            outEstimator->confidence = det;
+        VelocityTracker::Estimator estimator;
+        if (solveLeastSquares(time, positions, w, n, estimator.coeff, &det)) {
+            estimator.time = newestMovement.eventTime;
+            estimator.degree = degree;
+            estimator.confidence = det;
 
             ALOGD_IF(DEBUG_STRATEGY, "estimate: degree=%d, coeff=%s, confidence=%f",
-                     int(outEstimator->degree), vectorToString(outEstimator->coeff, n).c_str(),
-                     outEstimator->confidence);
+                     int(estimator.degree), vectorToString(estimator.coeff.data(), n).c_str(),
+                     estimator.confidence);
 
-            return true;
+            return estimator;
         }
     }
 
     // No velocity data available for this pointer, but we do have its current position.
-    outEstimator->coeff[0] = positions[0];
-    outEstimator->time = newestMovement.eventTime;
-    outEstimator->degree = 0;
-    outEstimator->confidence = 1;
-    return true;
+    VelocityTracker::Estimator estimator;
+    estimator.coeff[0] = positions[0];
+    estimator.time = newestMovement.eventTime;
+    estimator.degree = 0;
+    estimator.confidence = 1;
+    return estimator;
 }
 
-float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const {
+float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const {
+    const std::array<Movement, HISTORY_SIZE>& movements = mMovements.at(pointerId);
     switch (mWeighting) {
-    case WEIGHTING_DELTA: {
-        // Weight points based on how much time elapsed between them and the next
-        // point so that points that "cover" a shorter time span are weighed less.
-        //   delta  0ms: 0.5
-        //   delta 10ms: 1.0
-        if (index == mIndex) {
+        case Weighting::DELTA: {
+            // Weight points based on how much time elapsed between them and the next
+            // point so that points that "cover" a shorter time span are weighed less.
+            //   delta  0ms: 0.5
+            //   delta 10ms: 1.0
+            if (index == mIndex.at(pointerId)) {
+                return 1.0f;
+            }
+            uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
+            float deltaMillis =
+                    (movements[nextIndex].eventTime - movements[index].eventTime) * 0.000001f;
+            if (deltaMillis < 0) {
+                return 0.5f;
+            }
+            if (deltaMillis < 10) {
+                return 0.5f + deltaMillis * 0.05;
+            }
             return 1.0f;
         }
-        uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
-        float deltaMillis = (mMovements[nextIndex].eventTime- mMovements[index].eventTime)
-                * 0.000001f;
-        if (deltaMillis < 0) {
+
+        case Weighting::CENTRAL: {
+            // Weight points based on their age, weighing very recent and very old points less.
+            //   age  0ms: 0.5
+            //   age 10ms: 1.0
+            //   age 50ms: 1.0
+            //   age 60ms: 0.5
+            float ageMillis =
+                    (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) *
+                    0.000001f;
+            if (ageMillis < 0) {
+                return 0.5f;
+            }
+            if (ageMillis < 10) {
+                return 0.5f + ageMillis * 0.05;
+            }
+            if (ageMillis < 50) {
+                return 1.0f;
+            }
+            if (ageMillis < 60) {
+                return 0.5f + (60 - ageMillis) * 0.05;
+            }
             return 0.5f;
         }
-        if (deltaMillis < 10) {
-            return 0.5f + deltaMillis * 0.05;
-        }
-        return 1.0f;
-    }
 
-    case WEIGHTING_CENTRAL: {
-        // Weight points based on their age, weighing very recent and very old points less.
-        //   age  0ms: 0.5
-        //   age 10ms: 1.0
-        //   age 50ms: 1.0
-        //   age 60ms: 0.5
-        float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
-                * 0.000001f;
-        if (ageMillis < 0) {
+        case Weighting::RECENT: {
+            // Weight points based on their age, weighing older points less.
+            //   age   0ms: 1.0
+            //   age  50ms: 1.0
+            //   age 100ms: 0.5
+            float ageMillis =
+                    (movements[mIndex.at(pointerId)].eventTime - movements[index].eventTime) *
+                    0.000001f;
+            if (ageMillis < 50) {
+                return 1.0f;
+            }
+            if (ageMillis < 100) {
+                return 0.5f + (100 - ageMillis) * 0.01f;
+            }
             return 0.5f;
         }
-        if (ageMillis < 10) {
-            return 0.5f + ageMillis * 0.05;
-        }
-        if (ageMillis < 50) {
-            return 1.0f;
-        }
-        if (ageMillis < 60) {
-            return 0.5f + (60 - ageMillis) * 0.05;
-        }
-        return 0.5f;
-    }
 
-    case WEIGHTING_RECENT: {
-        // Weight points based on their age, weighing older points less.
-        //   age   0ms: 1.0
-        //   age  50ms: 1.0
-        //   age 100ms: 0.5
-        float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
-                * 0.000001f;
-        if (ageMillis < 50) {
+        case Weighting::NONE:
             return 1.0f;
-        }
-        if (ageMillis < 100) {
-            return 0.5f + (100 - ageMillis) * 0.01f;
-        }
-        return 0.5f;
-    }
-
-    case WEIGHTING_NONE:
-    default:
-        return 1.0f;
     }
 }
 
-
 // --- IntegratingVelocityTrackerStrategy ---
 
 IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) :
@@ -825,38 +814,32 @@
 IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {
 }
 
-void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
-    mPointerIdBits.value &= ~idBits.value;
+void IntegratingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
+    mPointerIdBits.clearBit(pointerId);
 }
 
-void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
-                                                     const std::vector<float>& positions) {
-    uint32_t index = 0;
-    for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) {
-        uint32_t id = iterIdBits.clearFirstMarkedBit();
-        State& state = mPointerState[id];
-        const float position = positions[index++];
-        if (mPointerIdBits.hasBit(id)) {
-            updateState(state, eventTime, position);
-        } else {
-            initState(state, eventTime, position);
-        }
+void IntegratingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+                                                     float position) {
+    State& state = mPointerState[pointerId];
+    if (mPointerIdBits.hasBit(pointerId)) {
+        updateState(state, eventTime, position);
+    } else {
+        initState(state, eventTime, position);
     }
 
-    mPointerIdBits = idBits;
+    mPointerIdBits.markBit(pointerId);
 }
 
-bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id,
-        VelocityTracker::Estimator* outEstimator) const {
-    outEstimator->clear();
-
-    if (mPointerIdBits.hasBit(id)) {
-        const State& state = mPointerState[id];
-        populateEstimator(state, outEstimator);
-        return true;
+std::optional<VelocityTracker::Estimator> IntegratingVelocityTrackerStrategy::getEstimator(
+        int32_t pointerId) const {
+    if (mPointerIdBits.hasBit(pointerId)) {
+        const State& state = mPointerState[pointerId];
+        VelocityTracker::Estimator estimator;
+        populateEstimator(state, &estimator);
+        return estimator;
     }
 
-    return false;
+    return std::nullopt;
 }
 
 void IntegratingVelocityTrackerStrategy::initState(State& state, nsecs_t eventTime,
@@ -916,49 +899,60 @@
 
 // --- LegacyVelocityTrackerStrategy ---
 
-LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() : mIndex(0) {}
+LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {}
 
 LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
 }
 
-void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
-    BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
-    mMovements[mIndex].idBits = remainingIdBits;
+void LegacyVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
+    mIndex.erase(pointerId);
+    mMovements.erase(pointerId);
 }
 
-void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
-                                                const std::vector<float>& positions) {
-    if (++mIndex == HISTORY_SIZE) {
-        mIndex = 0;
+void LegacyVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+                                                float position) {
+    // If data for this pointer already exists, we have a valid entry at the position of
+    // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
+    // to the next position in the circular buffer and write the new Movement there. Otherwise,
+    // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
+    // for this pointer and write to the first position.
+    auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
+    auto [indexIt, _] = mIndex.insert({pointerId, 0});
+    size_t& index = indexIt->second;
+    if (!inserted && movementIt->second[index].eventTime != eventTime) {
+        // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
+        // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
+        // the new pointer. If the eventtimes for both events are identical, just update the data
+        // for this time.
+        // We only compare against the last value, as it is likely that addMovement is called
+        // in chronological order as events occur.
+        index++;
+    }
+    if (index == HISTORY_SIZE) {
+        index = 0;
     }
 
-    Movement& movement = mMovements[mIndex];
+    Movement& movement = movementIt->second[index];
     movement.eventTime = eventTime;
-    movement.idBits = idBits;
-    uint32_t count = idBits.count();
-    for (uint32_t i = 0; i < count; i++) {
-        movement.positions[i] = positions[i];
-    }
+    movement.position = position;
 }
 
-bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id,
-        VelocityTracker::Estimator* outEstimator) const {
-    outEstimator->clear();
-
-    const Movement& newestMovement = mMovements[mIndex];
-    if (!newestMovement.idBits.hasBit(id)) {
-        return false; // no data
+std::optional<VelocityTracker::Estimator> LegacyVelocityTrackerStrategy::getEstimator(
+        int32_t pointerId) const {
+    const auto movementIt = mMovements.find(pointerId);
+    if (movementIt == mMovements.end()) {
+        return std::nullopt; // no data
     }
+    const Movement& newestMovement = movementIt->second[mIndex.at(pointerId)];
 
     // Find the oldest sample that contains the pointer and that is not older than HORIZON.
     nsecs_t minTime = newestMovement.eventTime - HORIZON;
-    uint32_t oldestIndex = mIndex;
+    uint32_t oldestIndex = mIndex.at(pointerId);
     uint32_t numTouches = 1;
     do {
         uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
-        const Movement& nextOldestMovement = mMovements[nextOldestIndex];
-        if (!nextOldestMovement.idBits.hasBit(id)
-                || nextOldestMovement.eventTime < minTime) {
+        const Movement& nextOldestMovement = mMovements.at(pointerId)[nextOldestIndex];
+        if (nextOldestMovement.eventTime < minTime) {
             break;
         }
         oldestIndex = nextOldestIndex;
@@ -978,22 +972,22 @@
     float accumV = 0;
     uint32_t index = oldestIndex;
     uint32_t samplesUsed = 0;
-    const Movement& oldestMovement = mMovements[oldestIndex];
-    float oldestPosition = oldestMovement.getPosition(id);
+    const Movement& oldestMovement = mMovements.at(pointerId)[oldestIndex];
+    float oldestPosition = oldestMovement.position;
     nsecs_t lastDuration = 0;
 
     while (numTouches-- > 1) {
         if (++index == HISTORY_SIZE) {
             index = 0;
         }
-        const Movement& movement = mMovements[index];
+        const Movement& movement = mMovements.at(pointerId)[index];
         nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
 
         // If the duration between samples is small, we may significantly overestimate
         // the velocity.  Consequently, we impose a minimum duration constraint on the
         // samples that we include in the calculation.
         if (duration >= MIN_DURATION) {
-            float position = movement.getPosition(id);
+            float position = movement.position;
             float scale = 1000000000.0f / duration; // one over time delta in seconds
             float v = (position - oldestPosition) * scale;
             accumV = (accumV * lastDuration + v * duration) / (duration + lastDuration);
@@ -1003,54 +997,59 @@
     }
 
     // Report velocity.
-    float newestPosition = newestMovement.getPosition(id);
-    outEstimator->time = newestMovement.eventTime;
-    outEstimator->confidence = 1;
-    outEstimator->coeff[0] = newestPosition;
+    float newestPosition = newestMovement.position;
+    VelocityTracker::Estimator estimator;
+    estimator.time = newestMovement.eventTime;
+    estimator.confidence = 1;
+    estimator.coeff[0] = newestPosition;
     if (samplesUsed) {
-        outEstimator->coeff[1] = accumV;
-        outEstimator->degree = 1;
+        estimator.coeff[1] = accumV;
+        estimator.degree = 1;
     } else {
-        outEstimator->degree = 0;
+        estimator.degree = 0;
     }
-    return true;
+    return estimator;
 }
 
 // --- ImpulseVelocityTrackerStrategy ---
 
 ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues)
-      : mDeltaValues(deltaValues), mIndex(0) {}
+      : mDeltaValues(deltaValues) {}
 
 ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() {
 }
 
-void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
-    BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
-    mMovements[mIndex].idBits = remainingIdBits;
+void ImpulseVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
+    mIndex.erase(pointerId);
+    mMovements.erase(pointerId);
 }
 
-void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, BitSet32 idBits,
-                                                 const std::vector<float>& positions) {
-    if (mMovements[mIndex].eventTime != eventTime) {
+void ImpulseVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+                                                 float position) {
+    // If data for this pointer already exists, we have a valid entry at the position of
+    // mIndex[pointerId] and mMovements[pointerId]. In that case, we need to advance the index
+    // to the next position in the circular buffer and write the new Movement there. Otherwise,
+    // if this is a first movement for this pointer, we initialize the maps mIndex and mMovements
+    // for this pointer and write to the first position.
+    auto [movementIt, inserted] = mMovements.insert({pointerId, {}});
+    auto [indexIt, _] = mIndex.insert({pointerId, 0});
+    size_t& index = indexIt->second;
+    if (!inserted && movementIt->second[index].eventTime != eventTime) {
         // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
         // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
         // the new pointer. If the eventtimes for both events are identical, just update the data
         // for this time.
         // We only compare against the last value, as it is likely that addMovement is called
         // in chronological order as events occur.
-        mIndex++;
+        index++;
     }
-    if (mIndex == HISTORY_SIZE) {
-        mIndex = 0;
+    if (index == HISTORY_SIZE) {
+        index = 0;
     }
 
-    Movement& movement = mMovements[mIndex];
+    Movement& movement = movementIt->second[index];
     movement.eventTime = eventTime;
-    movement.idBits = idBits;
-    uint32_t count = idBits.count();
-    for (uint32_t i = 0; i < count; i++) {
-        movement.positions[i] = positions[i];
-    }
+    movement.position = position;
 }
 
 /**
@@ -1178,55 +1177,61 @@
     return kineticEnergyToVelocity(work);
 }
 
-bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id,
-        VelocityTracker::Estimator* outEstimator) const {
-    outEstimator->clear();
+std::optional<VelocityTracker::Estimator> ImpulseVelocityTrackerStrategy::getEstimator(
+        int32_t pointerId) const {
+    const auto movementIt = mMovements.find(pointerId);
+    if (movementIt == mMovements.end()) {
+        return std::nullopt; // no data
+    }
 
     // Iterate over movement samples in reverse time order and collect samples.
     float positions[HISTORY_SIZE];
     nsecs_t time[HISTORY_SIZE];
     size_t m = 0; // number of points that will be used for fitting
-    size_t index = mIndex;
-    const Movement& newestMovement = mMovements[mIndex];
+    size_t index = mIndex.at(pointerId);
+    const Movement& newestMovement = movementIt->second[index];
     do {
-        const Movement& movement = mMovements[index];
-        if (!movement.idBits.hasBit(id)) {
-            break;
-        }
+        const Movement& movement = movementIt->second[index];
 
         nsecs_t age = newestMovement.eventTime - movement.eventTime;
         if (age > HORIZON) {
             break;
         }
+        if (movement.eventTime == 0 && index != 0) {
+            // All eventTime's are initialized to 0. If we encounter a time of 0 in a position
+            // that's >0, it means that we hit the block where the data wasn't initialized.
+            // It's also possible that the sample at 0 would be invalid, but there's no harm in
+            // processing it, since it would be just a single point, and will only be encountered
+            // in artificial circumstances (in tests).
+            break;
+        }
 
-        positions[m] = movement.getPosition(id);
+        positions[m] = movement.position;
         time[m] = movement.eventTime;
         index = (index == 0 ? HISTORY_SIZE : index) - 1;
     } while (++m < HISTORY_SIZE);
 
     if (m == 0) {
-        return false; // no data
+        return std::nullopt; // no data
     }
-    outEstimator->coeff[0] = 0;
-    outEstimator->coeff[1] = calculateImpulseVelocity(time, positions, m, mDeltaValues);
-    outEstimator->coeff[2] = 0;
+    VelocityTracker::Estimator estimator;
+    estimator.coeff[0] = 0;
+    estimator.coeff[1] = calculateImpulseVelocity(time, positions, m, mDeltaValues);
+    estimator.coeff[2] = 0;
 
-    outEstimator->time = newestMovement.eventTime;
-    outEstimator->degree = 2; // similar results to 2nd degree fit
-    outEstimator->confidence = 1;
+    estimator.time = newestMovement.eventTime;
+    estimator.degree = 2; // similar results to 2nd degree fit
+    estimator.confidence = 1;
 
-    ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", outEstimator->coeff[1]);
+    ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", estimator.coeff[1]);
 
     if (DEBUG_IMPULSE) {
         // TODO(b/134179997): delete this block once the switch to 'impulse' is complete.
         // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons.
         // X axis chosen arbitrarily for velocity comparisons.
         VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2);
-        BitSet32 idBits;
-        const uint32_t pointerId = 0;
-        idBits.markBit(pointerId);
         for (ssize_t i = m - 1; i >= 0; i--) {
-            lsq2.addMovement(time[i], idBits, {{AMOTION_EVENT_AXIS_X, {positions[i]}}});
+            lsq2.addMovement(time[i], pointerId, AMOTION_EVENT_AXIS_X, positions[i]);
         }
         std::optional<float> v = lsq2.getVelocity(AMOTION_EVENT_AXIS_X, pointerId);
         if (v) {
@@ -1235,7 +1240,7 @@
             ALOGD("lsq2 velocity: could not compute velocity");
         }
     }
-    return true;
+    return estimator;
 }
 
 } // namespace android
diff --git a/libs/input/android/hardware/input/InputDeviceCountryCode.aidl b/libs/input/android/hardware/input/InputDeviceCountryCode.aidl
deleted file mode 100644
index 6bb1a60..0000000
--- a/libs/input/android/hardware/input/InputDeviceCountryCode.aidl
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input;
-
-/**
- * Constant for HID country code declared by a HID device. These constants are declared as AIDL to
- * be used by java and native input code.
- *
- * @hide
- */
-@Backing(type="int")
-enum InputDeviceCountryCode {
-    /**
-     * Used as default value where country code is not set in the device HID descriptor
-     */
-    INVALID = -1,
-
-    /**
-     * Used as default value when country code is not supported by the HID device. The HID
-     * descriptor sets "00" as the country code in this case.
-     */
-    NOT_SUPPORTED = 0,
-
-    /**
-     * Arabic
-     */
-    ARABIC = 1,
-
-    /**
-     * Belgian
-     */
-    BELGIAN = 2,
-
-    /**
-     * Canadian (Bilingual)
-     */
-    CANADIAN_BILINGUAL = 3,
-
-    /**
-     * Canadian (French)
-     */
-    CANADIAN_FRENCH = 4,
-
-    /**
-     * Czech Republic
-     */
-    CZECH_REPUBLIC = 5,
-
-    /**
-     * Danish
-     */
-    DANISH = 6,
-
-    /**
-     * Finnish
-     */
-    FINNISH = 7,
-
-    /**
-     * French
-     */
-    FRENCH = 8,
-
-    /**
-     * German
-     */
-    GERMAN = 9,
-
-    /**
-     * Greek
-     */
-    GREEK = 10,
-
-    /**
-     * Hebrew
-     */
-    HEBREW = 11,
-
-    /**
-     * Hungary
-     */
-    HUNGARY = 12,
-
-    /**
-     * International (ISO)
-     */
-    INTERNATIONAL = 13,
-
-    /**
-     * Italian
-     */
-    ITALIAN = 14,
-
-    /**
-     * Japan (Katakana)
-     */
-    JAPAN = 15,
-
-    /**
-     * Korean
-     */
-    KOREAN = 16,
-
-    /**
-     * Latin American
-     */
-    LATIN_AMERICAN = 17,
-
-    /**
-     * Netherlands (Dutch)
-     */
-    DUTCH = 18,
-
-    /**
-     * Norwegian
-     */
-    NORWEGIAN = 19,
-
-    /**
-     * Persian
-     */
-    PERSIAN = 20,
-
-    /**
-     * Poland
-     */
-    POLAND = 21,
-
-    /**
-     * Portuguese
-     */
-    PORTUGUESE = 22,
-
-    /**
-     * Russia
-     */
-    RUSSIA = 23,
-
-    /**
-     * Slovakia
-     */
-    SLOVAKIA = 24,
-
-    /**
-     * Spanish
-     */
-    SPANISH = 25,
-
-    /**
-     * Swedish
-     */
-    SWEDISH = 26,
-
-    /**
-     * Swiss (French)
-     */
-    SWISS_FRENCH = 27,
-
-    /**
-     * Swiss (German)
-     */
-    SWISS_GERMAN = 28,
-
-    /**
-     * Switzerland
-     */
-    SWITZERLAND = 29,
-
-    /**
-     * Taiwan
-     */
-    TAIWAN = 30,
-
-    /**
-     * Turkish_Q
-     */
-    TURKISH_Q = 31,
-
-    /**
-     * UK
-     */
-    UK = 32,
-
-    /**
-     * US
-     */
-    US = 33,
-
-    /**
-     * Yugoslavia
-     */
-    YUGOSLAVIA = 34,
-
-    /**
-     * Turkish_F
-     */
-    TURKISH_F = 35,
-}
\ No newline at end of file
diff --git a/libs/input/tests/InputEvent_test.cpp b/libs/input/tests/InputEvent_test.cpp
index 4b31246..8a6e983 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -46,6 +46,7 @@
     coords.clear();
 
     ASSERT_EQ(0ULL, coords.bits);
+    ASSERT_FALSE(coords.isResampled);
 }
 
 TEST_F(PointerCoordsTest, AxisValues) {
@@ -158,11 +159,13 @@
     outCoords.readFromParcel(&parcel);
 
     ASSERT_EQ(0ULL, outCoords.bits);
+    ASSERT_FALSE(outCoords.isResampled);
 
     // Round trip with some values.
     parcel.freeData();
     inCoords.setAxisValue(2, 5);
     inCoords.setAxisValue(5, 8);
+    inCoords.isResampled = true;
 
     inCoords.writeToParcel(&parcel);
     parcel.setDataPosition(0);
@@ -171,6 +174,7 @@
     ASSERT_EQ(outCoords.bits, inCoords.bits);
     ASSERT_EQ(outCoords.values[0], inCoords.values[0]);
     ASSERT_EQ(outCoords.values[1], inCoords.values[1]);
+    ASSERT_TRUE(outCoords.isResampled);
 }
 
 
@@ -263,6 +267,7 @@
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 16);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 17);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 18);
+    pointerCoords[0].isResampled = true;
     pointerCoords[1].clear();
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 20);
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 21);
@@ -281,6 +286,7 @@
                       mRawTransform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2,
                       pointerProperties, pointerCoords);
 
+    pointerCoords[0].clear();
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 112);
@@ -290,6 +296,8 @@
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 116);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 117);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 118);
+    pointerCoords[0].isResampled = true;
+    pointerCoords[1].clear();
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 120);
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 121);
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 122);
@@ -299,8 +307,10 @@
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 126);
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 127);
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 128);
+    pointerCoords[1].isResampled = true;
     event->addSample(ARBITRARY_EVENT_TIME + 1, pointerCoords);
 
+    pointerCoords[0].clear();
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 210);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 211);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 212);
@@ -310,6 +320,7 @@
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 216);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 217);
     pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 218);
+    pointerCoords[1].clear();
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 220);
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 221);
     pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 222);
@@ -457,6 +468,13 @@
     ASSERT_EQ(toScaledOrientation(128), event->getHistoricalOrientation(1, 1));
     ASSERT_EQ(toScaledOrientation(218), event->getOrientation(0));
     ASSERT_EQ(toScaledOrientation(228), event->getOrientation(1));
+
+    ASSERT_TRUE(event->isResampled(0, 0));
+    ASSERT_FALSE(event->isResampled(1, 0));
+    ASSERT_TRUE(event->isResampled(0, 1));
+    ASSERT_TRUE(event->isResampled(1, 1));
+    ASSERT_FALSE(event->isResampled(0, 2));
+    ASSERT_FALSE(event->isResampled(1, 2));
 }
 
 TEST_F(MotionEventTest, Properties) {
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
index 0f39055..d2b59a1 100644
--- a/libs/input/tests/MotionPredictor_test.cpp
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -63,8 +63,7 @@
     predictor.record(getMotionEvent(MOVE, 1, 3, 10));
     predictor.record(getMotionEvent(MOVE, 2, 5, 20));
     predictor.record(getMotionEvent(MOVE, 3, 7, 30));
-    predictor.setExpectedPresentationTimeNanos(40);
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict();
+    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
     ASSERT_EQ(1u, predicted.size());
     ASSERT_EQ(predicted[0]->getX(0), 4);
     ASSERT_EQ(predicted[0]->getY(0), 9);
@@ -81,8 +80,7 @@
     predictor.record(getMotionEvent(MOVE, 0, 1, 10));
     predictor.record(getMotionEvent(MOVE, 0, 1, 20));
     predictor.record(getMotionEvent(MOVE, 0, 1, 30));
-    predictor.setExpectedPresentationTimeNanos(40);
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict();
+    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
     ASSERT_EQ(1u, predicted.size());
     ASSERT_EQ(predicted[0]->getX(0), 0);
     ASSERT_EQ(predicted[0]->getY(0), 1);
@@ -98,21 +96,19 @@
 TEST(MotionPredictorTest, Offset) {
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1,
                               []() { return true /*enable prediction*/; });
-    predictor.setExpectedPresentationTimeNanos(40);
     predictor.record(getMotionEvent(DOWN, 0, 1, 30));
     predictor.record(getMotionEvent(MOVE, 0, 1, 35));
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict();
+    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
     ASSERT_EQ(1u, predicted.size());
     ASSERT_GE(predicted[0]->getEventTime(), 41);
 }
 
-TEST(MotionPredictionTest, FlagDisablesPrediction) {
+TEST(MotionPredictorTest, FlagDisablesPrediction) {
     MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
                               []() { return false /*disable prediction*/; });
-    predictor.setExpectedPresentationTimeNanos(40);
     predictor.record(getMotionEvent(DOWN, 0, 1, 30));
     predictor.record(getMotionEvent(MOVE, 0, 1, 35));
-    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict();
+    std::vector<std::unique_ptr<MotionEvent>> predicted = predictor.predict(40);
     ASSERT_EQ(0u, predicted.size());
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_STYLUS));
     ASSERT_FALSE(predictor.isPredictionAvailable(/*deviceId=*/1, AINPUT_SOURCE_TOUCHSCREEN));
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index 1c8658b..024b6d3 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -117,7 +117,7 @@
 
 void TestBodySize() {
     static_assert(sizeof(InputMessage::Body::Key) == 96);
-    static_assert(sizeof(InputMessage::Body::Motion::Pointer) == 136);
+    static_assert(sizeof(InputMessage::Body::Motion::Pointer) == 144);
     static_assert(sizeof(InputMessage::Body::Motion) ==
                   offsetof(InputMessage::Body::Motion, pointers) +
                           sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
@@ -137,8 +137,8 @@
     static_assert(sizeof(InputMessage::Body) ==
                   offsetof(InputMessage::Body::Motion, pointers) +
                           sizeof(InputMessage::Body::Motion::Pointer) * MAX_POINTERS);
-    static_assert(sizeof(InputMessage::Body) == 160 + 136 * 16);
-    static_assert(sizeof(InputMessage::Body) == 2336);
+    static_assert(sizeof(InputMessage::Body) == 160 + 144 * 16);
+    static_assert(sizeof(InputMessage::Body) == 2464);
 }
 
 /**
@@ -148,8 +148,8 @@
  * still helpful to compute to get an idea of the sizes that are involved.
  */
 void TestWorstCaseInputMessageSize() {
-    static_assert(sizeof(InputMessage) == /*header*/ 8 + /*body*/ 2336);
-    static_assert(sizeof(InputMessage) == 2344);
+    static_assert(sizeof(InputMessage) == /*header*/ 8 + /*body*/ 2464);
+    static_assert(sizeof(InputMessage) == 2472);
 }
 
 /**
@@ -159,8 +159,8 @@
     constexpr size_t pointerCount = 1;
     constexpr size_t bodySize = offsetof(InputMessage::Body::Motion, pointers) +
             sizeof(InputMessage::Body::Motion::Pointer) * pointerCount;
-    static_assert(bodySize == 160 + 136);
-    static_assert(bodySize == 296); // For the total message size, add the small header
+    static_assert(bodySize == 160 + 144);
+    static_assert(bodySize == 304); // For the total message size, add the small header
 }
 
 // --- VerifiedInputEvent ---
diff --git a/libs/input/tests/TouchResampling_test.cpp b/libs/input/tests/TouchResampling_test.cpp
index c09a8e9..d01258c 100644
--- a/libs/input/tests/TouchResampling_test.cpp
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -31,6 +31,7 @@
     int32_t id;
     float x;
     float y;
+    bool isResampled = false;
 };
 
 struct InputEventEntry {
@@ -190,6 +191,8 @@
             ASSERT_EQ(entry.pointers[p].y,
                       motionEvent->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y,
                                                              motionEventPointerIndex, i));
+            ASSERT_EQ(entry.pointers[p].isResampled,
+                      motionEvent->isResampled(motionEventPointerIndex, i));
         }
     }
 
@@ -244,7 +247,7 @@
             //      id  x   y
             {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE},
             {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE},
-            {25ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+            {25ms, {{0, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE},
     };
     consumeInputEventEntries(expectedEntries, frameTime);
 }
@@ -283,7 +286,7 @@
             //      id  x   y
             {10ms, {{1, 20, 30}}, AMOTION_EVENT_ACTION_MOVE},
             {20ms, {{1, 30, 30}}, AMOTION_EVENT_ACTION_MOVE},
-            {25ms, {{1, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+            {25ms, {{1, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE},
     };
     consumeInputEventEntries(expectedEntries, frameTime);
 }
@@ -361,7 +364,7 @@
             //      id  x   y
             {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE},
             {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE},
-            {25ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+            {25ms, {{0, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE},
     };
     consumeInputEventEntries(expectedEntries, frameTime);
 
@@ -375,8 +378,12 @@
     frameTime = 45ms + 5ms /*RESAMPLE_LATENCY*/;
     expectedEntries = {
             //      id  x   y
-            {40ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
-            {45ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled event, rewritten
+            {40ms,
+             {{0, 35, 30, .isResampled = true}},
+             AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
+            {45ms,
+             {{0, 35, 30, .isResampled = true}},
+             AMOTION_EVENT_ACTION_MOVE}, // resampled event, rewritten
     };
     consumeInputEventEntries(expectedEntries, frameTime);
 }
@@ -411,7 +418,7 @@
             //      id  x   y
             {10ms, {{0, 20, 30}}, AMOTION_EVENT_ACTION_MOVE},
             {20ms, {{0, 30, 30}}, AMOTION_EVENT_ACTION_MOVE},
-            {25ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+            {25ms, {{0, 35, 30, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE},
     };
     consumeInputEventEntries(expectedEntries, frameTime);
     // Above, the resampled event is at 25ms rather than at 30 ms = 35ms - RESAMPLE_LATENCY
@@ -428,8 +435,12 @@
     frameTime = 50ms;
     expectedEntries = {
             //      id  x   y
-            {24ms, {{0, 35, 30}}, AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
-            {26ms, {{0, 45, 30}}, AMOTION_EVENT_ACTION_MOVE}, // resampled event, rewritten
+            {24ms,
+             {{0, 35, 30, .isResampled = true}},
+             AMOTION_EVENT_ACTION_MOVE}, // original event, rewritten
+            {26ms,
+             {{0, 45, 30, .isResampled = true}},
+             AMOTION_EVENT_ACTION_MOVE}, // resampled event, rewritten
     };
     consumeInputEventEntries(expectedEntries, frameTime);
 }
@@ -499,7 +510,9 @@
             //      id  x    y
             {30ms, {{0, 100, 100}, {1, 500, 500}}, AMOTION_EVENT_ACTION_MOVE},
             {40ms, {{0, 120, 120}, {1, 600, 600}}, AMOTION_EVENT_ACTION_MOVE},
-            {45ms, {{0, 130, 130}, {1, 650, 650}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+            {45ms,
+             {{0, 130, 130, .isResampled = true}, {1, 650, 650, .isResampled = true}},
+             AMOTION_EVENT_ACTION_MOVE},
     };
     consumeInputEventEntries(expectedEntries, frameTime);
 
@@ -518,11 +531,13 @@
      */
     expectedEntries = {
             {60ms,
-             {{0, 130, 130}, // not 120! because it matches previous real event
-              {1, 650, 650}},
+             {{0, 130, 130, .isResampled = true}, // not 120! because it matches previous real event
+              {1, 650, 650, .isResampled = true}},
              AMOTION_EVENT_ACTION_MOVE},
             {70ms, {{0, 130, 130}, {1, 700, 700}}, AMOTION_EVENT_ACTION_MOVE},
-            {75ms, {{0, 135, 135}, {1, 750, 750}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+            {75ms,
+             {{0, 135, 135, .isResampled = true}, {1, 750, 750, .isResampled = true}},
+             AMOTION_EVENT_ACTION_MOVE},
     };
     consumeInputEventEntries(expectedEntries, frameTime);
 
@@ -554,7 +569,7 @@
              * The latest event with ACTION_MOVE was at t = 70, coord = 700.
              * Use that value for resampling here: (600 - 700) / (90 - 70) * 5 + 600
              */
-            {95ms, {{1, 575, 575}}, AMOTION_EVENT_ACTION_MOVE}, // resampled value
+            {95ms, {{1, 575, 575, .isResampled = true}}, AMOTION_EVENT_ACTION_MOVE},
     };
     consumeInputEventEntries(expectedEntries, frameTime);
 }
diff --git a/libs/input/tests/VelocityTracker_test.cpp b/libs/input/tests/VelocityTracker_test.cpp
index 54feea2..c6ad3a2 100644
--- a/libs/input/tests/VelocityTracker_test.cpp
+++ b/libs/input/tests/VelocityTracker_test.cpp
@@ -84,6 +84,8 @@
     float x;
     float y;
 
+    bool isResampled = false;
+
     /**
      * If both values are NAN, then this is considered to be an empty entry (no pointer data).
      * If only one of the values is NAN, this is still a valid entry,
@@ -203,10 +205,11 @@
 
             coords[pointerIndex].clear();
             // We are treating column positions as pointerId
-            EXPECT_TRUE(entry.positions[pointerId].isValid()) <<
-                    "The entry at pointerId must be valid";
-            coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_X, entry.positions[pointerId].x);
-            coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_Y, entry.positions[pointerId].y);
+            const Position& position = entry.positions[pointerId];
+            EXPECT_TRUE(position.isValid()) << "The entry at " << pointerId << " must be valid";
+            coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_X, position.x);
+            coords[pointerIndex].setAxisValue(AMOTION_EVENT_AXIS_Y, position.y);
+            coords[pointerIndex].isResampled = position.isResampled;
 
             properties[pointerIndex].id = pointerId;
             properties[pointerIndex].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
@@ -288,13 +291,13 @@
     for (MotionEvent event : events) {
         vt.addMovement(&event);
     }
-    VelocityTracker::Estimator estimatorX;
-    VelocityTracker::Estimator estimatorY;
-    EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_X, 0, &estimatorX));
-    EXPECT_TRUE(vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0, &estimatorY));
+    std::optional<VelocityTracker::Estimator> estimatorX = vt.getEstimator(AMOTION_EVENT_AXIS_X, 0);
+    std::optional<VelocityTracker::Estimator> estimatorY = vt.getEstimator(AMOTION_EVENT_AXIS_Y, 0);
+    EXPECT_TRUE(estimatorX);
+    EXPECT_TRUE(estimatorY);
     for (size_t i = 0; i< coefficients.size(); i++) {
-        checkCoefficient(estimatorX.coeff[i], coefficients[i]);
-        checkCoefficient(estimatorY.coeff[i], coefficients[i]);
+        checkCoefficient((*estimatorX).coeff[i], coefficients[i]);
+        checkCoefficient((*estimatorY).coeff[i], coefficients[i]);
     }
 }
 
@@ -375,6 +378,44 @@
     EXPECT_FALSE(computedVelocity.getVelocity(AMOTION_EVENT_AXIS_X, MAX_POINTER_ID + 1));
 }
 
+/**
+ * For a single pointer, the resampled data is ignored.
+ */
+TEST_F(VelocityTrackerTest, SinglePointerResampledData) {
+    std::vector<PlanarMotionEventEntry> motions = {{10ms, {{1, 2}}},
+                                                   {20ms, {{2, 4}}},
+                                                   {30ms, {{3, 6}}},
+                                                   {35ms, {{30, 60, .isResampled = true}}},
+                                                   {40ms, {{4, 8}}}};
+
+    computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_X, 100);
+    computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_Y, 200);
+}
+
+/**
+ * For multiple pointers, the resampled data is ignored on a per-pointer basis. If a certain pointer
+ * does not have a resampled value, all of the points are used.
+ */
+TEST_F(VelocityTrackerTest, MultiPointerResampledData) {
+    std::vector<PlanarMotionEventEntry> motions = {
+            {0ms, {{0, 0}}},
+            {10ms, {{1, 0}, {1, 0}}},
+            {20ms, {{2, 0}, {2, 0}}},
+            {30ms, {{3, 0}, {3, 0}}},
+            {35ms, {{30, 0, .isResampled = true}, {30, 0}}},
+            {40ms, {{4, 0}, {4, 0}}},
+            {45ms, {{5, 0}}}, // ACTION_UP
+    };
+
+    // Sample at t=35ms breaks trend. It's marked as resampled for the first pointer, so it should
+    // be ignored, and the resulting velocity should be linear. For the second pointer, it's not
+    // resampled, so it should cause the velocity to be non-linear.
+    computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_X, 100,
+                            /*pointerId=*/0);
+    computeAndCheckVelocity(VelocityTracker::Strategy::DEFAULT, motions, AMOTION_EVENT_AXIS_X, 3455,
+                            /*pointerId=*/1);
+}
+
 TEST_F(VelocityTrackerTest, TestGetComputedVelocity) {
     std::vector<PlanarMotionEventEntry> motions = {
             {235089067457000ns, {{528.00, 0}}}, {235089084684000ns, {{527.00, 0}}},
@@ -420,8 +461,7 @@
 
     EXPECT_FALSE(vt.getVelocity(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
 
-    VelocityTracker::Estimator estimator;
-    EXPECT_FALSE(vt.getEstimator(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID, &estimator));
+    EXPECT_FALSE(vt.getEstimator(AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
 
     VelocityTracker::ComputedVelocity computedVelocity = vt.getComputedVelocity(1000, 1000);
     for (uint32_t id = 0; id <= MAX_POINTER_ID; id++) {
@@ -432,7 +472,7 @@
     EXPECT_EQ(-1, vt.getActivePointerId());
 
     // Make sure that the clearing functions execute without an issue.
-    vt.clearPointers(BitSet32(7U));
+    vt.clearPointer(7U);
     vt.clear();
 }
 
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
index 39c79c9..d0de48f 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h
@@ -36,11 +36,11 @@
     JpegDecoder();
     ~JpegDecoder();
     /*
-     * Decompresses JPEG image to raw image (YUV420planer or grey-scale) format. After calling
-     * this method, call getDecompressedImage() to get the image.
+     * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After
+     * calling this method, call getDecompressedImage() to get the image.
      * Returns false if decompressing the image fails.
      */
-    bool decompressImage(const void* image, int length);
+    bool decompressImage(const void* image, int length, bool decodeToRGBA = false);
     /*
      * Returns the decompressed raw image buffer pointer. This method must be called only after
      * calling decompressImage().
@@ -86,22 +86,23 @@
      */
     int getEXIFPos() { return mExifPos; }
     /*
-     * Decompresses metadata of the image.
+     * Decompresses metadata of the image. All vectors are owned by the caller.
      */
     bool getCompressedImageParameters(const void* image, int length,
                                       size_t* pWidth, size_t* pHeight,
-                                      std::vector<uint8_t>* &iccData,
-                                      std::vector<uint8_t>* &exifData);
+                                      std::vector<uint8_t>* iccData,
+                                      std::vector<uint8_t>* exifData);
     /*
      * Extracts EXIF package and updates the EXIF position / length without decoding the image.
      */
     bool extractEXIF(const void* image, int length);
 
 private:
-    bool decode(const void* image, int length);
+    bool decode(const void* image, int length, bool decodeToRGBA);
     // Returns false if errors occur.
     bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel);
     bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest);
+    bool decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest);
     bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest);
     // Process 16 lines of Y and 16 lines of U/V each time.
     // We must pass at least 16 scanlines according to libjpeg documentation.
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 905bf16..3597903 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -244,7 +244,8 @@
     *
     * The output is filled jpegr_info structure
     * @param compressed_jpegr_image compressed JPEGR image
-    * @param jpegr_info pointer to output JPEGR info
+    * @param jpegr_info pointer to output JPEGR info. Members of jpegr_info
+    *         are owned by the caller
     * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
     */
     status_t getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
@@ -339,11 +340,11 @@
     /*
      * This method will tone map a HDR image to an SDR image.
      *
-     * @param uncompressed_p010_image (input) uncompressed P010 image
+     * @param src (input) uncompressed P010 image
      * @param dest (output) tone mapping result as a YUV_420 image
      * @return NO_ERROR if calculation succeeds, error code if error occurs.
      */
-    status_t toneMap(jr_uncompressed_ptr uncompressed_p010_image,
+    status_t toneMap(jr_uncompressed_ptr src,
                      jr_uncompressed_ptr dest);
 };
 
diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp
index c2a8f45..7dc3df9 100644
--- a/libs/jpegrecoverymap/jpegdecoder.cpp
+++ b/libs/jpegrecoverymap/jpegdecoder.cpp
@@ -32,6 +32,10 @@
 
 const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
 const std::string kExifIdCode = "Exif";
+constexpr uint32_t kICCMarkerHeaderSize = 14;
+constexpr uint8_t kICCSig[] = {
+        'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
+};
 
 struct jpegr_source_mgr : jpeg_source_mgr {
     jpegr_source_mgr(const uint8_t* ptr, int len);
@@ -93,7 +97,7 @@
 JpegDecoder::~JpegDecoder() {
 }
 
-bool JpegDecoder::decompressImage(const void* image, int length) {
+bool JpegDecoder::decompressImage(const void* image, int length, bool decodeToRGBA) {
     if (image == nullptr || length <= 0) {
         ALOGE("Image size can not be handled: %d", length);
         return false;
@@ -101,7 +105,7 @@
 
     mResultBuffer.clear();
     mXMPBuffer.clear();
-    if (!decode(image, length)) {
+    if (!decode(image, length, decodeToRGBA)) {
         return false;
     }
 
@@ -140,7 +144,7 @@
     return mHeight;
 }
 
-bool JpegDecoder::decode(const void* image, int length) {
+bool JpegDecoder::decode(const void* image, int length, bool decodeToRGBA) {
     jpeg_decompress_struct cinfo;
     jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     jpegrerror_mgr myerr;
@@ -210,15 +214,26 @@
     mWidth = cinfo.image_width;
     mHeight = cinfo.image_height;
 
-    if (cinfo.jpeg_color_space == JCS_YCbCr) {
-        mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
-    } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
-        mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+    if (decodeToRGBA) {
+        if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
+            // We don't intend to support decoding grayscale to RGBA
+            return false;
+        }
+        // 4 bytes per pixel
+        mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4);
+        cinfo.out_color_space = JCS_EXT_RGBA;
+    } else {
+        if (cinfo.jpeg_color_space == JCS_YCbCr) {
+            // 1 byte per pixel for Y, 0.5 byte per pixel for U+V
+            mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
+        } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
+            mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+        }
+        cinfo.out_color_space = cinfo.jpeg_color_space;
+        cinfo.raw_data_out = TRUE;
     }
 
-    cinfo.raw_data_out = TRUE;
     cinfo.dct_method = JDCT_IFAST;
-    cinfo.out_color_space = cinfo.jpeg_color_space;
 
     jpeg_start_decompress(&cinfo);
 
@@ -292,12 +307,15 @@
     if (isSingleChannel) {
         return decompressSingleChannel(cinfo, dest);
     }
-    return decompressYUV(cinfo, dest);
+    if (cinfo->out_color_space == JCS_EXT_RGBA)
+        return decompressRGBA(cinfo, dest);
+    else
+        return decompressYUV(cinfo, dest);
 }
 
 bool JpegDecoder::getCompressedImageParameters(const void* image, int length,
                               size_t *pWidth, size_t *pHeight,
-                              std::vector<uint8_t> *&iccData , std::vector<uint8_t> *&exifData) {
+                              std::vector<uint8_t> *iccData , std::vector<uint8_t> *exifData) {
     jpeg_decompress_struct cinfo;
     jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
     jpegrerror_mgr myerr;
@@ -322,15 +340,60 @@
     *pWidth = cinfo.image_width;
     *pHeight = cinfo.image_height;
 
-    //TODO: Parse iccProfile and exifData
-    (void)iccData;
-    (void)exifData;
+    if (iccData != nullptr) {
+        for (jpeg_marker_struct* marker = cinfo.marker_list; marker;
+             marker = marker->next) {
+            if (marker->marker != kAPP2Marker) {
+                continue;
+            }
+            if (marker->data_length <= kICCMarkerHeaderSize ||
+                memcmp(marker->data, kICCSig, sizeof(kICCSig)) != 0) {
+                continue;
+            }
 
+            const unsigned int len = marker->data_length - kICCMarkerHeaderSize;
+            const uint8_t *src = marker->data + kICCMarkerHeaderSize;
+            iccData->insert(iccData->end(), src, src+len);
+        }
+    }
+
+    if (exifData != nullptr) {
+        bool exifAppears = false;
+        for (jpeg_marker_struct* marker = cinfo.marker_list; marker && !exifAppears;
+             marker = marker->next) {
+            if (marker->marker != kAPP1Marker) {
+                continue;
+            }
+
+            const unsigned int len = marker->data_length;
+            if (len >= kExifIdCode.size() &&
+                !strncmp(reinterpret_cast<const char*>(marker->data), kExifIdCode.c_str(),
+                         kExifIdCode.size())) {
+                exifData->resize(len, 0);
+                memcpy(static_cast<void*>(exifData->data()), marker->data, len);
+                exifAppears = true;
+            }
+        }
+    }
 
     jpeg_destroy_decompress(&cinfo);
     return true;
 }
 
+bool JpegDecoder::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+    JSAMPLE* decodeDst = (JSAMPLE*) dest;
+    uint32_t lines = 0;
+    // TODO: use batches for more effectiveness
+    while (lines < cinfo->image_height) {
+        uint32_t ret = jpeg_read_scanlines(cinfo, &decodeDst, 1);
+        if (ret == 0) {
+            break;
+        }
+        decodeDst += cinfo->image_width * 4;
+        lines++;
+    }
+    return lines == cinfo->image_height;
+}
 
 bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
 
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index 1b69743..ee68043 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -25,7 +25,6 @@
 #include <image_io/jpeg/jpeg_scanner.h>
 #include <image_io/jpeg/jpeg_info_builder.h>
 #include <image_io/base/data_segment_data_source.h>
-#include <utils/Log.h>
 
 #include <memory>
 #include <sstream>
@@ -239,6 +238,9 @@
   }
 
   jpegr_uncompressed_struct uncompressed_yuv_420_image;
+  unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
+      uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
+  uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
   JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
 
   jpegr_uncompressed_struct map;
@@ -265,7 +267,7 @@
   jpeg.length = jpeg_encoder.getCompressedImageSize();
 
   jpegr_exif_struct new_exif;
-  if (exif->data == nullptr) {
+  if (exif == nullptr || exif->data == nullptr) {
       new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
   } else {
       new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
@@ -542,23 +544,37 @@
   if (compressed_jpegr_image == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
-
   // TODO: fill EXIF data
   (void) exif;
 
+  if (request_sdr) {
+    JpegDecoder jpeg_decoder;
+    if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
+                                      true)) {
+        return ERROR_JPEGR_DECODE_ERROR;
+    }
+    jpegr_uncompressed_struct uncompressed_rgba_image;
+    uncompressed_rgba_image.data = jpeg_decoder.getDecompressedImagePtr();
+    uncompressed_rgba_image.width = jpeg_decoder.getDecompressedImageWidth();
+    uncompressed_rgba_image.height = jpeg_decoder.getDecompressedImageHeight();
+    memcpy(dest->data, uncompressed_rgba_image.data,
+           uncompressed_rgba_image.width * uncompressed_rgba_image.height * 4);
+    dest->width = uncompressed_rgba_image.width;
+    dest->height = uncompressed_rgba_image.height;
+    return NO_ERROR;
+  }
+
   jpegr_compressed_struct compressed_map;
   jpegr_metadata metadata;
   JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
 
-
   JpegDecoder jpeg_decoder;
   if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
   JpegDecoder recovery_map_decoder;
-  if (!recovery_map_decoder.decompressImage(compressed_map.data,
-                                    compressed_map.length)) {
+  if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
@@ -567,26 +583,17 @@
   map.width = recovery_map_decoder.getDecompressedImageWidth();
   map.height = recovery_map_decoder.getDecompressedImageHeight();
 
-
   jpegr_uncompressed_struct uncompressed_yuv_420_image;
   uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
   uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
   uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
 
   if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
-                                       jpeg_decoder.getXMPSize(), &metadata)) {
+                          jpeg_decoder.getXMPSize(), &metadata)) {
     return ERROR_JPEGR_DECODE_ERROR;
   }
 
-  if (request_sdr) {
-    memcpy(dest->data, uncompressed_yuv_420_image.data,
-            uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
-    dest->width = uncompressed_yuv_420_image.width;
-    dest->height = uncompressed_yuv_420_image.height;
-  } else {
-    JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
-  }
-
+  JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
   return NO_ERROR;
 }
 
@@ -926,18 +933,39 @@
   return NO_ERROR;
 }
 
-status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image,
+status_t RecoveryMap::toneMap(jr_uncompressed_ptr src,
                               jr_uncompressed_ptr dest) {
-  if (uncompressed_p010_image == nullptr || dest == nullptr) {
+  if (src == nullptr || dest == nullptr) {
     return ERROR_JPEGR_INVALID_NULL_PTR;
   }
 
-  dest->width = uncompressed_p010_image->width;
-  dest->height = uncompressed_p010_image->height;
-  unique_ptr<uint8_t[]> dest_data = make_unique<uint8_t[]>(dest->width * dest->height * 3 / 2);
-  dest->data = dest_data.get();
+  dest->width = src->width;
+  dest->height = src->height;
 
-  // TODO: Tone map algorighm here.
+  size_t pixel_count = src->width * src->height;
+  for (size_t y = 0; y < src->height; ++y) {
+    for (size_t x = 0; x < src->width; ++x) {
+      size_t pixel_y_idx = x + y * src->width;
+      size_t pixel_uv_idx = x / 2 + (y / 2) * (src->width / 2);
+
+      uint16_t y_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_y_idx]
+                        >> 6;
+      uint16_t u_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2]
+                        >> 6;
+      uint16_t v_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2 + 1]
+                        >> 6;
+
+      uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[pixel_y_idx];
+      uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count + pixel_uv_idx];
+      uint8_t* v = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count * 5 / 4 + pixel_uv_idx];
+
+      *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
+      *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
+      *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
+    }
+  }
+
+  dest->colorGamut = src->colorGamut;
 
   return NO_ERROR;
 }
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index c3c6fd4..8ff12fb 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -114,58 +114,57 @@
 }
 
 /* Test Encode API-0 and decode */
-// TODO: enable when tonemapper is ready.
-//TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
-//  int ret;
-//
-//  // Load input files.
-//  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
-//    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
-//  }
-//  mRawP010Image.width = TEST_IMAGE_WIDTH;
-//  mRawP010Image.height = TEST_IMAGE_HEIGHT;
-//  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
-//
-//  RecoveryMap recoveryMap;
-//
-//  jpegr_compressed_struct jpegR;
-//  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
-//  jpegR.data = malloc(jpegR.maxLength);
-//  ret = recoveryMap.encodeJPEGR(
-//      &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, 90, nullptr);
-//  if (ret != OK) {
-//    FAIL() << "Error code is " << ret;
-//  }
-//  if (SAVE_ENCODING_RESULT) {
-//    // Output image data to file
-//    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
-//    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-//    if (!imageFile.is_open()) {
-//      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-//    }
-//    imageFile.write((const char*)jpegR.data, jpegR.length);
-//  }
-//
-//  jpegr_uncompressed_struct decodedJpegR;
-//  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
-//  decodedJpegR.data = malloc(decodedJpegRSize);
-//  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
-//  if (ret != OK) {
-//    FAIL() << "Error code is " << ret;
-//  }
-//  if (SAVE_DECODING_RESULT) {
-//    // Output image data to file
-//    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
-//    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
-//    if (!imageFile.is_open()) {
-//      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
-//    }
-//    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
-//  }
-//
-//  free(jpegR.data);
-//  free(decodedJpegR.data);
-//}
+TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
+  int ret;
+
+  // Load input files.
+  if (!loadFile(RAW_P010_IMAGE, mRawP010Image.data, nullptr)) {
+    FAIL() << "Load file " << RAW_P010_IMAGE << " failed";
+  }
+  mRawP010Image.width = TEST_IMAGE_WIDTH;
+  mRawP010Image.height = TEST_IMAGE_HEIGHT;
+  mRawP010Image.colorGamut = jpegr_color_gamut::JPEGR_COLORGAMUT_BT2100;
+
+  RecoveryMap recoveryMap;
+
+  jpegr_compressed_struct jpegR;
+  jpegR.maxLength = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * sizeof(uint8_t);
+  jpegR.data = malloc(jpegR.maxLength);
+  ret = recoveryMap.encodeJPEGR(
+      &mRawP010Image, jpegr_transfer_function::JPEGR_TF_HLG, &jpegR, DEFAULT_JPEG_QUALITY, nullptr);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_ENCODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/encoded_from_jpeg_input.jpgr";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)jpegR.data, jpegR.length);
+  }
+
+  jpegr_uncompressed_struct decodedJpegR;
+  int decodedJpegRSize = TEST_IMAGE_WIDTH * TEST_IMAGE_HEIGHT * 4;
+  decodedJpegR.data = malloc(decodedJpegRSize);
+  ret = recoveryMap.decodeJPEGR(&jpegR, &decodedJpegR);
+  if (ret != OK) {
+    FAIL() << "Error code is " << ret;
+  }
+  if (SAVE_DECODING_RESULT) {
+    // Output image data to file
+    std::string filePath = "/sdcard/Documents/decoded_from_jpeg_input.rgb10";
+    std::ofstream imageFile(filePath.c_str(), std::ofstream::binary);
+    if (!imageFile.is_open()) {
+      ALOGE("%s: Unable to create file %s", __FUNCTION__, filePath.c_str());
+    }
+    imageFile.write((const char*)decodedJpegR.data, decodedJpegRSize);
+  }
+
+  free(jpegR.data);
+  free(decodedJpegR.data);
+}
 
 /* Test Encode API-1 and decode */
 TEST_F(RecoveryMapTest, encodeFromRawHdrAndSdrThenDecode) {
diff --git a/libs/nativewindow/include/android/hardware_buffer_aidl.h b/libs/nativewindow/include/android/hardware_buffer_aidl.h
index 906d9c6..1659d54 100644
--- a/libs/nativewindow/include/android/hardware_buffer_aidl.h
+++ b/libs/nativewindow/include/android/hardware_buffer_aidl.h
@@ -83,7 +83,7 @@
 class HardwareBuffer {
 public:
     HardwareBuffer() noexcept {}
-    explicit HardwareBuffer(HardwareBuffer&& other) noexcept : mBuffer(other.release()) {}
+    HardwareBuffer(HardwareBuffer&& other) noexcept : mBuffer(other.release()) {}
 
     ~HardwareBuffer() {
         reset();
@@ -119,6 +119,13 @@
     inline AHardwareBuffer* _Nullable get() const { return mBuffer; }
     inline explicit operator bool () const { return mBuffer != nullptr; }
 
+    inline bool operator!=(const HardwareBuffer& rhs) const { return get() != rhs.get(); }
+    inline bool operator<(const HardwareBuffer& rhs) const { return get() < rhs.get(); }
+    inline bool operator<=(const HardwareBuffer& rhs) const { return get() <= rhs.get(); }
+    inline bool operator==(const HardwareBuffer& rhs) const { return get() == rhs.get(); }
+    inline bool operator>(const HardwareBuffer& rhs) const { return get() > rhs.get(); }
+    inline bool operator>=(const HardwareBuffer& rhs) const { return get() >= rhs.get(); }
+
     HardwareBuffer& operator=(HardwareBuffer&& other) noexcept {
         reset(other.release());
         return *this;
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index f3b6ab9..511d7c9 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -25,6 +25,7 @@
 #include <SkSize.h>
 #include <SkString.h>
 #include <SkSurface.h>
+#include "include/gpu/GpuTypes.h" // from Skia
 #include <log/log.h>
 #include <utils/Trace.h>
 
@@ -44,7 +45,8 @@
     // Create blur surface with the bit depth and colorspace of the original surface
     SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
                                                        std::ceil(blurRect.height() * kInputScale));
-    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, scaledInfo);
+    sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(context,
+                                                           skgpu::Budgeted::kNo, scaledInfo);
 
     SkPaint paint;
     paint.setBlendMode(SkBlendMode::kSrc);
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index d33dd34..ec0ab4e 100644
--- a/libs/ui/Android.bp
+++ b/libs/ui/Android.bp
@@ -48,7 +48,6 @@
         integer_overflow: true,
         misc_undefined: ["bounds"],
     },
-
 }
 
 cc_library_static {
@@ -135,6 +134,7 @@
         "Gralloc2.cpp",
         "Gralloc3.cpp",
         "Gralloc4.cpp",
+        "Gralloc5.cpp",
         "GraphicBuffer.cpp",
         "GraphicBufferAllocator.cpp",
         "GraphicBufferMapper.cpp",
@@ -176,6 +176,7 @@
         "libsync",
         "libutils",
         "liblog",
+        "libvndksupport",
     ],
 
     export_shared_lib_headers: [
@@ -214,6 +215,8 @@
         "libnativewindow_headers",
         "libhardware_headers",
         "libui_headers",
+        "libimapper_stablec",
+        "libimapper_providerutils",
     ],
 
     export_static_lib_headers: [
diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp
index f23f10a..e9b5dec 100644
--- a/libs/ui/Gralloc2.cpp
+++ b/libs/ui/Gralloc2.cpp
@@ -161,7 +161,7 @@
     return static_cast<status_t>((ret.isOk()) ? error : kTransactionError);
 }
 
-status_t Gralloc2Mapper::importBuffer(const hardware::hidl_handle& rawHandle,
+status_t Gralloc2Mapper::importBuffer(const native_handle_t* rawHandle,
                                       buffer_handle_t* outBufferHandle) const {
     Error error;
     auto ret = mMapper->importBuffer(rawHandle,
diff --git a/libs/ui/Gralloc3.cpp b/libs/ui/Gralloc3.cpp
index 15c60bc..474d381 100644
--- a/libs/ui/Gralloc3.cpp
+++ b/libs/ui/Gralloc3.cpp
@@ -138,7 +138,7 @@
     return static_cast<status_t>((ret.isOk()) ? error : kTransactionError);
 }
 
-status_t Gralloc3Mapper::importBuffer(const hardware::hidl_handle& rawHandle,
+status_t Gralloc3Mapper::importBuffer(const native_handle_t* rawHandle,
                                       buffer_handle_t* outBufferHandle) const {
     Error error;
     auto ret = mMapper->importBuffer(rawHandle, [&](const auto& tmpError, const auto& tmpBuffer) {
diff --git a/libs/ui/Gralloc4.cpp b/libs/ui/Gralloc4.cpp
index f6ab7b2..7459466 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -196,7 +196,7 @@
     return static_cast<status_t>((ret.isOk()) ? error : kTransactionError);
 }
 
-status_t Gralloc4Mapper::importBuffer(const hardware::hidl_handle& rawHandle,
+status_t Gralloc4Mapper::importBuffer(const native_handle_t* rawHandle,
                                       buffer_handle_t* outBufferHandle) const {
     Error error;
     auto ret = mMapper->importBuffer(rawHandle, [&](const auto& tmpError, const auto& tmpBuffer) {
@@ -1233,7 +1233,10 @@
 
     if (mAidlAllocator) {
         AllocationResult result;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
         auto status = mAidlAllocator->allocate(descriptor, bufferCount, &result);
+#pragma clang diagnostic pop // deprecation
         if (!status.isOk()) {
             error = status.getExceptionCode();
             if (error == EX_SERVICE_SPECIFIC) {
diff --git a/libs/ui/Gralloc5.cpp b/libs/ui/Gralloc5.cpp
new file mode 100644
index 0000000..6f196b8
--- /dev/null
+++ b/libs/ui/Gralloc5.cpp
@@ -0,0 +1,903 @@
+/*
+ * Copyright (C) 2022 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 "Gralloc5"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <ui/Gralloc5.h>
+
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android/binder_manager.h>
+#include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
+#include <binder/IPCThreadState.h>
+#include <dlfcn.h>
+#include <ui/FatVector.h>
+#include <vndksupport/linker.h>
+
+using namespace aidl::android::hardware::graphics::allocator;
+using namespace aidl::android::hardware::graphics::common;
+using namespace ::android::hardware::graphics::mapper;
+
+namespace android {
+
+static const auto kIAllocatorServiceName = IAllocator::descriptor + std::string("/default");
+static const auto kIAllocatorMinimumVersion = 2;
+
+// TODO(b/72323293, b/72703005): Remove these invalid bits from callers
+static constexpr uint64_t kRemovedUsageBits = static_cast<uint64_t>((1 << 10) | (1 << 13));
+
+typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper *_Nullable *_Nonnull outImplementation);
+
+struct Gralloc5 {
+    std::shared_ptr<IAllocator> allocator;
+    AIMapper *mapper = nullptr;
+};
+
+static std::shared_ptr<IAllocator> waitForAllocator() {
+    if (__builtin_available(android 31, *)) {
+        if (!AServiceManager_isDeclared(kIAllocatorServiceName.c_str())) {
+            return nullptr;
+        }
+        auto allocator = IAllocator::fromBinder(
+                ndk::SpAIBinder(AServiceManager_waitForService(kIAllocatorServiceName.c_str())));
+        if (!allocator) {
+            ALOGE("AIDL IAllocator declared but failed to get service");
+            return nullptr;
+        }
+
+        int32_t version = 0;
+        if (!allocator->getInterfaceVersion(&version).isOk()) {
+            ALOGE("Failed to query interface version");
+            return nullptr;
+        }
+        if (version < kIAllocatorMinimumVersion) {
+            return nullptr;
+        }
+        return allocator;
+    } else {
+        // TODO: LOG_ALWAYS_FATAL("libui is not backwards compatible");
+        return nullptr;
+    }
+}
+
+static void *loadIMapperLibrary() {
+    static void *imapperLibrary = []() -> void * {
+        auto allocator = waitForAllocator();
+        std::string mapperSuffix;
+        auto status = allocator->getIMapperLibrarySuffix(&mapperSuffix);
+        if (!status.isOk()) {
+            ALOGE("Failed to get IMapper library suffix");
+            return nullptr;
+        }
+
+        std::string lib_name = "mapper." + mapperSuffix + ".so";
+        void *so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW);
+        if (!so) {
+            ALOGE("Failed to load %s", lib_name.c_str());
+        }
+        return so;
+    }();
+    return imapperLibrary;
+}
+
+static const Gralloc5 &getInstance() {
+    static Gralloc5 instance = []() {
+        auto allocator = waitForAllocator();
+        if (!allocator) {
+            return Gralloc5{};
+        }
+        void *so = loadIMapperLibrary();
+        if (!so) {
+            return Gralloc5{};
+        }
+        auto loadIMapper = (AIMapper_loadIMapperFn)dlsym(so, "AIMapper_loadIMapper");
+        AIMapper *mapper = nullptr;
+        AIMapper_Error error = loadIMapper(&mapper);
+        if (error != AIMAPPER_ERROR_NONE) {
+            ALOGE("AIMapper_loadIMapper failed %d", error);
+            return Gralloc5{};
+        }
+        return Gralloc5{std::move(allocator), mapper};
+    }();
+    return instance;
+}
+
+template <StandardMetadataType T>
+static auto getStandardMetadata(AIMapper *mapper, buffer_handle_t bufferHandle)
+        -> decltype(StandardMetadata<T>::value::decode(nullptr, 0)) {
+    using Value = typename StandardMetadata<T>::value;
+    // TODO: Tune for common-case better
+    FatVector<uint8_t, 128> buffer;
+    int32_t sizeRequired = mapper->v5.getStandardMetadata(bufferHandle, static_cast<int64_t>(T),
+                                                          buffer.data(), buffer.size());
+    if (sizeRequired < 0) {
+        ALOGW_IF(-AIMAPPER_ERROR_UNSUPPORTED != sizeRequired,
+                 "Unexpected error %d from valid getStandardMetadata call", -sizeRequired);
+        return std::nullopt;
+    }
+    if ((size_t)sizeRequired > buffer.size()) {
+        buffer.resize(sizeRequired);
+        sizeRequired = mapper->v5.getStandardMetadata(bufferHandle, static_cast<int64_t>(T),
+                                                      buffer.data(), buffer.size());
+    }
+    if (sizeRequired < 0 || (size_t)sizeRequired > buffer.size()) {
+        ALOGW("getStandardMetadata failed, received %d with buffer size %zd", sizeRequired,
+              buffer.size());
+        // Generate a fail type
+        return std::nullopt;
+    }
+    return Value::decode(buffer.data(), sizeRequired);
+}
+
+template <StandardMetadataType T>
+static AIMapper_Error setStandardMetadata(AIMapper *mapper, buffer_handle_t bufferHandle,
+                                          const typename StandardMetadata<T>::value_type &value) {
+    using Value = typename StandardMetadata<T>::value;
+    int32_t sizeRequired = Value::encode(value, nullptr, 0);
+    if (sizeRequired < 0) {
+        ALOGW("Failed to calculate required size");
+        return static_cast<AIMapper_Error>(-sizeRequired);
+    }
+    FatVector<uint8_t, 128> buffer;
+    buffer.resize(sizeRequired);
+    sizeRequired = Value::encode(value, buffer.data(), buffer.size());
+    if (sizeRequired < 0 || (size_t)sizeRequired > buffer.size()) {
+        ALOGW("Failed to encode with calculated size %d; buffer size %zd", sizeRequired,
+              buffer.size());
+        return static_cast<AIMapper_Error>(-sizeRequired);
+    }
+    return mapper->v5.setStandardMetadata(bufferHandle, static_cast<int64_t>(T), buffer.data(),
+                                          sizeRequired);
+}
+
+Gralloc5Allocator::Gralloc5Allocator(const Gralloc5Mapper &mapper) : mMapper(mapper) {
+    mAllocator = getInstance().allocator;
+}
+
+bool Gralloc5Allocator::isLoaded() const {
+    return mAllocator != nullptr;
+}
+
+static uint64_t getValidUsageBits() {
+    static const uint64_t validUsageBits = []() -> uint64_t {
+        uint64_t bits = 0;
+        for (const auto bit : ndk::enum_range<BufferUsage>{}) {
+            bits |= static_cast<int64_t>(bit);
+        }
+        return bits;
+    }();
+    return validUsageBits | kRemovedUsageBits;
+}
+
+static std::optional<BufferDescriptorInfo> makeDescriptor(std::string requestorName, uint32_t width,
+                                                          uint32_t height, PixelFormat format,
+                                                          uint32_t layerCount, uint64_t usage) {
+    uint64_t validUsageBits = getValidUsageBits();
+    if (usage & ~validUsageBits) {
+        ALOGE("buffer descriptor contains invalid usage bits 0x%" PRIx64, usage & ~validUsageBits);
+        return std::nullopt;
+    }
+
+    BufferDescriptorInfo descriptorInfo{
+            .width = static_cast<int32_t>(width),
+            .height = static_cast<int32_t>(height),
+            .layerCount = static_cast<int32_t>(layerCount),
+            .format = static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format),
+            .usage = static_cast<BufferUsage>(usage),
+    };
+    auto nameLength = std::min(requestorName.length(), descriptorInfo.name.size() - 1);
+    memcpy(descriptorInfo.name.data(), requestorName.data(), nameLength);
+    requestorName.data()[nameLength] = 0;
+    return descriptorInfo;
+}
+
+std::string Gralloc5Allocator::dumpDebugInfo(bool less) const {
+    return mMapper.dumpBuffers(less);
+}
+
+status_t Gralloc5Allocator::allocate(std::string requestorName, uint32_t width, uint32_t height,
+                                     android::PixelFormat format, uint32_t layerCount,
+                                     uint64_t usage, uint32_t bufferCount, uint32_t *outStride,
+                                     buffer_handle_t *outBufferHandles, bool importBuffers) const {
+    auto descriptorInfo = makeDescriptor(requestorName, width, height, format, layerCount, usage);
+    if (!descriptorInfo) {
+        return BAD_VALUE;
+    }
+
+    AllocationResult result;
+    auto status = mAllocator->allocate2(*descriptorInfo, bufferCount, &result);
+    if (!status.isOk()) {
+        auto error = status.getExceptionCode();
+        if (error == EX_SERVICE_SPECIFIC) {
+            error = status.getServiceSpecificError();
+        }
+        if (error == OK) {
+            error = UNKNOWN_ERROR;
+        }
+        return error;
+    }
+
+    if (importBuffers) {
+        for (uint32_t i = 0; i < bufferCount; i++) {
+            auto handle = makeFromAidl(result.buffers[i]);
+            auto error = mMapper.importBuffer(handle, &outBufferHandles[i]);
+            native_handle_delete(handle);
+            if (error != NO_ERROR) {
+                for (uint32_t j = 0; j < i; j++) {
+                    mMapper.freeBuffer(outBufferHandles[j]);
+                    outBufferHandles[j] = nullptr;
+                }
+                return error;
+            }
+        }
+    } else {
+        for (uint32_t i = 0; i < bufferCount; i++) {
+            outBufferHandles[i] = dupFromAidl(result.buffers[i]);
+            if (!outBufferHandles[i]) {
+                for (uint32_t j = 0; j < i; j++) {
+                    auto buffer = const_cast<native_handle_t *>(outBufferHandles[j]);
+                    native_handle_close(buffer);
+                    native_handle_delete(buffer);
+                    outBufferHandles[j] = nullptr;
+                }
+                return NO_MEMORY;
+            }
+        }
+    }
+
+    *outStride = result.stride;
+
+    // Release all the resources held by AllocationResult (specifically any remaining FDs)
+    result = {};
+    // make sure the kernel driver sees BC_FREE_BUFFER and closes the fds now
+    // TODO: Re-enable this at some point if it's necessary. We can't do it now because libui
+    // is marked apex_available (b/214400477) and libbinder isn't (which of course is correct)
+    // IPCThreadState::self()->flushCommands();
+
+    return OK;
+}
+
+void Gralloc5Mapper::preload() {
+    // TODO(b/261858155): Implement. We can't bounce off of IAllocator for this because zygote can't
+    // use binder. So when an alternate strategy of retrieving the library prefix is available,
+    // use that here.
+}
+
+Gralloc5Mapper::Gralloc5Mapper() {
+    mMapper = getInstance().mapper;
+}
+
+bool Gralloc5Mapper::isLoaded() const {
+    return mMapper != nullptr && mMapper->version >= AIMAPPER_VERSION_5;
+}
+
+std::string Gralloc5Mapper::dumpBuffer(buffer_handle_t bufferHandle, bool less) const {
+    // TODO(b/261858392): Implement
+    (void)bufferHandle;
+    (void)less;
+    return {};
+}
+
+std::string Gralloc5Mapper::dumpBuffers(bool less) const {
+    // TODO(b/261858392): Implement
+    (void)less;
+    return {};
+}
+
+status_t Gralloc5Mapper::importBuffer(const native_handle_t *rawHandle,
+                                      buffer_handle_t *outBufferHandle) const {
+    return mMapper->v5.importBuffer(rawHandle, outBufferHandle);
+}
+
+void Gralloc5Mapper::freeBuffer(buffer_handle_t bufferHandle) const {
+    mMapper->v5.freeBuffer(bufferHandle);
+}
+
+status_t Gralloc5Mapper::validateBufferSize(buffer_handle_t bufferHandle, uint32_t width,
+                                            uint32_t height, PixelFormat format,
+                                            uint32_t layerCount, uint64_t usage,
+                                            uint32_t stride) const {
+    {
+        auto value = getStandardMetadata<StandardMetadataType::WIDTH>(mMapper, bufferHandle);
+        if (width != value) {
+            ALOGW("Width didn't match, expected %d got %" PRId64, width, value.value_or(-1));
+            return BAD_VALUE;
+        }
+    }
+    {
+        auto value = getStandardMetadata<StandardMetadataType::HEIGHT>(mMapper, bufferHandle);
+        if (height != value) {
+            ALOGW("Height didn't match, expected %d got %" PRId64, height, value.value_or(-1));
+            return BAD_VALUE;
+        }
+    }
+    {
+        auto value =
+                getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(mMapper,
+                                                                                  bufferHandle);
+        if (static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format) !=
+            value) {
+            ALOGW("Format didn't match, expected %d got %s", format,
+                  value.has_value() ? toString(*value).c_str() : "<null>");
+            return BAD_VALUE;
+        }
+    }
+    {
+        auto value = getStandardMetadata<StandardMetadataType::LAYER_COUNT>(mMapper, bufferHandle);
+        if (layerCount != value) {
+            ALOGW("Layer count didn't match, expected %d got %" PRId64, layerCount,
+                  value.value_or(-1));
+            return BAD_VALUE;
+        }
+    }
+    {
+        auto value = getStandardMetadata<StandardMetadataType::USAGE>(mMapper, bufferHandle);
+        if (static_cast<BufferUsage>(usage) != value) {
+            ALOGW("Usage didn't match, expected %" PRIu64 " got %" PRId64, usage,
+                  static_cast<int64_t>(value.value_or(BufferUsage::CPU_READ_NEVER)));
+            return BAD_VALUE;
+        }
+    }
+    {
+        (void)stride;
+        // TODO(b/261856851): Add StandardMetadataType::STRIDE && enable this
+        //        auto value = getStandardMetadata<StandardMetadataType::STRIDE>(mMapper,
+        //        bufferHandle); if (static_cast<BufferUsage>(usage) != value) {
+        //            ALOGW("Layer count didn't match, expected %" PRIu64 " got %" PRId64, usage,
+        //                  static_cast<int64_t>(value.value_or(BufferUsage::CPU_READ_NEVER)));
+        //            return BAD_VALUE;
+        //        }
+    }
+    return OK;
+}
+
+void Gralloc5Mapper::getTransportSize(buffer_handle_t bufferHandle, uint32_t *outNumFds,
+                                      uint32_t *outNumInts) const {
+    mMapper->v5.getTransportSize(bufferHandle, outNumFds, outNumInts);
+}
+
+status_t Gralloc5Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds,
+                              int acquireFence, void **outData, int32_t *outBytesPerPixel,
+                              int32_t *outBytesPerStride) const {
+    std::vector<ui::PlaneLayout> planeLayouts;
+    status_t err = getPlaneLayouts(bufferHandle, &planeLayouts);
+
+    if (err == NO_ERROR && !planeLayouts.empty()) {
+        if (outBytesPerPixel) {
+            int32_t bitsPerPixel = planeLayouts.front().sampleIncrementInBits;
+            for (const auto &planeLayout : planeLayouts) {
+                if (bitsPerPixel != planeLayout.sampleIncrementInBits) {
+                    bitsPerPixel = -1;
+                }
+            }
+            if (bitsPerPixel >= 0 && bitsPerPixel % 8 == 0) {
+                *outBytesPerPixel = bitsPerPixel / 8;
+            } else {
+                *outBytesPerPixel = -1;
+            }
+        }
+        if (outBytesPerStride) {
+            int32_t bytesPerStride = planeLayouts.front().strideInBytes;
+            for (const auto &planeLayout : planeLayouts) {
+                if (bytesPerStride != planeLayout.strideInBytes) {
+                    bytesPerStride = -1;
+                }
+            }
+            if (bytesPerStride >= 0) {
+                *outBytesPerStride = bytesPerStride;
+            } else {
+                *outBytesPerStride = -1;
+            }
+        }
+    }
+
+    auto status = mMapper->v5.lock(bufferHandle, usage, bounds, acquireFence, outData);
+
+    ALOGW_IF(status != AIMAPPER_ERROR_NONE, "lock(%p, ...) failed: %d", bufferHandle, status);
+    return static_cast<status_t>(status);
+}
+
+status_t Gralloc5Mapper::lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds,
+                              int acquireFence, android_ycbcr *outYcbcr) const {
+    if (!outYcbcr) {
+        return BAD_VALUE;
+    }
+
+    // TODO(b/262279301): Change the return type of ::unlock to unique_fd instead of int so that
+    //  ignoring the return value "just works" instead
+    auto unlock = [this](buffer_handle_t bufferHandle) {
+        int fence = this->unlock(bufferHandle);
+        if (fence != -1) {
+            ::close(fence);
+        }
+    };
+
+    std::vector<ui::PlaneLayout> planeLayouts;
+    status_t error = getPlaneLayouts(bufferHandle, &planeLayouts);
+    if (error != NO_ERROR) {
+        return error;
+    }
+
+    void *data = nullptr;
+    error = lock(bufferHandle, usage, bounds, acquireFence, &data, nullptr, nullptr);
+    if (error != NO_ERROR) {
+        return error;
+    }
+
+    android_ycbcr ycbcr;
+
+    ycbcr.y = nullptr;
+    ycbcr.cb = nullptr;
+    ycbcr.cr = nullptr;
+    ycbcr.ystride = 0;
+    ycbcr.cstride = 0;
+    ycbcr.chroma_step = 0;
+
+    for (const auto &planeLayout : planeLayouts) {
+        for (const auto &planeLayoutComponent : planeLayout.components) {
+            if (!gralloc4::isStandardPlaneLayoutComponentType(planeLayoutComponent.type)) {
+                continue;
+            }
+
+            uint8_t *tmpData = static_cast<uint8_t *>(data) + planeLayout.offsetInBytes;
+
+            // Note that `offsetInBits` may not be a multiple of 8 for packed formats (e.g. P010)
+            // but we still want to point to the start of the first byte.
+            tmpData += (planeLayoutComponent.offsetInBits / 8);
+
+            uint64_t sampleIncrementInBytes;
+
+            auto type = static_cast<PlaneLayoutComponentType>(planeLayoutComponent.type.value);
+            switch (type) {
+                case PlaneLayoutComponentType::Y:
+                    if ((ycbcr.y != nullptr) || (planeLayout.sampleIncrementInBits % 8 != 0)) {
+                        unlock(bufferHandle);
+                        return BAD_VALUE;
+                    }
+                    ycbcr.y = tmpData;
+                    ycbcr.ystride = planeLayout.strideInBytes;
+                    break;
+
+                case PlaneLayoutComponentType::CB:
+                case PlaneLayoutComponentType::CR:
+                    if (planeLayout.sampleIncrementInBits % 8 != 0) {
+                        unlock(bufferHandle);
+                        return BAD_VALUE;
+                    }
+
+                    sampleIncrementInBytes = planeLayout.sampleIncrementInBits / 8;
+                    if ((sampleIncrementInBytes != 1) && (sampleIncrementInBytes != 2) &&
+                        (sampleIncrementInBytes != 4)) {
+                        unlock(bufferHandle);
+                        return BAD_VALUE;
+                    }
+
+                    if (ycbcr.cstride == 0 && ycbcr.chroma_step == 0) {
+                        ycbcr.cstride = planeLayout.strideInBytes;
+                        ycbcr.chroma_step = sampleIncrementInBytes;
+                    } else {
+                        if ((static_cast<int64_t>(ycbcr.cstride) != planeLayout.strideInBytes) ||
+                            (ycbcr.chroma_step != sampleIncrementInBytes)) {
+                            unlock(bufferHandle);
+                            return BAD_VALUE;
+                        }
+                    }
+
+                    if (type == PlaneLayoutComponentType::CB) {
+                        if (ycbcr.cb != nullptr) {
+                            unlock(bufferHandle);
+                            return BAD_VALUE;
+                        }
+                        ycbcr.cb = tmpData;
+                    } else {
+                        if (ycbcr.cr != nullptr) {
+                            unlock(bufferHandle);
+                            return BAD_VALUE;
+                        }
+                        ycbcr.cr = tmpData;
+                    }
+                    break;
+                default:
+                    break;
+            };
+        }
+    }
+
+    *outYcbcr = ycbcr;
+    return OK;
+}
+
+int Gralloc5Mapper::unlock(buffer_handle_t bufferHandle) const {
+    int fence = -1;
+    AIMapper_Error error = mMapper->v5.unlock(bufferHandle, &fence);
+    if (error != AIMAPPER_ERROR_NONE) {
+        ALOGW("unlock failed with error %d", error);
+    }
+    return fence;
+}
+
+status_t Gralloc5Mapper::isSupported(uint32_t width, uint32_t height, PixelFormat format,
+                                     uint32_t layerCount, uint64_t usage,
+                                     bool *outSupported) const {
+    auto descriptorInfo = makeDescriptor("", width, height, format, layerCount, usage);
+    if (!descriptorInfo) {
+        *outSupported = false;
+        return OK;
+    }
+    auto status = getInstance().allocator->isSupported(*descriptorInfo, outSupported);
+    if (!status.isOk()) {
+        ALOGW("IAllocator::isSupported error %d (%s)", status.getStatus(), status.getMessage());
+        *outSupported = false;
+    }
+    return OK;
+}
+
+status_t Gralloc5Mapper::getBufferId(buffer_handle_t bufferHandle, uint64_t *outBufferId) const {
+    auto value = getStandardMetadata<StandardMetadataType::BUFFER_ID>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outBufferId = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getName(buffer_handle_t bufferHandle, std::string *outName) const {
+    auto value = getStandardMetadata<StandardMetadataType::NAME>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outName = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getWidth(buffer_handle_t bufferHandle, uint64_t *outWidth) const {
+    auto value = getStandardMetadata<StandardMetadataType::WIDTH>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outWidth = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getHeight(buffer_handle_t bufferHandle, uint64_t *outHeight) const {
+    auto value = getStandardMetadata<StandardMetadataType::HEIGHT>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outHeight = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getLayerCount(buffer_handle_t bufferHandle,
+                                       uint64_t *outLayerCount) const {
+    auto value = getStandardMetadata<StandardMetadataType::LAYER_COUNT>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outLayerCount = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getPixelFormatRequested(buffer_handle_t bufferHandle,
+                                                 ui::PixelFormat *outPixelFormatRequested) const {
+    auto value = getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(mMapper,
+                                                                                   bufferHandle);
+    if (value.has_value()) {
+        *outPixelFormatRequested = static_cast<ui::PixelFormat>(*value);
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getPixelFormatFourCC(buffer_handle_t bufferHandle,
+                                              uint32_t *outPixelFormatFourCC) const {
+    auto value =
+            getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_FOURCC>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outPixelFormatFourCC = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getPixelFormatModifier(buffer_handle_t bufferHandle,
+                                                uint64_t *outPixelFormatModifier) const {
+    auto value =
+            getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_MODIFIER>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outPixelFormatModifier = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getUsage(buffer_handle_t bufferHandle, uint64_t *outUsage) const {
+    auto value = getStandardMetadata<StandardMetadataType::USAGE>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outUsage = static_cast<uint64_t>(*value);
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getAllocationSize(buffer_handle_t bufferHandle,
+                                           uint64_t *outAllocationSize) const {
+    auto value = getStandardMetadata<StandardMetadataType::ALLOCATION_SIZE>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outAllocationSize = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getProtectedContent(buffer_handle_t bufferHandle,
+                                             uint64_t *outProtectedContent) const {
+    auto value =
+            getStandardMetadata<StandardMetadataType::PROTECTED_CONTENT>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outProtectedContent = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getCompression(
+        buffer_handle_t bufferHandle,
+        aidl::android::hardware::graphics::common::ExtendableType *outCompression) const {
+    auto value = getStandardMetadata<StandardMetadataType::COMPRESSION>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outCompression = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getCompression(buffer_handle_t bufferHandle,
+                                        ui::Compression *outCompression) const {
+    auto value = getStandardMetadata<StandardMetadataType::COMPRESSION>(mMapper, bufferHandle);
+    if (!value.has_value()) {
+        return UNKNOWN_TRANSACTION;
+    }
+    if (!gralloc4::isStandardCompression(*value)) {
+        return BAD_TYPE;
+    }
+    *outCompression = gralloc4::getStandardCompressionValue(*value);
+    return OK;
+}
+
+status_t Gralloc5Mapper::getInterlaced(
+        buffer_handle_t bufferHandle,
+        aidl::android::hardware::graphics::common::ExtendableType *outInterlaced) const {
+    auto value = getStandardMetadata<StandardMetadataType::INTERLACED>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outInterlaced = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getInterlaced(buffer_handle_t bufferHandle,
+                                       ui::Interlaced *outInterlaced) const {
+    if (!outInterlaced) {
+        return BAD_VALUE;
+    }
+    ExtendableType interlaced;
+    status_t error = getInterlaced(bufferHandle, &interlaced);
+    if (error) {
+        return error;
+    }
+    if (!gralloc4::isStandardInterlaced(interlaced)) {
+        return BAD_TYPE;
+    }
+    *outInterlaced = gralloc4::getStandardInterlacedValue(interlaced);
+    return NO_ERROR;
+}
+
+status_t Gralloc5Mapper::getChromaSiting(
+        buffer_handle_t bufferHandle,
+        aidl::android::hardware::graphics::common::ExtendableType *outChromaSiting) const {
+    auto value = getStandardMetadata<StandardMetadataType::CHROMA_SITING>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outChromaSiting = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getChromaSiting(buffer_handle_t bufferHandle,
+                                         ui::ChromaSiting *outChromaSiting) const {
+    if (!outChromaSiting) {
+        return BAD_VALUE;
+    }
+    ExtendableType chromaSiting;
+    status_t error = getChromaSiting(bufferHandle, &chromaSiting);
+    if (error) {
+        return error;
+    }
+    if (!gralloc4::isStandardChromaSiting(chromaSiting)) {
+        return BAD_TYPE;
+    }
+    *outChromaSiting = gralloc4::getStandardChromaSitingValue(chromaSiting);
+    return NO_ERROR;
+}
+
+status_t Gralloc5Mapper::getPlaneLayouts(buffer_handle_t bufferHandle,
+                                         std::vector<ui::PlaneLayout> *outPlaneLayouts) const {
+    auto value = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outPlaneLayouts = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDataspace(buffer_handle_t bufferHandle,
+                                      ui::Dataspace *outDataspace) const {
+    auto value = getStandardMetadata<StandardMetadataType::DATASPACE>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outDataspace = static_cast<ui::Dataspace>(*value);
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace) const {
+    return setStandardMetadata<StandardMetadataType::DATASPACE>(mMapper, bufferHandle,
+                                                                static_cast<Dataspace>(dataspace));
+}
+
+status_t Gralloc5Mapper::getBlendMode(buffer_handle_t bufferHandle,
+                                      ui::BlendMode *outBlendMode) const {
+    auto value = getStandardMetadata<StandardMetadataType::BLEND_MODE>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outBlendMode = static_cast<ui::BlendMode>(*value);
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getSmpte2086(buffer_handle_t bufferHandle,
+                                      std::optional<ui::Smpte2086> *outSmpte2086) const {
+    auto value = getStandardMetadata<StandardMetadataType::SMPTE2086>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outSmpte2086 = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setSmpte2086(buffer_handle_t bufferHandle,
+                                      std::optional<ui::Smpte2086> smpte2086) const {
+    return setStandardMetadata<StandardMetadataType::SMPTE2086>(mMapper, bufferHandle, smpte2086);
+}
+
+status_t Gralloc5Mapper::getCta861_3(buffer_handle_t bufferHandle,
+                                     std::optional<ui::Cta861_3> *outCta861_3) const {
+    auto value = getStandardMetadata<StandardMetadataType::CTA861_3>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outCta861_3 = *value;
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setCta861_3(buffer_handle_t bufferHandle,
+                                     std::optional<ui::Cta861_3> cta861_3) const {
+    return setStandardMetadata<StandardMetadataType::CTA861_3>(mMapper, bufferHandle, cta861_3);
+}
+
+status_t Gralloc5Mapper::getSmpte2094_40(
+        buffer_handle_t bufferHandle, std::optional<std::vector<uint8_t>> *outSmpte2094_40) const {
+    auto value = getStandardMetadata<StandardMetadataType::SMPTE2094_40>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outSmpte2094_40 = std::move(*value);
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setSmpte2094_40(buffer_handle_t bufferHandle,
+                                         std::optional<std::vector<uint8_t>> smpte2094_40) const {
+    return setStandardMetadata<StandardMetadataType::SMPTE2094_40>(mMapper, bufferHandle,
+                                                                   smpte2094_40);
+}
+
+status_t Gralloc5Mapper::getSmpte2094_10(
+        buffer_handle_t bufferHandle, std::optional<std::vector<uint8_t>> *outSmpte2094_10) const {
+    auto value = getStandardMetadata<StandardMetadataType::SMPTE2094_10>(mMapper, bufferHandle);
+    if (value.has_value()) {
+        *outSmpte2094_10 = std::move(*value);
+        return OK;
+    }
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setSmpte2094_10(buffer_handle_t bufferHandle,
+                                         std::optional<std::vector<uint8_t>> smpte2094_10) const {
+    return setStandardMetadata<StandardMetadataType::SMPTE2094_10>(mMapper, bufferHandle,
+                                                                   smpte2094_10);
+}
+
+status_t Gralloc5Mapper::getDefaultPixelFormatFourCC(uint32_t, uint32_t, PixelFormat, uint32_t,
+                                                     uint64_t, uint32_t *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultPixelFormatModifier(uint32_t, uint32_t, PixelFormat, uint32_t,
+                                                       uint64_t, uint64_t *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultAllocationSize(uint32_t, uint32_t, PixelFormat, uint32_t,
+                                                  uint64_t, uint64_t *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultProtectedContent(uint32_t, uint32_t, PixelFormat, uint32_t,
+                                                    uint64_t, uint64_t *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultCompression(
+        uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+        aidl::android::hardware::graphics::common::ExtendableType *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultCompression(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+                                               ui::Compression *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultInterlaced(
+        uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+        aidl::android::hardware::graphics::common::ExtendableType *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultInterlaced(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+                                              ui::Interlaced *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultChromaSiting(
+        uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+        aidl::android::hardware::graphics::common::ExtendableType *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultChromaSiting(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+                                                ui::ChromaSiting *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getDefaultPlaneLayouts(uint32_t, uint32_t, PixelFormat, uint32_t, uint64_t,
+                                                std::vector<ui::PlaneLayout> *) const {
+    // TODO(b/261857910): Remove
+    return UNKNOWN_TRANSACTION;
+}
+
+} // namespace android
\ No newline at end of file
diff --git a/libs/ui/GraphicBufferAllocator.cpp b/libs/ui/GraphicBufferAllocator.cpp
index 3f958ba..c0abec2 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -34,6 +34,7 @@
 #include <ui/Gralloc2.h>
 #include <ui/Gralloc3.h>
 #include <ui/Gralloc4.h>
+#include <ui/Gralloc5.h>
 #include <ui/GraphicBufferMapper.h>
 
 namespace android {
@@ -48,23 +49,27 @@
     GraphicBufferAllocator::alloc_rec_t> GraphicBufferAllocator::sAllocList;
 
 GraphicBufferAllocator::GraphicBufferAllocator() : mMapper(GraphicBufferMapper::getInstance()) {
-    mAllocator = std::make_unique<const Gralloc4Allocator>(
-            reinterpret_cast<const Gralloc4Mapper&>(mMapper.getGrallocMapper()));
-    if (mAllocator->isLoaded()) {
-        return;
+    switch (mMapper.getMapperVersion()) {
+        case GraphicBufferMapper::GRALLOC_5:
+            mAllocator = std::make_unique<const Gralloc5Allocator>(
+                    reinterpret_cast<const Gralloc5Mapper&>(mMapper.getGrallocMapper()));
+            break;
+        case GraphicBufferMapper::GRALLOC_4:
+            mAllocator = std::make_unique<const Gralloc4Allocator>(
+                    reinterpret_cast<const Gralloc4Mapper&>(mMapper.getGrallocMapper()));
+            break;
+        case GraphicBufferMapper::GRALLOC_3:
+            mAllocator = std::make_unique<const Gralloc3Allocator>(
+                    reinterpret_cast<const Gralloc3Mapper&>(mMapper.getGrallocMapper()));
+            break;
+        case GraphicBufferMapper::GRALLOC_2:
+            mAllocator = std::make_unique<const Gralloc2Allocator>(
+                    reinterpret_cast<const Gralloc2Mapper&>(mMapper.getGrallocMapper()));
+            break;
     }
-    mAllocator = std::make_unique<const Gralloc3Allocator>(
-            reinterpret_cast<const Gralloc3Mapper&>(mMapper.getGrallocMapper()));
-    if (mAllocator->isLoaded()) {
-        return;
-    }
-    mAllocator = std::make_unique<const Gralloc2Allocator>(
-            reinterpret_cast<const Gralloc2Mapper&>(mMapper.getGrallocMapper()));
-    if (mAllocator->isLoaded()) {
-        return;
-    }
-
-    LOG_ALWAYS_FATAL("gralloc-allocator is missing");
+    LOG_ALWAYS_FATAL_IF(!mAllocator->isLoaded(),
+                        "Failed to load matching allocator for mapper version %d",
+                        mMapper.getMapperVersion());
 }
 
 GraphicBufferAllocator::~GraphicBufferAllocator() {}
diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp
index a98e697..6002a6d 100644
--- a/libs/ui/GraphicBufferMapper.cpp
+++ b/libs/ui/GraphicBufferMapper.cpp
@@ -36,6 +36,7 @@
 #include <ui/Gralloc2.h>
 #include <ui/Gralloc3.h>
 #include <ui/Gralloc4.h>
+#include <ui/Gralloc5.h>
 #include <ui/GraphicBuffer.h>
 
 #include <system/graphics.h>
@@ -49,9 +50,15 @@
     Gralloc2Mapper::preload();
     Gralloc3Mapper::preload();
     Gralloc4Mapper::preload();
+    Gralloc5Mapper::preload();
 }
 
 GraphicBufferMapper::GraphicBufferMapper() {
+    mMapper = std::make_unique<const Gralloc5Mapper>();
+    if (mMapper->isLoaded()) {
+        mMapperVersion = Version::GRALLOC_5;
+        return;
+    }
     mMapper = std::make_unique<const Gralloc4Mapper>();
     if (mMapper->isLoaded()) {
         mMapperVersion = Version::GRALLOC_4;
@@ -82,15 +89,14 @@
     ALOGD("%s", s.c_str());
 }
 
-status_t GraphicBufferMapper::importBuffer(buffer_handle_t rawHandle,
-        uint32_t width, uint32_t height, uint32_t layerCount,
-        PixelFormat format, uint64_t usage, uint32_t stride,
-        buffer_handle_t* outHandle)
-{
+status_t GraphicBufferMapper::importBuffer(const native_handle_t* rawHandle, uint32_t width,
+                                           uint32_t height, uint32_t layerCount, PixelFormat format,
+                                           uint64_t usage, uint32_t stride,
+                                           buffer_handle_t* outHandle) {
     ATRACE_CALL();
 
     buffer_handle_t bufferHandle;
-    status_t error = mMapper->importBuffer(hardware::hidl_handle(rawHandle), &bufferHandle);
+    status_t error = mMapper->importBuffer(rawHandle, &bufferHandle);
     if (error != NO_ERROR) {
         ALOGW("importBuffer(%p) failed: %d", rawHandle, error);
         return error;
@@ -109,6 +115,11 @@
     return NO_ERROR;
 }
 
+status_t GraphicBufferMapper::importBufferNoValidate(const native_handle_t* rawHandle,
+                                                     buffer_handle_t* outHandle) {
+    return mMapper->importBuffer(rawHandle, outHandle);
+}
+
 void GraphicBufferMapper::getTransportSize(buffer_handle_t handle,
             uint32_t* outTransportNumFds, uint32_t* outTransportNumInts)
 {
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index 6101d4b..b494cbe 100644
--- a/libs/ui/include/ui/Gralloc.h
+++ b/libs/ui/include/ui/Gralloc.h
@@ -39,14 +39,11 @@
         return "";
     }
 
-    virtual status_t createDescriptor(void* bufferDescriptorInfo,
-                                      void* outBufferDescriptor) const = 0;
-
     // Import a buffer that is from another HAL, another process, or is
     // cloned.
     //
     // The returned handle must be freed with freeBuffer.
-    virtual status_t importBuffer(const hardware::hidl_handle& rawHandle,
+    virtual status_t importBuffer(const native_handle_t* rawHandle,
                                   buffer_handle_t* outBufferHandle) const = 0;
 
     virtual void freeBuffer(buffer_handle_t bufferHandle) const = 0;
@@ -269,11 +266,6 @@
             std::vector<ui::PlaneLayout>* /*outPlaneLayouts*/) const {
         return INVALID_OPERATION;
     }
-
-    virtual std::vector<android::hardware::graphics::mapper::V4_0::IMapper::MetadataTypeDescription>
-    listSupportedMetadataTypes() const {
-        return {};
-    }
 };
 
 // A wrapper to IAllocator
diff --git a/libs/ui/include/ui/Gralloc2.h b/libs/ui/include/ui/Gralloc2.h
index f570c42..a7b6f492 100644
--- a/libs/ui/include/ui/Gralloc2.h
+++ b/libs/ui/include/ui/Gralloc2.h
@@ -38,9 +38,9 @@
 
     bool isLoaded() const override;
 
-    status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override;
+    status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const;
 
-    status_t importBuffer(const hardware::hidl_handle& rawHandle,
+    status_t importBuffer(const native_handle_t* rawHandle,
                           buffer_handle_t* outBufferHandle) const override;
 
     void freeBuffer(buffer_handle_t bufferHandle) const override;
diff --git a/libs/ui/include/ui/Gralloc3.h b/libs/ui/include/ui/Gralloc3.h
index 93a5077..7367549 100644
--- a/libs/ui/include/ui/Gralloc3.h
+++ b/libs/ui/include/ui/Gralloc3.h
@@ -37,9 +37,9 @@
 
     bool isLoaded() const override;
 
-    status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override;
+    status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const;
 
-    status_t importBuffer(const hardware::hidl_handle& rawHandle,
+    status_t importBuffer(const native_handle_t* rawHandle,
                           buffer_handle_t* outBufferHandle) const override;
 
     void freeBuffer(buffer_handle_t bufferHandle) const override;
diff --git a/libs/ui/include/ui/Gralloc4.h b/libs/ui/include/ui/Gralloc4.h
index cf023c9..6bc5ce5 100644
--- a/libs/ui/include/ui/Gralloc4.h
+++ b/libs/ui/include/ui/Gralloc4.h
@@ -42,9 +42,9 @@
     std::string dumpBuffer(buffer_handle_t bufferHandle, bool less = true) const override;
     std::string dumpBuffers(bool less = true) const;
 
-    status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const override;
+    status_t createDescriptor(void* bufferDescriptorInfo, void* outBufferDescriptor) const;
 
-    status_t importBuffer(const hardware::hidl_handle& rawHandle,
+    status_t importBuffer(const native_handle_t* rawHandle,
                           buffer_handle_t* outBufferHandle) const override;
 
     void freeBuffer(buffer_handle_t bufferHandle) const override;
diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h
new file mode 100644
index 0000000..bc10169
--- /dev/null
+++ b/libs/ui/include/ui/Gralloc5.h
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2022 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 <aidl/android/hardware/graphics/allocator/IAllocator.h>
+#include <android/hardware/graphics/mapper/IMapper.h>
+#include <ui/Gralloc.h>
+
+namespace android {
+
+class Gralloc5Mapper : public GrallocMapper {
+public:
+public:
+    static void preload();
+
+    Gralloc5Mapper();
+
+    [[nodiscard]] bool isLoaded() const override;
+
+    [[nodiscard]] std::string dumpBuffer(buffer_handle_t bufferHandle, bool less) const override;
+
+    [[nodiscard]] std::string dumpBuffers(bool less = true) const;
+
+    [[nodiscard]] status_t importBuffer(const native_handle_t *rawHandle,
+                                        buffer_handle_t *outBufferHandle) const override;
+
+    void freeBuffer(buffer_handle_t bufferHandle) const override;
+
+    [[nodiscard]] status_t validateBufferSize(buffer_handle_t bufferHandle, uint32_t width,
+                                              uint32_t height, PixelFormat format,
+                                              uint32_t layerCount, uint64_t usage,
+                                              uint32_t stride) const override;
+
+    void getTransportSize(buffer_handle_t bufferHandle, uint32_t *outNumFds,
+                          uint32_t *outNumInts) const override;
+
+    [[nodiscard]] status_t lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds,
+                                int acquireFence, void **outData, int32_t *outBytesPerPixel,
+                                int32_t *outBytesPerStride) const override;
+
+    [[nodiscard]] status_t lock(buffer_handle_t bufferHandle, uint64_t usage, const Rect &bounds,
+                                int acquireFence, android_ycbcr *ycbcr) const override;
+
+    [[nodiscard]] int unlock(buffer_handle_t bufferHandle) const override;
+
+    [[nodiscard]] status_t isSupported(uint32_t width, uint32_t height, PixelFormat format,
+                                       uint32_t layerCount, uint64_t usage,
+                                       bool *outSupported) const override;
+
+    [[nodiscard]] status_t getBufferId(buffer_handle_t bufferHandle,
+                                       uint64_t *outBufferId) const override;
+
+    [[nodiscard]] status_t getName(buffer_handle_t bufferHandle,
+                                   std::string *outName) const override;
+
+    [[nodiscard]] status_t getWidth(buffer_handle_t bufferHandle,
+                                    uint64_t *outWidth) const override;
+
+    [[nodiscard]] status_t getHeight(buffer_handle_t bufferHandle,
+                                     uint64_t *outHeight) const override;
+
+    [[nodiscard]] status_t getLayerCount(buffer_handle_t bufferHandle,
+                                         uint64_t *outLayerCount) const override;
+
+    [[nodiscard]] status_t getPixelFormatRequested(
+            buffer_handle_t bufferHandle, ui::PixelFormat *outPixelFormatRequested) const override;
+
+    [[nodiscard]] status_t getPixelFormatFourCC(buffer_handle_t bufferHandle,
+                                                uint32_t *outPixelFormatFourCC) const override;
+
+    [[nodiscard]] status_t getPixelFormatModifier(buffer_handle_t bufferHandle,
+                                                  uint64_t *outPixelFormatModifier) const override;
+
+    [[nodiscard]] status_t getUsage(buffer_handle_t bufferHandle,
+                                    uint64_t *outUsage) const override;
+
+    [[nodiscard]] status_t getAllocationSize(buffer_handle_t bufferHandle,
+                                             uint64_t *outAllocationSize) const override;
+
+    [[nodiscard]] status_t getProtectedContent(buffer_handle_t bufferHandle,
+                                               uint64_t *outProtectedContent) const override;
+
+    [[nodiscard]] status_t getCompression(buffer_handle_t bufferHandle,
+                                          aidl::android::hardware::graphics::common::ExtendableType
+                                                  *outCompression) const override;
+
+    [[nodiscard]] status_t getCompression(buffer_handle_t bufferHandle,
+                                          ui::Compression *outCompression) const override;
+
+    [[nodiscard]] status_t getInterlaced(buffer_handle_t bufferHandle,
+                                         aidl::android::hardware::graphics::common::ExtendableType
+                                                 *outInterlaced) const override;
+
+    [[nodiscard]] status_t getInterlaced(buffer_handle_t bufferHandle,
+                                         ui::Interlaced *outInterlaced) const override;
+
+    [[nodiscard]] status_t getChromaSiting(buffer_handle_t bufferHandle,
+                                           aidl::android::hardware::graphics::common::ExtendableType
+                                                   *outChromaSiting) const override;
+
+    [[nodiscard]] status_t getChromaSiting(buffer_handle_t bufferHandle,
+                                           ui::ChromaSiting *outChromaSiting) const override;
+
+    [[nodiscard]] status_t getPlaneLayouts(
+            buffer_handle_t bufferHandle,
+            std::vector<ui::PlaneLayout> *outPlaneLayouts) const override;
+
+    [[nodiscard]] status_t getDataspace(buffer_handle_t bufferHandle,
+                                        ui::Dataspace *outDataspace) const override;
+
+    [[nodiscard]] status_t setDataspace(buffer_handle_t bufferHandle,
+                                        ui::Dataspace dataspace) const override;
+
+    [[nodiscard]] status_t getBlendMode(buffer_handle_t bufferHandle,
+                                        ui::BlendMode *outBlendMode) const override;
+
+    [[nodiscard]] status_t getSmpte2086(buffer_handle_t bufferHandle,
+                                        std::optional<ui::Smpte2086> *outSmpte2086) const override;
+
+    [[nodiscard]] status_t setSmpte2086(buffer_handle_t bufferHandle,
+                                        std::optional<ui::Smpte2086> smpte2086) const override;
+
+    [[nodiscard]] status_t getCta861_3(buffer_handle_t bufferHandle,
+                                       std::optional<ui::Cta861_3> *outCta861_3) const override;
+
+    [[nodiscard]] status_t setCta861_3(buffer_handle_t bufferHandle,
+                                       std::optional<ui::Cta861_3> cta861_3) const override;
+
+    [[nodiscard]] status_t getSmpte2094_40(
+            buffer_handle_t bufferHandle,
+            std::optional<std::vector<uint8_t>> *outSmpte2094_40) const override;
+
+    [[nodiscard]] status_t setSmpte2094_40(
+            buffer_handle_t bufferHandle,
+            std::optional<std::vector<uint8_t>> smpte2094_40) const override;
+
+    [[nodiscard]] status_t getSmpte2094_10(
+            buffer_handle_t bufferHandle,
+            std::optional<std::vector<uint8_t>> *outSmpte2094_10) const override;
+
+    [[nodiscard]] status_t setSmpte2094_10(
+            buffer_handle_t bufferHandle,
+            std::optional<std::vector<uint8_t>> smpte2094_10) const override;
+
+    [[nodiscard]] status_t getDefaultPixelFormatFourCC(
+            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+            uint64_t usage, uint32_t *outPixelFormatFourCC) const override;
+
+    [[nodiscard]] status_t getDefaultPixelFormatModifier(
+            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+            uint64_t usage, uint64_t *outPixelFormatModifier) const override;
+
+    [[nodiscard]] status_t getDefaultAllocationSize(uint32_t width, uint32_t height,
+                                                    PixelFormat format, uint32_t layerCount,
+                                                    uint64_t usage,
+                                                    uint64_t *outAllocationSize) const override;
+
+    [[nodiscard]] status_t getDefaultProtectedContent(uint32_t width, uint32_t height,
+                                                      PixelFormat format, uint32_t layerCount,
+                                                      uint64_t usage,
+                                                      uint64_t *outProtectedContent) const override;
+
+    [[nodiscard]] status_t getDefaultCompression(
+            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+            uint64_t usage,
+            aidl::android::hardware::graphics::common::ExtendableType *outCompression)
+            const override;
+
+    [[nodiscard]] status_t getDefaultCompression(uint32_t width, uint32_t height,
+                                                 PixelFormat format, uint32_t layerCount,
+                                                 uint64_t usage,
+                                                 ui::Compression *outCompression) const override;
+
+    [[nodiscard]] status_t getDefaultInterlaced(
+            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+            uint64_t usage,
+            aidl::android::hardware::graphics::common::ExtendableType *outInterlaced)
+            const override;
+
+    [[nodiscard]] status_t getDefaultInterlaced(uint32_t width, uint32_t height, PixelFormat format,
+                                                uint32_t layerCount, uint64_t usage,
+                                                ui::Interlaced *outInterlaced) const override;
+
+    [[nodiscard]] status_t getDefaultChromaSiting(
+            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+            uint64_t usage,
+            aidl::android::hardware::graphics::common::ExtendableType *outChromaSiting)
+            const override;
+
+    [[nodiscard]] status_t getDefaultChromaSiting(uint32_t width, uint32_t height,
+                                                  PixelFormat format, uint32_t layerCount,
+                                                  uint64_t usage,
+                                                  ui::ChromaSiting *outChromaSiting) const override;
+
+    [[nodiscard]] status_t getDefaultPlaneLayouts(
+            uint32_t width, uint32_t height, PixelFormat format, uint32_t layerCount,
+            uint64_t usage, std::vector<ui::PlaneLayout> *outPlaneLayouts) const override;
+
+private:
+    void unlockBlocking(buffer_handle_t bufferHandle) const;
+
+    AIMapper *mMapper = nullptr;
+};
+
+class Gralloc5Allocator : public GrallocAllocator {
+public:
+    Gralloc5Allocator(const Gralloc5Mapper &mapper);
+
+    [[nodiscard]] bool isLoaded() const override;
+
+    [[nodiscard]] std::string dumpDebugInfo(bool less) const override;
+
+    [[nodiscard]] status_t allocate(std::string requestorName, uint32_t width, uint32_t height,
+                                    PixelFormat format, uint32_t layerCount, uint64_t usage,
+                                    uint32_t bufferCount, uint32_t *outStride,
+                                    buffer_handle_t *outBufferHandles,
+                                    bool importBuffers) const override;
+
+private:
+    const Gralloc5Mapper &mMapper;
+    std::shared_ptr<aidl::android::hardware::graphics::allocator::IAllocator> mAllocator;
+};
+
+} // namespace android
diff --git a/libs/ui/include/ui/GraphicBufferMapper.h b/libs/ui/include/ui/GraphicBufferMapper.h
index 507fa35..51c6e92 100644
--- a/libs/ui/include/ui/GraphicBufferMapper.h
+++ b/libs/ui/include/ui/GraphicBufferMapper.h
@@ -42,9 +42,10 @@
 {
 public:
     enum Version {
-        GRALLOC_2,
+        GRALLOC_2 = 2,
         GRALLOC_3,
         GRALLOC_4,
+        GRALLOC_5,
     };
     static void preloadHal();
     static inline GraphicBufferMapper& get() { return getInstance(); }
@@ -54,10 +55,11 @@
 
     // The imported outHandle must be freed with freeBuffer when no longer
     // needed. rawHandle is owned by the caller.
-    status_t importBuffer(buffer_handle_t rawHandle,
-            uint32_t width, uint32_t height, uint32_t layerCount,
-            PixelFormat format, uint64_t usage, uint32_t stride,
-            buffer_handle_t* outHandle);
+    status_t importBuffer(const native_handle_t* rawHandle, uint32_t width, uint32_t height,
+                          uint32_t layerCount, PixelFormat format, uint64_t usage, uint32_t stride,
+                          buffer_handle_t* outHandle);
+
+    status_t importBufferNoValidate(const native_handle_t* rawHandle, buffer_handle_t* outHandle);
 
     status_t freeBuffer(buffer_handle_t handle);
 
diff --git a/libs/ui/include_types/ui/DataspaceUtils.h b/libs/ui/include_types/ui/DataspaceUtils.h
index cd31167..a461cb4 100644
--- a/libs/ui/include_types/ui/DataspaceUtils.h
+++ b/libs/ui/include_types/ui/DataspaceUtils.h
@@ -22,10 +22,8 @@
 
 inline bool isHdrDataspace(ui::Dataspace dataspace) {
     const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
-    const auto range = dataspace & HAL_DATASPACE_RANGE_MASK;
 
-    return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG ||
-            range == HAL_DATASPACE_RANGE_EXTENDED;
+    return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
 }
 
-} // namespace android
+} // namespace android
\ No newline at end of file
diff --git a/libs/ui/tests/DataspaceUtils_test.cpp b/libs/ui/tests/DataspaceUtils_test.cpp
index ffe6438..3e09671 100644
--- a/libs/ui/tests/DataspaceUtils_test.cpp
+++ b/libs/ui/tests/DataspaceUtils_test.cpp
@@ -29,13 +29,12 @@
     EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_ITU_PQ));
     EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_PQ));
     EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_HLG));
-    // The original formulation of scRGB indicates the same white points as that
-    // of sRGB, however scRGB may be used to implement HDR.
-    EXPECT_TRUE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR));
-    EXPECT_TRUE(isHdrDataspace(ui::Dataspace::V0_SCRGB));
 
     EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB_LINEAR));
+    // scRGB defines a very wide gamut but not an expanded luminance range
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR));
     EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB));
+    EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB));
     EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_JFIF));
     EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_625));
     EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_525));
diff --git a/libs/ui/tests/colorspace_test.cpp b/libs/ui/tests/colorspace_test.cpp
index 0a4873c..3fb33b4 100644
--- a/libs/ui/tests/colorspace_test.cpp
+++ b/libs/ui/tests/colorspace_test.cpp
@@ -111,6 +111,7 @@
     EXPECT_NEAR(1.0f, sRGB.getEOTF()(1.0f), 1e-6f);
     EXPECT_NEAR(1.0f, sRGB.getOETF()(1.0f), 1e-6f);
 
+    // NOLINTNEXTLINE(clang-analyzer-security.FloatLoopCounter,cert-flp30-c)
     for (float v = 0.0f; v <= 0.5f; v += 1e-3f) {
         ASSERT_TRUE(v >= sRGB.getEOTF()(v));
         ASSERT_TRUE(v <= sRGB.getOETF()(v));
@@ -118,6 +119,7 @@
 
     float previousEOTF = std::numeric_limits<float>::lowest();
     float previousOETF = std::numeric_limits<float>::lowest();
+    // NOLINTNEXTLINE(clang-analyzer-security.FloatLoopCounter,cert-flp30-c)
     for (float v = 0.0f; v <= 1.0f; v += 1e-3f) {
         ASSERT_TRUE(previousEOTF < sRGB.getEOTF()(v));
         previousEOTF = sRGB.getEOTF()(v);
@@ -131,6 +133,7 @@
           {0.3127f, 0.3290f}
           // linear transfer functions
     );
+    // NOLINTNEXTLINE(clang-analyzer-security.FloatLoopCounter,cert-flp30-c)
     for (float v = 0.0f; v <= 1.0f; v += 1e-3f) {
         ASSERT_EQ(v, sRGB2.getEOTF()(v));
         ASSERT_EQ(v, sRGB2.getOETF()(v));
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index 5597944..1e8a348 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -69,7 +69,7 @@
 //
 egl_cache_t::egl_cache_t()
       : mInitialized(false),
-        mMultifileMode(true),
+        mMultifileMode(false),
         mCacheByteLimit(maxTotalSize),
         mMultifileCleanupPending(false) {}
 
@@ -114,8 +114,6 @@
         }
     }
 
-    mMultifileMode = true;
-
     // Allow forcing monolithic cache for debug purposes
     if (base::GetProperty("debug.egl.blobcache.multifilemode", "") == "false") {
         ALOGD("Forcing monolithic cache due to debug.egl.blobcache.multifilemode == \"false\"");
diff --git a/opengl/tests/EGLTest/egl_cache_test.cpp b/opengl/tests/EGLTest/egl_cache_test.cpp
index c4fa65f..265bec4 100644
--- a/opengl/tests/EGLTest/egl_cache_test.cpp
+++ b/opengl/tests/EGLTest/egl_cache_test.cpp
@@ -134,6 +134,9 @@
 }
 
 TEST_F(EGLCacheTest, ModifiedCacheMisses) {
+    // Turn this back on if multifile becomes the default
+    GTEST_SKIP() << "Skipping test designed for multifile, see b/263574392 and b/246966894";
+
     uint8_t buf[4] = { 0xee, 0xee, 0xee, 0xee };
     mCache->initialize(egl_display_t::get(EGL_DEFAULT_DISPLAY));
 
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 628ce6f..0c93f5c 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -263,11 +263,12 @@
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14);
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15);
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16);
-// TODO(hcutts): add GESTURE_X_OFFSET and GESTURE_Y_OFFSET.
+// TODO(b/251196347): add GESTURE_{X,Y}_OFFSET, GESTURE_SCROLL_{X,Y}_DISTANCE, and
+// GESTURE_PINCH_SCALE_FACTOR.
 // If you added a new axis, consider whether this should also be exposed as a HAL axis. Update the
 // static_assert below and add the new axis here, or leave a comment summarizing your decision.
 static_assert(static_cast<common::Axis>(AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) ==
-              static_cast<common::Axis>(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET));
+              static_cast<common::Axis>(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR));
 
 static common::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) {
     common::VideoFrame out;
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index d2c940f..d55ab28 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -197,6 +197,9 @@
         // The keyboard layout association has changed.
         CHANGE_KEYBOARD_LAYOUT_ASSOCIATION = 1 << 11,
 
+        // The stylus button reporting configurations has changed.
+        CHANGE_STYLUS_BUTTON_REPORTING = 1 << 12,
+
         // All devices must be reopened.
         CHANGE_MUST_REOPEN = 1 << 31,
     };
@@ -309,6 +312,10 @@
     // The set of currently disabled input devices.
     std::set<int32_t> disabledDevices;
 
+    // True if stylus button reporting through motion events should be enabled, in which case
+    // stylus button state changes are reported through motion events.
+    bool stylusButtonMotionEventsEnabled;
+
     InputReaderConfiguration()
           : virtualKeyQuietTime(0),
             pointerVelocityControlParameters(1.0f, 500.0f, 3000.0f,
@@ -329,7 +336,8 @@
             pointerGestureMovementSpeedRatio(0.8f),
             pointerGestureZoomSpeedRatio(0.3f),
             showTouches(false),
-            pointerCaptureRequest() {}
+            pointerCaptureRequest(),
+            stylusButtonMotionEventsEnabled(true) {}
 
     static std::string changesToString(uint32_t changes);
 
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index e9fa599..43b67ca 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -62,7 +62,6 @@
 #define INDENT3 "      "
 
 using android::base::StringPrintf;
-using android::hardware::input::InputDeviceCountryCode;
 
 namespace android {
 
@@ -135,6 +134,46 @@
                                                                   {"green", LightColor::GREEN},
                                                                   {"blue", LightColor::BLUE}};
 
+// Mapping for country code to Layout info.
+// See bCountryCode in 6.2.1 of https://usb.org/sites/default/files/hid1_11.pdf.
+const std::unordered_map<std::int32_t, RawLayoutInfo> LAYOUT_INFOS =
+        {{0, RawLayoutInfo{.languageTag = "", .layoutType = ""}},             // NOT_SUPPORTED
+         {1, RawLayoutInfo{.languageTag = "ar-Arab", .layoutType = ""}},      // ARABIC
+         {2, RawLayoutInfo{.languageTag = "fr-BE", .layoutType = ""}},        // BELGIAN
+         {3, RawLayoutInfo{.languageTag = "fr-CA", .layoutType = ""}},        // CANADIAN_BILINGUAL
+         {4, RawLayoutInfo{.languageTag = "fr-CA", .layoutType = ""}},        // CANADIAN_FRENCH
+         {5, RawLayoutInfo{.languageTag = "cs", .layoutType = ""}},           // CZECH_REPUBLIC
+         {6, RawLayoutInfo{.languageTag = "da", .layoutType = ""}},           // DANISH
+         {7, RawLayoutInfo{.languageTag = "fi", .layoutType = ""}},           // FINNISH
+         {8, RawLayoutInfo{.languageTag = "fr-FR", .layoutType = ""}},        // FRENCH
+         {9, RawLayoutInfo{.languageTag = "de", .layoutType = ""}},           // GERMAN
+         {10, RawLayoutInfo{.languageTag = "el", .layoutType = ""}},          // GREEK
+         {11, RawLayoutInfo{.languageTag = "iw", .layoutType = ""}},          // HEBREW
+         {12, RawLayoutInfo{.languageTag = "hu", .layoutType = ""}},          // HUNGARY
+         {13, RawLayoutInfo{.languageTag = "en", .layoutType = "extended"}},  // INTERNATIONAL (ISO)
+         {14, RawLayoutInfo{.languageTag = "it", .layoutType = ""}},          // ITALIAN
+         {15, RawLayoutInfo{.languageTag = "ja", .layoutType = ""}},          // JAPAN
+         {16, RawLayoutInfo{.languageTag = "ko", .layoutType = ""}},          // KOREAN
+         {17, RawLayoutInfo{.languageTag = "es-419", .layoutType = ""}},      // LATIN_AMERICA
+         {18, RawLayoutInfo{.languageTag = "nl", .layoutType = ""}},          // DUTCH
+         {19, RawLayoutInfo{.languageTag = "nb", .layoutType = ""}},          // NORWEGIAN
+         {20, RawLayoutInfo{.languageTag = "fa", .layoutType = ""}},          // PERSIAN
+         {21, RawLayoutInfo{.languageTag = "pl", .layoutType = ""}},          // POLAND
+         {22, RawLayoutInfo{.languageTag = "pt", .layoutType = ""}},          // PORTUGUESE
+         {23, RawLayoutInfo{.languageTag = "ru", .layoutType = ""}},          // RUSSIA
+         {24, RawLayoutInfo{.languageTag = "sk", .layoutType = ""}},          // SLOVAKIA
+         {25, RawLayoutInfo{.languageTag = "es-ES", .layoutType = ""}},       // SPANISH
+         {26, RawLayoutInfo{.languageTag = "sv", .layoutType = ""}},          // SWEDISH
+         {27, RawLayoutInfo{.languageTag = "fr-CH", .layoutType = ""}},       // SWISS_FRENCH
+         {28, RawLayoutInfo{.languageTag = "de-CH", .layoutType = ""}},       // SWISS_GERMAN
+         {29, RawLayoutInfo{.languageTag = "de-CH", .layoutType = ""}},       // SWITZERLAND
+         {30, RawLayoutInfo{.languageTag = "zh-TW", .layoutType = ""}},       // TAIWAN
+         {31, RawLayoutInfo{.languageTag = "tr", .layoutType = "turkish_q"}}, // TURKISH_Q
+         {32, RawLayoutInfo{.languageTag = "en-GB", .layoutType = ""}},       // UK
+         {33, RawLayoutInfo{.languageTag = "en-US", .layoutType = ""}},       // US
+         {34, RawLayoutInfo{.languageTag = "", .layoutType = ""}},            // YUGOSLAVIA
+         {35, RawLayoutInfo{.languageTag = "tr", .layoutType = "turkish_f"}}}; // TURKISH_F
+
 static std::string sha1(const std::string& in) {
     SHA_CTX ctx;
     SHA1_Init(&ctx);
@@ -311,22 +350,27 @@
 }
 
 /**
- * Read country code information exposed through the sysfs path.
+ * Read country code information exposed through the sysfs path and convert it to Layout info.
  */
-static InputDeviceCountryCode readCountryCodeLocked(const std::filesystem::path& sysfsRootPath) {
+static std::optional<RawLayoutInfo> readLayoutConfiguration(
+        const std::filesystem::path& sysfsRootPath) {
     // Check the sysfs root path
-    int hidCountryCode = static_cast<int>(InputDeviceCountryCode::INVALID);
+    int32_t hidCountryCode = -1;
     std::string str;
     if (base::ReadFileToString(sysfsRootPath / "country", &str)) {
         hidCountryCode = std::stoi(str, nullptr, 16);
+        // Update this condition if new supported country codes are added to HID spec.
         if (hidCountryCode > 35 || hidCountryCode < 0) {
             ALOGE("HID country code should be in range [0, 35], but for sysfs path %s it was %d",
                   sysfsRootPath.c_str(), hidCountryCode);
-            return InputDeviceCountryCode::INVALID;
         }
     }
+    const auto it = LAYOUT_INFOS.find(hidCountryCode);
+    if (it != LAYOUT_INFOS.end()) {
+        return it->second;
+    }
 
-    return static_cast<InputDeviceCountryCode>(hidCountryCode);
+    return std::nullopt;
 }
 
 /**
@@ -1299,13 +1343,13 @@
     }
 }
 
-InputDeviceCountryCode EventHub::getCountryCode(int32_t deviceId) const {
+std::optional<RawLayoutInfo> EventHub::getRawLayoutInfo(int32_t deviceId) const {
     std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
     if (device == nullptr || !device->associatedDevice) {
-        return InputDeviceCountryCode::INVALID;
+        return std::nullopt;
     }
-    return device->associatedDevice->countryCode;
+    return device->associatedDevice->layoutInfo;
 }
 
 void EventHub::setExcludedDevices(const std::vector<std::string>& devices) {
@@ -1372,12 +1416,17 @@
     return nullptr;
 }
 
+// If provided map is null, it will reset key character map to default KCM.
 bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId, std::shared_ptr<KeyCharacterMap> map) {
     std::scoped_lock _l(mLock);
     Device* device = getDeviceLocked(deviceId);
-    if (device == nullptr || map == nullptr || device->keyMap.keyCharacterMap == nullptr) {
+    if (device == nullptr || device->keyMap.keyCharacterMap == nullptr) {
         return false;
     }
+    if (map == nullptr) {
+        device->keyMap.keyCharacterMap->clearLayoutOverlay();
+        return true;
+    }
     device->keyMap.keyCharacterMap->combine(*map);
     return true;
 }
@@ -1449,9 +1498,9 @@
 
     std::shared_ptr<const AssociatedDevice> associatedDevice = std::make_shared<AssociatedDevice>(
             AssociatedDevice{.sysfsRootPath = path,
-                             .countryCode = readCountryCodeLocked(path),
                              .batteryInfos = readBatteryConfiguration(path),
-                             .lightInfos = readLightsConfiguration(path)});
+                             .lightInfos = readLightsConfiguration(path),
+                             .layoutInfo = readLayoutConfiguration(path)});
 
     bool associatedDeviceChanged = false;
     for (const auto& [id, dev] : mDevices) {
@@ -2686,9 +2735,12 @@
                                  device->keyMap.keyLayoutFile.c_str());
             dump += StringPrintf(INDENT3 "KeyCharacterMapFile: %s\n",
                                  device->keyMap.keyCharacterMapFile.c_str());
-            dump += StringPrintf(INDENT3 "CountryCode: %d\n",
-                                 device->associatedDevice ? device->associatedDevice->countryCode
-                                                          : InputDeviceCountryCode::INVALID);
+            if (device->associatedDevice && device->associatedDevice->layoutInfo) {
+                dump += StringPrintf(INDENT3 "LanguageTag: %s\n",
+                                     device->associatedDevice->layoutInfo->languageTag.c_str());
+                dump += StringPrintf(INDENT3 "LayoutType: %s\n",
+                                     device->associatedDevice->layoutInfo->layoutType.c_str());
+            }
             dump += StringPrintf(INDENT3 "ConfigurationFile: %s\n",
                                  device->configurationFile.c_str());
             dump += StringPrintf(INDENT3 "VideoDevice: %s\n",
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 13f40ee..6e78e82 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -37,8 +37,6 @@
 #include "TouchpadInputMapper.h"
 #include "VibratorInputMapper.h"
 
-using android::hardware::input::InputDeviceCountryCode;
-
 namespace android {
 
 InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t generation,
@@ -256,7 +254,6 @@
     mSources = 0;
     mClasses = ftl::Flags<InputDeviceClass>(0);
     mControllerNumber = 0;
-    mCountryCode = InputDeviceCountryCode::INVALID;
 
     for_each_subdevice([this](InputDeviceContext& context) {
         mClasses |= context.getDeviceClasses();
@@ -268,16 +265,6 @@
             }
             mControllerNumber = controllerNumber;
         }
-
-        InputDeviceCountryCode countryCode = context.getCountryCode();
-        if (countryCode != InputDeviceCountryCode::INVALID) {
-            if (mCountryCode != InputDeviceCountryCode::INVALID && mCountryCode != countryCode) {
-                ALOGW("InputDevice::configure(): %s device contains multiple unique country "
-                      "codes",
-                      getName().c_str());
-            }
-            mCountryCode = countryCode;
-        }
     });
 
     mIsExternal = mClasses.test(InputDeviceClass::EXTERNAL);
@@ -465,7 +452,7 @@
 InputDeviceInfo InputDevice::getDeviceInfo() {
     InputDeviceInfo outDeviceInfo;
     outDeviceInfo.initialize(mId, mGeneration, mControllerNumber, mIdentifier, mAlias, mIsExternal,
-                             mHasMic, mCountryCode);
+                             mHasMic);
     for_each_mapper(
             [&outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(&outDeviceInfo); });
 
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 8a844b2..a3ecf41 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -42,7 +42,6 @@
 
 #include "TouchVideoDevice.h"
 #include "VibrationElement.h"
-#include "android/hardware/input/InputDeviceCountryCode.h"
 
 struct inotify_event;
 
@@ -207,6 +206,15 @@
     bool operator!=(const RawBatteryInfo&) const = default;
 };
 
+/* Layout information associated with the device */
+struct RawLayoutInfo {
+    std::string languageTag;
+    std::string layoutType;
+
+    bool operator==(const RawLayoutInfo&) const = default;
+    bool operator!=(const RawLayoutInfo&) const = default;
+};
+
 /*
  * Gets the class that owns an axis, in cases where multiple classes might claim
  * the same axis for different purposes.
@@ -308,8 +316,8 @@
             int32_t deviceId, int32_t lightId) const = 0;
     virtual void setLightIntensities(int32_t deviceId, int32_t lightId,
                                      std::unordered_map<LightColor, int32_t> intensities) = 0;
-    /* Query Country code associated with the input device. */
-    virtual hardware::input::InputDeviceCountryCode getCountryCode(int32_t deviceId) const = 0;
+    /* Query Layout info associated with the input device. */
+    virtual std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const = 0;
     /* Query current input state. */
     virtual int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const = 0;
     virtual int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const = 0;
@@ -493,7 +501,7 @@
     void setLightIntensities(int32_t deviceId, int32_t lightId,
                              std::unordered_map<LightColor, int32_t> intensities) override final;
 
-    hardware::input::InputDeviceCountryCode getCountryCode(int32_t deviceId) const override final;
+    std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override final;
 
     void setExcludedDevices(const std::vector<std::string>& devices) override final;
 
@@ -556,9 +564,9 @@
     struct AssociatedDevice {
         // The sysfs root path of the misc device.
         std::filesystem::path sysfsRootPath;
-        hardware::input::InputDeviceCountryCode countryCode;
         std::unordered_map<int32_t /*batteryId*/, RawBatteryInfo> batteryInfos;
         std::unordered_map<int32_t /*lightId*/, RawLightInfo> lightInfos;
+        std::optional<RawLayoutInfo> layoutInfo;
 
         bool operator==(const AssociatedDevice&) const = default;
         bool operator!=(const AssociatedDevice&) const = default;
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index b173618..7867029 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -167,7 +167,6 @@
     int32_t mId;
     int32_t mGeneration;
     int32_t mControllerNumber;
-    hardware::input::InputDeviceCountryCode mCountryCode;
     InputDeviceIdentifier mIdentifier;
     std::string mAlias;
     ftl::Flags<InputDeviceClass> mClasses;
@@ -326,9 +325,6 @@
     }
 
     inline std::vector<TouchVideoFrame> getVideoFrames() { return mEventHub->getVideoFrames(mId); }
-    inline hardware::input::InputDeviceCountryCode getCountryCode() const {
-        return mEventHub->getCountryCode(mId);
-    }
     inline int32_t getScanCodeState(int32_t scanCode) const {
         return mEventHub->getScanCodeState(mId, scanCode);
     }
@@ -361,6 +357,9 @@
     inline bool setKeyboardLayoutOverlay(std::shared_ptr<KeyCharacterMap> map) {
         return mEventHub->setKeyboardLayoutOverlay(mId, map);
     }
+    inline const std::optional<RawLayoutInfo> getRawLayoutInfo() {
+        return mEventHub->getRawLayoutInfo(mId);
+    }
     inline void vibrate(const VibrationElement& element) {
         return mEventHub->vibrate(mId, element);
     }
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 6f01449..d147d60 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -123,6 +123,12 @@
 
     if (mKeyboardLayoutInfo) {
         info->setKeyboardLayoutInfo(*mKeyboardLayoutInfo);
+    } else {
+        std::optional<RawLayoutInfo> layoutInfo = getDeviceContext().getRawLayoutInfo();
+        if (layoutInfo) {
+            info->setKeyboardLayoutInfo(
+                    KeyboardLayoutInfo(layoutInfo->languageTag, layoutInfo->layoutType));
+        }
     }
 }
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 9a7af40..8cd2cf0 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -88,6 +88,14 @@
     return rotatedDisplaySize;
 }
 
+static int32_t filterButtonState(InputReaderConfiguration& config, int32_t buttonState) {
+    if (!config.stylusButtonMotionEventsEnabled) {
+        buttonState &=
+                ~(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY | AMOTION_EVENT_BUTTON_STYLUS_SECONDARY);
+    }
+    return buttonState;
+}
+
 // --- RawPointerData ---
 
 void RawPointerData::getCentroidOfTouchingPointers(float* outX, float* outY) const {
@@ -1400,8 +1408,9 @@
     next.readTime = readTime;
 
     // Sync button state.
-    next.buttonState =
-            mTouchButtonAccumulator.getButtonState() | mCursorButtonAccumulator.getButtonState();
+    next.buttonState = filterButtonState(mConfig,
+                                         mTouchButtonAccumulator.getButtonState() |
+                                                 mCursorButtonAccumulator.getButtonState());
 
     // Sync scroll
     next.rawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
@@ -1640,7 +1649,9 @@
 void TouchInputMapper::applyExternalStylusButtonState(nsecs_t when) {
     if (mDeviceMode == DeviceMode::DIRECT && hasExternalStylus()) {
         // If any of the external buttons are already pressed by the touch device, ignore them.
-        const int32_t pressedButtons = ~mCurrentRawState.buttonState & mExternalStylusState.buttons;
+        const int32_t pressedButtons =
+                filterButtonState(mConfig,
+                                  ~mCurrentRawState.buttonState & mExternalStylusState.buttons);
         const int32_t releasedButtons =
                 mExternalStylusButtonsApplied & ~mExternalStylusState.buttons;
 
@@ -2692,18 +2703,15 @@
 
     // Update the velocity tracker.
     {
-        std::vector<float> positionsX;
-        std::vector<float> positionsY;
         for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
             uint32_t id = idBits.clearFirstMarkedBit();
             const RawPointerData::Pointer& pointer =
                     mCurrentRawState.rawPointerData.pointerForId(id);
-            positionsX.push_back(pointer.x * mPointerXMovementScale);
-            positionsY.push_back(pointer.y * mPointerYMovementScale);
+            const float x = pointer.x * mPointerXMovementScale;
+            const float y = pointer.y * mPointerYMovementScale;
+            mPointerGesture.velocityTracker.addMovement(when, id, AMOTION_EVENT_AXIS_X, x);
+            mPointerGesture.velocityTracker.addMovement(when, id, AMOTION_EVENT_AXIS_Y, y);
         }
-        mPointerGesture.velocityTracker.addMovement(when, mCurrentCookedState.fingerIdBits,
-                                                    {{AMOTION_EVENT_AXIS_X, positionsX},
-                                                     {AMOTION_EVENT_AXIS_Y, positionsY}});
     }
 
     // If the gesture ever enters a mode other than TAP, HOVER or TAP_DRAG, without first returning
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index c563dba..3b51be8 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -16,11 +16,14 @@
 
 #include "../Macros.h"
 
+#include <optional>
+
 #include <android/input.h>
 #include <linux/input-event-codes.h>
 #include <log/log_main.h>
 #include "TouchCursorInputMapperCommon.h"
 #include "TouchpadInputMapper.h"
+#include "ui/Rotation.h"
 
 namespace android {
 
@@ -89,7 +92,7 @@
         mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
         mPointerController(getContext()->getPointerController(getDeviceId())),
         mStateConverter(deviceContext),
-        mGestureConverter(*getContext(), getDeviceId()) {
+        mGestureConverter(*getContext(), deviceContext, getDeviceId()) {
     mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
     mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
     // Even though we don't explicitly delete copy/move semantics, it's safe to
@@ -111,6 +114,22 @@
     return AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD;
 }
 
+std::list<NotifyArgs> TouchpadInputMapper::configure(nsecs_t when,
+                                                     const InputReaderConfiguration* config,
+                                                     uint32_t changes) {
+    if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+        std::optional<int32_t> displayId = mPointerController->getDisplayId();
+        ui::Rotation orientation = ui::ROTATION_0;
+        if (displayId.has_value()) {
+            if (auto viewport = config->getDisplayViewportById(*displayId); viewport) {
+                orientation = getInverseRotation(viewport->orientation);
+            }
+        }
+        mGestureConverter.setOrientation(orientation);
+    }
+    return {};
+}
+
 std::list<NotifyArgs> TouchpadInputMapper::reset(nsecs_t when) {
     mStateConverter.reset();
     mGestureConverter.reset();
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index b3bc831..3a92211 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -25,6 +25,7 @@
 #include "EventHub.h"
 #include "InputDevice.h"
 #include "InputMapper.h"
+#include "InputReaderBase.h"
 #include "NotifyArgs.h"
 #include "gestures/GestureConverter.h"
 #include "gestures/HardwareStateConverter.h"
@@ -39,6 +40,9 @@
     ~TouchpadInputMapper();
 
     uint32_t getSources() const override;
+    [[nodiscard]] std::list<NotifyArgs> configure(nsecs_t when,
+                                                  const InputReaderConfiguration* config,
+                                                  uint32_t changes) override;
     [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
     [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
 
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
index 23216d3..11ffd28 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -17,6 +17,8 @@
 #include "gestures/GestureConverter.h"
 
 #include <android/input.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
 
 #include "TouchCursorInputMapperCommon.h"
 #include "input/Input.h"
@@ -44,10 +46,14 @@
 
 } // namespace
 
-GestureConverter::GestureConverter(InputReaderContext& readerContext, int32_t deviceId)
+GestureConverter::GestureConverter(InputReaderContext& readerContext,
+                                   const InputDeviceContext& deviceContext, int32_t deviceId)
       : mDeviceId(deviceId),
         mReaderContext(readerContext),
-        mPointerController(readerContext.getPointerController(deviceId)) {}
+        mPointerController(readerContext.getPointerController(deviceId)) {
+    deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mXAxisInfo);
+    deviceContext.getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mYAxisInfo);
+}
 
 void GestureConverter::reset() {
     mButtonState = 0;
@@ -60,6 +66,21 @@
             return {handleMove(when, readTime, gesture)};
         case kGestureTypeButtonsChange:
             return handleButtonsChange(when, readTime, gesture);
+        case kGestureTypeScroll:
+            return handleScroll(when, readTime, gesture);
+        case kGestureTypeFling:
+            return {handleFling(when, readTime, gesture)};
+        case kGestureTypeSwipe:
+            return handleMultiFingerSwipe(when, readTime, 3, gesture.details.swipe.dx,
+                                          gesture.details.swipe.dy);
+        case kGestureTypeFourFingerSwipe:
+            return handleMultiFingerSwipe(when, readTime, 4, gesture.details.four_finger_swipe.dx,
+                                          gesture.details.four_finger_swipe.dy);
+        case kGestureTypeSwipeLift:
+        case kGestureTypeFourFingerSwipeLift:
+            return handleMultiFingerSwipeLift(when, readTime);
+        case kGestureTypePinch:
+            return handlePinch(when, readTime, gesture);
         default:
             // TODO(b/251196347): handle more gesture types.
             return {};
@@ -67,13 +88,12 @@
 }
 
 NotifyArgs GestureConverter::handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
-    PointerProperties props;
-    props.clear();
-    props.id = 0;
-    props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+    float deltaX = gesture.details.move.dx;
+    float deltaY = gesture.details.move.dy;
+    rotateDelta(mOrientation, &deltaX, &deltaY);
 
     mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-    mPointerController->move(gesture.details.move.dx, gesture.details.move.dy);
+    mPointerController->move(deltaX, deltaY);
     mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
     float xCursorPosition, yCursorPosition;
     mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
@@ -82,14 +102,15 @@
     coords.clear();
     coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, gesture.details.move.dx);
-    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, gesture.details.move.dy);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
     const bool down = isPointerDown(mButtonState);
     coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, down ? 1.0f : 0.0f);
 
     const int32_t action = down ? AMOTION_EVENT_ACTION_MOVE : AMOTION_EVENT_ACTION_HOVER_MOVE;
     return makeMotionArgs(when, readTime, action, /* actionButton= */ 0, mButtonState,
-                          /* pointerCount= */ 1, &props, &coords, xCursorPosition, yCursorPosition);
+                          /* pointerCount= */ 1, mFingerProps.data(), &coords, xCursorPosition,
+                          yCursorPosition);
 }
 
 std::list<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime,
@@ -99,11 +120,6 @@
     mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
     mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
 
-    PointerProperties props;
-    props.clear();
-    props.id = 0;
-    props.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
-
     float xCursorPosition, yCursorPosition;
     mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
 
@@ -127,15 +143,16 @@
             newButtonState |= actionButton;
             pressEvents.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS,
                                                  actionButton, newButtonState,
-                                                 /* pointerCount= */ 1, &props, &coords,
-                                                 xCursorPosition, yCursorPosition));
+                                                 /* pointerCount= */ 1, mFingerProps.data(),
+                                                 &coords, xCursorPosition, yCursorPosition));
         }
     }
     if (!isPointerDown(mButtonState) && isPointerDown(newButtonState)) {
         mDownTime = when;
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
                                      /* actionButton= */ 0, newButtonState, /* pointerCount= */ 1,
-                                     &props, &coords, xCursorPosition, yCursorPosition));
+                                     mFingerProps.data(), &coords, xCursorPosition,
+                                     yCursorPosition));
     }
     out.splice(out.end(), pressEvents);
 
@@ -151,19 +168,237 @@
             newButtonState &= ~actionButton;
             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
                                          actionButton, newButtonState, /* pointerCount= */ 1,
-                                         &props, &coords, xCursorPosition, yCursorPosition));
+                                         mFingerProps.data(), &coords, xCursorPosition,
+                                         yCursorPosition));
         }
     }
     if (isPointerDown(mButtonState) && !isPointerDown(newButtonState)) {
         coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.0f);
         out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
-                                     newButtonState, /* pointerCount= */ 1, &props, &coords,
-                                     xCursorPosition, yCursorPosition));
+                                     newButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+                                     &coords, xCursorPosition, yCursorPosition));
     }
     mButtonState = newButtonState;
     return out;
 }
 
+std::list<NotifyArgs> GestureConverter::handleScroll(nsecs_t when, nsecs_t readTime,
+                                                     const Gesture& gesture) {
+    std::list<NotifyArgs> out;
+    PointerCoords& coords = mFakeFingerCoords[0];
+    float xCursorPosition, yCursorPosition;
+    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    if (mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
+        mCurrentClassification = MotionClassification::TWO_FINGER_SWIPE;
+        coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+        coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+        coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+        mDownTime = when;
+        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
+                                     /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+                                     yCursorPosition));
+    }
+    float deltaX = gesture.details.scroll.dx;
+    float deltaY = gesture.details.scroll.dy;
+    rotateDelta(mOrientation, &deltaX, &deltaY);
+
+    coords.setAxisValue(AMOTION_EVENT_AXIS_X, coords.getAxisValue(AMOTION_EVENT_AXIS_X) - deltaX);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_Y, coords.getAxisValue(AMOTION_EVENT_AXIS_Y) - deltaY);
+    // TODO(b/262876643): set AXIS_GESTURE_{X,Y}_OFFSET.
+    coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, gesture.details.scroll.dx);
+    coords.setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, gesture.details.scroll.dy);
+    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
+                                 mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+    return out;
+}
+
+NotifyArgs GestureConverter::handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture) {
+    // We don't actually want to use the gestures library's fling velocity values (to ensure
+    // consistency between touchscreen and touchpad flings), so we're just using the "start fling"
+    // gestures as a marker for the end of a two-finger scroll gesture.
+    if (gesture.details.fling.fling_state != GESTURES_FLING_START ||
+        mCurrentClassification != MotionClassification::TWO_FINGER_SWIPE) {
+        return {};
+    }
+
+    float xCursorPosition, yCursorPosition;
+    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE, 0);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE, 0);
+    NotifyArgs args = makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP,
+                                     /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+                                     yCursorPosition);
+    mCurrentClassification = MotionClassification::NONE;
+    return args;
+}
+
+[[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipe(nsecs_t when,
+                                                                             nsecs_t readTime,
+                                                                             uint32_t fingerCount,
+                                                                             float dx, float dy) {
+    std::list<NotifyArgs> out = {};
+    float xCursorPosition, yCursorPosition;
+    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
+        // If the user changes the number of fingers mid-way through a swipe (e.g. they start with
+        // three and then put a fourth finger down), the gesture library will treat it as two
+        // separate swipes with an appropriate lift event between them, so we don't have to worry
+        // about the finger count changing mid-swipe.
+        mCurrentClassification = MotionClassification::MULTI_FINGER_SWIPE;
+        mSwipeFingerCount = fingerCount;
+
+        constexpr float FAKE_FINGER_SPACING = 100;
+        float xCoord = xCursorPosition - FAKE_FINGER_SPACING * (mSwipeFingerCount - 1) / 2;
+        for (size_t i = 0; i < mSwipeFingerCount; i++) {
+            PointerCoords& coords = mFakeFingerCoords[i];
+            coords.clear();
+            coords.setAxisValue(AMOTION_EVENT_AXIS_X, xCoord);
+            coords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+            coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+            xCoord += FAKE_FINGER_SPACING;
+        }
+
+        mDownTime = when;
+        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
+                                     /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+                                     yCursorPosition));
+        for (size_t i = 1; i < mSwipeFingerCount; i++) {
+            out.push_back(makeMotionArgs(when, readTime,
+                                         AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                                 (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                         /* actionButton= */ 0, mButtonState,
+                                         /* pointerCount= */ i + 1, mFingerProps.data(),
+                                         mFakeFingerCoords.data(), xCursorPosition,
+                                         yCursorPosition));
+        }
+    }
+    // TODO(b/251196347): Set the gesture properties appropriately to avoid needing to negate the Y
+    // values.
+    float rotatedDeltaX = dx, rotatedDeltaY = -dy;
+    rotateDelta(mOrientation, &rotatedDeltaX, &rotatedDeltaY);
+    for (size_t i = 0; i < mSwipeFingerCount; i++) {
+        PointerCoords& coords = mFakeFingerCoords[i];
+        coords.setAxisValue(AMOTION_EVENT_AXIS_X,
+                            coords.getAxisValue(AMOTION_EVENT_AXIS_X) + rotatedDeltaX);
+        coords.setAxisValue(AMOTION_EVENT_AXIS_Y,
+                            coords.getAxisValue(AMOTION_EVENT_AXIS_Y) + rotatedDeltaY);
+    }
+    float xOffset = dx / (mXAxisInfo.maxValue - mXAxisInfo.minValue);
+    // TODO(b/251196347): Set the gesture properties appropriately to avoid needing to negate the Y
+    // values.
+    float yOffset = -dy / (mYAxisInfo.maxValue - mYAxisInfo.minValue);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, xOffset);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, yOffset);
+    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
+                                 mButtonState, /* pointerCount= */ mSwipeFingerCount,
+                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+                                 yCursorPosition));
+    return out;
+}
+
+[[nodiscard]] std::list<NotifyArgs> GestureConverter::handleMultiFingerSwipeLift(nsecs_t when,
+                                                                                 nsecs_t readTime) {
+    std::list<NotifyArgs> out = {};
+    if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
+        return out;
+    }
+    float xCursorPosition, yCursorPosition;
+    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET, 0);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET, 0);
+
+    for (size_t i = mSwipeFingerCount; i > 1; i--) {
+        out.push_back(makeMotionArgs(when, readTime,
+                                     AMOTION_EVENT_ACTION_POINTER_UP |
+                                             ((i - 1) << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                                     /* actionButton= */ 0, mButtonState, /* pointerCount= */ i,
+                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+                                     yCursorPosition));
+    }
+    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP,
+                                 /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+                                 mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+                                 yCursorPosition));
+    mCurrentClassification = MotionClassification::NONE;
+    mSwipeFingerCount = 0;
+    return out;
+}
+
+[[nodiscard]] std::list<NotifyArgs> GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime,
+                                                                  const Gesture& gesture) {
+    std::list<NotifyArgs> out;
+    float xCursorPosition, yCursorPosition;
+    mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
+
+    // Pinch gesture phases are reported a little differently from others, in that the same details
+    // struct is used for all phases of the gesture, just with different zoom_state values. When
+    // zoom_state is START or END, dz will always be 1, so we don't need to move the pointers in
+    // those cases.
+
+    if (mCurrentClassification != MotionClassification::PINCH) {
+        LOG_ALWAYS_FATAL_IF(gesture.details.pinch.zoom_state != GESTURES_ZOOM_START,
+                            "First pinch gesture does not have the START zoom state (%d instead).",
+                            gesture.details.pinch.zoom_state);
+        mCurrentClassification = MotionClassification::PINCH;
+        mPinchFingerSeparation = INITIAL_PINCH_SEPARATION_PX;
+        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
+        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                          xCursorPosition - mPinchFingerSeparation / 2);
+        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                          xCursorPosition + mPinchFingerSeparation / 2);
+        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+        mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1.0f);
+        mDownTime = when;
+        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_DOWN,
+                                     /* actionButton= */ 0, mButtonState, /* pointerCount= */ 1,
+                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+                                     yCursorPosition));
+        out.push_back(makeMotionArgs(when, readTime,
+                                     AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                             1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
+                                     /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
+                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+                                     yCursorPosition));
+        return out;
+    }
+
+    if (gesture.details.pinch.zoom_state == GESTURES_ZOOM_END) {
+        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 1.0);
+        out.push_back(makeMotionArgs(when, readTime,
+                                     AMOTION_EVENT_ACTION_POINTER_UP |
+                                             1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT,
+                                     /* actionButton= */ 0, mButtonState, /* pointerCount= */ 2,
+                                     mFingerProps.data(), mFakeFingerCoords.data(), xCursorPosition,
+                                     yCursorPosition));
+        out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_UP, /* actionButton= */ 0,
+                                     mButtonState, /* pointerCount= */ 1, mFingerProps.data(),
+                                     mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+        mCurrentClassification = MotionClassification::NONE;
+        mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR, 0);
+        return out;
+    }
+
+    mPinchFingerSeparation *= gesture.details.pinch.dz;
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR,
+                                      gesture.details.pinch.dz);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                      xCursorPosition - mPinchFingerSeparation / 2);
+    mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X,
+                                      xCursorPosition + mPinchFingerSeparation / 2);
+    mFakeFingerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
+    out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, /* actionButton= */ 0,
+                                 mButtonState, /* pointerCount= */ 2, mFingerProps.data(),
+                                 mFakeFingerCoords.data(), xCursorPosition, yCursorPosition));
+    return out;
+}
+
 NotifyMotionArgs GestureConverter::makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                                   int32_t actionButton, int32_t buttonState,
                                                   uint32_t pointerCount,
@@ -174,13 +409,13 @@
     const uint32_t source = AINPUT_SOURCE_MOUSE;
 
     return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, source,
-                            mPointerController->getDisplayId(), /* policyFlags= */ 0, action,
-                            /* actionButton= */ actionButton, /* flags= */ 0,
+                            mPointerController->getDisplayId(), /* policyFlags= */ POLICY_FLAG_WAKE,
+                            action, /* actionButton= */ actionButton, /* flags= */ 0,
                             mReaderContext.getGlobalMetaState(), buttonState,
-                            MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
-                            pointerProperties, pointerCoords,
-                            /* xPrecision= */ 1.0f, /* yPrecision= */ 1.0f, xCursorPosition,
-                            yCursorPosition, /* downTime= */ mDownTime, /* videoFrames= */ {});
+                            mCurrentClassification, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
+                            pointerProperties, pointerCoords, /* xPrecision= */ 1.0f,
+                            /* yPrecision= */ 1.0f, xCursorPosition, yCursorPosition,
+                            /* downTime= */ mDownTime, /* videoFrames= */ {});
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/GestureConverter.h b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
index dc11f24..8e8e3d9 100644
--- a/services/inputflinger/reader/mapper/gestures/GestureConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -16,14 +16,18 @@
 
 #pragma once
 
+#include <array>
 #include <list>
 #include <memory>
 
 #include <PointerControllerInterface.h>
 #include <utils/Timers.h>
 
+#include "EventHub.h"
+#include "InputDevice.h"
 #include "InputReaderContext.h"
 #include "NotifyArgs.h"
+#include "ui/Rotation.h"
 
 #include "include/gestures.h"
 
@@ -33,17 +37,28 @@
 // PointerController calls.
 class GestureConverter {
 public:
-    GestureConverter(InputReaderContext& readerContext, int32_t deviceId);
+    GestureConverter(InputReaderContext& readerContext, const InputDeviceContext& deviceContext,
+                     int32_t deviceId);
 
+    void setOrientation(ui::Rotation orientation) { mOrientation = orientation; }
     void reset();
 
     [[nodiscard]] std::list<NotifyArgs> handleGesture(nsecs_t when, nsecs_t readTime,
                                                       const Gesture& gesture);
 
 private:
-    NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
+    [[nodiscard]] NotifyArgs handleMove(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
     [[nodiscard]] std::list<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
                                                             const Gesture& gesture);
+    [[nodiscard]] std::list<NotifyArgs> handleScroll(nsecs_t when, nsecs_t readTime,
+                                                     const Gesture& gesture);
+    [[nodiscard]] NotifyArgs handleFling(nsecs_t when, nsecs_t readTime, const Gesture& gesture);
+    [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime,
+                                                               uint32_t fingerCount, float dx,
+                                                               float dy);
+    [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipeLift(nsecs_t when, nsecs_t readTime);
+    [[nodiscard]] std::list<NotifyArgs> handlePinch(nsecs_t when, nsecs_t readTime,
+                                                    const Gesture& gesture);
 
     NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
                                     int32_t actionButton, int32_t buttonState,
@@ -56,10 +71,31 @@
     InputReaderContext& mReaderContext;
     std::shared_ptr<PointerControllerInterface> mPointerController;
 
+    ui::Rotation mOrientation = ui::ROTATION_0;
+    RawAbsoluteAxisInfo mXAxisInfo;
+    RawAbsoluteAxisInfo mYAxisInfo;
+
     // The current button state according to the gestures library, but converted into MotionEvent
     // button values (AMOTION_EVENT_BUTTON_...).
     uint32_t mButtonState = 0;
     nsecs_t mDownTime = 0;
+
+    MotionClassification mCurrentClassification = MotionClassification::NONE;
+    // Only used when mCurrentClassification is MULTI_FINGER_SWIPE.
+    uint32_t mSwipeFingerCount = 0;
+    static constexpr float INITIAL_PINCH_SEPARATION_PX = 200.0;
+    // Only used when mCurrentClassification is PINCH.
+    float mPinchFingerSeparation;
+    static constexpr size_t MAX_FAKE_FINGERS = 4;
+    // We never need any PointerProperties other than the finger tool type, so we can just keep a
+    // const array of them.
+    const std::array<PointerProperties, MAX_FAKE_FINGERS> mFingerProps = {{
+            {.id = 0, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
+            {.id = 1, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
+            {.id = 2, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
+            {.id = 3, .toolType = AMOTION_EVENT_TOOL_TYPE_FINGER},
+    }};
+    std::array<PointerCoords, MAX_FAKE_FINGERS> mFakeFingerCoords = {};
 };
 
 } // namespace android
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
index 289a780..6ac0bfb 100644
--- a/services/inputflinger/tests/FakeEventHub.cpp
+++ b/services/inputflinger/tests/FakeEventHub.cpp
@@ -120,8 +120,8 @@
     getDevice(deviceId)->keyCodeStates.replaceValueFor(keyCode, state);
 }
 
-void FakeEventHub::setCountryCode(int32_t deviceId, InputDeviceCountryCode countryCode) {
-    getDevice(deviceId)->countryCode = countryCode;
+void FakeEventHub::setRawLayoutInfo(int32_t deviceId, RawLayoutInfo info) {
+    getDevice(deviceId)->layoutInfo = info;
 }
 
 void FakeEventHub::setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) {
@@ -389,9 +389,9 @@
     return AKEY_STATE_UNKNOWN;
 }
 
-InputDeviceCountryCode FakeEventHub::getCountryCode(int32_t deviceId) const {
+std::optional<RawLayoutInfo> FakeEventHub::getRawLayoutInfo(int32_t deviceId) const {
     Device* device = getDevice(deviceId);
-    return device ? device->countryCode : InputDeviceCountryCode::INVALID;
+    return device ? device->layoutInfo : std::nullopt;
 }
 
 int32_t FakeEventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
diff --git a/services/inputflinger/tests/FakeEventHub.h b/services/inputflinger/tests/FakeEventHub.h
index fb3c859..72f8ac0 100644
--- a/services/inputflinger/tests/FakeEventHub.h
+++ b/services/inputflinger/tests/FakeEventHub.h
@@ -30,10 +30,6 @@
 #include <utils/Errors.h>
 #include <utils/KeyedVector.h>
 
-#include "android/hardware/input/InputDeviceCountryCode.h"
-
-using android::hardware::input::InputDeviceCountryCode;
-
 namespace android {
 
 class FakeEventHub : public EventHubInterface {
@@ -67,7 +63,7 @@
         BitArray<MSC_MAX> mscBitmask;
         std::vector<VirtualKeyDefinition> virtualKeys;
         bool enabled;
-        InputDeviceCountryCode countryCode;
+        std::optional<RawLayoutInfo> layoutInfo;
 
         status_t enable() {
             enabled = true;
@@ -124,7 +120,7 @@
     void addRelativeAxis(int32_t deviceId, int32_t axis);
     void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value);
 
-    void setCountryCode(int32_t deviceId, InputDeviceCountryCode countryCode);
+    void setRawLayoutInfo(int32_t deviceId, RawLayoutInfo info);
 
     void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state);
     void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state);
@@ -180,7 +176,7 @@
     std::vector<RawEvent> getEvents(int) override;
     std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override;
     int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override;
-    InputDeviceCountryCode getCountryCode(int32_t deviceId) const override;
+    std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override;
     int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override;
     int32_t getSwitchState(int32_t deviceId, int32_t sw) const override;
     status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const override;
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index f755356..bb8a30e 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -201,6 +201,10 @@
     mConfig.wheelVelocityControlParameters = params;
 }
 
+void FakeInputReaderPolicy::setStylusButtonMotionEventsEnabled(bool enabled) {
+    mConfig.stylusButtonMotionEventsEnabled = enabled;
+}
+
 void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) {
     *outConfig = mConfig;
 }
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 862ff0b..9ec3217 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -75,6 +75,7 @@
     float getPointerGestureMovementSpeedRatio();
     float getPointerGestureZoomSpeedRatio();
     void setVelocityControlParams(const VelocityControlParameters& params);
+    void setStylusButtonMotionEventsEnabled(bool enabled);
 
 private:
     void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
diff --git a/services/inputflinger/tests/GestureConverter_test.cpp b/services/inputflinger/tests/GestureConverter_test.cpp
index 91efd1a..36a39bb 100644
--- a/services/inputflinger/tests/GestureConverter_test.cpp
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -29,6 +29,7 @@
 #include "TestInputListener.h"
 #include "TestInputListenerMatchers.h"
 #include "include/gestures.h"
+#include "ui/Rotation.h"
 
 namespace android {
 
@@ -37,8 +38,9 @@
 class GestureConverterTest : public testing::Test {
 protected:
     static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+    static constexpr int32_t EVENTHUB_ID = 1;
     static constexpr stime_t ARBITRARY_GESTURE_TIME = 1.2;
-    static constexpr float POINTER_X = 100;
+    static constexpr float POINTER_X = 500;
     static constexpr float POINTER_Y = 200;
 
     void SetUp() {
@@ -47,6 +49,9 @@
         mFakeListener = std::make_unique<TestInputListener>();
         mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
                                                             *mFakeListener);
+        mDevice = newDevice();
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, -500, 500, 0, 0, 20);
+        mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, -500, 500, 0, 0, 20);
 
         mFakePointerController = std::make_shared<FakePointerController>();
         mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
@@ -54,15 +59,32 @@
         mFakePolicy->setPointerController(mFakePointerController);
     }
 
+    std::shared_ptr<InputDevice> newDevice() {
+        InputDeviceIdentifier identifier;
+        identifier.name = "device";
+        identifier.location = "USB1";
+        identifier.bus = 0;
+        std::shared_ptr<InputDevice> device =
+                std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, /* generation= */ 2,
+                                              identifier);
+        mReader->pushNextDevice(device);
+        mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD,
+                                 identifier.bus);
+        mReader->loopOnce();
+        return device;
+    }
+
     std::shared_ptr<FakeEventHub> mFakeEventHub;
     sp<FakeInputReaderPolicy> mFakePolicy;
     std::unique_ptr<TestInputListener> mFakeListener;
     std::unique_ptr<InstrumentedInputReader> mReader;
+    std::shared_ptr<InputDevice> mDevice;
     std::shared_ptr<FakePointerController> mFakePointerController;
 };
 
 TEST_F(GestureConverterTest, Move) {
-    GestureConverter converter(*mReader->getContext(), DEVICE_ID);
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
 
     Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
     std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
@@ -74,11 +96,30 @@
                       WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0),
                       WithPressure(0.0f)));
 
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(95, 210));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+}
+
+TEST_F(GestureConverterTest, Move_Rotated) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setOrientation(ui::ROTATION_90);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                      WithCoords(POINTER_X + 10, POINTER_Y + 5), WithRelativeMotion(10, 5),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithButtonState(0),
+                      WithPressure(0.0f)));
+
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5));
 }
 
 TEST_F(GestureConverterTest, ButtonsChange) {
-    GestureConverter converter(*mReader->getContext(), DEVICE_ID);
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
 
     // Press left and right buttons at once
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -143,7 +184,8 @@
 }
 
 TEST_F(GestureConverterTest, DragWithButton) {
-    GestureConverter converter(*mReader->getContext(), DEVICE_ID);
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
 
     // Press the button
     Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -176,7 +218,7 @@
                       WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER),
                       WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f)));
 
-    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(95, 210));
+    ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
 
     // Release the button
     Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
@@ -197,4 +239,546 @@
                       WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
 }
 
+TEST_F(GestureConverterTest, Scroll) {
+    const nsecs_t downTime = 12345;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+    ASSERT_EQ(2u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
+                      WithGestureScrollDistance(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithCoords(POINTER_X, POINTER_Y - 10),
+                      WithGestureScrollDistance(0, 10, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithCoords(POINTER_X, POINTER_Y - 15),
+                      WithGestureScrollDistance(0, 5, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithCoords(POINTER_X, POINTER_Y - 15),
+                      WithGestureScrollDistance(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Scroll_Rotated) {
+    const nsecs_t downTime = 12345;
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setOrientation(ui::ROTATION_90);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+    std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+    ASSERT_EQ(2u, args.size());
+
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
+                      WithGestureScrollDistance(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER), WithDownTime(downTime)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithCoords(POINTER_X - 10, POINTER_Y),
+                      WithGestureScrollDistance(0, 10, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithCoords(POINTER_X - 15, POINTER_Y),
+                      WithGestureScrollDistance(0, 5, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithCoords(POINTER_X - 15, POINTER_Y),
+                      WithGestureScrollDistance(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Scroll_ClearsClassificationAndOffsetsAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, 5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+
+    Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+                         GESTURES_FLING_START);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionClassification(MotionClassification::NONE),
+                      WithGestureScrollDistance(0, 0, EPSILON)));
+}
+
+TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAndOffsetsAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+                         /* dy= */ 0);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ -5,
+                        /* dy= */ 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionClassification(MotionClassification::NONE),
+                      WithGestureOffset(0, 0, EPSILON)));
+}
+
+TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) {
+    // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you
+    // start swiping up and then start moving left or right, it'll return gesture events with only Y
+    // deltas until you lift your fingers and start swiping again. That's why each of these tests
+    // only checks movement in one dimension.
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+                         /* dy= */ 10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    ASSERT_EQ(4u, args.size());
+
+    // Three fake fingers should be created. We don't actually care where they are, so long as they
+    // move appropriately.
+    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    PointerCoords finger0Start = arg.pointerCoords[0];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    PointerCoords finger1Start = arg.pointerCoords[1];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    PointerCoords finger2Start = arg.pointerCoords[2];
+    args.pop_front();
+
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.01, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 10);
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 10);
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 10);
+
+    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* dx= */ 0, /* dy= */ 5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.005, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 15);
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 15);
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    ASSERT_EQ(3u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+    converter.setOrientation(ui::ROTATION_90);
+
+    Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+                         /* dy= */ 10);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    ASSERT_EQ(4u, args.size());
+
+    // Three fake fingers should be created. We don't actually care where they are, so long as they
+    // move appropriately.
+    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+                      WithPointerCount(1u)));
+    PointerCoords finger0Start = arg.pointerCoords[0];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
+    PointerCoords finger1Start = arg.pointerCoords[1];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
+    PointerCoords finger2Start = arg.pointerCoords[2];
+    args.pop_front();
+
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 10);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 10);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 10);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+
+    Gesture continueGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* dx= */ 0, /* dy= */ 5);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    ASSERT_EQ(3u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+                      WithPointerCount(1u)));
+}
+
+TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                         /* dx= */ 10, /* dy= */ 0);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    ASSERT_EQ(5u, args.size());
+
+    // Four fake fingers should be created. We don't actually care where they are, so long as they
+    // move appropriately.
+    NotifyMotionArgs arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    PointerCoords finger0Start = arg.pointerCoords[0];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    PointerCoords finger1Start = arg.pointerCoords[1];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    PointerCoords finger2Start = arg.pointerCoords[2];
+    args.pop_front();
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    PointerCoords finger3Start = arg.pointerCoords[3];
+    args.pop_front();
+
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0.01, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 10);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+    EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
+
+    Gesture continueGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                            /* dx= */ 5, /* dy= */ 0);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+    ASSERT_EQ(1u, args.size());
+    arg = std::get<NotifyMotionArgs>(args.front());
+    ASSERT_THAT(arg,
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithGestureOffset(0.005, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 15);
+    EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+    EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+    EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+    EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
+
+    Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+    ASSERT_EQ(4u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(4u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(3u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(2u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+                      WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+                      WithPointerCount(1u), WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Pinch_Inwards) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    ASSERT_EQ(2u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON),
+                      WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON),
+                      WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(0.8f, EPSILON),
+                      WithPointerCoords(0, POINTER_X - 80, POINTER_Y),
+                      WithPointerCoords(1, POINTER_X + 80, POINTER_Y), WithPointerCount(2u),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    ASSERT_EQ(2u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Pinch_Outwards) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+    ASSERT_EQ(2u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON),
+                      WithCoords(POINTER_X - 100, POINTER_Y), WithPointerCount(1u),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON),
+                      WithPointerCoords(1, POINTER_X + 100, POINTER_Y), WithPointerCount(2u),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.2f, EPSILON),
+                      WithPointerCoords(0, POINTER_X - 120, POINTER_Y),
+                      WithPointerCoords(1, POINTER_X + 120, POINTER_Y), WithPointerCount(2u),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+    ASSERT_EQ(2u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+                                       1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(2u),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+    args.pop_front();
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                      WithMotionClassification(MotionClassification::PINCH),
+                      WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+                      WithToolType(AMOTION_EVENT_TOOL_TYPE_FINGER)));
+}
+
+TEST_F(GestureConverterTest, Pinch_ClearsClassificationAndScaleFactorAfterGesture) {
+    InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+    GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+
+    Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                         GESTURES_ZOOM_START);
+    std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+    Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+                          /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+
+    Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+                       GESTURES_ZOOM_END);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+
+    Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+    args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+    ASSERT_EQ(1u, args.size());
+    ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+                AllOf(WithMotionClassification(MotionClassification::NONE),
+                      WithGesturePinchScaleFactor(0, EPSILON)));
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 96d27b8..95d35f4 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -48,12 +48,9 @@
 #include "InputMapperTest.h"
 #include "InstrumentedInputReader.h"
 #include "TestConstants.h"
-#include "android/hardware/input/InputDeviceCountryCode.h"
 #include "input/DisplayViewport.h"
 #include "input/Input.h"
 
-using android::hardware::input::InputDeviceCountryCode;
-
 namespace android {
 
 using namespace ftl::flag_operators;
@@ -2023,6 +2020,56 @@
                   WithDeviceId(touchscreenId))));
 }
 
+TYPED_TEST(StylusButtonIntegrationTest, StylusButtonMotionEventsDisabled) {
+    TestFixture::mFakePolicy->setStylusButtonMotionEventsEnabled(false);
+    TestFixture::mReader->requestRefreshConfiguration(
+            InputReaderConfiguration::CHANGE_STYLUS_BUTTON_REPORTING);
+
+    const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
+    const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
+    const auto stylusId = TestFixture::mStylusInfo.getId();
+
+    // Start a stylus gesture. By the time this event is processed, the configuration change that
+    // was requested is guaranteed to be completed.
+    TestFixture::mTouchscreen->sendSlot(FIRST_SLOT);
+    TestFixture::mTouchscreen->sendTrackingId(FIRST_TRACKING_ID);
+    TestFixture::mTouchscreen->sendToolType(MT_TOOL_PEN);
+    TestFixture::mTouchscreen->sendDown(centerPoint);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+
+    // Press and release a stylus button. Each change only generates a MOVE motion event.
+    // Key events are unaffected.
+    TestFixture::mStylus->pressKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+
+    TestFixture::mStylus->releaseKey(BTN_STYLUS);
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyKeyWasCalled(
+            AllOf(WithKeyAction(AKEY_EVENT_ACTION_UP), WithSource(AINPUT_SOURCE_KEYBOARD),
+                  WithKeyCode(AKEYCODE_STYLUS_BUTTON_PRIMARY), WithDeviceId(stylusId))));
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+
+    // Finish the stylus gesture.
+    TestFixture::mTouchscreen->sendTrackingId(INVALID_TRACKING_ID);
+    TestFixture::mTouchscreen->sendSync();
+    ASSERT_NO_FATAL_FAILURE(TestFixture::mTestListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                  WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS), WithButtonState(0),
+                  WithDeviceId(touchscreenId))));
+}
+
 // --- ExternalStylusIntegrationTest ---
 
 // Verify the behavior of an external stylus. An external stylus can report pressure or button
@@ -2240,17 +2287,6 @@
     ASSERT_EQ(ftl::Flags<InputDeviceClass>(0), mDevice->getClasses());
 }
 
-TEST_F(InputDeviceTest, CountryCodeCorrectlyMapped) {
-    mFakeEventHub->setCountryCode(EVENTHUB_ID, InputDeviceCountryCode::INTERNATIONAL);
-
-    // Configuration
-    mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
-    InputReaderConfiguration config;
-    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
-
-    ASSERT_EQ(InputDeviceCountryCode::INTERNATIONAL, mDevice->getDeviceInfo().getCountryCode());
-}
-
 TEST_F(InputDeviceTest, WhenDeviceCreated_EnabledIsFalse) {
     ASSERT_EQ(mDevice->isEnabled(), false);
 }
@@ -3600,6 +3636,20 @@
               deviceInfo.getKeyboardLayoutInfo()->layoutType);
 }
 
+TEST_F(KeyboardInputMapperTest, LayoutInfoCorrectlyMapped) {
+    mFakeEventHub->setRawLayoutInfo(EVENTHUB_ID,
+                                    RawLayoutInfo{.languageTag = "en", .layoutType = "extended"});
+
+    // Configuration
+    addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+                                               AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+    InputReaderConfiguration config;
+    std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, &config, 0);
+
+    ASSERT_EQ("en", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->languageTag);
+    ASSERT_EQ("extended", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->layoutType);
+}
+
 // --- KeyboardInputMapperTest_ExternalDevice ---
 
 class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {
@@ -6577,6 +6627,46 @@
                   WithCoords(toDisplayX(100), toDisplayY(200)), WithButtonState(0))));
 }
 
+TEST_F(SingleTouchInputMapperTest, StylusButtonMotionEventsDisabled) {
+    addConfigurationProperty("touch.deviceType", "touchScreen");
+    prepareDisplay(ui::ROTATION_0);
+    prepareButtons();
+    prepareAxes(POSITION);
+
+    mFakePolicy->setStylusButtonMotionEventsEnabled(false);
+
+    SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+    // Press a stylus button.
+    processKey(mapper, BTN_STYLUS, 1);
+    processSync(mapper);
+
+    // Start a touch gesture and ensure that the stylus button is not reported.
+    processDown(mapper, 100, 200);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithButtonState(0))));
+
+    // Release and press the stylus button again.
+    processKey(mapper, BTN_STYLUS, 0);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0))));
+    processKey(mapper, BTN_STYLUS, 1);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithButtonState(0))));
+
+    // Release the touch gesture.
+    processUp(mapper);
+    processSync(mapper);
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+            AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0))));
+
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
 TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorrectType) {
     mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation");
     prepareDisplay(ui::ROTATION_0);
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index e5a4b14..b9d9607 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -16,6 +16,8 @@
 
 #pragma once
 
+#include <cmath>
+
 #include <android/input.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
@@ -66,6 +68,11 @@
     return arg.keyCode == keyCode;
 }
 
+MATCHER_P(WithPointerCount, count, "MotionEvent with specified number of pointers") {
+    *result_listener << "expected " << count << " pointer(s), but got " << arg.pointerCount;
+    return arg.pointerCount == count;
+}
+
 MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") {
     const auto argX = arg.pointerCoords[0].getX();
     const auto argY = arg.pointerCoords[0].getY();
@@ -74,6 +81,14 @@
     return argX == x && argY == y;
 }
 
+MATCHER_P3(WithPointerCoords, pointer, x, y, "InputEvent with specified coords for pointer") {
+    const auto argX = arg.pointerCoords[pointer].getX();
+    const auto argY = arg.pointerCoords[pointer].getY();
+    *result_listener << "expected pointer " << pointer << " to have coords (" << x << ", " << y
+                     << "), but got (" << argX << ", " << argY << ")";
+    return argX == x && argY == y;
+}
+
 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);
@@ -82,6 +97,39 @@
     return argX == x && argY == y;
 }
 
+MATCHER_P3(WithGestureOffset, dx, dy, epsilon,
+           "InputEvent with specified touchpad gesture offset") {
+    const auto argGestureX = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_X_OFFSET);
+    const auto argGestureY = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_Y_OFFSET);
+    const double xDiff = fabs(argGestureX - dx);
+    const double yDiff = fabs(argGestureY - dy);
+    *result_listener << "expected gesture offset (" << dx << ", " << dy << ") within " << epsilon
+                     << ", but got (" << argGestureX << ", " << argGestureY << ")";
+    return xDiff <= epsilon && yDiff <= epsilon;
+}
+
+MATCHER_P3(WithGestureScrollDistance, x, y, epsilon,
+           "InputEvent with specified touchpad gesture scroll distance") {
+    const auto argXDistance =
+            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_X_DISTANCE);
+    const auto argYDistance =
+            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SCROLL_Y_DISTANCE);
+    const double xDiff = fabs(argXDistance - x);
+    const double yDiff = fabs(argYDistance - y);
+    *result_listener << "expected gesture offset (" << x << ", " << y << ") within " << epsilon
+                     << ", but got (" << argXDistance << ", " << argYDistance << ")";
+    return xDiff <= epsilon && yDiff <= epsilon;
+}
+
+MATCHER_P2(WithGesturePinchScaleFactor, factor, epsilon,
+           "InputEvent with specified touchpad pinch gesture scale factor") {
+    const auto argScaleFactor =
+            arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR);
+    *result_listener << "expected gesture scale factor " << factor << " within " << epsilon
+                     << " but got " << argScaleFactor;
+    return fabs(argScaleFactor - factor) <= epsilon;
+}
+
 MATCHER_P(WithPressure, pressure, "InputEvent with specified pressure") {
     const auto argPressure = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE);
     *result_listener << "expected pressure " << pressure << ", but got " << argPressure;
@@ -100,6 +148,13 @@
     return arg.flags == flags;
 }
 
+MATCHER_P(WithMotionClassification, classification,
+          "InputEvent with specified MotionClassification") {
+    *result_listener << "expected classification " << motionClassificationToString(classification)
+                     << ", but got " << motionClassificationToString(arg.classification);
+    return arg.classification == classification;
+}
+
 MATCHER_P(WithButtonState, buttons, "InputEvent with specified button state") {
     *result_listener << "expected button state " << buttons << ", but got " << arg.buttonState;
     return arg.buttonState == buttons;
@@ -116,4 +171,9 @@
     return arg.eventTime == eventTime;
 }
 
+MATCHER_P(WithDownTime, downTime, "InputEvent with specified downTime") {
+    *result_listener << "expected down time " << downTime << ", but got " << arg.downTime;
+    return arg.downTime == downTime;
+}
+
 } // namespace android
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index a0910ea..7c9be5c 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -19,9 +19,6 @@
 #include <InputMapper.h>
 #include <InputReader.h>
 #include <ThreadSafeFuzzedDataProvider.h>
-#include "android/hardware/input/InputDeviceCountryCode.h"
-
-using android::hardware::input::InputDeviceCountryCode;
 
 constexpr size_t kValidTypes[] = {EV_SW,
                                   EV_SYN,
@@ -65,46 +62,6 @@
         BTN_TASK,
 };
 
-constexpr InputDeviceCountryCode kCountryCodes[] = {
-        InputDeviceCountryCode::INVALID,
-        InputDeviceCountryCode::NOT_SUPPORTED,
-        InputDeviceCountryCode::ARABIC,
-        InputDeviceCountryCode::BELGIAN,
-        InputDeviceCountryCode::CANADIAN_BILINGUAL,
-        InputDeviceCountryCode::CANADIAN_FRENCH,
-        InputDeviceCountryCode::CZECH_REPUBLIC,
-        InputDeviceCountryCode::DANISH,
-        InputDeviceCountryCode::FINNISH,
-        InputDeviceCountryCode::FRENCH,
-        InputDeviceCountryCode::GERMAN,
-        InputDeviceCountryCode::GREEK,
-        InputDeviceCountryCode::HEBREW,
-        InputDeviceCountryCode::HUNGARY,
-        InputDeviceCountryCode::INTERNATIONAL,
-        InputDeviceCountryCode::ITALIAN,
-        InputDeviceCountryCode::JAPAN,
-        InputDeviceCountryCode::KOREAN,
-        InputDeviceCountryCode::LATIN_AMERICAN,
-        InputDeviceCountryCode::DUTCH,
-        InputDeviceCountryCode::NORWEGIAN,
-        InputDeviceCountryCode::PERSIAN,
-        InputDeviceCountryCode::POLAND,
-        InputDeviceCountryCode::PORTUGUESE,
-        InputDeviceCountryCode::RUSSIA,
-        InputDeviceCountryCode::SLOVAKIA,
-        InputDeviceCountryCode::SPANISH,
-        InputDeviceCountryCode::SWEDISH,
-        InputDeviceCountryCode::SWISS_FRENCH,
-        InputDeviceCountryCode::SWISS_GERMAN,
-        InputDeviceCountryCode::SWITZERLAND,
-        InputDeviceCountryCode::TAIWAN,
-        InputDeviceCountryCode::TURKISH_Q,
-        InputDeviceCountryCode::UK,
-        InputDeviceCountryCode::US,
-        InputDeviceCountryCode::YUGOSLAVIA,
-        InputDeviceCountryCode::TURKISH_F,
-};
-
 constexpr size_t kMaxSize = 256;
 
 namespace android {
@@ -197,8 +154,8 @@
     void setLightIntensities(int32_t deviceId, int32_t lightId,
                              std::unordered_map<LightColor, int32_t> intensities) override{};
 
-    InputDeviceCountryCode getCountryCode(int32_t deviceId) const override {
-        return mFdp->PickValueInArray<InputDeviceCountryCode>(kCountryCodes);
+    std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override {
+        return std::nullopt;
     };
 
     int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override {
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index 82d0295..b94b1c0 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -82,7 +82,7 @@
 void SensorService::SensorEventConnection::dump(String8& result) {
     Mutex::Autolock _l(mConnectionLock);
     result.appendFormat("\tOperating Mode: ");
-    if (!mService->isWhiteListedPackage(getPackageName())) {
+    if (!mService->isAllowListedPackage(getPackageName())) {
         result.append("RESTRICTED\n");
     } else if (mDataInjectionMode) {
         result.append("DATA_INJECTION\n");
@@ -124,7 +124,7 @@
     using namespace service::SensorEventConnectionProto;
     Mutex::Autolock _l(mConnectionLock);
 
-    if (!mService->isWhiteListedPackage(getPackageName())) {
+    if (!mService->isAllowListedPackage(getPackageName())) {
         proto->write(OPERATING_MODE, OP_MODE_RESTRICTED);
     } else if (mDataInjectionMode) {
         proto->write(OPERATING_MODE, OP_MODE_DATA_INJECTION);
@@ -850,6 +850,11 @@
                     // Unregister call backs.
                     return 0;
                 }
+                if (!mService->isAllowListedPackage(mPackageName)) {
+                    ALOGE("App not allowed to inject data, dropping event"
+                          "package=%s uid=%d", mPackageName.string(), mUid);
+                    return 0;
+                }
                 sensors_event_t sensor_event;
                 memcpy(&sensor_event, buf, sizeof(sensors_event_t));
                 std::shared_ptr<SensorInterface> si =
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index 6504b79..5c98614 100644
--- a/services/sensorservice/SensorService.cpp
+++ b/services/sensorservice/SensorService.cpp
@@ -52,6 +52,7 @@
 #include "SensorEventConnection.h"
 #include "SensorRecord.h"
 #include "SensorRegistrationInfo.h"
+#include "SensorServiceUtils.h"
 
 #include <inttypes.h>
 #include <math.h>
@@ -64,6 +65,7 @@
 
 #include <ctime>
 #include <future>
+#include <string>
 
 #include <private/android_filesystem_config.h>
 
@@ -547,55 +549,22 @@
         if (args.size() > 2) {
            return INVALID_OPERATION;
         }
-        ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
-        SensorDevice& dev(SensorDevice::getInstance());
-        if (args.size() == 2 && args[0] == String16("restrict")) {
-            // If already in restricted mode. Ignore.
-            if (mCurrentOperatingMode == RESTRICTED) {
-                return status_t(NO_ERROR);
+        if (args.size() > 0) {
+            Mode targetOperatingMode = NORMAL;
+            std::string inputStringMode = String8(args[0]).string();
+            if (getTargetOperatingMode(inputStringMode, &targetOperatingMode)) {
+              status_t error = changeOperatingMode(args, targetOperatingMode);
+              // Dump the latest state only if no error was encountered.
+              if (error != NO_ERROR) {
+                return error;
+              }
             }
-            // If in any mode other than normal, ignore.
-            if (mCurrentOperatingMode != NORMAL) {
-                return INVALID_OPERATION;
-            }
+        }
 
-            mCurrentOperatingMode = RESTRICTED;
-            // temporarily stop all sensor direct report and disable sensors
-            disableAllSensorsLocked(&connLock);
-            mWhiteListedPackage.setTo(String8(args[1]));
-            return status_t(NO_ERROR);
-        } else if (args.size() == 1 && args[0] == String16("enable")) {
-            // If currently in restricted mode, reset back to NORMAL mode else ignore.
-            if (mCurrentOperatingMode == RESTRICTED) {
-                mCurrentOperatingMode = NORMAL;
-                // enable sensors and recover all sensor direct report
-                enableAllSensorsLocked(&connLock);
-            }
-            if (mCurrentOperatingMode == DATA_INJECTION) {
-               resetToNormalModeLocked();
-            }
-            mWhiteListedPackage.clear();
-            return status_t(NO_ERROR);
-        } else if (args.size() == 2 && args[0] == String16("data_injection")) {
-            if (mCurrentOperatingMode == NORMAL) {
-                dev.disableAllSensors();
-                status_t err = dev.setMode(DATA_INJECTION);
-                if (err == NO_ERROR) {
-                    mCurrentOperatingMode = DATA_INJECTION;
-                } else {
-                    // Re-enable sensors.
-                    dev.enableAllSensors();
-                }
-                mWhiteListedPackage.setTo(String8(args[1]));
-                return NO_ERROR;
-            } else if (mCurrentOperatingMode == DATA_INJECTION) {
-                // Already in DATA_INJECTION mode. Treat this as a no_op.
-                return NO_ERROR;
-            } else {
-                // Transition to data injection mode supported only from NORMAL mode.
-                return INVALID_OPERATION;
-            }
-        } else if (args.size() == 1 && args[0] == String16("--proto")) {
+        ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
+        // Run the following logic if a transition isn't requested above based on the input
+        // argument parsing.
+        if (args.size() == 1 && args[0] == String16("--proto")) {
             return dumpProtoLocked(fd, &connLock);
         } else if (!mSensors.hasAnySensor()) {
             result.append("No Sensors on the device\n");
@@ -654,10 +623,18 @@
                    result.appendFormat(" NORMAL\n");
                    break;
                case RESTRICTED:
-                   result.appendFormat(" RESTRICTED : %s\n", mWhiteListedPackage.string());
+                   result.appendFormat(" RESTRICTED : %s\n", mAllowListedPackage.string());
                    break;
                case DATA_INJECTION:
-                   result.appendFormat(" DATA_INJECTION : %s\n", mWhiteListedPackage.string());
+                   result.appendFormat(" DATA_INJECTION : %s\n", mAllowListedPackage.string());
+                   break;
+               case REPLAY_DATA_INJECTION:
+                   result.appendFormat(" REPLAY_DATA_INJECTION : %s\n",
+                            mAllowListedPackage.string());
+                   break;
+               default:
+                   result.appendFormat(" UNKNOWN\n");
+                   break;
             }
             result.appendFormat("Sensor Privacy: %s\n",
                     mSensorPrivacyPolicy->isSensorPrivacyEnabled() ? "enabled" : "disabled");
@@ -775,11 +752,11 @@
             break;
         case RESTRICTED:
             proto.write(OPERATING_MODE, OP_MODE_RESTRICTED);
-            proto.write(WHITELISTED_PACKAGE, std::string(mWhiteListedPackage.string()));
+            proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.string()));
             break;
         case DATA_INJECTION:
             proto.write(OPERATING_MODE, OP_MODE_DATA_INJECTION);
-            proto.write(WHITELISTED_PACKAGE, std::string(mWhiteListedPackage.string()));
+            proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.string()));
             break;
         default:
             proto.write(OPERATING_MODE, OP_MODE_UNKNOWN);
@@ -1498,8 +1475,10 @@
 
 sp<ISensorEventConnection> SensorService::createSensorEventConnection(const String8& packageName,
         int requestedMode, const String16& opPackageName, const String16& attributionTag) {
-    // Only 2 modes supported for a SensorEventConnection ... NORMAL and DATA_INJECTION.
-    if (requestedMode != NORMAL && requestedMode != DATA_INJECTION) {
+    // Only 3 modes supported for a SensorEventConnection ... NORMAL, DATA_INJECTION and
+    // REPLAY_DATA_INJECTION.
+    if (requestedMode != NORMAL && requestedMode != DATA_INJECTION &&
+            requestedMode != REPLAY_DATA_INJECTION) {
         return nullptr;
     }
     resetTargetSdkVersionCache(opPackageName);
@@ -1509,7 +1488,7 @@
     // operating in DI mode.
     if (requestedMode == DATA_INJECTION) {
         if (mCurrentOperatingMode != DATA_INJECTION) return nullptr;
-        if (!isWhiteListedPackage(packageName)) return nullptr;
+        if (!isAllowListedPackage(packageName)) return nullptr;
     }
 
     uid_t uid = IPCThreadState::self()->getCallingUid();
@@ -1520,8 +1499,9 @@
     String16 connOpPackageName =
             (opPackageName == String16("")) ? String16(connPackageName) : opPackageName;
     sp<SensorEventConnection> result(new SensorEventConnection(this, uid, connPackageName,
-            requestedMode == DATA_INJECTION, connOpPackageName, attributionTag));
-    if (requestedMode == DATA_INJECTION) {
+            requestedMode == DATA_INJECTION || requestedMode == REPLAY_DATA_INJECTION,
+            connOpPackageName, attributionTag));
+    if (requestedMode == DATA_INJECTION || requestedMode == REPLAY_DATA_INJECTION) {
         mConnectionHolder.addEventConnectionIfNotPresent(result);
         // Add the associated file descriptor to the Looper for polling whenever there is data to
         // be injected.
@@ -1880,8 +1860,8 @@
     }
 
     ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
-    if (mCurrentOperatingMode != NORMAL
-           && !isWhiteListedPackage(connection->getPackageName())) {
+    if (mCurrentOperatingMode != NORMAL && mCurrentOperatingMode != REPLAY_DATA_INJECTION &&
+           !isAllowListedPackage(connection->getPackageName())) {
         return INVALID_OPERATION;
     }
 
@@ -2229,6 +2209,95 @@
     }
 }
 
+bool SensorService::getTargetOperatingMode(const std::string &inputString, Mode *targetModeOut) {
+    if (inputString == std::string("restrict")) {
+      *targetModeOut = RESTRICTED;
+      return true;
+    }
+    if (inputString == std::string("enable")) {
+      *targetModeOut = NORMAL;
+      return true;
+    }
+    if (inputString == std::string("data_injection")) {
+      *targetModeOut = DATA_INJECTION;
+      return true;
+    }
+    if (inputString == std::string("replay_data_injection")) {
+      *targetModeOut = REPLAY_DATA_INJECTION;
+      return true;
+    }
+    return false;
+}
+
+status_t SensorService::changeOperatingMode(const Vector<String16>& args,
+                                            Mode targetOperatingMode) {
+    ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
+    SensorDevice& dev(SensorDevice::getInstance());
+    if (mCurrentOperatingMode == targetOperatingMode) {
+        return NO_ERROR;
+    }
+    if (targetOperatingMode != NORMAL && args.size() < 2) {
+        return INVALID_OPERATION;
+    }
+    switch (targetOperatingMode) {
+      case NORMAL:
+        // If currently in restricted mode, reset back to NORMAL mode else ignore.
+        if (mCurrentOperatingMode == RESTRICTED) {
+            mCurrentOperatingMode = NORMAL;
+            // enable sensors and recover all sensor direct report
+            enableAllSensorsLocked(&connLock);
+        }
+        if (mCurrentOperatingMode == REPLAY_DATA_INJECTION) {
+            dev.disableAllSensors();
+        }
+        if (mCurrentOperatingMode == DATA_INJECTION ||
+                mCurrentOperatingMode == REPLAY_DATA_INJECTION) {
+          resetToNormalModeLocked();
+        }
+        mAllowListedPackage.clear();
+        return status_t(NO_ERROR);
+      case RESTRICTED:
+        // If in any mode other than normal, ignore.
+        if (mCurrentOperatingMode != NORMAL) {
+            return INVALID_OPERATION;
+        }
+
+        mCurrentOperatingMode = RESTRICTED;
+        // temporarily stop all sensor direct report and disable sensors
+        disableAllSensorsLocked(&connLock);
+        mAllowListedPackage.setTo(String8(args[1]));
+        return status_t(NO_ERROR);
+      case REPLAY_DATA_INJECTION:
+        if (SensorServiceUtil::isUserBuild()) {
+            return INVALID_OPERATION;
+        }
+        FALLTHROUGH_INTENDED;
+      case DATA_INJECTION:
+        if (mCurrentOperatingMode == NORMAL) {
+            dev.disableAllSensors();
+            // Always use DATA_INJECTION here since this value goes to the HAL and the HAL
+            // doesn't have an understanding of replay vs. normal data injection.
+            status_t err = dev.setMode(DATA_INJECTION);
+            if (err == NO_ERROR) {
+                mCurrentOperatingMode = targetOperatingMode;
+            }
+            if (err != NO_ERROR || targetOperatingMode == REPLAY_DATA_INJECTION) {
+                // Re-enable sensors.
+                dev.enableAllSensors();
+            }
+            mAllowListedPackage.setTo(String8(args[1]));
+            return NO_ERROR;
+        } else {
+            // Transition to data injection mode supported only from NORMAL mode.
+            return INVALID_OPERATION;
+        }
+        break;
+      default:
+        break;
+    }
+    return NO_ERROR;
+}
+
 void SensorService::checkWakeLockState() {
     ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
     checkWakeLockStateLocked(&connLock);
@@ -2258,14 +2327,14 @@
     }
 }
 
-bool SensorService::isWhiteListedPackage(const String8& packageName) {
-    return (packageName.contains(mWhiteListedPackage.string()));
+bool SensorService::isAllowListedPackage(const String8& packageName) {
+    return (packageName.contains(mAllowListedPackage.string()));
 }
 
 bool SensorService::isOperationRestrictedLocked(const String16& opPackageName) {
     if (mCurrentOperatingMode == RESTRICTED) {
         String8 package(opPackageName);
-        return !isWhiteListedPackage(package);
+        return !isAllowListedPackage(package);
     }
     return false;
 }
diff --git a/services/sensorservice/SensorService.h b/services/sensorservice/SensorService.h
index 78df501..0798279 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -102,8 +102,7 @@
        // Step Detector etc. Typically in this mode, there will be a client (a
        // SensorEventConnection) which will be injecting sensor data into the HAL. Normal apps can
        // unregister and register for any sensor that supports injection. Registering to sensors
-       // that do not support injection will give an error.  TODO: Allow exactly one
-       // client to inject sensor data at a time.
+       // that do not support injection will give an error.
        DATA_INJECTION = 1,
        // This mode is used only for testing sensors. Each sensor can be tested in isolation with
        // the required sampling_rate and maxReportLatency parameters without having to think about
@@ -116,10 +115,14 @@
        // corresponding parameters if the application hasn't unregistered for sensors in the mean
        // time.  NOTE: Non allowlisted app whose sensors were previously deactivated may still
        // receive events if a allowlisted app requests data from the same sensor.
-       RESTRICTED = 2
+       RESTRICTED = 2,
+       // Mostly equivalent to DATA_INJECTION with the difference being that the injected data is
+       // delivered to all requesting apps rather than just the package allowed to inject data.
+       // This mode is only allowed to be used on development builds.
+       REPLAY_DATA_INJECTION = 3,
 
       // State Transitions supported.
-      //     RESTRICTED   <---  NORMAL   ---> DATA_INJECTION
+      //     RESTRICTED   <---  NORMAL   ---> DATA_INJECTION/REPLAY_DATA_INJECTION
       //                  --->           <---
 
       // Shell commands to switch modes in SensorService.
@@ -395,6 +398,9 @@
     static bool hasPermissionForSensor(const Sensor& sensor);
     static int getTargetSdkVersion(const String16& opPackageName);
     static void resetTargetSdkVersionCache(const String16& opPackageName);
+    // Checks if the provided target operating mode is valid and returns the enum if it is.
+    static bool getTargetOperatingMode(const std::string &inputString, Mode *targetModeOut);
+    status_t changeOperatingMode(const Vector<String16>& args, Mode targetOperatingMode);
     // SensorService acquires a partial wakelock for delivering events from wake up sensors. This
     // method checks whether all the events from these wake up sensors have been delivered to the
     // corresponding applications, if yes the wakelock is released.
@@ -420,7 +426,7 @@
     // If SensorService is operating in RESTRICTED mode, only select whitelisted packages are
     // allowed to register for or call flush on sensors. Typically only cts test packages are
     // allowed.
-    bool isWhiteListedPackage(const String8& packageName);
+    bool isAllowListedPackage(const String8& packageName);
 
     // Returns true if a connection with the specified opPackageName has no access to sensors
     // in the RESTRICTED mode (i.e. the service is in RESTRICTED mode, and the package is not
@@ -519,7 +525,7 @@
     // applications with this packageName are allowed to activate/deactivate or call flush on
     // sensors. To run CTS this is can be set to ".cts." and only CTS tests will get access to
     // sensors.
-    String8 mWhiteListedPackage;
+    String8 mAllowListedPackage;
 
     int mNextSensorRegIndex;
     Vector<SensorRegistrationInfo> mLastNSensorRegistrations;
diff --git a/services/sensorservice/SensorServiceUtils.cpp b/services/sensorservice/SensorServiceUtils.cpp
index 6bad962..46b4b5b 100644
--- a/services/sensorservice/SensorServiceUtils.cpp
+++ b/services/sensorservice/SensorServiceUtils.cpp
@@ -16,6 +16,7 @@
 
 #include "SensorServiceUtils.h"
 
+#include <android-base/properties.h>
 #include <hardware/sensors.h>
 
 namespace android {
@@ -76,5 +77,10 @@
     }
 }
 
+bool isUserBuild() {
+    std::string buildType = android::base::GetProperty("ro.build.type", "user");
+    return "user" == buildType;
+}
+
 } // namespace SensorServiceUtil
 } // namespace android;
diff --git a/services/sensorservice/SensorServiceUtils.h b/services/sensorservice/SensorServiceUtils.h
index 49457cf..a6e0d6b 100644
--- a/services/sensorservice/SensorServiceUtils.h
+++ b/services/sensorservice/SensorServiceUtils.h
@@ -38,6 +38,11 @@
 
 size_t eventSizeBySensorType(int type);
 
+/**
+ * Returns true if on a user (production) build.
+ */
+bool isUserBuild();
+
 } // namespace SensorServiceUtil
 } // namespace android;
 
diff --git a/services/surfaceflinger/CompositionEngine/src/Output.cpp b/services/surfaceflinger/CompositionEngine/src/Output.cpp
index d513731..3ec6816 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -118,8 +118,9 @@
 void Output::setName(const std::string& name) {
     mName = name;
     auto displayIdOpt = getDisplayId();
-    mNamePlusId = base::StringPrintf("%s (%s)", mName.c_str(),
-                                     displayIdOpt ? to_string(*displayIdOpt).c_str() : "NA");
+    mNamePlusId = displayIdOpt ? base::StringPrintf("%s (%s)", mName.c_str(),
+                                     to_string(*displayIdOpt).c_str())
+                               : mName;
 }
 
 void Output::setCompositionEnabled(bool enabled) {
@@ -1224,8 +1225,9 @@
     ALOGV(__FUNCTION__);
 
     const auto& outputState = getState();
-    const TracedOrdinal<bool> hasClientComposition = {"hasClientComposition",
-                                                      outputState.usesClientComposition};
+    const TracedOrdinal<bool> hasClientComposition = {
+        base::StringPrintf("hasClientComposition %s", mNamePlusId.c_str()),
+        outputState.usesClientComposition};
     if (!hasClientComposition) {
         setExpensiveRenderingExpected(false);
         return base::unique_fd();
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
index 33caa7a..6199a5a 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -112,6 +112,11 @@
     MOCK_METHOD1(clearBootDisplayMode, status_t(PhysicalDisplayId));
     MOCK_METHOD1(getPreferredBootDisplayMode, std::optional<hal::HWConfigId>(PhysicalDisplayId));
     MOCK_METHOD0(getBootDisplayModeSupport, bool());
+    MOCK_CONST_METHOD0(
+            getHdrConversionCapabilities,
+            std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>());
+    MOCK_METHOD1(setHdrConversionStrategy,
+                 status_t(aidl::android::hardware::graphics::common::HdrConversionStrategy));
     MOCK_METHOD2(setAutoLowLatencyMode, status_t(PhysicalDisplayId, bool));
     MOCK_METHOD(status_t, getSupportedContentTypes,
                 (PhysicalDisplayId, std::vector<hal::ContentType>*), (const, override));
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 96ae77f..0982077 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -171,7 +171,7 @@
 
 void DisplayDevice::setPowerMode(hal::PowerMode mode) {
     if (mode == hal::PowerMode::OFF || mode == hal::PowerMode::ON) {
-        if (mStagedBrightness && mBrightness != *mStagedBrightness) {
+        if (mStagedBrightness && mBrightness != mStagedBrightness) {
             getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
             mBrightness = *mStagedBrightness;
         }
@@ -298,7 +298,7 @@
 }
 
 void DisplayDevice::persistBrightness(bool needsComposite) {
-    if (mStagedBrightness && mBrightness != *mStagedBrightness) {
+    if (mStagedBrightness && mBrightness != mStagedBrightness) {
         if (needsComposite) {
             getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
         }
@@ -410,7 +410,8 @@
                            capabilities.getDesiredMinLuminance());
 }
 
-void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate) {
+void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate,
+                                             bool showInMiddle) {
     if (!enable) {
         mRefreshRateOverlay.reset();
         return;
@@ -425,6 +426,10 @@
         features |= RefreshRateOverlay::Features::RenderRate;
     }
 
+    if (showInMiddle) {
+        features |= RefreshRateOverlay::Features::ShowInMiddle;
+    }
+
     const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange();
     mRefreshRateOverlay = std::make_unique<RefreshRateOverlay>(fpsRange, features);
     mRefreshRateOverlay->setLayerStack(getLayerStack());
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index d757673..370bd66 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -237,8 +237,8 @@
     }
 
     // Enables an overlay to be displayed with the current refresh rate
-    void enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate)
-            REQUIRES(kMainThreadContext);
+    void enableRefreshRateOverlay(bool enable, bool showSpinner, bool showRenderRate,
+                                  bool showInMiddle) REQUIRES(kMainThreadContext);
     bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
     bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
     void animateRefreshRateOverlay();
@@ -272,7 +272,7 @@
     std::optional<hardware::graphics::composer::hal::PowerMode> mPowerMode;
 
     std::optional<float> mStagedBrightness;
-    float mBrightness = -1.f;
+    std::optional<float> mBrightness;
 
     // TODO(b/182939859): Remove special cases for primary display.
     const bool mIsPrimary;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
index e372b72..9470552 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -56,6 +56,9 @@
 using AidlDisplayAttribute = aidl::android::hardware::graphics::composer3::DisplayAttribute;
 using AidlDisplayCapability = aidl::android::hardware::graphics::composer3::DisplayCapability;
 using AidlHdrCapabilities = aidl::android::hardware::graphics::composer3::HdrCapabilities;
+using AidlHdrConversionCapability =
+        aidl::android::hardware::graphics::common::HdrConversionCapability;
+using AidlHdrConversionStrategy = aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using AidlOverlayProperties = aidl::android::hardware::graphics::composer3::OverlayProperties;
 using AidlPerFrameMetadata = aidl::android::hardware::graphics::composer3::PerFrameMetadata;
 using AidlPerFrameMetadataKey = aidl::android::hardware::graphics::composer3::PerFrameMetadataKey;
@@ -1396,6 +1399,27 @@
     return Error::NONE;
 }
 
+Error AidlComposer::getHdrConversionCapabilities(
+        std::vector<AidlHdrConversionCapability>* hdrConversionCapabilities) {
+    const auto status =
+            mAidlComposerClient->getHdrConversionCapabilities(hdrConversionCapabilities);
+    if (!status.isOk()) {
+        hdrConversionCapabilities = {};
+        ALOGE("getHdrConversionCapabilities failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
+Error AidlComposer::setHdrConversionStrategy(AidlHdrConversionStrategy hdrConversionStrategy) {
+    const auto status = mAidlComposerClient->setHdrConversionStrategy(hdrConversionStrategy);
+    if (!status.isOk()) {
+        ALOGE("setHdrConversionStrategy failed %s", status.getDescription().c_str());
+        return static_cast<Error>(status.getServiceSpecificError());
+    }
+    return Error::NONE;
+}
+
 Error AidlComposer::getClientTargetProperty(
         Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
     Error error = Error::NONE;
diff --git a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
index 9a7ade7..a5ddf74 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -48,6 +48,8 @@
 namespace android::Hwc2 {
 
 using aidl::android::hardware::graphics::common::DisplayDecorationSupport;
+using aidl::android::hardware::graphics::common::HdrConversionCapability;
+using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::ComposerClientReader;
 using aidl::android::hardware::graphics::composer3::ComposerClientWriter;
 using aidl::android::hardware::graphics::composer3::OverlayProperties;
@@ -235,6 +237,8 @@
                                         AidlTransform* outDisplayOrientation) override;
     void onHotplugConnect(Display) override;
     void onHotplugDisconnect(Display) override;
+    Error getHdrConversionCapabilities(std::vector<HdrConversionCapability>*) override;
+    Error setHdrConversionStrategy(HdrConversionStrategy) override;
 
 private:
     // Many public functions above simply write a command into the command
diff --git a/services/surfaceflinger/DisplayHardware/ComposerHal.h b/services/surfaceflinger/DisplayHardware/ComposerHal.h
index 1c2b8b5..82b677e 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -32,6 +32,8 @@
 #include <utils/StrongPointer.h>
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionCapability.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionStrategy.h>
 #include <aidl/android/hardware/graphics/composer3/Capability.h>
 #include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
 #include <aidl/android/hardware/graphics/composer3/Color.h>
@@ -288,6 +290,10 @@
     virtual Error getOverlaySupport(V3_0::OverlayProperties* outProperties) = 0;
     virtual void onHotplugConnect(Display) = 0;
     virtual void onHotplugDisconnect(Display) = 0;
+    virtual Error getHdrConversionCapabilities(
+            std::vector<::aidl::android::hardware::graphics::common::HdrConversionCapability>*) = 0;
+    virtual Error setHdrConversionStrategy(
+            ::aidl::android::hardware::graphics::common::HdrConversionStrategy) = 0;
 };
 
 } // namespace Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/HWC2.cpp b/services/surfaceflinger/DisplayHardware/HWC2.cpp
index 6738f00..aaf2523 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWC2.cpp
@@ -842,25 +842,24 @@
                                    mHdrMetadata.cta8613.maxFrameAverageLightLevel}});
     }
 
-    Error error = static_cast<Error>(
-            mComposer.setLayerPerFrameMetadata(mDisplay->getId(), mId, perFrameMetadatas));
+    const Error error = static_cast<Error>(
+        mComposer.setLayerPerFrameMetadata(mDisplay->getId(), mId, perFrameMetadatas));
+    if (error != Error::NONE) {
+        return error;
+    }
 
+    std::vector<Hwc2::PerFrameMetadataBlob> perFrameMetadataBlobs;
     if (validTypes & HdrMetadata::HDR10PLUS) {
         if (CC_UNLIKELY(mHdrMetadata.hdr10plus.size() == 0)) {
             return Error::BAD_PARAMETER;
         }
 
-        std::vector<Hwc2::PerFrameMetadataBlob> perFrameMetadataBlobs;
         perFrameMetadataBlobs.push_back(
                 {Hwc2::PerFrameMetadataKey::HDR10_PLUS_SEI, mHdrMetadata.hdr10plus});
-        Error setMetadataBlobsError =
-                static_cast<Error>(mComposer.setLayerPerFrameMetadataBlobs(mDisplay->getId(), mId,
-                                                                           perFrameMetadataBlobs));
-        if (error == Error::NONE) {
-            return setMetadataBlobsError;
-        }
     }
-    return error;
+
+    return static_cast<Error>(
+            mComposer.setLayerPerFrameMetadataBlobs(mDisplay->getId(), mId, perFrameMetadataBlobs));
 }
 
 Error Layer::setDisplayFrame(const Rect& frame)
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 10fde2a..7dde6b4 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -70,6 +70,8 @@
 #define RETURN_IF_HWC_ERROR(error, displayId, ...) \
     RETURN_IF_HWC_ERROR_FOR(__FUNCTION__, error, displayId, __VA_ARGS__)
 
+using aidl::android::hardware::graphics::common::HdrConversionCapability;
+using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::DisplayCapability;
 namespace hal = android::hardware::graphics::composer::hal;
@@ -97,6 +99,7 @@
     loadCapabilities();
     loadLayerMetadataSupport();
     loadOverlayProperties();
+    loadHdrConversionCapabilities();
 
     if (mRegisteredCallback) {
         ALOGW("Callback already registered. Ignored extra registration attempt.");
@@ -787,6 +790,18 @@
     return displayModeId;
 }
 
+std::vector<HdrConversionCapability> HWComposer::getHdrConversionCapabilities() const {
+    return mHdrConversionCapabilities;
+}
+
+status_t HWComposer::setHdrConversionStrategy(HdrConversionStrategy hdrConversionStrategy) {
+    const auto error = mComposer->setHdrConversionStrategy(hdrConversionStrategy);
+    if (error != hal::Error::NONE) {
+        ALOGE("Error in setting HDR conversion strategy %s", to_string(error).c_str());
+    }
+    return NO_ERROR;
+}
+
 status_t HWComposer::getDisplayDecorationSupport(
         PhysicalDisplayId displayId,
         std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
@@ -979,6 +994,14 @@
     mComposer->getOverlaySupport(&mOverlayProperties);
 }
 
+void HWComposer::loadHdrConversionCapabilities() {
+    const auto error = mComposer->getHdrConversionCapabilities(&mHdrConversionCapabilities);
+    if (error != hal::Error::NONE) {
+        ALOGE("Error in fetching HDR conversion capabilities %s", to_string(error).c_str());
+        mHdrConversionCapabilities = {};
+    }
+}
+
 status_t HWComposer::setIdleTimerEnabled(PhysicalDisplayId displayId,
                                          std::chrono::milliseconds timeout) {
     ATRACE_CALL();
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 78d4a68..f6155d2 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -44,6 +44,8 @@
 #include "Hal.h"
 
 #include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionCapability.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionStrategy.h>
 #include <aidl/android/hardware/graphics/composer3/Capability.h>
 #include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
 #include <aidl/android/hardware/graphics/composer3/Composition.h>
@@ -286,6 +288,10 @@
     virtual status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) = 0;
     virtual bool hasDisplayIdleTimerCapability(PhysicalDisplayId) const = 0;
     virtual Hwc2::AidlTransform getPhysicalDisplayOrientation(PhysicalDisplayId) const = 0;
+    virtual std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>
+    getHdrConversionCapabilities() const = 0;
+    virtual status_t setHdrConversionStrategy(
+            aidl::android::hardware::graphics::common::HdrConversionStrategy) = 0;
 };
 
 static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
@@ -437,6 +443,10 @@
     status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) override;
     bool hasDisplayIdleTimerCapability(PhysicalDisplayId) const override;
     Hwc2::AidlTransform getPhysicalDisplayOrientation(PhysicalDisplayId) const override;
+    std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>
+    getHdrConversionCapabilities() const override;
+    status_t setHdrConversionStrategy(
+            aidl::android::hardware::graphics::common::HdrConversionStrategy) override;
 
     // for debugging ----------------------------------------------------------
     void dump(std::string& out) const override;
@@ -490,12 +500,16 @@
     void loadCapabilities();
     void loadLayerMetadataSupport();
     void loadOverlayProperties();
+    void loadHdrConversionCapabilities();
 
     std::unordered_map<HalDisplayId, DisplayData> mDisplayData;
 
     std::unique_ptr<android::Hwc2::Composer> mComposer;
     std::unordered_set<aidl::android::hardware::graphics::composer3::Capability> mCapabilities;
     aidl::android::hardware::graphics::composer3::OverlayProperties mOverlayProperties;
+    std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>
+            mHdrConversionCapabilities = {};
+
     std::unordered_map<std::string, bool> mSupportedLayerGenericMetadata;
     bool mRegisteredCallback = false;
 
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
index c9e1e79..6fdb2d7 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -36,6 +36,8 @@
 #include <algorithm>
 #include <cinttypes>
 
+using aidl::android::hardware::graphics::common::HdrConversionCapability;
+using aidl::android::hardware::graphics::common::HdrConversionStrategy;
 using aidl::android::hardware::graphics::composer3::Capability;
 using aidl::android::hardware::graphics::composer3::ClientTargetPropertyWithBrightness;
 using aidl::android::hardware::graphics::composer3::DimmingStage;
@@ -1348,6 +1350,14 @@
     return Error::UNSUPPORTED;
 }
 
+Error HidlComposer::getHdrConversionCapabilities(std::vector<HdrConversionCapability>*) {
+    return Error::UNSUPPORTED;
+}
+
+Error HidlComposer::setHdrConversionStrategy(HdrConversionStrategy) {
+    return Error::UNSUPPORTED;
+}
+
 Error HidlComposer::getClientTargetProperty(
         Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
     IComposerClient::ClientTargetProperty property;
diff --git a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
index 921add5..8280af2 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.h
@@ -343,6 +343,11 @@
                                         AidlTransform* outDisplayOrientation) override;
     void onHotplugConnect(Display) override;
     void onHotplugDisconnect(Display) override;
+    Error getHdrConversionCapabilities(
+            std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>*)
+            override;
+    Error setHdrConversionStrategy(
+            aidl::android::hardware::graphics::common::HdrConversionStrategy) override;
 
 private:
     class CommandWriter : public CommandWriterBase {
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index cd1ba70..27a099c 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -1175,7 +1175,7 @@
 std::optional<size_t> FrameTimeline::getFirstSignalFenceIndex() const {
     for (size_t i = 0; i < mPendingPresentFences.size(); i++) {
         const auto& [fence, _] = mPendingPresentFences[i];
-        if (fence && fence->isValid() && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
+        if (fence && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING) {
             return i;
         }
     }
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index c2109b3..8629671 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -177,7 +177,7 @@
     }
 
     mStalledTransactions.push_back(transactionId);
-    listener->onTransactionQueueStalled(reason);
+    listener->onTransactionQueueStalled(String8(reason.c_str()));
 }
 
 void TransactionHandler::removeFromStalledTransactions(uint64_t id) {
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index 475ff1b..a06b870 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -29,7 +29,6 @@
 namespace android {
 
 class TestableSurfaceFlinger;
-using gui::IListenerHash;
 namespace surfaceflinger::frontend {
 
 class TransactionHandler {
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 1f159ae..de9ea04 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -2604,12 +2604,9 @@
         return;
     }
     ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64, getDebugName(), framenumber);
-    std::optional<os::ParcelFileDescriptor> fenceFd;
-    if (releaseFence) {
-        fenceFd = os::ParcelFileDescriptor(base::unique_fd(::dup(releaseFence->get())));
-    }
-    listener->onReleaseBuffer({buffer->getId(), framenumber}, fenceFd,
-                              static_cast<int32_t>(currentMaxAcquiredBufferCount));
+    listener->onReleaseBuffer({buffer->getId(), framenumber},
+                              releaseFence ? releaseFence : Fence::NO_FENCE,
+                              currentMaxAcquiredBufferCount);
 }
 
 void Layer::onLayerDisplayed(ftl::SharedFuture<FenceResult> futureFenceResult) {
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index 7aa7e17..0ade467 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -320,7 +320,12 @@
     const auto width = std::min({kMaxWidth, viewport.width, viewport.height});
     const auto height = 2 * width;
     Rect frame((5 * width) >> 4, height >> 5);
-    frame.offsetBy(width >> 5, height >> 4);
+
+    if (!mFeatures.test(Features::ShowInMiddle)) {
+        frame.offsetBy(width >> 5, height >> 4);
+    } else {
+        frame.offsetBy(width >> 1, height >> 4);
+    }
 
     createTransaction(mSurfaceControl->get())
             .setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast<float>(kBufferWidth),
diff --git a/services/surfaceflinger/RefreshRateOverlay.h b/services/surfaceflinger/RefreshRateOverlay.h
index d6f828f..b68a88c 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -54,6 +54,7 @@
     enum class Features {
         Spinner = 1 << 0,
         RenderRate = 1 << 1,
+        ShowInMiddle = 1 << 2,
     };
 
     RefreshRateOverlay(FpsRange, ftl::Flags<Features>);
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.cpp b/services/surfaceflinger/Scheduler/LayerHistory.cpp
index 7c9cedfa..55fa402 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -22,9 +22,9 @@
 
 #include <android-base/stringprintf.h>
 #include <cutils/properties.h>
+#include <gui/TraceUtils.h>
 #include <utils/Log.h>
 #include <utils/Timers.h>
-#include <utils/Trace.h>
 
 #include <algorithm>
 #include <cmath>
@@ -165,6 +165,7 @@
 }
 
 auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary {
+    ATRACE_CALL();
     Summary summary;
 
     std::lock_guard lock(mLock);
@@ -178,6 +179,7 @@
         ALOGV("%s has priority: %d %s focused", info->getName().c_str(), frameRateSelectionPriority,
               layerFocused ? "" : "not");
 
+        ATRACE_FORMAT("%s", info->getName().c_str());
         const auto vote = info->getRefreshRateVote(selector, now);
         // Skip NoVote layer as those don't have any requirements
         if (vote.type == LayerVoteType::NoVote) {
@@ -192,6 +194,8 @@
 
         const float layerArea = transformed.getWidth() * transformed.getHeight();
         float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
+        ATRACE_FORMAT_INSTANT("%s %s (%d%)", ftl::enum_string(vote.type).c_str(),
+                              to_string(vote.fps).c_str(), weight * 100);
         summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps,
                            vote.seamlessness, weight, layerFocused});
 
@@ -204,6 +208,7 @@
 }
 
 void LayerHistory::partitionLayers(nsecs_t now) {
+    ATRACE_CALL();
     const nsecs_t threshold = getActiveLayerThreshold(now);
 
     // iterate over inactive map
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 7247e4b..0142ccd 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -29,6 +29,7 @@
 #include <cutils/compiler.h>
 #include <cutils/trace.h>
 #include <ftl/enum.h>
+#include <gui/TraceUtils.h>
 
 #undef LOG_TAG
 #define LOG_TAG "LayerInfo"
@@ -76,12 +77,43 @@
 
 bool LayerInfo::isFrequent(nsecs_t now) const {
     using fps_approx_ops::operator>=;
-    // If we know nothing about this layer we consider it as frequent as it might be the start
-    // of an animation.
+    // If we know nothing about this layer (e.g. after touch event),
+    // we consider it as frequent as it might be the start of an animation.
     if (mFrameTimes.size() < kFrequentLayerWindowSize) {
         return true;
     }
-    return getFps(now) >= kMinFpsForFrequentLayer;
+
+    // Non-active layers are also infrequent
+    if (mLastUpdatedTime < getActiveLayerThreshold(now)) {
+        return false;
+    }
+
+    // We check whether we can classify this layer as frequent or infrequent:
+    //  - frequent: a layer posted kFrequentLayerWindowSize within
+    //              kMaxPeriodForFrequentLayerNs of each other.
+    // -  infrequent: a layer posted kFrequentLayerWindowSize with longer
+    //                gaps than kFrequentLayerWindowSize.
+    // If we can't determine the layer classification yet, we return the last
+    // classification.
+    bool isFrequent = true;
+    bool isInfrequent = true;
+    const auto n = mFrameTimes.size() - 1;
+    for (size_t i = 0; i < kFrequentLayerWindowSize - 1; i++) {
+        if (mFrameTimes[n - i].queueTime - mFrameTimes[n - i - 1].queueTime <
+            kMaxPeriodForFrequentLayerNs.count()) {
+            isInfrequent = false;
+        } else {
+            isFrequent = false;
+        }
+    }
+
+    if (isFrequent || isInfrequent) {
+        return isFrequent;
+    }
+
+    // If we can't determine whether the layer is frequent or not, we return
+    // the last known classification.
+    return !mLastRefreshRate.infrequent;
 }
 
 Fps LayerInfo::getFps(nsecs_t now) const {
@@ -189,6 +221,7 @@
 
 std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(const RefreshRateSelector& selector,
                                                              nsecs_t now) {
+    ATRACE_CALL();
     static constexpr float MARGIN = 1.0f; // 1Hz
     if (!hasEnoughDataForHeuristic()) {
         ALOGV("Not enough data");
@@ -224,20 +257,23 @@
 
 LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector,
                                                    nsecs_t now) {
+    ATRACE_CALL();
     if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
         ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
         return mLayerVote;
     }
 
     if (isAnimating(now)) {
+        ATRACE_FORMAT_INSTANT("animating");
         ALOGV("%s is animating", mName.c_str());
-        mLastRefreshRate.animatingOrInfrequent = true;
+        mLastRefreshRate.animating = true;
         return {LayerHistory::LayerVoteType::Max, Fps()};
     }
 
     if (!isFrequent(now)) {
+        ATRACE_FORMAT_INSTANT("infrequent");
         ALOGV("%s is infrequent", mName.c_str());
-        mLastRefreshRate.animatingOrInfrequent = true;
+        mLastRefreshRate.infrequent = true;
         // Infrequent layers vote for mininal refresh rate for
         // battery saving purposes and also to prevent b/135718869.
         return {LayerHistory::LayerVoteType::Min, Fps()};
@@ -246,7 +282,7 @@
     // If the layer was previously tagged as animating or infrequent, we clear
     // the history as it is likely the layer just changed its behavior
     // and we should not look at stale data
-    if (mLastRefreshRate.animatingOrInfrequent) {
+    if (mLastRefreshRate.animating || mLastRefreshRate.infrequent) {
         clearHistory(now);
     }
 
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index a5ffbbe..93485be 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -53,7 +53,7 @@
     // Layer is considered frequent if the earliest value in the window of most recent present times
     // is within a threshold. If a layer is infrequent, its average refresh rate is disregarded in
     // favor of a low refresh rate.
-    static constexpr size_t kFrequentLayerWindowSize = 3;
+    static constexpr size_t kFrequentLayerWindowSize = 4;
     static constexpr Fps kMinFpsForFrequentLayer = 10_Hz;
     static constexpr auto kMaxPeriodForFrequentLayerNs =
             std::chrono::nanoseconds(kMinFpsForFrequentLayer.getPeriodNsecs()) + 1ms;
@@ -214,7 +214,10 @@
         Fps reported;
         // Whether the last reported rate for LayerInfo::getRefreshRate()
         // was due to animation or infrequent updates
-        bool animatingOrInfrequent = false;
+        bool animating = false;
+        // Whether the last reported rate for LayerInfo::getRefreshRate()
+        // was due to infrequent updates
+        bool infrequent = false;
     };
 
     // Class to store past calculated refresh rate and determine whether
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
index a05d3df..04c2d41 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -180,7 +180,7 @@
         for (auto divisor = start; divisor <= end; divisor++) {
             const auto fps = mode->getFps() / divisor;
             using fps_approx_ops::operator<;
-            if (fps < kMinSupportedFrameRate) {
+            if (divisor > 1 && fps < kMinSupportedFrameRate) {
                 break;
             }
 
@@ -354,6 +354,7 @@
 
 float RefreshRateSelector::calculateLayerScoreLocked(const LayerRequirement& layer, Fps refreshRate,
                                                      bool isSeamlessSwitch) const {
+    ATRACE_CALL();
     // Slightly prefer seamless switches.
     constexpr float kSeamedSwitchPenalty = 0.95f;
     const float seamlessness = isSeamlessSwitch ? 1.0f : kSeamedSwitchPenalty;
diff --git a/services/surfaceflinger/Scheduler/RefreshRateSelector.h b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
index 14d08f8..4f5842a 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateSelector.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -61,7 +61,7 @@
             std::chrono::nanoseconds(800us).count();
 
     // The lowest Render Frame Rate that will ever be selected
-    static constexpr Fps kMinSupportedFrameRate = 1_Hz;
+    static constexpr Fps kMinSupportedFrameRate = 20_Hz;
 
     class Policy {
         static constexpr int kAllowGroupSwitchingDefault = false;
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 856fda0..74a81b7 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -661,6 +661,7 @@
 
 template <typename S, typename T>
 auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
+    ATRACE_CALL();
     std::vector<display::DisplayModeRequest> modeRequests;
     GlobalSignals consideredSignals;
 
diff --git a/services/surfaceflinger/ScreenCaptureOutput.cpp b/services/surfaceflinger/ScreenCaptureOutput.cpp
index 37b3218..6d195b9 100644
--- a/services/surfaceflinger/ScreenCaptureOutput.cpp
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -53,6 +53,13 @@
     Rect sourceCrop = args.renderArea.getSourceCrop();
     output->setDisplaySize({sourceCrop.getWidth(), sourceCrop.getHeight()});
 
+    {
+        std::string name = args.regionSampling ? "RegionSampling" : "ScreenCaptureOutput";
+        if (auto displayDevice = args.renderArea.getDisplayDevice()) {
+            base::StringAppendF(&name, " for %" PRIu64, displayDevice->getId().value);
+        }
+        output->setName(name);
+    }
     return output;
 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 3dd86aa..2d8b9c1 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -461,6 +461,8 @@
     mRefreshRateOverlaySpinner = property_get_bool("debug.sf.show_refresh_rate_overlay_spinner", 0);
     mRefreshRateOverlayRenderRate =
             property_get_bool("debug.sf.show_refresh_rate_overlay_render_rate", 0);
+    mRefreshRateOverlayShowInMiddle =
+            property_get_bool("debug.sf.show_refresh_rate_overlay_in_middle", 0);
 
     if (!mIsUserBuild && base::GetBoolProperty("debug.sf.enable_transaction_tracing"s, true)) {
         mTransactionTracing.emplace();
@@ -1492,6 +1494,7 @@
         outCombination.dataspaces = std::move(dataspaces);
         outProperties->combinations.emplace_back(outCombination);
     }
+    outProperties->supportMixedColorSpaces = aidlProperties.supportMixedColorSpaces;
     return NO_ERROR;
 }
 
@@ -1537,6 +1540,80 @@
     return future.get();
 }
 
+status_t SurfaceFlinger::getHdrConversionCapabilities(
+        std::vector<gui::HdrConversionCapability>* hdrConversionCapabilities) const {
+    bool hdrOutputConversionSupport;
+    getHdrOutputConversionSupport(&hdrOutputConversionSupport);
+    if (hdrOutputConversionSupport == false) {
+        ALOGE("hdrOutputConversion is not supported by this device.");
+        return INVALID_OPERATION;
+    }
+    const auto aidlConversionCapability = getHwComposer().getHdrConversionCapabilities();
+    for (auto capability : aidlConversionCapability) {
+        gui::HdrConversionCapability tempCapability;
+        tempCapability.sourceType = static_cast<int>(capability.sourceType.hdr);
+        tempCapability.outputType = static_cast<int>(capability.outputType->hdr);
+        tempCapability.addsLatency = capability.addsLatency;
+        hdrConversionCapabilities->push_back(tempCapability);
+    }
+    return NO_ERROR;
+}
+
+status_t SurfaceFlinger::setHdrConversionStrategy(
+        const gui::HdrConversionStrategy& hdrConversionStrategy) {
+    bool hdrOutputConversionSupport;
+    getHdrOutputConversionSupport(&hdrOutputConversionSupport);
+    if (hdrOutputConversionSupport == false) {
+        ALOGE("hdrOutputConversion is not supported by this device.");
+        return INVALID_OPERATION;
+    }
+    auto future = mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) mutable -> status_t {
+        using AidlHdrConversionStrategy =
+                aidl::android::hardware::graphics::common::HdrConversionStrategy;
+        using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag;
+        AidlHdrConversionStrategy aidlConversionStrategy;
+        switch (hdrConversionStrategy.getTag()) {
+            case GuiHdrConversionStrategyTag::passthrough: {
+                aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::passthrough>(
+                        hdrConversionStrategy.get<GuiHdrConversionStrategyTag::passthrough>());
+                return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy);
+            }
+            case GuiHdrConversionStrategyTag::autoAllowedHdrTypes: {
+                auto autoHdrTypes =
+                        hdrConversionStrategy
+                                .get<GuiHdrConversionStrategyTag::autoAllowedHdrTypes>();
+                std::vector<aidl::android::hardware::graphics::common::Hdr> aidlAutoHdrTypes;
+                for (auto type : autoHdrTypes) {
+                    aidlAutoHdrTypes.push_back(
+                            static_cast<aidl::android::hardware::graphics::common::Hdr>(type));
+                }
+                aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::autoAllowedHdrTypes>(
+                        aidlAutoHdrTypes);
+                return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy);
+            }
+            case GuiHdrConversionStrategyTag::forceHdrConversion: {
+                auto forceHdrConversion =
+                        hdrConversionStrategy
+                                .get<GuiHdrConversionStrategyTag::forceHdrConversion>();
+                aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::forceHdrConversion>(
+                        static_cast<aidl::android::hardware::graphics::common::Hdr>(
+                                forceHdrConversion));
+                return getHwComposer().setHdrConversionStrategy(aidlConversionStrategy);
+            }
+        }
+    });
+    return future.get();
+}
+
+status_t SurfaceFlinger::getHdrOutputConversionSupport(bool* outSupport) const {
+    auto future = mScheduler->schedule([this] {
+        return getHwComposer().hasCapability(Capability::HDR_OUTPUT_CONVERSION_CONFIG);
+    });
+
+    *outSupport = future.get();
+    return NO_ERROR;
+}
+
 void SurfaceFlinger::setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on) {
     const char* const whence = __func__;
     static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
@@ -2513,7 +2590,9 @@
     const TimePoint compositeTime =
             TimePoint::fromNs(mCompositionEngine->getLastFrameRefreshTimestamp());
     const Duration presentLatency =
-            mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime);
+            !getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)
+            ? mPresentLatencyTracker.trackPendingFrame(compositeTime, presentFenceTime)
+            : Duration::zero();
 
     const auto& schedule = mScheduler->getVsyncSchedule();
     const TimePoint vsyncDeadline = schedule.vsyncDeadlineAfter(presentTime);
@@ -2888,15 +2967,15 @@
 
         const auto enableFrameRateOverride = [&] {
             using Config = scheduler::RefreshRateSelector::Config;
-            if (!sysprop::enable_frame_rate_override(false)) {
+            if (!sysprop::enable_frame_rate_override(true)) {
                 return Config::FrameRateOverride::Disabled;
             }
 
-            if (sysprop::frame_rate_override_for_native_rates(true)) {
+            if (sysprop::frame_rate_override_for_native_rates(false)) {
                 return Config::FrameRateOverride::AppOverrideNativeRefreshRates;
             }
 
-            if (!sysprop::frame_rate_override_global(false)) {
+            if (!sysprop::frame_rate_override_global(true)) {
                 return Config::FrameRateOverride::AppOverride;
             }
 
@@ -6513,7 +6592,7 @@
             [=, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD(
                     kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
                 ScreenCaptureResults captureResults;
-                std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get();
+                std::shared_ptr<RenderArea> renderArea = renderAreaFuture.get();
                 if (!renderArea) {
                     ALOGW("Skipping screen capture because of invalid render area.");
                     if (captureListener) {
@@ -6525,7 +6604,7 @@
 
                 ftl::SharedFuture<FenceResult> renderFuture;
                 renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
-                    renderFuture = renderScreenImpl(std::move(renderArea), traverseLayers, buffer,
+                    renderFuture = renderScreenImpl(renderArea, traverseLayers, buffer,
                                                     canCaptureBlackoutContent, regionSampling,
                                                     grayscale, captureResults);
                 });
@@ -6553,7 +6632,7 @@
 }
 
 ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
-        std::unique_ptr<RenderArea> renderArea, TraverseLayersFunction traverseLayers,
+        std::shared_ptr<const RenderArea> renderArea, TraverseLayersFunction traverseLayers,
         const std::shared_ptr<renderengine::ExternalTexture>& buffer,
         bool canCaptureBlackoutContent, bool regionSampling, bool grayscale,
         ScreenCaptureResults& captureResults) {
@@ -6992,7 +7071,8 @@
         if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) {
             if (const auto device = getDisplayDeviceLocked(id)) {
                 device->enableRefreshRateOverlay(enable, mRefreshRateOverlaySpinner,
-                                                 mRefreshRateOverlayRenderRate);
+                                                 mRefreshRateOverlayRenderRate,
+                                                 mRefreshRateOverlayShowInMiddle);
             }
         }
     }
@@ -7154,7 +7234,8 @@
                                    layerName, static_cast<uint32_t>(mMaxRenderTargetSize));
         ALOGD("%s", errorMessage.c_str());
         if (bufferData.releaseBufferListener) {
-            bufferData.releaseBufferListener->onTransactionQueueStalled(errorMessage);
+            bufferData.releaseBufferListener->onTransactionQueueStalled(
+                    String8(errorMessage.c_str()));
         }
         return nullptr;
     }
@@ -7172,7 +7253,7 @@
 
             if (bufferData.releaseBufferListener) {
                 bufferData.releaseBufferListener->onTransactionQueueStalled(
-                        "Buffer processing hung due to full buffer cache");
+                        String8("Buffer processing hung due to full buffer cache"));
             }
         }
 
@@ -7578,6 +7659,32 @@
     return binderStatusFromStatusT(status);
 }
 
+binder::Status SurfaceComposerAIDL::getHdrConversionCapabilities(
+        std::vector<gui::HdrConversionCapability>* hdrConversionCapabilities) {
+    status_t status = checkAccessPermission();
+    if (status == OK) {
+        status = mFlinger->getHdrConversionCapabilities(hdrConversionCapabilities);
+    }
+    return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setHdrConversionStrategy(
+        const gui::HdrConversionStrategy& hdrConversionStrategy) {
+    status_t status = checkAccessPermission();
+    if (status == OK) {
+        status = mFlinger->setHdrConversionStrategy(hdrConversionStrategy);
+    }
+    return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::getHdrOutputConversionSupport(bool* outMode) {
+    status_t status = checkAccessPermission();
+    if (status == OK) {
+        status = mFlinger->getHdrOutputConversionSupport(outMode);
+    }
+    return binderStatusFromStatusT(status);
+}
+
 binder::Status SurfaceComposerAIDL::setAutoLowLatencyMode(const sp<IBinder>& display, bool on) {
     status_t status = checkAccessPermission();
     if (status != OK) {
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index 23ac19e..33f0402 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -26,7 +26,6 @@
 #include <android/gui/DisplayStatInfo.h>
 #include <android/gui/DisplayState.h>
 #include <android/gui/ISurfaceComposerClient.h>
-#include <android/gui/ITransactionCompletedListener.h>
 #include <cutils/atomic.h>
 #include <cutils/compiler.h>
 #include <ftl/future.h>
@@ -35,8 +34,8 @@
 #include <gui/CompositorTiming.h>
 #include <gui/FrameTimestamps.h>
 #include <gui/ISurfaceComposer.h>
+#include <gui/ITransactionCompletedListener.h>
 #include <gui/LayerDebugInfo.h>
-
 #include <gui/LayerState.h>
 #include <layerproto/LayerProtoHeader.h>
 #include <math/mat4.h>
@@ -127,9 +126,7 @@
 using gui::CaptureArgs;
 using gui::DisplayCaptureArgs;
 using gui::IRegionSamplingListener;
-using gui::ITransactionCompletedListener;
 using gui::LayerCaptureArgs;
-
 using gui::ScreenCaptureResults;
 
 namespace frametimeline {
@@ -534,6 +531,10 @@
     status_t setBootDisplayMode(const sp<display::DisplayToken>&, DisplayModeId);
     status_t getOverlaySupport(gui::OverlayProperties* outProperties) const;
     status_t clearBootDisplayMode(const sp<IBinder>& displayToken);
+    status_t getHdrConversionCapabilities(
+            std::vector<gui::HdrConversionCapability>* hdrConversionCapaabilities) const;
+    status_t setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy);
+    status_t getHdrOutputConversionSupport(bool* outSupport) const;
     void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on);
     void setGameContentType(const sp<IBinder>& displayToken, bool on);
     void setPowerMode(const sp<IBinder>& displayToken, int mode);
@@ -653,6 +654,8 @@
     bool mRefreshRateOverlaySpinner = false;
     // Show render rate with refresh rate overlay
     bool mRefreshRateOverlayRenderRate = false;
+    // Show render rate overlay offseted to the middle of the screen (e.g. for circular displays)
+    bool mRefreshRateOverlayShowInMiddle = false;
 
     void setDesiredActiveMode(display::DisplayModeRequest&&, bool force = false)
             REQUIRES(mStateLock);
@@ -802,7 +805,7 @@
             const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
             bool grayscale, const sp<IScreenCaptureListener>&);
     ftl::SharedFuture<FenceResult> renderScreenImpl(
-            std::unique_ptr<RenderArea>, TraverseLayersFunction,
+            std::shared_ptr<const RenderArea>, TraverseLayersFunction,
             const std::shared_ptr<renderengine::ExternalTexture>&, bool canCaptureBlackoutContent,
             bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock)
             REQUIRES(kMainThreadContext);
@@ -1432,6 +1435,11 @@
     binder::Status clearBootDisplayMode(const sp<IBinder>& display) override;
     binder::Status getBootDisplayModeSupport(bool* outMode) override;
     binder::Status getOverlaySupport(gui::OverlayProperties* outProperties) override;
+    binder::Status getHdrConversionCapabilities(
+            std::vector<gui::HdrConversionCapability>*) override;
+    binder::Status setHdrConversionStrategy(
+            const gui::HdrConversionStrategy& hdrConversionStrategy) override;
+    binder::Status getHdrOutputConversionSupport(bool* outSupport) override;
     binder::Status setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override;
     binder::Status setGameContentType(const sp<IBinder>& display, bool on) override;
     binder::Status captureDisplay(const DisplayCaptureArgs&,
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index c09bcce..61ff9bc 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -26,27 +26,14 @@
 #include <unordered_set>
 
 #include <android-base/thread_annotations.h>
-#include <android/gui/ITransactionCompletedListener.h>
-
 #include <binder/IBinder.h>
-#include <gui/ListenerStats.h>
-#include <gui/ReleaseCallbackId.h>
-#include <renderengine/RenderEngine.h>
+#include <ftl/future.h>
+#include <gui/ITransactionCompletedListener.h>
 #include <ui/Fence.h>
 #include <ui/FenceResult.h>
 
 namespace android {
 
-using gui::CallbackId;
-using gui::FrameEventHistoryStats;
-using gui::IListenerHash;
-using gui::ITransactionCompletedListener;
-using gui::JankData;
-using gui::ListenerCallbacks;
-using gui::ListenerStats;
-using gui::ReleaseCallbackId;
-using gui::TransactionStats;
-
 class CallbackHandle : public RefBase {
 public:
     CallbackHandle(const sp<IBinder>& transactionListener, const std::vector<CallbackId>& ids,
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
index 225ad16..ac5e927 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
@@ -96,5 +96,23 @@
     EXPECT_EQ(std::nullopt, displayDevice->getCompositionDisplay()->getState().displayBrightness);
 }
 
+TEST_F(SetDisplayBrightnessTest, firstDisplayBrightnessWithComposite) {
+    ftl::FakeGuard guard(kMainThreadContext);
+    sp<DisplayDevice> displayDevice = getDisplayDevice();
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+
+    constexpr float kDisplayBrightness = -1.0f;
+    displayDevice->stageBrightness(kDisplayBrightness);
+
+    EXPECT_EQ(-1.0f, displayDevice->getStagedBrightness());
+
+    displayDevice->persistBrightness(true);
+
+    EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+    EXPECT_EQ(kDisplayBrightness,
+              displayDevice->getCompositionDisplay()->getState().displayBrightness);
+}
+
 } // namespace
 } // namespace android
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 8f89a8c..afbc57a 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -147,6 +147,7 @@
                             }),
                             Return(hardware::graphics::composer::V2_4::Error::NONE)));
     EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::NONE));
+    EXPECT_CALL(*mHal, getHdrConversionCapabilities(_)).WillOnce(Return(HalError::NONE));
 
     EXPECT_CALL(*mHal, registerCallback(_));
 
@@ -165,6 +166,7 @@
     EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_))
             .WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED));
     EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::UNSUPPORTED));
+    EXPECT_CALL(*mHal, getHdrConversionCapabilities(_)).WillOnce(Return(HalError::UNSUPPORTED));
     EXPECT_CALL(*mHal, registerCallback(_));
 
     mHwc.setCallback(mCallback);
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 979924a..8397f8d 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -51,8 +51,6 @@
     static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::kMaxPeriodForFrequentLayerNs;
     static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfo::kFrequentLayerWindowSize;
     static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfo::HISTORY_DURATION;
-    static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
-            LayerInfo::RefreshRateHistory::HISTORY_DURATION;
 
     static constexpr Fps LO_FPS = 30_Hz;
     static constexpr auto LO_FPS_PERIOD = LO_FPS.getPeriodNsecs();
@@ -607,7 +605,7 @@
     // advance the time for the previous frame to be inactive
     time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
 
-    // Now event if we post a quick few frame we should stay infrequent
+    // Now even if we post a quick few frame we should stay infrequent
     for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
         history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
         time += HI_FPS_PERIOD;
@@ -706,6 +704,88 @@
     EXPECT_EQ(1, animatingLayerCount(time));
 }
 
+TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) {
+    auto layer = createLayer();
+
+    EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+    EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+    nsecs_t time = systemTime();
+
+    EXPECT_EQ(1, layerCount());
+    EXPECT_EQ(0, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // Fill up the window with frequent updates
+    for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
+        history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+        time += (60_Hz).getPeriodNsecs();
+
+        EXPECT_EQ(1, layerCount());
+        ASSERT_EQ(1, summarizeLayerHistory(time).size());
+        EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+        EXPECT_EQ(1, activeLayerCount());
+        EXPECT_EQ(1, frequentLayerCount(time));
+    }
+
+    // posting a buffer after long inactivity should retain the layer as active
+    time += std::chrono::nanoseconds(3s).count();
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting more infrequent buffer should make the layer infrequent
+    time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting another buffer should keep the layer infrequent
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(0, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting more buffers would mean starting of an animation, so making the layer frequent
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting a buffer after long inactivity should retain the layer as active
+    time += std::chrono::nanoseconds(3s).count();
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+
+    // posting another buffer should keep the layer frequent
+    time += (60_Hz).getPeriodNsecs();
+    history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+    ASSERT_EQ(1, summarizeLayerHistory(time).size());
+    EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+    EXPECT_EQ(1, activeLayerCount());
+    EXPECT_EQ(1, frequentLayerCount(time));
+    EXPECT_EQ(0, animatingLayerCount(time));
+}
+
 TEST_F(LayerHistoryTest, getFramerate) {
     auto layer = createLayer();
 
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
index a78f42c..06f45f9 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -141,12 +141,6 @@
     RefreshRateSelectorTest();
     ~RefreshRateSelectorTest();
 
-    // Represents the number of refresh rates possible
-    // from 1_Hz to 90_hz, including fractional rates.
-    static constexpr size_t kTotalRefreshRates120 = 120;
-    // Represents the number of refresh rates possible
-    // from 1_Hz to 120_hz, including fractional rates.
-    static constexpr size_t kTotalRefreshRates216 = 216;
     static constexpr DisplayModeId kModeId60{0};
     static constexpr DisplayModeId kModeId90{1};
     static constexpr DisplayModeId kModeId72{2};
@@ -159,6 +153,9 @@
     static constexpr DisplayModeId kModeId30Frac{9};
     static constexpr DisplayModeId kModeId60Frac{10};
     static constexpr DisplayModeId kModeId35{11};
+    static constexpr DisplayModeId kModeId1{12};
+    static constexpr DisplayModeId kModeId5{13};
+    static constexpr DisplayModeId kModeId10{14};
 
     static inline const ftl::NonNull<DisplayModePtr> kMode60 =
             ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz));
@@ -196,6 +193,12 @@
             ftl::as_non_null(createDisplayMode(kModeId24, 24_Hz));
     static inline const ftl::NonNull<DisplayModePtr> kMode24Frac =
             ftl::as_non_null(createDisplayMode(kModeId24Frac, 23.976_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode1 =
+            ftl::as_non_null(createDisplayMode(kModeId1, 1_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode5 =
+            ftl::as_non_null(createDisplayMode(kModeId5, 5_Hz));
+    static inline const ftl::NonNull<DisplayModePtr> kMode10 =
+            ftl::as_non_null(createDisplayMode(kModeId10, 10_Hz));
 
     // Test configurations.
     static inline const DisplayModes kModes_60 = makeModes(kMode60);
@@ -218,6 +221,7 @@
     static inline const DisplayModes kModes_25_30_50_60 =
             makeModes(kMode60, kMode90, kMode72_G1, kMode120_G1, kMode30_G1, kMode25_G1, kMode50);
     static inline const DisplayModes kModes_60_120 = makeModes(kMode60, kMode120);
+    static inline const DisplayModes kModes_1_5_10 = makeModes(kMode1, kMode5, kMode10);
 
     // This is a typical TV configuration.
     static inline const DisplayModes kModes_24_25_30_50_60_Frac =
@@ -230,12 +234,6 @@
         config.enableFrameRateOverride = GetParam();
         return TestableRefreshRateSelector(modes, activeModeId, config);
     }
-
-    // TODO(b/262783137) Test the complete range of refresh rates
-    struct FpsBeginEnd {
-        std::vector<std::pair<Fps, Fps>> begin;
-        std::vector<std::pair<Fps, Fps>> end;
-    };
 };
 
 RefreshRateSelectorTest::RefreshRateSelectorTest() {
@@ -1131,39 +1129,24 @@
     const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(),
                                                         RefreshRateOrder::Descending);
 
-    const auto expectedRefreshRates = []() -> FpsBeginEnd {
+    const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
         switch (GetParam()) {
             case Config::FrameRateOverride::Disabled:
             case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
             case Config::FrameRateOverride::AppOverride:
-                return {.begin{{90_Hz, 90_Hz}, {60_Hz, 60_Hz}, {30_Hz, 30_Hz}}};
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}, {30_Hz, kMode30}};
             case Config::FrameRateOverride::Enabled:
-                return {.begin{{90_Hz, 90_Hz}, {60_Hz, 60_Hz}, {45_Hz, 90_Hz}, {30_Hz, 30_Hz}},
-                        .end{{1.022_Hz, 90_Hz},
-                             {1.016_Hz, 60_Hz},
-                             {1.011_Hz, 90_Hz},
-                             {1_Hz, 30_Hz}}};
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}};
         }
     }();
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
-    } else {
-        ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size());
-    }
-
-    for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps());
-    }
-
-    for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) {
-        const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i;
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate,
-                  refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps());
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 }
 
@@ -1175,39 +1158,24 @@
     const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(),
                                                         RefreshRateOrder::Ascending);
 
-    const auto expectedRefreshRates = []() -> FpsBeginEnd {
+    const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
         switch (GetParam()) {
             case Config::FrameRateOverride::Disabled:
             case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
             case Config::FrameRateOverride::AppOverride:
-                return {.begin{{30_Hz, 30_Hz}, {60_Hz, 60_Hz}, {90_Hz, 90_Hz}}};
+                return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
             case Config::FrameRateOverride::Enabled:
-                return {.begin{{1_Hz, 30_Hz},
-                               {1.011_Hz, 90_Hz},
-                               {1.016_Hz, 60_Hz},
-                               {1.022_Hz, 90_Hz}},
-                        .end{{30_Hz, 30_Hz}, {45_Hz, 90_Hz}, {60_Hz, 60_Hz}, {90_Hz, 90_Hz}}};
+                return {{30_Hz, kMode30}, {45_Hz, kMode90}, {60_Hz, kMode60}, {90_Hz, kMode90}};
         }
     }();
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
-    } else {
-        ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size());
-    }
-
-    for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps());
-    }
-
-    for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) {
-        const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i;
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate,
-                  refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps());
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 }
 
@@ -1281,68 +1249,39 @@
     auto [refreshRates, signals] = selector.getRankedFrameRates({}, {});
     EXPECT_FALSE(signals.powerOnImminent);
 
-    auto expectedRefreshRates = []() -> FpsBeginEnd {
+    auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
         switch (GetParam()) {
             case Config::FrameRateOverride::Disabled:
             case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
             case Config::FrameRateOverride::AppOverride:
-                return {.begin{{90_Hz, 90_Hz}, {60_Hz, 60_Hz}}};
+                return {{90_Hz, kMode90}, {60_Hz, kMode60}};
             case Config::FrameRateOverride::Enabled:
-                return {.begin{{90_Hz, 90_Hz},
-                               {60_Hz, 60_Hz},
-                               {45_Hz, 90_Hz},
-                               {30_Hz, 60_Hz},
-                               {22.5_Hz, 90_Hz},
-                               {20_Hz, 60_Hz}},
-                        .end{{1.022_Hz, 90_Hz},
-                             {1.016_Hz, 60_Hz},
-                             {1.011_Hz, 90_Hz},
-                             {1_Hz, 60_Hz}}};
+                return {{90_Hz, kMode90}, {60_Hz, kMode60},   {45_Hz, kMode90},
+                        {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
         }
     }();
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
-    } else {
-        ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size());
-    }
-
-    for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps());
-    }
-
-    for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) {
-        const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i;
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate,
-                  refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps());
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     std::tie(refreshRates, signals) =
             selector.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true});
     EXPECT_TRUE(signals.powerOnImminent);
 
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
-    } else {
-        ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size());
-    }
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
-    for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps());
-    }
-
-    for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) {
-        const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i;
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate,
-                  refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps());
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     std::vector<LayerRequirement> layers = {{.weight = 1.f}};
@@ -1355,68 +1294,39 @@
             selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true});
     EXPECT_TRUE(signals.powerOnImminent);
 
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
-    } else {
-        ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size());
-    }
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
-    for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps());
-    }
-
-    for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) {
-        const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i;
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate,
-                  refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps());
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     std::tie(refreshRates, signals) =
             selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false});
     EXPECT_FALSE(signals.powerOnImminent);
 
-    expectedRefreshRates = []() -> FpsBeginEnd {
+    expectedRefreshRates = []() -> std::vector<FrameRateMode> {
         switch (GetParam()) {
             case Config::FrameRateOverride::Disabled:
             case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
             case Config::FrameRateOverride::AppOverride:
-                return {.begin{{60_Hz, 60_Hz}, {90_Hz, 90_Hz}}};
+                return {{60_Hz, kMode60}, {90_Hz, kMode90}};
             case Config::FrameRateOverride::Enabled:
-                return {.begin{{60_Hz, 60_Hz},
-                               {90_Hz, 90_Hz},
-                               {45_Hz, 90_Hz},
-                               {30_Hz, 60_Hz},
-                               {22.5_Hz, 90_Hz},
-                               {20_Hz, 60_Hz}},
-                        .end{{1.034_Hz, 60_Hz},
-                             {1.046_Hz, 90_Hz},
-                             {1.052_Hz, 60_Hz},
-                             {1.058_Hz, 90_Hz}}};
+                return {{60_Hz, kMode60}, {90_Hz, kMode90},   {45_Hz, kMode90},
+                        {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
         }
     }();
+    ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
 
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates120, refreshRates.size());
-    } else {
-        ASSERT_EQ(expectedRefreshRates.begin.size(), refreshRates.size());
-    }
-
-    for (size_t i = 0; i < expectedRefreshRates.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.begin[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[i].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate, refreshRates[i].frameRateMode.modePtr->getFps());
-    }
-
-    for (size_t i = 0; i < expectedRefreshRates.end.size(); i++) {
-        const size_t refreshRateIndex = refreshRates.size() - expectedRefreshRates.end.size() + i;
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRefreshRates.end[i];
-        EXPECT_EQ(expectedRenderRate, refreshRates[refreshRateIndex].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate,
-                  refreshRates[refreshRateIndex].frameRateMode.modePtr->getFps());
+    for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+        EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+                << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+                << expectedRefreshRates[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+                << refreshRates[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 }
 
@@ -1645,55 +1555,31 @@
     lr5.name = "30Hz";
     lr5.focused = true;
 
-    auto expectedRanking = []() -> FpsBeginEnd {
+    auto expectedRanking = []() -> std::vector<FrameRateMode> {
         switch (GetParam()) {
             case Config::FrameRateOverride::Disabled:
             case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
             case Config::FrameRateOverride::AppOverride:
-                return {.begin{{120_Hz, 120_Hz},
-                               {90_Hz, 90_Hz},
-                               {72_Hz, 72_Hz},
-                               {60_Hz, 60_Hz},
-                               {30_Hz, 30_Hz}}};
+                return {{120_Hz, kMode120},
+                        {90_Hz, kMode90},
+                        {72_Hz, kMode72},
+                        {60_Hz, kMode60},
+                        {30_Hz, kMode30}};
             case Config::FrameRateOverride::Enabled:
-                return {.begin{{120_Hz, 120_Hz},
-                               {90_Hz, 90_Hz},
-                               {72_Hz, 72_Hz},
-                               {60_Hz, 60_Hz},
-                               {45_Hz, 90_Hz},
-                               {40_Hz, 120_Hz},
-                               {36_Hz, 72_Hz},
-                               {30_Hz, 30_Hz}},
-                        .end{{1.028_Hz, 72_Hz},
-                             {1.0256_Hz, 120_Hz},
-                             {1.022_Hz, 90_Hz},
-                             {1.016_Hz, 60_Hz},
-                             {1.014_Hz, 72_Hz},
-                             {1.011_Hz, 90_Hz},
-                             {1.008_Hz, 120_Hz},
-                             {1_Hz, 30_Hz}}};
+                return {{120_Hz, kMode120}, {90_Hz, kMode90},  {72_Hz, kMode72}, {60_Hz, kMode60},
+                        {45_Hz, kMode90},   {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
         }
     }();
 
     auto actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates216, actualRanking.size());
-    } else {
-        ASSERT_EQ(expectedRanking.begin.size(), actualRanking.size());
-    }
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
 
-    for (size_t i = 0; i < expectedRanking.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.begin[i];
-        EXPECT_EQ(expectedRenderRate, actualRanking[i].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate, actualRanking[i].frameRateMode.modePtr->getFps());
-    }
-
-    for (size_t i = 0; i < expectedRanking.end.size(); i++) {
-        const size_t refreshRateIndex = actualRanking.size() - expectedRanking.end.size() + i;
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.end[i];
-        EXPECT_EQ(expectedRenderRate, actualRanking[refreshRateIndex].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate,
-                  actualRanking[refreshRateIndex].frameRateMode.modePtr->getFps());
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     lr1.vote = LayerVoteType::Max;
@@ -1711,55 +1597,31 @@
     lr5.desiredRefreshRate = 120_Hz;
     lr5.name = "120Hz";
 
-    expectedRanking = []() -> FpsBeginEnd {
+    expectedRanking = []() -> std::vector<FrameRateMode> {
         switch (GetParam()) {
             case Config::FrameRateOverride::Disabled:
             case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
             case Config::FrameRateOverride::AppOverride:
-                return {.begin{{120_Hz, 120_Hz},
-                               {90_Hz, 90_Hz},
-                               {72_Hz, 72_Hz},
-                               {60_Hz, 60_Hz},
-                               {30_Hz, 30_Hz}}};
+                return {{120_Hz, kMode120},
+                        {90_Hz, kMode90},
+                        {72_Hz, kMode72},
+                        {60_Hz, kMode60},
+                        {30_Hz, kMode30}};
             case Config::FrameRateOverride::Enabled:
-                return {.begin{{120_Hz, 120_Hz},
-                               {90_Hz, 90_Hz},
-                               {72_Hz, 72_Hz},
-                               {60_Hz, 60_Hz},
-                               {45_Hz, 90_Hz},
-                               {40_Hz, 120_Hz},
-                               {36_Hz, 72_Hz},
-                               {30_Hz, 30_Hz}},
-                        .end{{1.028_Hz, 72_Hz},
-                             {1.0256_Hz, 120_Hz},
-                             {1.022_Hz, 90_Hz},
-                             {1.016_Hz, 60_Hz},
-                             {1.014_Hz, 72_Hz},
-                             {1.011_Hz, 90_Hz},
-                             {1.008_Hz, 120_Hz},
-                             {1_Hz, 30_Hz}}};
+                return {{120_Hz, kMode120}, {90_Hz, kMode90},  {72_Hz, kMode72}, {60_Hz, kMode60},
+                        {45_Hz, kMode90},   {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
         }
     }();
     actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
 
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates216, actualRanking.size());
-    } else {
-        ASSERT_EQ(expectedRanking.begin.size(), actualRanking.size());
-    }
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
 
-    for (size_t i = 0; i < expectedRanking.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.begin[i];
-        EXPECT_EQ(expectedRenderRate, actualRanking[i].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate, actualRanking[i].frameRateMode.modePtr->getFps());
-    }
-
-    for (size_t i = 0; i < expectedRanking.end.size(); i++) {
-        const size_t refreshRateIndex = actualRanking.size() - expectedRanking.end.size() + i;
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.end[i];
-        EXPECT_EQ(expectedRenderRate, actualRanking[refreshRateIndex].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate,
-                  actualRanking[refreshRateIndex].frameRateMode.modePtr->getFps());
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     lr1.vote = LayerVoteType::Heuristic;
@@ -1775,55 +1637,31 @@
     lr5.desiredRefreshRate = 72_Hz;
     lr5.name = "72Hz";
 
-    expectedRanking = []() -> FpsBeginEnd {
+    expectedRanking = []() -> std::vector<FrameRateMode> {
         switch (GetParam()) {
             case Config::FrameRateOverride::Disabled:
             case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
             case Config::FrameRateOverride::AppOverride:
-                return {.begin{{30_Hz, 30_Hz},
-                               {60_Hz, 60_Hz},
-                               {90_Hz, 90_Hz},
-                               {120_Hz, 120_Hz},
-                               {72_Hz, 72_Hz}}};
+                return {{30_Hz, kMode30},
+                        {60_Hz, kMode60},
+                        {90_Hz, kMode90},
+                        {120_Hz, kMode120},
+                        {72_Hz, kMode72}};
             case Config::FrameRateOverride::Enabled:
-                return {.begin{{30_Hz, 30_Hz},
-                               {60_Hz, 60_Hz},
-                               {90_Hz, 90_Hz},
-                               {120_Hz, 120_Hz},
-                               {45_Hz, 90_Hz},
-                               {40_Hz, 120_Hz},
-                               {72_Hz, 72_Hz},
-                               {36_Hz, 72_Hz}},
-                        .end{{1.074_Hz, 72_Hz},
-                             {1.081_Hz, 120_Hz},
-                             {1.084_Hz, 90_Hz},
-                             {1.00_Hz, 30_Hz},
-                             {1.008_Hz, 120_Hz},
-                             {1.011_Hz, 90_Hz},
-                             {1.014_Hz, 72_Hz},
-                             {1.016_Hz, 60_Hz}}};
+                return {{30_Hz, kMode30}, {60_Hz, kMode60},  {90_Hz, kMode90}, {120_Hz, kMode120},
+                        {45_Hz, kMode90}, {40_Hz, kMode120}, {72_Hz, kMode72}, {36_Hz, kMode72}};
         }
     }();
     actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
 
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates216, actualRanking.size());
-    } else {
-        ASSERT_EQ(expectedRanking.begin.size(), actualRanking.size());
-    }
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
 
-    for (size_t i = 0; i < expectedRanking.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.begin[i];
-        EXPECT_EQ(expectedRenderRate, actualRanking[i].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate, actualRanking[i].frameRateMode.modePtr->getFps());
-    }
-
-    for (size_t i = 0; i < expectedRanking.end.size(); i++) {
-        const size_t refreshRateIndex = actualRanking.size() - expectedRanking.end.size() + i;
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.end[i];
-        EXPECT_EQ(expectedRenderRate, actualRanking[refreshRateIndex].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate,
-                  actualRanking[refreshRateIndex].frameRateMode.modePtr->getFps());
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 
     lr1.desiredRefreshRate = 120_Hz;
@@ -1842,48 +1680,31 @@
     lr5.desiredRefreshRate = 120_Hz;
     lr5.name = "120Hz-2";
 
-    expectedRanking = []() -> FpsBeginEnd {
+    expectedRanking = []() -> std::vector<FrameRateMode> {
         switch (GetParam()) {
             case Config::FrameRateOverride::Disabled:
             case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
             case Config::FrameRateOverride::AppOverride:
-                return {.begin{{90_Hz, 90_Hz},
-                               {60_Hz, 60_Hz},
-                               {120_Hz, 120_Hz},
-                               {72_Hz, 72_Hz},
-                               {30_Hz, 30_Hz}}};
+                return {{90_Hz, kMode90},
+                        {60_Hz, kMode60},
+                        {120_Hz, kMode120},
+                        {72_Hz, kMode72},
+                        {30_Hz, kMode30}};
             case Config::FrameRateOverride::Enabled:
-                return {.begin{{90_Hz, 90_Hz},
-                               {60_Hz, 60_Hz},
-                               {120_Hz, 120_Hz},
-                               {72_Hz, 72_Hz},
-                               {45_Hz, 90_Hz},
-                               {40_Hz, 120_Hz},
-                               {36_Hz, 72_Hz},
-                               {30_Hz, 30_Hz}},
-                        .end{}};
+                return {{90_Hz, kMode90}, {60_Hz, kMode60},  {120_Hz, kMode120}, {72_Hz, kMode72},
+                        {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72},   {30_Hz, kMode30}};
         }
     }();
     actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
 
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates216, actualRanking.size());
-    } else {
-        ASSERT_EQ(expectedRanking.begin.size(), actualRanking.size());
-    }
+    ASSERT_EQ(expectedRanking.size(), actualRanking.size());
 
-    for (size_t i = 0; i < expectedRanking.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.begin[i];
-        EXPECT_EQ(expectedRenderRate, actualRanking[i].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate, actualRanking[i].frameRateMode.modePtr->getFps());
-    }
-
-    for (size_t i = 0; i < expectedRanking.end.size(); i++) {
-        const size_t refreshRateIndex = actualRanking.size() - expectedRanking.end.size() + i;
-        const auto [expectedRenderRate, expectedRefreshRate] = expectedRanking.end[i];
-        EXPECT_EQ(expectedRenderRate, actualRanking[refreshRateIndex].frameRateMode.fps);
-        EXPECT_EQ(expectedRefreshRate,
-                  actualRanking[refreshRateIndex].frameRateMode.modePtr->getFps());
+    for (size_t i = 0; i < expectedRanking.size(); ++i) {
+        EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+                << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+                << expectedRanking[i].modePtr->getFps().getIntValue() << ")"
+                << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+                << actualRanking[i].frameRateMode.modePtr->getFps().getIntValue() << ")";
     }
 }
 
@@ -2506,8 +2327,11 @@
 }
 
 // b/190578904
-TEST_P(RefreshRateSelectorTest,
-       getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_Heuristic) {
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates) {
+    if (g_noSlowTests) {
+        GTEST_SKIP();
+    }
+
     const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
     constexpr int kMaxRefreshRate = 240;
 
@@ -2532,89 +2356,8 @@
     for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
         const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
         testRefreshRate(refreshRate, LayerVoteType::Heuristic);
-    }
-}
-TEST_P(RefreshRateSelectorTest,
-       getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitDefault) {
-    const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
-    constexpr int kMaxRefreshRate = 240;
-
-    DisplayModes displayModes;
-    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
-        const DisplayModeId modeId(fps);
-        displayModes.try_emplace(modeId,
-                                 createDisplayMode(modeId,
-                                                   Fps::fromValue(static_cast<float>(fps))));
-    }
-
-    const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate));
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
-        layers[0].desiredRefreshRate = fps;
-        layers[0].vote = vote;
-        EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
-                << "Failed for " << ftl::enum_string(vote);
-    };
-
-    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
-        const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
         testRefreshRate(refreshRate, LayerVoteType::ExplicitDefault);
-    }
-}
-TEST_P(RefreshRateSelectorTest,
-       getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitExactOrMultiple) {
-    const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
-    constexpr int kMaxRefreshRate = 240;
-
-    DisplayModes displayModes;
-    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
-        const DisplayModeId modeId(fps);
-        displayModes.try_emplace(modeId,
-                                 createDisplayMode(modeId,
-                                                   Fps::fromValue(static_cast<float>(fps))));
-    }
-
-    const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate));
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
-        layers[0].desiredRefreshRate = fps;
-        layers[0].vote = vote;
-        EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
-                << "Failed for " << ftl::enum_string(vote);
-    };
-
-    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
-        const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
         testRefreshRate(refreshRate, LayerVoteType::ExplicitExactOrMultiple);
-    }
-}
-TEST_P(RefreshRateSelectorTest,
-       getBestFrameRateMode_withCloseRefreshRates_LayerVoteType_ExplicitExact) {
-    const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
-    constexpr int kMaxRefreshRate = 240;
-
-    DisplayModes displayModes;
-    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
-        const DisplayModeId modeId(fps);
-        displayModes.try_emplace(modeId,
-                                 createDisplayMode(modeId,
-                                                   Fps::fromValue(static_cast<float>(fps))));
-    }
-
-    const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate));
-
-    std::vector<LayerRequirement> layers = {{.weight = 1.f}};
-    const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
-        layers[0].desiredRefreshRate = fps;
-        layers[0].vote = vote;
-        EXPECT_EQ(fps.getIntValue(), selector.getBestFrameRateMode(layers)->getFps().getIntValue())
-                << "Failed for " << ftl::enum_string(vote);
-    };
-
-    for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
-        const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
         testRefreshRate(refreshRate, LayerVoteType::ExplicitExact);
     }
 }
@@ -3063,57 +2806,30 @@
     auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
 
     // [renderRate, refreshRate]
-    const auto expected = []() -> FpsBeginEnd {
+    const auto expected = []() -> std::vector<std::pair<Fps, Fps>> {
         switch (GetParam()) {
             case Config::FrameRateOverride::Disabled:
             case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
             case Config::FrameRateOverride::AppOverride:
-                return {.begin{{30_Hz, 30_Hz},
-                               {60_Hz, 60_Hz},
-                               {72_Hz, 72_Hz},
-                               {90_Hz, 90_Hz},
-                               {120_Hz, 120_Hz}}};
+                return {{30_Hz, 30_Hz},
+                        {60_Hz, 60_Hz},
+                        {72_Hz, 72_Hz},
+                        {90_Hz, 90_Hz},
+                        {120_Hz, 120_Hz}};
             case Config::FrameRateOverride::Enabled:
-                return {.begin{{1_Hz, 30_Hz},
-                               {1.008_Hz, 120_Hz},
-                               {1.011_Hz, 90_Hz},
-                               {1.014_Hz, 72_Hz},
-                               {1.016_Hz, 60_Hz},
-                               {1.022_Hz, 90_Hz},
-                               {1.0256_Hz, 120_Hz},
-                               {1.028_Hz, 72_Hz}},
-                        .end{
-                                {30_Hz, 30_Hz},
-                                {36_Hz, 72_Hz},
-                                {40_Hz, 120_Hz},
-                                {45_Hz, 90_Hz},
-                                {60_Hz, 60_Hz},
-                                {72_Hz, 72_Hz},
-                                {90_Hz, 90_Hz},
-                                {120_Hz, 120_Hz},
-                        }};
+                return {{30_Hz, 30_Hz}, {36_Hz, 72_Hz}, {40_Hz, 120_Hz}, {45_Hz, 90_Hz},
+                        {60_Hz, 60_Hz}, {72_Hz, 72_Hz}, {90_Hz, 90_Hz},  {120_Hz, 120_Hz}};
         }
     }();
 
     const auto& primaryRefreshRates = selector.getPrimaryFrameRates();
-    if (GetParam() == Config::FrameRateOverride::Enabled) {
-        ASSERT_EQ(kTotalRefreshRates216, primaryRefreshRates.size());
-    } else {
-        ASSERT_EQ(expected.begin.size(), primaryRefreshRates.size());
-    }
+    ASSERT_EQ(expected.size(), primaryRefreshRates.size());
 
-    for (size_t i = 0; i < expected.begin.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expected.begin[i];
+    for (size_t i = 0; i < expected.size(); i++) {
+        const auto [expectedRenderRate, expectedRefreshRate] = expected[i];
         EXPECT_EQ(expectedRenderRate, primaryRefreshRates[i].fps);
         EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[i].modePtr->getFps());
     }
-
-    for (size_t i = 0; i < expected.end.size(); i++) {
-        const auto [expectedRenderRate, expectedRefreshRate] = expected.end[i];
-        const size_t refreshRateIndex = primaryRefreshRates.size() - expected.end.size() + i;
-        EXPECT_EQ(expectedRenderRate, primaryRefreshRates[refreshRateIndex].fps);
-        EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[refreshRateIndex].modePtr->getFps());
-    }
 }
 
 TEST_P(RefreshRateSelectorTest, refreshRateIsCappedWithRenderFrameRate) {
@@ -3250,5 +2966,12 @@
     EXPECT_EQ(kMode60, selector.getMinRefreshRateByPolicy());
 }
 
+TEST_P(RefreshRateSelectorTest, SupportsLowPhysicalRefreshRates) {
+    auto selector = createSelector(kModes_1_5_10, kModeId10);
+
+    EXPECT_EQ(kMode10, selector.getMaxRefreshRateByPolicy());
+    EXPECT_EQ(kMode1, selector.getMinRefreshRateByPolicy());
+}
+
 } // namespace
 } // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 2117084..113c518 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -401,7 +401,7 @@
         return mFlinger->setPowerModeInternal(display, mode);
     }
 
-    auto renderScreenImpl(std::unique_ptr<RenderArea> renderArea,
+    auto renderScreenImpl(std::shared_ptr<const RenderArea> renderArea,
                           SurfaceFlinger::TraverseLayersFunction traverseLayers,
                           const std::shared_ptr<renderengine::ExternalTexture>& buffer,
                           bool forSystem, bool regionSampling) {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index 2f16b7b..5e29fe7 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -145,6 +145,11 @@
     MOCK_METHOD2(setBootDisplayConfig, Error(Display, Config));
     MOCK_METHOD1(clearBootDisplayConfig, Error(Display));
     MOCK_METHOD2(getPreferredBootDisplayConfig, Error(Display, Config*));
+    MOCK_METHOD1(getHdrConversionCapabilities,
+                 Error(std::vector<
+                         aidl::android::hardware::graphics::common::HdrConversionCapability>*));
+    MOCK_METHOD1(setHdrConversionStrategy,
+                 Error(aidl::android::hardware::graphics::common::HdrConversionStrategy));
     MOCK_METHOD2(getSupportedContentTypes,
                  V2_4::Error(Display, std::vector<IComposerClient::ContentType>*));
     MOCK_METHOD2(setContentType, V2_4::Error(Display, IComposerClient::ContentType));