diff --git a/app/src/main/java/eu/darken/sdmse/scheduler/core/SchedulerNotifications.kt b/app/src/main/java/eu/darken/sdmse/scheduler/core/SchedulerNotifications.kt index 7fee9fec9..5625f3838 100644 --- a/app/src/main/java/eu/darken/sdmse/scheduler/core/SchedulerNotifications.kt +++ b/app/src/main/java/eu/darken/sdmse/scheduler/core/SchedulerNotifications.kt @@ -16,6 +16,7 @@ import eu.darken.sdmse.common.debug.logging.log import eu.darken.sdmse.common.debug.logging.logTag import eu.darken.sdmse.common.hasApiLevel import eu.darken.sdmse.common.notifications.PendingIntentCompat +import eu.darken.sdmse.main.core.SDMTool import eu.darken.sdmse.main.ui.MainActivity import javax.inject.Inject @@ -53,7 +54,7 @@ class SchedulerNotifications @Inject constructor( } } - fun getBuilder(schedule: Schedule?): NotificationCompat.Builder { + private fun getStateBuilder(schedule: Schedule?): NotificationCompat.Builder { if (schedule == null) { return builder.apply { setStyle(null) @@ -65,13 +66,13 @@ class SchedulerNotifications @Inject constructor( return builder.apply { setContentTitle(context.getString(R.string.scheduler_notification_title)) setContentText(context.getString(R.string.scheduler_notification_message, schedule.label)) - log(TAG) { "getBuilder(): $schedule" } + log(TAG) { "getStateBuilder(): $schedule" } } } - fun getNotification(schedule: Schedule?): Notification = getBuilder(schedule).build() + private fun getStateNotification(schedule: Schedule?): Notification = getStateBuilder(schedule).build() - fun getForegroundInfo(schedule: Schedule): ForegroundInfo = getBuilder(schedule).toForegroundInfo(schedule) + fun getForegroundInfo(schedule: Schedule): ForegroundInfo = getStateBuilder(schedule).toForegroundInfo(schedule) private fun NotificationCompat.Builder.toForegroundInfo(schedule: Schedule): ForegroundInfo = if (hasApiLevel(29)) { @Suppress("NewApi") @@ -82,13 +83,13 @@ class SchedulerNotifications @Inject constructor( private fun ScheduleId.toNotificationid(): Int { val baseId = (this.hashCode() and Int.MAX_VALUE) % 101 - return NOTIFICATION_ID_RANGE + baseId + return NOTIFICATION_ID_RANGE_STATE + baseId } - fun notify(schedule: Schedule) { + fun notifyState(schedule: Schedule) { val id = schedule.id.toNotificationid() - val notification = getNotification(schedule) - log(TAG) { "notify($id, $schedule)" } + val notification = getStateNotification(schedule) + log(TAG) { "notifyState($id, $schedule)" } notificationManager.notify(id, notification) } @@ -98,9 +99,39 @@ class SchedulerNotifications @Inject constructor( notificationManager.cancel(id) } + private fun getResultBuilder(results: Set): NotificationCompat.Builder = builder.apply { + setContentTitle(context.getString(R.string.scheduler_notification_result_title)) + val text = if (results.any { it.error != null }) { + context.getString(R.string.scheduler_notification_result_failure_message) + } else { + context.getString(R.string.scheduler_notification_result_success_message) + } + setContentText(text) + log(TAG) { "getResultBuilder(): $results" } + } + + private fun Set.toNotificationid(): Int { + val baseId = (this.hashCode() and Int.MAX_VALUE) % 101 + return NOTIFICATION_ID_RANGE_RESULT + baseId + } + + fun notifyResult(results: Set) { + val id = results.toNotificationid() + val notification = getResultBuilder(results).build() + log(TAG) { "notifyResult($id, $results)" } + notificationManager.notify(id, notification) + } + + data class Results( + val task: SDMTool.Task, + val result: SDMTool.Task.Result? = null, + val error: Exception? = null, + ) + companion object { val TAG = logTag("Scheduler", "Notifications", "Worker") private val CHANNEL_ID = "${BuildConfigWrap.APPLICATION_ID}.notification.channel.scheduler" - internal const val NOTIFICATION_ID_RANGE = 1000 + internal const val NOTIFICATION_ID_RANGE_STATE = 1000 + internal const val NOTIFICATION_ID_RANGE_RESULT = 1200 } } diff --git a/app/src/main/java/eu/darken/sdmse/scheduler/core/SchedulerWorker.kt b/app/src/main/java/eu/darken/sdmse/scheduler/core/SchedulerWorker.kt index 736b6c827..8ae954ddd 100644 --- a/app/src/main/java/eu/darken/sdmse/scheduler/core/SchedulerWorker.kt +++ b/app/src/main/java/eu/darken/sdmse/scheduler/core/SchedulerWorker.kt @@ -26,13 +26,13 @@ import eu.darken.sdmse.systemcleaner.core.tasks.SystemCleanerSchedulerTask import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.take -import kotlinx.coroutines.joinAll -import kotlinx.coroutines.launch @HiltWorker @@ -43,7 +43,7 @@ class SchedulerWorker @AssistedInject constructor( private val taskManager: TaskManager, private val schedulerManager: SchedulerManager, private val schedulerSettings: SchedulerSettings, - private val workerNotifications: SchedulerNotifications, + private val schedulerNotifications: SchedulerNotifications, private val notificationManager: NotificationManager, private val setupHealer: SetupHealer, ) : CoroutineWorker(context, params) { @@ -63,7 +63,7 @@ class SchedulerWorker @AssistedInject constructor( } override suspend fun getForegroundInfo(): ForegroundInfo { - return workerNotifications.getForegroundInfo(getSchedule()) + return schedulerNotifications.getForegroundInfo(getSchedule()) } override suspend fun doWork(): Result = try { @@ -75,7 +75,7 @@ class SchedulerWorker @AssistedInject constructor( log(TAG, INFO) { "Executing schedule $schedule" } Bugs.leaveBreadCrumb("Executing schedule") - workerNotifications.notify(schedule) + schedulerNotifications.notifyState(schedule) doDoWork(schedule) @@ -97,7 +97,7 @@ class SchedulerWorker @AssistedInject constructor( Result.success() } } finally { - workerNotifications.cancel(scheduleId) + schedulerNotifications.cancel(scheduleId) workerScope.cancel("Worker finished (withError?=$finishedWithError).") } @@ -124,18 +124,23 @@ class SchedulerWorker @AssistedInject constructor( .take(1) .first() - val taskJobs = tasks.map { task -> - workerScope.launch { + val taskJobs = tasks.mapNotNull { task -> + workerScope.async { try { - taskManager.submit(task) + log(TAG) { "Launching $task" } + val result = taskManager.submit(task) + log(TAG) { "Finished $task -> $result" } + SchedulerNotifications.Results(task, result = result) } catch (e: Exception) { log(TAG, ERROR) { "Scheduler task failed ($task): ${e.asLog()}" } + SchedulerNotifications.Results(task, error = e) } } } log(TAG) { "Waiting for jobs to complete: $taskJobs" } - taskJobs.joinAll() + val taskResults = taskJobs.awaitAll().toSet() + schedulerNotifications.notifyResult(taskResults) log(TAG) { "All task jobs have finished." } } diff --git a/app/src/main/java/eu/darken/sdmse/scheduler/ui/manager/SchedulerManagerFragment.kt b/app/src/main/java/eu/darken/sdmse/scheduler/ui/manager/SchedulerManagerFragment.kt index 134535223..4ee16fe25 100644 --- a/app/src/main/java/eu/darken/sdmse/scheduler/ui/manager/SchedulerManagerFragment.kt +++ b/app/src/main/java/eu/darken/sdmse/scheduler/ui/manager/SchedulerManagerFragment.kt @@ -9,6 +9,7 @@ import androidx.navigation.ui.setupWithNavController import dagger.hilt.android.AndroidEntryPoint import eu.darken.sdmse.R import eu.darken.sdmse.common.WebpageTool +import eu.darken.sdmse.common.debug.Bugs import eu.darken.sdmse.common.lists.differ.update import eu.darken.sdmse.common.lists.setupDefaults import eu.darken.sdmse.common.uix.Fragment3 @@ -33,9 +34,15 @@ class SchedulerManagerFragment : Fragment3(R.layout.scheduler_manager_fragment) true } + R.id.menu_debug_schedule -> { + vm.debugSchedule() + true + } + else -> false } } + menu?.findItem(R.id.menu_debug_schedule)?.isVisible = Bugs.isDebug } ui.mainAction.setOnClickListener { vm.createNew() } diff --git a/app/src/main/java/eu/darken/sdmse/scheduler/ui/manager/SchedulerManagerViewModel.kt b/app/src/main/java/eu/darken/sdmse/scheduler/ui/manager/SchedulerManagerViewModel.kt index fd16a33d4..918429126 100644 --- a/app/src/main/java/eu/darken/sdmse/scheduler/ui/manager/SchedulerManagerViewModel.kt +++ b/app/src/main/java/eu/darken/sdmse/scheduler/ui/manager/SchedulerManagerViewModel.kt @@ -22,7 +22,9 @@ import eu.darken.sdmse.scheduler.core.SchedulerSettings import eu.darken.sdmse.scheduler.ui.manager.items.AlarmHintRowVH import eu.darken.sdmse.scheduler.ui.manager.items.ScheduleRowVH import kotlinx.coroutines.flow.* +import java.time.Duration import java.time.Instant +import java.time.LocalTime import java.util.* import javax.inject.Inject @@ -119,6 +121,21 @@ class SchedulerManagerViewModel @Inject constructor( ).navigate() } + fun debugSchedule() = launch { + log(TAG) { "debugSchedule()" } + val id = UUID.randomUUID().toString() + val now = LocalTime.now().plusMinutes(1) + val testSchedule = Schedule( + id = id, + label = "Test Schedule $id", + hour = now.hour, + minute = now.minute, + repeatInterval = Duration.ofDays(1), + scheduledAt = Instant.now(), + ) + schedulerManager.saveSchedule(testSchedule) + } + data class State( val listItems: List? = null, ) diff --git a/app/src/main/res/layout/scheduler_manager_fragment.xml b/app/src/main/res/layout/scheduler_manager_fragment.xml index fe09522e6..1f85c213d 100644 --- a/app/src/main/res/layout/scheduler_manager_fragment.xml +++ b/app/src/main/res/layout/scheduler_manager_fragment.xml @@ -13,7 +13,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:menu="@menu/menu_data_areas" + app:menu="@menu/menu_scheduler_manager" app:title="@string/scheduler_label" /> + + Scheduler Scheduled task Executing: \"%s\" + Scheduler finished + All scheduled tasks ran successfully. + Some scheduled tasks failed or encountered errors. Try running them manually. Update available An update is available. You have %1$s and the latest version is %2$s.