haunt / commit
Restore light state after SIGKILL mid-blink
.../main/java/net/fuzzyworld/haunt/HauntService.kt | 38 ++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/app/src/main/java/net/fuzzyworld/haunt/HauntService.kt b/app/src/main/java/net/fuzzyworld/haunt/HauntService.kt index 734036a..755a6b7 100644 --- a/app/src/main/java/net/fuzzyworld/haunt/HauntService.kt +++ b/app/src/main/java/net/fuzzyworld/haunt/HauntService.kt @@ -7,6 +7,7 @@ import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent +import android.content.SharedPreferences import android.os.IBinder import android.os.PowerManager import androidx.core.app.NotificationCompat @@ -34,9 +35,11 @@ class HauntService : Service() { private val job = SupervisorJob() private val scope = CoroutineScope(Dispatchers.Default + job) private var wakeLock: PowerManager.WakeLock? = null + private lateinit var prefs: SharedPreferences override fun onCreate() { super.onCreate() + prefs = getSharedPreferences("haunt_prefs", Context.MODE_PRIVATE) createNotificationChannel() } @@ -45,6 +48,26 @@ class HauntService : Service() { wakeLock = (getSystemService(POWER_SERVICE) as PowerManager) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "haunt:ranging") .also { it.acquire(12 * 60 * 60 * 1000L) } + + // If the service was killed mid-blink, a pending restore entry will be in prefs. + // Clear it immediately (so a second restart doesn't try twice) then do the restore. + val pendingId = prefs.getString(PREF_RESTORE_ID, null) + if (pendingId != null) { + val wasOn = prefs.getBoolean(PREF_RESTORE_WAS_ON, false) + val isGroup = prefs.getBoolean(PREF_RESTORE_IS_GROUP, false) + prefs.edit().remove(PREF_RESTORE_ID).apply() + scope.launch(Dispatchers.IO) { + try { + val cfg = HueBridgeConfig(this@HauntService) + val ip = cfg.bridgeIp ?: return@launch + val u = cfg.username ?: return@launch + val restore = LightStateRequest(on = wasOn) + if (isGroup) HueBridgeClient(ip).setGroupState(u, pendingId, restore) + else HueBridgeClient(ip).setLightState(u, pendingId, restore) + } catch (_: Exception) {} + } + } + scope.launch { runHauntLoop() } return START_STICKY } @@ -98,6 +121,13 @@ class HauntService : Service() { else client.getLightState(username, point.hueLightId) } catch (_: Exception) { false } + // Persist before the first blink so a SIGKILL mid-cycle can be recovered on restart. + prefs.edit() + .putString(PREF_RESTORE_ID, point.hueLightId) + .putBoolean(PREF_RESTORE_WAS_ON, wasOn) + .putBoolean(PREF_RESTORE_IS_GROUP, point.isGroup) + .apply() + try { while (true) { try { @@ -127,6 +157,7 @@ class HauntService : Service() { if (point.isGroup) client.setGroupState(username, point.hueLightId, restore) else client.setLightState(username, point.hueLightId, restore) } catch (_: Exception) {} + prefs.edit().remove(PREF_RESTORE_ID).apply() } } } @@ -174,6 +205,13 @@ class HauntService : Service() { private const val CHANNEL_ID = "haunt_service" private const val HAUNT_THRESHOLD = 2.0 + // Keys used to persist light state across unexpected process death. + // Written before the first blink, cleared in the finally block on normal exit. + // If still present on next onStartCommand, the process was killed mid-blink. + private const val PREF_RESTORE_ID = "blink_restore_id" + private const val PREF_RESTORE_WAS_ON = "blink_restore_was_on" + private const val PREF_RESTORE_IS_GROUP = "blink_restore_is_group" + private val _hauntTarget = MutableStateFlow<String?>(null) val hauntTarget: StateFlow<String?> = _hauntTarget