diff --git a/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt b/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt index cd9f0ab0c..081b240ed 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/registry/FeatureRegistry.kt @@ -384,6 +384,37 @@ object FeatureRegistry { viewModel.setWirelessDisplayCertificationEnabled(enabled, context) }, + object : Feature( + id = "Auto turn off Wi-Fi", + title = R.string.wifi_auto_off_title, + iconRes = R.drawable.rounded_power_settings_new_24, + category = R.string.cat_connectivity, + description = R.string.wifi_auto_off_desc, + permissionKeys = listOf("ACCESSIBILITY"), + searchableSettings = listOf( + SearchSetting( + R.string.search_wifi_auto_off_title, + R.string.search_wifi_auto_off_desc, + "wifi_auto_off_toggle" + ), + SearchSetting( + R.string.search_wifi_auto_off_timeout_title, + R.string.search_wifi_auto_off_timeout_desc, + "wifi_auto_off_timeout_slider" + ) + ), + parentFeatureId = "Networks" + ) { + override fun isEnabled(viewModel: MainViewModel) = + viewModel.isWifiAutoOffEnabled.value + + override fun isToggleEnabled(viewModel: MainViewModel, context: Context) = + viewModel.isAccessibilityEnabled.value + + override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) = + viewModel.setWifiAutoOffEnabled(enabled) + }, + object : Feature( id = "Watch", title = R.string.feat_watch_title, diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt index 363d44654..c05fa4dda 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt @@ -152,6 +152,14 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene } } + private var isWifiConnected = false + private var networkCallback: android.net.ConnectivityManager.NetworkCallback? = null + private val wifiAutoOffHandler = Handler(Looper.getMainLooper()) + private val wifiAutoOffRunnable = Runnable { + android.util.Log.d("ScreenOffService", "Executing Wifi auto turn off") + com.sameerasw.essentials.utils.ShellUtils.runCommand(this, "svc wifi disable") + } + private fun schedulePocketFlashlightTurnOff() { pocketFlashlightHandler.removeCallbacks(pocketFlashlightRunnable) pocketFlashlightHandler.postDelayed(pocketFlashlightRunnable, 1500L) @@ -213,6 +221,13 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene updatePocketModeExcludedAppsSet() } else if (key == SettingsRepository.KEY_SMART_PIXELS_ENABLED || key == SettingsRepository.KEY_SMART_PIXELS_INTENSITY) { smartPixelsHandler.updateState() + } else if (key == "wifi_auto_off_enabled" || key == "wifi_auto_off_timeout") { + val isAutoOffEnabled = prefs.getBoolean("wifi_auto_off_enabled", false) + if (!isAutoOffEnabled) { + wifiAutoOffHandler.removeCallbacks(wifiAutoOffRunnable) + } else if (!isWifiConnected) { + checkAndScheduleWifiAutoOff() + } } } @@ -235,6 +250,7 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene flashlightHandler.register() statusBarIconHandler.register() smartPixelsHandler.init() + registerWifiNetworkCallback() // Screen Receiver screenReceiver = object : BroadcastReceiver() { @@ -396,9 +412,83 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene getSharedPreferences("essentials_prefs", MODE_PRIVATE) .unregisterOnSharedPreferenceChangeListener(preferenceChangeListener) instance = null + unregisterWifiNetworkCallback() super.onDestroy() } + private fun registerWifiNetworkCallback() { + val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as? android.net.ConnectivityManager ?: return + + isWifiConnected = isWifiCurrentlyConnected(connectivityManager) + + val networkRequest = android.net.NetworkRequest.Builder() + .addTransportType(android.net.NetworkCapabilities.TRANSPORT_WIFI) + .build() + + val callback = object : android.net.ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: android.net.Network) { + super.onAvailable(network) + isWifiConnected = true + wifiAutoOffHandler.removeCallbacks(wifiAutoOffRunnable) + android.util.Log.d("ScreenOffService", "Wi-Fi Connected - Cancelled auto off timer") + } + + override fun onLost(network: android.net.Network) { + super.onLost(network) + isWifiConnected = false + checkAndScheduleWifiAutoOff() + } + } + + networkCallback = callback + try { + connectivityManager.registerNetworkCallback(networkRequest, callback) + } catch (e: Exception) { + android.util.Log.e("ScreenOffService", "Failed to register network callback", e) + } + } + + private fun unregisterWifiNetworkCallback() { + wifiAutoOffHandler.removeCallbacks(wifiAutoOffRunnable) + val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as? android.net.ConnectivityManager + networkCallback?.let { + try { + connectivityManager?.unregisterNetworkCallback(it) + } catch (e: Exception) { + // Ignore + } + } + networkCallback = null + } + + private fun isWifiCurrentlyConnected(connectivityManager: android.net.ConnectivityManager): Boolean { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + val activeNetwork = connectivityManager.activeNetwork ?: return false + val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false + return capabilities.hasTransport(android.net.NetworkCapabilities.TRANSPORT_WIFI) + } else { + @Suppress("DEPRECATION") + val activeNetworkInfo = connectivityManager.activeNetworkInfo + @Suppress("DEPRECATION") + return activeNetworkInfo != null && activeNetworkInfo.type == android.net.ConnectivityManager.TYPE_WIFI && activeNetworkInfo.isConnected + } + } + + private fun checkAndScheduleWifiAutoOff() { + val isAutoOffEnabled = prefs.getBoolean("wifi_auto_off_enabled", false) + if (!isAutoOffEnabled) return + + val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as? android.net.wifi.WifiManager + if (wifiManager?.isWifiEnabled != true) return + + val timeoutSeconds = prefs.getFloat("wifi_auto_off_timeout", 60f) + val delayMs = (timeoutSeconds * 1000).toLong() + + wifiAutoOffHandler.removeCallbacks(wifiAutoOffRunnable) + wifiAutoOffHandler.postDelayed(wifiAutoOffRunnable, delayMs) + android.util.Log.d("ScreenOffService", "Scheduled Wi-Fi auto turn off in $timeoutSeconds seconds") + } + override fun onAccessibilityEvent(event: AccessibilityEvent?) { if (event == null) return diff --git a/app/src/main/java/com/sameerasw/essentials/ui/features/network/NetworksSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/features/network/NetworksSettingsUI.kt index e6ff1dd25..d7e65dd0c 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/features/network/NetworksSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/features/network/NetworksSettingsUI.kt @@ -248,6 +248,33 @@ fun NetworksSettingsUI( iconRes = R.drawable.rounded_cast_24, modifier = Modifier.highlight(highlightSetting == "wireless_display_certification_toggle") ) + + IconToggleItem( + title = stringResource(R.string.wifi_auto_off_title), + description = stringResource(R.string.wifi_auto_off_desc), + isChecked = viewModel.isWifiAutoOffEnabled.value, + onCheckedChange = { enabled -> + viewModel.setWifiAutoOffEnabled(enabled) + }, + enabled = true, + iconRes = R.drawable.rounded_power_settings_new_24, + modifier = Modifier.highlight(highlightSetting == "wifi_auto_off_toggle") + ) + + ConfigSliderItem( + title = stringResource(R.string.wifi_auto_off_timeout_title), + value = viewModel.wifiAutoOffTimeout.floatValue, + onValueChange = { seconds -> + viewModel.setWifiAutoOffTimeout(seconds) + }, + valueRange = 10f..300f, + steps = 29, + increment = 10f, + valueFormatter = { "${it.toInt()}s" }, + enabled = viewModel.isWifiAutoOffEnabled.value, + iconRes = R.drawable.rounded_timer_24, + modifier = Modifier.highlight(highlightSetting == "wifi_auto_off_timeout_slider") + ) } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9a4316f02..f088d203a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -422,6 +422,15 @@ Smart WiFi Hide mobile data when WiFi is connected Hide mobile data in certain modes + + Auto Turn Off Wi-Fi + Automatically turn off Wi-Fi when disconnected from any network + Auto Turn Off Timeout + Delay before Wi-Fi turns off: %1$d seconds + Auto Turn Off Wi-Fi + Automatically turn off Wi-Fi when disconnected from network + Wi-Fi auto turn off timeout + Adjust delay before Wi-Fi is automatically disabled Reset All Icons More Settings Advanced (Requires Shizuku/Root)