Skip to content

Play received voice notes from Android Auto - #2033

Open
JorgeRdg wants to merge 2 commits into
DrKLO:masterfrom
JorgeRdg:feat/car-voice-messages
Open

Play received voice notes from Android Auto#2033
JorgeRdg wants to merge 2 commits into
DrKLO:masterfrom
JorgeRdg:feat/car-voice-messages

Conversation

@JorgeRdg

Copy link
Copy Markdown

Android Auto only ever receives the text body of a message. For a voice note that is the placeholder "Voice message", so the recording is unreachable without picking up the phone — which is exactly what should not happen while driving.

CarMessage has carried setMultimediaMimeType/setMultimediaUri since androidx.car.app 1.4.0, the version already pinned in TMessagesProj/build.gradle, so this needs no dependency bump and is independent of #2008.

What it does

buildCarMessage() resolves the note through FileLoader, exposes it via the .provider FileProvider the app already declares, and sets it on the builder with audio/ogg. When the file is not cached yet it is fetched and the template rebuilt, so the player appears on its own — voice notes are not auto-downloaded by default and in the car the message typically arrives mid-drive, so without this it would almost always fall back to text.

This reuses the exact pattern NotificationsController already applies to the equivalent MessagingStyle attachment (setData("audio/ogg", uri) with pendingVoiceLoads), so the FileProvider paths involved are already exercised in production by the notification path.

Notes for review

Privacy. buildCarMessage() already asked getShortStringForMessage() for preview[0] and then ignored it. Harmless while only redacted text was sent, but attaching audio changes it: with previews off the body reads as a placeholder while the recording would play aloud in the car, leaking exactly what the user chose to hide. The attachment honours preview[0] and the passcode state, matching the existing notification guard. Encrypted chats were already excluded in collectUnreadDuringDrive().

fileLoaded is per-account. HomeScreen uses the global NotificationCenter for its other events, but fileLoaded is posted on NotificationCenter.getInstance(account). Observing it globally registers fine and never fires, so the observer lives on the current account and moves on activeAccountChanged, detaching from the outgoing account first.

URI permission. The host reads the URI from its own process and CarMessage crosses Binder rather than riding an Intent, so FLAG_GRANT_READ_URI_PERMISSION cannot be attached to one. The grant is issued explicitly against getHostInfo().getPackageName(), falling back to text when it fails, since an unreadable URI would surface as a broken control in the car.

Testing

Developed and verified in a Nagram-based fork, first on the Desktop Head Unit and then confirmed working in a real car: a voice note arriving mid-drive appears with a play control and plays through the car audio.

The gating is isolated in CarVoiceAttachment so it can be unit tested without a car host. I kept the tests out of this PR because the project has no JVM unit-test setup (src/test, Robolectric and testOptions are all absent) and adding one felt out of scope here — happy to include them, or the test scaffolding, if you would like it.

Android Auto only ever receives the text body of a message. For a voice note
that is the placeholder "Voice message", so the recording is unreachable
without picking up the phone -- which is exactly what should not happen while
driving.

CarMessage has carried setMultimediaMimeType/setMultimediaUri since
androidx.car.app 1.4.0, the version already pinned here, so the message can
carry the audio itself with no dependency bump.

buildCarMessage() now resolves the note's local file through FileLoader,
exposes it as a content:// URI via the FileProvider the app already declares,
and sets it on the builder with audio/ogg. When the file is not cached yet it
is fetched and the template rebuilt, so the player appears on its own; voice
notes are not auto-downloaded by default and in the car the message typically
arrives mid-drive, so without this it would almost always fall back to text.

Privacy gates the attachment. buildCarMessage() already asked
getShortStringForMessage() for preview[0] and then ignored it, which was
harmless while only redacted text was sent. Attaching audio changes that: with
previews off the body reads as a placeholder while the recording would play
aloud in the car, leaking exactly what the user chose to hide. The attachment
therefore honours preview[0] and the passcode state, matching the guard
NotificationsController already applies to the equivalent MessagingStyle
attachment. Encrypted chats were already excluded in collectUnreadDuringDrive().

Two details that would otherwise fail silently. fileLoaded is posted on the
per-account NotificationCenter, not the global one this screen uses for its
other events, so it is observed on the current account and moved when
activeAccountChanged arrives. And the host reads the URI from its own process
while CarMessage crosses Binder rather than riding an Intent, so
FLAG_GRANT_READ_URI_PERMISSION cannot be attached to one -- the grant is issued
explicitly against getHostInfo().getPackageName(), falling back to text when it
fails, since an unreadable URI would surface as a broken control in the car.

The gating lives in CarVoiceAttachment rather than inline so it can be unit
tested without a car host.
The read access handed to the car host was never given back, so it outlived the
drive: one grant accumulated per voice note, and after switching accounts the
host kept read access to the previous account's files.

Grants are now tracked and dropped when the screen is destroyed and before the
active account changes.

Not tied to onPause or to a timer, unlike the notification code that grants an
image to SystemUI and revokes 20s later. That works there because SystemUI
renders the image immediately; here the host opens the file when the user
presses play, which can come long after the message appeared, so a delayed or
pause-triggered revoke would risk cutting playback. Binding the grants to the
screen's lifetime and to the active account bounds them without that risk.

Reported by sourcery-ai on #106.
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