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.