android/app/src/main/java/com/owncloud/android/authentication/PassCodeManager.kt

144 lines
5.3 KiB
Kotlin

/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <alper_ozturk@proton.me>
* SPDX-FileCopyrightText: 2022-2023 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package com.owncloud.android.authentication
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.PowerManager
import android.view.View
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
import com.nextcloud.client.core.Clock
import com.nextcloud.client.preferences.AppPreferences
import com.owncloud.android.MainApp
import com.owncloud.android.ui.activity.PassCodeActivity
import com.owncloud.android.ui.activity.RequestCredentialsActivity
import com.owncloud.android.ui.activity.SettingsActivity
import com.owncloud.android.utils.DeviceCredentialUtils
import kotlin.math.abs
@Suppress("TooManyFunctions")
class PassCodeManager(private val preferences: AppPreferences, private val clock: Clock) {
companion object {
private val exemptOfPasscodeActivities = setOf(
PassCodeActivity::class.java,
RequestCredentialsActivity::class.java
)
const val PASSCODE_ACTIVITY = 9999
/**
* Keeping a "low" positive value is the easiest way to prevent
* the pass code being requested on screen rotations.
*/
private const val PASS_CODE_TIMEOUT = 5000
}
var canAskPin = true
private var askPinWhenDeviceLocked = false
private fun isExemptActivity(activity: Activity): Boolean {
return exemptOfPasscodeActivities.contains(activity.javaClass)
}
fun onActivityResumed(activity: Activity): Boolean {
var askedForPin = false
val timestamp = preferences.lockTimestamp
setSecureFlag(activity)
if (!isExemptActivity(activity)) {
val passcodeRequested = passCodeShouldBeRequested(timestamp)
val credentialsRequested = deviceCredentialsShouldBeRequested(timestamp, activity)
val shouldHideView = passcodeRequested || credentialsRequested
getActivityRootView(activity)?.visibility = if (shouldHideView) View.GONE else View.VISIBLE
askedForPin = shouldHideView
if (passcodeRequested) {
requestPasscode(activity)
} else if (credentialsRequested) {
requestCredentials(activity)
}
if (askedForPin) {
preferences.lockTimestamp = 0
}
}
if (!askedForPin && preferences.lockTimestamp != 0L || askPinWhenDeviceLocked) {
updateLockTimestamp()
askPinWhenDeviceLocked = false
}
return askedForPin
}
private fun setSecureFlag(activity: Activity) {
activity.window?.let { window ->
if (isPassCodeEnabled() || deviceCredentialsAreEnabled(activity)) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
}
private fun requestPasscode(activity: Activity) {
val i = Intent(MainApp.getAppContext(), PassCodeActivity::class.java).apply {
action = PassCodeActivity.ACTION_CHECK
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
activity.startActivityForResult(i, PASSCODE_ACTIVITY)
}
private fun requestCredentials(activity: Activity) {
val i = Intent(MainApp.getAppContext(), RequestCredentialsActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
activity.startActivityForResult(i, PASSCODE_ACTIVITY)
}
fun onActivityStopped(activity: Activity) {
val powerMgr = activity.getSystemService(Context.POWER_SERVICE) as PowerManager
if ((isPassCodeEnabled() || deviceCredentialsAreEnabled(activity)) && !powerMgr.isInteractive) {
askPinWhenDeviceLocked = true
}
}
fun updateLockTimestamp() {
preferences.lockTimestamp = clock.millisSinceBoot
canAskPin = false
}
/**
* `true` if the time elapsed since last unlock is longer than [PASS_CODE_TIMEOUT] and no activities are visible
*/
private fun shouldBeLocked(timestamp: Long): Boolean {
return (abs(clock.millisSinceBoot - timestamp) > PASS_CODE_TIMEOUT && canAskPin) || askPinWhenDeviceLocked
}
@VisibleForTesting
fun passCodeShouldBeRequested(timestamp: Long): Boolean {
return shouldBeLocked(timestamp) && isPassCodeEnabled()
}
private fun isPassCodeEnabled(): Boolean = SettingsActivity.LOCK_PASSCODE == preferences.lockPreference
private fun deviceCredentialsShouldBeRequested(timestamp: Long, activity: Activity): Boolean {
return shouldBeLocked(timestamp) && deviceCredentialsAreEnabled(activity)
}
private fun deviceCredentialsAreEnabled(activity: Activity): Boolean {
return SettingsActivity.LOCK_DEVICE_CREDENTIALS == preferences.lockPreference ||
(preferences.isFingerprintUnlockEnabled && DeviceCredentialUtils.areCredentialsAvailable(activity))
}
private fun getActivityRootView(activity: Activity): View? {
return activity.window?.findViewById(android.R.id.content)
?: activity.window?.decorView?.findViewById(android.R.id.content)
}
}