Fix app lock and make delay not survive app being killed (#8272)

* Fix app lock

* Always require unlock if app is killed
This commit is contained in:
stevenyomi 2022-10-23 03:07:44 +08:00 committed by GitHub
parent 4a244a598b
commit 2ab744c525
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 37 additions and 69 deletions

View File

@ -169,8 +169,8 @@ class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
}.build() }.build()
} }
override fun onCreate(owner: LifecycleOwner) { override fun onStart(owner: LifecycleOwner) {
SecureActivityDelegate.onApplicationCreated() SecureActivityDelegate.onApplicationStart()
} }
override fun onStop(owner: LifecycleOwner) { override fun onStop(owner: LifecycleOwner) {

View File

@ -17,55 +17,59 @@ import kotlinx.coroutines.flow.onEach
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date
interface SecureActivityDelegate { interface SecureActivityDelegate {
fun registerSecureActivity(activity: AppCompatActivity) fun registerSecureActivity(activity: AppCompatActivity)
companion object { companion object {
fun onApplicationCreated() { /**
val lockDelay = Injekt.get<SecurityPreferences>().lockAppAfter().get() * Set to true if we need the first activity to authenticate.
if (lockDelay <= 0) { *
// Restore always active/on start app lock * Always require unlock if app is killed.
// Delayed lock will be restored later on activity resume */
lockState = LockState.ACTIVE var requireUnlock = true
}
}
fun onApplicationStopped() { fun onApplicationStopped() {
val preferences = Injekt.get<SecurityPreferences>() val preferences = Injekt.get<SecurityPreferences>()
if (!preferences.useAuthenticator().get()) return if (!preferences.useAuthenticator().get()) return
if (lockState != LockState.ACTIVE) {
preferences.lastAppClosed().set(Date().time)
}
if (!AuthenticatorUtil.isAuthenticating) { if (!AuthenticatorUtil.isAuthenticating) {
val lockAfter = preferences.lockAppAfter().get() // Return if app is closed in locked state
lockState = if (lockAfter > 0) { if (requireUnlock) return
LockState.PENDING // Save app close time if lock is delayed
} else if (lockAfter == -1) { if (preferences.lockAppAfter().get() > 0) {
// Never lock on idle preferences.lastAppClosed().set(System.currentTimeMillis())
LockState.INACTIVE
} else {
LockState.ACTIVE
} }
} }
} }
/**
* Checks if unlock is needed when app comes foreground.
*/
fun onApplicationStart() {
val preferences = Injekt.get<SecurityPreferences>()
if (!preferences.useAuthenticator().get()) return
val lastClosedPref = preferences.lastAppClosed()
// `requireUnlock` can be true on process start or if app was closed in locked state
if (!AuthenticatorUtil.isAuthenticating && !requireUnlock) {
requireUnlock = when (val lockDelay = preferences.lockAppAfter().get()) {
-1 -> false // Never
0 -> true // Always
else -> lastClosedPref.get() + lockDelay * 60_000 <= System.currentTimeMillis()
}
}
lastClosedPref.delete()
}
fun unlock() { fun unlock() {
lockState = LockState.INACTIVE requireUnlock = false
Injekt.get<SecurityPreferences>().lastAppClosed().delete()
} }
} }
} }
private var lockState = LockState.INACTIVE
private enum class LockState {
INACTIVE,
PENDING,
ACTIVE,
}
class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObserver { class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObserver {
private lateinit var activity: AppCompatActivity private lateinit var activity: AppCompatActivity
@ -100,32 +104,11 @@ class SecureActivityDelegateImpl : SecureActivityDelegate, DefaultLifecycleObser
private fun setAppLock() { private fun setAppLock() {
if (!securityPreferences.useAuthenticator().get()) return if (!securityPreferences.useAuthenticator().get()) return
if (activity.isAuthenticationSupported()) { if (activity.isAuthenticationSupported()) {
updatePendingLockStatus() if (!SecureActivityDelegate.requireUnlock) return
if (!isAppLocked()) return
activity.startActivity(Intent(activity, UnlockActivity::class.java)) activity.startActivity(Intent(activity, UnlockActivity::class.java))
activity.overridePendingTransition(0, 0) activity.overridePendingTransition(0, 0)
} else { } else {
securityPreferences.useAuthenticator().set(false) securityPreferences.useAuthenticator().set(false)
} }
} }
private fun updatePendingLockStatus() {
val lastClosedPref = securityPreferences.lastAppClosed()
val lockDelay = 60000 * securityPreferences.lockAppAfter().get()
if (lastClosedPref.isSet() && lockDelay > 0) {
// Restore pending status in case app was killed
lockState = LockState.PENDING
}
if (lockState != LockState.PENDING) {
return
}
if (Date().time >= lastClosedPref.get() + lockDelay) {
// Activate lock after delay
lockState = LockState.ACTIVE
}
}
private fun isAppLocked(): Boolean {
return lockState == LockState.ACTIVE
}
} }

View File

@ -76,11 +76,6 @@ object AuthenticatorUtil {
activity?.toast(errString.toString()) activity?.toast(errString.toString())
cont.resume(false) cont.resume(false)
} }
override fun onAuthenticationFailed(activity: FragmentActivity?) {
super.onAuthenticationFailed(activity)
cont.resume(false)
}
}, },
) )
} }
@ -136,15 +131,5 @@ object AuthenticatorUtil {
) { ) {
isAuthenticating = false isAuthenticating = false
} }
/**
* Called when an authentication attempt by the user has been rejected.
*
* @param activity The activity that is currently hosting the prompt.
*/
@CallSuper
override fun onAuthenticationFailed(activity: FragmentActivity?) {
isAuthenticating = false
}
} }
} }