Widget trampoline waits for foreground recording; fixes 10s notification delay
This commit is contained in:
parent
1f46d2a0e4
commit
673330293a
1 changed files with 48 additions and 2 deletions
|
|
@ -6,6 +6,14 @@ import android.os.Bundle
|
|||
import com.shonar.MainActivity
|
||||
import com.shonar.recording.RecordingPermissionHelper
|
||||
import com.shonar.recording.RecordingService
|
||||
import com.shonar.recording.RecordingSnapshot
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
/**
|
||||
* Invisible trampoline for the widget tap. A widget broadcast into a cold
|
||||
|
|
@ -17,9 +25,20 @@ import com.shonar.recording.RecordingService
|
|||
* Forwards to RecordingService.START (or MainActivity when the mic
|
||||
* permission is missing) and finishes before the first frame draws, so
|
||||
* there is no visible flash.
|
||||
*
|
||||
* The trampoline stays alive (still invisible) until the service reports a
|
||||
* non-IDLE snapshot or a short bound fires. Finishing instantly drops our
|
||||
* uid from TOP before startForeground() lands, making the start
|
||||
* background-originated — the system then defers the notification's
|
||||
* buzz/heads-up by ~10s (traced on-device 2026-09-18: FGS allowed
|
||||
* 17:03:17.4, first buzz 17:03:27.5). Waiting for the snapshot keeps the
|
||||
* whole start foreground-originated; the wait ends the moment recording
|
||||
* state flips, so it adds no artificial delay.
|
||||
*/
|
||||
class StartRecordingActivity : Activity() {
|
||||
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (!RecordingPermissionHelper.hasRecordAudio(this)) {
|
||||
|
|
@ -28,10 +47,37 @@ class StartRecordingActivity : Activity() {
|
|||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setAction(RecordingService.ACTION_START),
|
||||
)
|
||||
} else {
|
||||
RecordingService.command(this, RecordingService.ACTION_START)
|
||||
finish()
|
||||
return
|
||||
}
|
||||
RecordingService.command(this, RecordingService.ACTION_START)
|
||||
ShonarWidgetUpdater.refresh(this)
|
||||
scope.launch {
|
||||
// Bound, not a delay: returns the instant recording starts; the
|
||||
// timeout only covers mic-failure paths (the service reverts the
|
||||
// UI itself there).
|
||||
withTimeoutOrNull(TRAMPOLINE_WAIT_MS) {
|
||||
RecordingService.snapshot.first {
|
||||
it.phase != RecordingSnapshot.Phase.IDLE
|
||||
}
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
// singleInstance re-tap while a waiter is pending: the first waiter
|
||||
// already covers the start — never strand a second instance.
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
scope.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TRAMPOLINE_WAIT_MS = 2_500L
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue