mirror of https://github.com/nextcloud/android
144 lines
5.3 KiB
Kotlin
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)
|
|
}
|
|
}
|