Merge "Allow touchpad debug logs to be enabled on the fly" into main
diff --git a/include/android/input.h b/include/android/input.h
index 5f44550..2f6c5b5 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -862,7 +862,7 @@
     AMOTION_EVENT_BUTTON_FORWARD = 1 << 4,
     AMOTION_EVENT_BUTTON_STYLUS_PRIMARY = 1 << 5,
     AMOTION_EVENT_BUTTON_STYLUS_SECONDARY = 1 << 6,
-    // LINT.ThenChange(/frameworks/native/libs/input/rust/input.rs)
+    // LINT.ThenChange(/frameworks/native/libs/input/rust/input.rs,/frameworks/native/services/inputflinger/tests/fuzzers/FuzzedInputStream.h)
 };
 
 /**
diff --git a/include/input/Input.h b/include/input/Input.h
index e84023e..002b3a7 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -316,6 +316,19 @@
 
 bool isStylusEvent(uint32_t source, const std::vector<PointerProperties>& properties);
 
+bool isStylusHoverEvent(uint32_t source, const std::vector<PointerProperties>& properties,
+                        int32_t action);
+
+bool isFromMouse(uint32_t source, ToolType tooltype);
+
+bool isFromTouchpad(uint32_t source, ToolType tooltype);
+
+bool isFromDrawingTablet(uint32_t source, ToolType tooltype);
+
+bool isHoverAction(int32_t action);
+
+bool isMouseOrTouchpad(uint32_t sources);
+
 /*
  * Flags that flow alongside events in the input dispatch system to help with certain
  * policy decisions such as waking from device sleep.
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 65a088e..155ea00 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -284,6 +284,36 @@
     return false;
 }
 
+bool isStylusHoverEvent(uint32_t source, const std::vector<PointerProperties>& properties,
+                        int32_t action) {
+    return isStylusEvent(source, properties) && isHoverAction(action);
+}
+
+bool isFromMouse(uint32_t source, ToolType toolType) {
+    return isFromSource(source, AINPUT_SOURCE_MOUSE) && toolType == ToolType::MOUSE;
+}
+
+bool isFromTouchpad(uint32_t source, ToolType toolType) {
+    return isFromSource(source, AINPUT_SOURCE_MOUSE) && toolType == ToolType::FINGER;
+}
+
+bool isFromDrawingTablet(uint32_t source, ToolType toolType) {
+    return isFromSource(source, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS) &&
+            isStylusToolType(toolType);
+}
+
+bool isHoverAction(int32_t action) {
+    return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+            action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
+}
+
+bool isMouseOrTouchpad(uint32_t sources) {
+    // Check if this is a mouse or touchpad, but not a drawing tablet.
+    return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
+            (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
+             !isFromSource(sources, AINPUT_SOURCE_STYLUS));
+}
+
 VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) {
     return {{VerifiedInputEvent::Type::KEY, event.getDeviceId(), event.getEventTime(),
              event.getSource(), event.getDisplayId()},
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 85f842c..e2a772a 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -36,37 +36,6 @@
 
 namespace {
 
-bool isFromMouse(const NotifyMotionArgs& args) {
-    return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
-            args.pointerProperties[0].toolType == ToolType::MOUSE;
-}
-
-bool isFromTouchpad(const NotifyMotionArgs& args) {
-    return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
-            args.pointerProperties[0].toolType == ToolType::FINGER;
-}
-
-bool isFromDrawingTablet(const NotifyMotionArgs& args) {
-    return isFromSource(args.source, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_STYLUS) &&
-            isStylusToolType(args.pointerProperties[0].toolType);
-}
-
-bool isHoverAction(int32_t action) {
-    return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
-            action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
-}
-
-bool isStylusHoverEvent(const NotifyMotionArgs& args) {
-    return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action);
-}
-
-bool isMouseOrTouchpad(uint32_t sources) {
-    // Check if this is a mouse or touchpad, but not a drawing tablet.
-    return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
-            (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
-             !isFromSource(sources, AINPUT_SOURCE_STYLUS));
-}
-
 inline void notifyPointerDisplayChange(std::optional<std::tuple<ui::LogicalDisplayId, vec2>> change,
                                        PointerChoreographerPolicyInterface& policy) {
     if (!change) {
@@ -239,15 +208,16 @@
     PointerDisplayChange pointerDisplayChange;
     { // acquire lock
         std::scoped_lock _l(getLock());
-        if (isFromMouse(args)) {
+        if (isFromMouse(args.source, args.pointerProperties[0].toolType)) {
             newArgs = processMouseEventLocked(args);
             pointerDisplayChange = calculatePointerDisplayChangeToNotify();
-        } else if (isFromTouchpad(args)) {
+        } else if (isFromTouchpad(args.source, args.pointerProperties[0].toolType)) {
             newArgs = processTouchpadEventLocked(args);
             pointerDisplayChange = calculatePointerDisplayChangeToNotify();
-        } else if (isFromDrawingTablet(args)) {
+        } else if (isFromDrawingTablet(args.source, args.pointerProperties[0].toolType)) {
             processDrawingTabletEventLocked(args);
-        } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
+        } else if (mStylusPointerIconEnabled &&
+                   isStylusHoverEvent(args.source, args.pointerProperties, args.action)) {
             processStylusHoverEventLocked(args);
         } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
             processTouchscreenAndStylusEventLocked(args);
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index a77dc43..04197ee 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -920,13 +920,6 @@
                         binderToString(info.applicationInfo.token).c_str());
 }
 
-bool isMouseOrTouchpad(uint32_t sources) {
-    // Check if this is a mouse or touchpad, but not a drawing tablet.
-    return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
-            (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
-             !isFromSource(sources, AINPUT_SOURCE_STYLUS));
-}
-
 } // namespace
 
 // --- InputDispatcher ---
@@ -943,6 +936,7 @@
         mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),
         mMinTimeBetweenUserActivityPokes(DEFAULT_USER_ACTIVITY_POKE_INTERVAL),
         mConnectionManager(mLooper),
+        mTouchStates(mWindowInfos, mConnectionManager),
         mNextUnblockedEvent(nullptr),
         mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT),
         mDispatchEnabled(false),
@@ -1473,14 +1467,13 @@
 
 std::vector<InputTarget> InputDispatcher::DispatcherTouchState::findOutsideTargets(
         ui::LogicalDisplayId displayId, const sp<gui::WindowInfoHandle>& touchedWindow,
-        int32_t pointerId, const ConnectionManager& connections,
-        const DispatcherWindowInfo& windowInfos, std::function<void()> dump) {
+        int32_t pointerId, std::function<void()> dump) {
     if (touchedWindow == nullptr) {
         return {};
     }
     // Traverse windows from front to back until we encounter the touched window.
     std::vector<InputTarget> outsideTargets;
-    const auto& windowHandles = windowInfos.getWindowHandlesForDisplay(displayId);
+    const auto& windowHandles = mWindowInfos.getWindowHandlesForDisplay(displayId);
     for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
         if (windowHandle == touchedWindow) {
             // Stop iterating once we found a touched window. Any WATCH_OUTSIDE_TOUCH window
@@ -1495,8 +1488,7 @@
             addPointerWindowTarget(windowHandle, InputTarget::DispatchMode::OUTSIDE,
                                    ftl::Flags<InputTarget::Flags>(), pointerIds,
                                    /*firstDownTimeInTarget=*/std::nullopt,
-                                   /*pointerDisplayId=*/std::nullopt, connections, windowInfos,
-                                   dump, outsideTargets);
+                                   /*pointerDisplayId=*/std::nullopt, dump, outsideTargets);
         }
     }
     return outsideTargets;
@@ -2058,8 +2050,7 @@
 
         Result<std::vector<InputTarget>, InputEventInjectionResult> result =
                 mTouchStates
-                        .findTouchedWindowTargets(currentTime, *entry, mConnectionManager,
-                                                  mWindowInfos,
+                        .findTouchedWindowTargets(currentTime, *entry,
                                                   mDragState ? mDragState->dragWindow : nullptr,
                                                   std::bind_front(&InputDispatcher::
                                                                           addDragEventLocked,
@@ -2379,8 +2370,7 @@
 
 base::Result<std::vector<InputTarget>, os::InputEventInjectionResult>
 InputDispatcher::DispatcherTouchState::findTouchedWindowTargets(
-        nsecs_t currentTime, const MotionEntry& entry, const ConnectionManager& connections,
-        const DispatcherWindowInfo& windowInfos,
+        nsecs_t currentTime, const MotionEntry& entry,
         const sp<android::gui::WindowInfoHandle> dragWindow,
         std::function<void(const MotionEntry&)> addDragEvent, std::function<void()> dump) {
     ATRACE_CALL();
@@ -2395,7 +2385,7 @@
     // Copy current touch state into tempTouchState.
     // This state will be used to update saved touch state at the end of this function.
     // If no state for the specified display exists, then our initial state will be empty.
-    const TouchState* oldState = getTouchStateForMotionEntry(entry, windowInfos);
+    const TouchState* oldState = getTouchStateForMotionEntry(entry);
     TouchState tempTouchState;
     if (oldState != nullptr) {
         tempTouchState = *oldState;
@@ -2441,12 +2431,10 @@
         // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down.
         const bool isStylus = isPointerFromStylus(entry, pointerIndex);
         sp<WindowInfoHandle> newTouchedWindowHandle =
-                windowInfos.findTouchedWindowAt(displayId, x, y, isStylus);
+                mWindowInfos.findTouchedWindowAt(displayId, x, y, isStylus);
 
         if (isDown) {
-            targets += DispatcherTouchState::findOutsideTargets(displayId, newTouchedWindowHandle,
-                                                                pointer.id, connections,
-                                                                windowInfos, dump);
+            targets += findOutsideTargets(displayId, newTouchedWindowHandle, pointer.id, dump);
         }
         LOG_IF(INFO, newTouchedWindowHandle == nullptr)
                 << "No new touched window at (" << std::format("{:.1f}, {:.1f}", x, y)
@@ -2459,7 +2447,7 @@
         }
 
         std::vector<sp<WindowInfoHandle>> newTouchedWindows =
-                findTouchedSpyWindowsAt(displayId, x, y, isStylus, entry.deviceId, windowInfos);
+                findTouchedSpyWindowsAt(displayId, x, y, isStylus, entry.deviceId, mWindowInfos);
         if (newTouchedWindowHandle != nullptr) {
             // Process the foreground window first so that it is the first to receive the event.
             newTouchedWindows.insert(newTouchedWindows.begin(), newTouchedWindowHandle);
@@ -2472,7 +2460,7 @@
         }
 
         for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
-            if (!canWindowReceiveMotion(windowHandle, entry, connections, windowInfos)) {
+            if (!canWindowReceiveMotion(windowHandle, entry)) {
                 continue;
             }
 
@@ -2484,8 +2472,7 @@
 
             // Set target flags.
             ftl::Flags<InputTarget::Flags> targetFlags =
-                    DispatcherTouchState::getTargetFlags(windowHandle, {x, y}, isSplit,
-                                                         windowInfos);
+                    getTargetFlags(windowHandle, {x, y}, isSplit);
 
             // Update the temporary touch state.
 
@@ -2515,7 +2502,7 @@
                     windowHandle->getInfo()->inputConfig.test(
                             gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
                     sp<WindowInfoHandle> wallpaper =
-                            windowInfos.findWallpaperWindowBelow(windowHandle);
+                            mWindowInfos.findWallpaperWindowBelow(windowHandle);
                     if (wallpaper != nullptr) {
                         ftl::Flags<InputTarget::Flags> wallpaperFlags =
                                 InputTarget::Flags::WINDOW_IS_OBSCURED |
@@ -2584,7 +2571,7 @@
                     tempTouchState.getFirstForegroundWindowHandle(entry.deviceId);
             LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr);
             sp<WindowInfoHandle> newTouchedWindowHandle =
-                    windowInfos.findTouchedWindowAt(displayId, x, y, isStylus);
+                    mWindowInfos.findTouchedWindowAt(displayId, x, y, isStylus);
 
             // Verify targeted injection.
             if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
@@ -2594,7 +2581,7 @@
 
             // Do not slide events to the window which can not receive motion event
             if (newTouchedWindowHandle != nullptr &&
-                !canWindowReceiveMotion(newTouchedWindowHandle, entry, connections, windowInfos)) {
+                !canWindowReceiveMotion(newTouchedWindowHandle, entry)) {
                 newTouchedWindowHandle = nullptr;
             }
 
@@ -2615,14 +2602,12 @@
                                        InputTarget::DispatchMode::SLIPPERY_EXIT,
                                        ftl::Flags<InputTarget::Flags>(), pointerIds,
                                        touchedWindow.getDownTimeInTarget(entry.deviceId),
-                                       /*pointerDisplayId=*/std::nullopt, connections, windowInfos,
-                                       dump, targets);
+                                       /*pointerDisplayId=*/std::nullopt, dump, targets);
 
                 // Make a slippery entrance into the new window.
 
                 ftl::Flags<InputTarget::Flags> targetFlags =
-                        DispatcherTouchState::getTargetFlags(newTouchedWindowHandle, {x, y},
-                                                             isSplit, windowInfos);
+                        getTargetFlags(newTouchedWindowHandle, {x, y}, isSplit);
 
                 tempTouchState.addOrUpdateWindow(newTouchedWindowHandle,
                                                  InputTarget::DispatchMode::SLIPPERY_ENTER,
@@ -2630,10 +2615,8 @@
                                                  entry.eventTime);
 
                 // Check if the wallpaper window should deliver the corresponding event.
-                DispatcherTouchState::slipWallpaperTouch(targetFlags, oldTouchedWindowHandle,
-                                                         newTouchedWindowHandle, tempTouchState,
-                                                         entry, targets, connections, windowInfos,
-                                                         dump);
+                slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
+                                   tempTouchState, entry, targets, dump);
                 tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointer.id,
                                                                oldTouchedWindowHandle);
             }
@@ -2668,8 +2651,7 @@
             addPointerWindowTarget(touchedWindow.windowHandle, touchedWindow.dispatchMode,
                                    touchedWindow.targetFlags, pointerIds,
                                    touchedWindow.getDownTimeInTarget(entry.deviceId),
-                                   /*pointerDisplayId=*/std::nullopt, connections, windowInfos,
-                                   dump, targets);
+                                   /*pointerDisplayId=*/std::nullopt, dump, targets);
         }
     }
 
@@ -2698,7 +2680,7 @@
             for (InputTarget& target : targets) {
                 if (target.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
                     sp<WindowInfoHandle> targetWindow =
-                            windowInfos.findWindowHandle(target.connection->getToken());
+                            mWindowInfos.findWindowHandle(target.connection->getToken());
                     if (targetWindow->getInfo()->ownerUid != foregroundWindowUid) {
                         target.flags |= InputTarget::Flags::ZERO_COORDS;
                     }
@@ -2725,8 +2707,7 @@
         addPointerWindowTarget(touchedWindow.windowHandle, touchedWindow.dispatchMode,
                                touchedWindow.targetFlags, getPointerIds(touchingPointers),
                                touchedWindow.getDownTimeInTarget(entry.deviceId),
-                               /*pointerDisplayId=*/displayId, connections, windowInfos, dump,
-                               targets);
+                               /*pointerDisplayId=*/displayId, dump, targets);
     }
 
     // During targeted injection, only allow owned targets to receive events
@@ -2781,9 +2762,9 @@
     if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
         if (displayId >= ui::LogicalDisplayId::DEFAULT) {
             tempTouchState.clearWindowsWithoutPointers();
-            saveTouchStateForMotionEntry(entry, std::move(tempTouchState), windowInfos);
+            saveTouchStateForMotionEntry(entry, std::move(tempTouchState));
         } else {
-            eraseTouchStateForMotionEntry(entry, windowInfos);
+            eraseTouchStateForMotionEntry(entry);
         }
     }
 
@@ -2930,8 +2911,7 @@
         const sp<android::gui::WindowInfoHandle>& windowHandle,
         InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
         std::bitset<MAX_POINTER_ID + 1> pointerIds, std::optional<nsecs_t> firstDownTimeInTarget,
-        std::optional<ui::LogicalDisplayId> pointerDisplayId, const ConnectionManager& connections,
-        const DispatcherWindowInfo& windowInfos, std::function<void()> dump,
+        std::optional<ui::LogicalDisplayId> pointerDisplayId, std::function<void()> dump,
         std::vector<InputTarget>& inputTargets) {
     if (pointerIds.none()) {
         for (const auto& target : inputTargets) {
@@ -2960,15 +2940,15 @@
     const WindowInfo& windowInfo = *windowHandle->getInfo();
 
     if (it == inputTargets.end()) {
-        std::shared_ptr<Connection> connection = connections.getConnection(windowInfo.token);
+        std::shared_ptr<Connection> connection = mConnectionManager.getConnection(windowInfo.token);
         if (connection == nullptr) {
             ALOGW("Not creating InputTarget for %s, no input channel", windowInfo.name.c_str());
             return;
         }
         inputTargets.push_back(
                 createInputTarget(connection, windowHandle, dispatchMode, targetFlags,
-                                  windowInfos.getRawTransform(*windowHandle->getInfo(),
-                                                              pointerDisplayId),
+                                  mWindowInfos.getRawTransform(*windowHandle->getInfo(),
+                                                               pointerDisplayId),
                                   firstDownTimeInTarget));
         it = inputTargets.end() - 1;
     }
@@ -4168,16 +4148,15 @@
                         sendDropWindowCommandLocked(nullptr, /*x=*/0, /*y=*/0);
                         mDragState.reset();
                     }
-                    DispatcherTouchState::
-                            addPointerWindowTarget(window, InputTarget::DispatchMode::AS_IS,
-                                                   ftl::Flags<InputTarget::Flags>(), pointerIds,
-                                                   motionEntry.downTime,
-                                                   /*pointerDisplayId=*/std::nullopt,
-                                                   mConnectionManager, mWindowInfos,
-                                                   std::bind_front(&InputDispatcher::
-                                                                           logDispatchStateLocked,
-                                                                   this),
-                                                   targets);
+                    mTouchStates
+                            .addPointerWindowTarget(window, InputTarget::DispatchMode::AS_IS,
+                                                    ftl::Flags<InputTarget::Flags>(), pointerIds,
+                                                    motionEntry.downTime,
+                                                    /*pointerDisplayId=*/std::nullopt,
+                                                    std::bind_front(&InputDispatcher::
+                                                                            logDispatchStateLocked,
+                                                                    this),
+                                                    targets);
                 } else {
                     targets.emplace_back(fallbackTarget);
                     // Since we don't have a window, use the display transform as the raw transform.
@@ -4256,15 +4235,14 @@
                          pointerIndex++) {
                         pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
                     }
-                    DispatcherTouchState::
-                            addPointerWindowTarget(windowHandle, InputTarget::DispatchMode::AS_IS,
-                                                   targetFlags, pointerIds, motionEntry.downTime,
-                                                   /*pointerDisplayId=*/std::nullopt,
-                                                   mConnectionManager, mWindowInfos,
-                                                   std::bind_front(&InputDispatcher::
-                                                                           logDispatchStateLocked,
-                                                                   this),
-                                                   targets);
+                    mTouchStates
+                            .addPointerWindowTarget(windowHandle, InputTarget::DispatchMode::AS_IS,
+                                                    targetFlags, pointerIds, motionEntry.downTime,
+                                                    /*pointerDisplayId=*/std::nullopt,
+                                                    std::bind_front(&InputDispatcher::
+                                                                            logDispatchStateLocked,
+                                                                    this),
+                                                    targets);
                 } else {
                     targets.emplace_back(connection, targetFlags);
                     // Since we don't have a window, use the display transform as the raw transform.
@@ -5238,8 +5216,7 @@
 
 bool InputDispatcher::DispatcherTouchState::canWindowReceiveMotion(
         const sp<android::gui::WindowInfoHandle>& window,
-        const android::inputdispatcher::MotionEntry& motionEntry,
-        const ConnectionManager& connections, const DispatcherWindowInfo& windowInfos) const {
+        const android::inputdispatcher::MotionEntry& motionEntry) const {
     const WindowInfo& info = *window->getInfo();
 
     // Skip spy window targets that are not valid for targeted injection.
@@ -5258,7 +5235,7 @@
         return false;
     }
 
-    std::shared_ptr<Connection> connection = connections.getConnection(window->getToken());
+    std::shared_ptr<Connection> connection = mConnectionManager.getConnection(window->getToken());
     if (connection == nullptr) {
         ALOGW("Not sending touch to %s because there's no corresponding connection",
               window->getName().c_str());
@@ -5273,8 +5250,8 @@
     // Drop events that can't be trusted due to occlusion
     const auto [x, y] = resolveTouchedPosition(motionEntry);
     DispatcherWindowInfo::TouchOcclusionInfo occlusionInfo =
-            windowInfos.computeTouchOcclusionInfo(window, x, y);
-    if (!windowInfos.isTouchTrusted(occlusionInfo)) {
+            mWindowInfos.computeTouchOcclusionInfo(window, x, y);
+    if (!mWindowInfos.isTouchTrusted(occlusionInfo)) {
         if (DEBUG_TOUCH_OCCLUSION) {
             ALOGD("Stack of obscuring windows during untrusted touch (%.1f, %.1f):", x, y);
             for (const auto& log : occlusionInfo.debugInfo) {
@@ -5287,7 +5264,7 @@
     }
 
     // Drop touch events if requested by input feature
-    if (shouldDropInput(motionEntry, window, windowInfos)) {
+    if (shouldDropInput(motionEntry, window, mWindowInfos)) {
         return false;
     }
 
@@ -5423,7 +5400,7 @@
     CancelationOptions hoverCancellationOptions(CancelationOptions::Mode::CANCEL_HOVER_EVENTS,
                                                 "WindowInfo changed", traceContext.getTracker());
     const std::list<DispatcherTouchState::CancellationArgs> cancellations =
-            mTouchStates.updateFromWindowInfo(displayId, mWindowInfos);
+            mTouchStates.updateFromWindowInfo(displayId);
     for (const auto& cancellationArgs : cancellations) {
         switch (cancellationArgs.mode) {
             case CancelationOptions::Mode::CANCEL_POINTER_EVENTS:
@@ -5464,14 +5441,13 @@
 }
 
 std::list<InputDispatcher::DispatcherTouchState::CancellationArgs>
-InputDispatcher::DispatcherTouchState::updateFromWindowInfo(
-        ui::LogicalDisplayId displayId, const DispatcherWindowInfo& windowInfos) {
+InputDispatcher::DispatcherTouchState::updateFromWindowInfo(ui::LogicalDisplayId displayId) {
     std::list<CancellationArgs> cancellations;
     forTouchAndCursorStatesOnDisplay(displayId, [&](TouchState& state) {
         cancellations.splice(cancellations.end(),
-                             eraseRemovedWindowsFromWindowInfo(state, displayId, windowInfos));
+                             eraseRemovedWindowsFromWindowInfo(state, displayId));
         cancellations.splice(cancellations.end(),
-                             updateHoveringStateFromWindowInfo(state, displayId, windowInfos));
+                             updateHoveringStateFromWindowInfo(state, displayId));
         return false;
     });
     return cancellations;
@@ -5479,12 +5455,11 @@
 
 std::list<InputDispatcher::DispatcherTouchState::CancellationArgs>
 InputDispatcher::DispatcherTouchState::eraseRemovedWindowsFromWindowInfo(
-        TouchState& state, ui::LogicalDisplayId displayId,
-        const DispatcherWindowInfo& windowInfos) {
+        TouchState& state, ui::LogicalDisplayId displayId) {
     std::list<CancellationArgs> cancellations;
     for (auto it = state.windows.begin(); it != state.windows.end();) {
         TouchedWindow& touchedWindow = *it;
-        if (windowInfos.isWindowPresent(touchedWindow.windowHandle)) {
+        if (mWindowInfos.isWindowPresent(touchedWindow.windowHandle)) {
             it++;
             continue;
         }
@@ -5510,12 +5485,11 @@
 
 std::list<InputDispatcher::DispatcherTouchState::CancellationArgs>
 InputDispatcher::DispatcherTouchState::updateHoveringStateFromWindowInfo(
-        TouchState& state, ui::LogicalDisplayId displayId,
-        const DispatcherWindowInfo& windowInfos) {
+        TouchState& state, ui::LogicalDisplayId displayId) {
     std::list<CancellationArgs> cancellations;
     // Check if the hovering should stop because the window is no longer eligible to receive it
     // (for example, if the touchable region changed)
-    ui::Transform displayTransform = windowInfos.getDisplayTransform(displayId);
+    ui::Transform displayTransform = mWindowInfos.getDisplayTransform(displayId);
     for (TouchedWindow& touchedWindow : state.windows) {
         std::vector<DeviceId> erasedDevices = touchedWindow.eraseHoveringPointersIf(
                 [&](const PointerProperties& properties, float x, float y) {
@@ -5770,8 +5744,7 @@
                                    "transferring touch from this window to another window",
                                    traceContext.getTracker());
 
-        auto result = mTouchStates.transferTouchGesture(fromToken, toToken, mWindowInfos,
-                                                        mConnectionManager);
+        auto result = mTouchStates.transferTouchGesture(fromToken, toToken);
         if (!result.has_value()) {
             return false;
         }
@@ -5815,9 +5788,7 @@
                          std::list<InputDispatcher::DispatcherTouchState::CancellationArgs>,
                          std::list<InputDispatcher::DispatcherTouchState::PointerDownArgs>>>
 InputDispatcher::DispatcherTouchState::transferTouchGesture(const sp<android::IBinder>& fromToken,
-                                                            const sp<android::IBinder>& toToken,
-                                                            const DispatcherWindowInfo& windowInfos,
-                                                            const ConnectionManager& connections) {
+                                                            const sp<android::IBinder>& toToken) {
     // Find the target touch state and touched window by fromToken.
     auto touchStateWindowAndDisplay = findTouchStateWindowAndDisplay(fromToken);
     if (!touchStateWindowAndDisplay.has_value()) {
@@ -5835,7 +5806,7 @@
     const DeviceId deviceId = *deviceIds.begin();
 
     const sp<WindowInfoHandle> fromWindowHandle = touchedWindow.windowHandle;
-    const sp<WindowInfoHandle> toWindowHandle = windowInfos.findWindowHandle(toToken, displayId);
+    const sp<WindowInfoHandle> toWindowHandle = mWindowInfos.findWindowHandle(toToken, displayId);
     if (!toWindowHandle) {
         ALOGW("Cannot transfer touch because the transfer target window was not found.");
         return std::nullopt;
@@ -5861,8 +5832,8 @@
                             deviceId, pointers, downTimeInTarget);
 
     // Synthesize cancel for old window and down for new window.
-    std::shared_ptr<Connection> fromConnection = connections.getConnection(fromToken);
-    std::shared_ptr<Connection> toConnection = connections.getConnection(toToken);
+    std::shared_ptr<Connection> fromConnection = mConnectionManager.getConnection(fromToken);
+    std::shared_ptr<Connection> toConnection = mConnectionManager.getConnection(toToken);
     std::list<CancellationArgs> cancellations;
     std::list<PointerDownArgs> pointerDowns;
     if (fromConnection != nullptr && toConnection != nullptr) {
@@ -5873,7 +5844,7 @@
         // Check if the wallpaper window should deliver the corresponding event.
         auto [wallpaperCancellations, wallpaperPointerDowns] =
                 transferWallpaperTouch(fromWindowHandle, toWindowHandle, state, deviceId, pointers,
-                                       oldTargetFlags, newTargetFlags, windowInfos, connections);
+                                       oldTargetFlags, newTargetFlags);
 
         cancellations.splice(cancellations.end(), wallpaperCancellations);
         pointerDowns.splice(pointerDowns.end(), wallpaperPointerDowns);
@@ -7092,8 +7063,7 @@
 void InputDispatcher::DispatcherTouchState::slipWallpaperTouch(
         ftl::Flags<InputTarget::Flags> targetFlags, const sp<WindowInfoHandle>& oldWindowHandle,
         const sp<WindowInfoHandle>& newWindowHandle, TouchState& state, const MotionEntry& entry,
-        std::vector<InputTarget>& targets, const ConnectionManager& connections,
-        const DispatcherWindowInfo& windowInfos, std::function<void()> dump) {
+        std::vector<InputTarget>& targets, std::function<void()> dump) {
     LOG_IF(FATAL, entry.getPointerCount() != 1) << "Entry not eligible for slip: " << entry;
     const DeviceId deviceId = entry.deviceId;
     const PointerProperties& pointerProperties = entry.pointerProperties[0];
@@ -7106,20 +7076,17 @@
     const sp<WindowInfoHandle> oldWallpaper =
             oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr;
     const sp<WindowInfoHandle> newWallpaper =
-            newHasWallpaper ? windowInfos.findWallpaperWindowBelow(newWindowHandle) : nullptr;
+            newHasWallpaper ? mWindowInfos.findWallpaperWindowBelow(newWindowHandle) : nullptr;
     if (oldWallpaper == newWallpaper) {
         return;
     }
 
     if (oldWallpaper != nullptr) {
         const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper);
-        DispatcherTouchState::addPointerWindowTarget(oldWallpaper,
-                                                     InputTarget::DispatchMode::SLIPPERY_EXIT,
-                                                     oldTouchedWindow.targetFlags,
-                                                     getPointerIds(pointers),
-                                                     oldTouchedWindow.getDownTimeInTarget(deviceId),
-                                                     /*pointerDisplayId=*/std::nullopt, connections,
-                                                     windowInfos, dump, targets);
+        addPointerWindowTarget(oldWallpaper, InputTarget::DispatchMode::SLIPPERY_EXIT,
+                               oldTouchedWindow.targetFlags, getPointerIds(pointers),
+                               oldTouchedWindow.getDownTimeInTarget(deviceId),
+                               /*pointerDisplayId=*/std::nullopt, dump, targets);
         state.removeTouchingPointerFromWindow(deviceId, pointerProperties.id, oldWallpaper);
     }
 
@@ -7138,8 +7105,7 @@
         const sp<gui::WindowInfoHandle> toWindowHandle, TouchState& state,
         android::DeviceId deviceId, const std::vector<PointerProperties>& pointers,
         ftl::Flags<InputTarget::Flags> oldTargetFlags,
-        ftl::Flags<InputTarget::Flags> newTargetFlags, const DispatcherWindowInfo& windowInfos,
-        const ConnectionManager& connections) {
+        ftl::Flags<InputTarget::Flags> newTargetFlags) {
     const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
             fromWindowHandle->getInfo()->inputConfig.test(
                     gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
@@ -7150,7 +7116,7 @@
     const sp<WindowInfoHandle> oldWallpaper =
             oldHasWallpaper ? state.getWallpaperWindow(deviceId) : nullptr;
     const sp<WindowInfoHandle> newWallpaper =
-            newHasWallpaper ? windowInfos.findWallpaperWindowBelow(toWindowHandle) : nullptr;
+            newHasWallpaper ? mWindowInfos.findWallpaperWindowBelow(toWindowHandle) : nullptr;
     if (oldWallpaper == newWallpaper) {
         return {};
     }
@@ -7171,10 +7137,10 @@
         state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::AS_IS, wallpaperFlags,
                                 deviceId, pointers, downTimeInTarget);
         std::shared_ptr<Connection> wallpaperConnection =
-                connections.getConnection(newWallpaper->getToken());
+                mConnectionManager.getConnection(newWallpaper->getToken());
         if (wallpaperConnection != nullptr) {
             std::shared_ptr<Connection> toConnection =
-                    connections.getConnection(toWindowHandle->getToken());
+                    mConnectionManager.getConnection(toWindowHandle->getToken());
             toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
             pointerDowns.emplace_back(downTimeInTarget, wallpaperConnection, wallpaperFlags);
         }
@@ -7374,9 +7340,12 @@
     mTopology = displayTopologyGraph;
 }
 
+InputDispatcher::DispatcherTouchState::DispatcherTouchState(const DispatcherWindowInfo& windowInfos,
+                                                            const ConnectionManager& connections)
+      : mWindowInfos(windowInfos), mConnectionManager(connections) {}
+
 ftl::Flags<InputTarget::Flags> InputDispatcher::DispatcherTouchState::getTargetFlags(
-        const sp<WindowInfoHandle>& targetWindow, vec2 targetPosition, bool isSplit,
-        const DispatcherWindowInfo& windowInfos) {
+        const sp<WindowInfoHandle>& targetWindow, vec2 targetPosition, bool isSplit) {
     ftl::Flags<InputTarget::Flags> targetFlags;
     if (canReceiveForegroundTouches(*targetWindow->getInfo())) {
         // There should only be one touched window that can be "foreground" for the pointer.
@@ -7385,9 +7354,9 @@
     if (isSplit) {
         targetFlags |= InputTarget::Flags::SPLIT;
     }
-    if (windowInfos.isWindowObscuredAtPoint(targetWindow, targetPosition.x, targetPosition.y)) {
+    if (mWindowInfos.isWindowObscuredAtPoint(targetWindow, targetPosition.x, targetPosition.y)) {
         targetFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED;
-    } else if (windowInfos.isWindowObscured(targetWindow)) {
+    } else if (mWindowInfos.isWindowObscured(targetWindow)) {
         targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
     }
     return targetFlags;
@@ -7500,15 +7469,14 @@
 
 void InputDispatcher::DispatcherTouchState::saveTouchStateForMotionEntry(
         const android::inputdispatcher::MotionEntry& entry,
-        android::inputdispatcher::TouchState&& touchState,
-        const DispatcherWindowInfo& windowInfos) {
+        android::inputdispatcher::TouchState&& touchState) {
     if (touchState.windows.empty()) {
-        eraseTouchStateForMotionEntry(entry, windowInfos);
+        eraseTouchStateForMotionEntry(entry);
         return;
     }
 
     if (InputFlags::connectedDisplaysCursorEnabled() && isMouseOrTouchpad(entry.source)) {
-        mCursorStateByDisplay[windowInfos.getPrimaryDisplayId(entry.displayId)] =
+        mCursorStateByDisplay[mWindowInfos.getPrimaryDisplayId(entry.displayId)] =
                 std::move(touchState);
     } else {
         mTouchStatesByDisplay[entry.displayId] = std::move(touchState);
@@ -7516,21 +7484,19 @@
 }
 
 void InputDispatcher::DispatcherTouchState::eraseTouchStateForMotionEntry(
-        const android::inputdispatcher::MotionEntry& entry,
-        const DispatcherWindowInfo& windowInfos) {
+        const android::inputdispatcher::MotionEntry& entry) {
     if (InputFlags::connectedDisplaysCursorEnabled() && isMouseOrTouchpad(entry.source)) {
-        mCursorStateByDisplay.erase(windowInfos.getPrimaryDisplayId(entry.displayId));
+        mCursorStateByDisplay.erase(mWindowInfos.getPrimaryDisplayId(entry.displayId));
     } else {
         mTouchStatesByDisplay.erase(entry.displayId);
     }
 }
 
 const TouchState* InputDispatcher::DispatcherTouchState::getTouchStateForMotionEntry(
-        const android::inputdispatcher::MotionEntry& entry,
-        const DispatcherWindowInfo& windowInfos) const {
+        const android::inputdispatcher::MotionEntry& entry) const {
     if (InputFlags::connectedDisplaysCursorEnabled() && isMouseOrTouchpad(entry.source)) {
         auto touchStateIt =
-                mCursorStateByDisplay.find(windowInfos.getPrimaryDisplayId(entry.displayId));
+                mCursorStateByDisplay.find(mWindowInfos.getPrimaryDisplayId(entry.displayId));
         if (touchStateIt != mCursorStateByDisplay.end()) {
             return &touchStateIt->second;
         }
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 7e8e142..4b4996d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -380,21 +380,20 @@
             const ftl::Flags<InputTarget::Flags> targetFlags;
         };
 
-        static void addPointerWindowTarget(const sp<android::gui::WindowInfoHandle>& windowHandle,
-                                           InputTarget::DispatchMode dispatchMode,
-                                           ftl::Flags<InputTarget::Flags> targetFlags,
-                                           std::bitset<MAX_POINTER_ID + 1> pointerIds,
-                                           std::optional<nsecs_t> firstDownTimeInTarget,
-                                           std::optional<ui::LogicalDisplayId> pointerDisplayId,
-                                           const ConnectionManager& connections,
-                                           const DispatcherWindowInfo& windowInfos,
-                                           std::function<void()> dump,
-                                           std::vector<InputTarget>& inputTargets);
+        DispatcherTouchState(const DispatcherWindowInfo& windowInfos,
+                             const ConnectionManager& connections);
+
+        void addPointerWindowTarget(const sp<android::gui::WindowInfoHandle>& windowHandle,
+                                    InputTarget::DispatchMode dispatchMode,
+                                    ftl::Flags<InputTarget::Flags> targetFlags,
+                                    std::bitset<MAX_POINTER_ID + 1> pointerIds,
+                                    std::optional<nsecs_t> firstDownTimeInTarget,
+                                    std::optional<ui::LogicalDisplayId> pointerDisplayId,
+                                    std::function<void()> dump,
+                                    std::vector<InputTarget>& inputTargets);
 
         base::Result<std::vector<InputTarget>, android::os::InputEventInjectionResult>
         findTouchedWindowTargets(nsecs_t currentTime, const MotionEntry& entry,
-                                 const ConnectionManager& connections,
-                                 const DispatcherWindowInfo& windowInfos,
                                  const sp<android::gui::WindowInfoHandle> dragWindow,
                                  std::function<void(const MotionEntry&)> addDragEvent,
                                  std::function<void()> dump);
@@ -421,8 +420,7 @@
 
         // Updates the touchState for display from WindowInfo,
         // returns list of CancellationArgs for every cancelled touch
-        std::list<CancellationArgs> updateFromWindowInfo(ui::LogicalDisplayId displayId,
-                                                         const DispatcherWindowInfo& windowInfos);
+        std::list<CancellationArgs> updateFromWindowInfo(ui::LogicalDisplayId displayId);
 
         void removeAllPointersForDevice(DeviceId deviceId);
 
@@ -431,9 +429,7 @@
         std::optional<
                 std::tuple<sp<gui::WindowInfoHandle>, DeviceId, std::vector<PointerProperties>,
                            std::list<CancellationArgs>, std::list<PointerDownArgs>>>
-        transferTouchGesture(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
-                             const DispatcherWindowInfo& windowInfos,
-                             const ConnectionManager& connections);
+        transferTouchGesture(const sp<IBinder>& fromToken, const sp<IBinder>& toToken);
 
         base::Result<std::list<CancellationArgs>, status_t> pilferPointers(
                 const sp<IBinder>& token, const Connection& requestingConnection);
@@ -474,40 +470,31 @@
                 const sp<gui::WindowInfoHandle> toWindowHandle, TouchState& state,
                 DeviceId deviceId, const std::vector<PointerProperties>& pointers,
                 ftl::Flags<InputTarget::Flags> oldTargetFlags,
-                ftl::Flags<InputTarget::Flags> newTargetFlags,
-                const DispatcherWindowInfo& windowInfos, const ConnectionManager& connections);
+                ftl::Flags<InputTarget::Flags> newTargetFlags);
 
-        void saveTouchStateForMotionEntry(const MotionEntry& entry, TouchState&& touchState,
-                                          const DispatcherWindowInfo& windowInfos);
+        void saveTouchStateForMotionEntry(const MotionEntry& entry, TouchState&& touchState);
 
-        void eraseTouchStateForMotionEntry(const MotionEntry& entry,
-                                           const DispatcherWindowInfo& windowInfos);
+        void eraseTouchStateForMotionEntry(const MotionEntry& entry);
 
         const TouchState* getTouchStateForMotionEntry(
-                const android::inputdispatcher::MotionEntry& entry,
-                const DispatcherWindowInfo& windowInfos) const;
+                const android::inputdispatcher::MotionEntry& entry) const;
 
         bool canWindowReceiveMotion(const sp<gui::WindowInfoHandle>& window,
-                                    const MotionEntry& motionEntry,
-                                    const ConnectionManager& connections,
-                                    const DispatcherWindowInfo& windowInfos) const;
+                                    const MotionEntry& motionEntry) const;
 
         // Return true if stylus is currently down anywhere on the specified display,
         // and false otherwise.
         bool isStylusActiveInDisplay(ui::LogicalDisplayId displayId) const;
 
-        static std::list<CancellationArgs> eraseRemovedWindowsFromWindowInfo(
-                TouchState& state, ui::LogicalDisplayId displayId,
-                const DispatcherWindowInfo& windowInfos);
+        std::list<CancellationArgs> eraseRemovedWindowsFromWindowInfo(
+                TouchState& state, ui::LogicalDisplayId displayId);
 
-        static std::list<CancellationArgs> updateHoveringStateFromWindowInfo(
-                TouchState& state, ui::LogicalDisplayId displayId,
-                const DispatcherWindowInfo& windowInfos);
+        std::list<CancellationArgs> updateHoveringStateFromWindowInfo(
+                TouchState& state, ui::LogicalDisplayId displayId);
 
-        static std::vector<InputTarget> findOutsideTargets(
-                ui::LogicalDisplayId displayId, const sp<gui::WindowInfoHandle>& touchedWindow,
-                int32_t pointerId, const ConnectionManager& connections,
-                const DispatcherWindowInfo& windowInfos, std::function<void()> dump);
+        std::vector<InputTarget> findOutsideTargets(ui::LogicalDisplayId displayId,
+                                                    const sp<gui::WindowInfoHandle>& touchedWindow,
+                                                    int32_t pointerId, std::function<void()> dump);
 
         /**
          * Slip the wallpaper touch if necessary.
@@ -522,18 +509,18 @@
          * @param targets the current targets to add the walpaper ones to
          * @param eventTime the new downTime for the wallpaper target
          */
-        static void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
-                                       const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
-                                       const sp<android::gui::WindowInfoHandle>& newWindowHandle,
-                                       TouchState& state, const MotionEntry& entry,
-                                       std::vector<InputTarget>& targets,
-                                       const ConnectionManager& connections,
-                                       const DispatcherWindowInfo& windowInfos,
-                                       std::function<void()> dump);
+        void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
+                                const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
+                                const sp<android::gui::WindowInfoHandle>& newWindowHandle,
+                                TouchState& state, const MotionEntry& entry,
+                                std::vector<InputTarget>& targets, std::function<void()> dump);
 
-        static ftl::Flags<InputTarget::Flags> getTargetFlags(
+        ftl::Flags<InputTarget::Flags> getTargetFlags(
                 const sp<android::gui::WindowInfoHandle>& targetWindow, vec2 targetPosition,
-                bool isSplit, const DispatcherWindowInfo& windowInfos);
+                bool isSplit);
+
+        const DispatcherWindowInfo& mWindowInfos;
+        const ConnectionManager& mConnectionManager;
     };
 
     DispatcherTouchState mTouchStates GUARDED_BY(mLock);
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index d21c4d7..782a54f 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "DebugConfig.h"
+#include "input/Input.h"
 #include "input/InputDevice.h"
 #include "input/InputFlags.h"
 
@@ -25,17 +26,6 @@
 
 namespace android::inputdispatcher {
 
-namespace {
-
-bool isMouseOrTouchpad(uint32_t sources) {
-    // Check if this is a mouse or touchpad, but not a drawing tablet.
-    return isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE) ||
-            (isFromSource(sources, AINPUT_SOURCE_MOUSE) &&
-             !isFromSource(sources, AINPUT_SOURCE_STYLUS));
-}
-
-} // namespace
-
 InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}
 
 InputState::~InputState() {}
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 5dce074..adbfdeb 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -88,6 +88,7 @@
  * If any new classes are added, we need to add them in rust input side too.
  */
 enum class InputDeviceClass : uint32_t {
+    // LINT.IfChange
     /* The input device is a keyboard or has buttons. */
     KEYBOARD = android::os::IInputConstants::DEVICE_CLASS_KEYBOARD,
 
@@ -144,6 +145,7 @@
 
     /* The input device is external (not built-in). */
     EXTERNAL = android::os::IInputConstants::DEVICE_CLASS_EXTERNAL,
+    // LINT.ThenChange(frameworks/native/services/inputflinger/tests/fuzzers/MapperHelpers.h)
 };
 
 enum class SysfsClass : uint32_t {
diff --git a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
index 767f9cd..43975f0 100644
--- a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
+++ b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
@@ -21,6 +21,14 @@
 static constexpr int32_t MAX_RANDOM_POINTERS = 4;
 static constexpr int32_t MAX_RANDOM_DEVICES = 4;
 
+// The maximum value that we use for the action button field of NotifyMotionArgs. (We allow multiple
+// bits to be set for this since we're just trying to generate a fuzzed event stream that doesn't
+// cause crashes when enum values are converted to Rust — we don't necessarily want it to be valid.)
+//
+// AMOTION_EVENT_BUTTON_STYLUS_SECONDARY should be replaced with whatever AMOTION_EVENT_BUTTON_
+// value is highest if the enum is edited.
+static constexpr int8_t MAX_ACTION_BUTTON_VALUE = (AMOTION_EVENT_BUTTON_STYLUS_SECONDARY << 1) - 1;
+
 int getFuzzedMotionAction(FuzzedDataProvider& fdp) {
     int actionMasked = fdp.PickValueInArray<int>({
             AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_ACTION_UP, AMOTION_EVENT_ACTION_MOVE,
@@ -185,18 +193,16 @@
             fdp.ConsumeIntegralInRange<nsecs_t>(currentTime - 5E9, currentTime + 5E9);
     const nsecs_t readTime = downTime;
     const nsecs_t eventTime = fdp.ConsumeIntegralInRange<nsecs_t>(downTime, downTime + 1E9);
+    const int32_t actionButton = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_ACTION_BUTTON_VALUE);
 
     const float cursorX = fdp.ConsumeIntegralInRange<int>(-10000, 10000);
     const float cursorY = fdp.ConsumeIntegralInRange<int>(-10000, 10000);
     return NotifyMotionArgs(idGenerator.nextId(), eventTime, readTime, deviceId, source, displayId,
-                            POLICY_FLAG_PASS_TO_USER, action,
-                            /*actionButton=*/fdp.ConsumeIntegral<int32_t>(),
+                            POLICY_FLAG_PASS_TO_USER, action, actionButton,
                             getFuzzedFlags(fdp, action), AMETA_NONE, getFuzzedButtonState(fdp),
                             MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
-                            pointerProperties.data(), pointerCoords.data(),
-                            /*xPrecision=*/0,
-                            /*yPrecision=*/0, cursorX, cursorY, downTime,
-                            /*videoFrames=*/{});
+                            pointerProperties.data(), pointerCoords.data(), /*xPrecision=*/0,
+                            /*yPrecision=*/0, cursorX, cursorY, downTime, /*videoFrames=*/{});
 }
 
 } // namespace android
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index 846260a..bba7389 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -34,6 +34,28 @@
                                   android::EventHubInterface::DEVICE_ADDED,
                                   android::EventHubInterface::DEVICE_REMOVED};
 
+static const android::InputDeviceClass kInputDeviceClasses[] = {
+        android::InputDeviceClass::KEYBOARD,
+        android::InputDeviceClass::ALPHAKEY,
+        android::InputDeviceClass::TOUCH,
+        android::InputDeviceClass::CURSOR,
+        android::InputDeviceClass::TOUCH_MT,
+        android::InputDeviceClass::DPAD,
+        android::InputDeviceClass::GAMEPAD,
+        android::InputDeviceClass::SWITCH,
+        android::InputDeviceClass::JOYSTICK,
+        android::InputDeviceClass::VIBRATOR,
+        android::InputDeviceClass::MIC,
+        android::InputDeviceClass::EXTERNAL_STYLUS,
+        android::InputDeviceClass::ROTARY_ENCODER,
+        android::InputDeviceClass::SENSOR,
+        android::InputDeviceClass::BATTERY,
+        android::InputDeviceClass::LIGHT,
+        android::InputDeviceClass::TOUCHPAD,
+        android::InputDeviceClass::VIRTUAL,
+        android::InputDeviceClass::EXTERNAL,
+};
+
 constexpr size_t kValidCodes[] = {
         SYN_REPORT,
         ABS_MT_SLOT,
@@ -105,7 +127,13 @@
     void addProperty(std::string key, std::string value) { mFuzzConfig.addProperty(key, value); }
 
     ftl::Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override {
-        return ftl::Flags<InputDeviceClass>(mFdp->ConsumeIntegral<uint32_t>());
+        uint32_t flags = 0;
+        for (auto inputDeviceClass : kInputDeviceClasses) {
+            if (mFdp->ConsumeBool()) {
+                flags |= static_cast<uint32_t>(inputDeviceClass);
+            }
+        }
+        return ftl::Flags<InputDeviceClass>(flags);
     }
     InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override {
         return mIdentifier;
@@ -367,8 +395,8 @@
 template <class Fdp>
 InputDevice getFuzzedInputDevice(Fdp& fdp, FuzzInputReaderContext* context) {
     InputDeviceIdentifier identifier;
-    identifier.name = fdp.ConsumeRandomLengthString(16);
-    identifier.location = fdp.ConsumeRandomLengthString(12);
+    identifier.name = fdp.ConsumeRandomLengthUtf8String(16);
+    identifier.location = fdp.ConsumeRandomLengthUtf8String(12);
     int32_t deviceID = fdp.ConsumeIntegralInRange(0, 5);
     int32_t deviceGeneration = fdp.ConsumeIntegralInRange(0, 5);
     return InputDevice(context, deviceID, deviceGeneration, identifier);
diff --git a/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h b/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h
index 2f76f18..b258118 100644
--- a/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h
+++ b/services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h
@@ -15,7 +15,7 @@
  */
 
 #include <fuzzer/FuzzedDataProvider.h>
-
+#include <algorithm>
 /**
  * A thread-safe interface to the FuzzedDataProvider
  */
@@ -60,6 +60,44 @@
         return FuzzedDataProvider::ConsumeRandomLengthString();
     }
 
+    // Converting the string to a UTF-8 string by setting the prefix bits of each
+    // byte according to UTF-8 encoding rules.
+    std::string ConsumeRandomLengthUtf8String(size_t max_length) {
+        std::scoped_lock _l(mLock);
+        std::string result = FuzzedDataProvider::ConsumeRandomLengthString(max_length);
+        size_t remaining_bytes = result.length(), idx = 0;
+        while (remaining_bytes > 0) {
+            size_t random_byte_size = FuzzedDataProvider::ConsumeIntegralInRange(1, 4);
+            size_t byte_size = std::min(random_byte_size, remaining_bytes);
+            switch (byte_size) {
+                // Prefix byte: 0xxxxxxx
+                case 1:
+                    result[idx++] &= 0b01111111;
+                    break;
+                // Prefix bytes: 110xxxxx 10xxxxxx
+                case 2:
+                    result[idx++] = (result[idx] & 0b00011111) | 0b11000000;
+                    result[idx++] = (result[idx] & 0b00111111) | 0b10000000;
+                    break;
+                // Prefix bytes: 1110xxxx 10xxxxxx 10xxxxxx
+                case 3:
+                    result[idx++] = (result[idx] & 0b00001111) | 0b11100000;
+                    result[idx++] = (result[idx] & 0b00111111) | 0b10000000;
+                    result[idx++] = (result[idx] & 0b00111111) | 0b10000000;
+                    break;
+                // Prefix bytes: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+                case 4:
+                    result[idx++] = (result[idx] & 0b00000111) | 0b11110000;
+                    result[idx++] = (result[idx] & 0b00111111) | 0b10000000;
+                    result[idx++] = (result[idx] & 0b00111111) | 0b10000000;
+                    result[idx++] = (result[idx] & 0b00111111) | 0b10000000;
+                    break;
+            }
+            remaining_bytes -= byte_size;
+        }
+        return result;
+    }
+
     std::string ConsumeRemainingBytesAsString() {
         std::scoped_lock _l(mLock);
         return FuzzedDataProvider::ConsumeRemainingBytesAsString();
diff --git a/vulkan/tests/Android.bp b/vulkan/tests/Android.bp
new file mode 100644
index 0000000..551d9b7
--- /dev/null
+++ b/vulkan/tests/Android.bp
@@ -0,0 +1,56 @@
+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_test {
+    name: "libvulkan_test",
+    test_suites: ["general-tests"],
+
+    srcs: [
+        "libvulkan_test.cpp",
+    ],
+
+    strip: {
+        none: true,
+    },
+
+    cflags: [
+        "-DVK_USE_PLATFORM_ANDROID_KHR",
+        "-Wall",
+        "-Werror",
+    ],
+
+    header_libs: [
+        "hwvulkan_headers",
+        "vulkan_headers",
+    ],
+
+    cppflags: [
+        "-Wno-c++98-compat-pedantic",
+        "-Wno-c99-extensions",
+        "-Wno-exit-time-destructors",
+        "-Wno-float-equal",
+        "-Wno-global-constructors",
+        "-Wno-zero-length-array",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libgraphicsenv",
+        "liblog",
+        "libmediandk",
+        "libvulkan",
+    ],
+
+    static_libs: [
+        "libgmock",
+        "libgtest",
+        "liblog",
+    ],
+
+}
diff --git a/vulkan/tests/README.md b/vulkan/tests/README.md
new file mode 100644
index 0000000..3c9b66c
--- /dev/null
+++ b/vulkan/tests/README.md
@@ -0,0 +1,24 @@
+#libvulkan_test
+
+This binary contains the unit tests for testing libvulkan (The Vulkan Loader).
+
+These tests rely on the underlying GPU driver to be able to successfully create a valid
+swapchain. These tests are design to run on an Android emulator to give us a consistent GPU
+driver to test against. YMMV when running this on a physical device with an arbitrary GPU
+driver.
+
+To run these tests run:
+```
+atest libvulkan_test
+```
+
+If using an acloud device the full command list for the root of a freshly cloned repo would be:
+```
+source build/envsetup.sh
+lunch aosp_cf_x86_64_phone-trunk_staging-eng
+m
+acloud create --local-image
+atest libvulkan_test
+```
+
+
diff --git a/vulkan/tests/libvulkan_test.cpp b/vulkan/tests/libvulkan_test.cpp
new file mode 100644
index 0000000..128d640
--- /dev/null
+++ b/vulkan/tests/libvulkan_test.cpp
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/log.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <media/NdkImageReader.h>
+#include <vulkan/vulkan.h>
+
+#define LOGI(...) \
+    __android_log_print(ANDROID_LOG_INFO, "libvulkan_test", __VA_ARGS__)
+#define LOGE(...) \
+    __android_log_print(ANDROID_LOG_ERROR, "libvulkan_test", __VA_ARGS__)
+
+#define VK_CHECK(result) ASSERT_EQ(VK_SUCCESS, result)
+
+namespace android {
+
+class AImageReaderVulkanSwapchainTest : public ::testing::Test {
+   public:
+    AImageReaderVulkanSwapchainTest() {}
+
+    AImageReader* mReader = nullptr;
+    ANativeWindow* mWindow = nullptr;
+    VkInstance mVkInstance = VK_NULL_HANDLE;
+    VkPhysicalDevice mPhysicalDev = VK_NULL_HANDLE;
+    VkDevice mDevice = VK_NULL_HANDLE;
+    VkSurfaceKHR mSurface = VK_NULL_HANDLE;
+    VkQueue mPresentQueue = VK_NULL_HANDLE;
+    uint32_t mPresentQueueFamily = UINT32_MAX;
+    VkSwapchainKHR mSwapchain = VK_NULL_HANDLE;
+
+    void SetUp() override {}
+
+    void TearDown() override {}
+
+    // ------------------------------------------------------
+    // Helper methods
+    // ------------------------------------------------------
+
+    void createVulkanInstance(std::vector<const char*>& layers) {
+        const char* extensions[] = {
+            VK_KHR_SURFACE_EXTENSION_NAME,
+            VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
+            VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
+        };
+
+        VkApplicationInfo appInfo{};
+        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+        appInfo.pApplicationName = "AImageReader Vulkan Swapchain Test";
+        appInfo.applicationVersion = 1;
+        appInfo.pEngineName = "TestEngine";
+        appInfo.engineVersion = 1;
+        appInfo.apiVersion = VK_API_VERSION_1_0;
+
+        VkInstanceCreateInfo instInfo{};
+        instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+        instInfo.pApplicationInfo = &appInfo;
+        instInfo.enabledExtensionCount =
+            sizeof(extensions) / sizeof(extensions[0]);
+        instInfo.ppEnabledExtensionNames = extensions;
+        instInfo.enabledLayerCount = layers.size();
+        instInfo.ppEnabledLayerNames = layers.data();
+        VkResult res = vkCreateInstance(&instInfo, nullptr, &mVkInstance);
+        VK_CHECK(res);
+        LOGE("Vulkan instance created");
+    }
+
+    void createAImageReader(int width, int height, int format, int maxImages) {
+        media_status_t status =
+            AImageReader_new(width, height, format, maxImages, &mReader);
+        ASSERT_EQ(AMEDIA_OK, status) << "Failed to create AImageReader";
+        ASSERT_NE(nullptr, mReader) << "AImageReader is null";
+
+        // Optionally set a listener
+        AImageReader_ImageListener listener{};
+        listener.context = this;
+        listener.onImageAvailable =
+            &AImageReaderVulkanSwapchainTest::onImageAvailable;
+        AImageReader_setImageListener(mReader, &listener);
+
+        LOGI("AImageReader created with %dx%d, format=%d", width, height,
+             format);
+    }
+
+    void getANativeWindowFromReader() {
+        ASSERT_NE(nullptr, mReader);
+
+        media_status_t status = AImageReader_getWindow(mReader, &mWindow);
+        ASSERT_EQ(AMEDIA_OK, status)
+            << "Failed to get ANativeWindow from AImageReader";
+        ASSERT_NE(nullptr, mWindow) << "ANativeWindow is null";
+        LOGI("ANativeWindow obtained from AImageReader");
+    }
+
+    void createVulkanSurface() {
+        ASSERT_NE((VkInstance)VK_NULL_HANDLE, mVkInstance);
+        ASSERT_NE((ANativeWindow*)nullptr, mWindow);
+
+        VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo{};
+        surfaceCreateInfo.sType =
+            VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
+        surfaceCreateInfo.window = mWindow;
+
+        VkResult res = vkCreateAndroidSurfaceKHR(
+            mVkInstance, &surfaceCreateInfo, nullptr, &mSurface);
+        VK_CHECK(res);
+        LOGI("Vulkan surface created from ANativeWindow");
+    }
+
+    void pickPhysicalDeviceAndQueueFamily() {
+        ASSERT_NE((VkInstance)VK_NULL_HANDLE, mVkInstance);
+
+        uint32_t deviceCount = 0;
+        vkEnumeratePhysicalDevices(mVkInstance, &deviceCount, nullptr);
+        ASSERT_GT(deviceCount, 0U) << "No Vulkan physical devices found!";
+
+        std::vector<VkPhysicalDevice> devices(deviceCount);
+        vkEnumeratePhysicalDevices(mVkInstance, &deviceCount, devices.data());
+
+        for (auto& dev : devices) {
+            uint32_t queueFamilyCount = 0;
+            vkGetPhysicalDeviceQueueFamilyProperties(dev, &queueFamilyCount,
+                                                     nullptr);
+            std::vector<VkQueueFamilyProperties> queueProps(queueFamilyCount);
+            vkGetPhysicalDeviceQueueFamilyProperties(dev, &queueFamilyCount,
+                                                     queueProps.data());
+
+            for (uint32_t i = 0; i < queueFamilyCount; i++) {
+                VkBool32 support = VK_FALSE;
+                vkGetPhysicalDeviceSurfaceSupportKHR(dev, i, mSurface,
+                                                     &support);
+                if (support == VK_TRUE) {
+                    // Found a queue family that can present
+                    mPhysicalDev = dev;
+                    mPresentQueueFamily = i;
+
+                    LOGI(
+                        "Physical device found with queue family %u supporting "
+                        "present",
+                        i);
+                    return;
+                }
+            }
+        }
+
+        FAIL()
+            << "No physical device found that supports present to the surface!";
+    }
+
+    void createDeviceAndGetQueue(std::vector<const char*>& layers) {
+        ASSERT_NE((void*)VK_NULL_HANDLE, mPhysicalDev);
+        ASSERT_NE(UINT32_MAX, mPresentQueueFamily);
+
+        float queuePriority = 1.0f;
+        VkDeviceQueueCreateInfo queueInfo{};
+        queueInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+        queueInfo.queueFamilyIndex = mPresentQueueFamily;
+        queueInfo.queueCount = 1;
+        queueInfo.pQueuePriorities = &queuePriority;
+
+        VkDeviceCreateInfo deviceInfo{};
+        deviceInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+        deviceInfo.queueCreateInfoCount = 1;
+        deviceInfo.pQueueCreateInfos = &queueInfo;
+        deviceInfo.enabledLayerCount = layers.size();
+        deviceInfo.ppEnabledLayerNames = layers.data();
+
+        const char* extensions[] = {
+            VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+        };
+        deviceInfo.enabledExtensionCount =
+            sizeof(extensions) / sizeof(extensions[0]);
+        deviceInfo.ppEnabledExtensionNames = extensions;
+
+        VkResult res =
+            vkCreateDevice(mPhysicalDev, &deviceInfo, nullptr, &mDevice);
+        VK_CHECK(res);
+        LOGI("Logical device created");
+
+        vkGetDeviceQueue(mDevice, mPresentQueueFamily, 0, &mPresentQueue);
+        ASSERT_NE((VkQueue)VK_NULL_HANDLE, mPresentQueue);
+        LOGI("Acquired present-capable queue");
+    }
+
+    void createSwapchain() {
+        ASSERT_NE((VkDevice)VK_NULL_HANDLE, mDevice);
+        ASSERT_NE((VkSurfaceKHR)VK_NULL_HANDLE, mSurface);
+
+        VkSurfaceCapabilitiesKHR surfaceCaps{};
+        VK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
+            mPhysicalDev, mSurface, &surfaceCaps));
+
+        uint32_t formatCount = 0;
+        vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDev, mSurface,
+                                             &formatCount, nullptr);
+        ASSERT_GT(formatCount, 0U);
+        std::vector<VkSurfaceFormatKHR> formats(formatCount);
+        vkGetPhysicalDeviceSurfaceFormatsKHR(mPhysicalDev, mSurface,
+                                             &formatCount, formats.data());
+
+        VkSurfaceFormatKHR chosenFormat = formats[0];
+        LOGI("Chosen surface format: %d", chosenFormat.format);
+
+        uint32_t presentModeCount = 0;
+        vkGetPhysicalDeviceSurfacePresentModesKHR(mPhysicalDev, mSurface,
+                                                  &presentModeCount, nullptr);
+        ASSERT_GT(presentModeCount, 0U);
+        std::vector<VkPresentModeKHR> presentModes(presentModeCount);
+        vkGetPhysicalDeviceSurfacePresentModesKHR(
+            mPhysicalDev, mSurface, &presentModeCount, presentModes.data());
+
+        VkPresentModeKHR chosenPresentMode = VK_PRESENT_MODE_FIFO_KHR;
+        for (auto mode : presentModes) {
+            if (mode == VK_PRESENT_MODE_FIFO_KHR) {
+                chosenPresentMode = mode;
+                break;
+            }
+        }
+        LOGI("Chosen present mode: %d", chosenPresentMode);
+
+        VkExtent2D swapchainExtent{};
+        if (surfaceCaps.currentExtent.width == 0xFFFFFFFF) {
+            swapchainExtent.width = 640;   // fallback
+            swapchainExtent.height = 480;  // fallback
+        } else {
+            swapchainExtent = surfaceCaps.currentExtent;
+        }
+        LOGI("Swapchain extent: %d x %d", swapchainExtent.width,
+             swapchainExtent.height);
+
+        uint32_t desiredImageCount = surfaceCaps.minImageCount + 1;
+        if (surfaceCaps.maxImageCount > 0 &&
+            desiredImageCount > surfaceCaps.maxImageCount) {
+            desiredImageCount = surfaceCaps.maxImageCount;
+        }
+
+        VkSwapchainCreateInfoKHR swapchainInfo{};
+        swapchainInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+        swapchainInfo.surface = mSurface;
+        swapchainInfo.minImageCount = desiredImageCount;
+        swapchainInfo.imageFormat = chosenFormat.format;
+        swapchainInfo.imageColorSpace = chosenFormat.colorSpace;
+        swapchainInfo.imageExtent = swapchainExtent;
+        swapchainInfo.imageArrayLayers = 1;
+        swapchainInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+                                   VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+        swapchainInfo.preTransform = surfaceCaps.currentTransform;
+        swapchainInfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
+        swapchainInfo.presentMode = chosenPresentMode;
+        swapchainInfo.clipped = VK_TRUE;
+        swapchainInfo.oldSwapchain = VK_NULL_HANDLE;
+
+        uint32_t queueFamilyIndices[] = {mPresentQueueFamily};
+        swapchainInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+        swapchainInfo.queueFamilyIndexCount = 1;
+        swapchainInfo.pQueueFamilyIndices = queueFamilyIndices;
+
+        VkResult res =
+            vkCreateSwapchainKHR(mDevice, &swapchainInfo, nullptr, &mSwapchain);
+        VK_CHECK(res);
+        LOGI("Swapchain created successfully");
+
+        uint32_t swapchainImageCount = 0;
+        vkGetSwapchainImagesKHR(mDevice, mSwapchain, &swapchainImageCount,
+                                nullptr);
+        std::vector<VkImage> swapchainImages(swapchainImageCount);
+        vkGetSwapchainImagesKHR(mDevice, mSwapchain, &swapchainImageCount,
+                                swapchainImages.data());
+
+        LOGI("Swapchain has %u images", swapchainImageCount);
+    }
+
+    // Image available callback (AImageReader)
+    static void onImageAvailable(void*, AImageReader* reader) {
+        LOGI("onImageAvailable callback triggered");
+        AImage* image = nullptr;
+        media_status_t status = AImageReader_acquireLatestImage(reader, &image);
+        if (status != AMEDIA_OK || !image) {
+            LOGE("Failed to acquire latest image");
+            return;
+        }
+        AImage_delete(image);
+        LOGI("Released acquired image");
+    }
+
+    void cleanUpSwapchainForTest() {
+        if (mSwapchain != VK_NULL_HANDLE) {
+            vkDestroySwapchainKHR(mDevice, mSwapchain, nullptr);
+            mSwapchain = VK_NULL_HANDLE;
+        }
+        if (mDevice != VK_NULL_HANDLE) {
+            vkDestroyDevice(mDevice, nullptr);
+            mDevice = VK_NULL_HANDLE;
+        }
+        if (mSurface != VK_NULL_HANDLE) {
+            vkDestroySurfaceKHR(mVkInstance, mSurface, nullptr);
+            mSurface = VK_NULL_HANDLE;
+        }
+        if (mVkInstance != VK_NULL_HANDLE) {
+            vkDestroyInstance(mVkInstance, nullptr);
+            mVkInstance = VK_NULL_HANDLE;
+        }
+        if (mReader) {
+            // AImageReader_delete(mReader);
+            mReader = nullptr;
+        }
+        // Note: The ANativeWindow from AImageReader is implicitly
+        // managed by the reader, so we don't explicitly delete it.
+        mWindow = nullptr;
+    }
+
+    void buildSwapchianForTest(std::vector<const char*>& instanceLayers,
+                               std::vector<const char*>& deviceLayers) {
+        createVulkanInstance(instanceLayers);
+
+        // the "atest libvulkan_test" command will execute this test as a binary
+        // (not apk) on the device. Consequently we can't render to the screen
+        // and need to work around this by using AImageReader*
+        createAImageReader(640, 480, AIMAGE_FORMAT_PRIVATE, 3);
+        getANativeWindowFromReader();
+        createVulkanSurface();
+        pickPhysicalDeviceAndQueueFamily();
+
+        createDeviceAndGetQueue(deviceLayers);
+        createSwapchain();
+    }
+};
+
+TEST_F(AImageReaderVulkanSwapchainTest, TestHelperMethods) {
+    // Verify that the basic plumbing/helper functions of these tests is
+    // working. This doesn't directly test any of the layer code. It only
+    // verifies that we can successfully create a swapchain with an AImageReader
+
+    std::vector<const char*> instanceLayers;
+    std::vector<const char*> deviceLayers;
+    buildSwapchianForTest(deviceLayers, instanceLayers);
+
+    ASSERT_NE(mVkInstance, (VkInstance)VK_NULL_HANDLE);
+    ASSERT_NE(mPhysicalDev, (VkPhysicalDevice)VK_NULL_HANDLE);
+    ASSERT_NE(mDevice, (VkDevice)VK_NULL_HANDLE);
+    ASSERT_NE(mSurface, (VkSurfaceKHR)VK_NULL_HANDLE);
+    ASSERT_NE(mSwapchain, (VkSwapchainKHR)VK_NULL_HANDLE);
+    cleanUpSwapchainForTest();
+}
+
+}  // namespace android