fix(scene): schedule time triggers independently
This commit is contained in:
@@ -15,13 +15,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val notificationPermissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
||||
val canAskAgain = !granted && shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
|
||||
getSharedPreferences(NOTIFICATION_PERMISSION_STATE, MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(KEY_PERMISSION_REQUESTED, !canAskAgain)
|
||||
.apply()
|
||||
}
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -39,16 +33,6 @@ class MainActivity : ComponentActivity() {
|
||||
return
|
||||
}
|
||||
|
||||
val permissionState = getSharedPreferences(NOTIFICATION_PERMISSION_STATE, MODE_PRIVATE)
|
||||
val canAskAgain = shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
|
||||
if (permissionState.getBoolean(KEY_PERMISSION_REQUESTED, false) && !canAskAgain) return
|
||||
|
||||
permissionState.edit().putBoolean(KEY_PERMISSION_REQUESTED, true).apply()
|
||||
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val NOTIFICATION_PERMISSION_STATE = "notification_permission_state"
|
||||
const val KEY_PERMISSION_REQUESTED = "permission_requested"
|
||||
}
|
||||
}
|
||||
|
||||
+11
-9
@@ -121,23 +121,25 @@ class DefaultAutomationEngine(
|
||||
if (conditionEvaluator.evaluate(scene.conditions, snapshot, now) !is ConditionsEvaluation.Satisfied) {
|
||||
continue
|
||||
}
|
||||
var executionCount = if (newlyMatchedStateTriggers.isEmpty()) 0 else 1
|
||||
if (matchedTimeTriggers.isNotEmpty()) {
|
||||
var claimedTimeTrigger = false
|
||||
for ((trigger, occurrenceKey) in matchedTimeTriggers) {
|
||||
when (val claim = runtimeRepository.claimOccurrence(profileId, scene.id, trigger.id, occurrenceKey)) {
|
||||
is DomainResult.Failure -> return claim
|
||||
is DomainResult.Success -> if (claim.value) claimedTimeTrigger = true
|
||||
is DomainResult.Success -> if (claim.value) executionCount++
|
||||
}
|
||||
}
|
||||
if (!claimedTimeTrigger && newlyMatchedStateTriggers.isEmpty()) continue
|
||||
}
|
||||
if (executionCount == 0) continue
|
||||
|
||||
sceneExecutor.execute(
|
||||
scene = scene,
|
||||
executionId = SceneExecutionId("auto-${UUID.randomUUID()}"),
|
||||
source = SceneExecutionSource.AUTOMATION,
|
||||
).collect { update ->
|
||||
if (update is com.flagship.abox.manager.domain.SceneExecutionUpdate.Completed) executed++
|
||||
repeat(executionCount) {
|
||||
sceneExecutor.execute(
|
||||
scene = scene,
|
||||
executionId = SceneExecutionId("auto-${UUID.randomUUID()}"),
|
||||
source = SceneExecutionSource.AUTOMATION,
|
||||
).collect { update ->
|
||||
if (update is com.flagship.abox.manager.domain.SceneExecutionUpdate.Completed) executed++
|
||||
}
|
||||
}
|
||||
}
|
||||
return DomainResult.Success(executed)
|
||||
|
||||
+24
-25
@@ -24,51 +24,56 @@ class WorkManagerAutomationScheduleStore(context: Context) : AutomationScheduleS
|
||||
.setInputData(androidx.work.Data.Builder().putString(KEY_PROFILE_ID, profileId.value).build())
|
||||
.build()
|
||||
workManager.enqueueUniquePeriodicWork(workName(profileId), ExistingPeriodicWorkPolicy.UPDATE, request)
|
||||
scheduleNextTimeTrigger(profileId, scenes)
|
||||
workManager.cancelAllWorkByTag(timeWorkTag(profileId))
|
||||
scenes.forEach { scene ->
|
||||
scene.triggers.filterIsInstance<SceneTrigger.Time>().forEach { trigger ->
|
||||
scheduleNextTimeTrigger(profileId, scene, trigger)
|
||||
}
|
||||
}
|
||||
return DomainResult.Success(Unit)
|
||||
}
|
||||
|
||||
override suspend fun remove(profileId: ProviderProfileId): DomainResult<Unit> {
|
||||
workManager.cancelUniqueWork(workName(profileId))
|
||||
workManager.cancelUniqueWork(timeWorkName(profileId))
|
||||
workManager.cancelAllWorkByTag(timeWorkTag(profileId))
|
||||
return DomainResult.Success(Unit)
|
||||
}
|
||||
|
||||
private fun workName(profileId: ProviderProfileId) = "automation:${profileId.value}"
|
||||
private fun timeWorkName(profileId: ProviderProfileId) = "automation-time:${profileId.value}"
|
||||
private fun timeWorkName(profileId: ProviderProfileId, scene: Scene, trigger: SceneTrigger.Time) =
|
||||
"automation-time:${profileId.value}:${scene.id.value}:${trigger.id.value}"
|
||||
private fun timeWorkTag(profileId: ProviderProfileId) = "automation-time:${profileId.value}"
|
||||
|
||||
private fun scheduleNextTimeTrigger(profileId: ProviderProfileId, scenes: List<Scene>) {
|
||||
private fun scheduleNextTimeTrigger(profileId: ProviderProfileId, scene: Scene, trigger: SceneTrigger.Time) {
|
||||
val now = LocalDateTime.now()
|
||||
val next = scenes.flatMap { scene ->
|
||||
scene.triggers.filterIsInstance<SceneTrigger.Time>().mapNotNull { trigger ->
|
||||
nextOccurrence(trigger, now)?.let { occurrence -> ScheduledWorkOccurrence(scene, trigger, occurrence) }
|
||||
}
|
||||
}.minByOrNull { it.scheduledAt } ?: run {
|
||||
workManager.cancelUniqueWork(timeWorkName(profileId))
|
||||
return
|
||||
}
|
||||
val delay = Duration.between(now.atZone(ZoneId.systemDefault()).toInstant(), next.scheduledAt.atZone(ZoneId.systemDefault()).toInstant())
|
||||
val scheduledAtInstant = next.scheduledAt.atZone(ZoneId.systemDefault()).toInstant()
|
||||
val scheduledAt = nextOccurrence(trigger, now) ?: return
|
||||
val delay = Duration.between(now.atZone(ZoneId.systemDefault()).toInstant(), scheduledAt.atZone(ZoneId.systemDefault()).toInstant())
|
||||
val scheduledAtInstant = scheduledAt.atZone(ZoneId.systemDefault()).toInstant()
|
||||
val maximumExpiry = scheduledAtInstant.plus(MAXIMUM_OCCURRENCE_LATENESS)
|
||||
val followingOccurrence = nextOccurrence(next.trigger, next.scheduledAt)
|
||||
val followingOccurrence = nextOccurrence(trigger, scheduledAt)
|
||||
?.atZone(ZoneId.systemDefault())
|
||||
?.toInstant()
|
||||
?.minusMillis(1)
|
||||
val expiresAt = listOfNotNull(maximumExpiry, followingOccurrence).minOrNull() ?: maximumExpiry
|
||||
val request = OneTimeWorkRequestBuilder<AutomationPollWorker>()
|
||||
.setInitialDelay(delay.toMillis().coerceAtLeast(0), TimeUnit.MILLISECONDS)
|
||||
.addTag(timeWorkTag(profileId))
|
||||
.setInputData(
|
||||
androidx.work.Data.Builder()
|
||||
.putString(KEY_PROFILE_ID, profileId.value)
|
||||
.putString(KEY_SCENE_ID, next.scene.id.value)
|
||||
.putString(KEY_TRIGGER_ID, next.trigger.id.value)
|
||||
.putString(KEY_OCCURRENCE_KEY, "${next.scheduledAt.toLocalDate()}:${next.trigger.timeOfDay}:${next.trigger.id.value}")
|
||||
.putString(KEY_SCENE_ID, scene.id.value)
|
||||
.putString(KEY_TRIGGER_ID, trigger.id.value)
|
||||
.putString(KEY_OCCURRENCE_KEY, "${scheduledAt.toLocalDate()}:${trigger.timeOfDay}:${trigger.id.value}")
|
||||
.putLong(KEY_SCHEDULED_AT_EPOCH_MILLIS, scheduledAtInstant.toEpochMilli())
|
||||
.putLong(KEY_EXPIRES_AT_EPOCH_MILLIS, expiresAt.toEpochMilli())
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
workManager.enqueueUniqueWork(timeWorkName(profileId), androidx.work.ExistingWorkPolicy.REPLACE, request)
|
||||
workManager.enqueueUniqueWork(
|
||||
timeWorkName(profileId, scene, trigger),
|
||||
androidx.work.ExistingWorkPolicy.REPLACE,
|
||||
request,
|
||||
)
|
||||
}
|
||||
|
||||
private fun nextOccurrence(trigger: SceneTrigger.Time, now: LocalDateTime): LocalDateTime? {
|
||||
@@ -82,12 +87,6 @@ class WorkManagerAutomationScheduleStore(context: Context) : AutomationScheduleS
|
||||
return null
|
||||
}
|
||||
|
||||
private data class ScheduledWorkOccurrence(
|
||||
val scene: Scene,
|
||||
val trigger: SceneTrigger.Time,
|
||||
val scheduledAt: LocalDateTime,
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val KEY_PROFILE_ID = "profile_id"
|
||||
const val KEY_SCENE_ID = "scene_id"
|
||||
|
||||
Reference in New Issue
Block a user