Skip to content

Commit 85dba16

Browse files
committed
Fix Shizuku bypass issue and freeform, split window bypass
1 parent caaf064 commit 85dba16

26 files changed

Lines changed: 2901 additions & 508 deletions

.idea/modules.xml

Lines changed: 0 additions & 8 deletions
This file was deleted.

app/src/main/AndroidManifest.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115

116116
<receiver
117117
android:name=".core.broadcast.BootReceiver"
118+
android:directBootAware="true"
118119
android:enabled="true"
119120
android:exported="true">
120121
<intent-filter>
@@ -124,8 +125,13 @@
124125
<intent-filter>
125126
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
126127
</intent-filter>
128+
<intent-filter>
129+
<action android:name="android.intent.action.USER_UNLOCKED" />
130+
<action android:name="android.intent.action.USER_PRESENT" />
131+
</intent-filter>
127132
</receiver>
128133

134+
129135
<provider
130136
android:name="androidx.core.content.FileProvider"
131137
android:authorities="${applicationId}.fileprovider"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package dev.pranav.applock.core.broadcast
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import android.os.Build
6+
import android.util.Log
7+
import androidx.core.content.ContextCompat
8+
import dev.pranav.applock.data.repository.BackendImplementation
9+
import dev.pranav.applock.services.AppLockAccessibilityService
10+
import dev.pranav.applock.services.ExperimentalAppLockService
11+
import dev.pranav.applock.services.ShizukuAppLockService
12+
import dev.pranav.applock.services.isServiceRunning
13+
14+
object AppLockServiceStarter {
15+
private const val TAG = "AppLockServiceStarter"
16+
17+
fun startAppropriateServices(
18+
context: Context,
19+
repository: dev.pranav.applock.data.repository.AppLockRepository
20+
) {
21+
if (repository.isAntiUninstallEnabled()) {
22+
startService(context, AppLockAccessibilityService::class.java)
23+
}
24+
25+
when (repository.getBackendImplementation()) {
26+
BackendImplementation.SHIZUKU -> {
27+
startService(context, ShizukuAppLockService::class.java)
28+
}
29+
30+
BackendImplementation.ACCESSIBILITY -> {
31+
startService(context, AppLockAccessibilityService::class.java)
32+
}
33+
34+
BackendImplementation.USAGE_STATS -> {
35+
startService(context, ExperimentalAppLockService::class.java)
36+
}
37+
}
38+
}
39+
40+
private fun startService(context: Context, serviceClass: Class<*>) {
41+
try {
42+
if (context.isServiceRunning(serviceClass)) {
43+
Log.d(TAG, "Service already running: ${serviceClass.simpleName}")
44+
return
45+
}
46+
47+
val serviceIntent = Intent(context, serviceClass)
48+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
49+
serviceClass != AppLockAccessibilityService::class.java
50+
) {
51+
ContextCompat.startForegroundService(context, serviceIntent)
52+
} else {
53+
context.startService(serviceIntent)
54+
}
55+
Log.d(TAG, "Started service: ${serviceClass.simpleName}")
56+
} catch (e: Exception) {
57+
Log.e(TAG, "Failed to start service: ${serviceClass.simpleName}", e)
58+
}
59+
}
60+
61+
}

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

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ package dev.pranav.applock.core.broadcast
33
import android.content.BroadcastReceiver
44
import android.content.Context
55
import android.content.Intent
6+
import android.os.Build
67
import android.util.Log
8+
import androidx.core.content.ContextCompat
79
import dev.pranav.applock.core.utils.LogUtils
810
import dev.pranav.applock.core.utils.appLockRepository
911
import dev.pranav.applock.data.repository.BackendImplementation
1012
import dev.pranav.applock.services.AppLockAccessibilityService
1113
import dev.pranav.applock.services.ExperimentalAppLockService
1214
import dev.pranav.applock.services.ShizukuAppLockService
15+
import dev.pranav.applock.services.isServiceRunning
1316

1417
class BootReceiver : BroadcastReceiver() {
1518

@@ -22,12 +25,20 @@ class BootReceiver : BroadcastReceiver() {
2225
repository.setShowDonateLink(true)
2326
// Clear all old logs on app update
2427
LogUtils.clearAllLogs()
28+
try {
29+
AppLockServiceStarter.startAppropriateServices(context, repository)
30+
} catch (e: Exception) {
31+
Log.e(TAG, "Error starting services on package replace", e)
32+
}
2533
}
26-
Intent.ACTION_BOOT_COMPLETED -> {
34+
35+
Intent.ACTION_BOOT_COMPLETED,
36+
Intent.ACTION_USER_UNLOCKED,
37+
Intent.ACTION_USER_PRESENT -> {
2738
try {
28-
startAppropriateServices(context, repository)
39+
AppLockServiceStarter.startAppropriateServices(context, repository)
2940
} catch (e: Exception) {
30-
Log.e(TAG, "Error starting services on boot", e)
41+
Log.e(TAG, "Error starting services on boot or unlock", e)
3142
}
3243
}
3344
else -> {
@@ -61,8 +72,19 @@ class BootReceiver : BroadcastReceiver() {
6172

6273
private fun startService(context: Context, serviceClass: Class<*>) {
6374
try {
75+
if (context.isServiceRunning(serviceClass)) {
76+
Log.d(TAG, "Service already running: ${serviceClass.simpleName}")
77+
return
78+
}
79+
6480
val serviceIntent = Intent(context, serviceClass)
65-
context.startService(serviceIntent)
81+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
82+
serviceClass != AppLockAccessibilityService::class.java
83+
) {
84+
ContextCompat.startForegroundService(context, serviceIntent)
85+
} else {
86+
context.startService(serviceIntent)
87+
}
6688
Log.d(TAG, "Started service: ${serviceClass.simpleName}")
6789
} catch (e: Exception) {
6890
Log.e(TAG, "Failed to start service: ${serviceClass.simpleName}", e)

app/src/main/java/dev/pranav/applock/core/utils/LogUtils.kt

Lines changed: 45 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@ import android.net.Uri
66
import android.os.Build
77
import android.util.Log
88
import androidx.core.content.FileProvider
9+
import kotlinx.coroutines.CoroutineScope
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.SupervisorJob
12+
import kotlinx.coroutines.launch
913
import java.io.File
1014
import java.time.Instant
1115
import java.time.temporal.ChronoUnit
12-
import kotlin.concurrent.thread
1316

1417
@SuppressLint("StaticFieldLeak")
1518
object LogUtils {
19+
private val logScope: CoroutineScope by lazy {
20+
CoroutineScope(SupervisorJob() + Dispatchers.IO)
21+
}
1622
private const val TAG = "LogUtils"
1723
private const val FILE_NAME = "app_logs.txt"
1824
private const val SECURITY_LOGS = "audit_log.txt"
@@ -30,15 +36,31 @@ object LogUtils {
3036
fun d(tag: String, message: String) {
3137
if (!loggingEnabled) return
3238

33-
val file = File(context.filesDir, SECURITY_LOGS)
39+
val line = "${Instant.now()} D $tag: $message\n"
40+
Log.d(tag, message)
41+
writeAuditLogLine(line)
42+
}
3443

35-
if (!file.exists()) {
36-
file.createNewFile()
37-
}
44+
fun e(tag: String, message: String, e: Throwable? = null) {
45+
if (!loggingEnabled) return
3846

39-
file.appendText(Instant.now().toString() + " D " + tag + ": " + message + "\n")
47+
val line = "${Instant.now()} E $tag: $message\n${Log.getStackTraceString(e)}\n"
48+
Log.e(tag, message)
49+
writeAuditLogLine(line)
50+
}
4051

41-
Log.d(tag, message)
52+
private fun writeAuditLogLine(line: String) {
53+
logScope.launch {
54+
try {
55+
val file = File(context.filesDir, SECURITY_LOGS)
56+
if (!file.exists()) {
57+
file.createNewFile()
58+
}
59+
file.appendText(line)
60+
} catch (e: Exception) {
61+
Log.e(TAG, "Error writing audit log", e)
62+
}
63+
}
4264
}
4365

4466
fun exportAuditLogs(): Uri? {
@@ -93,20 +115,22 @@ object LogUtils {
93115
* Called when the app is updated.
94116
*/
95117
fun clearAllLogs() {
96-
try {
97-
val securityLogFile = File(context.filesDir, SECURITY_LOGS)
98-
if (securityLogFile.exists()) {
99-
securityLogFile.delete()
100-
Log.d(TAG, "Cleared security logs")
101-
}
102-
103-
val appLogFile = File(context.cacheDir, FILE_NAME)
104-
if (appLogFile.exists()) {
105-
appLogFile.delete()
106-
Log.d(TAG, "Cleared app logs")
118+
logScope.launch {
119+
try {
120+
val securityLogFile = File(context.filesDir, SECURITY_LOGS)
121+
if (securityLogFile.exists()) {
122+
securityLogFile.delete()
123+
Log.d(TAG, "Cleared security logs")
124+
}
125+
126+
val appLogFile = File(context.cacheDir, FILE_NAME)
127+
if (appLogFile.exists()) {
128+
appLogFile.delete()
129+
Log.d(TAG, "Cleared app logs")
130+
}
131+
} catch (e: Exception) {
132+
Log.e(TAG, "Error clearing logs", e)
107133
}
108-
} catch (e: Exception) {
109-
Log.e(TAG, "Error clearing logs", e)
110134
}
111135
}
112136

@@ -116,7 +140,7 @@ object LogUtils {
116140
* Runs asynchronously to avoid blocking the main thread.
117141
*/
118142
fun purgeOldLogs() {
119-
thread(name = "LogPurgeThread", isDaemon = true) {
143+
logScope.launch {
120144
purgeOldLogsFromFile(File(context.filesDir, SECURITY_LOGS), "audit")
121145
purgeOldLogsFromFile(File(context.cacheDir, FILE_NAME), "app")
122146
}

app/src/main/java/dev/pranav/applock/data/manager/BackendServiceManager.kt

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,19 @@ class BackendServiceManager {
2424
chosenBackend: BackendImplementation
2525
): Boolean {
2626
Log.d(TAG, "Checking if service ${serviceClass.simpleName} should start")
27-
Log.d(TAG, "Active backend: ${activeBackend?.name}, Chosen backend: ${chosenBackend.name}")
27+
Log.d(TAG, "Chosen backend: ${chosenBackend.name}")
2828

2929
val serviceBackend = getBackendForService(serviceClass)
3030
if (serviceBackend == null) {
3131
Log.d(TAG, "Unknown service class: ${serviceClass.simpleName}")
3232
return false
3333
}
3434

35-
// Service should start if it matches the chosen backend
3635
if (serviceBackend == chosenBackend) {
3736
Log.d(TAG, "Service ${serviceClass.simpleName} matches chosen backend")
3837
return true
3938
}
4039

41-
// Service should start if it matches the active backend (fallback scenario)
42-
if (activeBackend != null && serviceBackend == activeBackend) {
43-
Log.d(TAG, "Service ${serviceClass.simpleName} matches active backend")
44-
return true
45-
}
46-
4740
Log.d(TAG, "Service ${serviceClass.simpleName} should not start")
4841
return false
4942
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package dev.pranav.applock.data.repository
22

33
import android.content.Context
44
import dev.pranav.applock.data.manager.BackendServiceManager
5+
import dev.pranav.applock.services.AppLockManager
56

67
/**
78
* Main repository that coordinates between different specialized repositories and managers.
@@ -14,9 +15,15 @@ class AppLockRepository(private val context: Context) {
1415
private val backendServiceManager = BackendServiceManager()
1516

1617
fun getLockedApps(): Set<String> = lockedAppsRepository.getLockedApps()
17-
fun addLockedApp(packageName: String) = lockedAppsRepository.addLockedApp(packageName)
18-
fun addMultipleLockedApps(packageNames: Set<String>) =
18+
fun addLockedApp(packageName: String) {
19+
lockedAppsRepository.addLockedApp(packageName)
20+
AppLockManager.clearAppUnlockState(packageName)
21+
}
22+
23+
fun addMultipleLockedApps(packageNames: Set<String>) {
1924
lockedAppsRepository.addMultipleLockedApps(packageNames)
25+
packageNames.forEach(AppLockManager::clearAppUnlockState)
26+
}
2027
fun removeLockedApp(packageName: String) = lockedAppsRepository.removeLockedApp(packageName)
2128
fun isAppLocked(packageName: String): Boolean = lockedAppsRepository.isAppLocked(packageName)
2229

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

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import android.os.Build
66
import android.os.Bundle
77
import android.util.Log
88
import android.view.WindowManager
9-
import androidx.activity.addCallback
109
import androidx.activity.compose.BackHandler
1110
import androidx.activity.compose.setContent
1211
import androidx.activity.enableEdgeToEdge
@@ -67,7 +66,6 @@ class PasswordOverlayActivity : FragmentActivity() {
6766
internal var triggeringPackageNameFromIntent: String? = null
6867

6968
private var isBiometricPromptShowingLocal = false
70-
private var movedToBackground = false
7169
private var appName: String = ""
7270

7371
private val TAG = "PasswordOverlayActivity"
@@ -87,9 +85,14 @@ class PasswordOverlayActivity : FragmentActivity() {
8785

8886
appLockRepository = AppLockRepository(applicationContext)
8987

90-
onBackPressedDispatcher.addCallback(this) {
91-
// Prevent back navigation to maintain security
92-
}
88+
onBackPressedDispatcher.addCallback(
89+
this,
90+
object: androidx.activity.OnBackPressedCallback(true) {
91+
override fun handleOnBackPressed() {
92+
// Prevent back navigation to maintain security
93+
Log.d(TAG, "Back pressed ignored on AppLock overlay")
94+
}
95+
})
9396

9497
setupWindow()
9598
loadAppNameAndSetupUI()
@@ -256,7 +259,6 @@ class PasswordOverlayActivity : FragmentActivity() {
256259

257260
override fun onResume() {
258261
super.onResume()
259-
movedToBackground = false
260262
AppLockManager.isLockScreenShown.set(true) // Set to true when activity is visible
261263
lifecycleScope.launch {
262264
applyUserPreferences()
@@ -290,10 +292,9 @@ class PasswordOverlayActivity : FragmentActivity() {
290292

291293
override fun onPause() {
292294
super.onPause()
293-
if (!isChangingConfigurations() && !isBiometricPromptShowingLocal && !movedToBackground) {
295+
if (!isChangingConfigurations() && !isBiometricPromptShowingLocal) {
296+
Log.d(TAG, "Overlay paused; lock screen hidden but app remains locked")
294297
AppLockManager.isLockScreenShown.set(false)
295-
AppLockManager.reportBiometricAuthFinished()
296-
finish()
297298
}
298299
}
299300

@@ -304,10 +305,12 @@ class PasswordOverlayActivity : FragmentActivity() {
304305

305306
override fun onStop() {
306307
super.onStop()
307-
movedToBackground = true
308+
if (isChangingConfigurations()) {
309+
return
310+
}
311+
Log.d(TAG, "Overlay stopped; finishing lock overlay")
308312
AppLockManager.isLockScreenShown.set(false)
309-
if (!isChangingConfigurations() && !isFinishing && !isDestroyed) {
310-
AppLockManager.reportBiometricAuthFinished()
313+
if (!isFinishing && !isDestroyed) {
311314
finish()
312315
}
313316
}
@@ -495,9 +498,7 @@ fun PasswordOverlayScreen(
495498
}
496499
}
497500

498-
if (fromMainActivity) {
499-
BackHandler {}
500-
}
501+
BackHandler {}
501502
}
502503

503504
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalAnimationApi::class)

0 commit comments

Comments
 (0)