mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	Biometrics lock (closes #1686)
This commit is contained in:
		@@ -3,18 +3,26 @@ package eu.kanade.tachiyomi
 | 
			
		||||
import android.app.Application
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.content.res.Configuration
 | 
			
		||||
import androidx.lifecycle.Lifecycle
 | 
			
		||||
import androidx.lifecycle.LifecycleObserver
 | 
			
		||||
import androidx.lifecycle.OnLifecycleEvent
 | 
			
		||||
import androidx.lifecycle.ProcessLifecycleOwner
 | 
			
		||||
import androidx.multidex.MultiDex
 | 
			
		||||
import com.evernote.android.job.JobManager
 | 
			
		||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
 | 
			
		||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 | 
			
		||||
import eu.kanade.tachiyomi.data.notification.Notifications
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
import eu.kanade.tachiyomi.data.updater.UpdaterJob
 | 
			
		||||
import eu.kanade.tachiyomi.ui.security.BiometricUnlockDelegate
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
 | 
			
		||||
import org.acra.ACRA
 | 
			
		||||
import org.acra.annotation.ReportsCrashes
 | 
			
		||||
import timber.log.Timber
 | 
			
		||||
import uy.kohesive.injekt.Injekt
 | 
			
		||||
import uy.kohesive.injekt.api.InjektScope
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
 | 
			
		||||
 | 
			
		||||
@ReportsCrashes(
 | 
			
		||||
@@ -24,7 +32,7 @@ import uy.kohesive.injekt.registry.default.DefaultRegistrar
 | 
			
		||||
        buildConfigClass = BuildConfig::class,
 | 
			
		||||
        excludeMatchingSharedPreferencesKeys = [".*username.*", ".*password.*", ".*token.*"]
 | 
			
		||||
)
 | 
			
		||||
open class App : Application() {
 | 
			
		||||
open class App : Application(), LifecycleObserver {
 | 
			
		||||
 | 
			
		||||
    override fun onCreate() {
 | 
			
		||||
        super.onCreate()
 | 
			
		||||
@@ -38,6 +46,8 @@ open class App : Application() {
 | 
			
		||||
        setupNotificationChannels()
 | 
			
		||||
 | 
			
		||||
        LocaleHelper.updateConfiguration(this, resources.configuration)
 | 
			
		||||
 | 
			
		||||
        ProcessLifecycleOwner.get().lifecycle.addObserver(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun attachBaseContext(base: Context) {
 | 
			
		||||
@@ -50,6 +60,14 @@ open class App : Application() {
 | 
			
		||||
        LocaleHelper.updateConfiguration(this, newConfig, true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
 | 
			
		||||
    fun onAppBackgrounded() {
 | 
			
		||||
        val preferences: PreferencesHelper by injectLazy()
 | 
			
		||||
        if (preferences.lockAppAfter().getOrDefault() >= 0) {
 | 
			
		||||
            BiometricUnlockDelegate.locked = true
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected open fun setupAcra() {
 | 
			
		||||
        ACRA.init(this)
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -105,6 +105,12 @@ object PreferenceKeys {
 | 
			
		||||
 | 
			
		||||
    const val startScreen = "start_screen"
 | 
			
		||||
 | 
			
		||||
    const val useBiometricLock = "use_biometric_lock"
 | 
			
		||||
 | 
			
		||||
    const val lockAppAfter = "lock_app_after"
 | 
			
		||||
 | 
			
		||||
    const val lastAppUnlock = "last_app_unlock"
 | 
			
		||||
 | 
			
		||||
    const val downloadNew = "download_new"
 | 
			
		||||
 | 
			
		||||
    const val downloadNewCategories = "download_new_categories"
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,12 @@ class PreferencesHelper(val context: Context) {
 | 
			
		||||
 | 
			
		||||
    fun startScreen() = prefs.getInt(Keys.startScreen, 1)
 | 
			
		||||
 | 
			
		||||
    fun useBiometricLock() = rxPrefs.getBoolean(Keys.useBiometricLock, false)
 | 
			
		||||
 | 
			
		||||
    fun lockAppAfter() = rxPrefs.getInteger(Keys.lockAppAfter, 0)
 | 
			
		||||
 | 
			
		||||
    fun lastAppUnlock() = rxPrefs.getLong(Keys.lastAppUnlock, 0)
 | 
			
		||||
 | 
			
		||||
    fun clear() = prefs.edit().clear().apply()
 | 
			
		||||
 | 
			
		||||
    fun themeMode() = rxPrefs.getString(Keys.themeMode, Values.THEME_MODE_SYSTEM)
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ import androidx.appcompat.app.AppCompatDelegate
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
import eu.kanade.tachiyomi.ui.security.BiometricUnlockDelegate
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues as Values
 | 
			
		||||
@@ -46,4 +47,9 @@ abstract class BaseActivity : AppCompatActivity() {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onResume() {
 | 
			
		||||
        super.onResume()
 | 
			
		||||
        BiometricUnlockDelegate.onResume(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.L2RPagerViewer
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
 | 
			
		||||
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
 | 
			
		||||
import eu.kanade.tachiyomi.ui.security.BiometricUnlockDelegate
 | 
			
		||||
import eu.kanade.tachiyomi.util.lang.plusAssign
 | 
			
		||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
 | 
			
		||||
import eu.kanade.tachiyomi.util.system.GLUtil
 | 
			
		||||
@@ -149,6 +150,11 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
 | 
			
		||||
        initializeMenu()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onResume() {
 | 
			
		||||
        super.onResume()
 | 
			
		||||
        BiometricUnlockDelegate.onResume(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the activity is destroyed. Cleans up the viewer, configuration and any view.
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.security
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import androidx.biometric.BiometricPrompt
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.util.Date
 | 
			
		||||
import java.util.concurrent.Executors
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Blank activity with a BiometricPrompt.
 | 
			
		||||
 */
 | 
			
		||||
class BiometricUnlockActivity : AppCompatActivity() {
 | 
			
		||||
 | 
			
		||||
    private val preferences: PreferencesHelper by injectLazy()
 | 
			
		||||
    private val executor = Executors.newSingleThreadExecutor()
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        val biometricPrompt = BiometricPrompt(this, executor, object : BiometricPrompt.AuthenticationCallback() {
 | 
			
		||||
            override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
 | 
			
		||||
                super.onAuthenticationError(errorCode, errString)
 | 
			
		||||
                finishAffinity()
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
 | 
			
		||||
                super.onAuthenticationSucceeded(result)
 | 
			
		||||
                BiometricUnlockDelegate.locked = false
 | 
			
		||||
                preferences.lastAppUnlock().set(Date().time)
 | 
			
		||||
                finish()
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        val promptInfo = BiometricPrompt.PromptInfo.Builder()
 | 
			
		||||
                .setTitle(getString(R.string.unlock_library))
 | 
			
		||||
                .setDeviceCredentialAllowed(true)
 | 
			
		||||
                .build()
 | 
			
		||||
 | 
			
		||||
        biometricPrompt.authenticate(promptInfo)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,36 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.security
 | 
			
		||||
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import androidx.biometric.BiometricManager
 | 
			
		||||
import androidx.fragment.app.FragmentActivity
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.util.Date
 | 
			
		||||
 | 
			
		||||
object BiometricUnlockDelegate {
 | 
			
		||||
 | 
			
		||||
    private val preferences by injectLazy<PreferencesHelper>()
 | 
			
		||||
 | 
			
		||||
    var locked: Boolean = true
 | 
			
		||||
 | 
			
		||||
    fun onResume(activity: FragmentActivity) {
 | 
			
		||||
        val lockApp = preferences.useBiometricLock().getOrDefault()
 | 
			
		||||
        if (lockApp && BiometricManager.from(activity).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
 | 
			
		||||
            if (isAppLocked()) {
 | 
			
		||||
                val intent = Intent(activity, BiometricUnlockActivity::class.java)
 | 
			
		||||
                activity.startActivity(intent)
 | 
			
		||||
                activity.overridePendingTransition(0, 0)
 | 
			
		||||
            }
 | 
			
		||||
        } else if (lockApp) {
 | 
			
		||||
            preferences.useBiometricLock().set(false)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun isAppLocked(): Boolean {
 | 
			
		||||
        return locked &&
 | 
			
		||||
                (preferences.lockAppAfter().getOrDefault() <= 0
 | 
			
		||||
                        || Date().time >= preferences.lastAppUnlock().getOrDefault() + 60 * 1000 * preferences.lockAppAfter().getOrDefault())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package eu.kanade.tachiyomi.ui.setting
 | 
			
		||||
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import androidx.biometric.BiometricManager
 | 
			
		||||
import androidx.preference.PreferenceScreen
 | 
			
		||||
import eu.kanade.tachiyomi.R
 | 
			
		||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
 | 
			
		||||
@@ -115,6 +116,36 @@ class SettingsGeneralController : SettingsController() {
 | 
			
		||||
            defaultValue = "1"
 | 
			
		||||
            summary = "%s"
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (BiometricManager.from(context).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
 | 
			
		||||
            preferenceCategory {
 | 
			
		||||
                titleRes = R.string.pref_category_security
 | 
			
		||||
 | 
			
		||||
                switchPreference {
 | 
			
		||||
                    key = Keys.useBiometricLock
 | 
			
		||||
                    titleRes = R.string.lock_with_biometrics
 | 
			
		||||
                    defaultValue = false
 | 
			
		||||
                }
 | 
			
		||||
                intListPreference {
 | 
			
		||||
                    key = Keys.lockAppAfter
 | 
			
		||||
                    titleRes = R.string.lock_when_idle
 | 
			
		||||
                    val values = arrayOf("0", "1", "2", "5", "10", "-1")
 | 
			
		||||
                    entries = values.mapNotNull {
 | 
			
		||||
                        when (it) {
 | 
			
		||||
                            "-1" -> context.getString(R.string.lock_never)
 | 
			
		||||
                            "0" -> context.getString(R.string.lock_always)
 | 
			
		||||
                            else -> resources?.getQuantityString(R.plurals.lock_after_mins, it.toInt(), it)
 | 
			
		||||
                        }
 | 
			
		||||
                    }.toTypedArray()
 | 
			
		||||
                    entryValues = values
 | 
			
		||||
                    defaultValue = "0"
 | 
			
		||||
                    summary = "%s"
 | 
			
		||||
 | 
			
		||||
                    preferences.useBiometricLock().asObservable()
 | 
			
		||||
                            .subscribeUntilDestroy { isVisible = it }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user