Reported from the field: an SC2 wired or on its Puck dongle changes the console UI to the
controller layout, but the same pad paired over Bluetooth "doesn't come up as a controller at
all".
The manifest declared BLUETOOTH_CONNECT and all three BLE gates tested for it — MainActivity's
menu capture, StreamScreen's stream capture, and the Controllers screen — but no code path in
the client ever REQUESTED it. It is a runtime permission from API 31, so the checks answered
DENIED forever and the BLE transport was unreachable in every shipped build. The failure was
silent by construction: `bondedDevices` behind a missing permission throws, and the enumeration
caught that into an empty list, which reads exactly like "no controller is paired".
- Ask for it, but only of people it helps: an uncaptured SC2 sits in lizard mode as a
keyboard/mouse InputDevice, which needs no permission to enumerate, so `sc2InputDevicePresent`
is the evidence that justifies the prompt. MainActivity asks once per process on that signal
and engages the capture on the grant.
- The Controllers screen offers the grant outright, since the probe reads a USB identity we
cannot assume a BLE stack reports. `sc2BluetoothGrantOffered` keeps that offer to the cases
where it changes something — pure, and table-tested.
- Declare the legacy BLUETOOTH permission below API 31. BLUETOOTH_CONNECT does not imply it, so
the bonded list and connectGatt would have thrown SecurityException on Android 11 and older
even once the runtime half worked.
- Gate the link on the permission itself and log when it is missing, so the next occurrence of
this says so in logcat instead of looking like an absent controller.
Verified: :kit:testDebugUnitTest + :app:testDebugUnitTest green (5 new), manifest merge carries
both permissions. On-glass with a BLE-paired SC2 is owed.