Skip to content

Latest commit

 

History

History
611 lines (582 loc) · 48.8 KB

File metadata and controls

611 lines (582 loc) · 48.8 KB

TODO.md

Open tasks, grouped by phase. Check items off and move completed major items to CHANGELOG.md (see PROJECT_RULES.md §6, rule 13, on technical debt tracking).

Recommended priority order (added 2026-08-23)

A cross-phase execution order, distinct from the phase grouping below (which organizes tasks by kind, not by when to do them). This section is a sequencing layer only — each item's full description still lives in its own phase/section below (or in PROTOCOL.md/ARCHITECTURE.md for protocol/architecture open questions, per this file's own "Open questions" section at the bottom); nothing here is a second copy of that detail, only a pointer plus the reasoning for the ordering.

  1. Decisions & sign-offs (no new data needed — cheapest, unlocks the most):
    • Maintainer sign-off on pending 🟢 FACT promotions — see the new Phase 3 item below for the current list. Per AGENTS.md §6 this step can only be done by the maintainer, not an agent.
    • DI approach (Hilt vs. manual) — Phase 4. Resolved 2026-09-13: Hilt (DECISIONS.md ADR-028).
    • Minimum Android API level — Phase 5. Resolved 2026-09-13: API 34 (DECISIONS.md ADR-029).
    • Find My Buds Case/"both simultaneously" — whether to accept a Google Find Hub/account-mediated fallback for this one sub-feature or ship v1 without local Case-ring support (PROTOCOL.md §6, Behavior) — a genuine Zero-GMS scope trade-off, not a research gap; no capture or static analysis can resolve this, only a maintainer product decision can. Resolved 2026-09-13: ship v1 with Left/Right ring only (DECISIONS.md ADR-027, PROJECT.md non-goals).
  2. Start Phase 4 app development, ANC-first: ANC, Find My Buds Left/Right (DECISIONS.md ADR-011), and EQ (DECISIONS.md ADR-020) are all fully 🟢 FACT and implementation-unblocked — ANC remains the recommended starting point (DECISIONS.md ADR-009): building it end-to-end (transport → framing → UI) is the cheapest way to prove the whole architecture works. Battery via HFP (PROTOCOL.md §4.3 Option C, also already 🟢 FACT) is the natural second target — together they cover most of PROJECT.md's "Definition of done (v1)".
  3. Phase 2 (APK reverse engineering) — updated 2026-08-30, no longer 0% done. APK pulled, JADX/apktool-decompiled, and multiple §4 keyword-search/follow-up passes done (REVERSE_ENGINEERING.md's growing class-entry list, 30+ entries as of the last pass), and DECISIONS.md ADR-018 accepted (DLCI 0x02 channel-ownership → 🟢 FACT), and .proto/pw_rpc schema recovery — updated 2026-09-03, done via a different route than planned here: pbtk confirmed structurally incapable of this APK's codegen (both the whole-APK and a 2026-09-03 targeted-class re-run wrote 0 .proto files; root cause is now source-cited, not just the tool's own caveat — see the Phase 2 checklist item below). The schemas were instead recovered by hand (scripts/decode_rawmessageinfo.py, DECISIONS.md ADR-019) and cross-correlated against wire captures for qhr fields 4, 7, 12, and 29. Updated 2026-09-08 — the "remaining confirmed-but-unchecked field numbers" item this bullet used to point to is now fully closed: fields 17/19/22/27/28 closed 2026-09-03, and fields 11/15 (the two the maintainer's 0002 sign-off was scoped to) closed 2026-09-08 — see this file's "Targeted research follow-ups" section below, PROTOCOL.md §4.5.2/§4.5.6/§4.5.7/§4.5.5a/§4.5.8/§4.5.1, DECISIONS.md ADR-019 and its two Updates. Current highest-leverage single next step is now the MaestroDeviceSettingsProviderService 6-case-ID→qhr-field forward trace (same "Targeted research follow-ups" section, added 2026-09-08 by prompt 0002) — the cheapest of the three open APK-RE leads from ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.md's Phase 1/Phase 2 (the other two, MaestroEndpointService's smali fallback read and gjv.p()'s caller trace, both need either an untried smali read or a fresh capture first). CAP-033 (Group AA, SDP-001/SDP-002) is done (2026-08-30) — see below.
  4. Remaining planned captures (updated 2026-09-13 — see below for the next-test-session queue; CAP-008, CAP-009, CAP-013, CAP-014, CAP-027, CAP-033CAP-042 are done, see CAPTURE_BLUETOOTH_HCI_SNOOP.md §9):
    • Next test session (added 2026-09-13, ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.md) — 5 short, self-contained captures, each with its own placeholder capture folder and event-notes skeleton already prepared under captures/ (see each CAP-0NN's own row in CAPTURE_BLUETOOTH_HCI_SNOOP.md §9), each ~3–10 minutes, no destructive/one-time action among them (all safely repeatable if a retry is needed):
      1. CAP-053 (Group AO) — EQ outer field 16-vs-18: isolate Save-tap / navigate-away / genuine slider-release from each other.
      2. CAP-054 (Group AP) — Battery Notification, connection-free, bracketing a single-bud insertion/removal (the Fast Pair spec's own "optional" trigger, untested so far).
      3. CAP-055 (Group AQ) — Nod/Shake head gestures with an actual active call/notification, camera also framing the gesture itself (needs a second phone to place the call).
      4. CAP-056 (Group AR) — ANC-rotation-checklist Left/Right split, genuine re-run — read the skeleton's anti-repeat safeguard first, CAP-045 skipped the actual checklist screen.
      5. CAP-057 (Group AS) — live GetSoftwareInfo/GetHardwareInfo correlation against the connect-time burst, using the firmware/serial-number screen.
    • Also still queued from before, lower priority than the 5 above: CAP-043 (Group Q repeat, Battery Notification BLE scan) is done (2026-09-13) — a second confirmed non-match, see Phase 1 above and CAP-054 in the next-test-session queue for its own follow-up.
    • CAP-044 (Group AA repeat, 2nd attempt, SDP-001/SDP-002) is done (2026-09-13) — still 🟡 HYPOTHESIS, a different isolation gap than CAP-033's; a 3rd attempt is a Tier-2 decision item in ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.md Phase 6, not yet designed as a skeleton pending that go/no-go.
    • CAP-018 and the still-uncaptured main-run-through remainder (CAP-026, CAP-029CAP-030) — CAP-028 (head gestures) is done (2026-09-12, inconclusive) and re-verified (2026-09-13); its follow-up is now tracked as planned CAP-055 (Group AQ), not this bullet. Closed this update: Group W's own untried GATT cache-busting methods — CAP-034 (2026-09-01) combined pm clear com.android.bluetooth with a Pixel 9a never before connected to this Buds unit and fully resolved the 0x0c0X/0x0f2X handle↔UUID mapping (see PROTOCOL.md §6, §4.3 Option D) — this bullet's own "untried" framing is now stale and removed.
  5. Targeted research follow-ups, lowest priority, tracked at their source per this file's "Open questions" section: the CAP-021 DLCI 0x0a burst trigger and the DLCI 0x02 AES-128 hypothesis (PROTOCOL.md §6) — the latter is only really testable once Phase 2 above provides a pw_rpc/protobuf schema to check against. Added 2026-08-28 (2026-08-28 project-wide audit, Phase 5), three specific new-capture ideas, none yet designed in CAPTURE_BLUETOOTH_HCI_SNOOP.md:
    • HOLD-005's Left/Right ANC-rotation-checklist split (PROTOCOL.md §6) — a purpose-built capture isolating one earbud's rotation list at a time (the envelope carries no Left/Right-distinguishing field for this specific write, unlike HOLD-001HOLD-004). Now designed, 2026-09-09: skeleton created as CAP-045 (Group AJ, new — see CAPTURE_BLUETOOTH_HCI_SNOOP.md §4.1 and ai-sessions/0005_MAINTENANCE_RESULT_2026_09_09.md). CAP-045 run 2026-09-12 but did not exercise Group AJ's own procedure (physical press-and-hold ANC cycling was captured instead — the rotation-checklist screen was never opened); the question remains fully open. A genuine re-run, with a mandatory on-camera anti-repeat safeguard, is designed as Group AR (planned CAP-056, ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.md Phase 4).
    • Volume balance (field 17) scale/direction (CAP-022-FINDINGS.md §5, PROTOCOL.md §4.5.7/§6) — a capture with isolated extreme-position samples (not a continuous drag) plus tighter video correlation. Now designed, 2026-09-09: skeleton created as CAP-046 (Group AK, new).
    • The CAP-021 DLCI 0x0a burst trigger, more precisely: a purpose-built hypothesis test (PROJECT_RULES.md §4's fixed template — hypothesis, setup, expected outcome, actual outcome, conclusion) bracketing candidate triggers one at a time (app backgrounded/foregrounded, a scheduled sync window, a charge-state change) — the burst recurred in exactly 1 of 16 sessions checked so far, so passively waiting for it to reappear is not expected to work. Now designed, 2026-09-09: skeleton created as CAP-047 (Group AL, new; no existing Test-ID, flagged as a TESTPLAN_BLUETOOTH_HCI_SNOOP.md follow-up).
    • Added 2026-08-30 (audit finding), closed 2026-09-08 (prompt 0002, implementing ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.md Phase 2/Phase 4): apply DECISIONS.md ADR-019's same static-analysis method (matching a confirmed wire field number against the recovered qhr schema) to the remaining confirmed-but-unchecked DLCI 0x02 field numbers — field 11, 15, 17, 19, 22, 27, 28 (PROTOCOL.md §6's "what do DLCI 0x02's confirmed inner field numbers actually represent" item). Fields 17/19/22/27/28 were closed 2026-09-03 (DECISIONS.md ADR-019 Update), though this bullet was never updated at the time to reflect that. Fields 11 (Multipoint) and 15 (Volume EQ) — the two the maintainer's sign-off for prompt 0002 was scoped to — are now closed too, via a forward trace from a named UI fragment/preference key to the write call site (PROTOCOL.md §4.5.2/§4.5.6, REVERSE_ENGINEERING.md's qhr entry, DECISIONS.md ADR-025 Update). All 7 field numbers this item originally listed are now checked against the recovered qhr schema — item closed.
    • Added 2026-09-03 (audit finding), closed 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.md Phase 3 item 4): traced fyd.d/fyd.e's own call sites in the EQ UI fragment — field 16 fires from the slider-drag/preset path; field 18 is reachable only via a dedicated, self-describing "On click save EQ button" handler, with no slider-release code path found anywhere. This closes the static-analysis question but contradicts CAP-015's own wire-timing "fires on slider-release" hypothesis rather than confirming it — a genuine, unreconciled tension, proposed for maintainer review. Re-verified 2026-09-13 (ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.md Phase 1): the "reachable only via Save button" reading was incomplete — a second call path exists (hod.java, a navigate-away-with-unsaved-changes trigger), adding a third candidate. A capture isolating all three (Save tap / navigate-away / genuine slider-release only) is proposed as CAPTURE_BLUETOOTH_HCI_SNOOP.md Group AO (planned CAP-053). See REVERSE_ENGINEERING.md's qjw entry and PROTOCOL.md §4.2/§6.
    • Added 2026-09-03 (audit finding): re-verify PROTOCOL.md §4.3 Option A's "shown ≥8s, auto-hidden after 20s" Battery Notification visibility-timing claim directly against the official Fast Pair spec pages (a 2026-09-03 re-check found no such text on the batterynotification extension page specifically — downgraded to 🟡 HYPOTHESIS pending this check; the detail may live on a different spec page not checked yet, e.g. the base Message Stream spec).
    • Added 2026-09-08 (ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.md Phase 1/Phase 4, prompt 0002), closed 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.md Phase 3 item 1): traced MaestroDeviceSettingsProviderService's 6 case IDs to their exact accessor call — 2102qhr field 2, 2103→field 27, 2104→field 11 (Multipoint — the head-gestures lead below did not pan out; field 29 is not among these 6 mappings), 2113→field 5, 2115→no qhr field at all (a separate "Feature A" toggle), 2116→field 32 (new). See REVERSE_ENGINEERING.md's MaestroDeviceSettingsProviderService entry for the full trace, including two byproduct qhr register corrections (fields 6 and 32) and an unreconciled CATEGORY_MULTIPOINT-vs-fpm.ENABLED_HEAD_GESTURES naming tension on case 2104 — proposed for PROTOCOL.md promotion, pending maintainer review.
    • Added 2026-09-08 (ai-sessions/0001_CROSSCHECK_RESULT_2026_09_07.md Phase 1, prompt 0002), largely closed 2026-09-08 (ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.md Phase 3 item 2): apktool smali-fallback read of MaestroEndpointService.onCreate() done — the registered services come from a Dagger multibinding (Map<String, Optional<ofd>>) assembled elsewhere (names not recovered), and ofd's method is a per-call, UID-based authorization check (not a service dispatcher as its shape first suggested) — two policies found, an internal-UID-only check and an allowlisted-Google-signed-caller check. See REVERSE_ENGINEERING.md's MaestroEndpointService entry. Still open, if pursued further: the literal registered service names (would need locating the Dagger multibinding's own assembly site — judged out of proportion to chase further this session) and whether GMS specifically is ever in the allowlist for this service's own methods.
    • Formalized 2026-09-08 (ai-sessions/0002_MAINTENANCE_RESULT_2026_09_08.md's own gap scan flagged this as never added to this list; re-attempted and still not closed by ai-sessions/0003_MAINTENANCE_RESULT_2026_09_08.md Phase 3 item 3): trace gjv.p()'s own caller — the remaining open link needed to determine whether fxm.i()'s GetSoftwareInfo fetch genuinely fires inside the CAP-036/CAP-041 connect-time settling window (REVERSE_ENGINEERING.md's frb/fuh/glk/gjv entry). Two static-analysis passes have now failed to locate this caller (generic-token searches on .p()/.u = are unproductive against this app's R8 obfuscation) — the byte-level capture-correlation alternative (PROTOCOL.md §6's matching item) is now the recommended path, not a further static-analysis attempt, unless a future session identifies a more targeted search strategy.
    • Added 2026-09-11 (ai-sessions/0008_CROSSCHECK_RESULT_2026_09_11.md), full non-sampled validation of Gemini's ai-sessions/0007_CROSSCHECK_RESULT_2026_09_11.md. Independently re-derived every citation in 0007; roughly half of its line-number citations from §2.2 onward point to the wrong location, two (the claimed field-19 write site and the claimed field-17 write site) point to code with no connection to the claim at all. 0007's §2.6 "NEW INDEPENDENT FINDING" (qhr field 19 as a "Volume Balance extreme/gate boolean") is not new — it restates REVERSE_ENGINEERING.md/DECISIONS.md ADR-019's 2026-09-03 Update — and omits field 19's actual, already-approved primary identity ("Mono audio"), and its reported field-17 value (10) skips the zigzag-decode correction ADR-019 already documents (correct value: 5). 0007's Executive Summary's "100%/absolute certainty" language is not supported. Do not act on 0007's recommendations directly — see 0008's §3 for the maintainer decisions this raised (field 19's documentation should not be changed to "limit gate"; 0007's "approve all Phase 4 Promotions" recommendation needs to be evaluated per-item, not as a bundle).
    • Added 2026-09-13 (ai-sessions/0013_FEATURE_RESULT_2026_09_13.md), housekeeping pointer for 0012. ai-sessions/0012_CROSSCHECK_RESULT_2026_09_12.md ran a full, non-sampled independent re-derivation of all 130 findings in Gemini CLI's 0011 review. Headline result: 0011's core protocol-decode content (opcodes, field mappings, byte-level payload claims) held up with zero errors; 5 citation errors and 1 overclaim were found, all in secondary/background material (co-occurring unrelated devices, one-off vendor commands, procedural timestamps), never in a primary protocol claim. Two pre-existing capture-count discrepancies 0012 itself surfaced (CAP-036's "34-frame" burst count, CAP-037's "34 reconnects" count) are now resolved — see 0012 §4 for both.

Setup

  • Set up the Fedora development workstation (WORKSTATION_PREPARATIONS.md)
  • Claude Code and Google Antigravity installed and configured
  • GitHub repository created + first commit
  • License chosen — AGPL-3.0 (see DECISIONS.md ADR-002, LICENSE)
  • Core project documentation drafted: AGENTS.md, PROJECT.md, PROJECT_RULES.md, ARCHITECTURE.md, PROTOCOL.md, PROTOCOL_NOTES.md (retired 2026-08-15, see CHANGELOG.md), REVERSE_ENGINEERING.md, DECISIONS.md, CHANGELOG.md, README.md, CAPTURE_BLUETOOTH_HCI_SNOOP.md, TESTPLAN_BLUETOOTH_HCI_SNOOP.md, SCREENSHOTS_PIXEL_BUDS_APP.md, SCREENSHOTS_PIXEL_BUDS_WEB_APP.md
  • Review the repository for inconsistencies, vagueness, ambiguity, contradictions, errors, or undocumented choices — both within each file and between files — and address what's found. Status so far:
    • ARCHITECTURE.md — reviewed and revised (transport layer naming aligned with RFCOMM-primary reality, DI/scanning-policy open questions added)
    • AGENTS.md — reviewed and revised (stale license section, BLE-only framing assumption, and duplicate heading artifacts fixed)
    • PROJECT_RULES.md, PROJECT.md, DECISIONS.md, PROTOCOL.md, REVERSE_ENGINEERING.md, README.md, CHANGELOG.md, CAPTURE_BLUETOOTH_HCI_SNOOP.md, TESTPLAN_BLUETOOTH_HCI_SNOOP.md, WORKSTATION_PREPARATIONS.md — given a dedicated cross-consistency pass via the 2026-08-20 comprehensive documentation audit (see CHANGELOG.md) and a further external audit on 2026-08-22/23 (see AUDIT_REPORT_2026-08-22.md and CHANGELOG.md's matching entry). This checklist item was left unchecked after that work already completed it — closed here to fix the staleness itself.

Phase 1 — Bluetooth analysis

  • Pipeline validation — the HCI snoop → bugreport → btsnooz.py extraction → Wireshark chain (RFCOMM/SPP + BLE dissectors) confirmed working via CAP-001 (Group Z), 2026-08-09. Logged in the Capture Index (CAPTURE_BLUETOOTH_HCI_SNOOP.md §9).
  • Pairing/bonding baseline — forget-and-re-pair captured via CAP-002 (Group A), 2026-08-09; a second, independent baseline via CAP-003 (Group R) and a third via CAP-004 (Group S). Logged in the Capture Index.
  • Log every capture session in the Capture Index (CAPTURE_BLUETOOTH_HCI_SNOOP.md §9) with a unique CAP-NNN ID and metadata (firmware version, Android version, app version, capture method — per PROJECT_RULES.md rule 11 and rule 14) — ongoing practice, not a one-time task; kept unchecked deliberately.
  • Optionally, as a deliberate one-time capture (not before), trigger the factory-reset re-pair for comparison (CAPTURE_BLUETOOTH_HCI_SNOOP.md §4.1 Group P #16 — destructive, also resets the Find My Device link, so this is a bonus capture, not a prerequisite). See also WORKSTATION_PREPARATIONS.md's Disaster Recovery section — this is the same procedure, deliberately triggered as an experiment rather than as an emergency recovery step.

Top priority (updated 2026-08-18) — these block implementation-readiness for the app's core v1 features and outrank everything else below, including the still-open edge-case protocol questions (DLCI 0x08's identity, Groups 0x04/0x05/0x09's semantics, the CTKD generalization, HFP battchg vs. AT+BIEV discrepancy, etc. — those stay valuable research but are explicitly lower priority than finishing ANC/Battery/EQ):

  • CAP-005/CAP-015 (Group T) — EQ command isolation. Done 2026-08-18 via CAP-015, a second, independent Group T session: field-to-band mapping promoted to 🟢 FACT (all 5 sliders individually isolated, 3 passes each), plus the ±6.0 band-gain clamp and a confirmed preset-quintet reference table (CAP-015-FINDINGS.md, PROTOCOL.md §4.2).
  • CAP-006 (Group B repeat) — ANC reliability confirmation. Done 2026-08-15 — isolated single-tap repeat of all four ANC modes; exactly 4 0x12 "Set ANC state" frames in the whole log, one per tap, zero misses (CAP-006-FINDINGS.md §3). CAP-001's 2/6 gap does not reproduce under isolated conditions. DECISIONS.md ADR-009 updated, FrameEncoder implementation block for the ANC command lifted.
  • CAP-010/CAP-017/CAP-014 (Group W) — stronger GATT cache-busting for live service discovery. Discovery goal achieved 2026-08-16 via CAP-017, a fresh-GATT-client-app path not originally in this row's scope — 137 live discovery frames, full 15-service GATT profile recovered. CAP-014 (2026-08-27) fixed that session's snaplen truncation but still did not close the mapping — the 0x0f2a/0x0c0X handle→UUID mapping remains open (CAP-014-FINDINGS.md §4/§8): 3 attempts now, and pm clear com.android.bluetooth/the Pixel 9a — Group W's own actual candidate methods — remain untried in all of them. That combination (proven snaplen fix + an actually-untried cache-busting method) is the clear next step.
  • CAP-016 (Group U re-run) — case/bud-removal hardware events. Synced into PROTOCOL.md 2026-08-18 — promotes 3 🟢 FACTs (§5/§7): Buds-initiated reconnect on bud removal, ACL disconnect the instant both buds are re-docked, and case-lid open/close producing zero wire signal (now 2-capture-confirmed). New open items (RFCOMM channel-bounce trigger, ANC settable-toggles byte, a 0x0044 BLE notification burst, an AndroidHeadTracker HID Feature report) tracked in PROTOCOL.md §6 and CAP-016-FINDINGS.md.
  • CAP-008 (Group V) — first real phone call. Done 2026-08-26. Both open questions resolved: the full HFP AT-command SLC handshake reoccurs on a fresh classic-link connection, and two clean SCO/eSCO pairs appear, one per call. DLCI 0x0a stayed silent through both calls, ruling it out as the call's audio path (CAP-021's later, unrelated 1123-frame burst on that same DLCI remains a separate, still-open question — PROTOCOL.md §6). See CAP-008-FINDINGS.md.
  • CAP-009 (Group X) — battery-level discrepancy bracket. Done 2026-08-23. AT+CIND/battchg confirmed a stale single snapshot; AT+BIEV confirmed per-earbud (Right, this session), not a fixed aggregate, and non-fixed-cadence — BATT-006 closed, maintainer-approved (DECISIONS.md ADR-015). See CAP-009-FINDINGS.md.

Next, still important but behind the above:

  • Capture the "Play sound on Left/Right earbud" (Find My Buds) action — done, CAP-025 (2026-08-21). Left/Right confirmed 🟡 HYPOTHESIS (strong), video-correlated, proposed for PROTOCOL.md §4.4 promotion to 🟢 FACT pending maintainer sign-off. New finding: Case/"both" route through a separate Find Hub/Find-My-Device-Network mechanism with no local wire command — possibly a Zero-GMS hard limit, flagged to the maintainer in PROTOCOL.md §6 (Behavior) and CAP-025-FINDINGS.md §7/§8.
  • Passively capture a BLE scan to confirm the Battery Notification advertisement — attempted, CAP-011 (2026-08-21), inconclusive. Fast Pair Service (0xFE2C) traffic confirmed present, but the procedure deviated (an active RFCOMM connection was present throughout, not the intended connection-free scan) and the sampled payloads don't structurally match the documented byte layout — see PROTOCOL.md §4.3 Option A and CAP-011-FINDINGS.md. Still open: a genuinely clean, connection-free repeat is needed — skeleton created 2026-09-09 as CAP-043 (Group Q repeat, see CAPTURE_BLUETOOTH_HCI_SNOOP.md §9 and ai-sessions/0005_MAINTENANCE_RESULT_2026_09_09.md). CAP-043 done 2026-09-13: genuinely clean isolation confirmed, a second confirmed non-match against Option A's documented layout — closes the active-connection-confound question, but only the idle/case-closed trigger condition was tested. A single-bud-insertion/removal bracket (the Fast Pair spec's own "optional" trigger) is proposed as CAPTURE_BLUETOOTH_HCI_SNOOP.md Group AP (planned CAP-054, ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.md Phase 2).
  • CAP-013/CAP-031/CAP-032 (Group A repeat) — whether "Forget" fully clears prior BLE association. Done 2026-08-27, on the fourth attempt (CAP-032) — the first three (CAP-001's original session, CAP-013, CAP-031) all either predate the question or failed to capture the pre-clearing-action window; CAP-032, extracted via the raw path instead of the lossy btsnooz fallback, finally captured it and found a clean counter-example (no prior BLE link/valid key for that session) — CAP-001's own session-specific puzzle (why that session had residual state) remains independently open, see PROTOCOL.md §6 (Behavior). See CAP-032-FINDINGS.md.
  • Updated 2026-08-28 — remaining planned captures not yet individually tracked here (each already has its own row in CAPTURE_BLUETOOTH_HCI_SNOOP.md §9's Capture Index; listed here only so this file's priority ordering covers them too, not as a duplicate description): CAP-018 (Group Y, 0x0044 BLE-notification-burst isolation), and the still-uncaptured main run-through remainder — CAP-026 (Group L, passive observation). CAP-027 (Group N, touch gestures) is done, 2026-08-30 — see CAP-027-FINDINGS.md. CAP-028 (Group O, head gestures) run 2026-09-12 — inconclusive: zero wire-visible traffic during the claimed gesture window, but no active call/notification existed for a gesture to act on, so this cannot distinguish "functionally inert, as expected" from "gesture not triggered." Re-verified across the full log 2026-09-13, same clean negative (ai-sessions/0017_MAINTENANCE_RESULT_2026_09_13.md Phase 3) — a correctly-scoped repeat with an active call/notification is designed as Group AQ (planned CAP-055). Still to do: CAP-029 (Group P, Conversation Detection voice trigger + the optional, destructive factory-reset comparison + the still-open shorter-press pairing-mode question), and CAP-030 (Group Q items #19–20, Loud Noise Protection/Adaptive Audio, needs firmware ≥4.467 — worth double-checking this against the project's release_5.203 baseline first, since the two version identifiers have never been explicitly reconciled, PROTOCOL.md §0.1). Lower priority than a clean CAP-011 repeat and a properly-done Group W attempt above.
  • Six captures planned 2026-09-05, follow-ups to CAP-036's OBS-004 session — done and analyzed 2026-09-06, updated here 2026-09-08 (was stale: still listed as "planned" though PROTOCOL.md §8's 2026-09-06 changelog row already syncs their findings). CAP-037 (Group AD, purpose-built repeat of the "Get ANC state" reconnect-reliability + dock-state-transition question) ran far longer than planned — 34 reconnects, 26/26 zero-miss DECISIONS.md ADR-022 replications and 26/26 ADR-024 dock-state matches, the largest single-session replication of either on file. CAP-038 (Group AE, realistic buds-out-of-case-and-worn reconnect) found a Settable-toggles=0x00 reading immediately after physical case-removal, in unreconciled tension with ADR-024 (PROTOCOL.md §6, still open). CAP-039 (Group AF, Set-vs-Get comparison) gave 10/10 same-session confirmations of ADR-024's trigger-independence. CAP-040 (Group AG, DLCI 0x08 unmapped codes) found the app's own in-app Connect/Disconnect buttons produce zero wire signal, leaving its 7 target codes at N=1 each — inconclusive, still open. CAP-041 (Group AH, connect-time burst vs. non-default settings) found the burst's length/shape signature invariant across 3 non-default states — a scoped negative at the length level; a full content diff remains open (Phase 4 item 1 below). CAP-042 (Group AI, long idle bracket) found the periodic cross-channel push far sparser than CAP-036's short sample suggested, with HFP dropping out of the sync entirely. See each capture's own findings file (in its captures/CAP-0NN-... folder) and PROTOCOL.md §6's matching open items for full detail.

Phase 2 — APK reverse engineering

  • Groundwork/tooling — done 2026-08-30. Governance, storage, and procedure now in place so the actual analysis work below can start; none of it constitutes analysis having happened yet: DECISIONS.md ADR-017 (supersedes ADR-003) permits AI mechanical assistance — search, pbtk extraction, native .so disassembly explanation — within a maintainer-decides-relevance boundary; WORKSTATION_PREPARATIONS.md documents pbtk installation/real scope/dependencies; a versioned APK storage structure exists (reverse-engineering/apk/v<versionName>-<versionCode>/, indexed in the git-tracked reverse-engineering/APK_VERSIONS.md, itself gitignored for the APK/decompiled output per .gitignore); APK_REVERSE_ENGINEERING_PROCEDURE.md documents the full pull → decompile → extract → search → analyze procedure, including the diff-against-previous-version pass and the out-of-scope exclusion list (AccountLinking/ OwnershipTransfer/AccessoryNonOwner/Firebase-Analytics-Crashlytics); REVERSE_ENGINEERING.md's template now requires a file+line citation per finding and a hypothesis-to-capture-test link.

  • APK pulled — done 2026-08-30. v1.0.955078536-10253511 (base + arm64_v8a/xxhdpi splits), pulled from the maintainer's own Pixel 7a, hashed, and recorded in reverse-engineering/APK_VERSIONS.md per APK_REVERSE_ENGINEERING_PROCEDURE.md §2.

  • JADX decompilation — done 2026-08-30. jadx-output/ (12,545 Java/Kotlin files); 22 non-fatal per-class errors, typical for an obfuscated multi-dex app of this size.

  • apktool decompilation — done 2026-08-30. apktool-output/ (base) and apktool-output-arm64_v8a/ (native libs live only in that split, not in base.apk).

  • Keyword search (§4 pass) — done 2026-08-30, one pass; more passes still valuable. Found: no libmaestro.so/libgfps.so anywhere (only libandroidx.graphics.path.so/ libpw_tokenizer_jni.so — the app's Maestro logic is pure Kotlin, not a native binary, contra this project's original assumption); the app's own RFCOMM-socket-selection logic (gbm.java/fzd.java) and its two candidate SDP UUIDs ("pigweed"/"default"); literal maestro_pw.* pw_rpc service/method names (Maestro.WriteSetting/GetSoftwareInfo, HeadGesture, EartipFitTest, Dosimeter, JitterBuffer, Multipoint, DynamicServerConfigService) and a surviving dev.pigweed.pw_rpc.MethodClient reference confirming the app's own transport vocabulary. Full write-up: REVERSE_ENGINEERING.md's "Identified relevant classes" section (10 entries). Not yet done: a second pass tracing how ClassicBTReceiver's connection-state events lead into gbm's socket selection, and how fsz's WriteSetting/fux's per-service calls obtain their MethodClient — flagged as untraced in REVERSE_ENGINEERING.md's Call graph notes.

  • Extract real .proto/pw_rpc schemas — done 2026-08-30/2026-09-03, via manual decode, not pbtk. pbtk-jar-extract against base.apk wrote 0 .proto files, and a 2026-09-03 follow-up confirmed this isn't a scope/targeting problem: pbtk-jar-extract has no class-filter flag, and a manually-built 32-class targeted JAR (qjc/qja/qhr/nqx/fux/ fsz/etc., plus the one legacy CodedInputStream/CodedOutputStream-signature class pair still present elsewhere in the APK) still produced 0 files — confirmed against pbtk's own jar_extract.py source: its extraction requires a per-class mergeFrom(CodedInputStream) switch-structure in the generated class's own bytecode, which this APK's GeneratedMessageLite.newMessageInfo(default, infoString, objects) reflection-based codegen never emits, for any class. pbtk's GUI shares the same extractor module, so it is not expected to differ. Solved instead via scripts/decode_rawmessageinfo.py (a dependency-free RawMessageInfo compact-schema-string decoder, ported field-for-field from the public protobuf runtime source): qjc/qja (5-alternative oneof), qhr (38 fields, all field-type/reference info recovered), and nqx (pw_rpc.RpcPacket, 7 fields) all decoded and cross-correlated against real wire bytes (CAP-020 frames 1741/1935). See DECISIONS.md ADR-019 (maintainer sign-off obtained) for the accepted findings. Not fully closed: only qhr fields 4, 7, 12 (field-number only), and 29 have been wire-cross-checked so far — fields 11, 15, 17, 19, 22, 27, 28 are confirmed on the wire but not yet run through this same method (tracked in this file's "Targeted research follow-ups" section).

  • DLCI 0x02 channel-ownership question — resolved 2026-08-30 (narrow promotion). DECISIONS.md ADR-018 (Option 2, maintainer-approved): DLCI 0x02 confirmed 🟢 FACT as the companion app's own internal RFCOMM channel (SDP UUID 25e97ff7-... = RFCOMM channel 1 = DLCI 0x02, cross-checked against CAP-001/CAP-002/CAP-032), via the app's own gbm.java/fzd.java selection logic — see PROTOCOL.md §2.2a. Not fully resolved: whether the Sent-direction payload content specifically carries libmaestro's settings commands, in general — still 🟡 HYPOTHESIS (strong) per DECISIONS.md ADR-019's own scope note, though now substantially strengthened for the 4 qhr fields ADR-019 sampled. Settling it further means running more fields through the manual-decode item above, not pbtk.

  • CAP-033 (Group AA) — done 2026-08-30. Tested whether the second, never-observed-on-the-wire "default internal rfcomm socket" SDP UUID (gbm/fzd) ever appears when SDP is queried by the OS's own pairing flow before the companion app opens (SDP-001); SDP-002 not attempted (no firmware update pending). Result: the "default" UUID still does not appear; the full named service list (including "MAESTRO APP") is returned even with the app force-stopped — but a confirmed Forget-before-Force-stop procedure deviation and a never-executed step 3 (opening the app for a baseline comparison) cap SDP-001 at 🟡 HYPOTHESIS, not a clean result either way. A proper isolation-clean repeat is still needed (see CAP-033-FINDINGS.md §8). New lead, unplanned: the session's SDP browse also named DLCI 0x08 "GSND CONTROL" and DLCI 0x0a "GSND AUDIO" for the first time — see PROTOCOL.md §2.3/§6.

  • GMS/Play Services reverse-engineering — decided out of scope, 2026-09-07 (DECISIONS.md ADR-025). A project-wide audit (AUDIT_REPORT_2026-09-07.md §1.0) found no trace of DLCI 0x04's Fast Pair Message Stream or DLCI 0x08's private envelope anywhere in the companion app's own decompiled code — both appear to be implemented inside Google Play Services itself. The resulting scope question (should this project decompile GMS to close that gap?) is now decided: no. DLCI 0x04/0x08 FrameEncoder/FrameDecoder work proceeds clean-room, from wire-capture evidence alone (plus, for DLCI 0x04, the public Fast Pair spec) — the same method already used for ANC/Find My Buds/EQ, none of which ever needed a companion-app code cross-reference. No further APK-search effort should be spent trying to locate DLCI 0x04/0x08 transport code in this companion app; REVERSE_ENGINEERING.md's empty Message Group/Code register for these two channels reflects this, not an unfinished search.

Phase 3 — Protocol reconstruction

  • Fill in the UUID register (REVERSE_ENGINEERING.md §UUID register) — cross-referencing pass done 2026-09-09 (ai-sessions/0004_MAINTENANCE_RESULT_2026_09_09.md Task 2), clean negative. CAP-034's 8 wire-confirmed GATT UUIDs (Fast Pair Service 0xFE2C/FE2C1233FE2C1239, Device Information 0x180A, Battery Service 0x180F/0x2A19, Firmware Revision 0x2A26, Accessory Non-Owner Service 15190001-..., "Unknown Service" 109b862f-...) were searched for across the entire decompiled tree (jadx-output/ and apktool-output/smali*/, both full and short UUID forms) — zero genuine matches found, corroborating DECISIONS.md ADR-025's existing finding that this companion app's own code contains no Fast-Pair-GATT handling at all. Recorded as a checked negative in the register itself rather than left silently untried. Kept unchecked as "exhaustive" is still not claimed — a future APK version or a different keyword angle could still surface something — but this specific, concretely-scoped next step is done.
  • Message Group/Code register — confirmed empty by design, not stalled (updated 2026-09-09). The Fast Pair Message Stream framing hypothesis is confirmed (PROTOCOL.md §2.1/§4.1), but DECISIONS.md ADR-025 and REVERSE_ENGINEERING.md's own register note already establish this table is expected to stay empty for DLCI 0x04/0x08 specifically — their transport lives inside Google Play Services, not the companion app's own decompiled code, so no vendor-specific Group/Code value will ever be found there to fill this table with. Checked here, not unfinished work.
  • Resolve the framing question — updated 2026-09-09, resolved per channel, not monolithic. Per PROTOCOL.md §2.3/ARCHITECTURE.md §5's own per-channel implementation gate: DLCI 0x02 (Pigweed pw_hdlc) and DLCI 0x04 (official Fast Pair Message Stream) are both 🟢 FACT and implementation-unblocked — FrameEncoder/FrameDecoder work for either is not blocked on this item. Only DLCI 0x08's own identity remains 🔴 open (structurally decodable, [Group][Code] [Length][Value], but which protocol it belongs to is unresolved) — kept unchecked for that one remaining channel only, not for the framing question as a whole.
  • Document the full connection lifecycle with real capture evidence — updated 2026-09-09, step 3 now analyzed and maintainer-reviewed. PROTOCOL.md §5.1 already promotes the classic BR/EDR link-establishment mechanics (steps 1–2) to 🟢 FACT across seven independent captures. Step 3 (the RFCOMM channel-opening sequence) is now analyzed: a new PROTOCOL.md §5.2 records 🟡 HYPOTHESIS (strong) that DLCI 0x02 (libmaestro) reliably opens last of the five data-carrying RFCOMM channels on a fresh reconnect (6/6 independent instances across CAP-036/CAP-037/CAP-041, zero counter-examples in that condition, one honestly-scoped exception during a mid-session channel-bounce) — the maintainer reviewed this directly and explicitly chose to keep it at HYPOTHESIS rather than promote, pending more evidence or an explanation for the exception (see ai-sessions/0004_MAINTENANCE_RESULT_2026_09_09.md Task 3 for the full analysis). Still open: steps 4/6 (the Message Stream/libmaestro handshake's own internal content ordering beyond channel-open timing, and user-triggered-command timing) — not attempted this pass.
  • Bring the first command to full 🟢 FACT status — updated 2026-09-09, ANC and battery Option C already done; only battery Option A remains, and that item is capture-blocked. ANC (PROTOCOL.md §4.1) reached full FACT status 2026-08-12 (see the checked item immediately below). Battery via HFP (PROTOCOL.md §4.3 Option C) is also already 🟢 FACT (DECISIONS.md ADR-015/ADR-023). Only battery Option A (the Fast Pair BLE Battery Notification) remains open, and it is specifically blocked on the still-outstanding clean, connection-free BLE-scan repeat this file's Phase 1 section already tracks (CAP-011 was inconclusive; skeleton now created as CAP-043, see Phase 1 above) — not a research gap an AI session can close without that capture.
  • Bring ANC mode switching to full 🟢 FACT status (PROTOCOL.md §4.1) — done 2026-08-12 via deskresearch correlation against the official Fast Pair "Hearable Controls" spec + CAP-001's existing capture. DECISIONS.md ADR-009 (added 2026-08-15) blocked FrameEncoder for this command pending CAP-006, since 2 of CAP-001's 6 ANC taps produced no command frame. CAP-006 (2026-08-15) resolved this — 4/4 isolated taps produced a matching frame, zero misses — and ADR-009 was updated to lift the block. FrameEncoder/FrameDecoder for the ANC command is now implementation-ready per AGENTS.md §6.
  • Log every hypothesis test in the relevant capture's CAP-NNN-FINDINGS.md before promoting a finding from HYPOTHESIS to FACT (PROJECT_RULES.md §4)
  • Added 2026-08-23 — maintainer sign-off session on pending FACT promotions. Done 2026-08-23. Per AGENTS.md §6 an agent may propose but never commit these; the maintainer reviewed all three and approved: Find My Buds Left/Right → 🟢 FACT (PROTOCOL.md §4.4, DECISIONS.md ADR-011); the wire-baseline firmware version "release_5.203" → 🟢 FACT (PROTOCOL.md §0.1, ADR-012); the general-purpose DLCI 0x02 settings-write envelope shape → 🟢 FACT, but explicitly not its 9+ individual field mappings, which stay 🟡 HYPOTHESIS per the maintainer's own narrower decision (PROTOCOL.md §4.5, ADR-013).

Phase 4 — App development

  • Set up the Android Studio project per ARCHITECTURE.md — done and verified 2026-09-13 (ai-sessions/0013_FEATURE_RESULT_2026_09_13.md Phase 7). Five Gradle modules (:app, :ui, :domain, :data, :hardware) at android/, version catalog (android/gradle/libs.versions.toml, pinned versions per AGENTS.md §10). ./gradlew assembleDebug testDebugUnitTest test actually run in this environment (Gradle 9.5.1 wrapper pinned to 8.9, JDK 21, Android SDK android-34/build-tools 34.0.0) — builds a real app-debug.apk, 232 unit tests, 0 failures.
  • Decide dependency injection approach — resolved 2026-09-13: Hilt (DECISIONS.md ADR-028, ARCHITECTURE.md §10/§15 updated). :app's composition root wired accordingly this same session — see ai-sessions/0013_FEATURE_RESULT_2026_09_13.md.
  • Decide the passive-scanning policy for the Fast Pair Battery Notification — resolved as a bounded exception (filtered, foreground-triggered, time-boxed); see DECISIONS.md ADR-006, AGENTS.md §7, ARCHITECTURE.md §9.1
  • Implement ProtocolCodec (FrameEncoder/FrameDecoder) with unit tests for ANC — done and verified 2026-09-13 (ai-sessions/0013_FEATURE_RESULT_2026_09_13.md Phase 7). :data's AncFrameEncoder/AncFrameDecoder (DLCI 0x04 Group 0x08) tested against real tshark-extracted fixture bytes from CAP-001/CAP-006/CAP-036 (225 passing tests, including a 206-case fuzz-adjacent malformed-input sweep, AGENTS.md §11). EQ/Battery/Find My Buds codecs remain unimplemented, as scoped.
  • Implement BudsTransport (RFCOMM primary, secondary GATT for case/charging characteristics) and ConnectionStateMachine — partially done 2026-09-13. ConnectionStateMachine and the BudsTransport interface + a scripted FakeBudsTransport are implemented and unit-tested (7/7 passing, ai-sessions/0013_FEATURE_RESULT_2026_09_13.md Phase 7) — but the real, BluetoothSocket-backed RfcommBudsTransport is only sketched (compiles, follows the sealed-error-conversion/Dispatchers.IO rules) and explicitly not verified against real hardware, and per-DLCI socket multiplexing (PROTOCOL.md §2.3's three channels) is left as a documented // TODO(verify), not implemented — this checkbox stays open until that part is.
  • Implement BudsRepository / BudsRepositoryImpl wiring :data to :domain (ARCHITECTURE.md §2.1, DECISIONS.md ADR-001) — not attempted 2026-09-13, out of that session's own scope (only the BudsRepository interface exists so far, in :domain).
  • First working end-to-end connection + battery status shown in the UI. Recommended mechanism (added 2026-08-23): HFP (PROTOCOL.md §4.3 Option C) — already 🟢 FACT and not blocked, unlike Option A (still inconclusive, see Phase 1) or Option B (battery message code unconfirmed).

Phase 5 — Testing & documentation

  • Execute TESTPLAN_BLUETOOTH_HCI_SNOOP.md on at least 2 devices (differing Android version and/or OEM, including GrapheneOS as the primary reference target per ARCHITECTURE.md §1)
  • Update README.md with build instructions once the app builds
  • Decide minimum supported Android API level — resolved 2026-09-13: API 34 (Android 14), matching compile/target SDK (DECISIONS.md ADR-029, ARCHITECTURE.md §1/§15). Applied to android/'s :app/:hardware/:ui modules (minSdk = 34).
  • Decide multi-device (multiple paired Buds) support for v1 and record it in PROJECT.md scope + DECISIONS.md (currently open, see ARCHITECTURE.md §15)
  • Prepare the first public release (tag, CHANGELOG.md entry, GitHub Release per the manual-update-distribution decision in AGENTS.md §1)

Known technical debt

(Fill in as quick fixes are made — see PROJECT_RULES.md rule 13. Every entry here should be short-lived: either resolved properly or promoted to a tracked task above.)

  • Capture extraction path matters, added 2026-08-28. Four captures (CAP-012, CAP-013, CAP-017, CAP-031) lost significant byte-level payload content to severe ACL truncation from the btsnooz.py-from-bugreport fallback path (CAPTURE_BLUETOOTH_HCI_SNOOP.md §3 step 4); the one session extracted via the raw btsnoop_hci.log path instead (CAP-032) came out fully untruncated. Always check §3 step 3 (the raw file) first and prefer it whenever present — see CAPTURE_BLUETOOTH_HCI_SNOOP.md §3's own PROPOSAL note for the full detail. Verified 2026-08-30 (audit pass): a frame.cap_len == frame.len sweep across all 29 non-planned captures confirms these exact 4 are the only ones affected — CAP-017 is a filename-invisible case (named -btsnoop_hci.log, not -btsnooz_hci.log, despite being truncated by a different mechanism, a phone-side snaplen setting) — no further silently-truncated log exists among the remaining 25.

Open questions

Open architectural and protocol questions are tracked at their source only — this file does not keep a second, synchronized checkbox list of them, since that duplication is exactly what caused this file to fall out of sync with ARCHITECTURE.md once already (see CHANGELOG.md). Each question has exactly one home:

  • Protocol-level open questions (framing hypothesis, unconfirmed opcodes, wire-visibility of on-device-only features, etc.) → PROTOCOL.md §6.
  • Architecture-level open questions (DI framework, minimum Android API level, multi-device scope, etc.) → ARCHITECTURE.md §15.

Check those sections directly when deciding what's still undecided; resolving one only requires updating it in that one place, plus a DECISIONS.md entry where the rule requires one (PROJECT_RULES.md §3).


https://github.com/tedsluis/opencontrolpixelbudspro2/blob/main/TODO.md - https://tedsluis.github.io/opencontrolpixelbudspro2/TODO