[fix] Android: stopping a meeting can no longer kill the process, and a failed one releases the microphone service - #355
Merged
Conversation
… a failed one releases the microphone service A static pass over the Android app after the Play rejection (see #354) found several ways the meeting flow could take the process down, all of them on the path from a recording to its end: - `MicCapture.start()` reset the stop flag at collection time, so a Stop tapped while the session was still connecting was undone: the read loop kept going, `stop()` gave up waiting after 5 s and finished the encoder, and the next chunk hit `append() after finish()` — an IllegalStateException nothing caught, on a scope with no handler. The microphone also kept recording after Stop. The instance is now one-shot: a stop is never undone, and the session skips the "recording" transition when a stop already won. - Nothing in the app installed a CoroutineExceptionHandler, so any exception the narrow catches did not cover (MediaCodec state errors, DataStore I/O, a bad header on reconnect, the task-swiped-away teardown racing a buffered chunk) reached the default handler and killed the process. The app, meeting and import scopes now log instead, and both capture loops map anything unexpected to a Failed state the user can retry from. - `MeetingService` had three paths that could stop without ever having called `startForeground()` after a `startForegroundService()` — which Android answers with ForegroundServiceDidNotStartInTimeException. Every such path now goes through `ensureForeground()` first. - A failed capture (mic busy, expired token, no encoder) left the microphone foreground service and its "Recording a meeting" notification running until the app was killed. The service now watches the session and stops on Failed; `clear()` stops a leftover instance too. - The library LazyColumn keyed pending uploads and cloud recordings on the same id; a recording drained between the two reads appeared in both lists and Compose threw on the duplicate key. Keys are namespaced. The release workflow now commits the Play edit with `changesNotSentForReview`: while the app has a rejected change outstanding, Play refuses an edit that would auto-submit, which is how the android-v0.1.2 upload failed. Sending the release for review is a click in Play Console, documented in RELEASING.md. versionCode 4 / versionName 0.1.3. Verified on the release build (Android 14 emulator): upgrade install over 0.1.2, cold start, record → Stop → upload, Stop from the notification while backgrounded, task swiped away mid-recording, file import — no crash, no leftover service or notification. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
6 tasks
✅ SonarQube Quality Gate passed — pathorsAI_parley0 open issues on this PR. |
This was referenced Sep 11, 2026
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.
What this changes
Closes the remaining process-death paths in the Android meeting flow found by a static audit after the Play rejection (#354), fixes the release workflow so the Play upload succeeds while the app has a rejected change outstanding, and bumps Android to
0.1.3 (4).Why
Play's rejection of
0.1.1 (2)("app opens but keeps stopping") was traced to the DataStore/R8 crash fixed in #354. Auditing the rest of the app for the same class of failure turned up five more, all reachable by a reviewer who records a meeting:MicCapture.start()reset the stop flag at collection timestop()timed out, finished the encoder, next chunk threwappend() after finish()→ process deathMicCaptureis one-shot;MeetingSessionskips the Recording transition when a stop already wonCoroutineExceptionHandleranywhereFailedstateMeetingServicecould stop without ever callingstartForeground()afterstartForegroundService()ForegroundServiceDidNotStartInTimeExceptionon the stop action reaching a fresh instance, on a start racingclear(), on the demo action in releaseensureForeground()before every stop-capable pathmicrophoneFGS stayed until the app was killedsession.stateand stops onFailed;clear()stops a leftover instanceLazyColumnkeyed pending and cloud rows on the same idIllegalArgumentExceptionon open after a drained uploadRelease workflow: the
android-v0.1.2run failed at Upload to Play withChanges cannot be sent for review automatically. Please set the query parameter changesNotSentForReview to true.Play refuses an auto-submitting edit while the app has a rejected/unreviewed change. The upload step now passeschangesNotSentForReview: true; sending for review is a click in Play Console (documented inRELEASING.md).Part of #244.
How it was verified
Release build (
assembleRelease, minified) on an Android 14 / API 34 Play emulator:0.1.2 (3), cold start with the stored tokenGET /recordings./gradlew :parleykit:test :app:testDebugUnitTestpassbunx tsc --noEmit/bunx vitest run— not applicable, no desktop code touched🤖 Generated with Claude Code