PassCodeManager.kt 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /*
  2. * ownCloud Android client application
  3. *
  4. * @author David A. Velasco
  5. * Copyright (C) 2015 ownCloud Inc.
  6. *
  7. * This program is free software: you can redistribute it and/or modify
  8. * it under the terms of the GNU General Public License version 2,
  9. * as published by the Free Software Foundation.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. *
  19. */
  20. package com.owncloud.android.authentication
  21. import android.app.Activity
  22. import android.content.Context
  23. import android.content.Intent
  24. import android.os.PowerManager
  25. import android.view.View
  26. import android.view.WindowManager
  27. import androidx.annotation.VisibleForTesting
  28. import com.nextcloud.client.core.Clock
  29. import com.nextcloud.client.preferences.AppPreferences
  30. import com.owncloud.android.MainApp
  31. import com.owncloud.android.ui.activity.PassCodeActivity
  32. import com.owncloud.android.ui.activity.RequestCredentialsActivity
  33. import com.owncloud.android.ui.activity.SettingsActivity
  34. import com.owncloud.android.utils.DeviceCredentialUtils
  35. import kotlin.math.abs
  36. @Suppress("TooManyFunctions")
  37. class PassCodeManager(private val preferences: AppPreferences, private val clock: Clock) {
  38. companion object {
  39. private val exemptOfPasscodeActivities = setOf(
  40. PassCodeActivity::class.java,
  41. RequestCredentialsActivity::class.java
  42. )
  43. const val PASSCODE_ACTIVITY = 9999
  44. /**
  45. * Keeping a "low" positive value is the easiest way to prevent
  46. * the pass code being requested on screen rotations.
  47. */
  48. private const val PASS_CODE_TIMEOUT = 5000
  49. }
  50. var canAskPin = true
  51. private var askPinWhenDeviceLocked = false
  52. private fun isExemptActivity(activity: Activity): Boolean {
  53. return exemptOfPasscodeActivities.contains(activity.javaClass)
  54. }
  55. fun onActivityResumed(activity: Activity): Boolean {
  56. var askedForPin = false
  57. val timestamp = preferences.lockTimestamp
  58. setSecureFlag(activity)
  59. if (!isExemptActivity(activity)) {
  60. val passcodeRequested = passCodeShouldBeRequested(timestamp)
  61. val credentialsRequested = deviceCredentialsShouldBeRequested(timestamp, activity)
  62. val shouldHideView = passcodeRequested || credentialsRequested
  63. getActivityRootView(activity)?.visibility = if (shouldHideView) View.GONE else View.VISIBLE
  64. askedForPin = shouldHideView
  65. if (passcodeRequested) {
  66. requestPasscode(activity)
  67. } else if (credentialsRequested) {
  68. requestCredentials(activity)
  69. }
  70. if (askedForPin) {
  71. preferences.lockTimestamp = 0
  72. }
  73. }
  74. if (!askedForPin && preferences.lockTimestamp != 0L || askPinWhenDeviceLocked) {
  75. updateLockTimestamp()
  76. askPinWhenDeviceLocked = false
  77. }
  78. return askedForPin
  79. }
  80. private fun setSecureFlag(activity: Activity) {
  81. activity.window?.let { window ->
  82. if (isPassCodeEnabled() || deviceCredentialsAreEnabled(activity)) {
  83. window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
  84. } else {
  85. window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
  86. }
  87. }
  88. }
  89. private fun requestPasscode(activity: Activity) {
  90. val i = Intent(MainApp.getAppContext(), PassCodeActivity::class.java).apply {
  91. action = PassCodeActivity.ACTION_CHECK
  92. flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
  93. }
  94. activity.startActivityForResult(i, PASSCODE_ACTIVITY)
  95. }
  96. private fun requestCredentials(activity: Activity) {
  97. val i = Intent(MainApp.getAppContext(), RequestCredentialsActivity::class.java).apply {
  98. flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
  99. }
  100. activity.startActivityForResult(i, PASSCODE_ACTIVITY)
  101. }
  102. fun onActivityStopped(activity: Activity) {
  103. val powerMgr = activity.getSystemService(Context.POWER_SERVICE) as PowerManager
  104. if ((isPassCodeEnabled() || deviceCredentialsAreEnabled(activity)) && !powerMgr.isInteractive) {
  105. askPinWhenDeviceLocked = true
  106. }
  107. }
  108. fun updateLockTimestamp() {
  109. preferences.lockTimestamp = clock.millisSinceBoot
  110. canAskPin = false
  111. }
  112. /**
  113. * `true` if the time elapsed since last unlock is longer than [PASS_CODE_TIMEOUT] and no activities are visible
  114. */
  115. private fun shouldBeLocked(timestamp: Long): Boolean {
  116. return (abs(clock.millisSinceBoot - timestamp) > PASS_CODE_TIMEOUT && canAskPin) || askPinWhenDeviceLocked
  117. }
  118. @VisibleForTesting
  119. fun passCodeShouldBeRequested(timestamp: Long): Boolean {
  120. return shouldBeLocked(timestamp) && isPassCodeEnabled()
  121. }
  122. private fun isPassCodeEnabled(): Boolean = SettingsActivity.LOCK_PASSCODE == preferences.lockPreference
  123. private fun deviceCredentialsShouldBeRequested(timestamp: Long, activity: Activity): Boolean {
  124. return shouldBeLocked(timestamp) && deviceCredentialsAreEnabled(activity)
  125. }
  126. private fun deviceCredentialsAreEnabled(activity: Activity): Boolean {
  127. return SettingsActivity.LOCK_DEVICE_CREDENTIALS == preferences.lockPreference ||
  128. (preferences.isFingerprintUnlockEnabled && DeviceCredentialUtils.areCredentialsAvailable(activity))
  129. }
  130. private fun getActivityRootView(activity: Activity): View? {
  131. return activity.window?.findViewById(android.R.id.content)
  132. ?: activity.window?.decorView?.findViewById(android.R.id.content)
  133. }
  134. }