Repository navigation
Conversation
…follow them Battery state was already collected for every supported pad - GIP pads through Controller::statusReceived, Shield and Bluetooth pads through the 120 s poll - but only ever forwarded to the host, so nothing on the box could show it. The label: while any level of the in-game menu is up, a small end-aligned line along the bottom of the screen lists every controller's battery by host player number - "Controller 1: 75% · Controller 2: 50%, charging · Controller 3: wired". A new Android-free ControllerBatteries store, fed by both battery paths, keeps one reading per number; Game formats it. The menu is a chain of AlertDialogs whose onDismiss fires for a parent after its child has opened, so a DialogChain token decides which dismissal is the last one and hides the label. The dialog's dim stays; the text is the primary colour so it reads through it. The LED: "Battery level" is a fifth value of the Xbox button brightness setting. The guide LED is monochrome, so it steps through the presets - full Bright, medium Normal, low Dim - and slow-blinks at critical, since a warning has to look like one. GuideButtonLed holds every LED constant and the ladder on the Java side, where it is tested; the native side gains Controller::setLed, which stores the command and sends it once the device has started, and startDevice() applies whatever is stored through the same applyLed() so the frame is built in one place. ledBrightness loses its const and gains a pattern, as std::atomic against the startThread fallback. Two pre-existing faults fixed on the way, both found while wiring this up: - A GIP pad's first status packet usually lands before its first input report, which is what assigns its player number, so its first battery report went to the host as player 0. It is now held on the context until the number is assigned. - On the cabled path the read thread runs before the Java object exists, and a status in that window armed the driver's change filter with nobody listening, so Java heard nothing until the battery actually moved. The filter now stays unarmed until a report is delivered; MS-GIPUSB 2.2.9 has the pad repeat status every second for its first ten seconds. Latency: nothing on a per-frame path. Battery writes happen at battery-change frequency on driver threads; the label is built once per menu level on the UI thread while the decoder is already stalled behind the dialog, and is a child View of the window rather than a layer; the LED sends one GIP frame per level change on the driver's read thread, deduplicated against charge-only changes. Tests: ControllerBatteriesTest, DialogChainTest, GuideButtonLedTest, and the guide LED cases in PreferenceConfigurationTest. HARDWARE_TESTING.md 31 lists what needs a device - in particular whether a pad honours LED_BLINK_SLOW at all, which Table 42 leaves open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The pad-audio submenu counted GIP pads by their position in the driver's list, while the battery label - and the game on the host - use the player number ControllerHandler assigns. The two disagreed whenever the Shield controller or a Bluetooth pad held a slot: the first GIP pad read "Controller 1" in the menu and was player 2 everywhere else. The number now lives on the AbstractController too, written when the handler assigns it and cleared when the pad goes, so the menu - which holds the controller and nothing else - can read it without reaching into the handler's maps from the UI thread. GameMenuLayout.padNumber() is the rule, tested: the player number, or "Unused controller" for a pad that has not sent input yet, or the position when multi-controller is off and every pad is player 0. Numbering is still assigned on a pad's first input, deliberately. Assigning at deviceAdded() was considered and dropped: the USB service binds in onCreate(), and LiSendControllerArrivalEvent() returns -2 until the stream's input channel exists with nothing re-sending it, so a pad attached at launch would have been announced into the void and the host would never learn its type or capabilities. Latency: none; the menu builds its rows once per open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 of 3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Battery state was already collected for every supported pad but only ever forwarded to the host. Two additions, plus two pre-existing faults fixed on the way.
Battery label. While any level of the in-game menu is up, a small end-aligned line along the bottom of the screen lists every controller's battery by host player number:
ControllerBatteriesstore (one slot per player number,synchronized), fed by both existing battery paths inControllerHandler;Gameformats it.AlertDialogs whoseonDismissfires for a parent after its child has opened, so aDialogChaintoken decides which dismissal is the last one and hides the label."Battery level" guide LED. A fifth value of the Xbox button brightness setting. The guide LED is monochrome, so it steps through the presets — full = Bright, medium = Normal, low = Dim — and slow-blinks at critical. A battery-less cabled pad goes Bright.
GuideButtonLedholds every LED constant and the ladder on the Java side, where it is tested; the native side only applies what it is handed and refuses a pattern outsideenum LedMode.Controller::setLed()stores the command and sends it once the device has started;startDevice()applies whatever is stored through the sameapplyLed(), so the frame is built in one place.ledBrightnessloses itsconst(and the comment that justified it) and gains a pattern, bothstd::atomicagainst the startThread fallback. New JNI entrysetLedNative (JII)Z.Fixed on the way:
Pad-audio menu numbering. The submenu counted GIP pads by their position in the driver's list, which disagreed with the label and the host whenever the Shield controller or a Bluetooth pad held a slot. It now names pads by the host's player number (
GameMenuLayout.padNumber(), tested), reading "Unused controller" for a pad that has not sent input yet, and keeping positional numbers only when multi-controller is off (every pad is player 0 there). Numbering is still assigned on first input, deliberately: assigning atdeviceAdded()was considered and dropped becauseLiSendControllerArrivalEvent()returns-2until the stream's input channel exists and nothing re-sends it, so a pad attached at launch would be announced into the void.Latency and performance impact
None on any per-frame path. Battery writes happen at battery-change frequency on driver threads; the label is built once per menu level on the UI thread while the decoder is already stalled behind the dialog, and is a child View of the window rather than a layer (§14); the LED sends one GIP frame per level change on the driver's read thread, deduplicated against charge-only changes.
Applies to
Label: all pads on both boxes (Shield controller via
sceManager; Bluetooth pads via API-31BatteryState, so Homatics only; GIP pads on both). LED: GIP pads this app drives, on both boxes.Test plan
./gradlew testDebugUnitTest— 366 tests, 0 failures (62 new/updated:ControllerBatteriesTest,DialogChainTest,GuideButtonLedTest,PreferenceConfigurationTest,GameMenuLayoutTest)./gradlew assembleRelease lintRelease— both ABIs build; the 2 lint warnings are pre-existing (UsbDriverService,Profiler)HARDWARE_TESTING.md§31 — in particular whether a pad honoursLED_BLINK_SLOWat all, which Table 42 leaves open, and that the label is legible under the dialog's dim🤖 Generated with Claude Code