fix(alarms): harden native alarm lifecycle
This commit is contained in:
@@ -222,6 +222,7 @@ class AlarmScheduler(private val context: Context) {
|
||||
fun onAlarmFired(id: String) {
|
||||
val spec = readSpec(id) ?: return
|
||||
val firedAt = spec.snoozeOriginMillis ?: spec.triggerAtMillis
|
||||
saveHandledOccurrence(id, firedAt)
|
||||
val next = spec.copy(
|
||||
snoozeUntilMillis = null,
|
||||
snoozeOriginMillis = null,
|
||||
@@ -288,6 +289,7 @@ class AlarmScheduler(private val context: Context) {
|
||||
fun cancelAlarm(id: String) {
|
||||
Log.d(tag, "alarm.cancel id=$id")
|
||||
removeScheduledAlarm(id)
|
||||
removeHandledOccurrence(id)
|
||||
cancelPending("fire", pendingFireIntent(id, PendingIntent.FLAG_NO_CREATE))
|
||||
cancelPending("show", pendingShowIntent(id, PendingIntent.FLAG_NO_CREATE))
|
||||
cancelPending("preNotice", pendingPreNoticeIntent(id, PendingIntent.FLAG_NO_CREATE))
|
||||
@@ -325,6 +327,18 @@ class AlarmScheduler(private val context: Context) {
|
||||
fun pendingAlarmCount(): Int =
|
||||
prefs().getStringSet(KEY_IDS, emptySet()).orEmpty().size
|
||||
|
||||
fun handledOccurrences(): List<Map<String, Any>> =
|
||||
prefs().getStringSet(KEY_HANDLED_IDS, emptySet()).orEmpty()
|
||||
.mapNotNull { id ->
|
||||
val handledAt = prefs().getLong("$KEY_HANDLED_PREFIX$id", 0L)
|
||||
.takeIf { it > 0L }
|
||||
?: return@mapNotNull null
|
||||
mapOf(
|
||||
"alarmId" to id,
|
||||
"handledAtMillis" to handledAt
|
||||
)
|
||||
}
|
||||
|
||||
private fun preserveNativeSnooze(
|
||||
existing: NativeAlarmSpec?,
|
||||
requestedTriggerAtMillis: Long,
|
||||
@@ -438,6 +452,24 @@ class AlarmScheduler(private val context: Context) {
|
||||
.apply()
|
||||
}
|
||||
|
||||
private fun saveHandledOccurrence(id: String, handledAtMillis: Long) {
|
||||
val ids = prefs().getStringSet(KEY_HANDLED_IDS, emptySet()).orEmpty().toMutableSet()
|
||||
ids.add(id)
|
||||
prefs().edit()
|
||||
.putStringSet(KEY_HANDLED_IDS, ids)
|
||||
.putLong("$KEY_HANDLED_PREFIX$id", handledAtMillis)
|
||||
.apply()
|
||||
}
|
||||
|
||||
private fun removeHandledOccurrence(id: String) {
|
||||
val ids = prefs().getStringSet(KEY_HANDLED_IDS, emptySet()).orEmpty().toMutableSet()
|
||||
ids.remove(id)
|
||||
prefs().edit()
|
||||
.putStringSet(KEY_HANDLED_IDS, ids)
|
||||
.remove("$KEY_HANDLED_PREFIX$id")
|
||||
.apply()
|
||||
}
|
||||
|
||||
private fun prefs() =
|
||||
appContext.createDeviceProtectedStorageContext()
|
||||
.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
@@ -619,6 +651,8 @@ class AlarmScheduler(private val context: Context) {
|
||||
private const val PREFS = "pluriwave_alarm_scheduler"
|
||||
private const val KEY_IDS = "scheduled_alarm_ids"
|
||||
private const val KEY_ALARM_PREFIX = "scheduled_alarm_"
|
||||
private const val KEY_HANDLED_IDS = "handled_alarm_ids"
|
||||
private const val KEY_HANDLED_PREFIX = "handled_alarm_"
|
||||
private const val PRE_NOTICE_MILLIS = 30 * 60 * 1000L
|
||||
private const val SCHEDULE_UNICA = "unica"
|
||||
private const val SCHEDULE_DIAS_SEMANA = "diasSemana"
|
||||
|
||||
@@ -178,6 +178,10 @@ class MainActivity : AudioServiceActivity() {
|
||||
result.success(payload)
|
||||
intent?.removeExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ACTION)
|
||||
}
|
||||
"getHandledAlarmOccurrences" -> {
|
||||
Log.d(tag, "alarm.channel getHandledAlarmOccurrences")
|
||||
result.success(alarmScheduler.handledOccurrences())
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,11 +112,13 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
|
||||
.setContentText(title)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
.setContentIntent(fullScreenIntent)
|
||||
.setFullScreenIntent(fullScreenIntent, true)
|
||||
.addAction(0, "Posponer $snoozeMinutes min", snoozePendingIntent(context, alarmId, snoozeMinutes))
|
||||
.addAction(0, "Detener", stopPendingIntent(context, alarmId))
|
||||
.build()
|
||||
|
||||
try {
|
||||
@@ -250,6 +252,17 @@ class PluriWaveAlarmReceiver : BroadcastReceiver() {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
private fun stopPendingIntent(context: Context, alarmId: String): PendingIntent =
|
||||
PendingIntent.getService(
|
||||
context,
|
||||
requestCode(alarmId, 40),
|
||||
Intent(context, PluriWaveAlarmService::class.java).apply {
|
||||
action = PluriWaveAlarmService.ACTION_STOP
|
||||
putExtra(EXTRA_ALARM_ID, alarmId)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val TAG = "PluriWave"
|
||||
const val CHANNEL_ID = "pluriwave_alarm_pre_notice"
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.AudioAttributes
|
||||
import android.media.MediaPlayer
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
@@ -119,7 +120,11 @@ class PluriWaveAlarmService : Service() {
|
||||
setAudioAttributes(alarmAudioAttributes())
|
||||
isLooping = false
|
||||
setVolume(volume, volume)
|
||||
setDataSource(stationUrl)
|
||||
setDataSource(
|
||||
this@PluriWaveAlarmService,
|
||||
Uri.parse(stationUrl),
|
||||
mapOf("User-Agent" to "PluriWave/0.1.0 (native alarm)")
|
||||
)
|
||||
setOnPreparedListener {
|
||||
if (activeAlarmId != alarmId) return@setOnPreparedListener
|
||||
cancelStationFallback()
|
||||
@@ -256,6 +261,7 @@ class PluriWaveAlarmService : Service() {
|
||||
)
|
||||
.setCategory(NotificationCompat.CATEGORY_ALARM)
|
||||
.setPriority(NotificationCompat.PRIORITY_MAX)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setOngoing(true)
|
||||
.setAutoCancel(false)
|
||||
.setFullScreenIntent(openAlarmPendingIntent(alarmId, title, snoozeMinutes), true)
|
||||
@@ -367,7 +373,7 @@ class PluriWaveAlarmService : Service() {
|
||||
private const val TAG = "PluriWave"
|
||||
private const val CHANNEL_ID = "pluriwave_alarm_native"
|
||||
private const val NOTIFICATION_ID = 92841
|
||||
private const val ACTION_STOP = "es.freetimelab.pluriwave.alarm.STOP_NATIVE"
|
||||
const val ACTION_STOP = "es.freetimelab.pluriwave.alarm.STOP_NATIVE"
|
||||
const val ACTION_SNOOZE = "es.freetimelab.pluriwave.alarm.SNOOZE_NATIVE"
|
||||
const val EXTRA_SNOOZE_MINUTES = "snoozeMinutes"
|
||||
private const val STATION_START_TIMEOUT_MILLIS = 15_000L
|
||||
@@ -392,10 +398,15 @@ class PluriWaveAlarmService : Service() {
|
||||
putExtra(PluriWaveAlarmReceiver.EXTRA_ALARM_ID, alarmId)
|
||||
}
|
||||
try {
|
||||
context.stopService(intent)
|
||||
Log.d(TAG, "alarm.service stop requested id=$alarmId")
|
||||
context.startService(intent)
|
||||
Log.d(TAG, "alarm.service stop action requested id=$alarmId")
|
||||
} catch (error: Throwable) {
|
||||
Log.e(TAG, "alarm.service stop request failed id=$alarmId", error)
|
||||
try {
|
||||
context.stopService(intent)
|
||||
} catch (fallbackError: Throwable) {
|
||||
Log.e(TAG, "alarm.service stop fallback failed id=$alarmId", fallbackError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user