From afb65a25c66a5d8d1693a06a16747c9a4c807f11 Mon Sep 17 00:00:00 2001 From: KremeCN <3265494849@qq.com> Date: Fri, 28 Aug 2026 03:27:23 +0800 Subject: [PATCH] fix(scene): schedule time triggers independently --- .../com/flagship/abox/manager/MainActivity.kt | 18 +------ .../automation/runtime/AutomationEngine.kt | 20 ++++---- .../WorkManagerAutomationScheduleStore.kt | 49 +++++++++---------- 3 files changed, 36 insertions(+), 51 deletions(-) diff --git a/android/app/src/main/kotlin/com/flagship/abox/manager/MainActivity.kt b/android/app/src/main/kotlin/com/flagship/abox/manager/MainActivity.kt index edecb33..2de3306 100644 --- a/android/app/src/main/kotlin/com/flagship/abox/manager/MainActivity.kt +++ b/android/app/src/main/kotlin/com/flagship/abox/manager/MainActivity.kt @@ -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" - } } diff --git a/android/feature/automation/src/main/kotlin/com/flagship/abox/manager/automation/runtime/AutomationEngine.kt b/android/feature/automation/src/main/kotlin/com/flagship/abox/manager/automation/runtime/AutomationEngine.kt index d7a3e3c..c501e73 100644 --- a/android/feature/automation/src/main/kotlin/com/flagship/abox/manager/automation/runtime/AutomationEngine.kt +++ b/android/feature/automation/src/main/kotlin/com/flagship/abox/manager/automation/runtime/AutomationEngine.kt @@ -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) diff --git a/android/feature/automation/src/main/kotlin/com/flagship/abox/manager/automation/runtime/WorkManagerAutomationScheduleStore.kt b/android/feature/automation/src/main/kotlin/com/flagship/abox/manager/automation/runtime/WorkManagerAutomationScheduleStore.kt index 5953027..798be10 100644 --- a/android/feature/automation/src/main/kotlin/com/flagship/abox/manager/automation/runtime/WorkManagerAutomationScheduleStore.kt +++ b/android/feature/automation/src/main/kotlin/com/flagship/abox/manager/automation/runtime/WorkManagerAutomationScheduleStore.kt @@ -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().forEach { trigger -> + scheduleNextTimeTrigger(profileId, scene, trigger) + } + } return DomainResult.Success(Unit) } override suspend fun remove(profileId: ProviderProfileId): DomainResult { 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) { + private fun scheduleNextTimeTrigger(profileId: ProviderProfileId, scene: Scene, trigger: SceneTrigger.Time) { val now = LocalDateTime.now() - val next = scenes.flatMap { scene -> - scene.triggers.filterIsInstance().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() .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"