Reach the screen in the background, and leave it again
Measured on an Android 15 emulator with the app backgrounded and the display
asleep: the siren played, the platform refused the takeover
("Background activity launch blocked! … BAL_BLOCK"), and the alarm was
destroyed mid-ring by its own receipt ack.
Takeover:
- the foreground service posts an alarm_firing notification whose
full-screen intent raises AlarmActivity — the only background launch
Android 14+ permits. Verified with the display asleep: wakefulness
Asleep → Awake, topResumedActivity=.AlarmActivity, the FSI on the record.
- the cancel lives on the four real silences (disarm, stop, JS ack, activity
destroy), not in AlarmPlayer.stop: play() calls stop() first, which
retracted the notification ~120 ms after posting it.
- "Back to alerts" now finishes the alarm window (AlarmModule
finishTakeover). A route pop is not an activity: the window stayed on top
for good with show-when-locked, keep-screen-on and no Recents entry — the
reported "no way back to the main menu".
The native-delivery contract test said "no notification ever announces an
alarm"; that was falsified on device, so it now pins one FSI notification and
a silent firing channel instead.
Verified end to end on the emulator (two runs, one with the screen off):
join without an account, armed service, background trigger → wake + takeover
+ siren, acknowledge → silence, notification retracted, alert delivered,
takeover closed by itself, Home reachable, reopening silent.
This commit is contained in:
@@ -7,6 +7,7 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import java.lang.ref.WeakReference
|
||||
import com.facebook.react.ReactActivity
|
||||
import com.facebook.react.ReactActivityDelegate
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
||||
@@ -38,6 +39,7 @@ class AlarmActivity : ReactActivity() {
|
||||
// Order matters: the flags must be set before the window is shown.
|
||||
showOverLockScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
current = WeakReference(this)
|
||||
}
|
||||
|
||||
private fun showOverLockScreen() {
|
||||
@@ -67,11 +69,30 @@ class AlarmActivity : ReactActivity() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
if (current?.get() === this) current = null
|
||||
AlarmPlayer.stop(this)
|
||||
// The ringing notification dies with the ring it announced. NOT in
|
||||
// AlarmPlayer.stop: play() calls that first, which would retract the
|
||||
// full-screen intent that raised this activity.
|
||||
AlarmNotifier.cancelFiring(this)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The takeover that is on screen, if any.
|
||||
*
|
||||
* The JS "Back to alerts" action pops the route but cannot dismiss an
|
||||
* Android activity, so without this the alarm window would stay on top
|
||||
* for good — visible over the lock screen, holding the screen awake, in a
|
||||
* task that is excluded from Recents. That is the reported "no way back to
|
||||
* the main menu". [AlarmModule.finishTakeover] ends it.
|
||||
*/
|
||||
@Volatile
|
||||
private var current: WeakReference<AlarmActivity>? = null
|
||||
|
||||
fun currentActivity(): AlarmActivity? = current?.get()
|
||||
|
||||
const val EXTRA_ALERT_ID = "alarm.alertId"
|
||||
const val EXTRA_MESSAGE = "alarm.message"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user