WIP: Android: adaptive triggers over Bluetooth (experimental, opt-in) #831

Draft
aoleg94 wants to merge 15 commits from aoleg94/android-dualsense-triggers into main
aoleg94 commented 2026-10-03 17:42:05 -05:00 (Migrated from github.com)

Stacked on #830 (includes #828 and #830 until they are merged).

Summary

Adaptive triggers need DualSense output reports, which Android's public APIs can't send. This
adds an opt-in setting, Adaptive triggers (experimental), off by default, Android 12+,
that writes Bluetooth output reports (0x31) to a paired DualSense directly. With it on, the
pad gets adaptive triggers, fine-grained rumble (vibration v2), lightbar colour and player LEDs.

How

  • The hidden BluetoothHidHost.sendData (HID host profile proxy), reached through
    org.lsposed.hiddenapibypass:hiddenapibypass 4.3 (Apache-2.0). This is a non-SDK API: it may
    stop working on some OS versions or OEM builds, which is why it is opt-in and falls back.
  • DualSenseReport builds the full 78-byte report (CRC32, valid flags, triggers, motors, LEDs);
    ReportCoalescer sends at most one report per 4 ms, latest wins.
  • On stream end or controller change the triggers are released (desktop's clear effect) and
    motors stopped before the proxy is closed.
  • Enabling it requests BLUETOOTH_CONNECT; the manifest adds that permission and a <queries>
    entry for the Bluetooth HID host service.
  • After 5 consecutive send failures the router falls back to #830's backend and shows one toast.

Testing

  • New DualSenseReportTest (golden byte arrays incl. CRC), DualSenseBtOutputTest,
    ReportCoalescerTest, OutputRouterFallbackTest.
  • Galaxy Z Fold 4 (Android 13) + DualSense over Bluetooth, PS5: adaptive triggers in Astro's
    Playroom, rumble indistinguishable from desktop, lightbar follows the game, player 1 LED lit,
    triggers released on leaving the stream.

Notes

Per CONTRIBUTOR_GUIDE.md: the new dependency is declared only in android/
(libs.versions.toml), nothing in CMakeLists.txt, scripts/ or workflows refers to it.

Developed with the assistance of Claude Code, and tested on real hardware as described above.

🤖 Generated with Claude Code

Stacked on #830 (includes #828 and #830 until they are merged). ### Summary Adaptive triggers need DualSense output reports, which Android's public APIs can't send. This adds an **opt-in** setting, **Adaptive triggers (experimental)**, off by default, Android 12+, that writes Bluetooth output reports (`0x31`) to a paired DualSense directly. With it on, the pad gets adaptive triggers, fine-grained rumble (vibration v2), lightbar colour and player LEDs. ### How - The hidden `BluetoothHidHost.sendData` (HID host profile proxy), reached through `org.lsposed.hiddenapibypass:hiddenapibypass` 4.3 (Apache-2.0). This is a non-SDK API: it may stop working on some OS versions or OEM builds, which is why it is opt-in and falls back. - `DualSenseReport` builds the full 78-byte report (CRC32, valid flags, triggers, motors, LEDs); `ReportCoalescer` sends at most one report per 4 ms, latest wins. - On stream end or controller change the triggers are released (desktop's clear effect) and motors stopped before the proxy is closed. - Enabling it requests `BLUETOOTH_CONNECT`; the manifest adds that permission and a `<queries>` entry for the Bluetooth HID host service. - After 5 consecutive send failures the router falls back to #830's backend and shows one toast. ### Testing - New `DualSenseReportTest` (golden byte arrays incl. CRC), `DualSenseBtOutputTest`, `ReportCoalescerTest`, `OutputRouterFallbackTest`. - Galaxy Z Fold 4 (Android 13) + DualSense over Bluetooth, PS5: adaptive triggers in Astro's Playroom, rumble indistinguishable from desktop, lightbar follows the game, player 1 LED lit, triggers released on leaving the stream. ### Notes Per `CONTRIBUTOR_GUIDE.md`: the new dependency is declared only in `android/` (`libs.versions.toml`), nothing in `CMakeLists.txt`, `scripts/` or workflows refers to it. Developed with the assistance of Claude Code, and tested on real hardware as described above. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This pull request is broken due to missing fork information.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin 'aoleg94/android-dualsense-triggers:aoleg94/android-dualsense-triggers'
git switch 'aoleg94/android-dualsense-triggers'

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch 'main'
git merge --no-ff 'aoleg94/android-dualsense-triggers'
git switch 'aoleg94/android-dualsense-triggers'
git rebase 'main'
git switch 'main'
git merge --ff-only 'aoleg94/android-dualsense-triggers'
git switch 'aoleg94/android-dualsense-triggers'
git rebase 'main'
git switch 'main'
git merge --no-ff 'aoleg94/android-dualsense-triggers'
git switch 'main'
git merge --squash 'aoleg94/android-dualsense-triggers'
git switch 'main'
git merge --ff-only 'aoleg94/android-dualsense-triggers'
git switch 'main'
git merge 'aoleg94/android-dualsense-triggers'
git push origin 'main'
Sign in to join this conversation.
No description provided.