fix(scene): harden delayed automation notifications
This commit is contained in:
@@ -15,7 +15,13 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
private val notificationPermissionLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) {}
|
||||
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()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -34,7 +40,8 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
val permissionState = getSharedPreferences(NOTIFICATION_PERMISSION_STATE, MODE_PRIVATE)
|
||||
if (permissionState.getBoolean(KEY_PERMISSION_REQUESTED, false)) return
|
||||
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)
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@ import com.flagship.abox.manager.domain.DomainError
|
||||
import com.flagship.abox.manager.domain.DomainResult
|
||||
import com.flagship.abox.manager.domain.NotificationGateway
|
||||
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||
import java.util.UUID
|
||||
|
||||
class AndroidNotificationGateway(private val context: Context) : NotificationGateway {
|
||||
override suspend fun show(profileId: ProviderProfileId, title: String, message: String): DomainResult<Unit> {
|
||||
@@ -40,7 +41,7 @@ class AndroidNotificationGateway(private val context: Context) : NotificationGat
|
||||
@SuppressLint("MissingPermission")
|
||||
fun postNotification() {
|
||||
notificationManager.notify(
|
||||
profileId.value.hashCode(),
|
||||
UUID.randomUUID().hashCode(),
|
||||
NotificationCompat.Builder(context, CHANNEL_ID)
|
||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||
.setContentTitle(title)
|
||||
|
||||
+1
-6
@@ -39,6 +39,7 @@ data class ScheduledAutomationOccurrence(
|
||||
val triggerId: String,
|
||||
val occurrenceKey: String,
|
||||
val scheduledAtEpochMillis: Long,
|
||||
val expiresAtEpochMillis: Long,
|
||||
)
|
||||
|
||||
class DefaultAutomationEngine(
|
||||
@@ -170,10 +171,4 @@ class DefaultAutomationEngine(
|
||||
)
|
||||
}
|
||||
|
||||
private val ScheduledAutomationOccurrence.expiresAtEpochMillis: Long
|
||||
get() = scheduledAtEpochMillis + SCHEDULED_OCCURRENCE_GRACE_MILLIS
|
||||
|
||||
private companion object {
|
||||
const val SCHEDULED_OCCURRENCE_GRACE_MILLIS = 20L * 60L * 1000L
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -38,8 +38,9 @@ class AutomationPollWorker @AssistedInject constructor(
|
||||
val sceneId = inputData.getString(WorkManagerAutomationScheduleStore.KEY_SCENE_ID)
|
||||
val triggerId = inputData.getString(WorkManagerAutomationScheduleStore.KEY_TRIGGER_ID)
|
||||
val scheduledAt = inputData.getLong(WorkManagerAutomationScheduleStore.KEY_SCHEDULED_AT_EPOCH_MILLIS, -1L)
|
||||
if (sceneId != null && triggerId != null && scheduledAt >= 0L) {
|
||||
ScheduledAutomationOccurrence(sceneId, triggerId, key, scheduledAt)
|
||||
val expiresAt = inputData.getLong(WorkManagerAutomationScheduleStore.KEY_EXPIRES_AT_EPOCH_MILLIS, -1L)
|
||||
if (sceneId != null && triggerId != null && scheduledAt >= 0L && expiresAt >= scheduledAt) {
|
||||
ScheduledAutomationOccurrence(sceneId, triggerId, key, scheduledAt, expiresAt)
|
||||
} else null
|
||||
}
|
||||
when (automationEngine.evaluate(profileId, scheduledOccurrence)) {
|
||||
|
||||
+12
-1
@@ -48,6 +48,13 @@ class WorkManagerAutomationScheduleStore(context: Context) : AutomationScheduleS
|
||||
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 maximumExpiry = scheduledAtInstant.plus(MAXIMUM_OCCURRENCE_LATENESS)
|
||||
val followingOccurrence = nextOccurrence(next.trigger, next.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)
|
||||
.setInputData(
|
||||
@@ -56,7 +63,8 @@ class WorkManagerAutomationScheduleStore(context: Context) : AutomationScheduleS
|
||||
.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}")
|
||||
.putLong(KEY_SCHEDULED_AT_EPOCH_MILLIS, next.scheduledAt.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli())
|
||||
.putLong(KEY_SCHEDULED_AT_EPOCH_MILLIS, scheduledAtInstant.toEpochMilli())
|
||||
.putLong(KEY_EXPIRES_AT_EPOCH_MILLIS, expiresAt.toEpochMilli())
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
@@ -86,5 +94,8 @@ class WorkManagerAutomationScheduleStore(context: Context) : AutomationScheduleS
|
||||
const val KEY_TRIGGER_ID = "trigger_id"
|
||||
const val KEY_OCCURRENCE_KEY = "occurrence_key"
|
||||
const val KEY_SCHEDULED_AT_EPOCH_MILLIS = "scheduled_at_epoch_millis"
|
||||
const val KEY_EXPIRES_AT_EPOCH_MILLIS = "expires_at_epoch_millis"
|
||||
|
||||
val MAXIMUM_OCCURRENCE_LATENESS: Duration = Duration.ofHours(24)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user