Repository navigation
Open
Labels
enhancementNew feature or requestNew feature or request
Description
can we have Stop Service when no clients are connected for a certain period of time?
diff --git a/shared/src/androidMain/kotlin/com/ismartcoding/plain/app/DesktopAccessIdleStopper.kt b/shared/src/androidMain/kotlin/com/ismartcoding/plain/app/DesktopAccessIdleStopper.kt
new file mode 100644
--- /dev/null
+++ b/shared/src/androidMain/kotlin/com/ismartcoding/plain/app/DesktopAccessIdleStopper.kt
@@ -0,0 +1,80 @@
+package com.ismartcoding.plain.app
+
+import android.annotation.SuppressLint
+import com.ismartcoding.plain.HttpServerManager
+import com.ismartcoding.plain.Prefs
+import com.ismartcoding.plain.appContext
+import com.ismartcoding.plain.features.sessions.onlineClientIds
+import com.ismartcoding.plain.lib.logcat.LogCat
+import com.ismartcoding.plain.platform.system.setDesktopAccessEnabled
+import com.ismartcoding.plain.ui.platform.notifications.NotificationHelper
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.launch
+import kotlin.time.Duration.Companion.minutes
+
+/**
+ * Turns Desktop Access off after it has had no connected web clients for
+ * [IDLE_TIMEOUT_MINUTES] minutes. Turning Desktop Access off closes any open
+ * websocket sessions and refuses new logins until it is switched back on.
+ *
+ * The timer starts when Desktop Access is on and no client is online
+ * (including right after enabling it), and resets whenever a client connects.
+ */
+internal object DesktopAccessIdleStopper {
+ /** Minutes without any online client before Desktop Access is turned off. 0 disables the feature. */
+ private const val IDLE_TIMEOUT_MINUTES = 10
+
+ /** Also turn off "Keep online in background" so the foreground service and wake lock are released. */
+ private const val ALSO_STOP_BACKGROUND_SERVICE = true
+
+ /** Post a notification when the idle stop happens. */
+ private const val NOTIFY_ON_STOP = true
+
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
+
+ fun observe() {
+ if (IDLE_TIMEOUT_MINUTES <= 0) return
+ scope.launch {
+ combine(Prefs.desktopAccess, onlineClientIds) { enabled, clients -> enabled && clients.isEmpty() }
+ .distinctUntilChanged()
+ .collectLatest { idle ->
+ // collectLatest cancels this delay as soon as a client connects
+ // or Desktop Access is switched off manually.
+ if (!idle) return@collectLatest
+ delay(IDLE_TIMEOUT_MINUTES.minutes)
+ stopForIdle()
+ }
+ }
+ }
+
+ private suspend fun stopForIdle() {
+ try {
+ LogCat.d("Desktop access idle for $IDLE_TIMEOUT_MINUTES min with no clients, turning it off")
+ setDesktopAccessEnabled(false)
+ if (ALSO_STOP_BACKGROUND_SERVICE && Prefs.backgroundServiceEnabled.value) {
+ HttpServerManager.setBackgroundServiceEnabled(false).join()
+ }
+ if (NOTIFY_ON_STOP) notifyStopped()
+ } catch (cancelled: CancellationException) {
+ throw cancelled
+ } catch (error: Exception) {
+ LogCat.e("Desktop access idle stop", error)
+ }
+ }
+
+ @SuppressLint("MissingPermission") // postWebLoginNotification checks POST_NOTIFICATIONS itself
+ private fun notifyStopped() {
+ NotificationHelper.postWebLoginNotification(
+ appContext,
+ "Desktop access turned off",
+ "No web clients were connected for $IDLE_TIMEOUT_MINUTES minutes.",
+ )
+ }
+}
diff --git a/shared/src/androidMain/kotlin/com/ismartcoding/plain/platform/system/AppInitialization.android.kt b/shared/src/androidMain/kotlin/com/ismartcoding/plain/platform/system/AppInitialization.android.kt
--- a/shared/src/androidMain/kotlin/com/ismartcoding/plain/platform/system/AppInitialization.android.kt
+++ b/shared/src/androidMain/kotlin/com/ismartcoding/plain/platform/system/AppInitialization.android.kt
@@ -1,6 +1,7 @@
package com.ismartcoding.plain.platform.system
import com.ismartcoding.plain.NetworkMonitor
+import com.ismartcoding.plain.app.DesktopAccessIdleStopper
import com.ismartcoding.plain.app.HttpServerForegroundServiceManager
import com.ismartcoding.plain.appContext
import com.ismartcoding.plain.Prefs
@@ -11,4 +12,5 @@ internal actual fun initializePlatformApp() {
setDarkMode(Prefs.darkTheme.value)
HttpServerForegroundServiceManager.observe()
HttpServerResources.observe()
+ DesktopAccessIdleStopper.observe()
}
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Activity