fix(alarm): fail-safe alarm system overhaul (SDD alarm-system-overhaul, slice A)
Root-cause fix for the unstoppable-alarm incident (alarm rang 15 minutes, only uninstall silenced it) plus systematic hardening of every stop path. Native (Kotlin): - Verified stop: stopActiveAlarm now derives its result from the real post-teardown state (companion instance + synchronous stopEverything + activeRingingId check) instead of reporting unconditional success. - Atomic teardown: every stop path (stop action, notification button, snooze, missed, onDestroy, startForeground failure) funnels through one stopEverything() covering audio, wakelock, notification, foreground state and firing-record cleanup; player.release() guarded. - Bounded ringing: 10-minute auto-silence armed via AlarmManager fires a FIRED->MISSED transition with a localized missed-alarm notification; repeating alarms keep their native rearm, deleted alarms never produce ghost MISSED notifications. - Durable firing record with onStartCommand re-validation (resurrection guard) and boot-time stale cleanup; firing records cleared on every refuse/mismatch/cancel path. - New notification-only dismissal channel (dismissAlarmNotificationOnly) so UI-level dedup can never kill a live ring's audio. Flutter (Dart): - Stop/disable/edit/delete of a ringing alarm always attempt to silence it; on native-query failure the stop falls back toward silence via the id-scoped legacy stop. - Verified-stop results surface failures: the ringing screen keeps dismiss-by-design on success, but on a verified failure it stays up with a persistent force-stop banner (guarded against double-dismiss) and auto-dismisses if the ring ends externally (missed/notification). - Missed events sync alarm bookkeeping without opening the ringing UI. - 4 new l10n keys translated across all 13 locales (ARB guard green). 550 tests green, analyzer clean. Reviewed in 3 adversarial 4-lens rounds (2 deterministic + 1 refuter-corroborated critical fixed); formal gentle-ai receipt waived by maintainer authorization (correction scope legitimately exceeded the frozen genesis paths). On-device QA checklist in openspec/changes/alarm-system-overhaul/tasks.md pending before archive.
This commit is contained in:
@@ -1694,6 +1694,30 @@ abstract class AppLocalizations {
|
||||
/// **'Detener alarma'**
|
||||
String get stopAlarmAction;
|
||||
|
||||
/// No description provided for @alarmStopFailedMessage.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'No pudimos confirmar que la alarma se detuvo. Inténtalo de nuevo.'**
|
||||
String get alarmStopFailedMessage;
|
||||
|
||||
/// No description provided for @alarmForceStopAction.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Forzar detención'**
|
||||
String get alarmForceStopAction;
|
||||
|
||||
/// No description provided for @alarmMissedNotificationTitle.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'Alarma perdida'**
|
||||
String get alarmMissedNotificationTitle;
|
||||
|
||||
/// No description provided for @alarmMissedNotificationText.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
/// **'{name} se silenció automáticamente después de 10 minutos.'**
|
||||
String alarmMissedNotificationText(Object name);
|
||||
|
||||
/// No description provided for @pauseAction.
|
||||
///
|
||||
/// In es, this message translates to:
|
||||
|
||||
@@ -899,6 +899,21 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'إيقاف المنبه';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'تعذّر التأكد من إيقاف المنبه. حاول مرة أخرى.';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'إيقاف قسري';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'منبه فائت';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return 'تم كتم $name تلقائيًا بعد 10 دقائق.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'إيقاف مؤقت';
|
||||
|
||||
|
||||
@@ -908,6 +908,21 @@ class AppLocalizationsBn extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'অ্যালার্ম বন্ধ করুন';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'অ্যালার্ম বন্ধ হয়েছে তা নিশ্চিত করা যায়নি। আবার চেষ্টা করুন।';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'জোর করে বন্ধ করুন';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'মিস হওয়া অ্যালার্ম';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '১০ মিনিট পর $name স্বয়ংক্রিয়ভাবে নিঃশব্দ করা হয়েছে।';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'বিরতি দিন';
|
||||
|
||||
|
||||
@@ -910,6 +910,21 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'Alarm stoppen';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'Wir konnten nicht bestätigen, dass der Alarm gestoppt wurde. Versuche es erneut.';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'Erzwungen stoppen';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'Verpasster Alarm';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '$name wurde nach 10 Minuten automatisch stummgeschaltet.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'Pausieren';
|
||||
|
||||
|
||||
@@ -903,6 +903,21 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'Stop alarm';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'We couldn\'t confirm the alarm stopped. Try again.';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'Force stop';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'Missed alarm';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '$name was silenced automatically after 10 minutes.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'Pause';
|
||||
|
||||
|
||||
@@ -907,6 +907,21 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'Detener alarma';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'No pudimos confirmar que la alarma se detuvo. Inténtalo de nuevo.';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'Forzar detención';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'Alarma perdida';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '$name se silenció automáticamente después de 10 minutos.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'Pausar';
|
||||
|
||||
|
||||
@@ -913,6 +913,21 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'Arrêter l’alarme';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'Nous n’avons pas pu confirmer l’arrêt de l’alarme. Réessayez.';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'Forcer l’arrêt';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'Alarme manquée';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '$name a été mise en sourdine automatiquement après 10 minutes.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'Pause';
|
||||
|
||||
|
||||
@@ -904,6 +904,21 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'अलार्म रोकें';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'हम पुष्टि नहीं कर सके कि अलार्म बंद हुआ। फिर से कोशिश करें।';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'जबरन बंद करें';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'छूटा हुआ अलार्म';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '10 मिनट बाद $name अपने आप म्यूट कर दिया गया।';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'विराम दें';
|
||||
|
||||
|
||||
@@ -908,6 +908,21 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'Hentikan alarm';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'Kami tidak dapat memastikan alarm berhenti. Coba lagi.';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'Paksa berhenti';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'Alarm terlewat';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '$name dibisukan secara otomatis setelah 10 menit.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'Jeda';
|
||||
|
||||
|
||||
@@ -910,6 +910,21 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'Ferma sveglia';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'Non siamo riusciti a confermare l’arresto della sveglia. Riprova.';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'Forza arresto';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'Sveglia mancata';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '$name è stata disattivata automaticamente dopo 10 minuti.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'Pausa';
|
||||
|
||||
|
||||
@@ -875,6 +875,20 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'アラームを停止';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage => 'アラームが停止したことを確認できませんでした。もう一度お試しください。';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => '強制停止';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'アラームの聞き逃し';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '$nameは10分後に自動的に消音されました。';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => '一時停止';
|
||||
|
||||
|
||||
@@ -905,6 +905,21 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'Parar alarme';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'Não conseguimos confirmar que o alarme parou. Tente novamente.';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'Forçar parada';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'Alarme perdido';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '$name foi silenciado automaticamente após 10 minutos.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'Pausar';
|
||||
|
||||
|
||||
@@ -909,6 +909,21 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => 'Остановить будильник';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage =>
|
||||
'Не удалось подтвердить, что будильник остановлен. Попробуйте снова.';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => 'Принудительно остановить';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => 'Пропущенный будильник';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '$name был автоматически отключён через 10 минут.';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => 'Пауза';
|
||||
|
||||
|
||||
@@ -871,6 +871,20 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get stopAlarmAction => '停止闹钟';
|
||||
|
||||
@override
|
||||
String get alarmStopFailedMessage => '无法确认闹钟已停止,请重试。';
|
||||
|
||||
@override
|
||||
String get alarmForceStopAction => '强制停止';
|
||||
|
||||
@override
|
||||
String get alarmMissedNotificationTitle => '错过的闹钟';
|
||||
|
||||
@override
|
||||
String alarmMissedNotificationText(Object name) {
|
||||
return '$name已在10分钟后自动静音。';
|
||||
}
|
||||
|
||||
@override
|
||||
String get pauseAction => '暂停';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user