diff --git a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/UserSettings.kt b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/UserSettings.kt index 6749eff..a4991a7 100644 --- a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/UserSettings.kt +++ b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/model/UserSettings.kt @@ -7,6 +7,7 @@ data class UserSettings( val softScreenOff: Boolean = false, val turnOffWifi: Boolean = false, val turnOffBluetooth: Boolean = false, + val returnToHome: Boolean = false, val hapticFeedbackEnabled: Boolean = true, val theme: ThemeId = ThemeId.Default, val starsEnabled: Boolean = true, diff --git a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepository.kt b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepository.kt index a6f7492..936bbc9 100644 --- a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepository.kt +++ b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepository.kt @@ -13,6 +13,7 @@ interface SettingsRepository { suspend fun updateSoftScreenOff(enabled: Boolean) suspend fun updateTurnOffWifi(enabled: Boolean) suspend fun updateTurnOffBluetooth(enabled: Boolean) + suspend fun updateReturnToHome(enabled: Boolean) suspend fun updateHapticFeedback(enabled: Boolean) suspend fun updateTheme(theme: ThemeId) suspend fun updateStarsEnabled(enabled: Boolean) diff --git a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepositoryImpl.kt b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepositoryImpl.kt index ae0ec26..eac1ed0 100644 --- a/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepositoryImpl.kt +++ b/core/data/src/main/kotlin/dev/xitee/sleeptimer/core/data/repository/SettingsRepositoryImpl.kt @@ -39,6 +39,7 @@ class SettingsRepositoryImpl @Inject constructor( val SOFT_SCREEN_OFF = booleanPreferencesKey("soft_screen_off") val TURN_OFF_WIFI = booleanPreferencesKey("turn_off_wifi") val TURN_OFF_BLUETOOTH = booleanPreferencesKey("turn_off_bluetooth") + val RETURN_TO_HOME = booleanPreferencesKey("return_to_home") val HAPTIC_FEEDBACK = booleanPreferencesKey("haptic_feedback") val THEME = stringPreferencesKey("theme") val STARS_ENABLED = booleanPreferencesKey("stars_enabled") @@ -92,6 +93,7 @@ class SettingsRepositoryImpl @Inject constructor( softScreenOff = prefs[SOFT_SCREEN_OFF] ?: d.softScreenOff, turnOffWifi = prefs[TURN_OFF_WIFI] ?: d.turnOffWifi, turnOffBluetooth = prefs[TURN_OFF_BLUETOOTH] ?: d.turnOffBluetooth, + returnToHome = prefs[RETURN_TO_HOME] ?: d.returnToHome, hapticFeedbackEnabled = prefs[HAPTIC_FEEDBACK] ?: d.hapticFeedbackEnabled, theme = ThemeId.fromStorage(prefs[THEME]), starsEnabled = prefs[STARS_ENABLED] ?: d.starsEnabled, @@ -128,6 +130,10 @@ class SettingsRepositoryImpl @Inject constructor( dataStore.edit { it[TURN_OFF_BLUETOOTH] = enabled } } + override suspend fun updateReturnToHome(enabled: Boolean) { + dataStore.edit { it[RETURN_TO_HOME] = enabled } + } + override suspend fun updateHapticFeedback(enabled: Boolean) { dataStore.edit { it[HAPTIC_FEEDBACK] = enabled } } diff --git a/core/service/src/main/kotlin/dev/xitee/sleeptimer/core/service/SleepTimerService.kt b/core/service/src/main/kotlin/dev/xitee/sleeptimer/core/service/SleepTimerService.kt index dfeab06..6dbb5dc 100644 --- a/core/service/src/main/kotlin/dev/xitee/sleeptimer/core/service/SleepTimerService.kt +++ b/core/service/src/main/kotlin/dev/xitee/sleeptimer/core/service/SleepTimerService.kt @@ -20,6 +20,7 @@ import dev.xitee.sleeptimer.core.service.media.MediaVolumeController import dev.xitee.sleeptimer.core.service.notification.TimerNotificationManager import dev.xitee.sleeptimer.core.service.screen.ScreenLockHelper import dev.xitee.sleeptimer.core.service.shizuku.ShizukuBluetoothController +import dev.xitee.sleeptimer.core.service.shizuku.ShizukuHomeController import dev.xitee.sleeptimer.core.service.shizuku.ShizukuManager import dev.xitee.sleeptimer.core.service.shizuku.ShizukuScreenOffHelper import dev.xitee.sleeptimer.core.service.shizuku.ShizukuWifiController @@ -49,6 +50,7 @@ class SleepTimerService : Service() { @Inject lateinit var shizukuScreenOffHelper: ShizukuScreenOffHelper @Inject lateinit var shizukuWifiController: ShizukuWifiController @Inject lateinit var shizukuBluetoothController: ShizukuBluetoothController + @Inject lateinit var shizukuHomeController: ShizukuHomeController private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private var countdownJob: Job? = null @@ -303,6 +305,15 @@ class SleepTimerService : Service() { shizukuBluetoothController.disableBluetooth() } + if (settings.returnToHome && shizukuManager.isReady()) { + // KEYCODE_HOME wakes a sleeping device, so only send it while the + // screen is still on. Runs before the screen-off step so the launcher + // — not the media app — is what greets the user at the next unlock. + if (powerManager.isInteractive) { + shizukuHomeController.goHome() + } + } + if (settings.screenOff) { if (settings.softScreenOff && shizukuManager.isReady()) { // KEYCODE_POWER is a toggle: only send it while the screen is on, diff --git a/core/service/src/main/kotlin/dev/xitee/sleeptimer/core/service/shizuku/ShizukuHomeController.kt b/core/service/src/main/kotlin/dev/xitee/sleeptimer/core/service/shizuku/ShizukuHomeController.kt new file mode 100644 index 0000000..c7d2e16 --- /dev/null +++ b/core/service/src/main/kotlin/dev/xitee/sleeptimer/core/service/shizuku/ShizukuHomeController.kt @@ -0,0 +1,17 @@ +package dev.xitee.sleeptimer.core.service.shizuku + +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Returns to the home screen via a simulated home key press (KEYCODE_HOME = 3). + * Goes through Shizuku because Android 10+ blocks background activity launches, + * so a plain ACTION_MAIN/CATEGORY_HOME startActivity from the service would be + * silently dropped while another app is in the foreground. + */ +@Singleton +class ShizukuHomeController @Inject constructor( + private val shell: ShizukuShell, +) { + suspend fun goHome(): Boolean = shell.exec("input", "keyevent", "3") +} diff --git a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsScreen.kt b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsScreen.kt index 57e3ed4..c2c7ade 100644 --- a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsScreen.kt +++ b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AutoAwesome import androidx.compose.material.icons.filled.Bluetooth +import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.MusicOff import androidx.compose.material.icons.filled.PhoneAndroid @@ -106,6 +107,7 @@ private fun SettingsContent( stringResource(R.string.shizuku_feature_soft_screen_off) val shizukuWifiExplanation = stringResource(R.string.shizuku_feature_wifi) val shizukuBluetoothExplanation = stringResource(R.string.shizuku_feature_bluetooth) + val shizukuReturnHomeExplanation = stringResource(R.string.shizuku_feature_return_home) val deviceAdminLauncher = rememberLauncherForActivityResult( ActivityResultContracts.StartActivityForResult(), @@ -323,6 +325,22 @@ private fun SettingsContent( }, ) + SettingsToggleRow( + icon = Icons.Default.Home, + title = stringResource(R.string.return_home_title), + description = stringResource(R.string.return_home_description), + checked = uiState.settings.returnToHome, + onCheckedChange = { enabled -> + if (enabled) { + requestWithShizuku(shizukuReturnHomeExplanation) { + viewModel.updateReturnToHome(true) + } + } else { + viewModel.updateReturnToHome(false) + } + }, + ) + SectionHeader(stringResource(R.string.category_haptic)) SettingsToggleRow( icon = Icons.Default.Vibration, diff --git a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsViewModel.kt b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsViewModel.kt index c4fec38..64d85ba 100644 --- a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsViewModel.kt +++ b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/settings/SettingsViewModel.kt @@ -80,6 +80,10 @@ class SettingsViewModel @Inject constructor( viewModelScope.launch { settingsRepository.updateTurnOffBluetooth(enabled) } } + fun updateReturnToHome(enabled: Boolean) { + viewModelScope.launch { settingsRepository.updateReturnToHome(enabled) } + } + fun updateHapticFeedback(enabled: Boolean) { viewModelScope.launch { settingsRepository.updateHapticFeedback(enabled) } } diff --git a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerScreen.kt b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerScreen.kt index 46a0502..ee46348 100644 --- a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerScreen.kt +++ b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerScreen.kt @@ -180,6 +180,7 @@ private fun TimerContent( ShizukuFeature.SCREEN_OFF -> stringResource(R.string.shizuku_feature_label_display) ShizukuFeature.WIFI -> stringResource(R.string.shizuku_feature_label_wifi) ShizukuFeature.BLUETOOTH -> stringResource(R.string.shizuku_feature_label_bluetooth) + ShizukuFeature.RETURN_HOME -> stringResource(R.string.shizuku_feature_label_return_home) } } ShizukuRequiredDialog( diff --git a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerViewModel.kt b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerViewModel.kt index e1ec991..e91c7bc 100644 --- a/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerViewModel.kt +++ b/feature/timer/src/main/kotlin/dev/xitee/sleeptimer/feature/timer/timer/TimerViewModel.kt @@ -98,6 +98,7 @@ class TimerViewModel @Inject constructor( if (s.screenOff && s.softScreenOff && !shizukuReady) add(ShizukuFeature.SCREEN_OFF) if (s.turnOffWifi && !shizukuReady) add(ShizukuFeature.WIFI) if (s.turnOffBluetooth && !shizukuReady) add(ShizukuFeature.BLUETOOTH) + if (s.returnToHome && !shizukuReady) add(ShizukuFeature.RETURN_HOME) } return StartupPermissionCheck(adminMissing, shizukuFeatures) } @@ -178,4 +179,4 @@ data class StartupPermissionCheck( val shizukuMissingFeatures: List, ) -enum class ShizukuFeature { SCREEN_OFF, WIFI, BLUETOOTH } +enum class ShizukuFeature { SCREEN_OFF, WIFI, BLUETOOTH, RETURN_HOME } diff --git a/feature/timer/src/main/res/values-de/strings.xml b/feature/timer/src/main/res/values-de/strings.xml index 8c134e2..e039a40 100644 --- a/feature/timer/src/main/res/values-de/strings.xml +++ b/feature/timer/src/main/res/values-de/strings.xml @@ -68,11 +68,13 @@ Sanftes Bildschirmabschalten simuliert einen Druck der Power-Taste via Shizuku, sodass die biometrische Entsperrung weiterhin funktioniert. WLAN nach Timer-Ende auszuschalten erfordert Shizuku, da moderne Android-Versionen Apps das direkte Umschalten von WLAN verbieten. Bluetooth nach Timer-Ende auszuschalten erfordert Shizuku, da moderne Android-Versionen Apps das direkte Umschalten von Bluetooth verbieten. + Nach Timer-Ende zum Startbildschirm zurückzukehren erfordert Shizuku, da moderne Android-Versionen Apps den Wechsel zu einem anderen Bildschirm aus dem Hintergrund verbieten. Display ausschalten WLAN ausschalten Bluetooth ausschalten + Zum Startbildschirm zurückkehren Die Shizuku-Berechtigung fehlt für folgende aktivierte Funktionen: @@ -88,6 +90,8 @@ WLAN deaktivieren (Shizuku benötigt) Bluetooth ausschalten Bluetooth deaktivieren (Shizuku benötigt) + Zum Startbildschirm zurückkehren + Nach Timer-Ende zum Startbildschirm wechseln (Shizuku benötigt) Sperrmethode wählen diff --git a/feature/timer/src/main/res/values/strings.xml b/feature/timer/src/main/res/values/strings.xml index 8834e1d..b06d5e1 100644 --- a/feature/timer/src/main/res/values/strings.xml +++ b/feature/timer/src/main/res/values/strings.xml @@ -68,11 +68,13 @@ Soft screen-off simulates a power-button press via Shizuku, so biometric unlock keeps working. Turning off Wi-Fi after the timer ends requires Shizuku because modern Android versions block apps from toggling Wi-Fi directly. Turning off Bluetooth after the timer ends requires Shizuku because modern Android versions block apps from toggling Bluetooth directly. + Returning to the home screen after the timer ends requires Shizuku because modern Android versions block apps from switching to another screen from the background. Turn off display Turn off Wi-Fi Turn off Bluetooth + Return to home screen Shizuku permission is missing for the following enabled features: @@ -88,6 +90,8 @@ Disable Wi-Fi when timer ends (requires Shizuku) Turn off Bluetooth Disable Bluetooth when timer ends (requires Shizuku) + Return to home screen + Go to the home screen when timer ends (requires Shizuku) Choose lock method