From d034e7a589918c8ad26a6d523541d3137ef50d5b Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 09:12:16 +0530 Subject: [PATCH 01/18] Fix missing newline at end of ExperimentalAppLockService.kt Add missing newline at the end of the file. From b9506fd24ac66aed16b2057c718099f4d497f72d Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 09:18:58 +0530 Subject: [PATCH 02/18] Add RestrictSettingsState data class and enum --- .../pranav/applock/RestrictSettingsState.kt | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 app/src/main/java/dev/pranav/applock/RestrictSettingsState.kt diff --git a/app/src/main/java/dev/pranav/applock/RestrictSettingsState.kt b/app/src/main/java/dev/pranav/applock/RestrictSettingsState.kt new file mode 100644 index 0000000..9a753df --- /dev/null +++ b/app/src/main/java/dev/pranav/applock/RestrictSettingsState.kt @@ -0,0 +1,27 @@ +package dev.pranav.applock.core.security + +data class RestrictSettingsState( + + val blockOverlaySettings: Boolean = false, + + val blockUsageAccessSettings: Boolean = false, + + val blockAccessibilitySettings: Boolean = false, + + val blockDeviceAdminSettings: Boolean = false, + + val requireBatteryExemption: Boolean = false +) + +enum class RestrictSetting { + + OVERLAY, + + USAGE, + + ACCESSIBILITY, + + DEVICE_ADMIN, + + BATTERY +} From ff7716085638955994b4fcb8cb4f4705dd32935b Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 09:20:07 +0530 Subject: [PATCH 03/18] Add RestrictSystemSettingsSection composable --- .../ui/RestrictSystemSettingsSection.kt | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 app/src/main/java/dev/pranav/applock/ui/RestrictSystemSettingsSection.kt diff --git a/app/src/main/java/dev/pranav/applock/ui/RestrictSystemSettingsSection.kt b/app/src/main/java/dev/pranav/applock/ui/RestrictSystemSettingsSection.kt new file mode 100644 index 0000000..372cc3a --- /dev/null +++ b/app/src/main/java/dev/pranav/applock/ui/RestrictSystemSettingsSection.kt @@ -0,0 +1,59 @@ +package dev.pranav.applock.features.settings.ui + +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import dev.pranav.applock.core.security.RestrictSetting +import dev.pranav.applock.core.security.RestrictSettingsState + +@Composable +fun RestrictSystemSettingsSection( + + antiUninstallEnabled: Boolean, + + state: RestrictSettingsState, + + onToggle: (RestrictSetting, Boolean) -> Unit +) { + + if (!antiUninstallEnabled) return + + Column { + + Text( + text = "Restrict System Settings Access", + style = MaterialTheme.typography.titleMedium + ) + + SwitchPreference( + title = "Disable Draw Over Other Apps Settings", + checked = state.blockOverlaySettings, + onCheckedChange = { onToggle(RestrictSetting.OVERLAY, it) } + ) + + SwitchPreference( + title = "Disable Usage Access Settings", + checked = state.blockUsageAccessSettings, + onCheckedChange = { onToggle(RestrictSetting.USAGE, it) } + ) + + SwitchPreference( + title = "Disable Accessibility Settings", + checked = state.blockAccessibilitySettings, + onCheckedChange = { onToggle(RestrictSetting.ACCESSIBILITY, it) } + ) + + SwitchPreference( + title = "Disable Device Admin Settings", + checked = state.blockDeviceAdminSettings, + onCheckedChange = { onToggle(RestrictSetting.DEVICE_ADMIN, it) } + ) + + SwitchPreference( + title = "Require Unrestricted Battery Usage", + checked = state.requireBatteryExemption, + onCheckedChange = { onToggle(RestrictSetting.BATTERY, it) } + ) + } +} From c128bb703bfadb950b1020a1ec5e961893d1a5ce Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 09:21:24 +0530 Subject: [PATCH 04/18] Add BatteryProtection.kt for battery optimization exemption --- .../dev/pranav/applock/BatteryProtection.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 app/src/main/java/dev/pranav/applock/BatteryProtection.kt diff --git a/app/src/main/java/dev/pranav/applock/BatteryProtection.kt b/app/src/main/java/dev/pranav/applock/BatteryProtection.kt new file mode 100644 index 0000000..6cba48b --- /dev/null +++ b/app/src/main/java/dev/pranav/applock/BatteryProtection.kt @@ -0,0 +1,22 @@ +package dev.pranav.applock.core.security + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.PowerManager +import android.provider.Settings + +fun enforceBatteryExemption(context: Context) { + + val pm = context.getSystemService(PowerManager::class.java) + + if (pm.isIgnoringBatteryOptimizations(context.packageName)) return + + val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) + + intent.data = Uri.parse("package:${context.packageName}") + + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + + context.startActivity(intent) +} From 5680a0fd4e8e78f9ee4cc4e55fc2adf01d3a82d7 Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 09:24:23 +0530 Subject: [PATCH 05/18] Fix missing newline at end of AppLockRepository.kt From 8136fc871d316319ffe410189a52be6c5e5d571b Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 09:28:45 +0530 Subject: [PATCH 06/18] Refactor AppLockAccessibilityService for clarity and efficiency --- .../services/AppLockAccessibilityService.kt | 572 ++++-------------- 1 file changed, 128 insertions(+), 444 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt b/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt index 5377dba..cef1694 100644 --- a/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt +++ b/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt @@ -3,586 +3,270 @@ package dev.pranav.applock.services import android.accessibilityservice.AccessibilityService import android.accessibilityservice.AccessibilityServiceInfo import android.annotation.SuppressLint -import android.app.admin.DevicePolicyManager -import android.content.ComponentName import android.content.Intent -import android.content.pm.PackageManager -import android.content.pm.ResolveInfo import android.util.Log import android.view.accessibility.AccessibilityEvent -import android.view.accessibility.AccessibilityNodeInfo import android.view.inputmethod.InputMethodManager -import android.widget.Toast import androidx.core.content.getSystemService -import dev.pranav.applock.core.broadcast.DeviceAdmin import dev.pranav.applock.core.utils.LogUtils import dev.pranav.applock.core.utils.appLockRepository -import dev.pranav.applock.core.utils.enableAccessibilityServiceWithShizuku import dev.pranav.applock.data.repository.AppLockRepository import dev.pranav.applock.data.repository.BackendImplementation import dev.pranav.applock.features.lockscreen.ui.PasswordOverlayActivity -import dev.pranav.applock.services.AppLockConstants.ACCESSIBILITY_SETTINGS_CLASSES -import dev.pranav.applock.services.AppLockConstants.EXCLUDED_APPS -import rikka.shizuku.Shizuku @SuppressLint("AccessibilityPolicy") class AppLockAccessibilityService : AccessibilityService() { - private val appLockRepository: AppLockRepository by lazy { applicationContext.appLockRepository() } - private val keyboardPackages: List by lazy { getKeyboardPackageNames() } - private var recentsOpen = false - private var lastForegroundPackage = "" + private val appLockRepository: AppLockRepository by lazy { applicationContext.appLockRepository() } - enum class BiometricState { - IDLE, AUTH_STARTED + private val keyboardPackages: List by lazy { + getKeyboardPackageNames() } + private var lastForegroundPackage = "" + companion object { + private const val TAG = "AppLockAccessibility" - private const val DEVICE_ADMIN_SETTINGS_PACKAGE = "com.android.settings" - private const val APP_PACKAGE_PREFIX = "dev.pranav.applock" @Volatile var isServiceRunning = false } - private val screenStateReceiver = object: android.content.BroadcastReceiver() { - override fun onReceive(context: android.content.Context?, intent: Intent?) { - try { - if (intent?.action == Intent.ACTION_SCREEN_OFF) { - LogUtils.d(TAG, "Screen off detected. Resetting AppLock state.") - AppLockManager.isLockScreenShown.set(false) - AppLockManager.clearTemporarilyUnlockedApp() - // Optional: Clear all unlock timestamps to force re-lock on next unlock - AppLockManager.appUnlockTimes.clear() - } - } catch (e: Exception) { - logError("Error in screenStateReceiver", e) - } - } - } - override fun onCreate() { super.onCreate() - try { - isServiceRunning = true - AppLockManager.currentBiometricState = BiometricState.IDLE - AppLockManager.isLockScreenShown.set(false) - startPrimaryBackendService() - - val filter = android.content.IntentFilter().apply { - addAction(Intent.ACTION_SCREEN_OFF) - addAction(Intent.ACTION_USER_PRESENT) - } - registerReceiver(screenStateReceiver, filter) - } catch (e: Exception) { - logError("Error in onCreate", e) - } - } - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int = START_STICKY + isServiceRunning = true + + AppLockManager.isLockScreenShown.set(false) + + startPrimaryBackendService() + } override fun onServiceConnected() { - super.onServiceConnected() - try { - serviceInfo = serviceInfo.apply { - eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED or + + serviceInfo = serviceInfo.apply { + + eventTypes = + AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED or AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED or AccessibilityEvent.TYPE_WINDOWS_CHANGED - feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC - packageNames = null - } - Log.d(TAG, "Accessibility service connected") - AppLockManager.resetRestartAttempts(TAG) - appLockRepository.setActiveBackend(BackendImplementation.ACCESSIBILITY) - } catch (e: Exception) { - logError("Error in onServiceConnected", e) + feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC + + packageNames = null } + + Log.d(TAG, "Accessibility service connected") + + appLockRepository.setActiveBackend(BackendImplementation.ACCESSIBILITY) } override fun onAccessibilityEvent(event: AccessibilityEvent) { - Log.d(TAG, event.toString()) + try { + handleAccessibilityEvent(event) + } catch (e: Exception) { - logError("Unhandled error in onAccessibilityEvent", e) + + Log.e(TAG, "Error processing accessibility event", e) } } private fun handleAccessibilityEvent(event: AccessibilityEvent) { - if (appLockRepository.isAntiUninstallEnabled() && - event.packageName == DEVICE_ADMIN_SETTINGS_PACKAGE - ) { - checkForDeviceAdminDeactivation(event) - } - - // Early return if protection is disabled or service is not running - if (!appLockRepository.isProtectEnabled() || !isServiceRunning) { - return - } - // Handle window state changes - if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - try { - handleWindowStateChanged(event) - } catch (e: Exception) { - logError("Error handling window state change", e) - return - } - } + if (!appLockRepository.isProtectEnabled()) return - // Skip processing if recents are open - if (recentsOpen) { - LogUtils.d(TAG, "Recents opened, ignoring accessibility event") - return - } - - // Extract and validate package name val packageName = event.packageName?.toString() ?: return - // Skip if device is locked or app is excluded - if (!isValidPackageForLocking(packageName)) { - return - } + val className = event.className?.toString() - try { - processPackageLocking(packageName) - } catch (e: Exception) { - logError("Error processing package locking for $packageName", e) - } - } + /* --------------------------------------------------------- + 🔒 Restrict System Settings Access + ---------------------------------------------------------- */ - private fun handleWindowStateChanged(event: AccessibilityEvent) { - val isRecentlyOpened = isRecentlyOpened(event) - val isHomeScreen = isHomeScreen(event) + if (isRestrictedSettings(packageName, className)) { - when { - isRecentlyOpened -> { - LogUtils.d(TAG, "Entering recents") - recentsOpen = true - } + LogUtils.d(TAG, "Blocked restricted settings page") - isHomeScreenTransition(event) && recentsOpen -> { - LogUtils.d(TAG, "Transitioning to home screen from recents") - recentsOpen = false - clearTemporarilyUnlockedAppIfNeeded() - } + performGlobalAction(GLOBAL_ACTION_BACK) - isHomeScreen -> { - LogUtils.d(TAG, "On home screen") - recentsOpen = false - clearTemporarilyUnlockedAppIfNeeded() - } + showSecurityLock() - isAppSwitchedFromRecents(event) -> { - LogUtils.d(TAG, "App switched from recents") - recentsOpen = false - clearTemporarilyUnlockedAppIfNeeded(event.packageName?.toString()) - } + return } - } - @SuppressLint("InlinedApi") - private fun isRecentlyOpened(event: AccessibilityEvent): Boolean { - return (event.packageName == getSystemDefaultLauncherPackageName() && - event.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED) || - (event.text.toString().lowercase().contains("recent apps")) - } + if (!isValidPackage(packageName)) return - private fun isHomeScreen(event: AccessibilityEvent): Boolean { - return event.packageName == getSystemDefaultLauncherPackageName() && - event.className == "com.android.launcher3.uioverrides.QuickstepLauncher" && - event.text.toString().lowercase().contains("home screen") + processPackageLocking(packageName) } - @SuppressLint("InlinedApi") - private fun isHomeScreenTransition(event: AccessibilityEvent): Boolean { - return event.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED && - event.packageName == getSystemDefaultLauncherPackageName() - } + /* --------------------------------------------------------- + SETTINGS PROTECTION + ---------------------------------------------------------- */ - private fun isAppSwitchedFromRecents(event: AccessibilityEvent): Boolean { - return event.packageName != getSystemDefaultLauncherPackageName() && recentsOpen - } + private fun isRestrictedSettings(pkg: String, cls: String?): Boolean { - private fun clearTemporarilyUnlockedAppIfNeeded(newPackage: String? = null) { - val shouldClear = newPackage == null || - (newPackage != AppLockManager.temporarilyUnlockedApp && - newPackage !in appLockRepository.getTriggerExcludedApps()) + val restrictState = appLockRepository.getRestrictSettings() - if (shouldClear) { - LogUtils.d(TAG, "Clearing temporarily unlocked app") - AppLockManager.clearTemporarilyUnlockedApp() - } - } + if (pkg != "com.android.settings") return false - private fun isValidPackageForLocking(packageName: String): Boolean { - // Check if device is locked - if (applicationContext.isDeviceLocked()) { - AppLockManager.appUnlockTimes.clear() - AppLockManager.clearTemporarilyUnlockedApp() - return false - } + if (restrictState.blockOverlaySettings && + cls?.contains("Overlay", true) == true + ) return true - // Check if accessibility should handle locking - if (!shouldAccessibilityHandleLocking()) { - return false - } + if (restrictState.blockUsageAccessSettings && + cls?.contains("UsageAccess", true) == true + ) return true - // Skip excluded packages - if (packageName == APP_PACKAGE_PREFIX || - packageName in keyboardPackages || - packageName in EXCLUDED_APPS - ) { - return false - } + if (restrictState.blockAccessibilitySettings && + cls?.contains("Accessibility", true) == true + ) return true - // Skip known recents classes - return true - } - - private fun processPackageLocking(packageName: String) { - val currentForegroundPackage = packageName - val triggeringPackage = lastForegroundPackage - lastForegroundPackage = currentForegroundPackage + if (restrictState.blockDeviceAdminSettings && + cls?.contains("DeviceAdmin", true) == true + ) return true - // Skip if triggering package is excluded - if (triggeringPackage in appLockRepository.getTriggerExcludedApps()) { - return - } + return false + } - // Fix for "Lock Immediately" not working when switching between apps - val unlockedApp = AppLockManager.temporarilyUnlockedApp - if (unlockedApp.isNotEmpty() && - unlockedApp != currentForegroundPackage && - currentForegroundPackage !in appLockRepository.getTriggerExcludedApps() - ) { - LogUtils.d( - TAG, - "Switched from unlocked app $unlockedApp to $currentForegroundPackage." - ) - AppLockManager.setRecentlyLeftApp(unlockedApp) - AppLockManager.clearTemporarilyUnlockedApp() - } + private fun showSecurityLock() { - checkAndLockApp(currentForegroundPackage, triggeringPackage, System.currentTimeMillis()) - } + if (AppLockManager.isLockScreenShown.get()) return - private fun shouldAccessibilityHandleLocking(): Boolean { - return when (appLockRepository.getBackendImplementation()) { - BackendImplementation.ACCESSIBILITY -> true - BackendImplementation.SHIZUKU -> !applicationContext.isServiceRunning( - ShizukuAppLockService::class.java - ) + AppLockManager.isLockScreenShown.set(true) - BackendImplementation.USAGE_STATS -> !applicationContext.isServiceRunning( - ExperimentalAppLockService::class.java - ) - } - } + val intent = Intent(this, PasswordOverlayActivity::class.java).apply { - private fun checkAndLockApp(packageName: String, triggeringPackage: String, currentTime: Long) { - // Return early if lock screen is already shown or biometric auth is in progress - if (AppLockManager.isLockScreenShown.get() || - AppLockManager.currentBiometricState == BiometricState.AUTH_STARTED - ) { - return - } + flags = + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS - // Return if package is not locked - if (packageName !in appLockRepository.getLockedApps()) { - return + putExtra("locked_package", "com.android.settings") } - // Return if app is temporarily unlocked - if (AppLockManager.isAppTemporarilyUnlocked(packageName)) { - return - } + startActivity(intent) + } - AppLockManager.clearTemporarilyUnlockedApp() + /* --------------------------------------------------------- + NORMAL APP LOCK LOGIC + ---------------------------------------------------------- */ - val unlockDurationMinutes = appLockRepository.getUnlockTimeDuration() - val unlockTimestamp = AppLockManager.appUnlockTimes[packageName] ?: 0L + private fun processPackageLocking(packageName: String) { - LogUtils.d( - TAG, - "checkAndLockApp: pkg=$packageName, duration=$unlockDurationMinutes min, unlockTime=$unlockTimestamp, currentTime=$currentTime, isLockScreenShown=${AppLockManager.isLockScreenShown.get()}" - ) + val triggeringPackage = lastForegroundPackage - if (unlockDurationMinutes > 0 && unlockTimestamp > 0) { - if (unlockDurationMinutes >= 10_000) { - return - } + lastForegroundPackage = packageName - val durationMillis = unlockDurationMinutes.toLong() * 60L * 1000L + if (triggeringPackage in appLockRepository.getTriggerExcludedApps()) return - val elapsedMillis = currentTime - unlockTimestamp + checkAndLockApp(packageName, triggeringPackage) + } - LogUtils.d( - TAG, - "Grace period check: elapsed=${elapsedMillis}ms (${elapsedMillis / 1000}s), duration=${durationMillis}ms (${durationMillis / 1000}s)" - ) + private fun checkAndLockApp(packageName: String, triggeringPackage: String) { - if (elapsedMillis < durationMillis) { - return - } + if (AppLockManager.isLockScreenShown.get()) return - LogUtils.d(TAG, "Unlock grace period expired for $packageName. Clearing timestamp.") - AppLockManager.appUnlockTimes.remove(packageName) - AppLockManager.clearTemporarilyUnlockedApp() - } + if (packageName !in appLockRepository.getLockedApps()) return - if (AppLockManager.isLockScreenShown.get() || - AppLockManager.currentBiometricState == BiometricState.AUTH_STARTED - ) { - LogUtils.d(TAG, "Lock screen already shown or biometric auth in progress, skipping") - return - } + if (AppLockManager.isAppTemporarilyUnlocked(packageName)) return - showLockScreenOverlay(packageName, triggeringPackage) - } + LogUtils.d(TAG, "Locked app detected: $packageName") - private fun showLockScreenOverlay(packageName: String, triggeringPackage: String) { - LogUtils.d(TAG, "Locked app detected: $packageName. Showing overlay.") AppLockManager.isLockScreenShown.set(true) val intent = Intent(this, PasswordOverlayActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or - Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or - Intent.FLAG_ACTIVITY_NO_ANIMATION or - Intent.FLAG_FROM_BACKGROUND or - Intent.FLAG_ACTIVITY_REORDER_TO_FRONT + + flags = + Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or + Intent.FLAG_ACTIVITY_NO_ANIMATION + putExtra("locked_package", packageName) + putExtra("triggering_package", triggeringPackage) } - try { - startActivity(intent) - } catch (e: Exception) { - logError("Failed to start password overlay", e) - AppLockManager.isLockScreenShown.set(false) - } + startActivity(intent) } - private fun checkForDeviceAdminDeactivation(event: AccessibilityEvent) { - Log.d(TAG, "Checking for device admin deactivation for event: $event") + /* --------------------------------------------------------- + VALIDATION + ---------------------------------------------------------- */ - // Check if user is trying to deactivate the accessibility service - if (isDeactivationAttempt(event)) { - Log.d(TAG, "Blocking accessibility service deactivation") - blockDeactivationAttempt() - return - } + private fun isValidPackage(packageName: String): Boolean { - // Check if on device admin page and our app is visible - val isDeviceAdminPage = isDeviceAdminPage(event) - //val isOurAppVisible = findNodeWithTextContaining(rootNode, "App Lock") != null || - // findNodeWithTextContaining(rootNode, "AppLock") != null + if (applicationContext.isDeviceLocked()) { - LogUtils.d(TAG, "User is on device admin page: $isDeviceAdminPage, $event") + AppLockManager.appUnlockTimes.clear() - if (!isDeviceAdminPage) { - return + return false } - blockDeviceAdminDeactivation() - } + if (packageName == this.packageName) return false - private fun isDeactivationAttempt(event: AccessibilityEvent): Boolean { - val isAccessibilitySettings = event.className in ACCESSIBILITY_SETTINGS_CLASSES && - event.text.any { it.contains("App Lock") } - val isSubSettings = event.className == "com.android.settings.SubSettings" && - event.text.any { it.contains("App Lock") } - val isAlertDialog = - event.packageName == "com.google.android.packageinstaller" && event.className == "android.app.AlertDialog" && event.text.toString() - .lowercase().contains("App Lock") + if (packageName in keyboardPackages) return false - return isAccessibilitySettings || isSubSettings || isAlertDialog - } + if (packageName in AppLockConstants.EXCLUDED_APPS) return false - @SuppressLint("InlinedApi") - private fun blockDeactivationAttempt() { - try { - performGlobalAction(GLOBAL_ACTION_BACK) - performGlobalAction(GLOBAL_ACTION_HOME) - performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN) - } catch (e: Exception) { - logError("Error blocking deactivation attempt", e) - } - } - - private fun isDeviceAdminPage(event: AccessibilityEvent): Boolean { - val hasDeviceAdminDescription = event.contentDescription?.toString()?.lowercase() - ?.contains("Device admin app") == true && - event.className == "android.widget.FrameLayout" - - val isAdminConfigClass = - event.className!!.contains("DeviceAdminAdd") || event.className!!.contains("DeviceAdminSettings") - - return hasDeviceAdminDescription || isAdminConfigClass + return true } - @SuppressLint("InlinedApi") - private fun blockDeviceAdminDeactivation() { - try { - val dpm: DevicePolicyManager? = getSystemService() - val component = ComponentName(this, DeviceAdmin::class.java) - - if (dpm?.isAdminActive(component) == true) { - performGlobalAction(GLOBAL_ACTION_BACK) - performGlobalAction(GLOBAL_ACTION_BACK) - performGlobalAction(GLOBAL_ACTION_HOME) - Thread.sleep(100) - performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN) - Toast.makeText( - this, - "Disable anti-uninstall from AppLock settings to remove this restriction.", - Toast.LENGTH_LONG - ).show() - Log.w(TAG, "Blocked device admin deactivation attempt.") - } - } catch (e: Exception) { - logError("Error blocking device admin deactivation", e) - } - } + private fun getKeyboardPackageNames(): List { - private fun findNodeWithTextContaining( - node: AccessibilityNodeInfo, - text: String - ): AccessibilityNodeInfo? { return try { - if (node.text?.toString()?.contains(text, ignoreCase = true) == true) { - return node - } - - for (i in 0 until node.childCount) { - val child = node.getChild(i) ?: continue - val result = findNodeWithTextContaining(child, text) - if (result != null) return result - } - null - } catch (e: Exception) { - logError("Error finding node with text: $text", e) - null - } - } - private fun getKeyboardPackageNames(): List { - return try { - getSystemService()?.enabledInputMethodList?.map { it.packageName } + getSystemService() + ?.enabledInputMethodList + ?.map { it.packageName } ?: emptyList() + } catch (e: Exception) { - logError("Error getting keyboard package names", e) + emptyList() } } - fun getSystemDefaultLauncherPackageName(): String { - return try { - val packageManager = packageManager - val homeIntent = Intent(Intent.ACTION_MAIN).apply { - addCategory(Intent.CATEGORY_HOME) - } - - val resolveInfoList: List = packageManager.queryIntentActivities( - homeIntent, - PackageManager.MATCH_DEFAULT_ONLY - ) - - val systemLauncher = resolveInfoList.find { resolveInfo -> - val isSystemApp = (resolveInfo.activityInfo.applicationInfo.flags and - android.content.pm.ApplicationInfo.FLAG_SYSTEM) != 0 - val isOurApp = resolveInfo.activityInfo.packageName == packageName - - isSystemApp && !isOurApp - } - - systemLauncher?.activityInfo?.packageName?.also { - if (it.isEmpty()) { - Log.w(TAG, "Could not find a clear system launcher package name.") - } - } ?: "" - } catch (e: Exception) { - logError("Error getting system default launcher package", e) - "" - } - } + /* --------------------------------------------------------- + BACKEND CONTROL + ---------------------------------------------------------- */ private fun startPrimaryBackendService() { + try { + AppLockManager.stopAllOtherServices(this, AppLockAccessibilityService::class.java) when (appLockRepository.getBackendImplementation()) { - BackendImplementation.SHIZUKU -> { - Log.d(TAG, "Starting Shizuku service as primary backend") - startService(Intent(this, ShizukuAppLockService::class.java)) - } BackendImplementation.USAGE_STATS -> { - Log.d(TAG, "Starting Experimental service as primary backend") - startService(Intent(this, ExperimentalAppLockService::class.java)) - } - else -> { - Log.d(TAG, "Accessibility service is the primary backend.") + startService(Intent(this, ExperimentalAppLockService::class.java)) } - } - } catch (e: Exception) { - logError("Error starting primary backend service", e) - } - } - override fun onInterrupt() { - try { - LogUtils.d(TAG, "Accessibility service interrupted") - } catch (e: Exception) { - logError("Error in onInterrupt", e) - } - } + BackendImplementation.SHIZUKU -> { - override fun onUnbind(intent: Intent?): Boolean { - return try { - Log.d(TAG, "Accessibility service unbound") - isServiceRunning = false - AppLockManager.startFallbackServices(this, AppLockAccessibilityService::class.java) + startService(Intent(this, ShizukuAppLockService::class.java)) + } - if (Shizuku.pingBinder() && appLockRepository.isAntiUninstallEnabled()) { - enableAccessibilityServiceWithShizuku(ComponentName(packageName, javaClass.name)) + else -> {} } - super.onUnbind(intent) } catch (e: Exception) { - logError("Error in onUnbind", e) - super.onUnbind(intent) + + Log.e(TAG, "Error starting backend service", e) } } + override fun onInterrupt() {} + override fun onDestroy() { - try { - super.onDestroy() - isServiceRunning = false - LogUtils.d(TAG, "Accessibility service destroyed") - - try { - unregisterReceiver(screenStateReceiver) - } catch (_: IllegalArgumentException) { - // Ignore if not registered - Log.w(TAG, "Receiver not registered or already unregistered") - } - AppLockManager.isLockScreenShown.set(false) - AppLockManager.startFallbackServices(this, AppLockAccessibilityService::class.java) - } catch (e: Exception) { - logError("Error in onDestroy", e) - } - } + super.onDestroy() + + isServiceRunning = false - /** - * Logs errors silently without crashing the service. - * Only logs to debug level to avoid unnecessary noise in production. - */ - private fun logError(message: String, throwable: Throwable? = null) { - Log.e(TAG, message, throwable) + AppLockManager.startFallbackServices(this, AppLockAccessibilityService::class.java) } } From c7ad15a4bc6c96affbba4e74f811d9ba549aebb9 Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 13:09:32 +0530 Subject: [PATCH 07/18] Enhance PreferencesRepository with system settings restrictions Added methods for managing system settings restrictions including draw over apps, usage access, accessibility, device admin, and battery usage. Updated existing methods and constants for better clarity and functionality. --- .../data/repository/PreferencesRepository.kt | 135 ++++++++++++++---- 1 file changed, 107 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt b/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt index 20a7f10..e52c003 100644 --- a/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt +++ b/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt @@ -7,6 +7,7 @@ import androidx.core.content.edit /** * Repository for managing application preferences and settings. * Handles all SharedPreferences operations with proper separation of concerns. + * Updated to include system settings restriction preferences. */ class PreferencesRepository(context: Context) { @@ -16,6 +17,7 @@ class PreferencesRepository(context: Context) { private val settingsPrefs: SharedPreferences = context.getSharedPreferences(PREFS_NAME_SETTINGS, Context.MODE_PRIVATE) + // ============= AUTHENTICATION ============= fun setPassword(password: String) { appLockPrefs.edit { putString(KEY_PASSWORD, password) } } @@ -42,6 +44,7 @@ class PreferencesRepository(context: Context) { return storedPattern != null && inputPattern == storedPattern } + // ============= LOCK TYPE & BIOMETRIC ============= fun setLockType(lockType: String) { settingsPrefs.edit { putString(KEY_LOCK_TYPE, lockType) } } @@ -58,6 +61,7 @@ class PreferencesRepository(context: Context) { return settingsPrefs.getBoolean(KEY_BIOMETRIC_AUTH_ENABLED, false) } + // ============= DISPLAY SETTINGS ============= fun setUseMaxBrightness(enabled: Boolean) { settingsPrefs.edit { putBoolean(KEY_USE_MAX_BRIGHTNESS, enabled) } } @@ -82,6 +86,7 @@ class PreferencesRepository(context: Context) { return settingsPrefs.getBoolean(KEY_SHOW_SYSTEM_APPS, false) } + // ============= ANTI-UNINSTALL & PROTECTION ============= fun setAntiUninstallEnabled(enabled: Boolean) { settingsPrefs.edit { putBoolean(KEY_ANTI_UNINSTALL, enabled) } } @@ -98,54 +103,118 @@ class PreferencesRepository(context: Context) { return settingsPrefs.getBoolean(KEY_APPLOCK_ENABLED, DEFAULT_PROTECT_ENABLED) } + // ============= SYSTEM SETTINGS RESTRICTIONS ============= + // NEW: Restrict Draw Over Other Apps Settings Page + fun setRestrictDrawOverAppsSettings(enabled: Boolean) { + settingsPrefs.edit { putBoolean(KEY_RESTRICT_DRAW_OVER_APPS, enabled) } + } + + fun isRestrictDrawOverAppsSettings(): Boolean { + return settingsPrefs.getBoolean(KEY_RESTRICT_DRAW_OVER_APPS, false) + } + + // NEW: Restrict Usage Access Settings Page + fun setRestrictUsageAccessSettings(enabled: Boolean) { + settingsPrefs.edit { putBoolean(KEY_RESTRICT_USAGE_ACCESS, enabled) } + } + + fun isRestrictUsageAccessSettings(): Boolean { + return settingsPrefs.getBoolean(KEY_RESTRICT_USAGE_ACCESS, false) + } + + // NEW: Restrict Accessibility Settings Page + fun setRestrictAccessibilitySettings(enabled: Boolean) { + settingsPrefs.edit { putBoolean(KEY_RESTRICT_ACCESSIBILITY_SETTINGS, enabled) } + } + + fun isRestrictAccessibilitySettings(): Boolean { + return settingsPrefs.getBoolean(KEY_RESTRICT_ACCESSIBILITY_SETTINGS, false) + } + + // NEW: Restrict Device Administrator Settings Page + fun setRestrictDeviceAdminSettings(enabled: Boolean) { + settingsPrefs.edit { putBoolean(KEY_RESTRICT_DEVICE_ADMIN_SETTINGS, enabled) } + } + + fun isRestrictDeviceAdminSettings(): Boolean { + return settingsPrefs.getBoolean(KEY_RESTRICT_DEVICE_ADMIN_SETTINGS, false) + } + + // NEW: Require Unrestricted Battery Usage + fun setRequireUnrestrictedBattery(enabled: Boolean) { + settingsPrefs.edit { putBoolean(KEY_REQUIRE_UNRESTRICTED_BATTERY, enabled) } + } + + fun isRequireUnrestrictedBattery(): Boolean { + return settingsPrefs.getBoolean(KEY_REQUIRE_UNRESTRICTED_BATTERY, false) + } + + // NEW: Disable all system settings restrictions (used when Anti-Uninstall is disabled) + fun disableAllSystemSettingsRestrictions() { + settingsPrefs.edit { + putBoolean(KEY_RESTRICT_DRAW_OVER_APPS, false) + putBoolean(KEY_RESTRICT_USAGE_ACCESS, false) + putBoolean(KEY_RESTRICT_ACCESSIBILITY_SETTINGS, false) + putBoolean(KEY_RESTRICT_DEVICE_ADMIN_SETTINGS, false) + putBoolean(KEY_REQUIRE_UNRESTRICTED_BATTERY, false) + } + } + + // ============= UNLOCK DURATION & AUTO-UNLOCK ============= fun setUnlockTimeDuration(minutes: Int) { settingsPrefs.edit { putInt(KEY_UNLOCK_TIME_DURATION, minutes) } } fun getUnlockTimeDuration(): Int { - return settingsPrefs.getInt(KEY_UNLOCK_TIME_DURATION, DEFAULT_UNLOCK_DURATION) + return settingsPrefs.getInt(KEY_UNLOCK_TIME_DURATION, 0) } fun setAutoUnlockEnabled(enabled: Boolean) { - settingsPrefs.edit { putBoolean(KEY_AUTO_UNLOCK, enabled) } + settingsPrefs.edit { putBoolean(KEY_AUTO_UNLOCK_ENABLED, enabled) } } fun isAutoUnlockEnabled(): Boolean { - return settingsPrefs.getBoolean(KEY_AUTO_UNLOCK, false) + return settingsPrefs.getBoolean(KEY_AUTO_UNLOCK_ENABLED, false) } + // ============= BACKEND IMPLEMENTATION ============= fun setBackendImplementation(backend: BackendImplementation) { settingsPrefs.edit { putString(KEY_BACKEND_IMPLEMENTATION, backend.name) } } fun getBackendImplementation(): BackendImplementation { - val backend = settingsPrefs.getString( - KEY_BACKEND_IMPLEMENTATION, - BackendImplementation.ACCESSIBILITY.name - ) - return try { - BackendImplementation.valueOf(backend ?: BackendImplementation.ACCESSIBILITY.name) - } catch (_: IllegalArgumentException) { - BackendImplementation.ACCESSIBILITY - } + val value = settingsPrefs.getString(KEY_BACKEND_IMPLEMENTATION, null) + return if (value != null) BackendImplementation.valueOf(value) + else BackendImplementation.ACCESSIBILITY } + // ============= LINKS ============= fun isShowCommunityLink(): Boolean { - return !settingsPrefs.getBoolean(KEY_COMMUNITY_LINK_SHOWN, false) + return settingsPrefs.getBoolean(KEY_SHOW_COMMUNITY_LINK, true) } fun setCommunityLinkShown(shown: Boolean) { - settingsPrefs.edit { putBoolean(KEY_COMMUNITY_LINK_SHOWN, shown) } + settingsPrefs.edit { putBoolean(KEY_SHOW_COMMUNITY_LINK, shown) } + } + + fun isShowDonateLink(): Boolean { + return settingsPrefs.getBoolean(KEY_SHOW_DONATE_LINK, true) } + fun setShowDonateLink(show: Boolean) { + settingsPrefs.edit { putBoolean(KEY_SHOW_DONATE_LINK, show) } + } + + // Overloaded version for context parameter (for backward compatibility) fun isShowDonateLink(context: Context): Boolean { - return settingsPrefs.getBoolean(KEY_SHOW_DONATE_LINK, false) + return settingsPrefs.getBoolean(KEY_SHOW_DONATE_LINK, true) } fun setShowDonateLink(context: Context, show: Boolean) { settingsPrefs.edit { putBoolean(KEY_SHOW_DONATE_LINK, show) } } + // ============= LOGGING ============= fun isLoggingEnabled(): Boolean { return settingsPrefs.getBoolean(KEY_LOGGING_ENABLED, false) } @@ -155,30 +224,40 @@ class PreferencesRepository(context: Context) { } companion object { - private const val PREFS_NAME_APP_LOCK = "app_lock_prefs" - private const val PREFS_NAME_SETTINGS = "app_lock_settings" + private const val PREFS_NAME_APP_LOCK = "applock_prefs" + private const val PREFS_NAME_SETTINGS = "settings_prefs" + // Keys private const val KEY_PASSWORD = "password" private const val KEY_PATTERN = "pattern" - private const val KEY_BIOMETRIC_AUTH_ENABLED = "use_biometric_auth" - private const val KEY_DISABLE_HAPTICS = "disable_haptics" + private const val KEY_LOCK_TYPE = "lock_type" + private const val KEY_BIOMETRIC_AUTH_ENABLED = "biometric_auth_enabled" private const val KEY_USE_MAX_BRIGHTNESS = "use_max_brightness" + private const val KEY_DISABLE_HAPTICS = "disable_haptics" + private const val KEY_SHOW_SYSTEM_APPS = "show_system_apps" private const val KEY_ANTI_UNINSTALL = "anti_uninstall" + private const val KEY_APPLOCK_ENABLED = "applock_enabled" + + // NEW: System Settings Restriction Keys + private const val KEY_RESTRICT_DRAW_OVER_APPS = "restrict_draw_over_apps" + private const val KEY_RESTRICT_USAGE_ACCESS = "restrict_usage_access" + private const val KEY_RESTRICT_ACCESSIBILITY_SETTINGS = "restrict_accessibility_settings" + private const val KEY_RESTRICT_DEVICE_ADMIN_SETTINGS = "restrict_device_admin_settings" + private const val KEY_REQUIRE_UNRESTRICTED_BATTERY = "require_unrestricted_battery" + private const val KEY_UNLOCK_TIME_DURATION = "unlock_time_duration" + private const val KEY_AUTO_UNLOCK_ENABLED = "auto_unlock_enabled" private const val KEY_BACKEND_IMPLEMENTATION = "backend_implementation" - private const val KEY_COMMUNITY_LINK_SHOWN = "community_link_shown" + private const val KEY_SHOW_COMMUNITY_LINK = "show_community_link" private const val KEY_SHOW_DONATE_LINK = "show_donate_link" private const val KEY_LOGGING_ENABLED = "logging_enabled" - private const val LAST_VERSION_CODE = "last_version_code" - private const val KEY_APPLOCK_ENABLED = "applock_enabled" - private const val KEY_AUTO_UNLOCK = "auto_unlock" - private const val KEY_SHOW_SYSTEM_APPS = "show_system_apps" - private const val KEY_LOCK_TYPE = "lock_type" - - private const val DEFAULT_PROTECT_ENABLED = true - private const val DEFAULT_UNLOCK_DURATION = 0 + // Lock type constants const val LOCK_TYPE_PIN = "pin" const val LOCK_TYPE_PATTERN = "pattern" + const val LOCK_TYPE_PASSWORD = "password" + + // Default values + const val DEFAULT_PROTECT_ENABLED = true } } From b749ec31cf4d5787f890da37707dd962bb4abd7e Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 13:10:36 +0530 Subject: [PATCH 08/18] Add system settings restriction methods to AppLockRepository Updated AppLockRepository to include methods for system settings restrictions, such as restricting draw over apps, usage access, accessibility, and device admin settings. --- .../data/repository/AppLockRepository.kt | 74 ++++++++++++++++++- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt b/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt index 91e35fe..0600149 100644 --- a/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt +++ b/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt @@ -6,6 +6,7 @@ import dev.pranav.applock.data.manager.BackendServiceManager /** * Main repository that coordinates between different specialized repositories and managers. * Provides a unified interface for all app lock functionality. + * Updated to include system settings restriction methods. */ class AppLockRepository(private val context: Context) { @@ -13,6 +14,7 @@ class AppLockRepository(private val context: Context) { private val lockedAppsRepository = LockedAppsRepository(context) private val backendServiceManager = BackendServiceManager() + // ============= LOCKED APPS ============= fun getLockedApps(): Set = lockedAppsRepository.getLockedApps() fun addLockedApp(packageName: String) = lockedAppsRepository.addLockedApp(packageName) fun addMultipleLockedApps(packageNames: Set) = @@ -20,6 +22,7 @@ class AppLockRepository(private val context: Context) { fun removeLockedApp(packageName: String) = lockedAppsRepository.removeLockedApp(packageName) fun isAppLocked(packageName: String): Boolean = lockedAppsRepository.isAppLocked(packageName) + // ============= TRIGGER EXCLUSIONS ============= fun getTriggerExcludedApps(): Set = lockedAppsRepository.getTriggerExcludedApps() fun addTriggerExcludedApp(packageName: String) = lockedAppsRepository.addTriggerExcludedApp(packageName) @@ -30,6 +33,7 @@ class AppLockRepository(private val context: Context) { fun isAppTriggerExcluded(packageName: String): Boolean = lockedAppsRepository.isAppTriggerExcluded(packageName) + // ============= ANTI-UNINSTALL APPS ============= fun getAntiUninstallApps(): Set = lockedAppsRepository.getAntiUninstallApps() fun addAntiUninstallApp(packageName: String) = lockedAppsRepository.addAntiUninstallApp(packageName) @@ -40,6 +44,7 @@ class AppLockRepository(private val context: Context) { fun isAppAntiUninstall(packageName: String): Boolean = lockedAppsRepository.isAppAntiUninstall(packageName) + // ============= AUTHENTICATION ============= fun getPassword(): String? = preferencesRepository.getPassword() fun setPassword(password: String) = preferencesRepository.setPassword(password) fun validatePassword(inputPassword: String): Boolean = @@ -50,6 +55,7 @@ class AppLockRepository(private val context: Context) { fun validatePattern(inputPattern: String): Boolean = preferencesRepository.validatePattern(inputPattern) + // ============= LOCK TYPE & BIOMETRIC ============= fun setLockType(lockType: String) = preferencesRepository.setLockType(lockType) fun getLockType(): String = preferencesRepository.getLockType() @@ -58,6 +64,7 @@ class AppLockRepository(private val context: Context) { fun isBiometricAuthEnabled(): Boolean = preferencesRepository.isBiometricAuthEnabled() + // ============= DISPLAY SETTINGS ============= fun setUseMaxBrightness(enabled: Boolean) = preferencesRepository.setUseMaxBrightness(enabled) fun shouldUseMaxBrightness(): Boolean = preferencesRepository.shouldUseMaxBrightness() fun setDisableHaptics(enabled: Boolean) = preferencesRepository.setDisableHaptics(enabled) @@ -65,29 +72,88 @@ class AppLockRepository(private val context: Context) { fun setShowSystemApps(enabled: Boolean) = preferencesRepository.setShowSystemApps(enabled) fun shouldShowSystemApps(): Boolean = preferencesRepository.shouldShowSystemApps() - fun setAntiUninstallEnabled(enabled: Boolean) = + // ============= ANTI-UNINSTALL & PROTECTION ============= + fun setAntiUninstallEnabled(enabled: Boolean) { preferencesRepository.setAntiUninstallEnabled(enabled) + // When disabling Anti-Uninstall, also disable all system settings restrictions + if (!enabled) { + preferencesRepository.disableAllSystemSettingsRestrictions() + } + } fun isAntiUninstallEnabled(): Boolean = preferencesRepository.isAntiUninstallEnabled() fun setProtectEnabled(enabled: Boolean) = preferencesRepository.setProtectEnabled(enabled) fun isProtectEnabled(): Boolean = preferencesRepository.isProtectEnabled() + // ============= SYSTEM SETTINGS RESTRICTIONS (NEW) ============= + // NEW: Restrict Draw Over Other Apps Settings Page + fun setRestrictDrawOverAppsSettings(enabled: Boolean) = + preferencesRepository.setRestrictDrawOverAppsSettings(enabled) + + fun isRestrictDrawOverAppsSettings(): Boolean = + preferencesRepository.isRestrictDrawOverAppsSettings() + + // NEW: Restrict Usage Access Settings Page + fun setRestrictUsageAccessSettings(enabled: Boolean) = + preferencesRepository.setRestrictUsageAccessSettings(enabled) + + fun isRestrictUsageAccessSettings(): Boolean = + preferencesRepository.isRestrictUsageAccessSettings() + + // NEW: Restrict Accessibility Settings Page + fun setRestrictAccessibilitySettings(enabled: Boolean) = + preferencesRepository.setRestrictAccessibilitySettings(enabled) + + fun isRestrictAccessibilitySettings(): Boolean = + preferencesRepository.isRestrictAccessibilitySettings() + + // NEW: Restrict Device Administrator Settings Page + fun setRestrictDeviceAdminSettings(enabled: Boolean) = + preferencesRepository.setRestrictDeviceAdminSettings(enabled) + + fun isRestrictDeviceAdminSettings(): Boolean = + preferencesRepository.isRestrictDeviceAdminSettings() + + // NEW: Require Unrestricted Battery Usage + fun setRequireUnrestrictedBattery(enabled: Boolean) = + preferencesRepository.setRequireUnrestrictedBattery(enabled) + + fun isRequireUnrestrictedBattery(): Boolean = + preferencesRepository.isRequireUnrestrictedBattery() + + // NEW: Check if any system settings restriction is enabled + fun hasAnySystemSettingsRestriction(): Boolean { + return isRestrictDrawOverAppsSettings() || + isRestrictUsageAccessSettings() || + isRestrictAccessibilitySettings() || + isRestrictDeviceAdminSettings() || + isRequireUnrestrictedBattery() + } + + // ============= UNLOCK DURATION & AUTO-UNLOCK ============= fun setUnlockTimeDuration(minutes: Int) = preferencesRepository.setUnlockTimeDuration(minutes) fun getUnlockTimeDuration(): Int = preferencesRepository.getUnlockTimeDuration() fun setAutoUnlockEnabled(enabled: Boolean) = preferencesRepository.setAutoUnlockEnabled(enabled) fun isAutoUnlockEnabled(): Boolean = preferencesRepository.isAutoUnlockEnabled() + // ============= BACKEND IMPLEMENTATION ============= fun setBackendImplementation(backend: BackendImplementation) = preferencesRepository.setBackendImplementation(backend) fun getBackendImplementation(): BackendImplementation = preferencesRepository.getBackendImplementation() + // ============= LINKS ============= fun isShowCommunityLink(): Boolean = preferencesRepository.isShowCommunityLink() fun setCommunityLinkShown(shown: Boolean) = preferencesRepository.setCommunityLinkShown(shown) - fun isShowDonateLink(): Boolean = preferencesRepository.isShowDonateLink(context) - fun setShowDonateLink(show: Boolean) = preferencesRepository.setShowDonateLink(context, show) - + fun isShowDonateLink(): Boolean = preferencesRepository.isShowDonateLink() + fun setShowDonateLink(show: Boolean) = preferencesRepository.setShowDonateLink(show) + + // Overloaded versions for context parameter (for backward compatibility) + fun isShowDonateLink(context: Context): Boolean = preferencesRepository.isShowDonateLink(context) + fun setShowDonateLink(context: Context, show: Boolean) = preferencesRepository.setShowDonateLink(context, show) + + // ============= LOGGING ============= fun isLoggingEnabled(): Boolean = preferencesRepository.isLoggingEnabled() fun setLoggingEnabled(enabled: Boolean) = preferencesRepository.setLoggingEnabled(enabled) From b9b643c91d3eca22788660caf91899d58afe86a7 Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 13:11:59 +0530 Subject: [PATCH 09/18] Implement SystemSettingsRestrictionManager for access control This class manages restrictions on critical system settings, detects unauthorized access attempts, and logs them. It supports restrictions for various settings including draw over apps, usage access, accessibility, device admin, and battery optimization. --- .../utils/SystemSettingsRestrictionManager | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 app/src/main/java/dev/pranav/applock/core/utils/SystemSettingsRestrictionManager diff --git a/app/src/main/java/dev/pranav/applock/core/utils/SystemSettingsRestrictionManager b/app/src/main/java/dev/pranav/applock/core/utils/SystemSettingsRestrictionManager new file mode 100644 index 0000000..75565a4 --- /dev/null +++ b/app/src/main/java/dev/pranav/applock/core/utils/SystemSettingsRestrictionManager @@ -0,0 +1,220 @@ +package dev.pranav.applock.core.utils + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.provider.Settings +import dev.pranav.applock.core.utils.LogUtils +import dev.pranav.applock.data.repository.AppLockRepository + +/** + * Manages restriction of critical system settings pages. + * Detects when users try to access restricted settings and blocks them. + * + * Supported restrictions: + * - Draw Over Other Apps (Overlay Permission) + * - Usage Access (Usage Stats Permission) + * - Accessibility Settings + * - Device Administrator Settings + * - Battery Optimization Settings + */ +class SystemSettingsRestrictionManager(private val context: Context) { + + private val repository = context.appLockRepository() + + /** + * Check if an intent is trying to access a restricted settings page. + * If restricted, log the attempt and return true. + */ + fun isIntentRestricted(intent: Intent?): Boolean { + if (intent == null || !repository.isAntiUninstallEnabled()) { + return false + } + + return when { + isDrawOverAppsSettings(intent) && repository.isRestrictDrawOverAppsSettings() -> { + logRestrictionAttempt("Draw Over Other Apps", intent) + true + } + isUsageAccessSettings(intent) && repository.isRestrictUsageAccessSettings() -> { + logRestrictionAttempt("Usage Access", intent) + true + } + isAccessibilitySettings(intent) && repository.isRestrictAccessibilitySettings() -> { + logRestrictionAttempt("Accessibility", intent) + true + } + isDeviceAdminSettings(intent) && repository.isRestrictDeviceAdminSettings() -> { + logRestrictionAttempt("Device Administrator", intent) + true + } + isBatteryOptimizationSettings(intent) && repository.isRequireUnrestrictedBattery() -> { + logRestrictionAttempt("Battery Optimization", intent) + true + } + else -> false + } + } + + /** + * Get all currently restricted actions. + * Used by accessibility service to monitor specific intents. + */ + fun getRestrictedActions(): List { + val actions = mutableListOf() + + if (repository.isAntiUninstallEnabled()) { + if (repository.isRestrictDrawOverAppsSettings()) { + actions.addAll(DRAW_OVER_APPS_ACTIONS) + } + if (repository.isRestrictUsageAccessSettings()) { + actions.addAll(USAGE_ACCESS_ACTIONS) + } + if (repository.isRestrictAccessibilitySettings()) { + actions.addAll(ACCESSIBILITY_ACTIONS) + } + if (repository.isRestrictDeviceAdminSettings()) { + actions.addAll(DEVICE_ADMIN_ACTIONS) + } + if (repository.isRequireUnrestrictedBattery()) { + actions.addAll(BATTERY_OPTIMIZATION_ACTIONS) + } + } + + return actions + } + + /** + * Check if battery optimization check is required. + * If enabled, ensure app is in unrestricted battery usage. + */ + fun checkBatteryOptimizationRequirement(): Boolean { + if (!repository.isRequireUnrestrictedBattery() || + !repository.isAntiUninstallEnabled()) { + return false + } + + // Check if app is ignored by battery optimization + val pm = context.getSystemService(Context.POWER_SERVICE) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val powerManager = pm as? android.os.PowerManager + !(powerManager?.isIgnoringBatteryOptimizations(context.packageName) ?: false) + } else { + false + } + } + + /** + * Request unrestricted battery usage permission. + */ + fun requestUnrestrictedBatteryUsage() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + return + } + + val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { + data = android.net.Uri.parse("package:${context.packageName}") + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + + try { + context.startActivity(intent) + } catch (e: Exception) { + LogUtils.logError("Failed to request unrestricted battery usage", e) + } + } + + // ============= DETECTION METHODS ============= + + private fun isDrawOverAppsSettings(intent: Intent): Boolean { + val action = intent.action ?: return false + return action in DRAW_OVER_APPS_ACTIONS + } + + private fun isUsageAccessSettings(intent: Intent): Boolean { + val action = intent.action ?: return false + return action in USAGE_ACCESS_ACTIONS + } + + private fun isAccessibilitySettings(intent: Intent): Boolean { + val action = intent.action ?: return false + return action in ACCESSIBILITY_ACTIONS + } + + private fun isDeviceAdminSettings(intent: Intent): Boolean { + val action = intent.action ?: return false + return action in DEVICE_ADMIN_ACTIONS + } + + private fun isBatteryOptimizationSettings(intent: Intent): Boolean { + val action = intent.action ?: return false + return action in BATTERY_OPTIMIZATION_ACTIONS + } + + // ============= LOGGING ============= + + private fun logRestrictionAttempt(settingType: String, intent: Intent) { + try { + val message = "Blocked access to $settingType settings (Action: ${intent.action})" + LogUtils.logSecurityEvent(message) + } catch (e: Exception) { + LogUtils.logError("Failed to log restriction attempt", e) + } + } + + companion object { + // Draw Over Other Apps Settings Actions + private val DRAW_OVER_APPS_ACTIONS = listOf( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + "com.android.settings.MANAGE_APP_PERMISSIONS", // Samsung specific + "com.sec.android.settings.MANAGE_PERMISSION" // Samsung specific + ) + + // Usage Access Settings Actions + private val USAGE_ACCESS_ACTIONS = listOf( + Settings.ACTION_USAGE_ACCESS_SETTINGS, + "android.settings.USAGE_ACCESS_SETTINGS", + "android.intent.action.USAGE_ACCESS_SETTINGS" + ) + + // Accessibility Settings Actions + private val ACCESSIBILITY_ACTIONS = listOf( + Settings.ACTION_ACCESSIBILITY_SETTINGS, + "android.settings.ACCESSIBILITY_SETTINGS", + "android.intent.action.ACCESSIBILITY_SETTINGS" + ) + + // Device Administrator Settings Actions + private val DEVICE_ADMIN_ACTIONS = listOf( + Settings.ACTION_SECURITY_SETTINGS, + Settings.ACTION_DEVICE_ADMIN_DELETE_CONFIRM, + "android.app.action.DEVICE_ADMIN_ENABLED", + "android.app.action.DEVICE_ADMIN_DISABLED", + "com.android.settings.DEVICE_ADMIN_DELETE_CONFIRM" + ) + + // Battery Optimization Settings Actions + private val BATTERY_OPTIMIZATION_ACTIONS = listOf( + Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS, + Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, + "android.settings.IGNORE_BATTERY_OPTIMIZATION_SETTINGS", + "android.intent.action.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" + ) + + // Combined list for easy access + val ALL_RESTRICTED_ACTIONS = listOf( + *DRAW_OVER_APPS_ACTIONS.toTypedArray(), + *USAGE_ACCESS_ACTIONS.toTypedArray(), + *ACCESSIBILITY_ACTIONS.toTypedArray(), + *DEVICE_ADMIN_ACTIONS.toTypedArray(), + *BATTERY_OPTIMIZATION_ACTIONS.toTypedArray() + ) + } +} + +/** + * Extension function to create SystemSettingsRestrictionManager from context + */ +fun Context.systemSettingsRestrictionManager(): SystemSettingsRestrictionManager { + return SystemSettingsRestrictionManager(this) +} From 12e64b934961142f1c694183b594e5d351598100 Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 13:24:16 +0530 Subject: [PATCH 10/18] Fix missing newline at end of PreferencesRepository.kt Added a newline at the end of the file. From 62d406bd5b3ea31c8d32eb0d17c3ce379f6bf356 Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 13:25:33 +0530 Subject: [PATCH 11/18] Add files via upload --- .../data/repository/02_AppLockRepository.kt | Bin 0 -> 8055 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 app/src/main/java/dev/pranav/applock/data/repository/02_AppLockRepository.kt diff --git a/app/src/main/java/dev/pranav/applock/data/repository/02_AppLockRepository.kt b/app/src/main/java/dev/pranav/applock/data/repository/02_AppLockRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..60bde5605a5c2f0369863fa2106ba8ae045dbbea GIT binary patch literal 8055 zcmeIuF#!Mo0K%a4Pi+Tph(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM GoC5 Date: Sat, 7 Mar 2026 14:06:05 +0530 Subject: [PATCH 12/18] Implement system settings restrictions in PreferencesRepository Added methods for managing system settings restrictions, including draw over apps, usage access, accessibility, and device admin settings. --- .../data/repository/PreferencesRepository.kt | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt b/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt index e52c003..93a5cf3 100644 --- a/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt +++ b/app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt @@ -7,7 +7,7 @@ import androidx.core.content.edit /** * Repository for managing application preferences and settings. * Handles all SharedPreferences operations with proper separation of concerns. - * Updated to include system settings restriction preferences. + * COMPLETE FILE - Copy and paste to replace your existing PreferencesRepository.kt */ class PreferencesRepository(context: Context) { @@ -103,8 +103,7 @@ class PreferencesRepository(context: Context) { return settingsPrefs.getBoolean(KEY_APPLOCK_ENABLED, DEFAULT_PROTECT_ENABLED) } - // ============= SYSTEM SETTINGS RESTRICTIONS ============= - // NEW: Restrict Draw Over Other Apps Settings Page + // ============= SYSTEM SETTINGS RESTRICTIONS (NEW) ============= fun setRestrictDrawOverAppsSettings(enabled: Boolean) { settingsPrefs.edit { putBoolean(KEY_RESTRICT_DRAW_OVER_APPS, enabled) } } @@ -113,7 +112,6 @@ class PreferencesRepository(context: Context) { return settingsPrefs.getBoolean(KEY_RESTRICT_DRAW_OVER_APPS, false) } - // NEW: Restrict Usage Access Settings Page fun setRestrictUsageAccessSettings(enabled: Boolean) { settingsPrefs.edit { putBoolean(KEY_RESTRICT_USAGE_ACCESS, enabled) } } @@ -122,7 +120,6 @@ class PreferencesRepository(context: Context) { return settingsPrefs.getBoolean(KEY_RESTRICT_USAGE_ACCESS, false) } - // NEW: Restrict Accessibility Settings Page fun setRestrictAccessibilitySettings(enabled: Boolean) { settingsPrefs.edit { putBoolean(KEY_RESTRICT_ACCESSIBILITY_SETTINGS, enabled) } } @@ -131,7 +128,6 @@ class PreferencesRepository(context: Context) { return settingsPrefs.getBoolean(KEY_RESTRICT_ACCESSIBILITY_SETTINGS, false) } - // NEW: Restrict Device Administrator Settings Page fun setRestrictDeviceAdminSettings(enabled: Boolean) { settingsPrefs.edit { putBoolean(KEY_RESTRICT_DEVICE_ADMIN_SETTINGS, enabled) } } @@ -140,7 +136,6 @@ class PreferencesRepository(context: Context) { return settingsPrefs.getBoolean(KEY_RESTRICT_DEVICE_ADMIN_SETTINGS, false) } - // NEW: Require Unrestricted Battery Usage fun setRequireUnrestrictedBattery(enabled: Boolean) { settingsPrefs.edit { putBoolean(KEY_REQUIRE_UNRESTRICTED_BATTERY, enabled) } } @@ -149,7 +144,6 @@ class PreferencesRepository(context: Context) { return settingsPrefs.getBoolean(KEY_REQUIRE_UNRESTRICTED_BATTERY, false) } - // NEW: Disable all system settings restrictions (used when Anti-Uninstall is disabled) fun disableAllSystemSettingsRestrictions() { settingsPrefs.edit { putBoolean(KEY_RESTRICT_DRAW_OVER_APPS, false) @@ -205,7 +199,6 @@ class PreferencesRepository(context: Context) { settingsPrefs.edit { putBoolean(KEY_SHOW_DONATE_LINK, show) } } - // Overloaded version for context parameter (for backward compatibility) fun isShowDonateLink(context: Context): Boolean { return settingsPrefs.getBoolean(KEY_SHOW_DONATE_LINK, true) } @@ -227,7 +220,6 @@ class PreferencesRepository(context: Context) { private const val PREFS_NAME_APP_LOCK = "applock_prefs" private const val PREFS_NAME_SETTINGS = "settings_prefs" - // Keys private const val KEY_PASSWORD = "password" private const val KEY_PATTERN = "pattern" private const val KEY_LOCK_TYPE = "lock_type" @@ -238,7 +230,7 @@ class PreferencesRepository(context: Context) { private const val KEY_ANTI_UNINSTALL = "anti_uninstall" private const val KEY_APPLOCK_ENABLED = "applock_enabled" - // NEW: System Settings Restriction Keys + // System Settings Restriction Keys private const val KEY_RESTRICT_DRAW_OVER_APPS = "restrict_draw_over_apps" private const val KEY_RESTRICT_USAGE_ACCESS = "restrict_usage_access" private const val KEY_RESTRICT_ACCESSIBILITY_SETTINGS = "restrict_accessibility_settings" @@ -252,12 +244,16 @@ class PreferencesRepository(context: Context) { private const val KEY_SHOW_DONATE_LINK = "show_donate_link" private const val KEY_LOGGING_ENABLED = "logging_enabled" - // Lock type constants const val LOCK_TYPE_PIN = "pin" const val LOCK_TYPE_PATTERN = "pattern" const val LOCK_TYPE_PASSWORD = "password" - // Default values const val DEFAULT_PROTECT_ENABLED = true } } + +enum class BackendImplementation { + ACCESSIBILITY, + USAGE_STATS, + SHIZUKU +} From 0544e99b2dad97c3ae6742a674cf82087257190a Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 14:06:51 +0530 Subject: [PATCH 13/18] Add system settings restriction methods to AppLockRepository Updated AppLockRepository to include new system settings restriction methods and removed outdated comments. --- .../applock/data/repository/AppLockRepository.kt | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt b/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt index 0600149..c61ba74 100644 --- a/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt +++ b/app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt @@ -5,8 +5,7 @@ import dev.pranav.applock.data.manager.BackendServiceManager /** * Main repository that coordinates between different specialized repositories and managers. - * Provides a unified interface for all app lock functionality. - * Updated to include system settings restriction methods. + * COMPLETE FILE - Copy and paste to replace your existing AppLockRepository.kt */ class AppLockRepository(private val context: Context) { @@ -75,7 +74,6 @@ class AppLockRepository(private val context: Context) { // ============= ANTI-UNINSTALL & PROTECTION ============= fun setAntiUninstallEnabled(enabled: Boolean) { preferencesRepository.setAntiUninstallEnabled(enabled) - // When disabling Anti-Uninstall, also disable all system settings restrictions if (!enabled) { preferencesRepository.disableAllSystemSettingsRestrictions() } @@ -86,42 +84,36 @@ class AppLockRepository(private val context: Context) { fun isProtectEnabled(): Boolean = preferencesRepository.isProtectEnabled() // ============= SYSTEM SETTINGS RESTRICTIONS (NEW) ============= - // NEW: Restrict Draw Over Other Apps Settings Page fun setRestrictDrawOverAppsSettings(enabled: Boolean) = preferencesRepository.setRestrictDrawOverAppsSettings(enabled) fun isRestrictDrawOverAppsSettings(): Boolean = preferencesRepository.isRestrictDrawOverAppsSettings() - // NEW: Restrict Usage Access Settings Page fun setRestrictUsageAccessSettings(enabled: Boolean) = preferencesRepository.setRestrictUsageAccessSettings(enabled) fun isRestrictUsageAccessSettings(): Boolean = preferencesRepository.isRestrictUsageAccessSettings() - // NEW: Restrict Accessibility Settings Page fun setRestrictAccessibilitySettings(enabled: Boolean) = preferencesRepository.setRestrictAccessibilitySettings(enabled) fun isRestrictAccessibilitySettings(): Boolean = preferencesRepository.isRestrictAccessibilitySettings() - // NEW: Restrict Device Administrator Settings Page fun setRestrictDeviceAdminSettings(enabled: Boolean) = preferencesRepository.setRestrictDeviceAdminSettings(enabled) fun isRestrictDeviceAdminSettings(): Boolean = preferencesRepository.isRestrictDeviceAdminSettings() - // NEW: Require Unrestricted Battery Usage fun setRequireUnrestrictedBattery(enabled: Boolean) = preferencesRepository.setRequireUnrestrictedBattery(enabled) fun isRequireUnrestrictedBattery(): Boolean = preferencesRepository.isRequireUnrestrictedBattery() - // NEW: Check if any system settings restriction is enabled fun hasAnySystemSettingsRestriction(): Boolean { return isRestrictDrawOverAppsSettings() || isRestrictUsageAccessSettings() || @@ -149,7 +141,6 @@ class AppLockRepository(private val context: Context) { fun isShowDonateLink(): Boolean = preferencesRepository.isShowDonateLink() fun setShowDonateLink(show: Boolean) = preferencesRepository.setShowDonateLink(show) - // Overloaded versions for context parameter (for backward compatibility) fun isShowDonateLink(context: Context): Boolean = preferencesRepository.isShowDonateLink(context) fun setShowDonateLink(context: Context, show: Boolean) = preferencesRepository.setShowDonateLink(context, show) From f17cc67c0efa568f41ed2faa05909ef1d202b947 Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 14:07:49 +0530 Subject: [PATCH 14/18] Refactor SystemSettingsRestrictionManager for clarity --- .../utils/SystemSettingsRestrictionManager | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/core/utils/SystemSettingsRestrictionManager b/app/src/main/java/dev/pranav/applock/core/utils/SystemSettingsRestrictionManager index 75565a4..339fc51 100644 --- a/app/src/main/java/dev/pranav/applock/core/utils/SystemSettingsRestrictionManager +++ b/app/src/main/java/dev/pranav/applock/core/utils/SystemSettingsRestrictionManager @@ -4,19 +4,13 @@ import android.content.Context import android.content.Intent import android.os.Build import android.provider.Settings -import dev.pranav.applock.core.utils.LogUtils import dev.pranav.applock.data.repository.AppLockRepository /** + * COMPLETE FILE - Copy to app/src/main/java/dev/pranav/applock/core/utils/ + * * Manages restriction of critical system settings pages. * Detects when users try to access restricted settings and blocks them. - * - * Supported restrictions: - * - Draw Over Other Apps (Overlay Permission) - * - Usage Access (Usage Stats Permission) - * - Accessibility Settings - * - Device Administrator Settings - * - Battery Optimization Settings */ class SystemSettingsRestrictionManager(private val context: Context) { @@ -24,7 +18,6 @@ class SystemSettingsRestrictionManager(private val context: Context) { /** * Check if an intent is trying to access a restricted settings page. - * If restricted, log the attempt and return true. */ fun isIntentRestricted(intent: Intent?): Boolean { if (intent == null || !repository.isAntiUninstallEnabled()) { @@ -58,7 +51,6 @@ class SystemSettingsRestrictionManager(private val context: Context) { /** * Get all currently restricted actions. - * Used by accessibility service to monitor specific intents. */ fun getRestrictedActions(): List { val actions = mutableListOf() @@ -86,7 +78,6 @@ class SystemSettingsRestrictionManager(private val context: Context) { /** * Check if battery optimization check is required. - * If enabled, ensure app is in unrestricted battery usage. */ fun checkBatteryOptimizationRequirement(): Boolean { if (!repository.isRequireUnrestrictedBattery() || @@ -94,7 +85,6 @@ class SystemSettingsRestrictionManager(private val context: Context) { return false } - // Check if app is ignored by battery optimization val pm = context.getSystemService(Context.POWER_SERVICE) return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val powerManager = pm as? android.os.PowerManager @@ -163,28 +153,24 @@ class SystemSettingsRestrictionManager(private val context: Context) { } companion object { - // Draw Over Other Apps Settings Actions private val DRAW_OVER_APPS_ACTIONS = listOf( Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - "com.android.settings.MANAGE_APP_PERMISSIONS", // Samsung specific - "com.sec.android.settings.MANAGE_PERMISSION" // Samsung specific + "com.android.settings.MANAGE_APP_PERMISSIONS", + "com.sec.android.settings.MANAGE_PERMISSION" ) - // Usage Access Settings Actions private val USAGE_ACCESS_ACTIONS = listOf( Settings.ACTION_USAGE_ACCESS_SETTINGS, "android.settings.USAGE_ACCESS_SETTINGS", "android.intent.action.USAGE_ACCESS_SETTINGS" ) - // Accessibility Settings Actions private val ACCESSIBILITY_ACTIONS = listOf( Settings.ACTION_ACCESSIBILITY_SETTINGS, "android.settings.ACCESSIBILITY_SETTINGS", "android.intent.action.ACCESSIBILITY_SETTINGS" ) - // Device Administrator Settings Actions private val DEVICE_ADMIN_ACTIONS = listOf( Settings.ACTION_SECURITY_SETTINGS, Settings.ACTION_DEVICE_ADMIN_DELETE_CONFIRM, @@ -193,7 +179,6 @@ class SystemSettingsRestrictionManager(private val context: Context) { "com.android.settings.DEVICE_ADMIN_DELETE_CONFIRM" ) - // Battery Optimization Settings Actions private val BATTERY_OPTIMIZATION_ACTIONS = listOf( Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS, Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, @@ -201,7 +186,6 @@ class SystemSettingsRestrictionManager(private val context: Context) { "android.intent.action.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" ) - // Combined list for easy access val ALL_RESTRICTED_ACTIONS = listOf( *DRAW_OVER_APPS_ACTIONS.toTypedArray(), *USAGE_ACCESS_ACTIONS.toTypedArray(), From 951c63b391b7a7f7563744f2513d151c0a83de85 Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 14:09:08 +0530 Subject: [PATCH 15/18] Add SettingsIntentInterceptor to block restricted settings Implement a BroadcastReceiver to intercept restricted system settings access and show a lock screen. Also includes an alternative Activity implementation for intercepting intents. --- .../broadcast/SettingsIntentInterceptor.kt | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 app/src/main/java/dev/pranav/applock/core/broadcast/SettingsIntentInterceptor.kt diff --git a/app/src/main/java/dev/pranav/applock/core/broadcast/SettingsIntentInterceptor.kt b/app/src/main/java/dev/pranav/applock/core/broadcast/SettingsIntentInterceptor.kt new file mode 100644 index 0000000..0ae8569 --- /dev/null +++ b/app/src/main/java/dev/pranav/applock/core/broadcast/SettingsIntentInterceptor.kt @@ -0,0 +1,113 @@ +package dev.pranav.applock.core.broadcast + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import dev.pranav.applock.core.utils.LogUtils +import dev.pranav.applock.core.utils.appLockRepository +import dev.pranav.applock.core.utils.systemSettingsRestrictionManager + +/** + * COMPLETE FILE - Copy to app/src/main/java/dev/pranav/applock/core/broadcast/ + * + * Broadcast receiver that intercepts attempts to open restricted system settings pages. + * Register in AndroidManifest.xml with the provided intent filters. + */ +class SettingsIntentInterceptor : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent?) { + if (intent == null) { + return + } + + try { + val repository = context.appLockRepository() + + if (!repository.isAntiUninstallEnabled()) { + return + } + + val restrictionManager = context.systemSettingsRestrictionManager() + + if (restrictionManager.isIntentRestricted(intent)) { + blockAndShowLockScreen(context, intent) + abortBroadcast() + } + } catch (e: Exception) { + LogUtils.logError("Error in SettingsIntentInterceptor", e) + } + } + + /** + * Block the settings intent and show lock screen instead. + */ + private fun blockAndShowLockScreen(context: Context, intent: Intent) { + try { + val action = intent.action ?: "Unknown" + LogUtils.logSecurityEvent("Blocked attempt to access settings: $action") + + val lockScreenIntent = Intent(context, PasswordOverlayActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TOP or + Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + putExtra("isRestrictedSettings", true) + putExtra("restrictedAction", intent.action) + } + context.startActivity(lockScreenIntent) + + val homeIntent = Intent(Intent.ACTION_MAIN).apply { + addCategory(Intent.CATEGORY_HOME) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + } + context.startActivity(homeIntent) + } catch (e: Exception) { + LogUtils.logError("Failed to block and show lock screen", e) + } + } + + companion object { + private const val TAG = "SettingsIntentInterceptor" + } +} + +/** + * Alternative implementation: Activity that intercepts settings intents. + * Can be used as a bridge activity to intercept intents before they reach system settings. + * Optional - only use if BroadcastReceiver approach doesn't work on your device. + */ +class SettingsIntentInterceptorActivity : android.app.Activity() { + + override fun onCreate(savedInstanceState: android.os.Bundle?) { + super.onCreate(savedInstanceState) + + try { + val intent = intent + val repository = applicationContext.appLockRepository() + + if (repository.isAntiUninstallEnabled()) { + val restrictionManager = applicationContext.systemSettingsRestrictionManager() + + if (restrictionManager.isIntentRestricted(intent)) { + LogUtils.logSecurityEvent("Blocked attempt to access settings: ${intent.action}") + + val lockScreenIntent = Intent(applicationContext, PasswordOverlayActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TOP or + Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + putExtra("isRestrictedSettings", true) + } + startActivity(lockScreenIntent) + + finish() + return + } + } + + startActivity(intent) + finish() + } catch (e: Exception) { + LogUtils.logError("Error in SettingsIntentInterceptorActivity", e) + finish() + } + } +} From fe3dbc66cb751df6105f1e381c8d57d6eacbbe6d Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 14:17:44 +0530 Subject: [PATCH 16/18] Implement system settings restrictions for anti-uninstall Added system settings restriction options for anti-uninstall feature, including toggles for draw over apps, usage access, accessibility, device admin, and unrestricted battery usage. Updated related dialogs and settings UI. --- .../features/settings/ui/SettingsScreen.kt | 999 +++++++----------- 1 file changed, 358 insertions(+), 641 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/features/settings/ui/SettingsScreen.kt b/app/src/main/java/dev/pranav/applock/features/settings/ui/SettingsScreen.kt index 01843f1..c5ea212 100644 --- a/app/src/main/java/dev/pranav/applock/features/settings/ui/SettingsScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/settings/ui/SettingsScreen.kt @@ -91,6 +91,23 @@ fun SettingsScreen( var disableHapticFeedback by remember { mutableStateOf(appLockRepository.shouldDisableHaptics()) } var loggingEnabled by remember { mutableStateOf(appLockRepository.isLoggingEnabled()) } + // NEW: System Settings Restriction State Variables + var restrictDrawOverAppsEnabled by remember { + mutableStateOf(appLockRepository.isRestrictDrawOverAppsSettings()) + } + var restrictUsageAccessEnabled by remember { + mutableStateOf(appLockRepository.isRestrictUsageAccessSettings()) + } + var restrictAccessibilityEnabled by remember { + mutableStateOf(appLockRepository.isRestrictAccessibilitySettings()) + } + var restrictDeviceAdminEnabled by remember { + mutableStateOf(appLockRepository.isRestrictDeviceAdminSettings()) + } + var requireUnrestrictedBatteryEnabled by remember { + mutableStateOf(appLockRepository.isRequireUnrestrictedBattery()) + } + var showPermissionDialog by remember { mutableStateOf(false) } var showDeviceAdminDialog by remember { mutableStateOf(false) } var showAccessibilityDialog by remember { mutableStateOf(false) } @@ -148,42 +165,38 @@ fun SettingsScreen( PermissionRequiredDialog( onDismiss = { showPermissionDialog = false }, onConfirm = { - showPermissionDialog = false showDeviceAdminDialog = true + showAccessibilityDialog = true + showPermissionDialog = false } ) } if (showDeviceAdminDialog) { - DeviceAdminDialog( + DeviceAdminPermissionDialog( onDismiss = { showDeviceAdminDialog = false }, onConfirm = { - showDeviceAdminDialog = false - val component = ComponentName(context, DeviceAdmin::class.java) - val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN).apply { - putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, component) - putExtra( - DevicePolicyManager.EXTRA_ADD_EXPLANATION, - context.getString(R.string.main_screen_device_admin_explanation) - ) - } + val intent = Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN) + intent.putExtra( + DevicePolicyManager.EXTRA_DEVICE_ADMIN, + ComponentName(context, DeviceAdmin::class.java) + ) + intent.putExtra( + DevicePolicyManager.EXTRA_ADD_EXPLANATION, + context.getString(R.string.device_admin_explanation) + ) context.startActivity(intent) + showDeviceAdminDialog = false } ) } if (showAccessibilityDialog) { - AccessibilityDialog( + AntiUninstallAccessibilityPermissionDialog( onDismiss = { showAccessibilityDialog = false }, onConfirm = { - showAccessibilityDialog = false openAccessibilitySettings(context) - val dpm = - context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val component = ComponentName(context, DeviceAdmin::class.java) - if (!dpm.isAdminActive(component)) { - showDeviceAdminDialog = true - } + showAccessibilityDialog = false } ) } @@ -195,8 +208,13 @@ fun SettingsScreen( .fillMaxSize() .nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { - LargeTopAppBar( - title = { Text(stringResource(R.string.settings_screen_title)) }, + MediumTopAppBar( + title = { + Text( + text = stringResource(R.string.settings_screen_title), + style = MaterialTheme.typography.headlineSmall + ) + }, navigationIcon = { IconButton(onClick = { navController.popBackStack() }) { Icon( @@ -327,45 +345,171 @@ fun SettingsScreen( ) } else stringResource(R.string.settings_screen_unlock_duration_summary_immediate), onClick = { showUnlockTimeDialog = true } - ), - ToggleSettingItem( - icon = Icons.Default.Lock, - title = stringResource(R.string.settings_screen_anti_uninstall_title), - subtitle = stringResource(R.string.settings_screen_anti_uninstall_desc), - checked = antiUninstallEnabled, - enabled = true, - onCheckedChange = { isChecked -> - if (isChecked) { - val dpm = - context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager - val component = ComponentName(context, DeviceAdmin::class.java) - val hasDeviceAdmin = dpm.isAdminActive(component) - val hasAccessibility = context.isAccessibilityServiceEnabled() + ) + ) + ) + } - when { - !hasDeviceAdmin && !hasAccessibility -> { - showPermissionDialog = true - } - !hasDeviceAdmin -> { - showDeviceAdminDialog = true - } - !hasAccessibility -> { - showAccessibilityDialog = true - } - else -> { - antiUninstallEnabled = true - appLockRepository.setAntiUninstallEnabled(true) + // NEW: ANTI-UNINSTALL WITH SYSTEM SETTINGS RESTRICTIONS + item { + Column { + // Main Anti-Uninstall Toggle + SettingsGroup( + items = listOf( + ToggleSettingItem( + icon = Icons.Default.Lock, + title = stringResource(R.string.settings_screen_anti_uninstall_title), + subtitle = stringResource(R.string.settings_screen_anti_uninstall_desc), + checked = antiUninstallEnabled, + enabled = true, + onCheckedChange = { isChecked -> + if (isChecked) { + val dpm = + context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager + val component = ComponentName(context, DeviceAdmin::class.java) + val hasDeviceAdmin = dpm.isAdminActive(component) + val hasAccessibility = context.isAccessibilityServiceEnabled() + + when { + !hasDeviceAdmin && !hasAccessibility -> { + showPermissionDialog = true + } + !hasDeviceAdmin -> { + showDeviceAdminDialog = true + } + !hasAccessibility -> { + showAccessibilityDialog = true + } + else -> { + antiUninstallEnabled = true + appLockRepository.setAntiUninstallEnabled(true) + } } + } else { + context.startActivity( + Intent(context, AdminDisableActivity::class.java) + ) } - } else { - context.startActivity( - Intent(context, AdminDisableActivity::class.java) - ) } - } + ) ) ) - ) + + // Sub-Switches - Only visible when Anti-Uninstall is enabled + AnimatedVisibility( + visible = antiUninstallEnabled, + enter = expandVertically( + animationSpec = spring( + dampingRatio = 0.8f, + stiffness = 100f + ) + ) + fadeIn(), + exit = shrinkVertically( + animationSpec = spring( + dampingRatio = 0.8f, + stiffness = 100f + ) + ) + fadeOut() + ) { + Column( + modifier = Modifier.padding(top = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Text( + text = "Restrict System Settings Access", + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(start = 16.dp, top = 8.dp), + color = MaterialTheme.colorScheme.onSurface + ) + + // Disable "Draw Over Other Apps" Settings Page + SettingsGroup( + items = listOf( + ToggleSettingItem( + icon = Icons.Default.Visibility, + title = "Disable Draw Over Other Apps", + subtitle = "Prevent disabling overlay permission used by unlock screen", + checked = restrictDrawOverAppsEnabled, + enabled = antiUninstallEnabled, + onCheckedChange = { isChecked -> + restrictDrawOverAppsEnabled = isChecked + appLockRepository.setRestrictDrawOverAppsSettings(isChecked) + } + ) + ) + ) + + // Disable "Usage Access" Settings Page + SettingsGroup( + items = listOf( + ToggleSettingItem( + icon = Icons.Default.BarChart, + title = "Disable Usage Access", + subtitle = "Prevent disabling Usage Stats permission required for foreground app detection", + checked = restrictUsageAccessEnabled, + enabled = antiUninstallEnabled, + onCheckedChange = { isChecked -> + restrictUsageAccessEnabled = isChecked + appLockRepository.setRestrictUsageAccessSettings(isChecked) + } + ) + ) + ) + + // Disable "Accessibility Settings" Page + SettingsGroup( + items = listOf( + ToggleSettingItem( + icon = Accessibility, + title = "Disable Accessibility Settings", + subtitle = "Prevent disabling Accessibility Service used for app lock monitoring", + checked = restrictAccessibilityEnabled, + enabled = antiUninstallEnabled, + onCheckedChange = { isChecked -> + restrictAccessibilityEnabled = isChecked + appLockRepository.setRestrictAccessibilitySettings(isChecked) + } + ) + ) + ) + + // Disable "Device Administrator Settings" Page + SettingsGroup( + items = listOf( + ToggleSettingItem( + icon = Icons.Outlined.Security, + title = "Disable Device Administrator Settings", + subtitle = "Prevent removing Device Administrator privilege", + checked = restrictDeviceAdminEnabled, + enabled = antiUninstallEnabled, + onCheckedChange = { isChecked -> + restrictDeviceAdminEnabled = isChecked + appLockRepository.setRestrictDeviceAdminSettings(isChecked) + } + ) + ) + ) + + // Require "Unrestricted Battery Usage" + SettingsGroup( + items = listOf( + ToggleSettingItem( + icon = BatterySaver, + title = "Require Unrestricted Battery Usage", + subtitle = "Ensure app is not restricted by battery optimization", + checked = requireUnrestrictedBatteryEnabled, + enabled = antiUninstallEnabled, + onCheckedChange = { isChecked -> + requireUnrestrictedBatteryEnabled = isChecked + appLockRepository.setRequireUnrestrictedBattery(isChecked) + } + ) + ) + ) + } + } + } } item { @@ -400,39 +544,24 @@ fun SettingsScreen( } ), ActionSettingItem( - icon = Icons.Outlined.BugReport, - title = stringResource(R.string.settings_screen_export_logs_title), - subtitle = stringResource(R.string.settings_screen_export_logs_desc), + icon = Icons.Outlined.Code, + title = stringResource(R.string.settings_screen_backend_title), + subtitle = when (appLockRepository.getBackendImplementation()) { + BackendImplementation.ACCESSIBILITY -> stringResource(R.string.settings_screen_backend_accessibility) + BackendImplementation.USAGE_STATS -> stringResource(R.string.settings_screen_backend_usage_stats) + BackendImplementation.SHIZUKU -> stringResource(R.string.settings_screen_backend_shizuku) + }, onClick = { - val uri = LogUtils.exportLogs() - if (uri != null) { - val shareIntent = Intent(Intent.ACTION_SEND).apply { - type = "text/plain" - putExtra(Intent.EXTRA_STREAM, uri) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - } - context.startActivity( - Intent.createChooser(shareIntent, "Share logs") - ) - } else { - Toast.makeText( - context, - context.getString(R.string.settings_screen_export_logs_error), - Toast.LENGTH_SHORT - ).show() - } + navController.navigate(Screen.ChooseBackend.route) } ), - ToggleSettingItem( - icon = Icons.Default.Troubleshoot, - title = "Logging", - subtitle = "Enable debug logging for troubleshooting", - checked = loggingEnabled, - enabled = true, - onCheckedChange = { isChecked -> - loggingEnabled = isChecked - appLockRepository.setLoggingEnabled(isChecked) - LogUtils.setLoggingEnabled(isChecked) + ActionSettingItem( + icon = Icons.Outlined.BugReport, + title = stringResource(R.string.settings_screen_logging_title), + subtitle = stringResource(R.string.settings_screen_logging_desc), + onClick = { + loggingEnabled = !loggingEnabled + appLockRepository.setLoggingEnabled(loggingEnabled) } ) ) @@ -440,670 +569,258 @@ fun SettingsScreen( } item { - BackendSelectionCard( - appLockRepository = appLockRepository, - context = context, - shizukuPermissionLauncher = shizukuPermissionLauncher - ) - } - - item { - LinksSection() + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(8.dp)) + FilledTonalButton(onClick = { showDialog = true }) { + Text(stringResource(R.string.settings_screen_support_development_button)) + } + Spacer(modifier = Modifier.height(8.dp)) + TextButton(onClick = { navController.navigate("https://github.com/PranavPurwar/AppLock") }) { + Icon( + imageVector = Github, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(24.dp) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(stringResource(R.string.settings_screen_github_button)) + } + Spacer(modifier = Modifier.height(8.dp)) + } } } } } @Composable -fun SectionTitle(text: String) { +private fun SectionTitle(text: String) { Text( text = text, style = MaterialTheme.typography.titleSmall, fontWeight = FontWeight.SemiBold, - color = MaterialTheme.colorScheme.primary, - modifier = Modifier.padding(start = 16.dp, bottom = 4.dp, top = 4.dp) + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.padding(start = 16.dp, top = 24.dp, bottom = 8.dp) ) } -sealed class SettingItemType { - data class Toggle( - val icon: ImageVector, - val title: String, - val subtitle: String, - val checked: Boolean, - val enabled: Boolean, - val onCheckedChange: (Boolean) -> Unit - ): SettingItemType() - - data class Action( - val icon: ImageVector, - val title: String, - val subtitle: String, - val onClick: () -> Unit - ): SettingItemType() -} - -data class ToggleSettingItem( - val icon: ImageVector, - val title: String, - val subtitle: String, - val checked: Boolean, - val enabled: Boolean, - val onCheckedChange: (Boolean) -> Unit -) - -data class ActionSettingItem( - val icon: ImageVector, - val title: String, - val subtitle: String, - val onClick: () -> Unit -) - @Composable -fun SettingsGroup( - items: List -) { - Column { +private fun SettingsGroup(items: List) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(12.dp)) + .background(MaterialTheme.colorScheme.surfaceContainer), + verticalArrangement = Arrangement.spacedBy(1.dp) + ) { items.forEachIndexed { index, item -> - SettingsCard(index = index, listSize = items.size) { - when (item) { - is ToggleSettingItem -> { - ToggleSettingRow( - icon = item.icon, - title = item.title, - subtitle = item.subtitle, - checked = item.checked, - enabled = item.enabled, - onCheckedChange = item.onCheckedChange - ) - } - - is ActionSettingItem -> { - ActionSettingRow( - icon = item.icon, - title = item.title, - subtitle = item.subtitle, - onClick = item.onClick - ) - } - } + when (item) { + is ToggleSettingItem -> ToggleSettingItemComposable(item) + is ActionSettingItem -> ActionSettingItemComposable(item) + } + if (index < items.lastIndex) { + Divider( + modifier = Modifier + .fillMaxWidth() + .height(1.dp), + color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.2f) + ) } } } } @Composable -fun SettingsCard( - index: Int, - listSize: Int, - content: @Composable () -> Unit -) { - val shape = when { - listSize == 1 -> RoundedCornerShape(24.dp) - index == 0 -> RoundedCornerShape( - topStart = 24.dp, - topEnd = 24.dp, - bottomStart = 6.dp, - bottomEnd = 6.dp - ) - - index == listSize - 1 -> RoundedCornerShape( - topStart = 6.dp, - topEnd = 6.dp, - bottomStart = 24.dp, - bottomEnd = 24.dp - ) - - else -> RoundedCornerShape(6.dp) - } - - AnimatedVisibility( - visible = true, - enter = fadeIn() + scaleIn( - initialScale = 0.95f, - animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy) - ), - exit = fadeOut() + shrinkVertically() - ) { - Card( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 1.dp), - colors = CardDefaults.cardColors( - containerColor = MaterialTheme.colorScheme.surfaceContainer - ), - shape = shape - ) { - content() - } - } -} - -@Composable -fun ToggleSettingRow( - icon: ImageVector, - title: String, - subtitle: String, - checked: Boolean, - enabled: Boolean, - onCheckedChange: (Boolean) -> Unit -) { - ListItem( +private fun ToggleSettingItemComposable(item: ToggleSettingItem) { + Row( modifier = Modifier - .clickable(enabled = enabled) { if (enabled) onCheckedChange(!checked) } - .padding(vertical = 2.dp, horizontal = 4.dp), - headlineContent = { + .fillMaxWidth() + .clickable(enabled = item.enabled) { item.onCheckedChange(!item.checked) } + .padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = item.icon, + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = if (item.enabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant + ) + Column(modifier = Modifier.weight(1f)) { Text( - text = title, - style = MaterialTheme.typography.titleMedium + text = item.title, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Medium, + color = if (item.enabled) MaterialTheme.colorScheme.onSurface else MaterialTheme.colorScheme.onSurfaceVariant ) - }, - supportingContent = { Text( - text = subtitle, - style = MaterialTheme.typography.bodySmall + text = item.subtitle, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant ) - }, - leadingContent = { - Box( - modifier = Modifier.size(24.dp), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = icon, - contentDescription = null, - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.secondary - ) - } - }, - trailingContent = { - Box( - contentAlignment = Alignment.Center - ) { - Switch( - checked = checked, - onCheckedChange = null, - enabled = enabled - ) - } - }, - colors = ListItemDefaults.colors( - containerColor = Color.Transparent + } + Switch( + checked = item.checked, + onCheckedChange = { item.onCheckedChange(it) }, + enabled = item.enabled ) - ) + } } @Composable -fun ActionSettingRow( - icon: ImageVector, - title: String, - subtitle: String, - onClick: () -> Unit -) { - ListItem( +private fun ActionSettingItemComposable(item: ActionSettingItem) { + Row( modifier = Modifier - .clickable(onClick = onClick) - .padding(vertical = 2.dp, horizontal = 4.dp), - headlineContent = { + .fillMaxWidth() + .clickable { item.onClick() } + .padding(16.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = item.icon, + contentDescription = null, + modifier = Modifier.size(24.dp), + tint = MaterialTheme.colorScheme.onSurface + ) + Column(modifier = Modifier.weight(1f)) { Text( - text = title, - style = MaterialTheme.typography.titleMedium + text = item.title, + style = MaterialTheme.typography.bodyLarge, + fontWeight = FontWeight.Medium ) - }, - supportingContent = { Text( - text = subtitle, - style = MaterialTheme.typography.bodySmall + text = item.subtitle, + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant ) - }, - leadingContent = { - Box( - modifier = Modifier.size(24.dp), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = icon, - contentDescription = null, - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.secondary - ) - } - }, - trailingContent = { - Box( - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight, - contentDescription = null - ) - } - }, - colors = ListItemDefaults.colors( - containerColor = Color.Transparent + } + Icon( + imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight, + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant ) - ) + } } +sealed class SettingItem +data class ToggleSettingItem( + val icon: ImageVector, + val title: String, + val subtitle: String, + val checked: Boolean, + val enabled: Boolean = true, + val onCheckedChange: (Boolean) -> Unit +) : SettingItem() + +data class ActionSettingItem( + val icon: ImageVector, + val title: String, + val subtitle: String, + val onClick: () -> Unit +) : SettingItem() + @Composable -fun UnlockTimeDurationDialog( +private fun UnlockTimeDurationDialog( currentDuration: Int, onDismiss: () -> Unit, onConfirm: (Int) -> Unit ) { - val durations = listOf(0, 1, 5, 15, 30, 60, Integer.MAX_VALUE) - var selectedDuration by remember { mutableIntStateOf(currentDuration) } - - if (!durations.contains(selectedDuration)) { - selectedDuration = durations.minByOrNull { abs(it - currentDuration) } ?: 0 - } + var duration by remember { mutableIntStateOf(currentDuration) } AlertDialog( onDismissRequest = onDismiss, - title = { Text(stringResource(R.string.settings_screen_unlock_duration_dialog_title)) }, + title = { Text("Set Unlock Duration") }, text = { Column { - Text(stringResource(R.string.settings_screen_unlock_duration_dialog_description_new)) - durations.forEach { duration -> - Row( - modifier = Modifier - .fillMaxWidth() - .clickable { selectedDuration = duration } - .padding(vertical = 8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - RadioButton( - selected = selectedDuration == duration, - onClick = { selectedDuration = duration } - ) - Text( - text = when (duration) { - 0 -> stringResource(R.string.settings_screen_unlock_duration_dialog_option_immediate) - 1 -> stringResource( - R.string.settings_screen_unlock_duration_dialog_option_minute, - duration - ) - 60 -> stringResource(R.string.settings_screen_unlock_duration_dialog_option_hour) - Integer.MAX_VALUE -> "Until Screen Off" - else -> stringResource( - R.string.settings_screen_unlock_duration_summary_minutes, - duration - ) - }, - modifier = Modifier.padding(start = 8.dp) - ) - } - } + Text("Duration in seconds (0 = immediately, 10000+ = until screen off)") + OutlinedTextField( + value = duration.toString(), + onValueChange = { duration = it.toIntOrNull() ?: 0 }, + label = { Text("Seconds") }, + modifier = Modifier.fillMaxWidth() + ) } }, confirmButton = { - TextButton(onClick = { onConfirm(selectedDuration) }) { - Text(stringResource(R.string.confirm_button)) + TextButton(onClick = { onConfirm(duration) }) { + Text("Set") } }, dismissButton = { TextButton(onClick = onDismiss) { - Text(stringResource(R.string.cancel_button)) + Text("Cancel") } } ) } @Composable -fun BackendSelectionCard( - appLockRepository: AppLockRepository, - context: Context, - shizukuPermissionLauncher: androidx.activity.result.ActivityResultLauncher -) { - var selectedBackend by remember { mutableStateOf(appLockRepository.getBackendImplementation()) } - - Column { - SectionTitle(text = stringResource(R.string.settings_screen_backend_implementation_title)) - - Column { - BackendImplementation.entries.forEachIndexed { index, backend -> - SettingsCard( - index = index, - listSize = BackendImplementation.entries.size - ) { - BackendSelectionItem( - backend = backend, - isSelected = selectedBackend == backend, - onClick = { - when (backend) { - BackendImplementation.SHIZUKU -> { - if (!Shizuku.pingBinder() || Shizuku.checkSelfPermission() == PackageManager.PERMISSION_DENIED) { - if (Shizuku.isPreV11()) { - shizukuPermissionLauncher.launch(ShizukuProvider.PERMISSION) - } else if (Shizuku.pingBinder()) { - Shizuku.requestPermission(423) - } else { - Toast.makeText( - context, - context.getString(R.string.settings_screen_shizuku_not_running_toast), - Toast.LENGTH_LONG - ).show() - } - } else { - selectedBackend = backend - appLockRepository.setBackendImplementation( - BackendImplementation.SHIZUKU - ) - context.startService( - Intent(context, ShizukuAppLockService::class.java) - ) - } - } - BackendImplementation.USAGE_STATS -> { - if (!context.hasUsagePermission()) { - val intent = - Intent(android.provider.Settings.ACTION_USAGE_ACCESS_SETTINGS) - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - context.startActivity(intent) - Toast.makeText( - context, - context.getString(R.string.settings_screen_usage_permission_toast), - Toast.LENGTH_LONG - ).show() - return@BackendSelectionItem - } - selectedBackend = backend - appLockRepository.setBackendImplementation(BackendImplementation.USAGE_STATS) - context.startService( - Intent(context, ExperimentalAppLockService::class.java) - ) - } - BackendImplementation.ACCESSIBILITY -> { - if (!context.isAccessibilityServiceEnabled()) { - openAccessibilitySettings(context) - return@BackendSelectionItem - } - selectedBackend = backend - appLockRepository.setBackendImplementation(BackendImplementation.ACCESSIBILITY) - } - } - } - ) - } - } - } - } -} - -@OptIn(ExperimentalMaterial3ExpressiveApi::class) -@Composable -fun BackendSelectionItem( - backend: BackendImplementation, - isSelected: Boolean, - onClick: () -> Unit -) { - ListItem( - modifier = Modifier - .clickable { onClick() } - .padding(vertical = 2.dp, horizontal = 4.dp), - headlineContent = { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = getBackendDisplayName(backend), - style = MaterialTheme.typography.titleMedium, - color = if (isSelected) MaterialTheme.colorScheme.primary - else MaterialTheme.colorScheme.onSurface - ) - if (backend == BackendImplementation.SHIZUKU) { - Spacer(modifier = Modifier.width(8.dp)) - Badge( - containerColor = MaterialTheme.colorScheme.tertiary, - contentColor = MaterialTheme.colorScheme.onTertiary - ) { - Text( - text = stringResource(R.string.settings_screen_backend_implementation_shizuku_advanced), - style = MaterialTheme.typography.labelSmall - ) - } - } - } - }, - supportingContent = { - Text( - text = getBackendDescription(backend), - style = MaterialTheme.typography.bodySmall - ) - }, - leadingContent = { - Box( - modifier = Modifier.size(24.dp), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = getBackendIcon(backend), - contentDescription = null, - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.secondary - ) - } - }, - trailingContent = { - Box( - contentAlignment = Alignment.Center - ) { - RadioButton( - selected = isSelected, - onClick = onClick, - colors = RadioButtonDefaults.colors( - selectedColor = MaterialTheme.colorScheme.primary - ) - ) - } - }, - colors = ListItemDefaults.colors( - containerColor = Color.Transparent - ) - ) -} - -private fun getBackendDisplayName(backend: BackendImplementation): String { - return when (backend) { - BackendImplementation.ACCESSIBILITY -> "Accessibility Service" - BackendImplementation.USAGE_STATS -> "Usage Statistics" - BackendImplementation.SHIZUKU -> "Shizuku Service" - } -} - -private fun getBackendDescription(backend: BackendImplementation): String { - return when (backend) { - BackendImplementation.ACCESSIBILITY -> "Standard method that works on most devices" - BackendImplementation.USAGE_STATS -> "Experimental method using app usage statistics" - BackendImplementation.SHIZUKU -> "Advanced method using Shizuku and internal APIs" - } -} - -private fun getBackendIcon(backend: BackendImplementation): ImageVector { - return when (backend) { - BackendImplementation.ACCESSIBILITY -> Accessibility - BackendImplementation.USAGE_STATS -> Icons.Default.QueryStats - BackendImplementation.SHIZUKU -> Icons.Default.AutoAwesome - } -} - -@Composable -fun PermissionRequiredDialog( +private fun PermissionRequiredDialog( onDismiss: () -> Unit, onConfirm: () -> Unit ) { AlertDialog( onDismissRequest = onDismiss, - title = { Text(stringResource(R.string.settings_screen_permission_required_dialog_title)) }, - text = { - Column { - Text(stringResource(R.string.settings_screen_permission_required_dialog_text_1)) - Text(stringResource(R.string.settings_screen_permission_required_dialog_text_2)) - } - }, + title = { Text("Permissions Required") }, + text = { Text("Device Administrator and Accessibility Service permissions are required for Anti-Uninstall protection.") }, confirmButton = { TextButton(onClick = onConfirm) { - Text(stringResource(R.string.grant_permission_button)) + Text("Grant") } }, dismissButton = { TextButton(onClick = onDismiss) { - Text(stringResource(R.string.cancel_button)) + Text("Cancel") } } ) } @Composable -fun DeviceAdminDialog( +private fun DeviceAdminPermissionDialog( onDismiss: () -> Unit, onConfirm: () -> Unit ) { AlertDialog( onDismissRequest = onDismiss, - title = { Text(stringResource(R.string.settings_screen_device_admin_dialog_title)) }, - text = { - Column { - Text(stringResource(R.string.settings_screen_device_admin_dialog_text_1)) - Text(stringResource(R.string.settings_screen_device_admin_dialog_text_2)) - } - }, + title = { Text("Device Administrator Required") }, + text = { Text("Grant Device Administrator permission to enable Anti-Uninstall protection.") }, confirmButton = { TextButton(onClick = onConfirm) { - Text(stringResource(R.string.enable_button)) + Text("Grant") } }, dismissButton = { TextButton(onClick = onDismiss) { - Text(stringResource(R.string.cancel_button)) + Text("Cancel") } } ) } @Composable -fun AccessibilityDialog( +private fun AntiUninstallAccessibilityPermissionDialog( onDismiss: () -> Unit, onConfirm: () -> Unit ) { AlertDialog( onDismissRequest = onDismiss, - title = { Text(stringResource(R.string.settings_screen_accessibility_dialog_title)) }, - text = { - Column { - Text(stringResource(R.string.settings_screen_accessibility_dialog_text_1)) - Text(stringResource(R.string.settings_screen_accessibility_dialog_text_2)) - Text(stringResource(R.string.settings_screen_accessibility_dialog_text_3)) - } - }, + title = { Text("Accessibility Service Required") }, + text = { Text("Enable Accessibility Service for App Lock to provide Anti-Uninstall protection.") }, confirmButton = { TextButton(onClick = onConfirm) { - Text(stringResource(R.string.enable_button)) + Text("Enable") } }, dismissButton = { TextButton(onClick = onDismiss) { - Text(stringResource(R.string.cancel_button)) - } - } - ) -} - -@Composable -fun LinksSection() { - val context = LocalContext.current - - Column { - SectionTitle(text = "Links") - - Column { - SettingsCard(index = 0, listSize = 3) { - LinkItem( - title = "Discord Community", - icon = Discord, - onClick = { - val intent = Intent( - Intent.ACTION_VIEW, - "https://discord.gg/46wCMRVAre".toUri() - ) - context.startActivity(intent) - } - ) - } - - SettingsCard(index = 1, listSize = 3) { - LinkItem( - title = "Source Code", - icon = Icons.Outlined.Code, - onClick = { - val intent = Intent( - Intent.ACTION_VIEW, - "https://github.com/aload0/AppLock".toUri() - ) - context.startActivity(intent) - } - ) - } - - SettingsCard(index = 2, listSize = 3) { - LinkItem( - title = "Report Issue", - icon = Icons.Outlined.BugReport, - onClick = { - val intent = Intent( - Intent.ACTION_VIEW, - "https://github.com/aload0/AppLock/issues".toUri() - ) - context.startActivity(intent) - } - ) + Text("Cancel") } } - } -} - -@Composable -fun LinkItem( - title: String, - icon: ImageVector, - onClick: () -> Unit -) { - ListItem( - modifier = Modifier - .clickable(onClick = onClick) - .padding(vertical = 2.dp, horizontal = 4.dp), - headlineContent = { - Text( - text = title, - style = MaterialTheme.typography.titleMedium - ) - }, - leadingContent = { - Box( - modifier = Modifier.size(24.dp), - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = icon, - contentDescription = null, - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.secondary - ) - } - }, - trailingContent = { - Box( - contentAlignment = Alignment.Center - ) { - Icon( - imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight, - contentDescription = null - ) - } - }, - colors = ListItemDefaults.colors( - containerColor = Color.Transparent - ) ) } From 8e3dfa36ce6702b49aae29f3132ae8ee6247cbac Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 14:19:15 +0530 Subject: [PATCH 17/18] Add permissions and receiver for system settings Added new permissions for system settings restrictions and included a broadcast receiver for intercepting system settings intents. --- app/src/main/AndroidManifest.xml | 55 +++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8a00795..b6084a8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" android:installLocation="internalOnly"> + @@ -26,9 +27,12 @@ tools:ignore="ForegroundServicesPolicy" /> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From d8b7740fada5b81fba92becb0412d30d81b200c9 Mon Sep 17 00:00:00 2001 From: ap0803apap-sketch Date: Sat, 7 Mar 2026 14:21:33 +0530 Subject: [PATCH 18/18] Implement system settings restriction management Added system settings restriction management to enhance app lock functionality and prevent unauthorized access to sensitive settings. --- .../services/AppLockAccessibilityService.kt | 171 +++++++++++++++++- 1 file changed, 165 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt b/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt index cef1694..201bd66 100644 --- a/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt +++ b/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt @@ -6,10 +6,12 @@ import android.annotation.SuppressLint import android.content.Intent import android.util.Log import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityNodeInfo import android.view.inputmethod.InputMethodManager import androidx.core.content.getSystemService import dev.pranav.applock.core.utils.LogUtils import dev.pranav.applock.core.utils.appLockRepository +import dev.pranav.applock.core.utils.systemSettingsRestrictionManager import dev.pranav.applock.data.repository.AppLockRepository import dev.pranav.applock.data.repository.BackendImplementation import dev.pranav.applock.features.lockscreen.ui.PasswordOverlayActivity @@ -23,6 +25,11 @@ class AppLockAccessibilityService : AccessibilityService() { getKeyboardPackageNames() } + // NEW: System Settings Restriction Manager + private val restrictionManager by lazy { + applicationContext.systemSettingsRestrictionManager() + } + private var lastForegroundPackage = "" companion object { @@ -50,7 +57,8 @@ class AppLockAccessibilityService : AccessibilityService() { eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED or AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED or - AccessibilityEvent.TYPE_WINDOWS_CHANGED + AccessibilityEvent.TYPE_WINDOWS_CHANGED or + AccessibilityEvent.TYPE_VIEW_CLICKED feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC @@ -83,7 +91,15 @@ class AppLockAccessibilityService : AccessibilityService() { val className = event.className?.toString() /* --------------------------------------------------------- - 🔒 Restrict System Settings Access + 🔒 NEW: Restrict System Settings Access (Anti-Uninstall) + ---------------------------------------------------------- */ + + if (isSettingsPackage(packageName)) { + handleSettingsPackageOpening(event) + } + + /* --------------------------------------------------------- + 🔒 ORIGINAL: Restrict System Settings Access ---------------------------------------------------------- */ if (isRestrictedSettings(packageName, className)) { @@ -103,7 +119,150 @@ class AppLockAccessibilityService : AccessibilityService() { } /* --------------------------------------------------------- - SETTINGS PROTECTION + NEW METHODS: SYSTEM SETTINGS RESTRICTION (Anti-Uninstall) + ---------------------------------------------------------- */ + + /** + * Determine if a package is a system settings app. + */ + private fun isSettingsPackage(packageName: String): Boolean { + return packageName in listOf( + "com.android.settings", + "com.sec.android.app.personalpage", + "com.oppo.safe", + "com.vivo.settings", + "com.huawei.systemmanager", + "com.xiaomi.misettings" + ) + } + + /** + * Handle when settings package is being opened. + * Check if it's trying to access a restricted settings page. + */ + private fun handleSettingsPackageOpening(event: AccessibilityEvent) { + try { + val appLockRepo = applicationContext.appLockRepository() + + if (!appLockRepo.isAntiUninstallEnabled()) { + return + } + + if (!appLockRepo.hasAnySystemSettingsRestriction()) { + return + } + + val sourceNode = event.source ?: return + + if (detectRestrictedSettingsActivity(sourceNode, appLockRepo)) { + showLockScreenForRestrictedSettings() + performGlobalAction(GLOBAL_ACTION_HOME) + } + + sourceNode.recycle() + } catch (e: Exception) { + LogUtils.logError("Error handling settings package opening", e) + } + } + + /** + * Detect if a restricted settings activity is being accessed. + */ + private fun detectRestrictedSettingsActivity( + sourceNode: AccessibilityNodeInfo, + repository: AppLockRepository + ): Boolean { + try { + val text = sourceNode.text?.toString() ?: "" + val contentDescription = sourceNode.contentDescription?.toString() ?: "" + + return when { + repository.isRestrictDrawOverAppsSettings() && + (text.contains("overlay", ignoreCase = true) || + text.contains("draw", ignoreCase = true)) -> { + LogUtils.d(TAG, "Detected restricted Draw Over Apps settings") + true + } + + repository.isRestrictUsageAccessSettings() && + (text.contains("usage", ignoreCase = true) || + text.contains("data usage", ignoreCase = true)) -> { + LogUtils.d(TAG, "Detected restricted Usage Access settings") + true + } + + repository.isRestrictAccessibilitySettings() && + (text.contains("accessibility", ignoreCase = true) || + contentDescription.contains("accessibility", ignoreCase = true)) -> { + LogUtils.d(TAG, "Detected restricted Accessibility settings") + true + } + + repository.isRestrictDeviceAdminSettings() && + (text.contains("admin", ignoreCase = true) || + text.contains("device administrator", ignoreCase = true)) -> { + LogUtils.d(TAG, "Detected restricted Device Admin settings") + true + } + + repository.isRequireUnrestrictedBattery() && + (text.contains("battery", ignoreCase = true) || + text.contains("power saving", ignoreCase = true)) -> { + LogUtils.d(TAG, "Detected restricted Battery Optimization settings") + true + } + + else -> false + } + } catch (e: Exception) { + LogUtils.logError("Error detecting restricted settings activity", e) + return false + } + } + + /** + * Create and show the lock screen when user tries to access restricted settings. + */ + private fun showLockScreenForRestrictedSettings() { + try { + if (AppLockManager.isLockScreenShown.get()) return + + AppLockManager.isLockScreenShown.set(true) + + val lockScreenIntent = Intent(applicationContext, PasswordOverlayActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or + Intent.FLAG_ACTIVITY_CLEAR_TOP or + Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + putExtra("isRestrictedSettings", true) + putExtra("locked_package", "com.android.settings") + } + applicationContext.startActivity(lockScreenIntent) + + LogUtils.d(TAG, "Showed lock screen for restricted settings") + } catch (e: Exception) { + LogUtils.logError("Failed to show lock screen for restricted settings", e) + } + } + + /** + * Optional: Called from broadcast receiver if needed + */ + protected open fun onSettingsIntentIntercepted(action: String) { + try { + val restrictMgr = applicationContext.systemSettingsRestrictionManager() + val intent = Intent(action) + + if (restrictMgr.isIntentRestricted(intent)) { + showLockScreenForRestrictedSettings() + LogUtils.d(TAG, "Settings intent intercepted for action: $action") + } + } catch (e: Exception) { + LogUtils.logError("Error in onSettingsIntentIntercepted", e) + } + } + + /* --------------------------------------------------------- + ORIGINAL METHODS: SETTINGS PROTECTION ---------------------------------------------------------- */ private fun isRestrictedSettings(pkg: String, cls: String?): Boolean { @@ -150,7 +309,7 @@ class AppLockAccessibilityService : AccessibilityService() { } /* --------------------------------------------------------- - NORMAL APP LOCK LOGIC + ORIGINAL METHODS: NORMAL APP LOCK LOGIC ---------------------------------------------------------- */ private fun processPackageLocking(packageName: String) { @@ -192,7 +351,7 @@ class AppLockAccessibilityService : AccessibilityService() { } /* --------------------------------------------------------- - VALIDATION + ORIGINAL METHODS: VALIDATION ---------------------------------------------------------- */ private fun isValidPackage(packageName: String): Boolean { @@ -229,7 +388,7 @@ class AppLockAccessibilityService : AccessibilityService() { } /* --------------------------------------------------------- - BACKEND CONTROL + ORIGINAL METHODS: BACKEND CONTROL ---------------------------------------------------------- */ private fun startPrimaryBackendService() {