fix(alarm): unfreeze snooze modal and add per-minute snooze countdown
The snooze button was fire-and-forget without error handling: if
posponerAlarma threw (e.g. native scheduleAlarm returns false on a
device without exact-alarm permission), _dismissScreen never ran. The
stuck modal also kept _alarmaSonandoActiva true, which made the next
ring get ignored. _posponer/_detener now dismiss in a finally and stop
audio defensively.
Add a native, AlarmManager-driven snooze countdown notification that
re-posts every minute ("Rings in N min", 3->2->1) while the engine is
dead. scheduleSpec drives scheduleSnoozeCountdown for snoozes (instead
of the 30-min pre-notice). Localized text and button labels travel
Dart->Kotlin as {minutes} templates, same pattern as preNoticeTemplate.
Notification actions: "snooze again" (snoozeAgain, anchored to now)
reports back via the existing snoozed event; "stop" (cancelSnooze)
records a handled occurrence for cold-start reconciliation and emits a
new snoozeCancelled event handled in EstadoAlarmas.
Adds snoozeCountdown/snoozeAgainAction keys across all 13 locales and a
test for the snoozeCancelled event. Kotlin changes are static-reviewed
only; no Android build environment available here.
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
package es.freetimelab.pluriwave
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
@@ -40,7 +43,10 @@ class AlarmScheduler(private val context: Context) {
|
||||
fallbackStationName: String? = null,
|
||||
fallbackStationUrl: String? = null,
|
||||
fadeInSegundos: Int = 0,
|
||||
preNoticeTemplate: String? = null
|
||||
preNoticeTemplate: String? = null,
|
||||
snoozeCountdownTemplate: String? = null,
|
||||
snoozeAgainLabel: String? = null,
|
||||
snoozeStopLabel: String? = null
|
||||
): Boolean {
|
||||
val existing = readSpec(id)
|
||||
val preservedSnooze = preserveNativeSnooze(
|
||||
@@ -72,7 +78,10 @@ class AlarmScheduler(private val context: Context) {
|
||||
volume = volume.coerceIn(0f, 1f),
|
||||
fadeInSegundos = fadeInSegundos.coerceIn(0, 60),
|
||||
timezoneId = TimeZone.getDefault().id,
|
||||
preNoticeTemplate = preNoticeTemplate
|
||||
preNoticeTemplate = preNoticeTemplate,
|
||||
snoozeCountdownTemplate = snoozeCountdownTemplate,
|
||||
snoozeAgainLabel = snoozeAgainLabel,
|
||||
snoozeStopLabel = snoozeStopLabel
|
||||
)
|
||||
return scheduleSpec(spec, persistOnSuccess = true)
|
||||
}
|
||||
@@ -118,7 +127,19 @@ class AlarmScheduler(private val context: Context) {
|
||||
if (persistOnSuccess) {
|
||||
saveScheduledAlarm(scheduledSpec)
|
||||
}
|
||||
schedulePreNotice(scheduledSpec)
|
||||
if (scheduledSpec.snoozeUntilMillis != null) {
|
||||
// Snoozed: drive the per-minute countdown notification instead of
|
||||
// the 30-minute pre-notice (which makes no sense for a short snooze).
|
||||
scheduleSnoozeCountdown(scheduledSpec)
|
||||
} else {
|
||||
// Normal occurrence: make sure no stale snooze countdown survives a
|
||||
// transition from snoozed -> normal, then arm the pre-notice.
|
||||
cancelSnoozeCountdown(scheduledSpec.id)
|
||||
NotificationManagerCompat.from(appContext).cancel(
|
||||
PluriWaveAlarmReceiver.notificationIdForAlarm(scheduledSpec.id)
|
||||
)
|
||||
schedulePreNotice(scheduledSpec)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -231,6 +252,12 @@ class AlarmScheduler(private val context: Context) {
|
||||
|
||||
fun onAlarmFired(id: String) {
|
||||
val spec = readSpec(id) ?: return
|
||||
// The alarm is ringing now: tear down any snooze countdown so its
|
||||
// notification and pending minute ticks don't outlive the ring.
|
||||
cancelSnoozeCountdown(id)
|
||||
NotificationManagerCompat.from(appContext).cancel(
|
||||
PluriWaveAlarmReceiver.notificationIdForAlarm(id)
|
||||
)
|
||||
val firedAt = spec.snoozeOriginMillis ?: spec.triggerAtMillis
|
||||
saveHandledOccurrence(id, firedAt)
|
||||
val next = spec.copy(
|
||||
@@ -315,6 +342,227 @@ class AlarmScheduler(private val context: Context) {
|
||||
return occurrenceAt
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-snoozes an already-snoozed alarm by [minutes] more from NOW (anchored
|
||||
* to now, unlike [snooze]/[postponeNext] which anchor to the occurrence).
|
||||
* Invoked from the "snooze again" action on the countdown notification.
|
||||
* Returns the new snooze so the caller can report it back to Flutter.
|
||||
*/
|
||||
fun snoozeAgain(id: String, minutes: Int): NativeSnoozeResult? {
|
||||
val spec = readSpec(id) ?: return null
|
||||
val safeMinutes = sanitizeSnoozeMinutes(minutes)
|
||||
val now = System.currentTimeMillis()
|
||||
val snoozeUntil = now + safeMinutes * 60_000L
|
||||
Log.d(tag, "alarm.snoozeAgain id=$id minutes=$safeMinutes until=$snoozeUntil")
|
||||
scheduleSpec(
|
||||
spec.copy(
|
||||
snoozeUntilMillis = snoozeUntil,
|
||||
snoozeOriginMillis = now,
|
||||
snoozeMinutes = safeMinutes
|
||||
),
|
||||
persistOnSuccess = true
|
||||
)
|
||||
return NativeSnoozeResult(
|
||||
snoozeUntilMillis = snoozeUntil,
|
||||
occurrenceAtMillis = now,
|
||||
title = spec.title
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels an active snooze ("stop" from the countdown notification): drops
|
||||
* the pending ring, advances to the next normal occurrence (or cancels a
|
||||
* one-shot) and records the occurrence as handled so the Flutter cold-start
|
||||
* sync reconciles the cleared snooze. Returns the occurrence consumed, or
|
||||
* null if the spec is gone.
|
||||
*/
|
||||
fun cancelSnooze(id: String): Long? {
|
||||
val spec = readSpec(id) ?: return null
|
||||
val snoozeUntil = spec.snoozeUntilMillis ?: return null
|
||||
val occurrence = spec.snoozeOriginMillis ?: spec.triggerAtMillis
|
||||
// handledAt must be >= the snooze ring time so Flutter's occurrence sync
|
||||
// does not treat the still-future snooze as pending and advances past it.
|
||||
val handledAt = maxOf(occurrence, snoozeUntil)
|
||||
Log.d(tag, "alarm.cancelSnooze id=$id occurrence=$occurrence handledAt=$handledAt")
|
||||
cancelSnoozeCountdown(id)
|
||||
NotificationManagerCompat.from(appContext).cancel(
|
||||
PluriWaveAlarmReceiver.notificationIdForAlarm(id)
|
||||
)
|
||||
saveHandledOccurrence(id, handledAt)
|
||||
val next = spec.copy(
|
||||
snoozeUntilMillis = null,
|
||||
snoozeOriginMillis = null,
|
||||
lastHandledAtMillis = handledAt,
|
||||
enabled = spec.scheduleType != SCHEDULE_UNICA
|
||||
)
|
||||
if (next.enabled) {
|
||||
scheduleSpec(next, persistOnSuccess = true)
|
||||
} else {
|
||||
removeScheduledAlarm(id)
|
||||
cancelPending("fire", pendingFireIntent(id, PendingIntent.FLAG_NO_CREATE))
|
||||
cancelPending("show", pendingShowIntent(id, PendingIntent.FLAG_NO_CREATE))
|
||||
}
|
||||
return occurrence
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-posts the snooze countdown notification with the up-to-date remaining
|
||||
* minutes and arms the next minute tick. Driven by ACTION_SNOOZE_COUNTDOWN
|
||||
* alarms (so it keeps decrementing while the engine is dead).
|
||||
*/
|
||||
fun handleSnoozeCountdownTick(id: String) {
|
||||
val spec = readSpec(id)
|
||||
val snoozeUntil = spec?.snoozeUntilMillis
|
||||
if (spec == null || snoozeUntil == null) {
|
||||
cancelSnoozeCountdown(id)
|
||||
return
|
||||
}
|
||||
val now = System.currentTimeMillis()
|
||||
if (snoozeUntil <= now) {
|
||||
// The ring is due; the fire broadcast posts the alarm UI and
|
||||
// onAlarmFired clears this notification.
|
||||
cancelSnoozeCountdown(id)
|
||||
return
|
||||
}
|
||||
val remaining = ceilMinutes(snoozeUntil - now)
|
||||
postSnoozeCountdownNotification(spec, remaining)
|
||||
armNextSnoozeCountdownTick(spec, remaining)
|
||||
}
|
||||
|
||||
private fun scheduleSnoozeCountdown(spec: NativeAlarmSpec) {
|
||||
val snoozeUntil = spec.snoozeUntilMillis ?: return
|
||||
val now = System.currentTimeMillis()
|
||||
if (snoozeUntil <= now) {
|
||||
cancelSnoozeCountdown(spec.id)
|
||||
return
|
||||
}
|
||||
val remaining = ceilMinutes(snoozeUntil - now)
|
||||
postSnoozeCountdownNotification(spec, remaining)
|
||||
armNextSnoozeCountdownTick(spec, remaining)
|
||||
Log.d(tag, "alarm.snoozeCountdown started id=${spec.id} remaining=$remaining until=$snoozeUntil")
|
||||
}
|
||||
|
||||
private fun armNextSnoozeCountdownTick(spec: NativeAlarmSpec, remaining: Long) {
|
||||
val snoozeUntil = spec.snoozeUntilMillis ?: return
|
||||
// On the final minute the fire alarm takes over and cancels the
|
||||
// countdown, so there is no further tick to schedule.
|
||||
if (remaining <= 1L) return
|
||||
val nextBoundary = snoozeUntil - (remaining - 1L) * 60_000L
|
||||
val pending = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode(spec.id, 8),
|
||||
Intent(appContext, PluriWaveAlarmReceiver::class.java).apply {
|
||||
action = PluriWaveAlarmReceiver.ACTION_SNOOZE_COUNTDOWN
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID, spec.id)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
try {
|
||||
alarmManager.setExactAndAllowWhileIdle(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
nextBoundary,
|
||||
pending
|
||||
)
|
||||
Log.d(tag, "alarm.snoozeCountdown next tick id=${spec.id} at=$nextBoundary")
|
||||
} catch (error: SecurityException) {
|
||||
Log.w(tag, "alarm.snoozeCountdown tick SecurityException id=${spec.id}", error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelSnoozeCountdown(id: String) {
|
||||
val pending = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode(id, 8),
|
||||
Intent(appContext, PluriWaveAlarmReceiver::class.java).apply {
|
||||
action = PluriWaveAlarmReceiver.ACTION_SNOOZE_COUNTDOWN
|
||||
},
|
||||
PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
cancelPending("snoozeCountdown", pending)
|
||||
}
|
||||
|
||||
private fun postSnoozeCountdownNotification(spec: NativeAlarmSpec, remaining: Long) {
|
||||
ensurePreNoticeChannel()
|
||||
val text = if (spec.snoozeCountdownTemplate.isNullOrBlank()) {
|
||||
"Rings in $remaining min"
|
||||
} else {
|
||||
spec.snoozeCountdownTemplate.replace("{minutes}", remaining.toString())
|
||||
}
|
||||
|
||||
val openIntent = PendingIntent.getActivity(
|
||||
appContext,
|
||||
requestCode(spec.id, 5),
|
||||
Intent(appContext, MainActivity::class.java).apply {
|
||||
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID, spec.id)
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_TITLE, spec.title)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val againIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode(spec.id, 6),
|
||||
Intent(appContext, PluriWaveAlarmReceiver::class.java).apply {
|
||||
action = PluriWaveAlarmReceiver.ACTION_SNOOZE_AGAIN
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID, spec.id)
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_TITLE, spec.title)
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_SNOOZE_MINUTES, spec.snoozeMinutes)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val stopIntent = PendingIntent.getBroadcast(
|
||||
appContext,
|
||||
requestCode(spec.id, 7),
|
||||
Intent(appContext, PluriWaveAlarmReceiver::class.java).apply {
|
||||
action = PluriWaveAlarmReceiver.ACTION_CANCEL_SNOOZE
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID, spec.id)
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_TITLE, spec.title)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val notification = NotificationCompat.Builder(appContext, PluriWaveAlarmReceiver.CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_lock_idle_alarm)
|
||||
.setContentTitle(spec.title)
|
||||
.setContentText(text)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(NotificationCompat.CATEGORY_REMINDER)
|
||||
.setSilent(true)
|
||||
.setOnlyAlertOnce(true)
|
||||
.setOngoing(true)
|
||||
.setContentIntent(openIntent)
|
||||
.addAction(0, spec.snoozeAgainLabel ?: "Snooze again", againIntent)
|
||||
.addAction(0, spec.snoozeStopLabel ?: "Stop", stopIntent)
|
||||
.build()
|
||||
|
||||
try {
|
||||
NotificationManagerCompat.from(appContext).notify(
|
||||
PluriWaveAlarmReceiver.notificationIdForAlarm(spec.id),
|
||||
notification
|
||||
)
|
||||
} catch (error: SecurityException) {
|
||||
Log.e(tag, "alarm.snoozeCountdown notify SecurityException id=${spec.id}", error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensurePreNoticeChannel() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
|
||||
val manager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
if (manager.getNotificationChannel(PluriWaveAlarmReceiver.CHANNEL_ID) != null) return
|
||||
val channel = NotificationChannel(
|
||||
PluriWaveAlarmReceiver.CHANNEL_ID,
|
||||
"Preavisos de alarmas",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
setSound(null, null)
|
||||
enableVibration(false)
|
||||
}
|
||||
manager.createNotificationChannel(channel)
|
||||
}
|
||||
|
||||
private fun ceilMinutes(deltaMillis: Long): Long =
|
||||
maxOf(1L, (deltaMillis + 59_999L) / 60_000L)
|
||||
|
||||
fun cancelAlarm(id: String) {
|
||||
Log.d(tag, "alarm.cancel id=$id")
|
||||
removeScheduledAlarm(id)
|
||||
@@ -322,6 +570,7 @@ class AlarmScheduler(private val context: Context) {
|
||||
cancelPending("fire", pendingFireIntent(id, PendingIntent.FLAG_NO_CREATE))
|
||||
cancelPending("show", pendingShowIntent(id, PendingIntent.FLAG_NO_CREATE))
|
||||
cancelPending("preNotice", pendingPreNoticeIntent(id, PendingIntent.FLAG_NO_CREATE))
|
||||
cancelSnoozeCountdown(id)
|
||||
NotificationManagerCompat.from(appContext).cancel(
|
||||
PluriWaveAlarmReceiver.notificationIdForAlarm(id)
|
||||
)
|
||||
@@ -659,7 +908,12 @@ class AlarmScheduler(private val context: Context) {
|
||||
val timezoneId: String,
|
||||
// Nullable for backward compat: old persisted alarms without this field
|
||||
// fall back to the English default in the receiver. Schema stays v3.
|
||||
val preNoticeTemplate: String? = null
|
||||
val preNoticeTemplate: String? = null,
|
||||
// Localized template + button labels for the per-minute snooze countdown
|
||||
// notification. Nullable so pre-existing persisted alarms keep working.
|
||||
val snoozeCountdownTemplate: String? = null,
|
||||
val snoozeAgainLabel: String? = null,
|
||||
val snoozeStopLabel: String? = null
|
||||
) {
|
||||
fun toJson(): JSONObject = JSONObject().apply {
|
||||
put("schemaVersion", 3)
|
||||
@@ -687,6 +941,9 @@ class AlarmScheduler(private val context: Context) {
|
||||
put("fadeInSegundos", fadeInSegundos)
|
||||
put("timezoneId", timezoneId)
|
||||
put("preNoticeTemplate", preNoticeTemplate)
|
||||
put("snoozeCountdownTemplate", snoozeCountdownTemplate)
|
||||
put("snoozeAgainLabel", snoozeAgainLabel)
|
||||
put("snoozeStopLabel", snoozeStopLabel)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -724,7 +981,11 @@ class AlarmScheduler(private val context: Context) {
|
||||
volume = json.optDouble("volume", 0.85).toFloat(),
|
||||
fadeInSegundos = json.optInt("fadeInSegundos", 0).coerceIn(0, 60),
|
||||
timezoneId = json.optString("timezoneId", TimeZone.getDefault().id),
|
||||
preNoticeTemplate = json.optString("preNoticeTemplate").takeIf { it.isNotBlank() }
|
||||
preNoticeTemplate = json.optString("preNoticeTemplate").takeIf { it.isNotBlank() },
|
||||
snoozeCountdownTemplate =
|
||||
json.optString("snoozeCountdownTemplate").takeIf { it.isNotBlank() },
|
||||
snoozeAgainLabel = json.optString("snoozeAgainLabel").takeIf { it.isNotBlank() },
|
||||
snoozeStopLabel = json.optString("snoozeStopLabel").takeIf { it.isNotBlank() }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,10 @@ class MainActivity : AudioServiceActivity() {
|
||||
fallbackStationName = call.argument<String>("fallbackStationName"),
|
||||
fallbackStationUrl = call.argument<String>("fallbackStationUrl"),
|
||||
fadeInSegundos = call.argument<Int>("fadeInSegundos") ?: 0,
|
||||
preNoticeTemplate = call.argument<String>("preNoticeTemplate")
|
||||
preNoticeTemplate = call.argument<String>("preNoticeTemplate"),
|
||||
snoozeCountdownTemplate = call.argument<String>("snoozeCountdownTemplate"),
|
||||
snoozeAgainLabel = call.argument<String>("snoozeAgainLabel"),
|
||||
snoozeStopLabel = call.argument<String>("snoozeStopLabel")
|
||||
)
|
||||
result.success(scheduled)
|
||||
}
|
||||
@@ -767,6 +770,9 @@ class MainActivity : AudioServiceActivity() {
|
||||
/** alarmAction reported when the native service snoozed by itself. */
|
||||
const val ALARM_ACTION_SNOOZED = "snoozed"
|
||||
|
||||
/** alarmAction reported when a pending snooze was cancelled natively. */
|
||||
const val ALARM_ACTION_SNOOZE_CANCELLED = "snoozeCancelled"
|
||||
|
||||
@Volatile
|
||||
private var activeInstance: MainActivity? = null
|
||||
|
||||
|
||||
@@ -91,6 +91,40 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
|
||||
Log.e(TAG, "alarm.receiver skipNext startActivity ERROR id=$alarmId", error)
|
||||
}
|
||||
}
|
||||
ACTION_SNOOZE_COUNTDOWN -> {
|
||||
AlarmScheduler(context).handleSnoozeCountdownTick(alarmId)
|
||||
}
|
||||
ACTION_SNOOZE_AGAIN -> {
|
||||
val snoozed = AlarmScheduler(context).snoozeAgain(alarmId, snoozeMinutes)
|
||||
if (snoozed != null) {
|
||||
// Reuses the existing native-snooze event so Flutter records
|
||||
// the new snooze (live) and the cold-start sync imports it.
|
||||
MainActivity.notifyAlarmEvent(
|
||||
mapOf(
|
||||
"alarmId" to alarmId,
|
||||
"alarmTitle" to snoozed.title,
|
||||
"alarmAction" to MainActivity.ALARM_ACTION_SNOOZED,
|
||||
"occurrenceAtMillis" to snoozed.occurrenceAtMillis,
|
||||
"snoozeUntilMillis" to snoozed.snoozeUntilMillis,
|
||||
"snoozeMinutes" to snoozeMinutes
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
ACTION_CANCEL_SNOOZE -> {
|
||||
val occurrence = AlarmScheduler(context).cancelSnooze(alarmId)
|
||||
NotificationManagerCompat.from(context).cancel(notificationIdForAlarm(alarmId))
|
||||
if (occurrence != null) {
|
||||
MainActivity.notifyAlarmEvent(
|
||||
mapOf(
|
||||
"alarmId" to alarmId,
|
||||
"alarmTitle" to title,
|
||||
"alarmAction" to MainActivity.ALARM_ACTION_SNOOZE_CANCELLED,
|
||||
"occurrenceAtMillis" to occurrence
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Log.w(TAG, "alarm.receiver unknown action=${intent.action} id=$alarmId")
|
||||
}
|
||||
}
|
||||
@@ -216,6 +250,9 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
|
||||
const val ACTION_PRE_NOTICE = "es.freetimelab.pluriwave.alarm.PRE_NOTICE"
|
||||
const val ACTION_SKIP_NEXT = "es.freetimelab.pluriwave.alarm.SKIP_NEXT"
|
||||
const val ACTION_POSTPONE_NEXT = "es.freetimelab.pluriwave.alarm.POSTPONE_NEXT"
|
||||
const val ACTION_SNOOZE_COUNTDOWN = "es.freetimelab.pluriwave.alarm.SNOOZE_COUNTDOWN"
|
||||
const val ACTION_SNOOZE_AGAIN = "es.freetimelab.pluriwave.alarm.SNOOZE_AGAIN"
|
||||
const val ACTION_CANCEL_SNOOZE = "es.freetimelab.pluriwave.alarm.CANCEL_SNOOZE"
|
||||
const val EXTRA_ALARM_ID = "alarmId"
|
||||
const val EXTRA_ALARM_TITLE = "alarmTitle"
|
||||
const val EXTRA_ALARM_ACTION = "alarmAction"
|
||||
|
||||
Reference in New Issue
Block a user