Skip to content

app: persist device mute across app restart#8504

Open
mdmohsin7 wants to merge 3 commits into
mainfrom
caleb/persist-device-mute
Open

app: persist device mute across app restart#8504
mdmohsin7 wants to merge 3 commits into
mainfrom
caleb/persist-device-mute

Conversation

@mdmohsin7

@mdmohsin7 mdmohsin7 commented Jun 28, 2026

Copy link
Copy Markdown
Member

Problem

Device mute (double-tap pause) was stored in-memory only (_isPaused), so it did not survive an app kill / force-quit. After the OS reclaimed the app (routine on Android background) or the user quit, the next device reconnect silently resumed recording even though the user had muted.

This is a long-standing, recurring complaint on Featurebase:

  • "If I turn off recording why doesn't it stay off?" (Limitless pendant)
  • "Cv1 unmutes on disconnect/reconnect"

The simple in-session reconnect case was already fixed (mute restored via wasPaused in streamDeviceRecording), but that state was lost on restart — so the complaints persisted.

Fix

Persist the mute and restore it on startup so the existing reconnect path re-applies it:

  • preferences.dart — new deviceMuted bool pref (mirrors batchMuted).
  • capture_provider.dart — restore _isPaused from deviceMuted in the constructor; write the pref true/false in pauseDeviceRecording / resumeDeviceRecording. On the next reconnect, streamDeviceRecording reads _isPaused as wasPaused and re-applies the mute instead of resuming. All mute entry points go through pause/resume, so they're covered.

Tests

Added 3 tests in capture_provider_test.dart:

  • constructor restores muted state when deviceMuted is set
  • constructor leaves recording unpaused when unset
  • pauseDeviceRecording persists the flag

All 3 pass. (Other failures in this file are a pre-existing Env._instance not initialized sandbox issue in the background-mode tests — unrelated.)

Drive-by fix

main did not compile this test file: the Frame removal (#8500) dropped DeviceType.frame from the enum but left 3 references in capture_provider_test.dart. Removed the two Frame-only tests and switched the third to DeviceType.bee.

Scope

This is gap #3 of the mute investigation. Still open:

  • colors to the LED board #2 — offline auto-sync (_checkAndStartAutoSync) ignores mute, so captures made during a mute window still transcribe on reconnect.
  • Client #1 — true device-side mute (firmware actually stops recording); needs firmware support.

Review in cubic

Mute was in-memory only, so an OS kill/force-quit while muted silently
resumed device recording on the next reconnect (Featurebase: 'If I turn
off recording why doesnt it stay off?', 'Cv1 unmutes on disconnect/
reconnect'). Restore _isPaused from the deviceMuted pref on construction
and persist it in pause/resumeDeviceRecording; the existing reconnect
path re-applies wasPaused.
… refs

Adds device-mute persistence tests. Also removes 3 DeviceType.frame
references left behind by the Frame removal (#8500) that broke
compilation of this test file on main.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Confidence score: 4/5

  • In app/lib/providers/capture_provider.dart, the SharedPreferences.setBool call is asynchronous and currently not awaited, so an app kill/right-after-exit path could drop the persisted flag and lead to inconsistent state on next launch — await the write (or otherwise guarantee completion) before merging to fully de-risk it.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/lib/providers/capture_provider.dart">

<violation number="1" location="app/lib/providers/capture_provider.dart:2286">
P3: SharedPreferences setBool writes are async; assign without `await` risks losing the persisted flag on kill.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

await SharedPreferencesUtil().saveBool('nativeBleStreamingEnabled', false);
_isPaused = true;
// Persist so the mute survives an app kill/restart, not just a reconnect.
SharedPreferencesUtil().deviceMuted = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: SharedPreferences setBool writes are async; assign without await risks losing the persisted flag on kill.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/lib/providers/capture_provider.dart, line 2286:

<comment>SharedPreferences setBool writes are async; assign without `await` risks losing the persisted flag on kill.</comment>

<file context>
@@ -2278,13 +2282,17 @@ class CaptureProvider extends ChangeNotifier
     await SharedPreferencesUtil().saveBool('nativeBleStreamingEnabled', false);
     _isPaused = true;
+    // Persist so the mute survives an app kill/restart, not just a reconnect.
+    SharedPreferencesUtil().deviceMuted = true;
     updateRecordingState(RecordingState.pause);
     notifyListeners();
</file context>

@Git-on-my-level

Copy link
Copy Markdown
Collaborator

Solid implementation — persisting the mute flag across restart closes a real privacy gap (the Featurebase complaints are exactly right: a muted device silently resuming recording on reconnect after an OS kill is a trust problem).

Verified the core logic: _isPaused is only mutated in the constructor restore, field init, and pause/resumeDeviceRecording, and all mute entry points (double-tap handler, UI buttons, quick actions) route through pause/resume. The reconnect path captures wasPaused before _resetStateVariables/_resetState, neither of which touches _isPaused, so the re-applied mute is correct. The deviceMuted pref cleanly mirrors the existing batchMuted pattern.

The drive-by DeviceType.frame cleanup is the right call — those refs were dead after #8500.

One minor suggestion: it'd round out the test coverage to add a case asserting resumeDeviceRecording clears deviceMuted back to false (symmetric to the pause-persists test). Not blocking.

Leaving formal approval to a human maintainer since this changes recording-state persistence semantics — worth a quick confirm that indefinite persistence until explicit resume is the intended UX, especially given the broader mute investigation this is part of.

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.

2 participants