From 6280c8ef8074ace738e67a9a1cdd486cb0bebb9a Mon Sep 17 00:00:00 2001 From: ajp_anton Date: Thu, 23 Jul 2026 10:00:12 +0000 Subject: [PATCH] Add opt-in device credential app lock --- .../ajpanton/notificationlog/MainActivity.kt | 26 +++++++++++++++++++ .../notificationlog/SettingsFragment.kt | 4 +++ .../notificationlog/settings/AppLockStore.kt | 10 +++++++ app/src/main/res/layout/fragment_settings.xml | 2 +- 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/se/ajpanton/notificationlog/settings/AppLockStore.kt diff --git a/app/src/main/java/se/ajpanton/notificationlog/MainActivity.kt b/app/src/main/java/se/ajpanton/notificationlog/MainActivity.kt index 38d67b3..8b86c79 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/MainActivity.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/MainActivity.kt @@ -1,6 +1,9 @@ package se.ajpanton.notificationlog import android.os.Bundle +import android.hardware.biometrics.BiometricPrompt +import android.hardware.biometrics.BiometricManager.Authenticators +import android.os.CancellationSignal import android.content.res.Configuration import android.view.Menu import android.view.MenuItem @@ -19,11 +22,13 @@ import androidx.fragment.app.commit import com.google.android.material.navigation.NavigationView import se.ajpanton.notificationlog.databinding.ActivityMainBinding import se.ajpanton.notificationlog.settings.LoggingType +import se.ajpanton.notificationlog.settings.AppLockStore class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { private lateinit var binding: ActivityMainBinding private lateinit var drawerToggle: ActionBarDrawerToggle private var permanentSidebar = false + private var needsUnlock = true private var currentItemId = R.id.nav_view_logs private var drawerNavigationBasePaddingLeft: Int? = null private var permanentNavigationBasePaddingLeft: Int? = null @@ -77,6 +82,27 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte super.onSaveInstanceState(outState) } + override fun onResume() { + super.onResume() + if (needsUnlock && AppLockStore(this).enabled) requestUnlock() + } + + override fun onStop() { + super.onStop() + if (!isChangingConfigurations) needsUnlock = true + } + + private fun requestUnlock() { + BiometricPrompt.Builder(this) + .setTitle("Unlock Notification Log") + .setAllowedAuthenticators(Authenticators.BIOMETRIC_STRONG or Authenticators.DEVICE_CREDENTIAL) + .build() + .authenticate(CancellationSignal(), mainExecutor, object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult?) { needsUnlock = false } + override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { finishAndRemoveTask() } + }) + } + override fun onNavigationItemSelected(item: MenuItem): Boolean { if (item.itemId == R.id.nav_settings_header) { return false diff --git a/app/src/main/java/se/ajpanton/notificationlog/SettingsFragment.kt b/app/src/main/java/se/ajpanton/notificationlog/SettingsFragment.kt index ef6d7ef..df1c3e0 100644 --- a/app/src/main/java/se/ajpanton/notificationlog/SettingsFragment.kt +++ b/app/src/main/java/se/ajpanton/notificationlog/SettingsFragment.kt @@ -11,6 +11,7 @@ import se.ajpanton.notificationlog.settings.LogViewSettingsStore import se.ajpanton.notificationlog.settings.TimestampZone import se.ajpanton.notificationlog.settings.LoggingType import se.ajpanton.notificationlog.settings.LoggingRuleStore +import se.ajpanton.notificationlog.settings.AppLockStore import com.google.android.material.dialog.MaterialAlertDialogBuilder class SettingsFragment : Fragment(R.layout.fragment_settings) { @@ -46,6 +47,9 @@ class SettingsFragment : Fragment(R.layout.fragment_settings) { } } binding!!.copyEventSettings.setOnClickListener { chooseCopySource() } + val lockStore = AppLockStore(requireContext()) + binding!!.appLock.isChecked = lockStore.enabled + binding!!.appLock.setOnCheckedChangeListener { _, enabled -> lockStore.enabled = enabled } } override fun onDestroyView() { binding = null; super.onDestroyView() } diff --git a/app/src/main/java/se/ajpanton/notificationlog/settings/AppLockStore.kt b/app/src/main/java/se/ajpanton/notificationlog/settings/AppLockStore.kt new file mode 100644 index 0000000..39dd867 --- /dev/null +++ b/app/src/main/java/se/ajpanton/notificationlog/settings/AppLockStore.kt @@ -0,0 +1,10 @@ +package se.ajpanton.notificationlog.settings + +import android.content.Context + +class AppLockStore(context: Context) { + private val prefs = context.getSharedPreferences("app-lock", Context.MODE_PRIVATE) + var enabled: Boolean + get() = prefs.getBoolean("enabled", false) + set(value) = prefs.edit().putBoolean("enabled", value).apply() +} diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index da09ba8..0ee1f05 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -1,2 +1,2 @@ -