Merge "SF: add render frame rate dimension to RefreshRateSelector"
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 07d0a65..4b07608 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -966,7 +966,15 @@
     }
 }
 
+void Parcel::setEnforceNoDataAvail(bool enforceNoDataAvail) {
+    mEnforceNoDataAvail = enforceNoDataAvail;
+}
+
 binder::Status Parcel::enforceNoDataAvail() const {
+    if (!mEnforceNoDataAvail) {
+        return binder::Status::ok();
+    }
+
     const auto n = dataAvail();
     if (n == 0) {
         return binder::Status::ok();
@@ -3077,6 +3085,7 @@
     mAllowFds = true;
     mDeallocZero = false;
     mOwner = nullptr;
+    mEnforceNoDataAvail = true;
 }
 
 void Parcel::scanForFds() const {
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index bd72a53..0820cd1 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -531,53 +531,35 @@
     LOG_RPC_DETAIL("Setting up socket server %s", addr.toString().c_str());
     LOG_ALWAYS_FATAL_IF(hasServer(), "Each RpcServer can only have one server.");
 
-    RpcTransportFd transportFd(unique_fd(TEMP_FAILURE_RETRY(
-            socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0))));
-    if (!transportFd.fd.ok()) {
+    unique_fd socket_fd(TEMP_FAILURE_RETRY(
+            socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
+    if (!socket_fd.ok()) {
         int savedErrno = errno;
         ALOGE("Could not create socket: %s", strerror(savedErrno));
         return -savedErrno;
     }
-
-    if (0 != TEMP_FAILURE_RETRY(bind(transportFd.fd.get(), addr.addr(), addr.addrSize()))) {
+    if (0 != TEMP_FAILURE_RETRY(bind(socket_fd.get(), addr.addr(), addr.addrSize()))) {
         int savedErrno = errno;
         ALOGE("Could not bind socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
         return -savedErrno;
     }
 
-    // Right now, we create all threads at once, making accept4 slow. To avoid hanging the client,
-    // the backlog is increased to a large number.
-    // TODO(b/189955605): Once we create threads dynamically & lazily, the backlog can be reduced
-    //  to 1.
-    if (0 != TEMP_FAILURE_RETRY(listen(transportFd.fd.get(), 50 /*backlog*/))) {
-        int savedErrno = errno;
-        ALOGE("Could not listen socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
-        return -savedErrno;
-    }
-
-    LOG_RPC_DETAIL("Successfully setup socket server %s", addr.toString().c_str());
-
-    if (status_t status = setupExternalServer(std::move(transportFd.fd)); status != OK) {
-        ALOGE("Another thread has set up server while calling setupSocketServer. Race?");
-        return status;
-    }
-    return OK;
+    return setupRawSocketServer(std::move(socket_fd));
 }
 
-status_t RpcServer::setupRawSocketServer(base::unique_fd socket_fd) {
+status_t RpcServer::setupRawSocketServer(unique_fd socket_fd) {
     LOG_ALWAYS_FATAL_IF(!socket_fd.ok(), "Socket must be setup to listen.");
-    RpcTransportFd transportFd(std::move(socket_fd));
 
     // Right now, we create all threads at once, making accept4 slow. To avoid hanging the client,
     // the backlog is increased to a large number.
     // TODO(b/189955605): Once we create threads dynamically & lazily, the backlog can be reduced
     //  to 1.
-    if (0 != TEMP_FAILURE_RETRY(listen(transportFd.fd.get(), 50 /*backlog*/))) {
+    if (0 != TEMP_FAILURE_RETRY(listen(socket_fd.get(), 50 /*backlog*/))) {
         int savedErrno = errno;
         ALOGE("Could not listen initialized Unix socket: %s", strerror(savedErrno));
         return -savedErrno;
     }
-    if (status_t status = setupExternalServer(std::move(transportFd.fd)); status != OK) {
+    if (status_t status = setupExternalServer(std::move(socket_fd)); status != OK) {
         ALOGE("Another thread has set up server while calling setupSocketServer. Race?");
         return status;
     }
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 6de6ce8..f730acb 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -150,6 +150,9 @@
     // Returns Status(EX_BAD_PARCELABLE) when the Parcel is not consumed.
     binder::Status enforceNoDataAvail() const;
 
+    // This Api is used by fuzzers to skip dataAvail checks.
+    void setEnforceNoDataAvail(bool enforceNoDataAvail);
+
     void                freeData();
 
     size_t              objectsCount() const;
@@ -1329,6 +1332,9 @@
     // data to be overridden with zero when deallocated
     mutable bool        mDeallocZero;
 
+    // Set this to false to skip dataAvail checks.
+    bool mEnforceNoDataAvail;
+
     release_func        mOwner;
 
     size_t mReserved;
diff --git a/libs/binder/ndk/parcel.cpp b/libs/binder/ndk/parcel.cpp
index c320e8d..8693022 100644
--- a/libs/binder/ndk/parcel.cpp
+++ b/libs/binder/ndk/parcel.cpp
@@ -129,7 +129,13 @@
     }
 
     T* array;
-    if (!allocator(arrayData, length, &array)) return STATUS_NO_MEMORY;
+    if (!allocator(arrayData, length, &array)) {
+        if (length < 0) {
+            return STATUS_UNEXPECTED_NULL;
+        } else {
+            return STATUS_NO_MEMORY;
+        }
+    }
 
     if (length <= 0) return STATUS_OK;
     if (array == nullptr) return STATUS_NO_MEMORY;
@@ -157,7 +163,13 @@
     }
 
     char16_t* array;
-    if (!allocator(arrayData, length, &array)) return STATUS_NO_MEMORY;
+    if (!allocator(arrayData, length, &array)) {
+        if (length < 0) {
+            return STATUS_UNEXPECTED_NULL;
+        } else {
+            return STATUS_NO_MEMORY;
+        }
+    }
 
     if (length <= 0) return STATUS_OK;
     if (array == nullptr) return STATUS_NO_MEMORY;
@@ -204,7 +216,13 @@
         return status;
     }
 
-    if (!allocator(arrayData, length)) return STATUS_NO_MEMORY;
+    if (!allocator(arrayData, length)) {
+        if (length < 0) {
+            return STATUS_UNEXPECTED_NULL;
+        } else {
+            return STATUS_NO_MEMORY;
+        }
+    }
 
     if (length <= 0) return STATUS_OK;
 
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index 4e10fa9..ca2cedc 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -502,7 +502,7 @@
         let instances = binder::get_declared_instances("android.hardware.light.ILights")
             .expect("Could not get declared instances");
 
-        let expected_defaults = if has_lights { 1 } else { 0 };
+        let expected_defaults = usize::from(has_lights);
         assert_eq!(expected_defaults, instances.iter().filter(|i| i.as_str() == "default").count());
     }
 
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 03e4a23..a999d59 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -723,5 +723,7 @@
             "smoreland@google.com",
             "waghpawan@google.com",
         ],
+        // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+        hotlists: ["4637097"],
     },
 }
diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
index 25f6096..86461c8 100644
--- a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
+++ b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
@@ -18,6 +18,7 @@
 #include <fuzzbinder/random_parcel.h>
 
 #include <android-base/logging.h>
+#include <binder/IPCThreadState.h>
 #include <binder/ProcessState.h>
 
 namespace android {
@@ -30,10 +31,17 @@
             .extraFds = {},
     };
 
+    if (provider.ConsumeBool()) {
+        // set calling uid
+        IPCThreadState::self()->restoreCallingIdentity(provider.ConsumeIntegral<int64_t>());
+    }
+
     while (provider.remaining_bytes() > 0) {
         uint32_t code = provider.ConsumeIntegral<uint32_t>();
         uint32_t flags = provider.ConsumeIntegral<uint32_t>();
         Parcel data;
+        // for increased fuzz coverage
+        data.setEnforceNoDataAvail(provider.ConsumeBool());
 
         sp<IBinder> target = options.extraBinders.at(
                 provider.ConsumeIntegralInRange<size_t>(0, options.extraBinders.size() - 1));
@@ -50,6 +58,8 @@
         fillRandomParcel(&data, FuzzedDataProvider(subData.data(), subData.size()), &options);
 
         Parcel reply;
+        // for increased fuzz coverage
+        reply.setEnforceNoDataAvail(provider.ConsumeBool());
         (void)target->transact(code, data, &reply, flags);
 
         // feed back in binders and fds that are returned from the service, so that
diff --git a/libs/gui/LayerState.cpp b/libs/gui/LayerState.cpp
index 924e65e..95962af 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -24,10 +24,31 @@
 #include <binder/Parcel.h>
 #include <gui/IGraphicBufferProducer.h>
 #include <gui/LayerState.h>
+#include <gui/SurfaceControl.h>
 #include <private/gui/ParcelUtils.h>
 #include <system/window.h>
 #include <utils/Errors.h>
 
+#define CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD)          \
+    {                                                               \
+        if ((OTHER.what & CHANGE_FLAG) && (FIELD != OTHER.FIELD)) { \
+            DIFF_RESULT |= CHANGE_FLAG;                             \
+        }                                                           \
+    }
+
+#define CHECK_DIFF2(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1, FIELD2) \
+    {                                                                \
+        CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1)          \
+        CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD2)          \
+    }
+
+#define CHECK_DIFF3(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1, FIELD2, FIELD3) \
+    {                                                                        \
+        CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD1)                  \
+        CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD2)                  \
+        CHECK_DIFF(DIFF_RESULT, CHANGE_FLAG, OTHER, FIELD3)                  \
+    }
+
 namespace android {
 
 using gui::FocusRequest;
@@ -626,6 +647,74 @@
     }
 }
 
+uint64_t layer_state_t::diff(const layer_state_t& other) const {
+    uint64_t diff = 0;
+    CHECK_DIFF2(diff, ePositionChanged, other, x, y);
+    if (other.what & eLayerChanged) {
+        diff |= eLayerChanged;
+        diff &= ~eRelativeLayerChanged;
+    }
+    CHECK_DIFF(diff, eAlphaChanged, other, color.a);
+    CHECK_DIFF(diff, eMatrixChanged, other, matrix);
+    if (other.what & eTransparentRegionChanged &&
+        (!transparentRegion.hasSameRects(other.transparentRegion))) {
+        diff |= eTransparentRegionChanged;
+    }
+    if (other.what & eFlagsChanged) {
+        uint64_t changedFlags = (flags & other.mask) ^ (other.flags & other.mask);
+        if (changedFlags) diff |= eFlagsChanged;
+    }
+    CHECK_DIFF(diff, eLayerStackChanged, other, layerStack);
+    CHECK_DIFF(diff, eCornerRadiusChanged, other, cornerRadius);
+    CHECK_DIFF(diff, eBackgroundBlurRadiusChanged, other, backgroundBlurRadius);
+    if (other.what & eBlurRegionsChanged) diff |= eBlurRegionsChanged;
+    if (other.what & eRelativeLayerChanged) {
+        diff |= eRelativeLayerChanged;
+        diff &= ~eLayerChanged;
+    }
+    if (other.what & eReparent &&
+        !SurfaceControl::isSameSurface(parentSurfaceControlForChild,
+                                       other.parentSurfaceControlForChild)) {
+        diff |= eReparent;
+    }
+    CHECK_DIFF(diff, eBufferTransformChanged, other, bufferTransform);
+    CHECK_DIFF(diff, eTransformToDisplayInverseChanged, other, transformToDisplayInverse);
+    CHECK_DIFF(diff, eCropChanged, other, crop);
+    if (other.what & eBufferChanged) diff |= eBufferChanged;
+    CHECK_DIFF(diff, eDataspaceChanged, other, dataspace);
+    CHECK_DIFF(diff, eHdrMetadataChanged, other, hdrMetadata);
+    if (other.what & eSurfaceDamageRegionChanged &&
+        (!surfaceDamageRegion.hasSameRects(other.surfaceDamageRegion))) {
+        diff |= eSurfaceDamageRegionChanged;
+    }
+    CHECK_DIFF(diff, eApiChanged, other, api);
+    if (other.what & eSidebandStreamChanged) diff |= eSidebandStreamChanged;
+    CHECK_DIFF(diff, eApiChanged, other, api);
+    CHECK_DIFF(diff, eColorTransformChanged, other, colorTransform);
+    if (other.what & eHasListenerCallbacksChanged) diff |= eHasListenerCallbacksChanged;
+    if (other.what & eInputInfoChanged) diff |= eInputInfoChanged;
+    CHECK_DIFF3(diff, eBackgroundColorChanged, other, color.rgb, bgColorAlpha, bgColorDataspace);
+    if (other.what & eMetadataChanged) diff |= eMetadataChanged;
+    CHECK_DIFF(diff, eShadowRadiusChanged, other, shadowRadius);
+    CHECK_DIFF3(diff, eRenderBorderChanged, other, borderEnabled, borderWidth, borderColor);
+    CHECK_DIFF(diff, eDefaultFrameRateCompatibilityChanged, other, defaultFrameRateCompatibility);
+    CHECK_DIFF(diff, eFrameRateSelectionPriority, other, frameRateSelectionPriority);
+    CHECK_DIFF3(diff, eFrameRateChanged, other, frameRate, frameRateCompatibility,
+                changeFrameRateStrategy);
+    CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
+    CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
+    CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay);
+    CHECK_DIFF(diff, eStretchChanged, other, stretchEffect);
+    CHECK_DIFF(diff, eBufferCropChanged, other, bufferCrop);
+    CHECK_DIFF(diff, eDestinationFrameChanged, other, destinationFrame);
+    if (other.what & eProducerDisconnect) diff |= eProducerDisconnect;
+    CHECK_DIFF(diff, eDropInputModeChanged, other, dropInputMode);
+    CHECK_DIFF(diff, eColorChanged, other, color.rgb);
+    CHECK_DIFF(diff, eColorSpaceAgnosticChanged, other, colorSpaceAgnostic);
+    CHECK_DIFF(diff, eDimmingEnabledChanged, other, dimmingEnabled);
+    return diff;
+}
+
 bool layer_state_t::hasBufferChanges() const {
     return what & layer_state_t::eBufferChanged;
 }
diff --git a/libs/gui/SurfaceComposerClient.cpp b/libs/gui/SurfaceComposerClient.cpp
index d18d22d..1e43700 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -1250,6 +1250,7 @@
     if ((mask & layer_state_t::eLayerOpaque) || (mask & layer_state_t::eLayerHidden) ||
         (mask & layer_state_t::eLayerSecure) || (mask & layer_state_t::eLayerSkipScreenshot) ||
         (mask & layer_state_t::eEnableBackpressure) ||
+        (mask & layer_state_t::eIgnoreDestinationFrame) ||
         (mask & layer_state_t::eLayerIsDisplayDecoration)) {
         s->what |= layer_state_t::eFlagsChanged;
     }
diff --git a/libs/gui/include/gui/LayerState.h b/libs/gui/include/gui/LayerState.h
index 09f171d..6ec6bd7 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -201,7 +201,32 @@
     void merge(const layer_state_t& other);
     status_t write(Parcel& output) const;
     status_t read(const Parcel& input);
+    // Compares two layer_state_t structs and returns a set of change flags describing all the
+    // states that are different.
+    uint64_t diff(const layer_state_t& other) const;
     bool hasBufferChanges() const;
+
+    // Changes to the tree structure.
+    static constexpr uint64_t HIERARCHY_CHANGES = layer_state_t::eLayerChanged |
+            layer_state_t::eRelativeLayerChanged | layer_state_t::eReparent |
+            layer_state_t::eBackgroundColorChanged;
+    // Content updates.
+    static constexpr uint64_t CONTENT_CHANGES = layer_state_t::eAlphaChanged |
+            layer_state_t::eTransparentRegionChanged | layer_state_t::eShadowRadiusChanged |
+            layer_state_t::eRenderBorderChanged | layer_state_t::eColorChanged |
+            layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged |
+            layer_state_t::eApiChanged | layer_state_t::eSidebandStreamChanged |
+            layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
+            layer_state_t::eBackgroundColorChanged | layer_state_t::eColorSpaceAgnosticChanged |
+            layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged |
+            layer_state_t::eAutoRefreshChanged | layer_state_t::eStretchChanged;
+    // Changes to content or children size.
+    static constexpr uint64_t GEOMETRY_CHANGES = layer_state_t::ePositionChanged |
+            layer_state_t::eMatrixChanged | layer_state_t::eTransparentRegionChanged |
+            layer_state_t::eBufferCropChanged | layer_state_t::eBufferTransformChanged |
+            layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged |
+            layer_state_t::eDestinationFrameChanged;
+
     bool hasValidBuffer() const;
     void sanitize(int32_t permissions);
 
@@ -212,6 +237,11 @@
         float dsdy{0};
         status_t write(Parcel& output) const;
         status_t read(const Parcel& input);
+        inline bool operator==(const matrix22_t& other) const {
+            return std::tie(dsdx, dtdx, dtdy, dsdy) ==
+                    std::tie(other.dsdx, other.dtdx, other.dtdy, other.dsdy);
+        }
+        inline bool operator!=(const matrix22_t& other) const { return !(*this == other); }
     };
     sp<IBinder> surface;
     int32_t layerId;
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
index ef9112e..e76bc20 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymapmath.h
@@ -23,6 +23,9 @@
 
 namespace android::recoverymap {
 
+////////////////////////////////////////////////////////////////////////////////
+// Framework
+
 const float kSdrWhiteNits = 100.0f;
 
 struct Color {
@@ -40,35 +43,142 @@
   };
 };
 
-/*
- * Convert from OETF'd bt.2100 RGB to YUV, according to BT.2100
- */
-Color bt2100RgbToYuv(Color e);
+inline Color operator+=(Color& lhs, const Color& rhs) {
+  lhs.r += rhs.r;
+  lhs.g += rhs.g;
+  lhs.b += rhs.b;
+  return lhs;
+}
+inline Color operator-=(Color& lhs, const Color& rhs) {
+  lhs.r -= rhs.r;
+  lhs.g -= rhs.g;
+  lhs.b -= rhs.b;
+  return lhs;
+}
+
+inline Color operator+(const Color& lhs, const Color& rhs) {
+  Color temp = lhs;
+  return temp += rhs;
+}
+inline Color operator-(const Color& lhs, const Color& rhs) {
+  Color temp = lhs;
+  return temp -= rhs;
+}
+
+inline Color operator+=(Color& lhs, const float rhs) {
+  lhs.r += rhs;
+  lhs.g += rhs;
+  lhs.b += rhs;
+  return lhs;
+}
+inline Color operator-=(Color& lhs, const float rhs) {
+  lhs.r -= rhs;
+  lhs.g -= rhs;
+  lhs.b -= rhs;
+  return lhs;
+}
+inline Color operator*=(Color& lhs, const float rhs) {
+  lhs.r *= rhs;
+  lhs.g *= rhs;
+  lhs.b *= rhs;
+  return lhs;
+}
+inline Color operator/=(Color& lhs, const float rhs) {
+  lhs.r /= rhs;
+  lhs.g /= rhs;
+  lhs.b /= rhs;
+  return lhs;
+}
+
+inline Color operator+(const Color& lhs, const float rhs) {
+  Color temp = lhs;
+  return temp += rhs;
+}
+inline Color operator-(const Color& lhs, const float rhs) {
+  Color temp = lhs;
+  return temp -= rhs;
+}
+inline Color operator*(const Color& lhs, const float rhs) {
+  Color temp = lhs;
+  return temp *= rhs;
+}
+inline Color operator/(const Color& lhs, const float rhs) {
+  Color temp = lhs;
+  return temp /= rhs;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// sRGB transformations
 
 /*
- * Convert srgb YUV to RGB, according to ECMA TR/98.
+ * Calculate the luminance of a linear RGB sRGB pixel, according to IEC 61966-2-1.
  */
-Color srgbYuvToRgb(Color e);
+float srgbLuminance(Color e);
 
 /*
- * TODO: better source for srgb transfer function
- * Convert from srgb to linear, according to https://en.wikipedia.org/wiki/SRGB.
+ * Convert from OETF'd srgb YUV to RGB, according to ECMA TR/98.
+ */
+Color srgbYuvToRgb(Color e_gamma);
+
+/*
+ * Convert from OETF'd srgb RGB to YUV, according to ECMA TR/98.
+ */
+Color srgbRgbToYuv(Color e_gamma);
+
+/*
+ * Convert from srgb to linear, according to IEC 61966-2-1.
+ *
  * [0.0, 1.0] range in and out.
  */
-float srgbInvOetf(float e);
-Color srgbInvOetf(Color e);
+float srgbInvOetf(float e_gamma);
+Color srgbInvOetf(Color e_gamma);
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Display-P3 transformations
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// BT.2100 transformations - according to ITU-R BT.2100-2
 
 /*
- * Convert from HLG to scene luminance in nits, according to BT.2100.
+ * Calculate the luminance of a linear RGB BT.2100 pixel.
  */
-float hlgInvOetf(float e);
+float bt2100Luminance(Color e);
 
 /*
- * Convert from scene luminance in nits to HLG,  according to BT.2100.
+ * Convert from OETF'd BT.2100 RGB to YUV.
+ */
+Color bt2100RgbToYuv(Color e_gamma);
+
+/*
+ * Convert from OETF'd BT.2100 YUV to RGB.
+ */
+Color bt2100YuvToRgb(Color e_gamma);
+
+/*
+ * Convert from scene luminance in nits to HLG.
  */
 Color hlgOetf(Color e);
 
 /*
+ * Convert from HLG to scene luminance in nits.
+ */
+Color hlgInvOetf(Color e_gamma);
+
+////////////////////////////////////////////////////////////////////////////////
+// Color space conversions
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Recovery map calculations
+
+/*
  * Calculate the 8-bit unsigned integer recovery value for the given SDR and HDR
  * luminances in linear space, and the hdr ratio to encode against.
  */
@@ -86,6 +196,11 @@
 Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
 
 /*
+ * Helper for sampling from images.
+ */
+Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
+
+/*
  * Sample the recovery value for the map from a given x,y coordinate on a scale
  * that is map scale factor larger than the map size.
  */
@@ -97,8 +212,13 @@
  *
  * Expect narrow-range image data for P010.
  */
-float sampleYuv420Y(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
-float sampleP010Y(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+
+/*
+ * Sample the image Y value at the provided location, with a weighting based on nearby pixels
+ * and the map scale factor. Assumes narrow-range image data for P010.
+ */
+Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
 
 } // namespace android::recoverymap
 
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index bd16a68..86eb8a8 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -301,31 +301,38 @@
   std::unique_ptr<uint8_t[]> map_data;
   map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
 
-  uint16_t yp_hdr_max = 0;
+  float hdr_y_nits_max = 0.0f;
   for (size_t y = 0; y < image_height; ++y) {
     for (size_t x = 0; x < image_width; ++x) {
-      size_t pixel_idx =  x + y * image_width;
-      uint16_t yp_hdr = reinterpret_cast<uint8_t*>(uncompressed_yuv_420_image->data)[pixel_idx];
-      if (yp_hdr > yp_hdr_max) {
-        yp_hdr_max = yp_hdr;
+      Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
+      Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+      Color hdr_rgb = hlgInvOetf(hdr_rgb_gamma);
+      float hdr_y_nits = bt2100Luminance(hdr_rgb);
+
+      if (hdr_y_nits > hdr_y_nits_max) {
+        hdr_y_nits_max = hdr_y_nits;
       }
     }
   }
 
-  float y_hdr_max_nits = hlgInvOetf(yp_hdr_max);
-  hdr_ratio = y_hdr_max_nits / kSdrWhiteNits;
+  hdr_ratio = hdr_y_nits_max / kSdrWhiteNits;
 
   for (size_t y = 0; y < map_height; ++y) {
     for (size_t x = 0; x < map_width; ++x) {
-      float yp_sdr = sampleYuv420Y(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
-      float yp_hdr = sampleP010Y(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
+      Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
+                                         kMapDimensionScaleFactor, x, y);
+      Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
+      Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
+      float sdr_y_nits = srgbLuminance(sdr_rgb);
 
-      float y_sdr_nits = srgbInvOetf(yp_sdr);
-      float y_hdr_nits = hlgInvOetf(yp_hdr);
+      Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
+      Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
+      Color hdr_rgb = hlgInvOetf(hdr_rgb_gamma);
+      float hdr_y_nits = bt2100Luminance(hdr_rgb);
 
       size_t pixel_idx =  x + y * map_width;
       reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
-          encodeRecovery(y_sdr_nits, y_hdr_nits, hdr_ratio);
+          encodeRecovery(sdr_y_nits, hdr_y_nits, hdr_ratio);
     }
   }
 
@@ -367,7 +374,8 @@
       Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
 
       Color rgbp_hdr = hlgOetf(rgb_hdr);
-      Color ypuv_hdr = bt2100RgbToYuv(rgbp_hdr);
+      // TODO: actually just leave in RGB and convert to RGBA1010102 instead.
+      Color ypuv_hdr = srgbRgbToYuv(rgbp_hdr);
 
       reinterpret_cast<uint16_t*>(dest->data)[pixel_y_idx] = ypuv_hdr.r;
       reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g;
diff --git a/libs/jpegrecoverymap/recoverymapmath.cpp b/libs/jpegrecoverymap/recoverymapmath.cpp
index 368783f..0d3319f 100644
--- a/libs/jpegrecoverymap/recoverymapmath.cpp
+++ b/libs/jpegrecoverymap/recoverymapmath.cpp
@@ -20,42 +20,102 @@
 
 namespace android::recoverymap {
 
-static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f;
-static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
+////////////////////////////////////////////////////////////////////////////////
+// sRGB transformations
 
-Color bt2100RgbToYuv(Color e) {
-  float yp = kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b;
-  return {{{yp, (e.b - yp) / kBt2100Cb, (e.r - yp) / kBt2100Cr }}};
+static const float kSrgbR = 0.299f, kSrgbG = 0.587f, kSrgbB = 0.114f;
+
+float srgbLuminance(Color e) {
+  return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b;
 }
 
 static const float kSrgbRCr = 1.402f, kSrgbGCb = 0.34414f, kSrgbGCr = 0.71414f, kSrgbBCb = 1.772f;
 
-Color srgbYuvToRgb(Color e) {
-  return {{{ e.y + kSrgbRCr * e.v, e.y - kSrgbGCb * e.u - kSrgbGCr * e.v, e.y + kSrgbBCb * e.u }}};
+Color srgbYuvToRgb(Color e_gamma) {
+  return {{{ e_gamma.y + kSrgbRCr * e_gamma.v,
+             e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v,
+             e_gamma.y + kSrgbBCb * e_gamma.u }}};
 }
 
-float srgbInvOetf(float e) {
-  if (e <= 0.04045f) {
-    return e / 12.92f;
+static const float kSrgbUR = -0.1687f, kSrgbUG = -0.3313f, kSrgbUB = 0.5f;
+static const float kSrgbVR = 0.5f, kSrgbVG = -0.4187f, kSrgbVB = -0.0813f;
+
+Color srgbRgbToYuv(Color e_gamma) {
+  return {{{ kSrgbR * e_gamma.r + kSrgbG * e_gamma.g + kSrgbB * e_gamma.b,
+             kSrgbUR * e_gamma.r + kSrgbUG * e_gamma.g + kSrgbUB * e_gamma.b,
+             kSrgbVR * e_gamma.r + kSrgbVG * e_gamma.g + kSrgbVB * e_gamma.b }}};
+}
+
+float srgbInvOetf(float e_gamma) {
+  if (e_gamma <= 0.04045f) {
+    return e_gamma / 12.92f;
   } else {
-    return pow((e + 0.055f) / 1.055f, 2.4);
+    return pow((e_gamma + 0.055f) / 1.055f, 2.4);
   }
 }
 
-Color srgbInvOetf(Color e) {
-  return {{{ srgbInvOetf(e.r), srgbInvOetf(e.g), srgbInvOetf(e.b) }}};
+Color srgbInvOetf(Color e_gamma) {
+  return {{{ srgbInvOetf(e_gamma.r),
+             srgbInvOetf(e_gamma.g),
+             srgbInvOetf(e_gamma.b) }}};
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Display-P3 transformations
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// BT.2100 transformations - according to ITU-R BT.2100-2
+
+static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f;
+
+float bt2100Luminance(Color e) {
+  return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b;
+}
+
+static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
+
+Color bt2100RgbToYuv(Color e_gamma) {
+  float y_gamma = bt2100Luminance(e_gamma);
+  return {{{ y_gamma,
+             (e_gamma.b - y_gamma) / kBt2100Cb,
+             (e_gamma.r - y_gamma) / kBt2100Cr }}};
+}
+
+// Derived from the reverse of bt2100RgbToYuv. The derivation for R and B are
+// pretty straight forward; we just reverse the formulas for U and V above. But
+// deriving the formula for G is a bit more complicated:
+//
+// Start with equation for luminance:
+//   Y = kBt2100R * R + kBt2100G * G + kBt2100B * B
+// Solve for G:
+//   G = (Y - kBt2100R * R - kBt2100B * B) / kBt2100B
+// Substitute equations for R and B in terms YUV:
+//   G = (Y - kBt2100R * (Y + kBt2100Cr * V) - kBt2100B * (Y + kBt2100Cb * U)) / kBt2100B
+// Simplify:
+//   G = Y * ((1 - kBt2100R - kBt2100B) / kBt2100G)
+//     + U * (kBt2100B * kBt2100Cb / kBt2100G)
+//     + V * (kBt2100R * kBt2100Cr / kBt2100G)
+//
+// We then get the following coeficients for calculating G from YUV:
+//
+// Coef for Y = (1 - kBt2100R - kBt2100B) / kBt2100G = 1
+// Coef for U = kBt2100B * kBt2100Cb / kBt2100G = kBt2100GCb = ~0.1645
+// Coef for V = kBt2100R * kBt2100Cr / kBt2100G = kBt2100GCr = ~0.5713
+
+static const float kBt2100GCb = kBt2100B * kBt2100Cb / kBt2100G;
+static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G;
+
+Color bt2100YuvToRgb(Color e_gamma) {
+  return {{{ e_gamma.y + kBt2100Cr * e_gamma.v,
+             e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v,
+             e_gamma.y + kBt2100Cb * e_gamma.u }}};
 }
 
 static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073;
 
-float hlgInvOetf(float e) {
-  if (e <= 0.5f) {
-    return pow(e, 2.0f) / 3.0f;
-  } else {
-    return (exp((e - kHlgC) / kHlgA) + kHlgB) / 12.0f;
-  }
-}
-
 static float hlgOetf(float e) {
   if (e <= 1.0f/12.0f) {
     return sqrt(3.0f * e);
@@ -68,6 +128,28 @@
   return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
 }
 
+float hlgInvOetf(float e_gamma) {
+  if (e_gamma <= 0.5f) {
+    return pow(e_gamma, 2.0f) / 3.0f;
+  } else {
+    return (exp((e_gamma - kHlgC) / kHlgA) + kHlgB) / 12.0f;
+  }
+}
+
+Color hlgInvOetf(Color e_gamma) {
+  return {{{ hlgInvOetf(e_gamma.r),
+             hlgInvOetf(e_gamma.g),
+             hlgInvOetf(e_gamma.b) }}};
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Color conversions
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Recovery map calculations
+
 uint8_t encodeRecovery(float y_sdr, float y_hdr, float hdr_ratio) {
   float gain = 1.0f;
   if (y_sdr > 0.0f) {
@@ -137,40 +219,42 @@
              (static_cast<float>(v_uint) - 128.0f) / 255.0f }}};
 }
 
-typedef float (*sampleComponentFn)(jr_uncompressed_ptr, size_t, size_t);
+Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
+  size_t pixel_count = image->width * image->height;
 
-static float sampleComponent(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y,
-                             sampleComponentFn sample_fn) {
-  float e = 0.0f;
+  size_t pixel_y_idx = x + y * image->width;
+  size_t pixel_uv_idx = x / 2 + (y / 2) * (image->width / 2);
+
+  uint16_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_y_idx];
+  uint16_t u_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2];
+  uint16_t v_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_count + pixel_uv_idx * 2 + 1];
+
+  // Conversions include taking narrow-range into account.
+  return {{{ static_cast<float>(y_uint) / 940.0f,
+             (static_cast<float>(u_uint) - 64.0f) / 940.0f - 0.5f,
+             (static_cast<float>(v_uint) - 64.0f) / 940.0f - 0.5f }}};
+}
+
+typedef Color (*getPixelFn)(jr_uncompressed_ptr, size_t, size_t);
+
+static Color samplePixels(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y,
+                          getPixelFn get_pixel_fn) {
+  Color e = {{{ 0.0f, 0.0f, 0.0f }}};
   for (size_t dy = 0; dy < map_scale_factor; ++dy) {
     for (size_t dx = 0; dx < map_scale_factor; ++dx) {
-      e += sample_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy);
+      e += get_pixel_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy);
     }
   }
 
   return e / static_cast<float>(map_scale_factor * map_scale_factor);
 }
 
-static float getYuv420Y(jr_uncompressed_ptr image, size_t x, size_t y) {
-  size_t pixel_idx = x + y * image->width;
-  uint8_t y_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_idx];
-  return static_cast<float>(y_uint) / 255.0f;
+Color sampleYuv420(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
+  return samplePixels(image, map_scale_factor, x, y, getYuv420Pixel);
 }
 
-
-float sampleYuv420Y(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
-  return sampleComponent(image, map_scale_factor, x, y, getYuv420Y);
-}
-
-static float getP010Y(jr_uncompressed_ptr image, size_t x, size_t y) {
-  size_t pixel_idx = x + y * image->width;
-  uint8_t y_uint = reinterpret_cast<uint16_t*>(image->data)[pixel_idx];
-  // Expecting narrow range input
-  return (static_cast<float>(y_uint) - 64.0f) / 960.0f;
-}
-
-float sampleP010Y(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
-  return sampleComponent(image, map_scale_factor, x, y, getP010Y);
+Color sampleP010(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) {
+  return samplePixels(image, map_scale_factor, x, y, getP010Pixel);
 }
 
 } // namespace android::recoverymap
diff --git a/libs/jpegrecoverymap/tests/Android.bp b/libs/jpegrecoverymap/tests/Android.bp
index 41af991..8f37954 100644
--- a/libs/jpegrecoverymap/tests/Android.bp
+++ b/libs/jpegrecoverymap/tests/Android.bp
@@ -27,7 +27,15 @@
     srcs: [
         "recoverymap_test.cpp",
     ],
+    shared_libs: [
+        "libimage_io",
+        "libjpeg",
+        "liblog",
+    ],
     static_libs: [
+        "libgtest",
+        "libjpegdecoder",
+        "libjpegencoder",
         "libjpegrecoverymap",
     ],
 }
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index c436138..226056b 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -14,9 +14,33 @@
  * limitations under the License.
  */
 
+#include <gtest/gtest.h>
 #include <jpegrecoverymap/recoverymap.h>
 
-namespace android {
+namespace android::recoverymap {
 
-// Add new tests here.
-} // namespace android
+class RecoveryMapTest : public testing::Test {
+public:
+  RecoveryMapTest();
+  ~RecoveryMapTest();
+protected:
+  virtual void SetUp();
+  virtual void TearDown();
+};
+
+RecoveryMapTest::RecoveryMapTest() {}
+RecoveryMapTest::~RecoveryMapTest() {}
+
+void RecoveryMapTest::SetUp() {}
+void RecoveryMapTest::TearDown() {}
+
+TEST_F(RecoveryMapTest, build) {
+  // Force all of the recovery map lib to be linked by calling all public functions.
+  RecoveryMap recovery_map;
+  recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, 0, nullptr);
+  recovery_map.encodeJPEGR(nullptr, nullptr, nullptr, nullptr);
+  recovery_map.encodeJPEGR(nullptr, nullptr, nullptr);
+  recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false);
+}
+
+} // namespace android::recoverymap
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 4a0f2ec..b3b4fb1 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -41,6 +41,7 @@
           "include-filter": "android.view.cts.input",
           "include-filter": "android.view.cts.MotionEventTest",
           "include-filter": "android.view.cts.PointerCaptureTest",
+          "include-filter": "android.view.cts.TooltipTest",
           "include-filter": "android.view.cts.VerifyInputEventTest"
         }
       ]
@@ -128,6 +129,7 @@
         {
           "include-filter": "android.view.cts.MotionEventTest",
           "include-filter": "android.view.cts.PointerCaptureTest",
+          "include-filter": "android.view.cts.TooltipTest",
           "include-filter": "android.view.cts.VerifyInputEventTest"
         }
       ]
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 87a4ff4..eb938c8 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -1769,15 +1769,16 @@
 
 void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionEntry& entry) {
     if (DEBUG_OUTBOUND_EVENT_DETAILS) {
-        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
+        ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%" PRId32
               ", policyFlags=0x%x, "
               "action=%s, actionButton=0x%x, flags=0x%x, "
               "metaState=0x%x, buttonState=0x%x,"
               "edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
-              prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId,
-              entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
-              entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.edgeFlags,
-              entry.xPrecision, entry.yPrecision, entry.downTime);
+              prefix, entry.eventTime, entry.deviceId,
+              inputEventSourceToString(entry.source).c_str(), entry.displayId, entry.policyFlags,
+              MotionEvent::actionToString(entry.action).c_str(), entry.actionButton, entry.flags,
+              entry.metaState, entry.buttonState, entry.edgeFlags, entry.xPrecision,
+              entry.yPrecision, entry.downTime);
 
         for (uint32_t i = 0; i < entry.pointerCount; i++) {
             ALOGD("  Pointer %d: id=%d, toolType=%d, "
@@ -2502,6 +2503,10 @@
         }
     }
 
+    if (tempTouchState.windows.empty()) {
+        mTouchStatesByDisplay.erase(displayId);
+    }
+
     // Update hover state.
     mLastHoverWindowHandle = newHoverWindowHandle;
 
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index ee7da93..114e0bf 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -148,7 +148,8 @@
 
 std::string TouchState::dump() const {
     std::string out;
-    out += StringPrintf("deviceId=%d, source=0x%08x\n", deviceId, source);
+    out += StringPrintf("deviceId=%d, source=%s\n", deviceId,
+                        inputEventSourceToString(source).c_str());
     if (!windows.empty()) {
         out += "  Windows:\n";
         for (size_t i = 0; i < windows.size(); i++) {
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 5657d61..a1a2af9 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -300,10 +300,11 @@
     mCursorScrollAccumulator.process(rawEvent);
 
     if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
-        const nsecs_t eventTime =
+        const auto [eventTime, readTime] =
                 applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(),
-                                                   rawEvent->when, mLastEventTime);
-        out += sync(eventTime, rawEvent->readTime);
+                                                   rawEvent->when, rawEvent->readTime,
+                                                   mLastEventTime);
+        out += sync(eventTime, readTime);
         mLastEventTime = eventTime;
     }
     return out;
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
index ccff353..b193dff 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp
@@ -119,6 +119,18 @@
             if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
                 outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
             }
+        } else if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS && !mStylusMtToolSeen) {
+            mStylusMtToolSeen = true;
+            // The multi-touch device produced a stylus event with MT_TOOL_PEN. Dynamically
+            // re-configure this input device so that we add SOURCE_STYLUS if we haven't already.
+            // This is to cover the case where we cannot reliably detect whether a multi-touch
+            // device will ever produce stylus events when it is initially being configured.
+            if (!isFromSource(mSource, AINPUT_SOURCE_STYLUS)) {
+                // Add the stylus source immediately so that it is included in any events generated
+                // before we have a chance to re-configure the device.
+                mSource |= AINPUT_SOURCE_STYLUS;
+                bumpGeneration();
+            }
         }
         if (shouldSimulateStylusWithTouch() &&
             outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
@@ -200,7 +212,8 @@
 }
 
 bool MultiTouchInputMapper::hasStylus() const {
-    return mTouchButtonAccumulator.hasStylus() || shouldSimulateStylusWithTouch();
+    return mStylusMtToolSeen || mTouchButtonAccumulator.hasStylus() ||
+            shouldSimulateStylusWithTouch();
 }
 
 bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const {
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index ddf9e80..5f8bccf 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -50,6 +50,8 @@
     // Specifies the pointer id bits that are in use, and their associated tracking id.
     BitSet32 mPointerIdBits;
     int32_t mPointerTrackingIdMap[MAX_POINTER_ID + 1];
+
+    bool mStylusMtToolSeen{false};
 };
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
index 0b7ff84..d8a4d34 100644
--- a/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
+++ b/services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h
@@ -110,10 +110,11 @@
 // coordinates result in extremely large instantaneous velocities, which can negatively impact
 // user experience. To avoid this, we augment the timestamps so that subsequent event timestamps
 // differ by at least a minimum delta value.
-static nsecs_t applyBluetoothTimestampSmoothening(const InputDeviceIdentifier& identifier,
-                                                  nsecs_t currentEventTime, nsecs_t lastEventTime) {
+static std::tuple<nsecs_t /*eventTime*/, nsecs_t /*readTime*/> applyBluetoothTimestampSmoothening(
+        const InputDeviceIdentifier& identifier, nsecs_t currentEventTime, nsecs_t readTime,
+        nsecs_t lastEventTime) {
     if (identifier.bus != BUS_BLUETOOTH) {
-        return currentEventTime;
+        return {currentEventTime, readTime};
     }
 
     // Assume the fastest rate at which a Bluetooth touch device can report input events is one
@@ -123,8 +124,14 @@
     // We define a maximum smoothing time delta so that we don't generate events too far into the
     // future.
     constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32);
-    return std::min(std::max(currentEventTime, lastEventTime + MIN_BLUETOOTH_TIMESTAMP_DELTA),
-                    currentEventTime + MAX_BLUETOOTH_SMOOTHING_DELTA);
+    const nsecs_t smoothenedEventTime =
+            std::min(std::max(currentEventTime, lastEventTime + MIN_BLUETOOTH_TIMESTAMP_DELTA),
+                     currentEventTime + MAX_BLUETOOTH_SMOOTHING_DELTA);
+    // If we are modifying the event time, treat this event as a synthetically generated event for
+    // latency tracking purposes and use the event time as the read time (zero read latency).
+    const nsecs_t smoothenedReadTime =
+            smoothenedEventTime != currentEventTime ? currentEventTime : readTime;
+    return {smoothenedEventTime, smoothenedReadTime};
 }
 
 } // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 380c3a5..5be694f 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -1467,8 +1467,9 @@
     const RawState& last =
             mRawStatesPending.size() == 1 ? mCurrentRawState : mRawStatesPending.rbegin()[1];
 
-    next.when = applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), when,
-                                                   last.when);
+    std::tie(next.when, next.readTime) =
+            applyBluetoothTimestampSmoothening(getDeviceContext().getDeviceIdentifier(), when,
+                                               readTime, last.when);
 
     // Assign pointer ids.
     if (!mHavePointerIds) {
@@ -3762,15 +3763,12 @@
     PointerCoords pointerCoords[MAX_POINTERS];
     PointerProperties pointerProperties[MAX_POINTERS];
     uint32_t pointerCount = 0;
-    bool stylusToolFound = false;
     while (!idBits.isEmpty()) {
         uint32_t id = idBits.clearFirstMarkedBit();
         uint32_t index = idToIndex[id];
         pointerProperties[pointerCount].copyFrom(properties[index]);
         pointerCoords[pointerCount].copyFrom(coords[index]);
 
-        stylusToolFound |= isStylusToolType(pointerProperties[pointerCount].toolType);
-
         if (changedId >= 0 && id == uint32_t(changedId)) {
             action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
         }
@@ -3802,12 +3800,6 @@
     if (mDeviceMode == DeviceMode::POINTER) {
         mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
     }
-    if (stylusToolFound) {
-        // Dynamically add the stylus source when there's a stylus tool being used to cover the case
-        // where we cannot reliably detect whether a multi-touch device will ever produce stylus
-        // events when it is initially being configured.
-        source |= AINPUT_SOURCE_STYLUS;
-    }
     const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
     const int32_t deviceId = getDeviceId();
     std::vector<TouchVideoFrame> frames = getDeviceContext().getVideoFrames();
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index aaf50ce..689fe5c 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -22,6 +22,7 @@
 #include <android-base/thread_annotations.h>
 #include <binder/Binder.h>
 #include <fcntl.h>
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/Input.h>
 #include <linux/input.h>
@@ -43,6 +44,7 @@
 namespace android::inputdispatcher {
 
 using namespace ftl::flag_operators;
+using testing::AllOf;
 
 // An arbitrary time value.
 static constexpr nsecs_t ARBITRARY_TIME = 1234;
@@ -102,6 +104,28 @@
             << MotionEvent::actionToString(receivedAction);
 }
 
+MATCHER_P(WithMotionAction, action, "MotionEvent with specified action") {
+    bool matches = action == arg.getAction();
+    if (!matches) {
+        *result_listener << "expected action " << MotionEvent::actionToString(action)
+                         << ", but got " << MotionEvent::actionToString(arg.getAction());
+    }
+    if (action == AMOTION_EVENT_ACTION_CANCEL) {
+        if (!matches) {
+            *result_listener << "; ";
+        }
+        *result_listener << "expected FLAG_CANCELED to be set with ACTION_CANCEL, but was not set";
+        matches &= (arg.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+    }
+    return matches;
+}
+
+MATCHER_P(WithSource, source, "InputEvent with specified source") {
+    *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
+                     << inputEventSourceToString(arg.getSource());
+    return arg.getSource() == source;
+}
+
 // --- FakeInputDispatcherPolicy ---
 
 class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
@@ -1205,6 +1229,12 @@
         mInputReceiver->consumeCaptureEvent(hasCapture);
     }
 
+    void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+        MotionEvent* motionEvent = consumeMotion();
+        ASSERT_NE(nullptr, motionEvent) << "Did not get a motion event";
+        ASSERT_THAT(*motionEvent, matcher);
+    }
+
     void consumeEvent(int32_t expectedEventType, int32_t expectedAction,
                       std::optional<int32_t> expectedDisplayId,
                       std::optional<int32_t> expectedFlags) {
@@ -2207,6 +2237,48 @@
                          ADISPLAY_ID_DEFAULT, 0 /* expectedFlag */);
 }
 
+/**
+ * Inject a mouse hover event followed by a tap from touchscreen.
+ * In the current implementation, the tap does not cause a HOVER_EXIT event.
+ */
+TEST_F(InputDispatcherTest, MouseHoverAndTouchTap) {
+    std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+    sp<FakeWindowHandle> window =
+            sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+    window->setFrame(Rect(0, 0, 100, 100));
+
+    mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+
+    // Inject a hover_move from mouse.
+    NotifyMotionArgs motionArgs =
+            generateMotionArgs(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE,
+                               ADISPLAY_ID_DEFAULT, {{50, 50}});
+    motionArgs.xCursorPosition = 50;
+    motionArgs.yCursorPosition = 50;
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+                                             WithSource(AINPUT_SOURCE_MOUSE))));
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+                                             WithSource(AINPUT_SOURCE_MOUSE))));
+
+    // Tap on the window
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT, {{10, 10}});
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
+
+    motionArgs = generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+                                    ADISPLAY_ID_DEFAULT, {{10, 10}});
+    mDispatcher->notifyMotion(&motionArgs);
+    ASSERT_NO_FATAL_FAILURE(
+            window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+                                             WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
+}
+
 TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
     std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 5c5fc77..6b6d0bb 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -10788,11 +10788,12 @@
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
 }
 
-TEST_F(MultiTouchInputMapperTest, ToolTypeSource) {
+TEST_F(MultiTouchInputMapperTest, StylusSourceIsAddedDynamicallyFromToolType) {
     addConfigurationProperty("touch.deviceType", "touchScreen");
     prepareDisplay(DISPLAY_ORIENTATION_0);
     prepareAxes(POSITION | ID | SLOT | PRESSURE | TOOL_TYPE);
     MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
 
     // Even if the device supports reporting the ABS_MT_TOOL_TYPE axis, which could give it the
     // ability to report MT_TOOL_PEN, we do not report the device as coming from a stylus source.
@@ -10801,7 +10802,7 @@
     ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
 
     // However, if the device ever ends up reporting an event with MT_TOOL_PEN, it should be
-    // reported with the stylus source, even through the device doesn't support the stylus source.
+    // reported with the stylus source.
     processId(mapper, FIRST_TRACKING_ID);
     processToolType(mapper, MT_TOOL_PEN);
     processPosition(mapper, 100, 200);
@@ -10812,15 +10813,27 @@
                   WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
                   WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
 
+    // Now that we know the device supports styluses, ensure that the device is re-configured with
+    // the stylus source.
+    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, mapper.getSources());
+    {
+        const auto& devices = mReader->getInputDevices();
+        auto deviceInfo =
+                std::find_if(devices.begin(), devices.end(),
+                             [](const InputDeviceInfo& info) { return info.getId() == DEVICE_ID; });
+        LOG_ALWAYS_FATAL_IF(deviceInfo == devices.end(), "Cannot find InputDevice");
+        ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS, deviceInfo->getSources());
+    }
+
+    // Ensure the device was not reset to prevent interruptions of any ongoing gestures.
+    ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled());
+
     processId(mapper, INVALID_TRACKING_ID);
     processSync(mapper);
     ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
             AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
                   WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
                   WithToolType(AMOTION_EVENT_TOOL_TYPE_STYLUS))));
-
-    // The mapper should still report only a touchscreen source.
-    ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
 }
 
 // --- MultiTouchInputMapperTest_ExternalDevice ---
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 8721bd8..9db3422 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -46,7 +46,8 @@
 }
 
 MATCHER_P(WithSource, source, "InputEvent with specified source") {
-    *result_listener << "expected source " << source << ", but got " << arg.source;
+    *result_listener << "expected source " << inputEventSourceToString(source) << ", but got "
+                     << inputEventSourceToString(arg.source);
     return arg.source == source;
 }
 
diff --git a/services/sensorservice/AidlSensorHalWrapper.cpp b/services/sensorservice/AidlSensorHalWrapper.cpp
index f67c610..f5b360f 100644
--- a/services/sensorservice/AidlSensorHalWrapper.cpp
+++ b/services/sensorservice/AidlSensorHalWrapper.cpp
@@ -23,6 +23,7 @@
 #include <aidlcommonsupport/NativeHandle.h>
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
+#include <aidl/sensors/convert.h>
 
 using ::aidl::android::hardware::sensors::AdditionalInfo;
 using ::aidl::android::hardware::sensors::DynamicSensorInfo;
@@ -34,469 +35,16 @@
 using ::android::AidlMessageQueue;
 using ::android::hardware::EventFlag;
 using ::android::hardware::sensors::V2_1::implementation::MAX_RECEIVE_BUFFER_EVENT_COUNT;
+using ::android::hardware::sensors::implementation::convertToStatus;
+using ::android::hardware::sensors::implementation::convertToSensor;
+using ::android::hardware::sensors::implementation::convertToSensorEvent;
+using ::android::hardware::sensors::implementation::convertFromSensorEvent;
+
 
 namespace android {
 
 namespace {
 
-status_t convertToStatus(ndk::ScopedAStatus status) {
-    if (status.isOk()) {
-        return OK;
-    } else {
-        switch (status.getExceptionCode()) {
-            case EX_ILLEGAL_ARGUMENT: {
-                return BAD_VALUE;
-            }
-            case EX_SECURITY: {
-                return PERMISSION_DENIED;
-            }
-            case EX_UNSUPPORTED_OPERATION: {
-                return INVALID_OPERATION;
-            }
-            case EX_SERVICE_SPECIFIC: {
-                switch (status.getServiceSpecificError()) {
-                    case ISensors::ERROR_BAD_VALUE: {
-                        return BAD_VALUE;
-                    }
-                    case ISensors::ERROR_NO_MEMORY: {
-                        return NO_MEMORY;
-                    }
-                    default: {
-                        return UNKNOWN_ERROR;
-                    }
-                }
-            }
-            default: {
-                return UNKNOWN_ERROR;
-            }
-        }
-    }
-}
-
-void convertToSensor(const SensorInfo &src, sensor_t *dst) {
-    dst->name = strdup(src.name.c_str());
-    dst->vendor = strdup(src.vendor.c_str());
-    dst->version = src.version;
-    dst->handle = src.sensorHandle;
-    dst->type = (int)src.type;
-    dst->maxRange = src.maxRange;
-    dst->resolution = src.resolution;
-    dst->power = src.power;
-    dst->minDelay = src.minDelayUs;
-    dst->fifoReservedEventCount = src.fifoReservedEventCount;
-    dst->fifoMaxEventCount = src.fifoMaxEventCount;
-    dst->stringType = strdup(src.typeAsString.c_str());
-    dst->requiredPermission = strdup(src.requiredPermission.c_str());
-    dst->maxDelay = src.maxDelayUs;
-    dst->flags = src.flags;
-    dst->reserved[0] = dst->reserved[1] = 0;
-}
-
-void convertToSensorEvent(const Event &src, sensors_event_t *dst) {
-    *dst = {.version = sizeof(sensors_event_t),
-            .sensor = src.sensorHandle,
-            .type = (int32_t)src.sensorType,
-            .reserved0 = 0,
-            .timestamp = src.timestamp};
-
-    switch (src.sensorType) {
-        case SensorType::META_DATA: {
-            // Legacy HALs expect the handle reference in the meta data field.
-            // Copy it over from the handle of the event.
-            dst->meta_data.what = (int32_t)src.payload.get<Event::EventPayload::meta>().what;
-            dst->meta_data.sensor = src.sensorHandle;
-            // Set the sensor handle to 0 to maintain compatibility.
-            dst->sensor = 0;
-            break;
-        }
-
-        case SensorType::ACCELEROMETER:
-        case SensorType::MAGNETIC_FIELD:
-        case SensorType::ORIENTATION:
-        case SensorType::GYROSCOPE:
-        case SensorType::GRAVITY:
-        case SensorType::LINEAR_ACCELERATION: {
-            dst->acceleration.x = src.payload.get<Event::EventPayload::vec3>().x;
-            dst->acceleration.y = src.payload.get<Event::EventPayload::vec3>().y;
-            dst->acceleration.z = src.payload.get<Event::EventPayload::vec3>().z;
-            dst->acceleration.status = (int32_t)src.payload.get<Event::EventPayload::vec3>().status;
-            break;
-        }
-
-        case SensorType::GAME_ROTATION_VECTOR: {
-            dst->data[0] = src.payload.get<Event::EventPayload::vec4>().x;
-            dst->data[1] = src.payload.get<Event::EventPayload::vec4>().y;
-            dst->data[2] = src.payload.get<Event::EventPayload::vec4>().z;
-            dst->data[3] = src.payload.get<Event::EventPayload::vec4>().w;
-            break;
-        }
-
-        case SensorType::ROTATION_VECTOR:
-        case SensorType::GEOMAGNETIC_ROTATION_VECTOR: {
-            dst->data[0] = src.payload.get<Event::EventPayload::data>().values[0];
-            dst->data[1] = src.payload.get<Event::EventPayload::data>().values[1];
-            dst->data[2] = src.payload.get<Event::EventPayload::data>().values[2];
-            dst->data[3] = src.payload.get<Event::EventPayload::data>().values[3];
-            dst->data[4] = src.payload.get<Event::EventPayload::data>().values[4];
-            break;
-        }
-
-        case SensorType::MAGNETIC_FIELD_UNCALIBRATED:
-        case SensorType::GYROSCOPE_UNCALIBRATED:
-        case SensorType::ACCELEROMETER_UNCALIBRATED: {
-            dst->uncalibrated_gyro.x_uncalib = src.payload.get<Event::EventPayload::uncal>().x;
-            dst->uncalibrated_gyro.y_uncalib = src.payload.get<Event::EventPayload::uncal>().y;
-            dst->uncalibrated_gyro.z_uncalib = src.payload.get<Event::EventPayload::uncal>().z;
-            dst->uncalibrated_gyro.x_bias = src.payload.get<Event::EventPayload::uncal>().xBias;
-            dst->uncalibrated_gyro.y_bias = src.payload.get<Event::EventPayload::uncal>().yBias;
-            dst->uncalibrated_gyro.z_bias = src.payload.get<Event::EventPayload::uncal>().zBias;
-            break;
-        }
-
-        case SensorType::HINGE_ANGLE:
-        case SensorType::DEVICE_ORIENTATION:
-        case SensorType::LIGHT:
-        case SensorType::PRESSURE:
-        case SensorType::PROXIMITY:
-        case SensorType::RELATIVE_HUMIDITY:
-        case SensorType::AMBIENT_TEMPERATURE:
-        case SensorType::SIGNIFICANT_MOTION:
-        case SensorType::STEP_DETECTOR:
-        case SensorType::TILT_DETECTOR:
-        case SensorType::WAKE_GESTURE:
-        case SensorType::GLANCE_GESTURE:
-        case SensorType::PICK_UP_GESTURE:
-        case SensorType::WRIST_TILT_GESTURE:
-        case SensorType::STATIONARY_DETECT:
-        case SensorType::MOTION_DETECT:
-        case SensorType::HEART_BEAT:
-        case SensorType::LOW_LATENCY_OFFBODY_DETECT: {
-            dst->data[0] = src.payload.get<Event::EventPayload::scalar>();
-            break;
-        }
-
-        case SensorType::STEP_COUNTER: {
-            dst->u64.step_counter = src.payload.get<Event::EventPayload::stepCount>();
-            break;
-        }
-
-        case SensorType::HEART_RATE: {
-            dst->heart_rate.bpm = src.payload.get<Event::EventPayload::heartRate>().bpm;
-            dst->heart_rate.status =
-                    (int8_t)src.payload.get<Event::EventPayload::heartRate>().status;
-            break;
-        }
-
-        case SensorType::POSE_6DOF: { // 15 floats
-            for (size_t i = 0; i < 15; ++i) {
-                dst->data[i] = src.payload.get<Event::EventPayload::pose6DOF>().values[i];
-            }
-            break;
-        }
-
-        case SensorType::DYNAMIC_SENSOR_META: {
-            dst->dynamic_sensor_meta.connected =
-                    src.payload.get<Event::EventPayload::dynamic>().connected;
-            dst->dynamic_sensor_meta.handle =
-                    src.payload.get<Event::EventPayload::dynamic>().sensorHandle;
-            dst->dynamic_sensor_meta.sensor = NULL; // to be filled in later
-
-            memcpy(dst->dynamic_sensor_meta.uuid,
-                   src.payload.get<Event::EventPayload::dynamic>().uuid.values.data(), 16);
-
-            break;
-        }
-
-        case SensorType::ADDITIONAL_INFO: {
-            const AdditionalInfo &srcInfo = src.payload.get<Event::EventPayload::additional>();
-
-            additional_info_event_t *dstInfo = &dst->additional_info;
-            dstInfo->type = (int32_t)srcInfo.type;
-            dstInfo->serial = srcInfo.serial;
-
-            switch (srcInfo.payload.getTag()) {
-                case AdditionalInfo::AdditionalInfoPayload::Tag::dataInt32: {
-                    const auto &values =
-                            srcInfo.payload.get<AdditionalInfo::AdditionalInfoPayload::dataInt32>()
-                                    .values;
-                    CHECK_EQ(values.size() * sizeof(int32_t), sizeof(dstInfo->data_int32));
-                    memcpy(dstInfo->data_int32, values.data(), sizeof(dstInfo->data_int32));
-                    break;
-                }
-                case AdditionalInfo::AdditionalInfoPayload::Tag::dataFloat: {
-                    const auto &values =
-                            srcInfo.payload.get<AdditionalInfo::AdditionalInfoPayload::dataFloat>()
-                                    .values;
-                    CHECK_EQ(values.size() * sizeof(float), sizeof(dstInfo->data_float));
-                    memcpy(dstInfo->data_float, values.data(), sizeof(dstInfo->data_float));
-                    break;
-                }
-                default: {
-                    ALOGE("Invalid sensor additional info tag: %d", (int)srcInfo.payload.getTag());
-                }
-            }
-            break;
-        }
-
-        case SensorType::HEAD_TRACKER: {
-            const auto &ht = src.payload.get<Event::EventPayload::headTracker>();
-            dst->head_tracker.rx = ht.rx;
-            dst->head_tracker.ry = ht.ry;
-            dst->head_tracker.rz = ht.rz;
-            dst->head_tracker.vx = ht.vx;
-            dst->head_tracker.vy = ht.vy;
-            dst->head_tracker.vz = ht.vz;
-            dst->head_tracker.discontinuity_count = ht.discontinuityCount;
-            break;
-        }
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES:
-        case SensorType::GYROSCOPE_LIMITED_AXES:
-            dst->limited_axes_imu.x = src.payload.get<Event::EventPayload::limitedAxesImu>().x;
-            dst->limited_axes_imu.y = src.payload.get<Event::EventPayload::limitedAxesImu>().y;
-            dst->limited_axes_imu.z = src.payload.get<Event::EventPayload::limitedAxesImu>().z;
-            dst->limited_axes_imu.x_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImu>().xSupported;
-            dst->limited_axes_imu.y_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImu>().ySupported;
-            dst->limited_axes_imu.z_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImu>().zSupported;
-            break;
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
-        case SensorType::GYROSCOPE_LIMITED_AXES_UNCALIBRATED:
-            dst->limited_axes_imu_uncalibrated.x_uncalib =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().x;
-            dst->limited_axes_imu_uncalibrated.y_uncalib =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().y;
-            dst->limited_axes_imu_uncalibrated.z_uncalib =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().z;
-            dst->limited_axes_imu_uncalibrated.x_bias =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().xBias;
-            dst->limited_axes_imu_uncalibrated.y_bias =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().yBias;
-            dst->limited_axes_imu_uncalibrated.z_bias =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().zBias;
-            dst->limited_axes_imu_uncalibrated.x_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().xSupported;
-            dst->limited_axes_imu_uncalibrated.y_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().ySupported;
-            dst->limited_axes_imu_uncalibrated.z_supported =
-                    src.payload.get<Event::EventPayload::limitedAxesImuUncal>().zSupported;
-            break;
-
-        case SensorType::HEADING:
-            dst->heading.heading = src.payload.get<Event::EventPayload::heading>().heading;
-            dst->heading.accuracy = src.payload.get<Event::EventPayload::heading>().accuracy;
-            break;
-
-        default: {
-            CHECK_GE((int32_t)src.sensorType, (int32_t)SensorType::DEVICE_PRIVATE_BASE);
-
-            memcpy(dst->data, src.payload.get<Event::EventPayload::data>().values.data(),
-                   16 * sizeof(float));
-            break;
-        }
-    }
-}
-
-void convertFromSensorEvent(const sensors_event_t &src, Event *dst) {
-    *dst = {
-            .timestamp = src.timestamp,
-            .sensorHandle = src.sensor,
-            .sensorType = (SensorType)src.type,
-    };
-
-    switch (dst->sensorType) {
-        case SensorType::META_DATA: {
-            Event::EventPayload::MetaData meta;
-            meta.what = (Event::EventPayload::MetaData::MetaDataEventType)src.meta_data.what;
-            // Legacy HALs contain the handle reference in the meta data field.
-            // Copy that over to the handle of the event. In legacy HALs this
-            // field was expected to be 0.
-            dst->sensorHandle = src.meta_data.sensor;
-            dst->payload.set<Event::EventPayload::Tag::meta>(meta);
-            break;
-        }
-
-        case SensorType::ACCELEROMETER:
-        case SensorType::MAGNETIC_FIELD:
-        case SensorType::ORIENTATION:
-        case SensorType::GYROSCOPE:
-        case SensorType::GRAVITY:
-        case SensorType::LINEAR_ACCELERATION: {
-            Event::EventPayload::Vec3 vec3;
-            vec3.x = src.acceleration.x;
-            vec3.y = src.acceleration.y;
-            vec3.z = src.acceleration.z;
-            vec3.status = (SensorStatus)src.acceleration.status;
-            dst->payload.set<Event::EventPayload::Tag::vec3>(vec3);
-            break;
-        }
-
-        case SensorType::GAME_ROTATION_VECTOR: {
-            Event::EventPayload::Vec4 vec4;
-            vec4.x = src.data[0];
-            vec4.y = src.data[1];
-            vec4.z = src.data[2];
-            vec4.w = src.data[3];
-            dst->payload.set<Event::EventPayload::Tag::vec4>(vec4);
-            break;
-        }
-
-        case SensorType::ROTATION_VECTOR:
-        case SensorType::GEOMAGNETIC_ROTATION_VECTOR: {
-            Event::EventPayload::Data data;
-            memcpy(data.values.data(), src.data, 5 * sizeof(float));
-            dst->payload.set<Event::EventPayload::Tag::data>(data);
-            break;
-        }
-
-        case SensorType::MAGNETIC_FIELD_UNCALIBRATED:
-        case SensorType::GYROSCOPE_UNCALIBRATED:
-        case SensorType::ACCELEROMETER_UNCALIBRATED: {
-            Event::EventPayload::Uncal uncal;
-            uncal.x = src.uncalibrated_gyro.x_uncalib;
-            uncal.y = src.uncalibrated_gyro.y_uncalib;
-            uncal.z = src.uncalibrated_gyro.z_uncalib;
-            uncal.xBias = src.uncalibrated_gyro.x_bias;
-            uncal.yBias = src.uncalibrated_gyro.y_bias;
-            uncal.zBias = src.uncalibrated_gyro.z_bias;
-            dst->payload.set<Event::EventPayload::Tag::uncal>(uncal);
-            break;
-        }
-
-        case SensorType::DEVICE_ORIENTATION:
-        case SensorType::LIGHT:
-        case SensorType::PRESSURE:
-        case SensorType::PROXIMITY:
-        case SensorType::RELATIVE_HUMIDITY:
-        case SensorType::AMBIENT_TEMPERATURE:
-        case SensorType::SIGNIFICANT_MOTION:
-        case SensorType::STEP_DETECTOR:
-        case SensorType::TILT_DETECTOR:
-        case SensorType::WAKE_GESTURE:
-        case SensorType::GLANCE_GESTURE:
-        case SensorType::PICK_UP_GESTURE:
-        case SensorType::WRIST_TILT_GESTURE:
-        case SensorType::STATIONARY_DETECT:
-        case SensorType::MOTION_DETECT:
-        case SensorType::HEART_BEAT:
-        case SensorType::LOW_LATENCY_OFFBODY_DETECT:
-        case SensorType::HINGE_ANGLE: {
-            dst->payload.set<Event::EventPayload::Tag::scalar>((float)src.data[0]);
-            break;
-        }
-
-        case SensorType::STEP_COUNTER: {
-            dst->payload.set<Event::EventPayload::Tag::stepCount>(src.u64.step_counter);
-            break;
-        }
-
-        case SensorType::HEART_RATE: {
-            Event::EventPayload::HeartRate heartRate;
-            heartRate.bpm = src.heart_rate.bpm;
-            heartRate.status = (SensorStatus)src.heart_rate.status;
-            dst->payload.set<Event::EventPayload::Tag::heartRate>(heartRate);
-            break;
-        }
-
-        case SensorType::POSE_6DOF: { // 15 floats
-            Event::EventPayload::Pose6Dof pose6DOF;
-            for (size_t i = 0; i < 15; ++i) {
-                pose6DOF.values[i] = src.data[i];
-            }
-            dst->payload.set<Event::EventPayload::Tag::pose6DOF>(pose6DOF);
-            break;
-        }
-
-        case SensorType::DYNAMIC_SENSOR_META: {
-            DynamicSensorInfo dynamic;
-            dynamic.connected = src.dynamic_sensor_meta.connected;
-            dynamic.sensorHandle = src.dynamic_sensor_meta.handle;
-
-            memcpy(dynamic.uuid.values.data(), src.dynamic_sensor_meta.uuid, 16);
-            dst->payload.set<Event::EventPayload::Tag::dynamic>(dynamic);
-            break;
-        }
-
-        case SensorType::ADDITIONAL_INFO: {
-            AdditionalInfo info;
-            const additional_info_event_t &srcInfo = src.additional_info;
-            info.type = (AdditionalInfo::AdditionalInfoType)srcInfo.type;
-            info.serial = srcInfo.serial;
-
-            AdditionalInfo::AdditionalInfoPayload::Int32Values data;
-            CHECK_EQ(data.values.size() * sizeof(int32_t), sizeof(srcInfo.data_int32));
-            memcpy(data.values.data(), srcInfo.data_int32, sizeof(srcInfo.data_int32));
-            info.payload.set<AdditionalInfo::AdditionalInfoPayload::Tag::dataInt32>(data);
-
-            dst->payload.set<Event::EventPayload::Tag::additional>(info);
-            break;
-        }
-
-        case SensorType::HEAD_TRACKER: {
-            Event::EventPayload::HeadTracker headTracker;
-            headTracker.rx = src.head_tracker.rx;
-            headTracker.ry = src.head_tracker.ry;
-            headTracker.rz = src.head_tracker.rz;
-            headTracker.vx = src.head_tracker.vx;
-            headTracker.vy = src.head_tracker.vy;
-            headTracker.vz = src.head_tracker.vz;
-            headTracker.discontinuityCount = src.head_tracker.discontinuity_count;
-
-            dst->payload.set<Event::EventPayload::Tag::headTracker>(headTracker);
-            break;
-        }
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES:
-        case SensorType::GYROSCOPE_LIMITED_AXES: {
-            Event::EventPayload::LimitedAxesImu limitedAxesImu;
-            limitedAxesImu.x = src.limited_axes_imu.x;
-            limitedAxesImu.y = src.limited_axes_imu.y;
-            limitedAxesImu.z = src.limited_axes_imu.z;
-            limitedAxesImu.xSupported = src.limited_axes_imu.x_supported;
-            limitedAxesImu.ySupported = src.limited_axes_imu.y_supported;
-            limitedAxesImu.zSupported = src.limited_axes_imu.z_supported;
-            dst->payload.set<Event::EventPayload::Tag::limitedAxesImu>(limitedAxesImu);
-            break;
-        }
-
-        case SensorType::ACCELEROMETER_LIMITED_AXES_UNCALIBRATED:
-        case SensorType::GYROSCOPE_LIMITED_AXES_UNCALIBRATED: {
-            Event::EventPayload::LimitedAxesImuUncal limitedAxesImuUncal;
-            limitedAxesImuUncal.x = src.limited_axes_imu_uncalibrated.x_uncalib;
-            limitedAxesImuUncal.y = src.limited_axes_imu_uncalibrated.y_uncalib;
-            limitedAxesImuUncal.z = src.limited_axes_imu_uncalibrated.z_uncalib;
-            limitedAxesImuUncal.xBias = src.limited_axes_imu_uncalibrated.x_bias;
-            limitedAxesImuUncal.yBias = src.limited_axes_imu_uncalibrated.y_bias;
-            limitedAxesImuUncal.zBias = src.limited_axes_imu_uncalibrated.z_bias;
-            limitedAxesImuUncal.xSupported = src.limited_axes_imu_uncalibrated.x_supported;
-            limitedAxesImuUncal.ySupported = src.limited_axes_imu_uncalibrated.y_supported;
-            limitedAxesImuUncal.zSupported = src.limited_axes_imu_uncalibrated.z_supported;
-            dst->payload.set<Event::EventPayload::Tag::limitedAxesImuUncal>(limitedAxesImuUncal);
-            break;
-        }
-
-        case SensorType::HEADING: {
-            Event::EventPayload::Heading heading;
-            heading.heading = src.heading.heading;
-            heading.accuracy = src.heading.accuracy;
-            dst->payload.set<Event::EventPayload::heading>(heading);
-            break;
-        }
-
-        default: {
-            CHECK_GE((int32_t)dst->sensorType, (int32_t)SensorType::DEVICE_PRIVATE_BASE);
-
-            Event::EventPayload::Data data;
-            memcpy(data.values.data(), src.data, 16 * sizeof(float));
-            dst->payload.set<Event::EventPayload::Tag::data>(data);
-            break;
-        }
-    }
-}
-
 void serviceDied(void *cookie) {
     ALOGW("Sensors HAL died, attempting to reconnect.");
     ((AidlSensorHalWrapper *)cookie)->prepareForReconnect();
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index 5ad4815..0eeb820 100644
--- a/services/sensorservice/Android.bp
+++ b/services/sensorservice/Android.bp
@@ -75,6 +75,7 @@
     static_libs: [
         "libaidlcommonsupport",
         "android.hardware.sensors@1.0-convert",
+        "android.hardware.sensors-V1-convert",
         "android.hardware.sensors-V1-ndk",
     ],
 
diff --git a/services/sensorservice/aidl/Android.bp b/services/sensorservice/aidl/Android.bp
new file mode 100644
index 0000000..bbf49da
--- /dev/null
+++ b/services/sensorservice/aidl/Android.bp
@@ -0,0 +1,43 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_native_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_library_shared {
+    name: "libsensorserviceaidl",
+    srcs: [
+        "EventQueue.cpp",
+        "DirectReportChannel.cpp",
+        "SensorManager.cpp",
+        "utils.cpp",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    header_libs: ["jni_headers"],
+    shared_libs: [
+        "libbase",
+        "libutils",
+        "libcutils",
+        "libbinder_ndk",
+        "libsensor",
+        "android.frameworks.sensorservice-V1-ndk",
+        "android.hardware.sensors-V1-ndk",
+    ],
+    export_include_dirs: [
+        "include/",
+    ],
+    static_libs: [
+        "android.hardware.sensors-V1-convert",
+    ],
+
+    export_header_lib_headers: ["jni_headers"],
+    local_include_dirs: [
+        "include/sensorserviceaidl/",
+    ],
+}
diff --git a/services/sensorservice/aidl/DirectReportChannel.cpp b/services/sensorservice/aidl/DirectReportChannel.cpp
new file mode 100644
index 0000000..cab53c1
--- /dev/null
+++ b/services/sensorservice/aidl/DirectReportChannel.cpp
@@ -0,0 +1,45 @@
+/*
+ * 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 "DirectReportChannel.h"
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+DirectReportChannel::DirectReportChannel(::android::SensorManager& manager, int channelId)
+      : mManager(manager), mId(channelId) {}
+
+DirectReportChannel::~DirectReportChannel() {
+    mManager.destroyDirectChannel(mId);
+}
+
+ndk::ScopedAStatus DirectReportChannel::configure(
+        int32_t sensorHandle, ::aidl::android::hardware::sensors::ISensors::RateLevel rate,
+        int32_t* _aidl_return) {
+    int token = mManager.configureDirectChannel(mId, sensorHandle, static_cast<int>(rate));
+    if (token <= 0) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(token);
+    }
+    *_aidl_return = token;
+    return ndk::ScopedAStatus::ok();
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/DirectReportChannel.h b/services/sensorservice/aidl/DirectReportChannel.h
new file mode 100644
index 0000000..d9ea73d
--- /dev/null
+++ b/services/sensorservice/aidl/DirectReportChannel.h
@@ -0,0 +1,45 @@
+/*
+ * 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/frameworks/sensorservice/BnDirectReportChannel.h>
+#include <aidl/android/hardware/sensors/ISensors.h>
+#include <sensor/SensorManager.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+class DirectReportChannel final
+      : public ::aidl::android::frameworks::sensorservice::BnDirectReportChannel {
+public:
+    DirectReportChannel(::android::SensorManager& manager, int channelId);
+    ~DirectReportChannel();
+
+    ndk::ScopedAStatus configure(int32_t sensorHandle,
+                                 ::aidl::android::hardware::sensors::ISensors::RateLevel rate,
+                                 int32_t* _aidl_return) override;
+
+private:
+    ::android::SensorManager& mManager;
+    const int mId;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/EventQueue.cpp b/services/sensorservice/aidl/EventQueue.cpp
new file mode 100644
index 0000000..d4e8906
--- /dev/null
+++ b/services/sensorservice/aidl/EventQueue.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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 "EventQueue.h"
+#include "utils.h"
+
+#include <android-base/logging.h>
+#include <utils/Looper.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+using ::aidl::android::frameworks::sensorservice::IEventQueueCallback;
+using ::aidl::android::hardware::sensors::Event;
+
+class EventQueueLooperCallback : public ::android::LooperCallback {
+public:
+    EventQueueLooperCallback(sp<::android::SensorEventQueue> queue,
+                             std::shared_ptr<IEventQueueCallback> callback)
+          : mQueue(queue), mCallback(callback) {}
+
+    int handleEvent(__unused int fd, __unused int events, __unused void* data) {
+        ASensorEvent event;
+        ssize_t actual;
+
+        auto internalQueue = mQueue.promote();
+        if (internalQueue == nullptr) {
+            return 1;
+        }
+
+        while ((actual = internalQueue->read(&event, 1)) > 0) {
+            internalQueue->sendAck(&event, actual);
+            ndk::ScopedAStatus ret = mCallback->onEvent(convertEvent(event));
+            if (!ret.isOk()) {
+                LOG(ERROR) << "Failed to envoke EventQueueCallback: " << ret;
+            }
+        }
+
+        return 1; // continue to receive callbacks
+    }
+
+private:
+    wp<::android::SensorEventQueue> mQueue;
+    std::shared_ptr<IEventQueueCallback> mCallback;
+};
+
+EventQueue::EventQueue(std::shared_ptr<IEventQueueCallback> callback, sp<::android::Looper> looper,
+                       sp<::android::SensorEventQueue> internalQueue)
+      : mLooper(looper), mInternalQueue(internalQueue) {
+    mLooper->addFd(internalQueue->getFd(), ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT,
+                   new EventQueueLooperCallback(internalQueue, callback), nullptr);
+}
+
+// FIXME why was this on onLastStrongRef instead of dtor?
+EventQueue::~EventQueue() {
+    mLooper->removeFd(mInternalQueue->getFd());
+}
+
+ndk::ScopedAStatus EventQueue::enableSensor(int32_t in_sensorHandle, int32_t in_samplingPeriodUs,
+                                            int64_t in_maxBatchReportLatencyUs) {
+    return convertResult(mInternalQueue->enableSensor(in_sensorHandle, in_samplingPeriodUs,
+                                                      in_maxBatchReportLatencyUs, 0));
+}
+
+ndk::ScopedAStatus EventQueue::disableSensor(int32_t in_sensorHandle) {
+    return convertResult(mInternalQueue->disableSensor(in_sensorHandle));
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/EventQueue.h b/services/sensorservice/aidl/EventQueue.h
new file mode 100644
index 0000000..0ae1eba
--- /dev/null
+++ b/services/sensorservice/aidl/EventQueue.h
@@ -0,0 +1,47 @@
+/*
+ * 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 "SensorManagerAidl.h"
+
+#include <aidl/android/frameworks/sensorservice/BnEventQueue.h>
+#include <sensor/SensorManager.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+struct EventQueue final : public aidl::android::frameworks::sensorservice::BnEventQueue {
+    EventQueue(
+            std::shared_ptr<aidl::android::frameworks::sensorservice::IEventQueueCallback> callback,
+            sp<::android::Looper> looper, sp<::android::SensorEventQueue> internalQueue);
+    ~EventQueue();
+
+    ndk::ScopedAStatus enableSensor(int32_t in_sensorHandle, int32_t in_samplingPeriodUs,
+                                    int64_t in_maxBatchReportLatencyUs) override;
+    ndk::ScopedAStatus disableSensor(int32_t sensorHandle) override;
+
+private:
+    friend class EventQueueLooperCallback;
+    sp<::android::Looper> mLooper;
+    sp<::android::SensorEventQueue> mInternalQueue;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/SensorManager.cpp b/services/sensorservice/aidl/SensorManager.cpp
new file mode 100644
index 0000000..6d8d574
--- /dev/null
+++ b/services/sensorservice/aidl/SensorManager.cpp
@@ -0,0 +1,254 @@
+/*
+ * 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.
+ */
+
+// LOG_TAG defined via build flag.
+#ifndef LOG_TAG
+#define LOG_TAG "AidlSensorManager"
+#endif
+
+#include "DirectReportChannel.h"
+#include "EventQueue.h"
+#include "SensorManagerAidl.h"
+#include "utils.h"
+
+#include <aidl/android/hardware/sensors/ISensors.h>
+#include <android-base/logging.h>
+#include <android/binder_ibinder.h>
+#include <sched.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+using ::aidl::android::frameworks::sensorservice::IDirectReportChannel;
+using ::aidl::android::frameworks::sensorservice::IEventQueue;
+using ::aidl::android::frameworks::sensorservice::IEventQueueCallback;
+using ::aidl::android::frameworks::sensorservice::ISensorManager;
+using ::aidl::android::hardware::common::Ashmem;
+using ::aidl::android::hardware::sensors::ISensors;
+using ::aidl::android::hardware::sensors::SensorInfo;
+using ::aidl::android::hardware::sensors::SensorType;
+using ::android::frameworks::sensorservice::implementation::SensorManagerAidl;
+
+static const char* POLL_THREAD_NAME = "aidl_ssvc_poll";
+
+SensorManagerAidl::SensorManagerAidl(JavaVM* vm)
+      : mLooper(new Looper(false)), mStopThread(true), mJavaVm(vm) {}
+SensorManagerAidl::~SensorManagerAidl() {
+    // Stops pollAll inside the thread.
+    std::lock_guard<std::mutex> lock(mThreadMutex);
+
+    mStopThread = true;
+    if (mLooper != nullptr) {
+        mLooper->wake();
+    }
+    if (mPollThread.joinable()) {
+        mPollThread.join();
+    }
+}
+
+ndk::ScopedAStatus createDirectChannel(::android::SensorManager& manager, size_t size, int type,
+                                       const native_handle_t* handle,
+                                       std::shared_ptr<IDirectReportChannel>* chan) {
+    int channelId = manager.createDirectChannel(size, type, handle);
+    if (channelId < 0) {
+        return convertResult(channelId);
+    }
+    if (channelId == 0) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+    *chan = ndk::SharedRefBase::make<DirectReportChannel>(manager, channelId);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createAshmemDirectChannel(
+        const Ashmem& in_mem, int64_t in_size,
+        std::shared_ptr<IDirectReportChannel>* _aidl_return) {
+    if (in_size > in_mem.size || in_size < ISensors::DIRECT_REPORT_SENSOR_EVENT_TOTAL_LENGTH) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_BAD_VALUE);
+    }
+    native_handle_t* handle = native_handle_create(1, 0);
+    handle->data[0] = dup(in_mem.fd.get());
+
+    auto status = createDirectChannel(getInternalManager(), in_size, SENSOR_DIRECT_MEM_TYPE_ASHMEM,
+                                      handle, _aidl_return);
+    int result = native_handle_close(handle);
+    CHECK(result == 0) << "Failed to close the native_handle_t: " << result;
+    result = native_handle_delete(handle);
+    CHECK(result == 0) << "Failed to delete the native_handle_t: " << result;
+
+    return status;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createGrallocDirectChannel(
+        const ndk::ScopedFileDescriptor& in_mem, int64_t in_size,
+        std::shared_ptr<IDirectReportChannel>* _aidl_return) {
+    native_handle_t* handle = native_handle_create(1, 0);
+    handle->data[0] = dup(in_mem.get());
+
+    auto status = createDirectChannel(getInternalManager(), in_size, SENSOR_DIRECT_MEM_TYPE_GRALLOC,
+                                      handle, _aidl_return);
+    int result = native_handle_close(handle);
+    CHECK(result == 0) << "Failed to close the native_handle_t: " << result;
+    result = native_handle_delete(handle);
+    CHECK(result == 0) << "Failed to delete the native_handle_t: " << result;
+
+    return status;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::createEventQueue(
+        const std::shared_ptr<IEventQueueCallback>& in_callback,
+        std::shared_ptr<IEventQueue>* _aidl_return) {
+    if (in_callback == nullptr) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_BAD_VALUE);
+    }
+
+    sp<::android::Looper> looper = getLooper();
+    if (looper == nullptr) {
+        LOG(ERROR) << "::android::SensorManagerAidl::createEventQueue cannot initialize looper";
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+
+    String8 package(String8::format("aidl_client_pid_%d", AIBinder_getCallingPid()));
+    sp<::android::SensorEventQueue> internalQueue = getInternalManager().createEventQueue(package);
+    if (internalQueue == nullptr) {
+        LOG(ERROR) << "::android::SensorManagerAidl::createEventQueue returns nullptr.";
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+
+    *_aidl_return = ndk::SharedRefBase::make<EventQueue>(in_callback, looper, internalQueue);
+
+    return ndk::ScopedAStatus::ok();
+}
+
+SensorInfo convertSensor(Sensor src) {
+    SensorInfo dst;
+    dst.sensorHandle = src.getHandle();
+    dst.name = src.getName();
+    dst.vendor = src.getVendor();
+    dst.version = src.getVersion();
+    dst.type = static_cast<SensorType>(src.getType());
+    dst.typeAsString = src.getStringType();
+    // maxRange uses maxValue because ::android::Sensor wraps the
+    // internal sensor_t in this way.
+    dst.maxRange = src.getMaxValue();
+    dst.resolution = src.getResolution();
+    dst.power = src.getPowerUsage();
+    dst.minDelayUs = src.getMinDelay();
+    dst.fifoReservedEventCount = src.getFifoReservedEventCount();
+    dst.fifoMaxEventCount = src.getFifoMaxEventCount();
+    dst.requiredPermission = src.getRequiredPermission();
+    dst.maxDelayUs = src.getMaxDelay();
+    dst.flags = src.getFlags();
+    return dst;
+}
+
+ndk::ScopedAStatus SensorManagerAidl::getDefaultSensor(SensorType in_type,
+                                                       SensorInfo* _aidl_return) {
+    ::android::Sensor const* sensor =
+            getInternalManager().getDefaultSensor(static_cast<int>(in_type));
+    if (!sensor) {
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_NOT_EXIST);
+    }
+    *_aidl_return = convertSensor(*sensor);
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus SensorManagerAidl::getSensorList(std::vector<SensorInfo>* _aidl_return) {
+    Sensor const* const* list;
+    _aidl_return->clear();
+    ssize_t count = getInternalManager().getSensorList(&list);
+    if (count < 0 || list == nullptr) {
+        LOG(ERROR) << "SensorMAanger::getSensorList failed with count: " << count;
+        return ndk::ScopedAStatus::fromServiceSpecificError(ISensorManager::RESULT_UNKNOWN_ERROR);
+    }
+    _aidl_return->reserve(static_cast<size_t>(count));
+    for (ssize_t i = 0; i < count; ++i) {
+        _aidl_return->push_back(convertSensor(*list[i]));
+    }
+
+    return ndk::ScopedAStatus::ok();
+}
+
+::android::SensorManager& SensorManagerAidl::getInternalManager() {
+    std::lock_guard<std::mutex> lock(mInternalManagerMutex);
+    if (mInternalManager == nullptr) {
+        mInternalManager = &::android::SensorManager::getInstanceForPackage(
+                String16(ISensorManager::descriptor));
+    }
+    return *mInternalManager;
+}
+
+/* One global looper for all event queues created from this SensorManager. */
+sp<Looper> SensorManagerAidl::getLooper() {
+    std::lock_guard<std::mutex> lock(mThreadMutex);
+
+    if (!mPollThread.joinable()) {
+        // if thread not initialized, start thread
+        mStopThread = false;
+        std::thread pollThread{[&stopThread = mStopThread, looper = mLooper, javaVm = mJavaVm] {
+            struct sched_param p = {0};
+            p.sched_priority = 10;
+            if (sched_setscheduler(0 /* current thread*/, SCHED_FIFO, &p) != 0) {
+                LOG(ERROR) << "Could not use SCHED_FIFO for looper thread: " << strerror(errno);
+            }
+
+            // set looper
+            Looper::setForThread(looper);
+
+            // Attach the thread to JavaVM so that pollAll do not crash if the thread
+            // eventually calls into Java.
+            JavaVMAttachArgs args{.version = JNI_VERSION_1_2,
+                                  .name = POLL_THREAD_NAME,
+                                  .group = nullptr};
+            JNIEnv* env;
+            if (javaVm->AttachCurrentThread(&env, &args) != JNI_OK) {
+                LOG(FATAL) << "Cannot attach SensorManager looper thread to Java VM.";
+            }
+
+            LOG(INFO) << POLL_THREAD_NAME << " started.";
+            for (;;) {
+                int pollResult = looper->pollAll(-1 /* timeout */);
+                if (pollResult == Looper::POLL_WAKE) {
+                    if (stopThread == true) {
+                        LOG(INFO) << POLL_THREAD_NAME << ": requested to stop";
+                        break;
+                    } else {
+                        LOG(INFO) << POLL_THREAD_NAME << ": spurious wake up, back to work";
+                    }
+                } else {
+                    LOG(ERROR) << POLL_THREAD_NAME << ": Looper::pollAll returns unexpected "
+                               << pollResult;
+                    break;
+                }
+            }
+
+            if (javaVm->DetachCurrentThread() != JNI_OK) {
+                LOG(ERROR) << "Cannot detach SensorManager looper thread from Java VM.";
+            }
+
+            LOG(INFO) << POLL_THREAD_NAME << " is terminated.";
+        }};
+        mPollThread = std::move(pollThread);
+    }
+    return mLooper;
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
new file mode 100644
index 0000000..c77ee88
--- /dev/null
+++ b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
@@ -0,0 +1,74 @@
+/*
+ * 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/frameworks/sensorservice/BnSensorManager.h>
+#include <jni.h>
+#include <sensor/SensorManager.h>
+#include <utils/Looper.h>
+#include <mutex>
+#include <thread>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+class SensorManagerAidl : public ::aidl::android::frameworks::sensorservice::BnSensorManager {
+public:
+    explicit SensorManagerAidl(JavaVM* vm);
+    ~SensorManagerAidl();
+
+    ::ndk::ScopedAStatus createAshmemDirectChannel(
+            const ::aidl::android::hardware::common::Ashmem& in_mem, int64_t in_size,
+            std::shared_ptr<::aidl::android::frameworks::sensorservice::IDirectReportChannel>*
+                    _aidl_return) override;
+    ::ndk::ScopedAStatus createEventQueue(
+            const std::shared_ptr<::aidl::android::frameworks::sensorservice::IEventQueueCallback>&
+                    in_callback,
+            std::shared_ptr<::aidl::android::frameworks::sensorservice::IEventQueue>* _aidl_return)
+            override;
+    ::ndk::ScopedAStatus createGrallocDirectChannel(
+            const ::ndk::ScopedFileDescriptor& in_buffer, int64_t in_size,
+            std::shared_ptr<::aidl::android::frameworks::sensorservice::IDirectReportChannel>*
+                    _aidl_return) override;
+    ::ndk::ScopedAStatus getDefaultSensor(
+            ::aidl::android::hardware::sensors::SensorType in_type,
+            ::aidl::android::hardware::sensors::SensorInfo* _aidl_return) override;
+    ::ndk::ScopedAStatus getSensorList(
+            std::vector<::aidl::android::hardware::sensors::SensorInfo>* _aidl_return) override;
+
+private:
+    // Block until ::android::SensorManager is initialized.
+    ::android::SensorManager& getInternalManager();
+    sp<Looper> getLooper();
+
+    std::mutex mInternalManagerMutex;
+    ::android::SensorManager* mInternalManager = nullptr; // does not own
+    sp<Looper> mLooper;
+
+    volatile bool mStopThread;
+    std::mutex mThreadMutex; // protects mPollThread
+    std::thread mPollThread;
+
+    JavaVM* mJavaVm;
+};
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/utils.cpp b/services/sensorservice/aidl/utils.cpp
new file mode 100644
index 0000000..26bcdc5
--- /dev/null
+++ b/services/sensorservice/aidl/utils.cpp
@@ -0,0 +1,68 @@
+/*
+ * 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 "utils.h"
+
+#include <aidl/android/frameworks/sensorservice/ISensorManager.h>
+#include <aidl/sensors/convert.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+ndk::ScopedAStatus convertResult(status_t src) {
+    using ::aidl::android::frameworks::sensorservice::ISensorManager;
+
+    int err = 0;
+    switch (src) {
+        case OK:
+            return ndk::ScopedAStatus::ok();
+        case NAME_NOT_FOUND:
+            err = ISensorManager::RESULT_NOT_EXIST;
+            break;
+        case NO_MEMORY:
+            err = ISensorManager::RESULT_NO_MEMORY;
+            break;
+        case NO_INIT:
+            err = ISensorManager::RESULT_NO_INIT;
+            break;
+        case PERMISSION_DENIED:
+            err = ISensorManager::RESULT_PERMISSION_DENIED;
+            break;
+        case BAD_VALUE:
+            err = ISensorManager::RESULT_BAD_VALUE;
+            break;
+        case INVALID_OPERATION:
+            err = ISensorManager::RESULT_INVALID_OPERATION;
+            break;
+        default:
+            err = ISensorManager::RESULT_UNKNOWN_ERROR;
+    }
+    return ndk::ScopedAStatus::fromServiceSpecificError(err);
+}
+
+::aidl::android::hardware::sensors::Event convertEvent(const ::ASensorEvent& src) {
+    ::aidl::android::hardware::sensors::Event dst;
+    ::android::hardware::sensors::implementation::
+            convertFromSensorEvent(reinterpret_cast<const sensors_event_t&>(src), &dst);
+    return dst;
+}
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/sensorservice/aidl/utils.h b/services/sensorservice/aidl/utils.h
new file mode 100644
index 0000000..06ba59e
--- /dev/null
+++ b/services/sensorservice/aidl/utils.h
@@ -0,0 +1,32 @@
+/*
+ * 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/sensors/Event.h>
+#include <sensor/Sensor.h>
+
+namespace android {
+namespace frameworks {
+namespace sensorservice {
+namespace implementation {
+
+::ndk::ScopedAStatus convertResult(status_t status);
+::aidl::android::hardware::sensors::Event convertEvent(const ::ASensorEvent& event);
+
+} // namespace implementation
+} // namespace sensorservice
+} // namespace frameworks
+} // namespace android
diff --git a/services/surfaceflinger/DisplayDevice.cpp b/services/surfaceflinger/DisplayDevice.cpp
index 9868c8e..a5195f4 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -41,6 +41,7 @@
 
 #include "Display/DisplaySnapshot.h"
 #include "DisplayDevice.h"
+#include "FrontEnd/DisplayInfo.h"
 #include "Layer.h"
 #include "RefreshRateOverlay.h"
 #include "SurfaceFlinger.h"
@@ -131,7 +132,7 @@
     }
 }
 
-auto DisplayDevice::getInputInfo() const -> InputInfo {
+auto DisplayDevice::getFrontEndInfo() const -> frontend::DisplayInfo {
     gui::DisplayInfo info;
     info.displayId = getLayerStack().id;
 
@@ -160,7 +161,9 @@
     return {.info = info,
             .transform = displayTransform,
             .receivesInput = receivesInput(),
-            .isSecure = isSecure()};
+            .isSecure = isSecure(),
+            .isPrimary = isPrimary(),
+            .rotationFlags = ui::Transform::toRotationFlags(mOrientation)};
 }
 
 void DisplayDevice::setPowerMode(hal::PowerMode mode) {
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index 1602a71..4b64aab 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -45,11 +45,11 @@
 #include "DisplayHardware/DisplayMode.h"
 #include "DisplayHardware/Hal.h"
 #include "DisplayHardware/PowerAdvisor.h"
+#include "FrontEnd/DisplayInfo.h"
 #include "Scheduler/RefreshRateSelector.h"
 #include "ThreadContext.h"
 #include "TracedOrdinal.h"
 #include "Utils/Dumper.h"
-
 namespace android {
 
 class Fence;
@@ -167,14 +167,7 @@
     void setDisplayName(const std::string& displayName);
     const std::string& getDisplayName() const { return mDisplayName; }
 
-    struct InputInfo {
-        gui::DisplayInfo info;
-        ui::Transform transform;
-        bool receivesInput;
-        bool isSecure;
-    };
-
-    InputInfo getInputInfo() const;
+    surfaceflinger::frontend::DisplayInfo getFrontEndInfo() const;
 
     /* ------------------------------------------------------------------------
      * Display power mode management.
diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h
new file mode 100644
index 0000000..0c7b24a
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h
@@ -0,0 +1,34 @@
+/*
+ * 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 <gui/DisplayInfo.h>
+
+namespace android::surfaceflinger::frontend {
+
+// Display information needed to populate input and calculate layer geometry.
+struct DisplayInfo {
+    gui::DisplayInfo info;
+    ui::Transform transform;
+    bool receivesInput;
+    bool isSecure;
+    // TODO(b/238781169) can eliminate once sPrimaryDisplayRotationFlags is removed.
+    bool isPrimary;
+    ui::Transform::RotationFlags rotationFlags;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
index 95961fe..8629671 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -24,7 +24,7 @@
 
 #include "TransactionHandler.h"
 
-namespace android {
+namespace android::surfaceflinger::frontend {
 
 void TransactionHandler::queueTransaction(TransactionState&& state) {
     mLocklessTransactionQueue.push(std::move(state));
@@ -186,4 +186,4 @@
         mStalledTransactions.erase(it);
     }
 }
-} // namespace android
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
index 2b6f07d..a06b870 100644
--- a/services/surfaceflinger/FrontEnd/TransactionHandler.h
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -18,9 +18,6 @@
 
 #include <semaphore.h>
 #include <cstdint>
-#include <mutex>
-#include <queue>
-#include <thread>
 #include <vector>
 
 #include <LocklessQueue.h>
@@ -30,6 +27,10 @@
 #include <ftl/small_vector.h>
 
 namespace android {
+
+class TestableSurfaceFlinger;
+namespace surfaceflinger::frontend {
+
 class TransactionHandler {
 public:
     struct TransactionFlushState {
@@ -60,7 +61,7 @@
 
 private:
     // For unit tests
-    friend class TestableSurfaceFlinger;
+    friend class ::android::TestableSurfaceFlinger;
 
     int flushPendingTransactionQueues(std::vector<TransactionState>&, TransactionFlushState&);
     TransactionReadiness applyFilters(TransactionFlushState&);
@@ -71,5 +72,5 @@
     ftl::SmallVector<TransactionFilter, 2> mTransactionReadyFilters;
     std::vector<uint64_t> mStalledTransactions;
 };
-
+} // namespace surfaceflinger::frontend
 } // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 37eb20b..d53f0b1 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -3973,17 +3973,14 @@
 }
 
 LayerSnapshotGuard::LayerSnapshotGuard(Layer* layer) : mLayer(layer) {
-    LOG_ALWAYS_FATAL_IF(!mLayer, "LayerSnapshotGuard received a null layer.");
-    mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot);
-    LOG_ALWAYS_FATAL_IF(!mLayer->mLayerFE->mSnapshot,
-                        "LayerFE snapshot null after taking ownership from layer");
+    if (mLayer) {
+        mLayer->mLayerFE->mSnapshot = std::move(mLayer->mSnapshot);
+    }
 }
 
 LayerSnapshotGuard::~LayerSnapshotGuard() {
     if (mLayer) {
         mLayer->mSnapshot = std::move(mLayer->mLayerFE->mSnapshot);
-        LOG_ALWAYS_FATAL_IF(!mLayer->mSnapshot,
-                            "Layer snapshot null after taking ownership from LayerFE");
     }
 }
 
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index a723a7d..032541c 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -180,6 +180,7 @@
 using base::StringAppendF;
 using display::PhysicalDisplay;
 using display::PhysicalDisplays;
+using frontend::TransactionHandler;
 using gui::DisplayInfo;
 using gui::GameMode;
 using gui::IDisplayEventConnection;
@@ -2190,14 +2191,13 @@
 
     refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
     refreshArgs.updatingGeometryThisFrame = mGeometryDirty.exchange(false) || mVisibleRegionsDirty;
-    std::vector<sp<Layer>> layers;
+    std::vector<Layer*> layers;
 
     mDrawingState.traverseInZOrder([&refreshArgs, &layers](Layer* layer) {
-        auto strongLayer = sp<Layer>::fromExisting(layer);
         if (auto layerFE = layer->getCompositionEngineLayerFE()) {
             layer->updateSnapshot(refreshArgs.updatingGeometryThisFrame);
             refreshArgs.layers.push_back(layerFE);
-            layers.push_back(std::move(strongLayer));
+            layers.push_back(layer);
         }
     });
     refreshArgs.blursAreExpensive = mBlursAreExpensive;
@@ -2229,8 +2229,8 @@
 
     {
         std::vector<LayerSnapshotGuard> layerSnapshotGuards;
-        for (auto& layer : layers) {
-            layerSnapshotGuards.emplace_back(layer.get());
+        for (Layer* layer : layers) {
+            layerSnapshotGuards.emplace_back(layer);
         }
         mCompositionEngine->present(refreshArgs);
     }
@@ -3128,6 +3128,10 @@
     const bool displayTransactionNeeded = transactionFlags & eDisplayTransactionNeeded;
     if (displayTransactionNeeded) {
         processDisplayChangesLocked();
+        mFrontEndDisplayInfos.clear();
+        for (const auto& [_, display] : mDisplays) {
+            mFrontEndDisplayInfos.try_emplace(display->getLayerStack(), display->getFrontEndInfo());
+        }
     }
     mForceTransactionDisplayChange = displayTransactionNeeded;
 
@@ -3297,29 +3301,6 @@
 
 void SurfaceFlinger::buildWindowInfos(std::vector<WindowInfo>& outWindowInfos,
                                       std::vector<DisplayInfo>& outDisplayInfos) {
-    display::DisplayMap<ui::LayerStack, DisplayDevice::InputInfo> displayInputInfos;
-
-    for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
-        const auto layerStack = display->getLayerStack();
-        const auto info = display->getInputInfo();
-
-        const auto [it, emplaced] = displayInputInfos.try_emplace(layerStack, info);
-        if (emplaced) {
-            continue;
-        }
-
-        // If the layer stack is mirrored on multiple displays, the first display that is configured
-        // to receive input takes precedence.
-        auto& otherInfo = it->second;
-        if (otherInfo.receivesInput) {
-            ALOGW_IF(display->receivesInput(),
-                     "Multiple displays claim to accept input for the same layer stack: %u",
-                     layerStack.id);
-        } else {
-            otherInfo = info;
-        }
-    }
-
     static size_t sNumWindowInfos = 0;
     outWindowInfos.reserve(sNumWindowInfos);
     sNumWindowInfos = 0;
@@ -3327,8 +3308,8 @@
     mDrawingState.traverseInReverseZOrder([&](Layer* layer) {
         if (!layer->needsInputInfo()) return;
 
-        const auto opt = displayInputInfos.get(layer->getLayerStack())
-                                 .transform([](const DisplayDevice::InputInfo& info) {
+        const auto opt = mFrontEndDisplayInfos.get(layer->getLayerStack())
+                                 .transform([](const frontend::DisplayInfo& info) {
                                      return Layer::InputDisplayArgs{&info.transform, info.isSecure};
                                  });
 
@@ -3337,8 +3318,8 @@
 
     sNumWindowInfos = outWindowInfos.size();
 
-    outDisplayInfos.reserve(displayInputInfos.size());
-    for (const auto& [_, info] : displayInputInfos) {
+    outDisplayInfos.reserve(mFrontEndDisplayInfos.size());
+    for (const auto& [_, info] : mFrontEndDisplayInfos) {
         outDisplayInfos.push_back(info.info);
     }
 }
@@ -3353,12 +3334,7 @@
 
     std::vector<LayerSnapshotGuard> layerSnapshotGuards;
     mDrawingState.traverse([&layerSnapshotGuards](Layer* layer) {
-        auto strongLayer = sp<Layer>::fromExisting(layer);
-        const LayerSnapshot* snapshot = layer->getLayerSnapshot();
-        if (!snapshot) {
-            LOG_ALWAYS_FATAL("Layer snapshot unexpectedly null");
-        }
-        if (snapshot->compositionType ==
+        if (layer->getLayerSnapshot()->compositionType ==
             aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
             layer->updateSnapshot(false /* updateGeometry */);
             layerSnapshotGuards.emplace_back(layer);
@@ -6415,8 +6391,10 @@
                 std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get();
                 if (!renderArea) {
                     ALOGW("Skipping screen capture because of invalid render area.");
-                    captureResults.fenceResult = base::unexpected(NO_MEMORY);
-                    captureListener->onScreenCaptureCompleted(captureResults);
+                    if (captureListener) {
+                        captureResults.fenceResult = base::unexpected(NO_MEMORY);
+                        captureListener->onScreenCaptureCompleted(captureResults);
+                    }
                     return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
                 }
 
@@ -6502,7 +6480,6 @@
     layers.reserve(layerCount);
     std::unordered_set<compositionengine::LayerFE*> filterForScreenshot;
     traverseLayers([&](Layer* layer) {
-        auto strongLayer = sp<Layer>::fromExisting(layer);
         captureResults.capturedHdrLayers |= isHdrLayer(layer);
         // Layer::prepareClientComposition uses the layer's snapshot to populate the resulting
         // LayerSettings. Calling Layer::updateSnapshot ensures that LayerSettings are
diff --git a/services/surfaceflinger/SurfaceFlinger.h b/services/surfaceflinger/SurfaceFlinger.h
index c2d3343..50e8e73 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -66,6 +66,7 @@
 #include "DisplayIdGenerator.h"
 #include "Effects/Daltonizer.h"
 #include "FlagManager.h"
+#include "FrontEnd/DisplayInfo.h"
 #include "FrontEnd/LayerCreationArgs.h"
 #include "FrontEnd/TransactionHandler.h"
 #include "LayerVector.h"
@@ -120,6 +121,7 @@
 class ScreenCapturer;
 class WindowInfosListenerInvoker;
 
+using frontend::TransactionHandler;
 using gui::CaptureArgs;
 using gui::DisplayCaptureArgs;
 using gui::IRegionSamplingListener;
@@ -1366,6 +1368,7 @@
     } mPowerHintSessionMode;
 
     TransactionHandler mTransactionHandler;
+    display::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
 };
 
 class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index 488d4a9..d84698f 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -42,6 +42,8 @@
 using testing::Return;
 
 using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using frontend::TransactionHandler;
+
 constexpr nsecs_t TRANSACTION_TIMEOUT = s2ns(5);
 class TransactionApplicationTest : public testing::Test {
 public:
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index 4f7b923..475bc40 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -715,6 +715,17 @@
     capabilities->minImageExtent = VkExtent2D{1, 1};
     capabilities->maxImageExtent = VkExtent2D{4096, 4096};
 
+    if (capabilities->maxImageExtent.height <
+        capabilities->currentExtent.height) {
+        capabilities->maxImageExtent.height =
+            capabilities->currentExtent.height;
+    }
+
+    if (capabilities->maxImageExtent.width <
+        capabilities->currentExtent.width) {
+        capabilities->maxImageExtent.width = capabilities->currentExtent.width;
+    }
+
     capabilities->maxImageArrayLayers = 1;
 
     capabilities->supportedTransforms = kSupportedTransforms;
@@ -743,7 +754,6 @@
 
     const InstanceData& instance_data = GetData(pdev);
 
-    bool wide_color_support = false;
     uint64_t consumer_usage = 0;
     bool colorspace_ext =
         instance_data.hook_extensions.test(ProcHook::EXT_swapchain_colorspace);
@@ -754,27 +764,15 @@
         if (!surfaceless_enabled) {
             return VK_ERROR_SURFACE_LOST_KHR;
         }
-        // Support for VK_GOOGLE_surfaceless_query.  The EGL loader
-        // unconditionally supports wide color formats, even if they will cause
-        // a SurfaceFlinger fallback.  Based on that, wide_color_support will be
-        // set to true in this case.
-        wide_color_support = true;
+        // Support for VK_GOOGLE_surfaceless_query.
 
         // TODO(b/203826952): research proper value; temporarily use the
         // values seen on Pixel
         consumer_usage = AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY;
     } else {
         Surface& surface = *SurfaceFromHandle(surface_handle);
-        int err = native_window_get_wide_color_support(surface.window.get(),
-                                                       &wide_color_support);
-        if (err) {
-            return VK_ERROR_SURFACE_LOST_KHR;
-        }
-        ALOGV("wide_color_support is: %d", wide_color_support);
-
         consumer_usage = surface.consumer_usage;
     }
-    wide_color_support = wide_color_support && colorspace_ext;
 
     AHardwareBuffer_Desc desc = {};
     desc.width = 1;
@@ -796,9 +794,6 @@
             VK_FORMAT_R8G8B8A8_SRGB, VK_COLOR_SPACE_PASS_THROUGH_EXT});
         all_formats.emplace_back(VkSurfaceFormatKHR{
             VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_BT709_LINEAR_EXT});
-    }
-
-    if (wide_color_support) {
         all_formats.emplace_back(VkSurfaceFormatKHR{
             VK_FORMAT_R8G8B8A8_UNORM, VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT});
         all_formats.emplace_back(VkSurfaceFormatKHR{
@@ -828,8 +823,6 @@
             all_formats.emplace_back(
                 VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT,
                                    VK_COLOR_SPACE_PASS_THROUGH_EXT});
-        }
-        if (wide_color_support) {
             all_formats.emplace_back(
                 VkSurfaceFormatKHR{VK_FORMAT_R16G16B16A16_SFLOAT,
                                    VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT});
@@ -848,8 +841,6 @@
             all_formats.emplace_back(
                 VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
                                    VK_COLOR_SPACE_PASS_THROUGH_EXT});
-        }
-        if (wide_color_support) {
             all_formats.emplace_back(
                 VkSurfaceFormatKHR{VK_FORMAT_A2B10G10R10_UNORM_PACK32,
                                    VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT});