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:
parent
4a244a598b
commit
2ab744c525
@ -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) {
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user