From e3999d6e9a8f697e1b2b5f60cb7dd7380a533c1b Mon Sep 17 00:00:00 2001
From: IndusAryan <125901294+IndusAryan@users.noreply.github.com>
Date: Fri, 8 Mar 2024 06:15:20 +0530
Subject: [PATCH] feat(security): add biometric fingerprint sensor / face
unlock authentication (#826)
---
app/build.gradle.kts | 1 +
app/src/main/AndroidManifest.xml | 2 +-
.../lagradost/cloudstream3/MainActivity.kt | 41 +++-
.../ui/account/AccountSelectActivity.kt | 34 +++-
.../ui/settings/SettingsAccount.kt | 17 ++
.../ui/settings/SettingsFragment.kt | 6 +
.../cloudstream3/utils/BackupUtils.kt | 1 +
.../utils/BiometricAuthenticator.kt | 177 ++++++++++++++++++
.../lagradost/cloudstream3/utils/UIHelper.kt | 1 -
app/src/main/res/drawable/ic_fingerprint.xml | 11 ++
app/src/main/res/values/strings.xml | 15 +-
app/src/main/res/xml/settings_account.xml | 31 ++-
12 files changed, 306 insertions(+), 31 deletions(-)
create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt
create mode 100644 app/src/main/res/drawable/ic_fingerprint.xml
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 83100db8..31e225de 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -212,6 +212,7 @@ dependencies {
implementation("androidx.palette:palette-ktx:1.0.0") // Palette For Images -> Colors
implementation("androidx.tvprovider:tvprovider:1.0.0")
implementation("com.github.discord:OverlappingPanels:0.1.5") // Gestures
+ implementation ("androidx.biometric:biometric:1.2.0-alpha05") // Fingerprint Authentication
implementation("com.github.rubensousa:previewseekbar-media3:1.1.1.0") // SeekBar Preview
// Extensions & Other Libs
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e665c3bc..a23ef725 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -14,7 +14,7 @@
-
+
diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
index afb2f76f..6308117b 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt
@@ -19,6 +19,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.Toast
+import android.widget.Toast.LENGTH_LONG
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IdRes
@@ -28,6 +29,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.children
import androidx.core.view.isGone
+import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.marginStart
import androidx.fragment.app.FragmentActivity
@@ -112,6 +114,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
@@ -131,11 +134,15 @@ import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
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.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
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper
+import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
@@ -166,7 +173,6 @@ import kotlin.math.absoluteValue
import kotlin.reflect.KClass
import kotlin.system.exitProcess
-
//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898
//https://wiki.videolan.org/Android_Player_Intents/
@@ -285,7 +291,7 @@ var app = Requests(responseParser = object : ResponseParser {
defaultHeaders = mapOf("user-agent" to USER_AGENT)
}
-class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
+class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricAuthenticator.BiometricAuthCallback {
companion object {
const val TAG = "MAINACT"
const val ANIMATED_OUTLINE: Boolean = false
@@ -1171,7 +1177,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
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)
@@ -1183,14 +1189,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
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)
@@ -1204,6 +1208,25 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
changeStatusBarState(isEmulatorSettings())
+ /** Biometric stuff for users without accounts **/
+ val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_key), false)
+ val noAccounts = settingsManager.getBoolean(getString(R.string.skip_startup_account_select_key), false) || accounts.count() <= 1
+
+ if (isTruePhone() && authEnabled && noAccounts) {
+ if (deviceHasPasswordPinLock(this)) {
+ startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
+
+ BiometricAuthenticator.promptInfo?.let {
+ BiometricAuthenticator.biometricPrompt?.authenticate(it)
+ }
+
+ // hide background while authenticating, Sorry moms & dads 🙏
+ binding?.navHostFragment?.isInvisible = true
+ } else {
+ showToast(R.string.phone_not_secured, LENGTH_LONG)
+ }
+ }
+
// Automatically enable jsdelivr if cant connect to raw.githubusercontent.com
if (this.getKey(getString(R.string.jsdelivr_proxy_key)) == null && isNetworkAvailable()) {
main {
@@ -1743,6 +1766,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
)
}
+ /** Biometric stuff **/
+ override fun onAuthenticationSuccess() {
+ // make background (nav host fragment) visible again
+ binding?.navHostFragment?.isInvisible = false
+ }
+
private var backPressedCallback: OnBackPressedCallback? = null
private fun attachBackPressedCallback() {
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 23071f59..c6e1e1fe 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,13 +19,17 @@ 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
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
-class AccountSelectActivity : AppCompatActivity() {
+class AccountSelectActivity : AppCompatActivity(), BiometricAuthenticator.BiometricAuthCallback {
lateinit var viewModel: AccountViewModel
@@ -41,13 +47,27 @@ class AccountSelectActivity : AppCompatActivity() {
)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
- val skipStartup = settingsManager.getBoolean(
- getString(R.string.skip_startup_account_select_key),
- false
+ val authEnabled = settingsManager.getBoolean(getString(R.string.biometric_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 (isTruePhone() && authEnabled) {
+ if (deviceHasPasswordPinLock(this)) {
+ startBiometricAuthentication(this, R.string.biometric_authentication_title, false)
+
+ BiometricAuthenticator.promptInfo?.let {
+ BiometricAuthenticator.biometricPrompt?.authenticate(it)
+ }
+ }
+ } else {
+ showToast(R.string.phone_not_secured, Toast.LENGTH_LONG)
+ }
+ }
+
// Don't show account selection if there is only
// one account that exists
if (!isEditingFromMainActivity && skipStartup) {
@@ -158,6 +178,8 @@ class AccountSelectActivity : AppCompatActivity() {
} else 6
}
}
+
+ askBiometricAuth()
}
private fun navigateToMainActivity() {
@@ -165,4 +187,8 @@ class AccountSelectActivity : AppCompatActivity() {
startActivity(mainIntent)
finish() // Finish the account selection activity
}
+
+ override fun onAuthenticationSuccess() {
+ 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/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt
index aa5a3182..d04757da 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt
@@ -13,6 +13,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceFragmentCompat
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
+import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.AccountManagmentBinding
@@ -32,7 +33,10 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSet
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
+import com.lagradost.cloudstream3.utils.AppUtils.html
+import com.lagradost.cloudstream3.utils.BackupUtils
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
+import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
import com.lagradost.cloudstream3.utils.UIHelper.setImage
@@ -256,6 +260,19 @@ class SettingsAccount : PreferenceFragmentCompat() {
hideKeyboard()
setPreferencesFromResource(R.xml.settings_account, rootKey)
+ getPref(R.string.biometric_key)?.setOnPreferenceClickListener {
+
+ BackupUtils.backup(activity)
+ val title = activity?.getString(R.string.biometric_setting)
+ val warning = activity?.getString(R.string.biometric_warning)
+ activity?.showBottomDialogText(
+ title as String,
+ warning.html()
+ ) { onDialogDismissedEvent }
+
+ true
+ }
+
val syncApis =
listOf(
R.string.mal_key to malApi,
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/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
index e50131fe..db001ef5 100644
--- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt
@@ -66,6 +66,7 @@ object BackupUtils {
OPEN_SUBTITLES_USER_KEY,
"nginx_user", // Nginx user key
+ "biometric_key" // can lock down users if backup is shared on a incompatible device
)
/** false if blacklisted key */
diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt
new file mode 100644
index 00000000..de9b9963
--- /dev/null
+++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt
@@ -0,0 +1,177 @@
+package com.lagradost.cloudstream3.utils
+
+import android.app.Activity
+import android.app.KeyguardManager
+import android.content.Context
+import android.os.Build
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.biometric.BiometricManager
+import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
+import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
+import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
+import androidx.biometric.BiometricPrompt
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.FragmentActivity
+import com.lagradost.cloudstream3.CommonActivity.showToast
+import com.lagradost.cloudstream3.R
+
+object BiometricAuthenticator {
+
+ private const val MAX_FAILED_ATTEMPTS = 3
+ private var failedAttempts = 0
+ const val TAG = "cs3Auth"
+
+ private var biometricManager: BiometricManager? = null
+ var biometricPrompt: BiometricPrompt? = null
+ var promptInfo: BiometricPrompt.PromptInfo? = null
+
+ var authCallback: BiometricAuthCallback? = null // listen to authentication success
+
+ private fun initializeBiometrics(activity: Activity) {
+ val executor = ContextCompat.getMainExecutor(activity)
+
+ biometricManager = BiometricManager.from(activity)
+
+ biometricPrompt = BiometricPrompt(
+ activity as FragmentActivity,
+ executor,
+ object : BiometricPrompt.AuthenticationCallback() {
+
+ override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
+ super.onAuthenticationError(errorCode, errString)
+ showToast("$errString")
+ Log.e(TAG, "$errorCode")
+ failedAttempts++
+
+ if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
+ failedAttempts = 0
+ activity.finish()
+ } else {
+ failedAttempts = 0
+ activity.finish()
+ }
+ }
+
+ override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
+ super.onAuthenticationSucceeded(result)
+ failedAttempts = 0
+ authCallback?.onAuthenticationSuccess()
+ }
+
+ override fun onAuthenticationFailed() {
+ super.onAuthenticationFailed()
+ failedAttempts++
+ if (failedAttempts >= MAX_FAILED_ATTEMPTS) {
+ failedAttempts = 0
+ activity.finish()
+ }
+ }
+ })
+ }
+
+ @Suppress("DEPRECATION")
+ // authentication dialog prompt builder
+ private fun authenticationDialog(
+ 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) {
+
+ val authFlag = DEVICE_CREDENTIAL or BIOMETRIC_WEAK or BIOMETRIC_STRONG
+ promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(activity.getString(title))
+ .setDescription(description)
+ .setAllowedAuthenticators(authFlag)
+ .build()
+
+ } else {
+ // for apis < 30
+ 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
+ promptInfo = BiometricPrompt.PromptInfo.Builder()
+ .setTitle(activity.getString(title))
+ .setDescription(description)
+ .setDeviceCredentialAllowed(true)
+ .build()
+ }
+ }
+
+ private fun isBiometricHardWareAvailable(): Boolean {
+ // authentication occurs only when this is true and device is truly capable
+ var result = false
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+
+ 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
+ }
+ }
+
+ return result
+ }
+
+ // checks if device is secured i.e has at least some type of lock
+ fun deviceHasPasswordPinLock(context: Context?): Boolean {
+ val keyMgr =
+ context?.getSystemService(AppCompatActivity.KEYGUARD_SERVICE) as? KeyguardManager
+ return keyMgr?.isKeyguardSecure ?: false
+ }
+
+ // function to start authentication in any fragment or activity
+ fun startBiometricAuthentication(activity: Activity, title: Int, setDeviceCred: Boolean) {
+ initializeBiometrics(activity)
+
+ if (isBiometricHardWareAvailable()) {
+ authCallback = activity as? BiometricAuthCallback
+ authenticationDialog(activity, title, setDeviceCred)
+ promptInfo?.let { biometricPrompt?.authenticate(it) }
+
+ } else {
+ if (deviceHasPasswordPinLock(activity)) {
+ authCallback = activity as? BiometricAuthCallback
+ authenticationDialog(activity, R.string.password_pin_authentication_title, true)
+ promptInfo?.let { biometricPrompt?.authenticate(it) }
+
+ } else {
+ showToast(R.string.biometric_unsupported)
+ }
+ }
+ }
+
+ 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/drawable/ic_fingerprint.xml b/app/src/main/res/drawable/ic_fingerprint.xml
new file mode 100644
index 00000000..5c96e5a5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_fingerprint.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 14bb9552..95e5fdab 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -247,7 +247,7 @@
Error backing up %s
Search
Library
- Accounts
+ Accounts and Security
Updates and backup
Info
Advanced Search
@@ -745,4 +745,17 @@
Display a toggle button for screen orientation
Enable automatic switching of screen orientation based on video orientation
Auto rotate
+
+
+ Unlock CloudStream
+ Lock with Biometrics
+ biometric_key
+ Password/PIN Authentication
+ Biometric authentication is not supported on this device
+ Please disable fingerprint authentication if device has no lock.
+ Unlock the app with Fingerprint, Face ID, PIN, Pattern and Password.
+ This window will close after few failed attempts. You\'ll have to restart the App.
+ Your CloudStream data has been backed up now, although probability of this rare case is very low but all
+ devices behave differently, in case you get locked down from accessing the app in worst case scenario,
+ Clear the app data wholly and restore the backup. Any inconvenience if arrived is deeply regretted.
diff --git a/app/src/main/res/xml/settings_account.xml b/app/src/main/res/xml/settings_account.xml
index ec882088..5cde06c4 100644
--- a/app/src/main/res/xml/settings_account.xml
+++ b/app/src/main/res/xml/settings_account.xml
@@ -1,11 +1,5 @@
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
\ No newline at end of file