Skip to content

Commit 1ca7e5d

Browse files
committed
Add "until screen lock" unlock time option
1 parent fb70b8d commit 1ca7e5d

File tree

12 files changed

+126
-111
lines changed

12 files changed

+126
-111
lines changed

app/src/main/java/dev/pranav/applock/core/broadcast/BootReceiver.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ import dev.pranav.applock.services.ShizukuAppLockService
1212

1313
class BootReceiver : BroadcastReceiver() {
1414

15-
override fun onReceive(context: Context?, intent: Intent?) {
16-
if (context == null || intent?.action != Intent.ACTION_BOOT_COMPLETED) {
15+
override fun onReceive(context: Context, intent: Intent) {
16+
val repository = context.appLockRepository()
17+
if (intent.action == Intent.ACTION_PACKAGE_REPLACED) {
18+
repository.setShowDonateLink(true)
19+
}
20+
if (intent.action != Intent.ACTION_BOOT_COMPLETED) {
1721
Log.w(TAG, "Invalid context or intent action")
1822
return
1923
}

app/src/main/java/dev/pranav/applock/data/repository/AppLockRepository.kt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,11 @@ class AppLockRepository(private val context: Context) {
1313
private val lockedAppsRepository = LockedAppsRepository(context)
1414
private val backendServiceManager = BackendServiceManager(context)
1515

16-
// Delegate locked apps operations
1716
fun getLockedApps(): Set<String> = lockedAppsRepository.getLockedApps()
1817
fun addLockedApp(packageName: String) = lockedAppsRepository.addLockedApp(packageName)
1918
fun removeLockedApp(packageName: String) = lockedAppsRepository.removeLockedApp(packageName)
2019
fun isAppLocked(packageName: String): Boolean = lockedAppsRepository.isAppLocked(packageName)
2120

22-
// Delegate trigger exclusions operations
2321
fun getTriggerExcludedApps(): Set<String> = lockedAppsRepository.getTriggerExcludedApps()
2422
fun addTriggerExcludedApp(packageName: String) =
2523
lockedAppsRepository.addTriggerExcludedApp(packageName)
@@ -30,7 +28,6 @@ class AppLockRepository(private val context: Context) {
3028
fun isAppTriggerExcluded(packageName: String): Boolean =
3129
lockedAppsRepository.isAppTriggerExcluded(packageName)
3230

33-
// Delegate authentication operations
3431
fun getPassword(): String? = preferencesRepository.getPassword()
3532
fun setPassword(password: String) = preferencesRepository.setPassword(password)
3633
fun validatePassword(inputPassword: String): Boolean =
@@ -44,7 +41,6 @@ class AppLockRepository(private val context: Context) {
4441
fun setLockType(lockType: String) = preferencesRepository.setLockType(lockType)
4542
fun getLockType(): String = preferencesRepository.getLockType()
4643

47-
// Delegate biometric operations
4844
fun setBiometricAuthEnabled(enabled: Boolean) =
4945
preferencesRepository.setBiometricAuthEnabled(enabled)
5046

@@ -78,6 +74,7 @@ class AppLockRepository(private val context: Context) {
7874
fun isShowCommunityLink(): Boolean = preferencesRepository.isShowCommunityLink()
7975
fun setCommunityLinkShown(shown: Boolean) = preferencesRepository.setCommunityLinkShown(shown)
8076
fun isShowDonateLink(): Boolean = preferencesRepository.isShowDonateLink(context)
77+
fun setShowDonateLink(show: Boolean) = preferencesRepository.setShowDonateLink(context, show)
8178

8279
fun setActiveBackend(backend: BackendImplementation) =
8380
backendServiceManager.setActiveBackend(backend)

app/src/main/java/dev/pranav/applock/data/repository/PreferencesRepository.kt

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -139,19 +139,11 @@ class PreferencesRepository(context: Context) {
139139
}
140140

141141
fun isShowDonateLink(context: Context): Boolean {
142-
val currentVersionCode = try {
143-
context.packageManager.getPackageInfo(context.packageName, 0).versionCode
144-
} catch (_: Exception) {
145-
return false
146-
}
147-
148-
val savedVersionCode = settingsPrefs.getInt(LAST_VERSION_CODE, -1)
142+
return settingsPrefs.getBoolean(KEY_SHOW_DONATE_LINK, false)
143+
}
149144

150-
if (currentVersionCode > savedVersionCode) {
151-
settingsPrefs.edit { putInt(LAST_VERSION_CODE, currentVersionCode) }
152-
return true
153-
}
154-
return false
145+
fun setShowDonateLink(context: Context, show: Boolean) {
146+
settingsPrefs.edit { putBoolean(KEY_SHOW_DONATE_LINK, show) }
155147
}
156148

157149
companion object {
@@ -167,6 +159,7 @@ class PreferencesRepository(context: Context) {
167159
private const val KEY_UNLOCK_TIME_DURATION = "unlock_time_duration"
168160
private const val KEY_BACKEND_IMPLEMENTATION = "backend_implementation"
169161
private const val KEY_COMMUNITY_LINK_SHOWN = "community_link_shown"
162+
private const val KEY_SHOW_DONATE_LINK = "show_donate_link"
170163
private const val LAST_VERSION_CODE = "last_version_code"
171164
private const val KEY_APPLOCK_ENABLED = "applock_enabled"
172165
private const val KEY_AUTO_UNLOCK = "auto_unlock"

app/src/main/java/dev/pranav/applock/features/applist/ui/MainScreen.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,14 @@ fun MainScreen(
318318
)
319319
)
320320
showDonateDialog = false
321+
appLockRepository.setShowDonateLink(false)
321322
}
322323
) { Text(stringResource(R.string.main_screen_support_development_donate_button)) }
323324
},
324325
dismissButton = {
325326
TextButton(onClick = {
326327
showDonateDialog = false
328+
appLockRepository.setShowDonateLink(false)
327329
}) { Text(stringResource(R.string.cancel_button)) }
328330
},
329331
containerColor = MaterialTheme.colorScheme.surfaceContainer

app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PasswordOverlayScreen.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,10 @@ import androidx.compose.foundation.layout.Box
2525
import androidx.compose.foundation.layout.Column
2626
import androidx.compose.foundation.layout.Row
2727
import androidx.compose.foundation.layout.Spacer
28-
import androidx.compose.foundation.layout.WindowInsets
29-
import androidx.compose.foundation.layout.asPaddingValues
3028
import androidx.compose.foundation.layout.fillMaxSize
3129
import androidx.compose.foundation.layout.fillMaxWidth
3230
import androidx.compose.foundation.layout.height
33-
import androidx.compose.foundation.layout.navigationBars
31+
import androidx.compose.foundation.layout.navigationBarsPadding
3432
import androidx.compose.foundation.layout.padding
3533
import androidx.compose.foundation.layout.size
3634
import androidx.compose.foundation.layout.width
@@ -743,12 +741,12 @@ fun KeypadSection(
743741
horizontalAlignment = Alignment.CenterHorizontally,
744742
modifier = if (isLandscape) {
745743
Modifier
746-
.padding(WindowInsets.navigationBars.asPaddingValues())
744+
.navigationBarsPadding()
747745
.padding(bottom = 12.dp)
748746
} else {
749747
Modifier
750748
.padding(horizontal = horizontalPadding)
751-
.padding(WindowInsets.navigationBars.asPaddingValues())
749+
.navigationBarsPadding()
752750
}
753751
) {
754752
if (showBiometricButton) {

app/src/main/java/dev/pranav/applock/features/lockscreen/ui/PatternLockScreen.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@ import androidx.compose.foundation.layout.Arrangement
88
import androidx.compose.foundation.layout.Column
99
import androidx.compose.foundation.layout.Row
1010
import androidx.compose.foundation.layout.Spacer
11-
import androidx.compose.foundation.layout.WindowInsets
12-
import androidx.compose.foundation.layout.asPaddingValues
1311
import androidx.compose.foundation.layout.aspectRatio
1412
import androidx.compose.foundation.layout.fillMaxSize
1513
import androidx.compose.foundation.layout.fillMaxWidth
1614
import androidx.compose.foundation.layout.height
1715
import androidx.compose.foundation.layout.padding
1816
import androidx.compose.foundation.layout.size
19-
import androidx.compose.foundation.layout.statusBars
17+
import androidx.compose.foundation.layout.statusBarsPadding
2018
import androidx.compose.foundation.shape.RoundedCornerShape
2119
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
2220
import androidx.compose.material3.FilledTonalIconButton
@@ -111,7 +109,7 @@ fun PatternLockScreen(
111109
Row(
112110
modifier = Modifier
113111
.fillMaxSize()
114-
.padding(WindowInsets.statusBars.asPaddingValues())
112+
.statusBarsPadding()
115113
.padding(horizontal = 24.dp, vertical = 16.dp),
116114
horizontalArrangement = Arrangement.spacedBy(24.dp),
117115
verticalAlignment = Alignment.CenterVertically
@@ -194,7 +192,7 @@ fun PatternLockScreen(
194192
Column(
195193
modifier = Modifier
196194
.fillMaxSize()
197-
.padding(WindowInsets.statusBars.asPaddingValues())
195+
.statusBarsPadding()
198196
.padding(vertical = 24.dp, horizontal = 16.dp),
199197
horizontalAlignment = Alignment.CenterHorizontally,
200198
verticalArrangement = Arrangement.SpaceBetween

app/src/main/java/dev/pranav/applock/features/settings/ui/SettingsScreen.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -354,10 +354,12 @@ fun SettingsScreen(
354354
ActionSettingItem(
355355
icon = Timer,
356356
title = stringResource(R.string.settings_screen_unlock_duration_title),
357-
description = if (unlockTimeDuration > 0) stringResource(
358-
R.string.settings_screen_unlock_duration_summary_minutes,
359-
unlockTimeDuration
360-
) else stringResource(R.string.settings_screen_unlock_duration_summary_immediate),
357+
description = if (unlockTimeDuration > 0) {
358+
if (unlockTimeDuration > 10_000) "Until screen off" else stringResource(
359+
R.string.settings_screen_unlock_duration_summary_minutes,
360+
unlockTimeDuration
361+
)
362+
} else stringResource(R.string.settings_screen_unlock_duration_summary_immediate),
361363
onClick = { showUnlockTimeDialog = true }
362364
)
363365
HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp))
@@ -562,7 +564,7 @@ fun UnlockTimeDurationDialog(
562564
onDismiss: () -> Unit,
563565
onConfirm: (Int) -> Unit
564566
) {
565-
val durations = listOf(0, 1, 5, 15, 30, 60)
567+
val durations = listOf(0, 1, 5, 15, 30, 60, Integer.MAX_VALUE)
566568
var selectedDuration by remember { mutableIntStateOf(currentDuration) }
567569

568570
// If the current duration is not in our list, default to the closest value
@@ -598,6 +600,7 @@ fun UnlockTimeDurationDialog(
598600
)
599601

600602
60 -> stringResource(R.string.settings_screen_unlock_duration_dialog_option_hour)
603+
Integer.MAX_VALUE -> "Until Screen Off"
601604
else -> stringResource(
602605
R.string.settings_screen_unlock_duration_summary_minutes,
603606
duration

app/src/main/java/dev/pranav/applock/services/AppLockAccessibilityService.kt

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import dev.pranav.applock.features.lockscreen.ui.PasswordOverlayActivity
2323
import dev.pranav.applock.services.AppLockConstants.ACCESSIBILITY_SETTINGS_CLASSES
2424
import dev.pranav.applock.services.AppLockConstants.ADMIN_CONFIG_CLASSES
2525
import dev.pranav.applock.services.AppLockConstants.EXCLUDED_APPS
26-
import dev.pranav.applock.services.AppLockConstants.KNOWN_RECENTS_CLASSES
2726
import rikka.shizuku.Shizuku
2827

2928
@SuppressLint("AccessibilityPolicy")
@@ -89,7 +88,7 @@ class AppLockAccessibilityService : AccessibilityService() {
8988

9089
private fun handleAccessibilityEvent(event: AccessibilityEvent) {
9190
// Check for device admin deactivation (anti-uninstall feature)
92-
if (appLockRepository.isAntiUninstallEnabled() &&
91+
if (appLockRepository.isAntiUninstallEnabled() &&
9392
event.packageName == DEVICE_ADMIN_SETTINGS_PACKAGE) {
9493
try {
9594
checkForDeviceAdminDeactivation(event)
@@ -165,7 +164,7 @@ class AppLockAccessibilityService : AccessibilityService() {
165164
}
166165

167166
private fun isRecentlyOpened(event: AccessibilityEvent): Boolean {
168-
return (event.packageName == getSystemDefaultLauncherPackageName() &&
167+
return (event.packageName == getSystemDefaultLauncherPackageName() &&
169168
event.contentChangeTypes == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED) ||
170169
(event.text.toString().lowercase().contains("recent apps"))
171170
}
@@ -186,8 +185,8 @@ class AppLockAccessibilityService : AccessibilityService() {
186185
}
187186

188187
private fun clearTemporarilyUnlockedAppIfNeeded(newPackage: String? = null) {
189-
val shouldClear = newPackage == null ||
190-
(newPackage != AppLockManager.temporarilyUnlockedApp &&
188+
val shouldClear = newPackage == null ||
189+
(newPackage != AppLockManager.temporarilyUnlockedApp &&
191190
newPackage !in appLockRepository.getTriggerExcludedApps())
192191

193192
if (shouldClear) {
@@ -210,8 +209,8 @@ class AppLockAccessibilityService : AccessibilityService() {
210209
}
211210

212211
// Skip excluded packages
213-
if (packageName == APP_PACKAGE_PREFIX ||
214-
packageName in keyboardPackages ||
212+
if (packageName == APP_PACKAGE_PREFIX ||
213+
packageName in keyboardPackages ||
215214
packageName in EXCLUDED_APPS) {
216215
return false
217216
}
@@ -265,29 +264,45 @@ class AppLockAccessibilityService : AccessibilityService() {
265264

266265
AppLockManager.clearTemporarilyUnlockedApp()
267266

268-
// Check if unlock duration is still valid
269-
if (isUnlockStillValid(packageName, currentTime)) {
270-
AppLockManager.unlockApp(packageName)
271-
return
272-
}
273-
274-
// Show lock screen overlay
275-
showLockScreenOverlay(packageName, triggeringPackage)
276-
}
277-
278-
private fun isUnlockStillValid(packageName: String, currentTime: Long): Boolean {
279267
val unlockDurationMinutes = appLockRepository.getUnlockTimeDuration()
280268
val unlockTimestamp = AppLockManager.appUnlockTimes[packageName] ?: 0L
281269

270+
Log.d(
271+
TAG,
272+
"checkAndLockApp: pkg=$packageName, duration=$unlockDurationMinutes min, unlockTime=$unlockTimestamp, currentTime=$currentTime, isLockScreenShown=${AppLockManager.isLockScreenShown.get()}"
273+
)
274+
282275
if (unlockDurationMinutes > 0 && unlockTimestamp > 0) {
283-
val durationMillis = unlockDurationMinutes * 60 * 1000L
284-
if (currentTime - unlockTimestamp < durationMillis) {
285-
return true
276+
if (unlockDurationMinutes >= 10_000) {
277+
return
286278
}
279+
280+
val durationMillis = unlockDurationMinutes.toLong() * 60L * 1000L
281+
282+
val elapsedMillis = currentTime - unlockTimestamp
283+
284+
Log.d(
285+
TAG,
286+
"Grace period check: elapsed=${elapsedMillis}ms (${elapsedMillis / 1000}s), duration=${durationMillis}ms (${durationMillis / 1000}s)"
287+
)
288+
289+
if (elapsedMillis < durationMillis) {
290+
return
291+
}
292+
293+
Log.d(TAG, "Unlock grace period expired for $packageName. Clearing timestamp.")
287294
AppLockManager.appUnlockTimes.remove(packageName)
288295
AppLockManager.clearTemporarilyUnlockedApp()
289296
}
290-
return false
297+
298+
if (AppLockManager.isLockScreenShown.get() ||
299+
AppLockManager.currentBiometricState == BiometricState.AUTH_STARTED
300+
) {
301+
Log.d(TAG, "Lock screen already shown or biometric auth in progress, skipping")
302+
return
303+
}
304+
305+
showLockScreenOverlay(packageName, triggeringPackage)
291306
}
292307

293308
private fun showLockScreenOverlay(packageName: String, triggeringPackage: String) {
@@ -337,11 +352,11 @@ class AppLockAccessibilityService : AccessibilityService() {
337352
}
338353

339354
private fun isDeactivationAttempt(event: AccessibilityEvent): Boolean {
340-
val isAccessibilitySettings = event.className in ACCESSIBILITY_SETTINGS_CLASSES &&
355+
val isAccessibilitySettings = event.className in ACCESSIBILITY_SETTINGS_CLASSES &&
341356
event.text.any { it.contains("App Lock") }
342-
val isSubSettings = event.className == "com.android.settings.SubSettings" &&
357+
val isSubSettings = event.className == "com.android.settings.SubSettings" &&
343358
event.text.any { it.contains("App Lock") }
344-
val isAlertDialog = event.className == "android.app.AlertDialog" &&
359+
val isAlertDialog = event.className == "android.app.AlertDialog" &&
345360
event.text.isNotEmpty() && event.text.first().contains("App Lock")
346361

347362
return isAccessibilitySettings || isSubSettings || isAlertDialog
@@ -432,7 +447,7 @@ class AppLockAccessibilityService : AccessibilityService() {
432447
)
433448

434449
val systemLauncher = resolveInfoList.find { resolveInfo ->
435-
val isSystemApp = (resolveInfo.activityInfo.applicationInfo.flags and
450+
val isSystemApp = (resolveInfo.activityInfo.applicationInfo.flags and
436451
android.content.pm.ApplicationInfo.FLAG_SYSTEM) != 0
437452
val isOurApp = resolveInfo.activityInfo.packageName == packageName
438453

app/src/main/java/dev/pranav/applock/services/AppLockManager.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ object AppLockManager {
7878
fun unlockApp(packageName: String) {
7979
temporarilyUnlockedApp = packageName
8080
appUnlockTimes[packageName] = System.currentTimeMillis()
81-
Log.d(TAG, "App $packageName temporarily unlocked.")
81+
Log.d(
82+
TAG,
83+
"App $packageName unlocked at timestamp: ${appUnlockTimes[packageName]}, current time: ${System.currentTimeMillis()}"
84+
)
8285
}
8386

8487
fun temporarilyUnlockAppWithBiometrics(packageName: String) {

0 commit comments

Comments
 (0)