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:
@@ -78,21 +78,33 @@ describe('native alarm delivery contract', () => {
|
||||
expect(cancelFireBody).toContain('REQ_FIRE');
|
||||
});
|
||||
|
||||
it('has exactly two notification channels and no full-screen-intent notification', () => {
|
||||
// The old contract said "no notification ever announces an alarm" — that was
|
||||
// falsified on an Android 15 emulator: the alarm's own PendingIntent cannot
|
||||
// start the takeover from the background (`BAL_BLOCK`), so the ringing alarm
|
||||
// needs the one notification the platform does launch for you, a full-screen
|
||||
// intent. Delivery is still the alarm clock + the service's own audio stream;
|
||||
// the notification is only the launch path for the screen.
|
||||
it('has three channels, and exactly one full-screen-intent notification', () => {
|
||||
const channels = notifier.split('= NotificationChannel(').length - 1;
|
||||
expect(channels).toBe(2);
|
||||
expect(channels).toBe(3);
|
||||
expect(notifier).toContain('CHANNEL_VOLUME');
|
||||
expect(notifier).toContain('CHANNEL_SERVICE');
|
||||
// The volume warning is informational; only an alarm may take over the screen,
|
||||
// and that is scheduled, not notified.
|
||||
expect(notifier).not.toContain('setFullScreenIntent');
|
||||
expect(notifier).toContain('CHANNEL_FIRING');
|
||||
const fsi = notifier.split('setFullScreenIntent').length - 1;
|
||||
expect(fsi).toBe(1);
|
||||
// The firing channel is silent: the siren is our own audio stream, and a
|
||||
// channel sound on top of it would double the alarm.
|
||||
const firingAt = notifier.indexOf('CHANNEL_FIRING,');
|
||||
expect(notifier.slice(firingAt, firingAt + 400)).toContain('setSound(null, null)');
|
||||
});
|
||||
|
||||
it('only warns about volume, never about a ringing alarm', () => {
|
||||
it('keeps the volume warning informational and never arms from a notification', () => {
|
||||
expect(notifier).toContain('Alarm volume is at ');
|
||||
expect(notifier).not.toMatch(/Alarm ringing/);
|
||||
expect(notifier).not.toMatch(/Alarm armed"\)/);
|
||||
expect(notifier).toContain('CATEGORY_RECOMMENDATION');
|
||||
// The volume warning must not carry the takeover: only the firing
|
||||
// notification may take over the screen.
|
||||
const warningAt = notifier.indexOf('fun showVolumeWarning');
|
||||
expect(notifier.slice(warningAt, warningAt + 1200)).not.toContain('setFullScreenIntent');
|
||||
});
|
||||
|
||||
it('refuses to arm a device that cannot deliver a real alarm', () => {
|
||||
|
||||
Reference in New Issue
Block a user