Skip to content

Device: Fix AirPods rename not actually changing the name#490

Merged
d4rken merged 2 commits into
mainfrom
fix/aap-rename-opcode
Apr 8, 2026
Merged

Device: Fix AirPods rename not actually changing the name#490
d4rken merged 2 commits into
mainfrom
fix/aap-rename-opcode

Conversation

@d4rken

@d4rken d4rken commented Apr 8, 2026

Copy link
Copy Markdown
Member

What changed

Renaming AirPods from the device settings screen now actually changes the name. Before, tapping Rename sent a command that AirPods silently ignored — the new name only appeared in CAPod briefly and then reverted on the next reconnect.

After this fix, the rename is accepted by the AirPods firmware and the new name persists across disconnect/reconnect. On reconnect, AirPods report the new name back and CAPod shows it.

We also try to update Android's own Bluetooth device name (the one shown in system Bluetooth settings) at the same time. On Android 12+ this is blocked by a Companion Device Manager permission check, so if the system rename isn't allowed CAPod shows a snackbar telling the user to rename it in Android's Bluetooth settings or re-pair the AirPods once to let Android pick up the new name.

A few smaller rename/settings improvements came along with it:

  • The rename button is now only shown when the AAP connection is fully ready (avoids a brief window where tapping would silently fail).
  • If any device setting fails to apply, a snackbar now appears with the error instead of the failure being invisible.
  • The rename dialog only accepts ASCII characters (the internal decoder can only read them back, so non-ASCII names would disappear on the next reconnect).

Technical Context

  • Root cause of the AAP part: buildRenameMessage was using opcode 0x1E with a trailing NUL (04 00 04 00 1E 00 [size] 00 [name] 00), which matches the LibrePods Android createRenamePacket implementation. On-device testing on Pro 2 USB-C (firmware 81.2675...) showed AirPods silently discard this variant — no 0x001D echo, no persistence.
  • Working format: 04 00 04 00 1A 00 01 [size] 00 [name] (opcode 0x1A, no trailing NUL). This matches the LibrePods AAP Definitions.md docs and the LibrePods Linux airpods_packets.h. Verified end-to-end: device echoes the new name in its next 0x001D device info and the name survives a force-stop / reconnect cycle.
  • LibrePods discrepancy: Their Android code and Linux code disagree on the opcode. Their own issue Reaction: Fix popup appearing twice when opening AirPods case #411 (Pro 3 user reporting rename doesn't propagate) is consistent with the Android 0x1E variant being ignored by firmware. A follow-up PR is going to LibrePods with the same fix.
  • setAlias() path: Uses reflection on the hidden BluetoothDevice.setAlias(String) because the public API 30+ variant requires BLUETOOTH_PRIVILEGED. On Android 12+ the hidden method throws SecurityException: does not have a CDM association with the Bluetooth Device unless the app has explicitly set up a Companion Device Manager association — which CAPod doesn't currently pursue (that'd be a separate pairing flow). So setAlias is effectively always best-effort, and the snackbar is the designed fallback.
  • Optimistic update rollback: AapConnection.send() now rolls back deviceInfo.name if sendRaw throws — scoped only to SetDeviceName because the rename has no device echo to correct a bad optimistic write, unlike the ANC / toggle paths.
  • Generalized send failures: DeviceSettingsViewModel used to swallow all send exceptions with a log line. Unified both send() and sendProGated() paths through sendInternal(), which emits a new Event.SendFailed on exception. Host collects this and shows a snackbar. Benefits every setting, not just rename.
  • Tests: encoder test now full-frame asserts the exact 15-byte wire format for SetDeviceName("MyPods"). New ViewModel tests cover rename forwarding, the no-target-address no-op, and the SendFailed event on manager exceptions. AapConnection-level rename rollback tests are intentionally skipped — reaching ConnectionState.READY in a unit test needs a blocking-InputStream harness that doesn't exist yet. The working protocol format is locked in by the full-frame encoder assertion, and the rollback logic is small enough to review without a test.
  • Review notes: the ASCII-only restriction in the rename dialog is deliberate — parseNullTerminatedStrings decodes device info with Charsets.US_ASCII and filters to 0x20..0x7E, so a UTF-8 name would round-trip as garbage even if the device accepted it. Expanding the decoder to UTF-8 touches all device-info parsing and is tracked as a follow-up if non-ASCII rename becomes a real requirement.

d4rken added 2 commits April 8, 2026 14:56
Switch the AAP rename packet to the opcode 0x1A format (04 00 04 00 1A 00 01 [size] 00 [name]) matching the LibrePods documentation and Linux implementation. The previous 0x1E variant (from the LibrePods Android code) was silently ignored by AirPods Pro 2 USB-C firmware — no 0x001D echo, no persistence across reconnect.

Verified on AirPods Pro 2 USB-C (firmware 81.2675...): the device now echoes the new name back via the next 0x001D INFORMATION message, and the name persists after disconnect/reconnect.

Also hardens the rename UX: gate the edit icon on isAapReady (was isAapConnected, which allowed sending during HANDSHAKING), apply an optimistic deviceInfo update with a scoped rollback on send failure, surface send errors via a new Event.SendFailed + snackbar, and restrict dialog input to ASCII with inline error feedback. Unifies send() / sendProGated() through a single sendInternal() helper so error plumbing benefits every command, not just rename.

Note: this only updates the AirPods firmware's self-reported name. Android's system Bluetooth settings read from the bond database and are not affected — renaming there still requires the Android system Bluetooth UI.
After the AAP rename succeeds, try to update Android's per-device bond alias via the hidden BluetoothDevice.setAlias(String) method, so the new name also shows up in the system Bluetooth settings on this phone.

Known failure mode on Android 12+: setAlias is gated behind a Companion Device Manager (CDM) association at the service layer, and raises 'does not have a CDM association with the Bluetooth Device' for third-party apps that don't hold one. In that case, a dedicated snackbar explains that the system rename didn't go through and suggests renaming manually in system settings or re-pairing.

The AAP-level rename is always attempted first and is the load-bearing part; the system alias is a best-effort extra.
@d4rken d4rken merged commit cb915d6 into main Apr 8, 2026
10 checks passed
@d4rken d4rken deleted the fix/aap-rename-opcode branch April 8, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant