Skip to content

Commit

Permalink
Merge pull request #14 from ReneeVandervelde/alarm-ids
Browse files Browse the repository at this point in the history
Use alarm ID in broadcast intent request
  • Loading branch information
ReneeVandervelde authored Feb 20, 2024
2 parents d563896 + dd5bb7f commit 7f7c6d9
Show file tree
Hide file tree
Showing 11 changed files with 52 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import android.content.Context
import android.content.Intent
import android.os.IBinder
import com.inkapplications.sleeps.android.SleepApplication
import com.inkapplications.sleeps.state.alarms.AlarmId
import com.inkapplications.sleeps.state.alarms.AlarmType
import kimchi.Kimchi
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
Expand All @@ -23,7 +23,7 @@ private const val AlarmIdExtra = "alarm.id"
class AlarmService: Service() {
private var job: Job? = Job()

private val Intent.alarmId get() = getStringExtra(AlarmIdExtra)!!.let(::AlarmId)
private val Intent.alarmId get() = getStringExtra(AlarmIdExtra).let(AlarmType::findById)

override fun onBind(intent: Intent): IBinder? = null

Expand All @@ -37,12 +37,12 @@ class AlarmService: Service() {
return super.onStartCommand(intent, flags, startId)
}

private fun start(alarmId: AlarmId) = with (SleepApplication.module) {
private fun start(alarm: AlarmType) = with (SleepApplication.module) {
job?.cancel()
startForeground(NotificationId, notifications.createAlarmNotification())
job = backgroundScope.launch {
beeper.prepare()
alarmExecutor.onStartAlarm(alarmId)
alarmExecutor.onStartAlarm(alarm)
}
}

Expand All @@ -69,8 +69,8 @@ fun Context.createStopAlarmServicePendingIntent(): PendingIntent = PendingIntent
* Create an intent that can be used to start the alarm service.
*/
fun Context.createStartAlarmServiceIntent(
id: AlarmId,
alarm: AlarmType,
): Intent = Intent(this, AlarmService::class.java).apply {
action = StartAction
putExtra(AlarmIdExtra, id.value)
putExtra(AlarmIdExtra, alarm.id)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import android.content.Context
import android.os.Build
import com.inkapplications.sleeps.android.createEditAlarmPendingIntent
import com.inkapplications.sleeps.state.alarms.AlarmAccess
import com.inkapplications.sleeps.state.alarms.AlarmId
import com.inkapplications.sleeps.state.alarms.AlarmType
import kimchi.Kimchi
import kotlinx.datetime.Instant

Expand All @@ -16,7 +16,7 @@ class AndroidAlarmAccess(
private val context: Context,
private val alarmManager: AlarmManager,
): AlarmAccess {
override fun addAlarm(id: AlarmId, time: Instant) {
override fun addAlarm(id: AlarmType, time: Instant) {
Kimchi.info("Adding Alarm $id at $time")
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || !alarmManager.canScheduleExactAlarms()) {
Kimchi.error("Unable to schedule Exact Alarms")
Expand All @@ -31,7 +31,7 @@ class AndroidAlarmAccess(
)
}

override fun removeAlarm(id: AlarmId) {
override fun removeAlarm(id: AlarmType) {
Kimchi.info("Removing Alarm $id")
alarmManager.cancel(
context.createAlarmBroadcastPendingIntent(id),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,33 @@ import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import com.inkapplications.sleeps.state.alarms.AlarmId
import com.inkapplications.sleeps.state.alarms.AlarmType
import kimchi.Kimchi

private const val AlarmBroadcastIntentId = 7876
private const val AlarmIdExtra = "alarm.id"

/**
* Receiver registered to be called when an alarm goes off.
*/
class AndroidAlarmReceiver: BroadcastReceiver() {
private val Intent.alarmId get() = getStringExtra(AlarmIdExtra)!!.let(::AlarmId)
private val Intent.alarmType get() = getStringExtra(AlarmIdExtra).let(AlarmType::findById)

override fun onReceive(context: Context, intent: Intent) {
Kimchi.info("Alarm Received")
context.startService(context.createStartAlarmServiceIntent(intent.alarmId))
context.startService(context.createStartAlarmServiceIntent(intent.alarmType))
}
}

/**
* Create a pending intent used to broadcast an alarm.
*/
fun Context.createAlarmBroadcastPendingIntent(
id: AlarmId,
alarm: AlarmType,
): PendingIntent = PendingIntent.getBroadcast(
this,
AlarmBroadcastIntentId,
alarm.hashCode(),
Intent(this, AndroidAlarmReceiver::class.java).apply {
putExtra(AlarmIdExtra, id.value)
putExtra(AlarmIdExtra, alarm.id)
},
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ interface AlarmAccess {
/**
* Add a new alarm to be invoked at the given [time].
*/
fun addAlarm(id: AlarmId, time: Instant)
fun addAlarm(id: AlarmType, time: Instant)

/**
* Remove a specific alam by its ID used when the alarm was added.
*/
fun removeAlarm(id: AlarmId)
fun removeAlarm(id: AlarmType)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ interface AlarmController {
/**
* Start any operations that should be performed when an alarm goes off.
*/
suspend fun onStartAlarm(id: AlarmId)
suspend fun onStartAlarm(id: AlarmType)
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ internal class AlarmScheduler(
private val clock: Clock,
private val logger: KimchiLogger,
): Daemon, DeviceBootController {
private val wakeAlarm = AlarmId("wake")
private val sleepAlarm = AlarmId("sleep")

private val alarmParameters = combine(
scheduleAccess.schedule,
notificationSettings.notificationsState,
Expand All @@ -52,19 +49,19 @@ internal class AlarmScheduler(

if (alarmParameters.settings.wakeAlarm) {
logger.trace("Scheduling Wake alarm for ${alarmParameters.schedule.wake}")
alarmAccess.addAlarm(wakeAlarm, alarmParameters.schedule.wake.instant)
alarmAccess.addAlarm(AlarmType.Wake, alarmParameters.schedule.wake.instant)
} else {
logger.trace("Wake alarm disabled")
alarmAccess.removeAlarm(wakeAlarm)
alarmAccess.removeAlarm(AlarmType.Wake)
}

when {
!alarmParameters.settings.sleepNotifications -> {
logger.trace("Sleep alarm disabled")
alarmAccess.removeAlarm(sleepAlarm)
alarmAccess.removeAlarm(AlarmType.Sleep)
}
clock.now() > alarmParameters.schedule.sleep.instant -> logger.trace("Sleep alarm time has passed")
else -> alarmAccess.addAlarm(sleepAlarm, alarmParameters.schedule.sleep.instant)
else -> alarmAccess.addAlarm(AlarmType.Sleep, alarmParameters.schedule.sleep.instant)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.inkapplications.sleeps.state.alarms

/**
* Identifies a scheduled alarm.
*/
@JvmInline
value class AlarmType(val id: String) {
override fun toString(): String = "($id)"

companion object {
val Wake = AlarmType("wake")
val Sleep = AlarmType("sleep")

fun findById(key: String?): AlarmType = when (key) {
"wake" -> Wake
"sleep" -> Sleep
else -> throw IllegalArgumentException("Unknown Alarm Key: $key")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ internal class BeepingAlarmController(
private val minimumDelay = 3.seconds
private val acceleration = 0.9

override suspend fun onStartAlarm(id: AlarmId) {
override suspend fun onStartAlarm(id: AlarmType) {
var current = initialDelay
while (currentCoroutineContext().isActive) {
beeper.play()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package com.inkapplications.sleeps.state.alarms
import kotlinx.datetime.Instant

class AlarmAccessSpy: AlarmAccess {
val addCalls = mutableListOf<Pair<AlarmId, Instant>>()
val clearCalls = mutableListOf<AlarmId>()
val addCalls = mutableListOf<Pair<AlarmType, Instant>>()
val clearCalls = mutableListOf<AlarmType>()

override fun addAlarm(id: AlarmId, time: Instant) {
override fun addAlarm(id: AlarmType, time: Instant) {
addCalls += id to time
}

override fun removeAlarm(id: AlarmId) {
override fun removeAlarm(id: AlarmType) {
clearCalls += id
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ class AlarmSchedulerTest {
assertEquals(0, alarmAccess.clearCalls.size, "Should not clear alarms")
assertEquals(2, alarmAccess.addCalls.size)

val wakeAlarm = alarmAccess.addCalls.find { it.first.value == "wake" }?.second
val wakeAlarm = alarmAccess.addCalls.find { it.first.id == "wake" }?.second
assertNotNull(wakeAlarm, "Wake alarm should be scheduled")
assertEquals(
LocalDateTime(2021, 1, 2, 7, 1).atZone(TimeZone.UTC).instant,
wakeAlarm,
"Alarm is scheduled"
)

val sleepAlarm = alarmAccess.addCalls.find { it.first.value == "sleep" }?.second
val sleepAlarm = alarmAccess.addCalls.find { it.first.id == "sleep" }?.second
assertNotNull(sleepAlarm, "Sleep alarm should be scheduled")
assertEquals(
LocalDateTime(2021, 1, 1, 20, 2).atZone(TimeZone.UTC).instant,
Expand Down Expand Up @@ -109,8 +109,8 @@ class AlarmSchedulerTest {
runCurrent()

assertEquals(2, alarmAccess.clearCalls.size, "Should clear existing alarms when disabled")
assertNotNull(alarmAccess.clearCalls.find { it.value == "wake" }, " Wake alarm should be cleared")
assertNotNull(alarmAccess.clearCalls.find { it.value == "sleep" }, " Sleep alarm should be cleared")
assertNotNull(alarmAccess.clearCalls.find { it.id == "wake" }, " Wake alarm should be cleared")
assertNotNull(alarmAccess.clearCalls.find { it.id == "sleep" }, " Sleep alarm should be cleared")
assertEquals(0, alarmAccess.addCalls.size, "Should not set any alarms when disabled")

job.cancel()
Expand Down Expand Up @@ -144,7 +144,7 @@ class AlarmSchedulerTest {
fakeScheduleAccess.schedule.emit(fakeSchedule)
runCurrent()

val sleepAlarms = alarmAccess.addCalls.count { it.first.value == "sleep" }
val sleepAlarms = alarmAccess.addCalls.count { it.first.id == "sleep" }
assertEquals(0, sleepAlarms, "No sleep alarms scheduled after initial time")

job.cancel()
Expand Down

0 comments on commit 7f7c6d9

Please sign in to comment.