From 674a78276df51e99a1216c3ab8cce46349acca60 Mon Sep 17 00:00:00 2001 From: Mudit200408 Date: Sat, 30 May 2026 19:26:42 +0530 Subject: [PATCH 1/3] feat: Add WiFi, Cellular Data, and Auto Brightness automation actions --- .../sameerasw/essentials/domain/diy/Action.kt | 48 +++- .../executors/CombinedActionExecutor.kt | 33 +++ .../ui/activities/AutomationEditorActivity.kt | 230 ++++++++++++------ app/src/main/res/values/strings.xml | 6 + 4 files changed, 238 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt index 3716b8e67..2d5a8da4b 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt @@ -60,7 +60,7 @@ sealed interface Action { ) : Action { override val title: Int get() = R.string.diy_action_dim_wallpaper override val icon: Int get() = R.drawable.rounded_mobile_screensaver_24 - override val permissions: List = listOf("shizuku", "root") + override val permissions: List = listOf("SHIZUKU", "ROOT") override val isConfigurable: Boolean = true } @@ -74,7 +74,7 @@ sealed interface Action { ) : Action { override val title: Int get() = R.string.diy_action_device_effects override val icon: Int get() = R.drawable.rounded_bed_24 - override val permissions: List = listOf("notification_policy") + override val permissions: List = listOf("NOTIFICATION_POLICY") override val isConfigurable: Boolean = true } @@ -101,7 +101,7 @@ sealed interface Action { SoundModeType.VIBRATE -> R.drawable.rounded_mobile_vibrate_24 SoundModeType.SILENT -> R.drawable.rounded_volume_off_24 } - override val permissions: List = listOf("notification_policy") + override val permissions: List = listOf("NOTIFICATION_POLICY") override val isConfigurable: Boolean = true } @@ -116,4 +116,46 @@ sealed interface Action { override val title: Int = R.string.diy_action_low_power_off override val icon: Int = R.drawable.rounded_battery_android_frame_shield_24 } + + @Keep + data object TurnOnWifi : Action { + override val title: Int = R.string.diy_action_wifi_on + override val icon: Int = R.drawable.rounded_android_wifi_4_bar_plus_24 + override val permissions: List = listOf("SHIZUKU", "ROOT") + } + + @Keep + data object TurnOffWifi : Action { + override val title: Int = R.string.diy_action_wifi_off + override val icon: Int = R.drawable.rounded_android_wifi_4_bar_plus_24 + override val permissions: List = listOf("SHIZUKU", "ROOT") + } + + @Keep + data object TurnOnCellularData : Action { + override val title: Int = R.string.diy_action_cellular_on + override val icon: Int = R.drawable.rounded_signal_cellular_alt_24 + override val permissions: List = listOf("SHIZUKU", "ROOT") + } + + @Keep + data object TurnOffCellularData : Action { + override val title: Int = R.string.diy_action_cellular_off + override val icon: Int = R.drawable.rounded_signal_cellular_alt_24 + override val permissions: List = listOf("SHIZUKU", "ROOT") + } + + @Keep + data object TurnOnAutoBrightness : Action { + override val title: Int = R.string.diy_action_auto_brightness_on + override val icon: Int = R.drawable.rounded_brightness_auto_24 + override val permissions: List = listOf("WRITE_SETTINGS") + } + + @Keep + data object TurnOffAutoBrightness : Action { + override val title: Int = R.string.diy_action_auto_brightness_off + override val icon: Int = R.drawable.rounded_brightness_auto_24 + override val permissions: List = listOf("WRITE_SETTINGS") + } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt index bda28e088..83d6a1b6d 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt @@ -195,6 +195,13 @@ object CombinedActionExecutor { e.printStackTrace() } } + + is Action.TurnOnWifi -> setWifiEnabled(context, true) + is Action.TurnOffWifi -> setWifiEnabled(context, false) + is Action.TurnOnCellularData -> setCellularDataEnabled(context, true) + is Action.TurnOffCellularData -> setCellularDataEnabled(context, false) + is Action.TurnOnAutoBrightness -> setAutoBrightnessEnabled(context, true) + is Action.TurnOffAutoBrightness -> setAutoBrightnessEnabled(context, false) } } } @@ -217,4 +224,30 @@ object CombinedActionExecutor { e.printStackTrace() } } + + private fun setWifiEnabled(context: Context, enabled: Boolean) { + val state = if (enabled) "enable" else "disable" + com.sameerasw.essentials.utils.ShellUtils.runCommand(context, "svc wifi $state") + } + + private fun setCellularDataEnabled(context: Context, enabled: Boolean) { + val state = if (enabled) "enable" else "disable" + com.sameerasw.essentials.utils.ShellUtils.runCommand(context, "svc data $state") + } + + private fun setAutoBrightnessEnabled(context: Context, enabled: Boolean) { + val value = if (enabled) 1 else 0 + try { + android.provider.Settings.System.putInt( + context.contentResolver, + android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE, + value + ) + } catch (e: Exception) { + com.sameerasw.essentials.utils.ShellUtils.runCommand( + context, + "settings put system screen_brightness_mode $value" + ) + } + } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt index 4b5fc4074..319036d4a 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt @@ -37,7 +37,10 @@ import androidx.compose.material3.Text import androidx.compose.material3.carousel.HorizontalMultiBrowseCarousel import androidx.compose.material3.carousel.rememberCarouselState import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.Lifecycle import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -226,6 +229,24 @@ class AutomationEditorActivity : ComponentActivity() { var showTimeSettings by remember { mutableStateOf(false) } var configAction by remember { mutableStateOf(null) } // Generic config action + var showPermissionSheet by remember { mutableStateOf(false) } + var permissionKeysToShow by remember { mutableStateOf>(emptyList()) } + var permissionFeatureTitle by remember { mutableStateOf("") } + + // Automatic refresh on resume + val lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current + DisposableEffect(lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + if (event == Lifecycle.Event.ON_RESUME) { + viewModel.check(context) + } + } + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + } + } + // Validation val isValid = when (automationType) { Automation.Type.TRIGGER -> selectedTrigger != null && selectedAction != null @@ -558,7 +579,13 @@ class AutomationEditorActivity : ComponentActivity() { Action.DimWallpaper(), Action.SoundMode(), Action.TurnOnLowPower, - Action.TurnOffLowPower + Action.TurnOffLowPower, + Action.TurnOnWifi, + Action.TurnOffWifi, + Action.TurnOnCellularData, + Action.TurnOffCellularData, + Action.TurnOnAutoBrightness, + Action.TurnOffAutoBrightness ) // Only show Device Effects on Android 15+ actions.add(Action.DeviceEffects()) @@ -599,32 +626,26 @@ class AutomationEditorActivity : ComponentActivity() { iconRes = resolvedAction.icon, isSelected = currentSelection != null && currentSelection::class == resolvedAction::class, isConfigurable = resolvedAction.isConfigurable, - onClick = { - when (automationType) { - Automation.Type.TRIGGER -> selectedAction = - resolvedAction - - Automation.Type.STATE, Automation.Type.APP -> { - if (selectedActionTab == 0) selectedInAction = - resolvedAction - else selectedOutAction = - resolvedAction - } - } - // Check permissions immediately on selection - // For Device Effects, we need Notification Policy Access - if (resolvedAction is Action.DeviceEffects) { - val nm = - context.getSystemService( - NOTIFICATION_SERVICE - ) as android.app.NotificationManager - if (!nm.isNotificationPolicyAccessGranted) { - val intent = - Intent(android.provider.Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS) - context.startActivity(intent) - } - } - }, + onClick = { + when (automationType) { + Automation.Type.TRIGGER -> selectedAction = + resolvedAction + + Automation.Type.STATE, Automation.Type.APP -> { + if (selectedActionTab == 0) selectedInAction = + resolvedAction + else selectedOutAction = + resolvedAction + } + } + // Check permissions immediately on selection + val missing = getMissingPermissions(context, resolvedAction, viewModel) + if (missing.isNotEmpty()) { + permissionKeysToShow = missing + permissionFeatureTitle = resolvedAction.title + showPermissionSheet = true + } + }, onSettingsClick = { configAction = resolvedAction if (resolvedAction is Action.DimWallpaper) { @@ -713,6 +734,25 @@ class AutomationEditorActivity : ComponentActivity() { } ) } + + if (showPermissionSheet) { + val permissionItems = com.sameerasw.essentials.utils.PermissionUIHelper.getPermissionItems( + permissionKeysToShow, + context, + viewModel, + this@AutomationEditorActivity + ) + if (permissionItems.isNotEmpty()) { + com.sameerasw.essentials.ui.components.sheets.PermissionsBottomSheet( + onDismissRequest = { + showPermissionSheet = false + permissionKeysToShow = emptyList() + }, + featureTitle = permissionFeatureTitle, + permissions = permissionItems + ) + } + } // Bottom Actions Row( @@ -744,62 +784,100 @@ class AutomationEditorActivity : ComponentActivity() { Button( onClick = { HapticUtil.performVirtualKeyHaptic(view) - // Save logic - if (automationType == Automation.Type.TRIGGER) { - val newAutomation = Automation( - id = if (isEditMode) existingAutomation.id else java.util.UUID.randomUUID() - .toString(), - type = Automation.Type.TRIGGER, - trigger = selectedTrigger, - actions = listOfNotNull(selectedAction) - ) - if (isEditMode) DIYRepository.updateAutomation(newAutomation) else DIYRepository.addAutomation( - newAutomation - ) - } else if (automationType == Automation.Type.STATE) { - val newAutomation = Automation( - id = if (isEditMode) existingAutomation.id else java.util.UUID.randomUUID() - .toString(), - type = Automation.Type.STATE, - state = selectedState, - entryAction = selectedInAction, - exitAction = selectedOutAction - ) - if (isEditMode) DIYRepository.updateAutomation(newAutomation) else DIYRepository.addAutomation( - newAutomation - ) - } else { - val newAutomation = Automation( - id = if (isEditMode) existingAutomation.id else java.util.UUID.randomUUID() - .toString(), - type = Automation.Type.APP, - selectedApps = selectedApps, - entryAction = selectedInAction, - exitAction = selectedOutAction - ) - if (isEditMode) DIYRepository.updateAutomation(newAutomation) else DIYRepository.addAutomation( - newAutomation - ) + // Check for missing permissions before saving + val actionsToCheck = when (automationType) { + Automation.Type.TRIGGER -> listOfNotNull(selectedAction) + else -> listOfNotNull(selectedInAction, selectedOutAction) } - finish() - }, - modifier = Modifier.weight(1f), - enabled = isValid - ) { - Icon( - painter = painterResource(id = R.drawable.rounded_check_24), - contentDescription = null, - modifier = Modifier.size(20.dp) - ) - Spacer(modifier = Modifier.size(8.dp)) - Text(stringResource(R.string.action_save)) - } + val allMissingPermissions = actionsToCheck.flatMap { getMissingPermissions(context, it, viewModel) }.distinct() + if (allMissingPermissions.isNotEmpty()) { + permissionKeysToShow = allMissingPermissions + permissionFeatureTitle = R.string.tab_diy + showPermissionSheet = true + return@Button + } + // Save logic + if (automationType == Automation.Type.TRIGGER) { + val newAutomation = Automation( + id = if (isEditMode) existingAutomation.id else java.util.UUID.randomUUID() + .toString(), + type = Automation.Type.TRIGGER, + trigger = selectedTrigger, + actions = listOfNotNull(selectedAction) + ) + if (isEditMode) DIYRepository.updateAutomation(newAutomation) else DIYRepository.addAutomation( + newAutomation + ) + } else if (automationType == Automation.Type.STATE) { + val newAutomation = Automation( + id = if (isEditMode) existingAutomation.id else java.util.UUID.randomUUID() + .toString(), + type = Automation.Type.STATE, + state = selectedState, + entryAction = selectedInAction, + exitAction = selectedOutAction + ) + if (isEditMode) DIYRepository.updateAutomation(newAutomation) else DIYRepository.addAutomation( + newAutomation + ) + } else { + val newAutomation = Automation( + id = if (isEditMode) existingAutomation.id else java.util.UUID.randomUUID() + .toString(), + type = Automation.Type.APP, + selectedApps = selectedApps, + entryAction = selectedInAction, + exitAction = selectedOutAction + ) + if (isEditMode) DIYRepository.updateAutomation(newAutomation) else DIYRepository.addAutomation( + newAutomation + ) + } + finish() + }, + modifier = Modifier.weight(1f), + enabled = isValid + ) { + Icon( + painter = painterResource(id = R.drawable.rounded_check_24), + contentDescription = null, + modifier = Modifier.size(20.dp) + ) + Spacer(modifier = Modifier.size(8.dp)) + Text(stringResource(R.string.action_save)) + } } } } } } } + + private fun getMissingPermissions( + context: Context, + action: Action?, + viewModel: com.sameerasw.essentials.viewmodels.MainViewModel + ): List { + if (action == null) return emptyList() + val resolvedPermissions = action.permissions.map { permKey -> + if (permKey == "SHIZUKU" || permKey == "ROOT") { + if (com.sameerasw.essentials.utils.ShellUtils.isRootEnabled(context)) "ROOT" else "SHIZUKU" + } else { + permKey + } + }.distinct() + + return resolvedPermissions.filter { permKey -> + when (permKey) { + "SHIZUKU" -> !viewModel.isShizukuPermissionGranted.value + "ROOT" -> !viewModel.isRootPermissionGranted.value + "WRITE_SETTINGS" -> !viewModel.isWriteSettingsEnabled.value + "NOTIFICATION_POLICY" -> !viewModel.isNotificationPolicyAccessGranted.value + "WRITE_SECURE_SETTINGS" -> !viewModel.isWriteSecureSettingsEnabled.value + else -> false + } + } + } } @Composable diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2d79cf8a2..322149011 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -840,6 +840,12 @@ Toggle Flashlight Turn On Low Power Mode Turn Off Low Power Mode + Turn On WiFi + Turn Off WiFi + Turn On Cellular Data + Turn Off Cellular Data + Turn On Auto Brightness + Turn Off Auto Brightness Dim Wallpaper This action requires Shizuku or Root to adjust system wallpaper dimming. Select Trigger From e766b47904fd755aaeecaeb68968a6b9aeea5c1b Mon Sep 17 00:00:00 2001 From: Mudit200408 Date: Mon, 1 Jun 2026 00:20:28 +0530 Subject: [PATCH 2/3] feat: Add Freeze/Unfreeze apps in Automations --- .../sameerasw/essentials/domain/diy/Action.kt | 21 +++++ .../executors/CombinedActionExecutor.kt | 11 +++ .../ui/activities/AutomationEditorActivity.kt | 87 ++++++++++++++----- .../components/sheets/AppSelectionSheets.kt | 4 +- app/src/main/res/values/strings.xml | 5 ++ 5 files changed, 106 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt b/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt index 2d5a8da4b..9342092a1 100644 --- a/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt +++ b/app/src/main/java/com/sameerasw/essentials/domain/diy/Action.kt @@ -158,4 +158,25 @@ sealed interface Action { override val icon: Int = R.drawable.rounded_brightness_auto_24 override val permissions: List = listOf("WRITE_SETTINGS") } + + @Keep + data class FreezeApps( + @SerializedName("packageNames") val packageNames: List = emptyList() + ) : Action { + override val title: Int get() = R.string.diy_action_freeze_apps + override val icon: Int get() = R.drawable.rounded_mode_cool_24 + override val permissions: List = listOf("SHIZUKU", "ROOT") + override val isConfigurable: Boolean = true + } + + @Keep + data class UnfreezeApps( + @SerializedName("packageNames") val packageNames: List = emptyList() + ) : Action { + override val title: Int get() = R.string.diy_action_unfreeze_apps + override val icon: Int get() = R.drawable.rounded_mode_cool_off_24 + override val permissions: List = listOf("SHIZUKU", "ROOT") + override val isConfigurable: Boolean = true + } } + diff --git a/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt b/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt index 83d6a1b6d..45cb94d97 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/automation/executors/CombinedActionExecutor.kt @@ -202,7 +202,18 @@ object CombinedActionExecutor { is Action.TurnOffCellularData -> setCellularDataEnabled(context, false) is Action.TurnOnAutoBrightness -> setAutoBrightnessEnabled(context, true) is Action.TurnOffAutoBrightness -> setAutoBrightnessEnabled(context, false) + is Action.FreezeApps -> { + action.packageNames.forEach { pkg -> + com.sameerasw.essentials.utils.FreezeManager.freezeApp(context, pkg) + } + } + is Action.UnfreezeApps -> { + action.packageNames.forEach { pkg -> + com.sameerasw.essentials.utils.FreezeManager.unfreezeApp(context, pkg) + } + } } + } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt b/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt index 319036d4a..95b2d44c9 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/activities/AutomationEditorActivity.kt @@ -73,8 +73,10 @@ import com.sameerasw.essentials.ui.components.containers.RoundedCardContainer import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenu import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem import com.sameerasw.essentials.ui.components.pickers.SegmentedPicker +import com.sameerasw.essentials.ui.components.sheets.AppSelectionSheet import com.sameerasw.essentials.ui.components.sheets.DimWallpaperSettingsSheet import com.sameerasw.essentials.ui.components.sheets.SoundModeSettingsSheet + import com.sameerasw.essentials.ui.theme.EssentialsTheme import com.sameerasw.essentials.utils.AppUtil import com.sameerasw.essentials.utils.HapticUtil @@ -226,9 +228,12 @@ class AutomationEditorActivity : ComponentActivity() { var showDimSettings by remember { mutableStateOf(false) } var showDeviceEffectsSettings by remember { mutableStateOf(false) } var showSoundModeSettings by remember { mutableStateOf(false) } + var showFreezeAppsSettings by remember { mutableStateOf(false) } + var temporarySelectedAppsForAction by remember { mutableStateOf>(emptyList()) } var showTimeSettings by remember { mutableStateOf(false) } var configAction by remember { mutableStateOf(null) } // Generic config action + var showPermissionSheet by remember { mutableStateOf(false) } var permissionKeysToShow by remember { mutableStateOf>(emptyList()) } var permissionFeatureTitle by remember { mutableStateOf("") } @@ -585,7 +590,9 @@ class AutomationEditorActivity : ComponentActivity() { Action.TurnOnCellularData, Action.TurnOffCellularData, Action.TurnOnAutoBrightness, - Action.TurnOffAutoBrightness + Action.TurnOffAutoBrightness, + Action.FreezeApps(), + Action.UnfreezeApps() ) // Only show Device Effects on Android 15+ actions.add(Action.DeviceEffects()) @@ -626,26 +633,26 @@ class AutomationEditorActivity : ComponentActivity() { iconRes = resolvedAction.icon, isSelected = currentSelection != null && currentSelection::class == resolvedAction::class, isConfigurable = resolvedAction.isConfigurable, - onClick = { - when (automationType) { - Automation.Type.TRIGGER -> selectedAction = - resolvedAction - - Automation.Type.STATE, Automation.Type.APP -> { - if (selectedActionTab == 0) selectedInAction = - resolvedAction - else selectedOutAction = - resolvedAction - } - } - // Check permissions immediately on selection - val missing = getMissingPermissions(context, resolvedAction, viewModel) - if (missing.isNotEmpty()) { - permissionKeysToShow = missing - permissionFeatureTitle = resolvedAction.title - showPermissionSheet = true - } - }, + onClick = { + when (automationType) { + Automation.Type.TRIGGER -> selectedAction = + resolvedAction + + Automation.Type.STATE, Automation.Type.APP -> { + if (selectedActionTab == 0) selectedInAction = + resolvedAction + else selectedOutAction = + resolvedAction + } + } + // Check permissions immediately on selection + val missing = getMissingPermissions(context, resolvedAction, viewModel) + if (missing.isNotEmpty()) { + permissionKeysToShow = missing + permissionFeatureTitle = resolvedAction.title + showPermissionSheet = true + } + }, onSettingsClick = { configAction = resolvedAction if (resolvedAction is Action.DimWallpaper) { @@ -654,6 +661,12 @@ class AutomationEditorActivity : ComponentActivity() { showDeviceEffectsSettings = true } else if (resolvedAction is Action.SoundMode) { showSoundModeSettings = true + } else if (resolvedAction is Action.FreezeApps) { + temporarySelectedAppsForAction = resolvedAction.packageNames + showFreezeAppsSettings = true + } else if (resolvedAction is Action.UnfreezeApps) { + temporarySelectedAppsForAction = resolvedAction.packageNames + showFreezeAppsSettings = true } } ) @@ -734,6 +747,38 @@ class AutomationEditorActivity : ComponentActivity() { } ) } + + if (showFreezeAppsSettings && (configAction is Action.FreezeApps || configAction is Action.UnfreezeApps)) { + AppSelectionSheet( + onDismissRequest = { + val finalAction = when (val action = configAction) { + is Action.FreezeApps -> action.copy(packageNames = temporarySelectedAppsForAction) + is Action.UnfreezeApps -> action.copy(packageNames = temporarySelectedAppsForAction) + else -> configAction + } + if (finalAction != null) { + when (automationType) { + Automation.Type.TRIGGER -> selectedAction = finalAction + Automation.Type.STATE, Automation.Type.APP -> { + if (selectedActionTab == 0) selectedInAction = finalAction + else selectedOutAction = finalAction + } + } + } + showFreezeAppsSettings = false + configAction = null + }, + onLoadApps = { + temporarySelectedAppsForAction.map { AppSelection(it, true) } + }, + onSaveApps = { _, selections -> + temporarySelectedAppsForAction = selections.filter { it.isEnabled }.map { it.packageName } + }, + excludePackages = if (automationType == Automation.Type.APP) selectedApps else emptyList() + ) + } + + if (showPermissionSheet) { val permissionItems = com.sameerasw.essentials.utils.PermissionUIHelper.getPermissionItems( diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/AppSelectionSheets.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/AppSelectionSheets.kt index a1e14b2c0..8ee95ce59 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/AppSelectionSheets.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/AppSelectionSheets.kt @@ -60,6 +60,7 @@ fun AppSelectionSheet( onLoadApps: suspend (Context) -> List, onSaveApps: suspend (Context, List) -> Unit, onAppToggle: ((Context, String, Boolean) -> Unit)? = null, + excludePackages: List = emptyList(), context: Context = LocalContext.current ) { val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true) @@ -112,7 +113,8 @@ fun AppSelectionSheet( searchQuery.isEmpty() || it.appName.contains(searchQuery, ignoreCase = true) val isVisible = !it.isSystemApp || showSystemApps || it.isEnabled // Always show if enabled, or if system toggle checks out - matchesSearch && isVisible + val isExcluded = excludePackages.contains(it.packageName) + matchesSearch && isVisible && !isExcluded } .sortedWith(compareByDescending { initialEnabledPackageNames.contains(it.packageName) }.thenBy { it.appName.lowercase() }) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 322149011..6de300345 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -848,6 +848,11 @@ Turn Off Auto Brightness Dim Wallpaper This action requires Shizuku or Root to adjust system wallpaper dimming. + Freeze Apps + Unfreeze Apps + This action requires Shizuku or Root to freeze specific applications. + This action requires Shizuku or Root to unfreeze specific applications. + Select Trigger App Automate based on open app From c04ac1b29bb8b59368f0034ee1e869826a4637d7 Mon Sep 17 00:00:00 2001 From: Mudit200408 Date: Mon, 1 Jun 2026 18:08:28 +0530 Subject: [PATCH 3/3] feat: Improve the app automation so that system ui or keyboard does not trigger out-action --- .../services/handlers/AppFlowHandler.kt | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt index f554d622f..428266fb5 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/AppFlowHandler.kt @@ -14,6 +14,7 @@ import android.os.Looper import android.provider.Settings import android.util.Log import androidx.core.app.NotificationCompat +import android.view.inputmethod.InputMethodManager import com.google.gson.Gson import com.sameerasw.essentials.domain.diy.Automation import com.sameerasw.essentials.domain.diy.DIYRepository @@ -92,10 +93,28 @@ class AppFlowHandler( "com.google.android.inputmethod.latin" ) + private fun isSystemOrIme(packageName: String): Boolean { + if (ignoredSystemPackages.contains(packageName)) return true + return try { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager + val ims = imm?.enabledInputMethodList + ims?.any { it.packageName == packageName } == true + } catch (_: Exception) { + false + } + } + fun onPackageChanged(packageName: String, isFromUsageStats: Boolean = false) { val prefs = context.getSharedPreferences("essentials_prefs", Context.MODE_PRIVATE) val useUsageAccess = prefs.getBoolean("use_usage_access", false) + // If the new foreground window belongs to a system overlay (status bar, quick settings, + // notifications) or a keyboard (IME), completely ignore it. We do NOT update currentPackage + // so that state-dependent features (app lock timing, automation tracking) remain stable. + if (isSystemOrIme(packageName)) { + Log.d("AppFlowHandler", "onPackageChanged: Ignoring system/IME package $packageName") + return + } val oldPackage = currentPackage currentPackage = packageName @@ -203,7 +222,7 @@ class AppFlowHandler( pendingNLRunnable?.let { handler.removeCallbacks(it) } - if (ignoredSystemPackages.contains(packageName)) { + if (isSystemOrIme(packageName)) { Log.d("NightLight", "Ignoring system package $packageName") return } @@ -275,6 +294,10 @@ class AppFlowHandler( } private fun checkAppAutomations(packageName: String) { + if (isSystemOrIme(packageName)) { + Log.d("AppFlowHandler", "checkAppAutomations: Ignoring system/IME package $packageName") + return + } scope.launch { val automations = DIYRepository.automations.value val appAutomations =