From 996378fb44c22c056458952287f6ee13f4b3d5a7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 01:12:55 +0000 Subject: [PATCH 1/6] Initial plan From 2db6fc6e24a444049d2c824c608dadae900e714a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 01:20:05 +0000 Subject: [PATCH 2/6] Fix issues #138, #137, #135, and #132 - Issue #138: Cancel biometric prompt in onPause and finish activity when user cancels - Issue #137: Add configChanges to PasswordOverlayActivity to handle screen rotation - Issue #135: Skip temporarily unlocked check when duration is 0 (lock immediately) - Issue #132: Use BIOMETRIC_STRONG only to prefer fingerprint over face unlock Co-authored-by: PranavPurwar <75154889+PranavPurwar@users.noreply.github.com> --- app/src/main/AndroidManifest.xml | 1 + .../applock/core/navigation/AppNavigator.kt | 3 +-- .../lockscreen/ui/PasswordOverlayScreen.kt | 22 +++++++++++++++++-- .../services/AppLockAccessibilityService.kt | 21 +++++++++++++----- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c85184f..81f0f61 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -57,6 +57,7 @@ android:name=".features.lockscreen.ui.PasswordOverlayActivity" android:excludeFromRecents="true" android:exported="false" + android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" android:theme="@android:style/Theme.Material.NoActionBar.TranslucentDecor" /> 0) + if (AppLockManager.isAppTemporarilyUnlocked(packageName)) { + return + } + + AppLockManager.clearTemporarilyUnlockedApp() } - AppLockManager.clearTemporarilyUnlockedApp() - - val unlockDurationMinutes = appLockRepository.getUnlockTimeDuration() val unlockTimestamp = AppLockManager.appUnlockTimes[packageName] ?: 0L Log.d( From cbe1a296e1339ca695a330bf163d031be76b5ac9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 01:22:23 +0000 Subject: [PATCH 3/6] Address code review feedback - Simplify unlock duration logic by extracting common clearTemporarilyUnlockedApp call - Catch specific IllegalStateException instead of generic Exception Co-authored-by: PranavPurwar <75154889+PranavPurwar@users.noreply.github.com> --- .../features/lockscreen/ui/PasswordOverlayScreen.kt | 2 +- .../applock/services/AppLockAccessibilityService.kt | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt index 0870c55..0b4c91d 100644 --- a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt @@ -333,7 +333,7 @@ class PasswordOverlayActivity : FragmentActivity() { if (isBiometricPromptShowingLocal) { try { biometricPrompt.cancelAuthentication() - } catch (e: Exception) { + } catch (e: IllegalStateException) { Log.e(TAG, "Error canceling biometric prompt: ${e.message}") } isBiometricPromptShowingLocal = false 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 c2824b4..2dc11bc 100644 --- a/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt +++ b/app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt @@ -260,19 +260,17 @@ class AppLockAccessibilityService : AccessibilityService() { val unlockDurationMinutes = appLockRepository.getUnlockTimeDuration() // If duration is 0 (lock immediately), skip temporarily unlocked check - // and go straight to showing lock screen + // and clear unlock state immediately if (unlockDurationMinutes == 0) { - // Clear any unlock state for immediate locking AppLockManager.appUnlockTimes.remove(packageName) - AppLockManager.clearTemporarilyUnlockedApp() } else { // Return if app is temporarily unlocked (only when duration > 0) if (AppLockManager.isAppTemporarilyUnlocked(packageName)) { return } - - AppLockManager.clearTemporarilyUnlockedApp() } + + AppLockManager.clearTemporarilyUnlockedApp() val unlockTimestamp = AppLockManager.appUnlockTimes[packageName] ?: 0L From 9251f577390dcc84627c17206502c7d7b7b05121 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 01:27:26 +0000 Subject: [PATCH 4/6] Remove incorrect finish() call in onAuthenticationError The finish() call in onAuthenticationError would allow access to locked app without authentication. The correct fix for issue #138 is already in onPause() which cancels biometric prompt and finishes the activity when moving to background. Co-authored-by: PranavPurwar <75154889+PranavPurwar@users.noreply.github.com> --- .../features/lockscreen/ui/PasswordOverlayScreen.kt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt index 0b4c91d..c6825e9 100644 --- a/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt +++ b/app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt @@ -263,14 +263,6 @@ class PasswordOverlayActivity : FragmentActivity() { isBiometricPromptShowingLocal = false AppLockManager.reportBiometricAuthFinished() Log.w(TAG, "Authentication error: $errString ($errorCode)") - - // If user cancels authentication, finish the activity to remove the overlay - // This prevents the overlay from staying visible over other apps - if (errorCode == BiometricPrompt.ERROR_USER_CANCELED || - errorCode == BiometricPrompt.ERROR_CANCELED || - errorCode == BiometricPrompt.ERROR_NEGATIVE_BUTTON) { - finish() - } } override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { From eb8c5061d902e6630a4f51e4e113f6044671b610 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 01:32:13 +0000 Subject: [PATCH 5/6] Add exception handling to prevent service freezes (issue #129) - Wrap ExperimentalAppLockService timer task in try-catch to prevent freezes - Wrap ShizukuAppLockService callback in try-catch to handle exceptions gracefully - Ensures services continue running even if errors occur in monitoring loops Co-authored-by: PranavPurwar <75154889+PranavPurwar@users.noreply.github.com> --- .../services/ExperimentalAppLockService.kt | 34 +++++++++-------- .../applock/services/ShizukuAppLockService.kt | 38 ++++++++++--------- 2 files changed, 40 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/dev/pranav/applock/services/ExperimentalAppLockService.kt b/app/src/main/java/dev/pranav/applock/services/ExperimentalAppLockService.kt index 4a6be14..57249a4 100644 --- a/app/src/main/java/dev/pranav/applock/services/ExperimentalAppLockService.kt +++ b/app/src/main/java/dev/pranav/applock/services/ExperimentalAppLockService.kt @@ -82,27 +82,31 @@ class ExperimentalAppLockService : Service() { timer?.cancel() timer = Timer() timer?.schedule(timerTask { - if (!appLockRepository.isProtectEnabled() || applicationContext.isDeviceLocked()) { - if (applicationContext.isDeviceLocked()) { - AppLockManager.appUnlockTimes.clear() + try { + if (!appLockRepository.isProtectEnabled() || applicationContext.isDeviceLocked()) { + if (applicationContext.isDeviceLocked()) { + AppLockManager.appUnlockTimes.clear() + } + return@timerTask } - return@timerTask - } - val foregroundApp = getCurrentForegroundAppPackage() ?: return@timerTask - val currentPackage = foregroundApp.first - val triggeringPackage = previousForegroundPackage - previousForegroundPackage = currentPackage + val foregroundApp = getCurrentForegroundAppPackage() ?: return@timerTask + val currentPackage = foregroundApp.first + val triggeringPackage = previousForegroundPackage + previousForegroundPackage = currentPackage - if (isExclusionApp(currentPackage)) return@timerTask + if (isExclusionApp(currentPackage)) return@timerTask - if (triggeringPackage in appLockRepository.getTriggerExcludedApps()) { - return@timerTask - } + if (triggeringPackage in appLockRepository.getTriggerExcludedApps()) { + return@timerTask + } - if (currentPackage == triggeringPackage) return@timerTask + if (currentPackage == triggeringPackage) return@timerTask - checkAndLockApp(currentPackage, triggeringPackage, System.currentTimeMillis()) + checkAndLockApp(currentPackage, triggeringPackage, System.currentTimeMillis()) + } catch (e: Exception) { + Log.e(TAG, "Error in monitoring timer task", e) + } }, 0, 250) } diff --git a/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt b/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt index 5553805..f643197 100644 --- a/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt +++ b/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt @@ -161,24 +161,28 @@ class ShizukuAppLockService : Service() { private fun setupShizukuActivityManager() { shizukuActivityManager = ShizukuActivityManager(this, appLockRepository) { packageName, _, timeMillis -> - val triggeringPackage = previousForegroundPackage - previousForegroundPackage = packageName - - if (AppLockManager.isLockScreenShown.get() || packageName == this.packageName) { - return@ShizukuActivityManager - } - - val triggerExclusions = appLockRepository.getTriggerExcludedApps() - if (triggeringPackage in triggerExclusions) { - Log.d( - TAG, - "Trigger app $triggeringPackage is excluded, skipping lock for $packageName" - ) - return@ShizukuActivityManager + try { + val triggeringPackage = previousForegroundPackage + previousForegroundPackage = packageName + + if (AppLockManager.isLockScreenShown.get() || packageName == this.packageName) { + return@ShizukuActivityManager + } + + val triggerExclusions = appLockRepository.getTriggerExcludedApps() + if (triggeringPackage in triggerExclusions) { + Log.d( + TAG, + "Trigger app $triggeringPackage is excluded, skipping lock for $packageName" + ) + return@ShizukuActivityManager + } + + Log.d(TAG, "Current package=$packageName, trigger=$triggeringPackage") + checkAndLockApp(packageName, triggeringPackage, timeMillis) + } catch (e: Exception) { + Log.e(TAG, "Error in ShizukuActivityManager callback", e) } - - Log.d(TAG, "Current package=$packageName, trigger=$triggeringPackage") - checkAndLockApp(packageName, triggeringPackage, timeMillis) } } From 0ee2a5fe24a4a4cde96ab963bf484a2e004fc037 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 18 Jan 2026 01:34:09 +0000 Subject: [PATCH 6/6] Apply lock immediately fix to all services (issue #135) - Add lock immediately logic to ExperimentalAppLockService - Add lock immediately logic to ShizukuAppLockService - Ensures consistent behavior across all backend implementations Co-authored-by: PranavPurwar <75154889+PranavPurwar@users.noreply.github.com> --- .../pranav/applock/services/ExperimentalAppLockService.kt | 6 ++++++ .../dev/pranav/applock/services/ShizukuAppLockService.kt | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/src/main/java/dev/pranav/applock/services/ExperimentalAppLockService.kt b/app/src/main/java/dev/pranav/applock/services/ExperimentalAppLockService.kt index 57249a4..8ea664e 100644 --- a/app/src/main/java/dev/pranav/applock/services/ExperimentalAppLockService.kt +++ b/app/src/main/java/dev/pranav/applock/services/ExperimentalAppLockService.kt @@ -153,6 +153,12 @@ class ExperimentalAppLockService : Service() { if (packageName !in lockedApps) return val unlockDurationMinutes = appLockRepository.getUnlockTimeDuration() + + // If duration is 0 (lock immediately), clear any unlock state + if (unlockDurationMinutes == 0) { + AppLockManager.appUnlockTimes.remove(packageName) + } + val unlockTimestamp = AppLockManager.appUnlockTimes[packageName] ?: 0L Log.d( diff --git a/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt b/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt index f643197..1c301fb 100644 --- a/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt +++ b/app/src/main/java/dev/pranav/applock/services/ShizukuAppLockService.kt @@ -192,6 +192,12 @@ class ShizukuAppLockService : Service() { if (packageName !in lockedApps) return val unlockDurationMinutes = appLockRepository.getUnlockTimeDuration() + + // If duration is 0 (lock immediately), clear any unlock state + if (unlockDurationMinutes == 0) { + AppLockManager.appUnlockTimes.remove(packageName) + } + val unlockTimestamp = AppLockManager.appUnlockTimes[packageName] ?: 0L Log.d(