Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ android {
minSdk = 26
targetSdk = 36
// targetSdkPreview = "CANARY"
versionCode = 223
versionName = "2.2.3"
versionCode = 22304
versionName = "3.1.0.00(18-03-26)"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down
22 changes: 17 additions & 5 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.HIDE_OVERLAY_WINDOWS" />
<uses-permission android:name="android.permission.CAMERA" />

<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.front" android:required="false" />

<uses-permission
android:name="android.permission.PACKAGE_USAGE_STATS"
Expand All @@ -27,7 +31,8 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />

<uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER" />
<uses-permission android:name="android.permission.SET_ACTIVITY_WATCHER"
tools:ignore="ProtectedPermissions" />

<application
android:name=".AppLockApplication"
Expand All @@ -46,7 +51,9 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.AppLock">
android:label="@string/app_name"
android:theme="@style/Theme.AppLock"
tools:ignore="RedundantLabel">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
Expand All @@ -58,13 +65,17 @@
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:excludeFromRecents="true"
android:exported="false"
android:label="@string/app_name"
android:taskAffinity=""
android:theme="@android:style/Theme.Material.NoActionBar.TranslucentDecor" />
android:theme="@android:style/Theme.Material.NoActionBar.TranslucentDecor"
tools:ignore="RedundantLabel" />

<activity
android:name=".features.admin.AdminDisableActivity"
android:exported="false"
android:theme="@style/Theme.AppLock" />
android:label="@string/app_name"
android:theme="@style/Theme.AppLock"
tools:ignore="RedundantLabel" />

<service
android:name=".services.ShizukuAppLockService"
Expand All @@ -85,7 +96,7 @@
<service
android:name=".services.AppLockAccessibilityService"
android:exported="false"
android:foregroundServiceType="specialUse"
android:foregroundServiceType="specialUse|systemExempted"
android:label="@string/app_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
tools:ignore="AccessibilityPolicy">
Expand All @@ -104,6 +115,7 @@
android:name=".core.broadcast.DeviceAdmin"
android:description="@string/device_admin_description"
android:exported="false"
android:label="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
Expand Down
Binary file modified app/src/main/ic_launcher-playstore.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 23 additions & 9 deletions app/src/main/java/dev/pranav/applock/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ package dev.pranav.applock
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.Text
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.fragment.app.FragmentActivity
import dev.pranav.applock.core.utils.BiometricStatus
import dev.pranav.applock.core.utils.getBiometricStatus
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.navigation.compose.rememberNavController
Expand All @@ -22,18 +29,25 @@ class MainActivity : FragmentActivity() {

navigationManager = NavigationManager(this)

val biometricStatus = getBiometricStatus(this)
setContent {
AppLockTheme {
val navController = rememberNavController()
val startDestination = navigationManager.determineStartDestination()
if (biometricStatus is BiometricStatus.Unavailable) {
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(biometricStatus.message)
}
} else {
val navController = rememberNavController()
val startDestination = navigationManager.determineStartDestination()

AppNavHost(
navController = navController,
startDestination = startDestination
)
AppNavHost(
navController = navController,
startDestination = startDestination
)

LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
handleOnResume(navController)
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
handleOnResume(navController)
}
}
}
}
Expand All @@ -46,7 +60,7 @@ class MainActivity : FragmentActivity() {
return
}

if (currentRoute != Screen.PasswordOverlay.route && currentRoute != Screen.SetPassword.route && currentRoute != Screen.SetPasswordPattern.route) {
if (currentRoute != Screen.PasswordOverlay.route && currentRoute != Screen.SetPassword.route && currentRoute != Screen.SetPasswordPattern.route && currentRoute != Screen.SetPasswordText.route) {
navController.navigate(Screen.PasswordOverlay.route)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ class DeviceAdmin : DeviceAdminReceiver() {
context.getSharedPreferences("app_lock_settings", Context.MODE_PRIVATE).edit {
putBoolean("anti_uninstall", true)
}

val component = ComponentName(context, DeviceAdmin::class.java)

getManager(context).setUninstallBlocked(component, context.packageName, true)
}

override fun onDisabled(context: Context, intent: android.content.Intent) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,25 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import dev.pranav.applock.AppLockApplication
import dev.pranav.applock.core.utils.IntruderSelfieManager
import dev.pranav.applock.core.utils.LogUtils
import dev.pranav.applock.data.repository.PreferencesRepository
import dev.pranav.applock.features.antiuninstall.ui.AntiUninstallScreen
import dev.pranav.applock.features.appintro.ui.AppIntroScreen
import dev.pranav.applock.features.applist.ui.MainScreen
import dev.pranav.applock.features.intruderselfie.ui.IntruderSelfieScreen
import dev.pranav.applock.features.lockscreen.ui.PasswordOverlayScreen
import dev.pranav.applock.features.lockscreen.ui.PatternLockScreen
import dev.pranav.applock.features.setpassword.ui.PatternSetPasswordScreen
import dev.pranav.applock.features.setpassword.ui.SetPasswordScreen
import dev.pranav.applock.features.setpassword.ui.SetPasswordTextScreen
import dev.pranav.applock.features.settings.ui.SettingsScreen
import dev.pranav.applock.features.triggerexclusions.ui.TriggerExclusionsScreen

@Composable
fun AppNavHost(navController: NavHostController, startDestination: String) {
val application = LocalContext.current.applicationContext as AppLockApplication
val context = LocalContext.current

NavHost(
navController = navController,
Expand All @@ -52,23 +56,39 @@ fun AppNavHost(navController: NavHostController, startDestination: String) {
}

composable(Screen.ChangePassword.route) {
if (application.appLockRepository.getLockType() == PreferencesRepository.LOCK_TYPE_PATTERN) {
PatternSetPasswordScreen(navController, false)
} else {
SetPasswordScreen(navController, isFirstTimeSetup = false)
when (application.appLockRepository.getLockType()) {
PreferencesRepository.LOCK_TYPE_PATTERN -> PatternSetPasswordScreen(navController, false)
PreferencesRepository.LOCK_TYPE_PASSWORD -> SetPasswordTextScreen(navController, false)
else -> SetPasswordScreen(navController, isFirstTimeSetup = false)
}
}

composable(Screen.ChangePasswordPin.route) {
SetPasswordScreen(navController, isFirstTimeSetup = false)
}

composable(Screen.ChangePasswordPattern.route) {
PatternSetPasswordScreen(navController, false)
}

composable(Screen.ChangePasswordText.route) {
SetPasswordTextScreen(navController, false)
}

composable(Screen.SetPasswordPattern.route) {
PatternSetPasswordScreen(navController, isFirstTimeSetup = true)
}

composable(Screen.SetPasswordText.route) {
SetPasswordTextScreen(navController, isFirstTimeSetup = true)
}

composable(Screen.Main.route) {
MainScreen(navController)
}

composable(Screen.PasswordOverlay.route) {
val context = LocalActivity.current as FragmentActivity
val activity = LocalActivity.current as FragmentActivity
val lockType = application.appLockRepository.getLockType()

when (lockType) {
Expand All @@ -78,12 +98,47 @@ fun AppNavHost(navController: NavHostController, startDestination: String) {
onPatternAttempt = { pattern ->
val isValid = application.appLockRepository.validatePattern(pattern)
if (isValid) {
IntruderSelfieManager.resetFailedAttempts()
handleAuthenticationSuccess(navController)
} else {
IntruderSelfieManager.recordFailedAttempt(context)
}
isValid
},
onBiometricAuth = {
handleBiometricAuthentication(context, navController)
handleBiometricAuthentication(activity, navController)
},
onForgotPasscodeReset = {
navController.navigate(Screen.ChangePasswordPattern.route)
}
)
}

PreferencesRepository.LOCK_TYPE_PASSWORD -> {
PasswordOverlayScreen(
showBiometricButton = application.appLockRepository.isBiometricAuthEnabled(),
fromMainActivity = true,
useTextPassword = true,
onBiometricAuth = {
handleBiometricAuthentication(activity, navController)
},
onAuthSuccess = {
IntruderSelfieManager.resetFailedAttempts()
handleAuthenticationSuccess(navController)
},
onForgotPasscodeReset = {
navController.navigate(Screen.ChangePasswordText.route)
},
onPinAttempt = { pin, isFinal ->
val correctPassword = application.appLockRepository.getPassword() ?: ""
val isValid = pin == correctPassword
if (isValid) {
IntruderSelfieManager.resetFailedAttempts()
handleAuthenticationSuccess(navController)
} else if (isFinal) {
IntruderSelfieManager.recordFailedAttempt(context)
}
isValid
}
)
}
Expand All @@ -93,10 +148,30 @@ fun AppNavHost(navController: NavHostController, startDestination: String) {
showBiometricButton = application.appLockRepository.isBiometricAuthEnabled(),
fromMainActivity = true,
onBiometricAuth = {
handleBiometricAuthentication(context, navController)
handleBiometricAuthentication(activity, navController)
},
onAuthSuccess = {
IntruderSelfieManager.resetFailedAttempts()
handleAuthenticationSuccess(navController)
},
onForgotPasscodeReset = {
navController.navigate(Screen.ChangePasswordPin.route)
},
onPinAttempt = { pin, isFinal ->
val correctPassword = application.appLockRepository.getPassword() ?: ""
val isValid = pin == correctPassword
if (isValid) {
IntruderSelfieManager.resetFailedAttempts()
handleAuthenticationSuccess(navController)
} else {
// Proper failed attempt count for PIN:
// 1. If it's a "Proceed" button click (isFinal = true), always record.
// 2. If it's an auto-unlock check, only record if length matches.
if (isFinal || (pin.length >= correctPassword.length && correctPassword.isNotEmpty())) {
IntruderSelfieManager.recordFailedAttempt(context)
}
}
isValid
}
)
}
Expand All @@ -114,6 +189,10 @@ fun AppNavHost(navController: NavHostController, startDestination: String) {
composable(Screen.AntiUninstall.route) {
AntiUninstallScreen(navController)
}

composable(Screen.IntruderSelfies.route) {
IntruderSelfieScreen(navController)
}
}
}

Expand All @@ -135,6 +214,7 @@ private fun handleBiometricAuthentication(
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
LogUtils.d(TAG, "Biometric authentication succeeded")
IntruderSelfieManager.resetFailedAttempts()
navigateToMain(navController)
}

Expand Down Expand Up @@ -182,6 +262,6 @@ private fun navigateToMain(navController: NavHostController) {
private const val TAG = "AppNavHost"
private const val ANIMATION_DURATION = 400
private const val SCALE_INITIAL = 0.9f
private const val BIOMETRIC_TITLE = "Confirm password"
private const val BIOMETRIC_TITLE = "APP Lock by AP"
private const val BIOMETRIC_SUBTITLE = "Confirm biometric to continue"
private const val BIOMETRIC_NEGATIVE_BUTTON = "Use PIN"
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ sealed class Screen(val route: String) {
object AppIntro : Screen("app_intro")
object SetPassword : Screen("set_password")
object SetPasswordPattern : Screen("set_password_pattern")
object SetPasswordText : Screen("set_password_text")
object ChangePassword : Screen("change_password")
object ChangePasswordPin : Screen("change_password_pin")
object ChangePasswordPattern : Screen("change_password_pattern")
object ChangePasswordText : Screen("change_password_text")
object Main : Screen("main")
object PasswordOverlay : Screen("password_overlay")
object Settings : Screen("settings")
object TriggerExclusions : Screen("trigger_exclusions")
object AntiUninstall: Screen("anti_uninstall")
object IntruderSelfies: Screen("intruder_selfies")
}
27 changes: 27 additions & 0 deletions app/src/main/java/dev/pranav/applock/core/utils/BiometricUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dev.pranav.applock.core.utils

import android.content.Context
import androidx.biometric.BiometricManager

sealed class BiometricStatus {
data object Available : BiometricStatus()
data class Unavailable(val message: String) : BiometricStatus()
}

fun getBiometricStatus(context: Context): BiometricStatus {
val biometricManager = BiometricManager.from(context)
return when (
biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG)
) {
BiometricManager.BIOMETRIC_SUCCESS -> BiometricStatus.Available
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> BiometricStatus.Unavailable(
"Sorry this app cant run on this device because of security limitation: no biometric hardware."
)
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> BiometricStatus.Unavailable(
"Sorry this app cant run on this device because of security limitation: biometric not enabled."
)
else -> BiometricStatus.Unavailable(
"Sorry this app cant run on this device because of security limitation: biometric authentication unavailable."
)
}
}
Loading
Loading