Skip to content

Commit 57a47b5

Browse files
committed
Fix biometric crashing applock
1 parent 52c86c5 commit 57a47b5

6 files changed

Lines changed: 216 additions & 57 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@
6363
android:taskAffinity=""
6464
android:theme="@android:style/Theme.Material.NoActionBar.TranslucentDecor" />
6565

66+
67+
<activity
68+
android:name=".features.lockscreen.ui.TransparentBiometricActivity"
69+
android:excludeFromRecents="true"
70+
android:exported="false"
71+
android:launchMode="singleInstance" />
72+
73+
6674
<activity
6775
android:name=".features.admin.AdminDisableActivity"
6876
android:exported="false"

app/src/main/java/dev/pranav/applock/features/antiuninstall/ui/AntiUninstallScreen.kt

Lines changed: 142 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
package dev.pranav.applock.features.antiuninstall.ui
22

33
import android.content.Context
4+
import android.content.Intent
45
import android.content.pm.ApplicationInfo
56
import android.content.pm.PackageManager
67
import androidx.compose.foundation.Image
78
import androidx.compose.foundation.clickable
8-
import androidx.compose.foundation.layout.*
9+
import androidx.compose.foundation.layout.Arrangement
10+
import androidx.compose.foundation.layout.Box
11+
import androidx.compose.foundation.layout.Column
12+
import androidx.compose.foundation.layout.PaddingValues
13+
import androidx.compose.foundation.layout.Row
14+
import androidx.compose.foundation.layout.Spacer
15+
import androidx.compose.foundation.layout.fillMaxSize
16+
import androidx.compose.foundation.layout.fillMaxWidth
17+
import androidx.compose.foundation.layout.height
18+
import androidx.compose.foundation.layout.padding
19+
import androidx.compose.foundation.layout.size
20+
import androidx.compose.foundation.layout.width
21+
import androidx.compose.foundation.layout.windowInsetsPadding
922
import androidx.compose.foundation.lazy.LazyColumn
1023
import androidx.compose.foundation.lazy.items
1124
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -14,8 +27,27 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
1427
import androidx.compose.material.icons.filled.Add
1528
import androidx.compose.material.icons.filled.Search
1629
import androidx.compose.material.icons.outlined.Shield
17-
import androidx.compose.material3.*
18-
import androidx.compose.runtime.*
30+
import androidx.compose.material3.AlertDialog
31+
import androidx.compose.material3.CircularProgressIndicator
32+
import androidx.compose.material3.ExperimentalMaterial3Api
33+
import androidx.compose.material3.Icon
34+
import androidx.compose.material3.IconButton
35+
import androidx.compose.material3.MaterialTheme
36+
import androidx.compose.material3.OutlinedTextField
37+
import androidx.compose.material3.Scaffold
38+
import androidx.compose.material3.Surface
39+
import androidx.compose.material3.Switch
40+
import androidx.compose.material3.Text
41+
import androidx.compose.material3.TextButton
42+
import androidx.compose.material3.TopAppBarDefaults
43+
import androidx.compose.runtime.Composable
44+
import androidx.compose.runtime.DisposableEffect
45+
import androidx.compose.runtime.LaunchedEffect
46+
import androidx.compose.runtime.collectAsState
47+
import androidx.compose.runtime.getValue
48+
import androidx.compose.runtime.mutableStateOf
49+
import androidx.compose.runtime.remember
50+
import androidx.compose.runtime.setValue
1951
import androidx.compose.ui.Alignment
2052
import androidx.compose.ui.Modifier
2153
import androidx.compose.ui.draw.clip
@@ -26,6 +58,7 @@ import androidx.compose.ui.text.style.TextOverflow
2658
import androidx.compose.ui.unit.dp
2759
import androidx.compose.ui.window.DialogProperties
2860
import androidx.core.graphics.drawable.toBitmap
61+
import androidx.core.net.toUri
2962
import androidx.lifecycle.ViewModel
3063
import androidx.lifecycle.viewModelScope
3164
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -41,6 +74,14 @@ import kotlinx.coroutines.flow.asStateFlow
4174
import kotlinx.coroutines.launch
4275
import kotlinx.coroutines.withContext
4376
import rikka.shizuku.Shizuku
77+
import rikka.shizuku.ShizukuProvider
78+
79+
enum class ShizukuState {
80+
NOT_INSTALLED,
81+
NOT_RUNNING, // Binder hasn't been received
82+
PERMISSION_DENIED,
83+
READY
84+
}
4485

4586
class AntiUninstallViewModel: ViewModel() {
4687
private val _allApps = MutableStateFlow<List<AppInfo>>(emptyList())
@@ -157,6 +198,22 @@ class AntiUninstallViewModel: ViewModel() {
157198
}
158199
}
159200

201+
fun isShizukuInstalled(context: Context): Boolean {
202+
return try {
203+
context.packageManager.getPackageInfo(ShizukuProvider.MANAGER_APPLICATION_ID, 0)
204+
true
205+
} catch (e: PackageManager.NameNotFoundException) {
206+
false
207+
}
208+
}
209+
210+
fun checkShizukuState(context: Context): ShizukuState {
211+
if (!isShizukuInstalled(context)) return ShizukuState.NOT_INSTALLED
212+
if (!Shizuku.pingBinder()) return ShizukuState.NOT_RUNNING
213+
if (Shizuku.checkSelfPermission() == PackageManager.PERMISSION_DENIED) return ShizukuState.PERMISSION_DENIED
214+
return ShizukuState.READY
215+
}
216+
160217
@OptIn(ExperimentalMaterial3Api::class)
161218
@Composable
162219
fun AntiUninstallScreen(
@@ -174,12 +231,33 @@ fun AntiUninstallScreen(
174231

175232
val showManualAddDialog = remember { mutableStateOf(false) }
176233

234+
var shizukuState by remember { mutableStateOf(checkShizukuState(context)) }
235+
177236
LaunchedEffect(Unit) {
178237
viewModel.loadApps(context)
179238
}
180239

181-
val showMessage =
182-
remember { mutableStateOf(Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) }
240+
DisposableEffect(Unit) {
241+
val binderReceivedListener = Shizuku.OnBinderReceivedListener {
242+
shizukuState = checkShizukuState(context)
243+
}
244+
val binderDeadListener = Shizuku.OnBinderDeadListener {
245+
shizukuState = checkShizukuState(context)
246+
}
247+
val permissionResultListener = Shizuku.OnRequestPermissionResultListener { _, _ ->
248+
shizukuState = checkShizukuState(context)
249+
}
250+
251+
Shizuku.addBinderReceivedListener(binderReceivedListener)
252+
Shizuku.addBinderDeadListener(binderDeadListener)
253+
Shizuku.addRequestPermissionResultListener(permissionResultListener)
254+
255+
onDispose {
256+
Shizuku.removeBinderReceivedListener(binderReceivedListener)
257+
Shizuku.removeBinderDeadListener(binderDeadListener)
258+
Shizuku.removeRequestPermissionResultListener(permissionResultListener)
259+
}
260+
}
183261

184262
Scaffold(
185263
modifier = Modifier.fillMaxSize(),
@@ -302,33 +380,79 @@ fun AntiUninstallScreen(
302380
)
303381
}
304382

305-
if (showMessage.value) {
383+
if (shizukuState != ShizukuState.READY) {
306384
AlertDialog(
307-
onDismissRequest = { showMessage.value = false },
308-
properties = DialogProperties(usePlatformDefaultWidth = false),
309-
title = { Text("Shizuku") },
385+
onDismissRequest = { /* Force response to use feature */ },
386+
properties = DialogProperties(
387+
usePlatformDefaultWidth = false,
388+
dismissOnBackPress = false,
389+
dismissOnClickOutside = false
390+
),
391+
title = {
392+
Text(
393+
text = when (shizukuState) {
394+
ShizukuState.NOT_INSTALLED -> "Shizuku Not Installed"
395+
ShizukuState.NOT_RUNNING -> "Shizuku Not Running"
396+
ShizukuState.PERMISSION_DENIED -> "Permission Required"
397+
else -> "Shizuku Setup"
398+
}
399+
)
400+
},
310401
text = {
311402
Column(Modifier.fillMaxWidth(0.8f)) {
312403
Text(
313-
text = "Please note that Shizuku must be installed and granted for this feature to work. You may revoke the permission later on, if you wish.\n\nThankfully, Shizuku does not require root, and its only required while you Block/Unblock uninstalls, so you don't need it always running ;)",
314-
style = MaterialTheme.typography.bodyMedium,
315-
modifier = Modifier.padding(bottom = 16.dp)
404+
text = when (shizukuState) {
405+
ShizukuState.NOT_INSTALLED -> "Shizuku is required to configure Anti-Uninstall protection without root.\n\nPlease install Shizuku from the Play Store or GitHub to continue."
406+
ShizukuState.NOT_RUNNING -> "Shizuku is installed, but the background service has not been started yet (Binder hasn't been received).\n\nPlease open the Shizuku application and start the service via Wireless Debugging or Root."
407+
ShizukuState.PERMISSION_DENIED -> "Shizuku is active, but this application needs your permission authorization to configure package restrictions.\n\nThankfully, Shizuku is only used while blocking/unblocking uninstalls, so it doesn't need to stay running forever!"
408+
else -> ""
409+
},
410+
style = MaterialTheme.typography.bodyMedium
316411
)
317412
}
318413
},
319414
confirmButton = {
320415
TextButton(
321416
onClick = {
322-
Shizuku.requestPermission(0)
323-
showMessage.value = false
417+
when (shizukuState) {
418+
ShizukuState.NOT_INSTALLED -> {
419+
val intent = Intent(
420+
Intent.ACTION_VIEW,
421+
"https://play.google.com/store/apps/details?id=${ShizukuProvider.MANAGER_APPLICATION_ID}".toUri()
422+
)
423+
context.startActivity(intent)
424+
}
425+
426+
ShizukuState.NOT_RUNNING -> {
427+
val launchIntent =
428+
context.packageManager.getLaunchIntentForPackage(ShizukuProvider.MANAGER_APPLICATION_ID)
429+
if (launchIntent != null) {
430+
context.startActivity(launchIntent)
431+
}
432+
}
433+
434+
ShizukuState.PERMISSION_DENIED -> {
435+
Shizuku.requestPermission(0)
436+
}
437+
438+
else -> {}
439+
}
324440
}
325-
) { Text("Confirm") }
441+
) {
442+
Text(
443+
text = when (shizukuState) {
444+
ShizukuState.NOT_INSTALLED -> "Install Shizuku"
445+
ShizukuState.NOT_RUNNING -> "Open Shizuku"
446+
ShizukuState.PERMISSION_DENIED -> "Grant Permission"
447+
else -> "Confirm"
448+
}
449+
)
450+
}
326451
},
327452
dismissButton = {
328453
TextButton(onClick = {
329454
navController.popBackStack()
330-
showMessage.value = false
331-
}) { Text("Cancel") }
455+
}) { Text("Go Back") }
332456
}
333457
)
334458
}
@@ -477,4 +601,4 @@ private fun ManualPackageItem(packageName: String, onToggle: () -> Unit) {
477601
Switch(checked = true, onCheckedChange = { onToggle() })
478602
}
479603
}
480-
}
604+
}

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import androidx.savedstate.SavedStateRegistryOwner
2828
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
2929
import dev.pranav.applock.core.utils.appLockRepository
3030
import dev.pranav.applock.data.repository.PreferencesRepository
31+
import dev.pranav.applock.services.AppLockManager
3132
import dev.pranav.applock.ui.theme.AppLockTheme
3233

3334
@SuppressLint("ViewConstructor")
@@ -124,7 +125,20 @@ class LockScreenOverlayManager(private val context: Context):
124125
},
125126
lockedAppName = appName,
126127
triggeringPackageName = triggeringPackageName,
127-
onPatternAttempt = onPatternAttemptCallback
128+
onPatternAttempt = onPatternAttemptCallback,
129+
onBiometricAuth = {
130+
val intent = Intent(
131+
context,
132+
TransparentBiometricActivity::class.java
133+
).apply {
134+
flags =
135+
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_ANIMATION
136+
putExtra("locked_package", lockedPackageName)
137+
}
138+
AppLockManager.reportBiometricAuthStarted()
139+
removeOverlay()
140+
context.startActivity(intent)
141+
}
128142
)
129143
}
130144

@@ -152,6 +166,8 @@ class LockScreenOverlayManager(private val context: Context):
152166
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_ANIMATION
153167
putExtra("locked_package", lockedPackageName)
154168
}
169+
AppLockManager.reportBiometricAuthStarted()
170+
removeOverlay()
155171
context.startActivity(intent)
156172
},
157173
onPasswordAttempt = onPinAttemptCallback
@@ -182,6 +198,8 @@ class LockScreenOverlayManager(private val context: Context):
182198
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_ANIMATION
183199
putExtra("locked_package", lockedPackageName)
184200
}
201+
AppLockManager.reportBiometricAuthStarted()
202+
removeOverlay()
185203
context.startActivity(intent)
186204
},
187205
onPinAttempt = onPinAttemptCallback

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,18 @@ package dev.pranav.applock.features.lockscreen.ui
22

33
import android.os.Bundle
44
import android.util.Log
5+
import androidx.activity.compose.setContent
56
import androidx.biometric.BiometricManager
67
import androidx.biometric.BiometricPrompt
8+
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.material3.MaterialTheme
10+
import androidx.compose.material3.Surface
11+
import androidx.compose.ui.Modifier
712
import androidx.core.content.ContextCompat
813
import androidx.fragment.app.FragmentActivity
914
import dev.pranav.applock.R
1015
import dev.pranav.applock.services.AppLockManager
16+
import dev.pranav.applock.ui.theme.AppLockTheme
1117

1218
class TransparentBiometricActivity: FragmentActivity() {
1319
private val TAG = "TransparentBiometric"
@@ -17,6 +23,15 @@ class TransparentBiometricActivity: FragmentActivity() {
1723
super.onCreate(savedInstanceState)
1824
lockedPackageName = intent.getStringExtra("locked_package")
1925

26+
setContent {
27+
AppLockTheme {
28+
Surface(
29+
modifier = Modifier.fillMaxSize(),
30+
color = MaterialTheme.colorScheme.background
31+
) {}
32+
}
33+
}
34+
2035
AppLockManager.reportBiometricAuthStarted()
2136

2237
val executor = ContextCompat.getMainExecutor(this)
@@ -32,10 +47,12 @@ class TransparentBiometricActivity: FragmentActivity() {
3247
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
3348
super.onAuthenticationSucceeded(result)
3449
AppLockManager.reportBiometricAuthFinished()
50+
AppLockManager.isLockScreenShown.set(false)
3551
lockedPackageName?.let {
3652
AppLockManager.temporarilyUnlockAppWithBiometrics(it)
3753
}
3854

55+
3956
// The Accessibility service will detect the unlock state
4057
// and close the Service View automatically
4158
finish()

0 commit comments

Comments
 (0)