Skip to content

Commit 018e812

Browse files
fix: biometric auth lifecycle, Samsung SDK 35 root process crashes, bump version to 114236
1 parent a49ebce commit 018e812

6 files changed

Lines changed: 97 additions & 12 deletions

File tree

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@
149149
android:exported="false" />
150150

151151
<provider
152-
android:name="androidx.core.content.FileProvider"
152+
android:name=".util.SafeFileProvider"
153153
android:authorities="${applicationId}.fileprovider"
154154
android:exported="false"
155155
android:grantUriPermissions="true">

app/src/main/java/me/bmax/apatch/APatchApp.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,9 +354,12 @@ class APApplication : Application(), Thread.UncaughtExceptionHandler, ImageLoade
354354

355355
override fun onCreate() {
356356
super.onCreate()
357+
apApp = this
358+
if (Application.getProcessName().endsWith(":root") || Application.getProcessName().endsWith(":webui")) {
359+
return
360+
}
357361
bypassHiddenApiRestrictions()
358362
Log.d(TAG, "APApplication onCreate started")
359-
apApp = this
360363

361364
val isArm64 = Build.SUPPORTED_ABIS.any { it == "arm64-v8a" }
362365
Log.d(TAG, "Device architecture check: isArm64=$isArm64, supported ABIs=${Build.SUPPORTED_ABIS.joinToString(", ")}")

app/src/main/java/me/bmax/apatch/ui/MainActivity.kt

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,9 @@ class MainActivity : AppCompatActivity() {
237237
private var installUris: ArrayList<Uri>? = null
238238
private lateinit var permissionHandler: PermissionRequestHandler
239239
private val isLocked = mutableStateOf(false)
240+
private var isAuthenticated = false
241+
private var biometricPromptShowing = false
242+
private var startupSoundPlayed = false
240243
private var pendingActionModuleId by mutableStateOf<String?>(null)
241244
private var pendingScriptId by mutableStateOf<String?>(null)
242245

@@ -368,6 +371,17 @@ class MainActivity : AppCompatActivity() {
368371
// 初始化权限处理器
369372
permissionHandler = PermissionRequestHandler(this)
370373

374+
setupUI()
375+
}
376+
377+
override fun onResume() {
378+
super.onResume()
379+
showBiometricPromptIfNeeded()
380+
}
381+
382+
private fun showBiometricPromptIfNeeded() {
383+
if (isAuthenticated || biometricPromptShowing) return
384+
371385
val prefs = APApplication.sharedPreferences
372386
val biometricLogin = prefs.getBoolean("biometric_login", false)
373387
val biometricManager = androidx.biometric.BiometricManager.from(this)
@@ -379,21 +393,34 @@ class MainActivity : AppCompatActivity() {
379393
val isShareIntent = intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE
380394
if (biometricLogin && canAuthenticate && !isShareIntent) {
381395
isLocked.value = true
396+
biometricPromptShowing = true
382397
val biometricPrompt = androidx.biometric.BiometricPrompt(
383398
this,
384399
androidx.core.content.ContextCompat.getMainExecutor(this),
385400
object : androidx.biometric.BiometricPrompt.AuthenticationCallback() {
386401
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
387402
super.onAuthenticationError(errorCode, errString)
388-
showToast(this@MainActivity, errString.toString())
389-
finishAndRemoveTask()
403+
biometricPromptShowing = false
404+
if (errorCode == androidx.biometric.BiometricPrompt.ERROR_USER_CANCELED) {
405+
finishAndRemoveTask()
406+
} else {
407+
Handler(Looper.getMainLooper()).postDelayed({
408+
if (!isAuthenticated && !biometricPromptShowing) {
409+
showBiometricPromptIfNeeded()
410+
}
411+
}, 300)
412+
}
390413
}
391414

392415
override fun onAuthenticationSucceeded(result: androidx.biometric.BiometricPrompt.AuthenticationResult) {
393416
super.onAuthenticationSucceeded(result)
394417
isLocked.value = false
395-
// Play startup sound after biometric auth success
396-
me.bmax.apatch.util.SoundEffectManager.playStartup(this@MainActivity)
418+
isAuthenticated = true
419+
biometricPromptShowing = false
420+
if (!startupSoundPlayed) {
421+
startupSoundPlayed = true
422+
me.bmax.apatch.util.SoundEffectManager.playStartup(this@MainActivity)
423+
}
397424
}
398425
})
399426
val promptInfo = androidx.biometric.BiometricPrompt.PromptInfo.Builder()
@@ -402,11 +429,14 @@ class MainActivity : AppCompatActivity() {
402429
.setAllowedAuthenticators(androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG or androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL)
403430
.build()
404431
biometricPrompt.authenticate(promptInfo)
405-
} else {
406-
// Play startup sound directly if no biometric auth needed
407-
me.bmax.apatch.util.SoundEffectManager.playStartup(this)
432+
} else if (!biometricLogin || !canAuthenticate || isShareIntent) {
433+
isAuthenticated = true
434+
isLocked.value = false
435+
if (!startupSoundPlayed) {
436+
startupSoundPlayed = true
437+
me.bmax.apatch.util.SoundEffectManager.playStartup(this)
438+
}
408439
}
409-
setupUI()
410440
}
411441

412442
private fun setupUI() {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package me.bmax.apatch.util
2+
3+
import android.app.Application
4+
import android.database.Cursor
5+
import android.database.MatrixCursor
6+
import android.net.Uri
7+
import android.os.ParcelFileDescriptor
8+
import androidx.core.content.FileProvider
9+
10+
class SafeFileProvider : FileProvider() {
11+
private var shouldInit = false
12+
13+
override fun onCreate(): Boolean {
14+
val processName = Application.getProcessName()
15+
shouldInit = !processName.endsWith(":root") && !processName.endsWith(":webui")
16+
if (!shouldInit) return false
17+
return try {
18+
super.onCreate()
19+
} catch (e: Exception) {
20+
false
21+
}
22+
}
23+
24+
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor {
25+
if (!shouldInit) return MatrixCursor(emptyArray())
26+
return try {
27+
super.query(uri, projection, selection, selectionArgs, sortOrder)
28+
} catch (e: Exception) {
29+
MatrixCursor(emptyArray())
30+
}
31+
}
32+
33+
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor {
34+
if (!shouldInit) throw java.io.FileNotFoundException("Not available in this process")
35+
return try {
36+
super.openFile(uri, mode) ?: throw java.io.FileNotFoundException("Null result")
37+
} catch (e: java.io.FileNotFoundException) {
38+
throw e
39+
} catch (e: Exception) {
40+
throw java.io.FileNotFoundException(e.message)
41+
}
42+
}
43+
44+
override fun getType(uri: Uri): String? {
45+
if (!shouldInit) return null
46+
return try {
47+
super.getType(uri)
48+
} catch (e: Exception) {
49+
null
50+
}
51+
}
52+
}

app/src/main/java/me/bmax/apatch/util/ui/ToastExt.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ private const val TAG = "SafeToast"
1616
fun showToast(context: Context, message: String) {
1717
try {
1818
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
19-
} catch (e: SecurityException) {
19+
} catch (e: Exception) {
2020
Log.w(TAG, "System toast unavailable, using fallback: $message", e)
2121
showFallbackToast(context, message)
2222
}

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ fun getGitDescribe(): String {
3434
}
3535

3636
fun getVersionCode(): Int {
37-
return 114235
37+
return 114236
3838
}
3939

4040
fun getbranch(): String {

0 commit comments

Comments
 (0)