From ab06dc6e459b5114866bb919a12491d721bfc59e Mon Sep 17 00:00:00 2001 From: Mudit200408 Date: Fri, 10 Jul 2026 15:06:27 +0530 Subject: [PATCH] feat: implement pocket mode exclusions and active app checks - Improve the app detection and also filter system noise --- .../domain/registry/FeatureRegistry.kt | 2 +- .../services/AppDetectionService.kt | 21 +++++ .../services/handlers/AppFlowHandler.kt | 63 ++++++++++++- .../tiles/ScreenOffAccessibilityService.kt | 88 +++++++++++++++---- .../configs/PocketModeSettingsUI.kt | 2 +- .../essentials/viewmodels/MainViewModel.kt | 5 +- 6 files changed, 161 insertions(+), 20 deletions(-) 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 35c2e0bea..991a0af67 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 @@ -962,7 +962,7 @@ object FeatureRegistry { override fun isEnabled(viewModel: MainViewModel) = viewModel.isPocketModeEnabled.value override fun onToggle(viewModel: MainViewModel, context: Context, enabled: Boolean) = - viewModel.setPocketModeEnabled(enabled) + viewModel.setPocketModeEnabled(enabled, context) override fun isDeviceSupported(context: Context) = !DeviceUtils.isGoogleDevice() }, diff --git a/app/src/main/java/com/sameerasw/essentials/services/AppDetectionService.kt b/app/src/main/java/com/sameerasw/essentials/services/AppDetectionService.kt index 0447434dc..c9f83ef05 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/AppDetectionService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/AppDetectionService.kt @@ -4,6 +4,7 @@ import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.Service +import android.app.usage.UsageEvents import android.app.usage.UsageStats import android.app.usage.UsageStatsManager import android.content.BroadcastReceiver @@ -101,6 +102,26 @@ class AppDetectionService : Service() { private fun getForegroundPackage(): String? { val usageStatsManager = getSystemService(USAGE_STATS_SERVICE) as UsageStatsManager val time = System.currentTimeMillis() + + // 1. Try to find the last resumed activity using queryEvents (real-time & accurate) + try { + val events = usageStatsManager.queryEvents(time - 1000 * 15, time) + val event = UsageEvents.Event() + var lastResumedPackage: String? = null + while (events.hasNextEvent()) { + events.getNextEvent(event) + if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED) { + lastResumedPackage = event.packageName + } + } + if (lastResumedPackage != null) { + return lastResumedPackage + } + } catch (e: Exception) { + android.util.Log.e("AppDetectionService", "Failed to query usage events", e) + } + + // 2. Fallback to queryUsageStats if no events found in the window val stats = usageStatsManager.queryUsageStats( UsageStatsManager.INTERVAL_DAILY, time - 1000 * 10, 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 1a6b75f94..cab12289f 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 @@ -9,8 +9,10 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager +import android.media.AudioManager import android.os.Handler import android.os.Looper +import android.view.inputmethod.InputMethodManager import android.provider.Settings import android.util.Log import androidx.core.app.NotificationCompat @@ -105,14 +107,67 @@ class AppFlowHandler( private val ignoredSystemPackages = listOf( "android", "com.android.systemui", - "com.google.android.inputmethod.latin" + "com.google.android.inputmethod.latin", + "com.google.android.gms" ) + private fun isIgnoredPackage(packageName: String): Boolean { + if (ignoredSystemPackages.contains(packageName)) return true + + val lowerPkg = packageName.lowercase() + if (lowerPkg.contains("systemui") || + lowerPkg.contains("keyguard") || + lowerPkg.contains("volume") || + lowerPkg.contains("soundassistant") || + lowerPkg.contains("dialer") || + lowerPkg.contains("telecom") || + lowerPkg.contains("phone") || + lowerPkg.contains("incallui") || + lowerPkg.contains("packageinstaller") || + lowerPkg.contains("permissioncontroller") + ) { + return true + } + + // Check active call state via AudioManager mode + val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager + if (audioManager != null) { + val mode = audioManager.mode + if (mode == AudioManager.MODE_IN_CALL || + mode == AudioManager.MODE_IN_COMMUNICATION || + mode == AudioManager.MODE_RINGTONE + ) { + 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) + Log.d("AppFlowHandler", "onPackageChanged: packageName=$packageName, isFromUsageStats=$isFromUsageStats, useUsageAccess=$useUsageAccess, currentPackage=$currentPackage") + + if (isIgnoredPackage(packageName)) { + Log.d("AppFlowHandler", "onPackageChanged: Ignoring system/IME/volume/call package $packageName") + return + } + val oldPackage = currentPackage + + val serviceInstance = com.sameerasw.essentials.services.tiles.ScreenOffAccessibilityService.instance + if (serviceInstance != null && serviceInstance.isAppBypassedForPocketMode(packageName)) { + serviceInstance.dismissPocketMode() + } + if (isFromUsageStats == useUsageAccess) { currentPackage = packageName if (oldPackage != null && oldPackage != packageName) { @@ -215,7 +270,7 @@ class AppFlowHandler( pendingNLRunnable?.let { handler.removeCallbacks(it) } - if (ignoredSystemPackages.contains(packageName)) { + if (isIgnoredPackage(packageName)) { Log.d("NightLight", "Ignoring system package $packageName") return } @@ -287,6 +342,10 @@ class AppFlowHandler( } private fun checkAppAutomations(packageName: String) { + if (isIgnoredPackage(packageName)) { + Log.d("AppFlowHandler", "checkAppAutomations: Ignoring system/IME package $packageName") + return + } scope.launch { val automations = DIYRepository.automations.value val appAutomations = 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 26d8eaf1d..a558cbfc1 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 @@ -198,8 +198,10 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene statusBarIconHandler.updateAll() } else if (key == "pocket_mode_enabled" || key == "pocket_mode_use_light_sensor") { updatePocketModeSensors() + com.sameerasw.essentials.utils.ServiceUtils.startRequiredServices(this) } else if (key == "pocket_mode_excluded_apps") { updatePocketModeExcludedAppsSet() + com.sameerasw.essentials.utils.ServiceUtils.startRequiredServices(this) } } @@ -249,7 +251,9 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene Intent.ACTION_USER_PRESENT -> { val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) - if (prefs.getBoolean("pocket_mode_lock_screen_only", false)) { + val lockScreenOnly = prefs.getBoolean("pocket_mode_lock_screen_only", false) + val currentApp = appFlowHandler.currentPackage + if (lockScreenOnly || isAppBypassedForPocketMode(currentApp)) { pocketModeHandler.onScreenOff() // cancel pending timer + remove overlay } } @@ -389,6 +393,66 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene override fun onInterrupt() {} + fun isAppBypassedForPocketMode(packageName: String?): Boolean { + val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) + val lockScreenOnly = prefs.getBoolean("pocket_mode_lock_screen_only", false) + if (lockScreenOnly && !keyguardManager.isKeyguardLocked) { + return true + } + + val checkedPackage = if (packageName != null && ( + pocketModeExcludedAppsSet.contains(packageName) || + isGameOrVideoApp(packageName) || + hasActiveMediaSession(packageName) + )) { + packageName + } else { + val realPkg = getActivePackageName() + if (realPkg != null && ( + pocketModeExcludedAppsSet.contains(realPkg) || + isGameOrVideoApp(realPkg) || + hasActiveMediaSession(realPkg) + )) { + realPkg + } else { + packageName + } + } + + val isExcluded = !keyguardManager.isKeyguardLocked && checkedPackage != null && ( + pocketModeExcludedAppsSet.contains(checkedPackage) || + isGameOrVideoApp(checkedPackage) || + hasActiveMediaSession(checkedPackage) + ) + return isExcluded + } + + private fun getActivePackageName(): String? { + val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) + val useUsageAccess = prefs.getBoolean("use_usage_access", false) + if (useUsageAccess) { + try { + val usm = getSystemService(USAGE_STATS_SERVICE) as? android.app.usage.UsageStatsManager + val time = System.currentTimeMillis() + val events = usm?.queryEvents(time - 1000 * 15, time) + val event = android.app.usage.UsageEvents.Event() + var lastResumed: String? = null + while (events?.hasNextEvent() == true) { + events.getNextEvent(event) + if (event.eventType == android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED) { + lastResumed = event.packageName + } + } + if (lastResumed != null) return lastResumed + } catch (_: Exception) {} + } + return rootInActiveWindow?.packageName?.toString() + } + + fun dismissPocketMode() { + pocketModeHandler.onScreenOff() + } + private fun updatePocketModeSensors() { val prefs = getSharedPreferences("essentials_prefs", MODE_PRIVATE) val pocketModeEnabled = prefs.getBoolean("pocket_mode_enabled", false) @@ -434,15 +498,12 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene val pocketModeEnabled = prefs.getBoolean("pocket_mode_enabled", false) val useLightSensor = prefs.getBoolean("pocket_mode_use_light_sensor", false) val triggerDelayMs = (prefs.getFloat("pocket_mode_trigger_delay", 3f) * 1000).toLong() - val lockScreenOnly = prefs.getBoolean("pocket_mode_lock_screen_only", false) if (pocketModeEnabled && !pocketModeHandler.isBypassed) { val currentApp = appFlowHandler.currentPackage - val shouldBypass = (currentApp != null && ( - pocketModeExcludedAppsSet.contains(currentApp) || - isGameOrVideoApp(currentApp) || - hasActiveMediaSession(currentApp) - )) || (lockScreenOnly && !keyguardManager.isKeyguardLocked) - if (!shouldBypass) { + val shouldBypass = isAppBypassedForPocketMode(currentApp) + if (shouldBypass) { + pocketModeHandler.onScreenOff() + } else { pocketModeHandler.onProximityChanged( isBlocked = flashlightHandler.isProximityBlocked, isLightDark = lightSensorLux <= 3f, @@ -470,15 +531,12 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene val pocketModeEnabled = prefs.getBoolean("pocket_mode_enabled", false) val useLightSensor = prefs.getBoolean("pocket_mode_use_light_sensor", false) val triggerDelayMs = (prefs.getFloat("pocket_mode_trigger_delay", 3f) * 1000).toLong() - val lockScreenOnly = prefs.getBoolean("pocket_mode_lock_screen_only", false) if (pocketModeEnabled && !pocketModeHandler.isBypassed) { val currentApp = appFlowHandler.currentPackage - val shouldBypass = (currentApp != null && ( - pocketModeExcludedAppsSet.contains(currentApp) || - isGameOrVideoApp(currentApp) || - hasActiveMediaSession(currentApp) - )) || (lockScreenOnly && !keyguardManager.isKeyguardLocked) - if (!shouldBypass) { + val shouldBypass = isAppBypassedForPocketMode(currentApp) + if (shouldBypass) { + pocketModeHandler.onScreenOff() + } else { pocketModeHandler.onProximityChanged( isBlocked = isBlocked, isLightDark = lightSensorLux <= 3f, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/PocketModeSettingsUI.kt b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/PocketModeSettingsUI.kt index 0e82ae603..eb2754d20 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/PocketModeSettingsUI.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/composables/configs/PocketModeSettingsUI.kt @@ -62,7 +62,7 @@ fun PocketModeSettingsUI( description = stringResource(R.string.feat_pocket_mode_desc), isChecked = viewModel.isPocketModeEnabled.value, onCheckedChange = { isChecked -> - viewModel.setPocketModeEnabled(isChecked) + viewModel.setPocketModeEnabled(isChecked, context) }, enabled = true, iconRes = R.drawable.ic_pocket_mode, diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index f47245904..e554bbfc1 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -4035,9 +4035,10 @@ class MainViewModel : ViewModel() { isAodForceTurnOffEnabled.value = enabled } - fun setPocketModeEnabled(enabled: Boolean) { + fun setPocketModeEnabled(enabled: Boolean, context: Context) { settingsRepository.putBoolean(SettingsRepository.KEY_POCKET_MODE_ENABLED, enabled) isPocketModeEnabled.value = enabled + updateAppDetectionService(context) } fun setPocketModeUseLightSensor(enabled: Boolean) { @@ -4085,6 +4086,7 @@ class MainViewModel : ViewModel() { fun savePocketModeExcludedApps(context: Context, apps: List) { settingsRepository.savePocketModeExcludedApps(apps) + updateAppDetectionService(context) } fun updatePocketModeExcludedAppEnabled( @@ -4093,6 +4095,7 @@ class MainViewModel : ViewModel() { enabled: Boolean ) { settingsRepository.updatePocketModeExcludedAppSelection(packageName, enabled) + updateAppDetectionService(context) } override fun onCleared() {