diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
index 7f0cc144..61236235 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -113,6 +113,7 @@ import com.lagradost.cloudstream3.ui.result.txt
import com.lagradost.cloudstream3.ui.search.SearchFragment
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
+import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTruePhone
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.updateTv
@@ -133,9 +134,8 @@ import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus
import com.lagradost.cloudstream3.utils.BackupUtils.backup
import com.lagradost.cloudstream3.utils.BackupUtils.setUpBackup
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.TAG
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isTruePhone
-import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey
@@ -1176,7 +1176,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
val newLocalBinding = ActivityMainTvBinding.inflate(layoutInflater, null, false)
setContentView(newLocalBinding.root)
- if(isTrueTvSettings() && ANIMATED_OUTLINE) {
+ if (isTrueTvSettings() && ANIMATED_OUTLINE) {
TvFocus.focusOutline = WeakReference(newLocalBinding.focusOutline)
newLocalBinding.root.viewTreeObserver.addOnScrollChangedListener {
TvFocus.updateFocusView(TvFocus.lastFocus.get(), same = true)
@@ -1188,14 +1188,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
newLocalBinding.focusOutline.isVisible = false
}
- if(isTrueTvSettings()) {
+ if (isTrueTvSettings()) {
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
centerView(newFocus)
}
}
-
-
ActivityMainBinding.bind(newLocalBinding.root) // this may crash
} else {
val newLocalBinding = ActivityMainBinding.inflate(layoutInflater, null, false)
@@ -1213,12 +1211,20 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_enabled_key), false)
val noAccounts = settingsManager.getBoolean(getString(R.string.skip_startup_account_select_key), false) || accounts.count() <= 1
- if (isTruePhone() && authEnabled && noAccounts ) {
+ if (isTruePhone() && authEnabled && noAccounts) {
+ if (deviceHasPasswordPinLock(this)) {
+ startBiometricAuthentication(this,
+ R.string.biometric_authentication_title,
+ false
+ )
- BiometricAuthenticator.initializeBiometrics(this@MainActivity, this)
- BiometricAuthenticator.checkBiometricAvailability()
- BiometricAuthenticator.biometricPrompt.authenticate(promptInfo)
- binding?.navHostFragment?.isInvisible = true // hide background while authenticating
+ BiometricAuthenticator.biometricPrompt.authenticate(BiometricAuthenticator.promptInfo)
+
+ // hide background while authenticating, Sorry moms & dads 🙏
+ binding?.navHostFragment?.isInvisible = true
+ } else {
+ showToast(R.string.phone_not_secured, Toast.LENGTH_LONG)
+ }
}
// Automatically enable jsdelivr if cant connect to raw.githubusercontent.com
@@ -1760,7 +1766,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAu
)
}
- override fun onAuthenticationSuccess() { /** Biometric stuff **/
+ /** Biometric stuff **/
+ override fun onAuthenticationSuccess() {
// make background (nav host fragment) visible again
binding?.navHostFragment?.isInvisible = false
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt
index 77bbc837..e1986e85 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt
@@ -3,6 +3,8 @@ package com.lagradost.cloudstream3.ui.account
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Bundle
+import android.util.Log
+import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
@@ -17,8 +19,11 @@ import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_EDIT_ACCOUNT
import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_SELECT_ACCOUNT
+import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTruePhone
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.BiometricAuthenticator
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator.deviceHasPasswordPinLock
+import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication
import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts
import com.lagradost.cloudstream3.utils.DataStoreHelper.selectedKeyIndex
import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount
@@ -43,19 +48,24 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_enabled_key), false)
- val skipStartup = settingsManager.getBoolean(
- getString(R.string.skip_startup_account_select_key),
- false
+ val skipStartup = settingsManager.getBoolean(getString(R.string.skip_startup_account_select_key), false
) || accounts.count() <= 1
viewModel = ViewModelProvider(this)[AccountViewModel::class.java]
fun askBiometricAuth() {
- if (BiometricAuthenticator.isTruePhone() && authEnabled) {
- BiometricAuthenticator.initializeBiometrics(this@AccountSelectActivity, this)
- BiometricAuthenticator.checkBiometricAvailability()
- BiometricAuthenticator.biometricPrompt.authenticate(BiometricAuthenticator.promptInfo)
+ if (isTruePhone() && authEnabled) {
+ if (deviceHasPasswordPinLock(this)) {
+ startBiometricAuthentication(
+ this,
+ R.string.biometric_authentication_title,
+ false
+ )
+ BiometricAuthenticator.biometricPrompt.authenticate(BiometricAuthenticator.promptInfo)
+ }
+ } else {
+ showToast(R.string.phone_not_secured, Toast.LENGTH_LONG)
}
}
@@ -178,7 +188,8 @@ class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.Biomet
startActivity(mainIntent)
finish() // Finish the account selection activity
}
+
override fun onAuthenticationSuccess() {
- //ask github.com/IndusAryan if confusion occurs
+ Log.i(BiometricAuthenticator.TAG,"Authentication successful in AccountSelectActivity")
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt
index 37c71134..8dedd896 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt
@@ -19,6 +19,7 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.MaterialToolbar
+import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.MainSettingsBinding
import com.lagradost.cloudstream3.mvvm.logError
@@ -155,6 +156,11 @@ class SettingsFragment : Fragment() {
return getLayoutInt() == 2
}
+ // phone exclusive
+ fun isTruePhone(): Boolean {
+ return !isTrueTvSettings() && !isTvSettings() && context?.isEmulatorSettings() != true
+ }
+
private fun Context.isAutoTv(): Boolean {
val uiModeManager = getSystemService(Context.UI_MODE_SERVICE) as UiModeManager?
// AFT = Fire TV
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt
index 2bc17891..a1b0693a 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt
@@ -5,7 +5,7 @@ import android.app.KeyguardManager
import android.content.Context
import android.os.Build
import android.util.Log
-import android.widget.Toast
+import android.widget.Toast.LENGTH_SHORT
import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
@@ -13,139 +13,164 @@ import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
-import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.CommonActivity.showToast
+import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isEmulatorSettings
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
-import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
object BiometricAuthenticator {
+ private const val MAX_FAILED_ATTEMPTS = 3
+ private var failedAttempts = 0
+ const val TAG = "cs3Auth"
+
private lateinit var biometricManager: BiometricManager
lateinit var biometricPrompt: BiometricPrompt
lateinit var promptInfo: BiometricPrompt.PromptInfo
- const val TAG = "cs3Auth"
- private var authCallback: BiometricAuthCallback? = null
+ var authCallback: BiometricAuthCallback? = null // listen to authentication success
- fun initializeBiometrics(activity: Activity, callback: BiometricAuthCallback) {
+ private fun initializeBiometrics(activity: Activity) {
val executor = ContextCompat.getMainExecutor(activity)
- authCallback = callback // to listen success authentication
biometricManager = BiometricManager.from(activity)
- biometricPrompt = BiometricPrompt(activity as AppCompatActivity, executor, object : BiometricPrompt.AuthenticationCallback() {
+ if (!::promptInfo.isInitialized) {
+ initBiometricPrompt(
+ activity as AppCompatActivity,
+ R.string.biometric_authentication_title,
+ false
+ )
+ }
+
+ biometricPrompt = BiometricPrompt(
+ activity as AppCompatActivity,
+ executor,
+ object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
- Log.d(TAG, "Authentication error: $errString")
+ showToast("$errString", LENGTH_SHORT)
+ Log.i(TAG, "$errorCode")
activity.finish()
+ failedAttempts++
+ if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
+ failedAttempts = 0
+ activity.finish()
+ } else {
+ failedAttempts = 0
+ MainActivity().finish()
+ }
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
- Log.d(TAG, "Biometric succeeded.")
- unblockMain() //to make background visible after authenticating
+ failedAttempts = 0
+ authCallback?.onAuthenticationSuccess()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
- Log.d(TAG, "Authentication error")
- showToast(R.string.biometric_failed, Toast.LENGTH_SHORT)
- activity.finish()
+ failedAttempts++
+ if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
+ failedAttempts = 0
+ activity.finish()
+ }
}
})
+ }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ private fun initBiometricPrompt(
+ activity: Activity,
+ title: Int,
+ setDeviceCred: Boolean,
+ ) {
+ val description = activity.getString(R.string.biometric_prompt_description)
+ if (setDeviceCred) {
+ // For API level > 30, Newer API setAllowedAuthenticators is used
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- promptInfo = BiometricPrompt.PromptInfo.Builder()
- .setTitle("Unlock CloudStream")
- //.setSubtitle("Log in using your biometric credential")
- //.setNegativeButtonText("Use account password")
- .setAllowedAuthenticators(BIOMETRIC_WEAK or BIOMETRIC_STRONG or DEVICE_CREDENTIAL)
- .build()
-
- } else if (context?.let { deviceHasPasswordPinLock(it) } == true) {
+ val authFlag = DEVICE_CREDENTIAL or BIOMETRIC_WEAK or BIOMETRIC_STRONG
+ promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(activity.getString(title))
+ .setDescription(description)
+ .setAllowedAuthenticators(authFlag)
+ .build()
+ } else {
+ @Suppress("DEPRECATION")
+ promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(activity.getString(title))
+ .setDescription(description)
+ .setDeviceCredentialAllowed(true)
+ .build()
+ }
+ } else {
+ // fallback for A12+ when both fingerprint & Face unlock is absent but PIN is set
@Suppress("DEPRECATION")
promptInfo = BiometricPrompt.PromptInfo.Builder()
- .setTitle("Unlock CloudStream")
+ .setTitle(activity.getString(title))
+ .setDescription(description)
.setDeviceCredentialAllowed(true)
.build()
}
}
- fun checkBiometricAvailability() {
+ private fun isBiometricHardWareAvailable(): Boolean {
+ var result = false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-
- // Strong and device credential bundle cannot be checked at same time in API < A11 (R)
- when (biometricManager.canAuthenticate(BIOMETRIC_WEAK or BIOMETRIC_STRONG or DEVICE_CREDENTIAL)) {
- BiometricManager.BIOMETRIC_SUCCESS ->
- Log.d(TAG, "App can authenticate.")
-
- BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
- Log.d(TAG, "No biometric sensor found.")
-
- BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE ->
- Log.d(TAG, "Biometric authentication is currently unavailable.")
-
- BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ->
- showToast(R.string.biometric_not_enrolled, Toast.LENGTH_LONG)
-
- BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED ->
- showToast(R.string.biometric_update_required, Toast.LENGTH_LONG)
-
- BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED ->
- showToast(R.string.biometric_unsupported, Toast.LENGTH_SHORT)
-
- BiometricManager.BIOMETRIC_STATUS_UNKNOWN ->
- Log.e(TAG, "Unknown error encountered while authenticating fingerprint.")
+ when (biometricManager.canAuthenticate(
+ DEVICE_CREDENTIAL or BIOMETRIC_STRONG or BIOMETRIC_WEAK
+ )) {
+ BiometricManager.BIOMETRIC_SUCCESS -> result = true
+ BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false
+ BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false
+ BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true
+ BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true
+ BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false
+ }
+ } else {
+ @Suppress("DEPRECATION")
+ when (biometricManager.canAuthenticate()) {
+ BiometricManager.BIOMETRIC_SUCCESS -> result = true
+ BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false
+ BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false
+ BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false
+ BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true
+ BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true
+ BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false
}
}
- else {
+ return result
+ }
- when (biometricManager.canAuthenticate(BIOMETRIC_WEAK or BIOMETRIC_STRONG)) {
+ // only needed for Android 9 and below
+ fun deviceHasPasswordPinLock(context: Context?): Boolean {
+ val keyMgr =
+ context?.getSystemService(AppCompatActivity.KEYGUARD_SERVICE) as KeyguardManager
+ return keyMgr.isKeyguardSecure
+ }
- BiometricManager.BIOMETRIC_SUCCESS ->
- Log.d(TAG, "App can authenticate using biometrics.")
+ fun startBiometricAuthentication(activity: Activity, title: Int, setDeviceCred: Boolean) {
+ initializeBiometrics(activity)
- BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
- Log.d(TAG, "No biometric features available on this device.")
+ if (isBiometricHardWareAvailable()) {
+ authCallback = activity as? BiometricAuthCallback
+ initBiometricPrompt(activity, title, setDeviceCred)
+ biometricPrompt.authenticate(promptInfo)
- BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE ->
- Log.e(TAG, "Biometric features are currently unavailable.")
-
- BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ->
- showToast(R.string.biometric_not_enrolled, Toast.LENGTH_LONG)
-
- BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED ->
- showToast(R.string.biometric_update_required, Toast.LENGTH_LONG)
-
- BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED ->
- showToast(R.string.biometric_unsupported, Toast.LENGTH_SHORT)
-
- BiometricManager.BIOMETRIC_STATUS_UNKNOWN ->
- Log.d(TAG, "Unknown error encountered while authenticating fingerprint.")
+ } else {
+ if (deviceHasPasswordPinLock(activity)) {
+ authCallback = activity as? BiometricAuthCallback
+ initBiometricPrompt(activity, R.string.password_pin_authentication_title, true)
+ biometricPrompt.authenticate(promptInfo)
+ } else {
+ showToast(R.string.biometric_unsupported, LENGTH_SHORT)
}
}
}
- // yes, this feature is phone exclusive
- fun isTruePhone(): Boolean {
- return !isTrueTvSettings() && !isTvSettings() && context?.isEmulatorSettings() != true
- }
-
- fun unblockMain() {
- authCallback?.onAuthenticationSuccess()
- }
-
- private fun deviceHasPasswordPinLock(con: Context): Boolean {
- val keyManager = con.getSystemService(AppCompatActivity.KEYGUARD_SERVICE) as KeyguardManager
- return keyManager.isKeyguardSecure
- }
-
interface BiometricAuthCallback {
fun onAuthenticationSuccess()
}
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt
index 76142f72..6e925d15 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt
@@ -532,7 +532,6 @@ object UIHelper {
WindowInsetsControllerCompat(window, View(this)).show(WindowInsetsCompat.Type.systemBars())
} else {*/ /** WINDOW COMPAT IS BUGGY DUE TO FU*KED UP PLAYER AND TRAILERS **/
- Suppress("DEPRECATION")
window.decorView.systemUiVisibility =
(View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN)
//}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e6aa10c8..79b3c813 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -745,13 +745,13 @@
Enable automatic switching of screen orientation based on video orientation
Auto rotate
+
+ Unlock CloudStream
Fingerprint sensor authentication
- Fingerprint authentication succeeded
- Fingerprint authentication failed
- Unlock CloudStream
- Please add fingerprint or screen lock or disable setting
- Please update software and security patches.
- Biometric authentication is not supported on this device
biometric_key
+ Password/PIN Authentication
+ Biometric authentication is not supported on this device
+ Please disable fingerprint authentication if device has no lock.
+ This window will close after few failed attempts. You\'ll have to restart the App.