PassCodeManager.kt 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  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.WindowManager
  26. import com.nextcloud.client.core.Clock
  27. import com.nextcloud.client.preferences.AppPreferences
  28. import com.owncloud.android.MainApp
  29. import com.owncloud.android.ui.activity.PassCodeActivity
  30. import com.owncloud.android.ui.activity.RequestCredentialsActivity
  31. import com.owncloud.android.ui.activity.SettingsActivity
  32. import com.owncloud.android.utils.DeviceCredentialUtils
  33. import kotlin.math.abs
  34. @Suppress("TooManyFunctions")
  35. class PassCodeManager(private val preferences: AppPreferences, private val clock: Clock) {
  36. companion object {
  37. private val exemptOfPasscodeActivities = setOf(
  38. PassCodeActivity::class.java,
  39. RequestCredentialsActivity::class.java
  40. )
  41. const val PASSCODE_ACTIVITY = 9999
  42. /**
  43. * Keeping a "low" positive value is the easiest way to prevent
  44. * the pass code being requested on screen rotations.
  45. */
  46. private const val PASS_CODE_TIMEOUT = 5000
  47. }
  48. private var visibleActivitiesCounter = 0
  49. private fun isExemptActivity(activity: Activity): Boolean {
  50. return exemptOfPasscodeActivities.contains(activity.javaClass)
  51. }
  52. fun onActivityStarted(activity: Activity): Boolean {
  53. var askedForPin = false
  54. val timestamp = preferences.lockTimestamp
  55. setSecureFlag(activity)
  56. if (!isExemptActivity(activity)) {
  57. if (passCodeShouldBeRequested(timestamp)) {
  58. askedForPin = true
  59. preferences.lockTimestamp = 0
  60. requestPasscode(activity)
  61. }
  62. if (deviceCredentialsShouldBeRequested(timestamp, activity)) {
  63. askedForPin = true
  64. preferences.lockTimestamp = 0
  65. requestCredentials(activity)
  66. }
  67. }
  68. if (!askedForPin && preferences.lockTimestamp != 0L) {
  69. updateLockTimestamp()
  70. }
  71. if (!isExemptActivity(activity)) {
  72. visibleActivitiesCounter++ // keep it AFTER passCodeShouldBeRequested was checked
  73. }
  74. return askedForPin
  75. }
  76. private fun setSecureFlag(activity: Activity) {
  77. val window = activity.window
  78. if (window != null) {
  79. if (isPassCodeEnabled() || deviceCredentialsAreEnabled(activity)) {
  80. window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
  81. } else {
  82. window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
  83. }
  84. }
  85. }
  86. private fun requestPasscode(activity: Activity) {
  87. val i = Intent(MainApp.getAppContext(), PassCodeActivity::class.java)
  88. i.action = PassCodeActivity.ACTION_CHECK
  89. i.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
  90. activity.startActivityForResult(i, PASSCODE_ACTIVITY)
  91. }
  92. private fun requestCredentials(activity: Activity) {
  93. val i = Intent(MainApp.getAppContext(), RequestCredentialsActivity::class.java)
  94. i.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
  95. activity.startActivityForResult(i, PASSCODE_ACTIVITY)
  96. }
  97. fun onActivityStopped(activity: Activity) {
  98. if (visibleActivitiesCounter > 0 && !isExemptActivity(activity)) {
  99. visibleActivitiesCounter--
  100. }
  101. val powerMgr = activity.getSystemService(Context.POWER_SERVICE) as PowerManager
  102. if ((isPassCodeEnabled() || deviceCredentialsAreEnabled(activity)) && !powerMgr?.isScreenOn) {
  103. activity.moveTaskToBack(true)
  104. }
  105. }
  106. fun updateLockTimestamp() {
  107. preferences.lockTimestamp = clock.millisSinceBoot
  108. }
  109. /**
  110. * `true` if the time elapsed since last unlock is longer than [PASS_CODE_TIMEOUT] and no activities are visible
  111. */
  112. private fun shouldBeLocked(timestamp: Long) =
  113. abs(clock.millisSinceBoot - timestamp) > PASS_CODE_TIMEOUT &&
  114. visibleActivitiesCounter <= 0
  115. private fun passCodeShouldBeRequested(timestamp: Long): Boolean {
  116. return shouldBeLocked(timestamp) && isPassCodeEnabled()
  117. }
  118. private fun isPassCodeEnabled(): Boolean = SettingsActivity.LOCK_PASSCODE == preferences.lockPreference
  119. private fun deviceCredentialsShouldBeRequested(timestamp: Long, activity: Activity): Boolean {
  120. return shouldBeLocked(timestamp) && deviceCredentialsAreEnabled(activity)
  121. }
  122. private fun deviceCredentialsAreEnabled(activity: Activity): Boolean {
  123. return SettingsActivity.LOCK_DEVICE_CREDENTIALS == preferences.lockPreference ||
  124. (preferences.isFingerprintUnlockEnabled && DeviceCredentialUtils.areCredentialsAvailable(activity))
  125. }
  126. }