Commit Graph
1095 Commits
Author SHA1 Message Date
darken 7d93e772b4 fix(monitor): Prevent NPE in cache merge from freezing device flow
Battery slot percent comparisons in mergeBatterySlot/hasStateChanged compiled to Intrinsics.areEqual on boxed Float; R8 optimization on Android 10/11 dropped a null check during inlining and the resulting NPE escaped onEach { persistLiveDevices }, cancelling the upstream combine and freezing every observer of DeviceMonitor.devices.

Comparisons now operate on primitive float (cmpg-float in dex) so no Intrinsics.areEqual call remains in the merge path. The persist loop also catches and reports per-profile, and AAP-only profiles with active DeviceInfo are now persisted even when no BLE pod is in range.
2026-05-03 14:20:49 +02:00
darken 82dc88faac fix: Harden uncaught exception safety net
Wrap logging, reporting, and Looper resume calls so the foreground service timing exception suppression cannot itself trigger another crash. Extract handler into a dedicated class with seams for unit tests.
2026-05-03 12:41:32 +02:00
darken 06b41ec8eb fix(monitor): Recover from revoked Bluetooth scan permission 2026-05-03 11:54:11 +02:00
darken acc6e92b2d feat(tile): Add ANC Quick Settings tile 2026-05-03 10:58:05 +02:00
d4rken-org-releaser[bot] 39922df792 Release: 5.1.3-rc0 v5.1.3-rc0 2026-05-01 19:52:12 +00:00
darken 5a8c4b94ef fix: Correct wear detection on AirPods Max 2
Bit 5 of pubStatus is always set on A3454 and no longer carries the wear flag (unlike Max gen 1). Read bits 1 and 3 instead — the per-earcup sensors. OR rather than AND so phones that only see one bit reliably still report worn correctly.

Closes #548
2026-05-01 21:50:51 +02:00
darken 0ffa87deea CI: Skip release bump-only workflow runs 2026-05-01 20:08:42 +02:00
darken fc98db09a2 fix: Correct PodModel feature flags for ear detection and call controls
Sync flags with what the BLE classes actually report and what iOS exposes:

- AirPods Gen 1/2/3: enable hasEarDetection (already parsed via DualApplePods) and hasEarDetectionToggle

- AirPods Gen 3, Pro 1: enable hasEndCallMuteMic (force-sensor stems)

- Powerbeats Pro, Beats Fit Pro: enable hasEarDetectionToggle (iOS exposes it)

- Beats Solo Pro, Studio 3: drop hasEarDetection (over-ear, BLE class is bare SingleApplePods)

- FAKE_AIRPODS_GEN1/2/3: enable hasEarDetection to match HasEarDetectionDual

- Generalize microphone mode description from 'AirPod' to 'earbud'

Tests rewritten as exhaustive set assertions plus implication invariants.
2026-05-01 20:08:29 +02:00
darken e1c0a702dd chore(ci): Rename stale step name and use client-id for App auth 2026-05-01 09:15:43 +02:00
d4rken-org-releaser[bot] 3f310016b1 Release: 5.1.2-rc1 v5.1.2-rc1 2026-05-01 07:04:37 +00:00
darken d399a6d908 fix(ci): Drop redundant explicit dispatch in release-prepare 2026-05-01 09:00:29 +02:00
d4rken-org-releaser[bot] fb104cbe61 Release: 5.1.2-rc0 v5.1.2-rc0 2026-05-01 06:56:07 +00:00
darken 97cbca7a6e fix(ci): Resolve bot slug via action output to avoid JWT-only endpoint 2026-05-01 08:55:15 +02:00
darken 02278dab17 chore(ci): Use GitHub App token for release-prepare push 2026-05-01 08:50:32 +02:00
darken 2240bd7ae5 chore(ci): Drop env gate on release-prepare to keep two-click approval 2026-05-01 08:16:33 +02:00
darken bc4096cb57 chore(ci): Run bats + shellcheck on release tooling 2026-05-01 08:00:15 +02:00
darken 5a0ba1b0bd chore(ci): Move release process to GitHub Actions 2026-05-01 08:00:15 +02:00
darken d04abdf80c General: Fix translation issues across multiple locales
Targeted character / escape / vandalism fixes:

- tr: %80 -> 80%% (UnknownFormatConversionException crash risk)

- pt: replace Cyrillic 'не' with 'não' in autoconnect description

- sk: fix 'ladUciaho' -> 'ladiaceho' (corruption)

- ms: restore 'Discord' brand name

- ca: 'maxima' -> 'minima' for signal_quality_title; fix 'suprimrira' typo

- zh-rTW: fix homophones and Japanese-kanji variants (4 strings)

- zh-rHK: Simplified -> Traditional for press_action_fast_forward

- zu: remove spurious 'Zn' prefix in 2 strings (vandalism)

- lv: fix wrong cedilla character in 3 Bluetooth a11y descriptions

- kmr-rTR: fix 4 encoding artifacts (a-feminine-ordinal substituted for e-circumflex)

- ko: fix 2 Hangul typos (conversation_awareness, allow_off)

Garbled translations removed (fall back to English source):

- hy-rAM: 13 press_controls/press_action strings

- mn-rMN: 2 strings (aap_unavailable_description, listening_mode_cycle_minimum)

- lt: settings_popup_warning_manual_mode

- mk-rMK: device_settings_allow_off_description (Syriac char vandalism)

- lo-rLA: signal_badge_ble_cd (English fallback was effectively unchanged)

- km-rKH: device_settings_microphone_mode_label (Thai script in Khmer file)
2026-05-01 05:19:34 +02:00
darken 0609352cc9 General: Update app translations from Crowdin 2026-05-01 05:19:34 +02:00
darken abde0b7d6a feat(reactions): Add discovery hint and outline settings icon 2026-04-30 17:46:27 +02:00
darken b952aff38d fix(widget): Re-render widgets reactively after initial session start
Glance only calls provideGlance() once per widget session; subsequent update() calls recompose the existing composition without re-running provideGlance. The previous capture-once approach left widgets frozen at initial state because the composition had no reactive State to read.

Subscribe to a widgetDeviceFlow(profileId) inside provideContent that pre-filters by WidgetDeviceKey, so the composition recomposes on visible state changes without firing on every BLE advertisement. Replace updateAll() with explicit per-GlanceId update() calls in WidgetManager, with a platform-id fallback when Glance returns no IDs.
2026-04-30 16:44:57 +02:00
darken c8f5ceda34 fix(monitor): Auto-tune BLE scanner mode by foreground state and connections
Replaces the user-facing scanner mode setting with an automatic policy that picks LOW_LATENCY when a profile-paired device is connected, BALANCED in the foreground, and LOW_POWER in the background. The TroubleShooter scopes a temporary LOW_LATENCY override via a refcounted withTemporaryOverride block so overlapping callers stay correct.

Fixes a regression where the controller could block BLE scanning entirely if BLUETOOTH_CONNECT was missing or the HEADSET profile proxy stalled, and adds a reactive bondedDeviceAddresses flow so bond changes propagate without waiting for an unrelated input. Cleans up the now-dead scanner mode strings across all locales and unused ScannerMode fields.
2026-04-30 15:26:45 +02:00
darken e6dbd2d660 fix(reaction): Restore BLE-only autoplay and stop false reactions on app start
- Apply seenLastAt freshness to all unauthenticated BLE samples (worn and not-worn). The earlier scoping to not-worn-only collapsed the second worn sample for BLE-only autoplay confirmation, so the staged play never fired.

- Replace distinctUntilChangedBy with a manual filter so worn samples that need to reset an active pause debounce (count went up) can pass through even when the monitor key is otherwise identical.

- Skip BLE-only autoplay confirmation for trusted sources. With BLE_IRK_MATCH and AAP, autoplay now fires on the first not-worn -> worn transition, mirroring the pause-debounce skip on the same sources.

- Skip the reaction entirely when the previous emission had no live evidence (NO_LIVE_BLE). Prevents app-process-start from synthesising a fake not-worn -> worn transition and firing autoplay while the user is already wearing the pods. Same guard handles mid-session BLE gap recoveries.

- Add MonitorFlowTests covering process-start-worn, genuine-insertion-after-startup, mid-session BLE-gap recovery, IRK-matched immediate autoplay, BLE-only autoplay confirmation, 3-sample pause debounce, and rebound-tolerated debounce reset.
2026-04-29 18:14:51 +02:00
darken 228d7d1f0c fix(reaction): Tighten auto-pause debounce after review findings
- Commit pending pause when a trusted source (AAP / BLE_IRK_MATCH) corroborates the not-worn condition mid-debounce, instead of dropping pending silently.

- Scope debounceFreshness to not-worn samples only; identical both-in samples no longer pass distinctUntilChangedBy and can't accidentally trigger BLE-only auto-play confirmation.

- Add resetTolerance to PendingPauseDebounce so a single corrupt count-up advert no longer kills a legitimate pending pause; reorder reset checks so rawDecision.shouldPlay resets immediately.

- Drop bleKeyState from the INFO autoPause log; source already encodes trust without leaking key-configuration state to logcat.

- Add flow-level MonitorFlowTests verifying the distinctUntilChangedBy interaction with seenLastAt freshness, plus the #557-direction test (AAP-worn vs corrupt-BLE-not-worn) and rebound-tolerance test.

- Clarify in BLE_ANONYMOUS KDoc that the path is unreachable in production via DeviceMonitor.primaryDevice.
2026-04-29 18:14:51 +02:00
darken 67fead3589 fix(reaction): Debounce auto-pause for unauthenticated BLE sources
Classifies the ear-detection source (AAP / BLE_IRK_MATCH / BLE_PROFILE_FALLBACK / BLE_ANONYMOUS / NO_LIVE_BLE) and applies a 3-sample debounce only to unauthenticated BLE paths. AAP and IRK-authenticated BLE pass through unchanged.

Also tightens toEarDetectionState() to prefer AAP aggregate over BLE per-side bits whenever AAP EarDetection is present, and suppresses pause on NO_LIVE_BLE (cache-only state) to avoid firing without live evidence.
2026-04-29 18:14:51 +02:00
darken d0aab583d2 fix(presscontrols): Auto-exit when device disconnects 2026-04-28 22:39:42 +02:00
darken 367c8d28ff chore: Drop stale app-common exclude from _config.yml
app-common module was merged into app/ in be8f4919; the exclude has been a no-op since.
2026-04-28 12:08:23 +02:00
darken b04e1fe628 chore(ci): Also rebuild Pages on main pushes for content edits
Adds push: [main] alongside workflow_dispatch so edits to README.md, _config.yml, _layouts, or the CHANGELOG.md template publish without a manual dispatch. The chain step in release-tag.yml still runs after release publish to guarantee the new release is in site.github.releases by the time Pages rebuilds — concurrency: cancel-in-progress: false serialises the two runs.

Adds an if: github.ref == 'refs/heads/main' guard on the deploy job so workflow_dispatch from a non-main branch builds for verification but doesn't deploy.
2026-04-28 12:08:23 +02:00
darken ea3ef1ff3b chore: Drop dead Gemfile excludes and ignore Jekyll artifacts
After moving Gemfile/Gemfile.lock to fastlane/, the - Gemfile / - Gemfile.lock entries in _config.yml's exclude list are no-ops; the parent fastlane exclude already covers everything inside.

.gitignore picks up _site/, .jekyll-cache/, vendor/bundle/ so local Jekyll runs don't leave tracked artifacts.
2026-04-28 12:08:23 +02:00
darken ff8b9cf6ed chore(ci): Chain Pages deploy from release-tag.yml and adopt org refinements
release: published events triggered by secrets.GITHUB_TOKEN do not start new workflow runs (only workflow_dispatch and repository_dispatch are exceptions). The Pages workflow's release: published trigger would never have fired in production since release-tag.yml's softprops/action-gh-release uses GITHUB_TOKEN to publish.

Fix: drop the release: published trigger and have release-tag.yml's release-github job explicitly run gh workflow run pages.yml --ref main after the release is created. release-github gains actions: write to authorize the dispatch.

Also adopts refinements from sibling org PRs (permission-pilot#356, bluemusic#220):

- Top-level permissions reduced to contents: read; pages: write and id-token: write moved to the deploy job only (least privilege)

- JEKYLL_GITHUB_TOKEN on the build step so jekyll-github-metadata authenticates when fetching site.github.releases

- Sanity-check step (test -f _site/index.html && _site/CNAME) fails fast if Jekyll produced nothing

- Explicit upload-pages-artifact path: ./_site matches the build's destination

- Verify fastlane Bundler wiring step (bundle exec fastlane --version) lets workflow_dispatch dry_run=true exercise the relocated Gemfile before the next real release
2026-04-28 12:08:23 +02:00
darken 96bc3f5df7 chore(ci): Move Gemfile to fastlane/ for tidier layout
The root Gemfile only ever declared the fastlane gem and lived next to fastlane configuration anyway. Moving it under fastlane/ matches that ownership and keeps the repo root cleaner.

release-gplay job now sets BUNDLE_GEMFILE=fastlane/Gemfile and runs ruby/setup-ruby with working-directory=fastlane so bundler-cache resolves the moved Gemfile. fastlane lanes still run from the repo root.
2026-04-28 12:08:23 +02:00
darken 1891a6d73a chore(ci): Migrate Pages to custom workflow with Node 24 actions
Replaces the auto pages-build-deployment (which still uses Node-20 actions/checkout@v4 and actions/upload-artifact@v4) with a custom workflow using configure-pages@v6, jekyll-build-pages@v1.0.13, upload-pages-artifact@v5, deploy-pages@v5.

Triggered by release publication so the changelog Liquid template (which reads site.github.releases) only rebuilds when a release actually exists. workflow_dispatch is kept for manual rebuilds when debugging Pages content.
2026-04-28 12:08:23 +02:00
darken 698415237d chore(ci): Drop unused Android build-cache step 2026-04-28 12:08:23 +02:00
darken 7ef672a11f chore(ci): Bump action-gh-release to v3.0.0 (Node 24) 2026-04-28 12:08:23 +02:00
darken f67e8aa7d1 fix: Harden foreground service exception handler 2026-04-25 23:40:10 +02:00
darken bcb4bdff08 fix(overview): Prevent crash from duplicate LazyList keys 2026-04-25 23:39:59 +02:00
darken 70d9432b2b Release: 5.1.1-rc0 v5.1.1-rc0 2026-04-25 21:58:56 +02:00
darken 9d06839a1c General: Update app translations from Crowdin 2026-04-25 21:58:10 +02:00
darken 97f294e5f7 fix(widget): Persist theme settings across device reboot
Closes #549
2026-04-25 20:39:40 +02:00
darken a40599c1dc feat(settings): Tailor device settings to connection state
- Hide reactions and AAP sections unless device is classically connected

- Move advanced-settings-unavailable card to the bottom of the list

- Show 'device not nearby' infobox when out of range

- Show missing-paired-device banner with edit-profile action

- Replace pending banner with snackbar on user-initiated change
2026-04-25 19:00:19 +02:00
darken 175aef9e2f feat(logging): Log raw advertisement and decoded payload bytes 2026-04-25 16:19:21 +02:00
darken 36ae35918a refactor(widget): Move WidgetDeviceKey into widget package 2026-04-25 16:19:21 +02:00
darken 60ad1943b6 fix(widget): Gate refresh on display fields, drop log to VERBOSE 2026-04-25 16:19:21 +02:00
darken 5e727e907d refactor: Drop per-emission flow logger and redundant widget logs 2026-04-25 16:19:21 +02:00
darken 5360f54ea8 fix(privacy): Stop logging AirPods encryption keys 2026-04-25 16:19:21 +02:00
darken 486efe6e65 refactor: Reduce debug log noise from redundant state emissions 2026-04-25 16:19:21 +02:00
darken c2ff1f111e chore(reaction): Rename sleep notification channel label 2026-04-25 10:21:30 +02:00
darken 26b72726a7 feat(reaction): Pause media when AirPods detect sleep 2026-04-25 10:21:30 +02:00
darken f35fe968c6 docs(aap): Document SET_BAND_EDGES as RF band gating 2026-04-24 17:54:53 +02:00
darken 498f040a57 feat(aap): Drop experimental warning on Optimized Charge toggle
Verified on a real AirPods Pro 3: toggling flips pod charging state from CHARGING_OPTIMIZED to CHARGING and persists across reconnects. Apple-bool wire format is confirmed, so the 'experimental' warning box is no longer warranted.
2026-04-24 17:27:48 +02:00