Repository navigation
Android parity: stop reporting a disarm that never reached the strap (#730) - #785
Merged
Merged
Conversation
…730) #784 fixed this on iOS/macOS; Android had the identical defect. disableStrapAlarm() logged 'Alarm: disarmed' unconditionally while send() drops the write with 'ignored — not connected', so the app claimed the firmware alarm was cleared when the command never reached the strap. If one IS armed the strap still buzzes. Mirrors the Swift fix: a commandChannelReady twin (the same conditions send() guards on) decides which line is logged. Android did NOT have the second iOS defect. reconcileStrapAlarm() is the sole caller of arm/disarm, handles the disarm case internally, and is called UNGATED on the bond edge (state.bonded && !lastBonded) plus the daily tick - so a deferred disarm was already re-issued on reconnect. iOS's smartAlarmEnabled guard was the outlier, and #784 brought it into line. The #730 restored-connect diagnostic does not port: Android has no CoreBluetooth state restoration, so that path has no Android analogue. App-target Android: not compiled here (aapt2 is x86-only on this host) and not hardware-tested.
Nothing outside WhoopBleClient reads it, and the macOS twin is private - keep the API surface identical on both platforms.
ryanbr
added a commit
that referenced
this pull request
Jul 25, 2026
MARKETING_VERSION/versionName move together per the versioning rule; build numbers increment independently (207->208 Apple, 298->299 Android). The staging workflow reads MARKETING_VERSION for its title and asset names, so main has to carry 9.1.1 for the rolling testing-latest release to be labelled as such — the previous 9.1.1 staging build was cut from a commit that never landed on main. This is also the first compile of the app-target changes merged today (#783/#784/#785, #787, #788, #793, #795, #519/#798, #523, #778), which CI does not cover: app-build.yml and android.yml are disabled and only the Swift packages and the i18n gate run on PRs.
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
…yanbr#730) (ryanbr#785) * Android parity: stop reporting a disarm that never reached the strap (ryanbr#730) ryanbr#784 fixed this on iOS/macOS; Android had the identical defect. disableStrapAlarm() logged 'Alarm: disarmed' unconditionally while send() drops the write with 'ignored — not connected', so the app claimed the firmware alarm was cleared when the command never reached the strap. If one IS armed the strap still buzzes. Mirrors the Swift fix: a commandChannelReady twin (the same conditions send() guards on) decides which line is logged. Android did NOT have the second iOS defect. reconcileStrapAlarm() is the sole caller of arm/disarm, handles the disarm case internally, and is called UNGATED on the bond edge (state.bonded && !lastBonded) plus the daily tick - so a deferred disarm was already re-issued on reconnect. iOS's smartAlarmEnabled guard was the outlier, and ryanbr#784 brought it into line. The ryanbr#730 restored-connect diagnostic does not port: Android has no CoreBluetooth state restoration, so that path has no Android analogue. App-target Android: not compiled here (aapt2 is x86-only on this host) and not hardware-tested. * Make commandChannelReady private, matching the Swift twin Nothing outside WhoopBleClient reads it, and the macOS twin is private - keep the API surface identical on both platforms.
simoncad7
pushed a commit
to simoncad7/noop
that referenced
this pull request
Aug 17, 2026
MARKETING_VERSION/versionName move together per the versioning rule; build numbers increment independently (207->208 Apple, 298->299 Android). The staging workflow reads MARKETING_VERSION for its title and asset names, so main has to carry 9.1.1 for the rolling testing-latest release to be labelled as such — the previous 9.1.1 staging build was cut from a commit that never landed on main. This is also the first compile of the app-target changes merged today (ryanbr#783/ryanbr#784/ryanbr#785, ryanbr#787, ryanbr#788, ryanbr#793, ryanbr#795, ryanbr#519/ryanbr#798, ryanbr#523, ryanbr#778), which CI does not cover: app-build.yml and android.yml are disabled and only the Swift packages and the i18n gate run on PRs.
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.
Parity follow-up to #784, which fixed this on iOS/macOS. Android had the identical defect.
disableStrapAlarm()logged"Alarm: disarmed"unconditionally, whilesend()drops the write and logssend(...) ignored — not connected. So the app claimed the firmware alarm was cleared when the command never reached the strap — and if one is armed, the strap still buzzes.Mirrors the Swift fix exactly: a
commandChannelReadytwin (the same conditionssend()guards on —gatt != null && cmdCharacteristic != null) decides which line gets logged.What Android did NOT need
The second iOS defect doesn't exist here, and it's worth recording why:
reconcileStrapAlarm()is the sole caller of arm/disarm, handles the disarm case internally (epochSec == null → disableStrapAlarm()), and is called ungated on the bond edge (state.bonded && !lastBonded) plus the daily tick. So a deferred disarm was already re-issued on reconnect. iOS'ssmartAlarmEnabledguard was the outlier — #784 brought iOS into line with Android, not the other way round.Minor mechanism difference, feature-level parity intact: Android re-asserts the disarm on every bond edge; iOS (#784) latches
disarmPendingand only re-issues a dropped one, so it doesn't add aDISABLE_ALARMto every connect. Observable behaviour is the same — the strap ends up disarmed.The #730 restored-connect diagnostic also doesn't port: Android has no CoreBluetooth state restoration, so that code path has no analogue.
Verification
aapt2is x86-only on this arm64 host) and not hardware-tested.WhoopBleClientis Android-coupled, so the pure-Kotlin test route that verifiedWhoop5Variantdoesn't reach it. Small and self-contained (+14/−2); wants a./gradlew compileFullDebugKotlin.