Fingerprint/Biometric Unlock Support

Version 0.9.3 set
This commit is contained in:
Jay 2019-12-17 23:12:05 -08:00
parent 5448a16b35
commit 51e735a9b6
12 changed files with 194 additions and 17 deletions

View File

@ -4,7 +4,7 @@ Tachiyomi is a free and open source manga reader for Android.
![screenshots of app](./.github/readme-images/theming-screenshots.gif) ![screenshots of app](./.github/readme-images/theming-screenshots.gif)
## Newest Release ## Newest Release
[v0.9.2](https://github.com/Jays2Kings/tachiyomi/releases) [v0.9.3](https://github.com/Jays2Kings/tachiyomi/releases)
## Features ## Features

View File

@ -38,8 +38,8 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 44 versionCode 45
versionName '0.9.2' versionName '0.9.3'
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\"" buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\"" buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
@ -115,10 +115,11 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.0.0' implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.preference:preference:1.1.0' implementation 'androidx.preference:preference:1.1.0'
implementation 'androidx.annotation:annotation:1.1.0' implementation 'androidx.annotation:annotation:1.1.0'
implementation 'androidx.browser:browser:1.0.0' implementation 'androidx.browser:browser:1.0.0'
implementation 'androidx.biometric:biometric:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
@ -126,6 +127,10 @@ dependencies {
standardImplementation 'com.google.firebase:firebase-core:17.2.1' standardImplementation 'com.google.firebase:firebase-core:17.2.1'
final lifecycle_version = "2.1.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
// ReactiveX // ReactiveX
implementation 'io.reactivex:rxandroid:1.2.1' implementation 'io.reactivex:rxandroid:1.2.1'
implementation 'io.reactivex:rxjava:1.3.8' implementation 'io.reactivex:rxjava:1.3.8'
@ -245,7 +250,7 @@ dependencies {
} }
buildscript { buildscript {
ext.kotlin_version = '1.3.50' ext.kotlin_version = '1.3.61'
repositories { repositories {
mavenCentral() mavenCentral()
} }

View File

@ -50,6 +50,8 @@
<activity <activity
android:name=".ui.manga.info.WebViewActivity" android:name=".ui.manga.info.WebViewActivity"
android:configChanges="uiMode|orientation|screenSize"/> android:configChanges="uiMode|orientation|screenSize"/>
<activity
android:name=".ui.main.BiometricActivity" />
<activity <activity
android:name=".widget.CustomLayoutPickerActivity" android:name=".widget.CustomLayoutPickerActivity"
android:label="@string/app_name" android:label="@string/app_name"

View File

@ -3,18 +3,26 @@ package eu.kanade.tachiyomi
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.res.Configuration 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 androidx.multidex.MultiDex
import com.evernote.android.job.JobManager import com.evernote.android.job.JobManager
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.notification.Notifications 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.data.updater.UpdaterJob
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.LocaleHelper import eu.kanade.tachiyomi.util.LocaleHelper
import org.acra.ACRA import org.acra.ACRA
import org.acra.annotation.ReportsCrashes import org.acra.annotation.ReportsCrashes
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.InjektScope import uy.kohesive.injekt.api.InjektScope
import uy.kohesive.injekt.injectLazy
import uy.kohesive.injekt.registry.default.DefaultRegistrar import uy.kohesive.injekt.registry.default.DefaultRegistrar
@ReportsCrashes( @ReportsCrashes(
@ -24,7 +32,7 @@ import uy.kohesive.injekt.registry.default.DefaultRegistrar
buildConfigClass = BuildConfig::class, buildConfigClass = BuildConfig::class,
excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*", ".*token.*") excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*", ".*token.*")
) )
open class App : Application() { open class App : Application(), LifecycleObserver {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -38,6 +46,16 @@ open class App : Application() {
setupNotificationChannels() setupNotificationChannels()
LocaleHelper.updateConfiguration(this, resources.configuration) LocaleHelper.updateConfiguration(this, resources.configuration)
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onAppBackgrounded() {
//App in background
val preferences: PreferencesHelper by injectLazy()
if (preferences.lockAfter().getOrDefault() >= 0) {
MainActivity.unlocked = false
}
} }
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {

View File

@ -117,6 +117,12 @@ object PreferenceKeys {
const val downloadBadge = "display_download_badge" const val downloadBadge = "display_download_badge"
const val useBiometrics = "use_biometrics"
const val lockAfter = "lock_after"
const val lastUnlock = "last_unlock"
@Deprecated("Use the preferences of the source") @Deprecated("Use the preferences of the source")
fun sourceUsername(sourceId: Long) = "pref_source_username_$sourceId" fun sourceUsername(sourceId: Long) = "pref_source_username_$sourceId"

View File

@ -176,6 +176,12 @@ class PreferencesHelper(val context: Context) {
fun skipRead() = prefs.getBoolean(Keys.skipRead, false) fun skipRead() = prefs.getBoolean(Keys.skipRead, false)
fun useBiometrics() = rxPrefs.getBoolean(Keys.useBiometrics, false)
fun lockAfter() = rxPrefs.getInteger(Keys.lockAfter, 0)
fun lastUnlock() = rxPrefs.getLong(Keys.lastUnlock, 0)
fun migrateFlags() = rxPrefs.getInteger("migrate_flags", Int.MAX_VALUE) fun migrateFlags() = rxPrefs.getInteger("migrate_flags", Int.MAX_VALUE)
fun trustedSignatures() = rxPrefs.getStringSet("trusted_signatures", emptySet()) fun trustedSignatures() = rxPrefs.getStringSet("trusted_signatures", emptySet())

View File

@ -0,0 +1,47 @@
package eu.kanade.tachiyomi.ui.main
import android.os.Bundle
import androidx.biometric.BiometricPrompt
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import uy.kohesive.injekt.injectLazy
import java.util.Date
import java.util.concurrent.Executors
class BiometricActivity : BaseActivity() {
val executor = Executors.newSingleThreadExecutor()
val preferences: PreferencesHelper by injectLazy()
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)
MainActivity.unlocked = true
preferences.lastUnlock().set(Date().time)
finish()
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
// TODO("Called when a biometric is valid but not recognized.")
}
})
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.unlock_library))
.setNegativeButtonText(getString(android.R.string.cancel))
.build()
biometricPrompt.authenticate(promptInfo)
}
}

View File

@ -16,6 +16,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.biometric.BiometricManager
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import com.bluelinelabs.conductor.* import com.bluelinelabs.conductor.*
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -23,6 +24,7 @@ import eu.kanade.tachiyomi.Migrations
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
import eu.kanade.tachiyomi.ui.base.controller.* import eu.kanade.tachiyomi.ui.base.controller.*
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
@ -46,6 +48,7 @@ import eu.kanade.tachiyomi.util.updatePaddingRelative
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.Date
class MainActivity : BaseActivity() { class MainActivity : BaseActivity() {
@ -60,7 +63,6 @@ class MainActivity : BaseActivity() {
private var snackBar:Snackbar? = null private var snackBar:Snackbar? = null
var extraViewForUndo:View? = null var extraViewForUndo:View? = null
private var canDismissSnackBar = false private var canDismissSnackBar = false
fun setUndoSnackBar(snackBar: Snackbar?, extraViewToCheck: View? = null) { fun setUndoSnackBar(snackBar: Snackbar?, extraViewToCheck: View? = null) {
this.snackBar = snackBar this.snackBar = snackBar
canDismissSnackBar = false canDismissSnackBar = false
@ -248,6 +250,22 @@ class MainActivity : BaseActivity() {
} }
} }
override fun onResume() {
super.onResume()
val useBiometrics = preferences.useBiometrics().getOrDefault()
if (useBiometrics && BiometricManager.from(this)
.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
if (!unlocked && (preferences.lockAfter().getOrDefault() <= 0 || Date().time >=
preferences.lastUnlock().getOrDefault() + 60 * 1000 * preferences.lockAfter().getOrDefault())) {
val intent = Intent(this, BiometricActivity::class.java)
startActivity(intent)
this.overridePendingTransition(0, 0)
}
}
else if (useBiometrics)
preferences.useBiometrics().set(false)
}
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {
if (!handleIntentAction(intent)) { if (!handleIntentAction(intent)) {
super.onNewIntent(intent) super.onNewIntent(intent)
@ -314,6 +332,7 @@ class MainActivity : BaseActivity() {
} else if (backstackSize == 1 && router.getControllerWithTag("$startScreenId") == null) { } else if (backstackSize == 1 && router.getControllerWithTag("$startScreenId") == null) {
setSelectedDrawerItem(startScreenId) setSelectedDrawerItem(startScreenId)
} else if (backstackSize == 1 || !router.handleBack()) { } else if (backstackSize == 1 || !router.handleBack()) {
unlocked = false
super.onBackPressed() super.onBackPressed()
} }
} }
@ -412,6 +431,8 @@ class MainActivity : BaseActivity() {
const val INTENT_SEARCH_FILTER = "filter" const val INTENT_SEARCH_FILTER = "filter"
private const val URL_HELP = "https://tachiyomi.org/help/" private const val URL_HELP = "https://tachiyomi.org/help/"
var unlocked = false
} }
} }

View File

@ -11,15 +11,19 @@ import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import com.google.android.material.bottomsheet.BottomSheetDialog import android.view.KeyEvent
import android.view.* import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.view.animation.Animation import android.view.animation.Animation
import android.view.animation.AnimationUtils import android.view.animation.AnimationUtils
import android.widget.LinearLayout
import android.widget.SeekBar import android.widget.SeekBar
import androidx.biometric.BiometricManager
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.android.material.bottomsheet.BottomSheetDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
@ -27,6 +31,8 @@ import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
import eu.kanade.tachiyomi.ui.main.BiometricActivity
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success
@ -38,12 +44,17 @@ 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.R2LPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.util.GLUtil
import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.util.getUriCompat
import eu.kanade.tachiyomi.util.gone
import eu.kanade.tachiyomi.util.launchUI
import eu.kanade.tachiyomi.util.plusAssign
import eu.kanade.tachiyomi.util.toast
import eu.kanade.tachiyomi.util.visible
import eu.kanade.tachiyomi.widget.SimpleAnimationListener import eu.kanade.tachiyomi.widget.SimpleAnimationListener
import eu.kanade.tachiyomi.widget.SimpleSeekBarListener import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
import kotlinx.android.synthetic.main.reader_activity.* import kotlinx.android.synthetic.main.reader_activity.*
import kotlinx.android.synthetic.main.reader_activity.toolbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import me.zhanghai.android.systemuihelper.SystemUiHelper import me.zhanghai.android.systemuihelper.SystemUiHelper
@ -55,6 +66,7 @@ import rx.subscriptions.CompositeSubscription
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
@ -506,6 +518,23 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>(),
presenter.shareImage(page) presenter.shareImage(page)
} }
override fun onResume() {
super.onResume()
val useBiometrics = preferences.useBiometrics().getOrDefault()
if (useBiometrics && BiometricManager.from(this)
.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
if (!MainActivity.unlocked && (preferences.lockAfter().getOrDefault() <= 0 || Date()
.time >=
preferences.lastUnlock().getOrDefault() + 60 * 1000 * preferences.lockAfter().getOrDefault())) {
val intent = Intent(this, BiometricActivity::class.java)
startActivity(intent)
this.overridePendingTransition(0, 0)
}
}
else if (useBiometrics)
preferences.useBiometrics().set(false)
}
/** /**
* Called from the presenter when a page is ready to be shared. It shows Android's default * Called from the presenter when a page is ready to be shared. It shows Android's default
* sharing tool. * sharing tool.

View File

@ -1,12 +1,11 @@
package eu.kanade.tachiyomi.ui.setting package eu.kanade.tachiyomi.ui.setting
import android.app.Dialog import android.app.Dialog
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.PreferenceScreen
import android.view.View import android.view.View
import androidx.biometric.BiometricManager
import androidx.preference.PreferenceScreen
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -16,6 +15,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.LocaleHelper import eu.kanade.tachiyomi.util.LocaleHelper
import eu.kanade.tachiyomi.widget.preference.IntListPreference
import kotlinx.android.synthetic.main.pref_library_columns.view.* import kotlinx.android.synthetic.main.pref_library_columns.view.*
import rx.Observable import rx.Observable
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -199,6 +199,36 @@ class SettingsGeneralController : SettingsController() {
true true
} }
} }
val biometricManager = BiometricManager.from(context)
if (biometricManager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
var preference:IntListPreference? = null
switchPreference {
key = Keys.useBiometrics
titleRes = R.string.lock_with_biometrics
defaultValue = false
onChange {
preference?.isVisible = it as Boolean
true
}
}
preference = intListPreference {
key = Keys.lockAfter
titleRes = R.string.lock_when_idle
isVisible = preferences.useBiometrics().getOrDefault()
val values = arrayOf("0", "2", "5", "10", "20", "30", "60", "90", "120", "-1")
entries = values.map {
when (it) {
"0" -> context.getString(R.string.lock_always)
"-1" -> context.getString(R.string.lock_never)
else -> context.getString(R.string.lock_after_mins, it)
}
}.toTypedArray()
entryValues = values
defaultValue = "0"
summary = "%s"
}
}
} }
class LibraryColumnsDialog : DialogController() { class LibraryColumnsDialog : DialogController() {

View File

@ -1,5 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<changelog bulletedList="true"> <changelog bulletedList="true">
<changelogversion versionName="v0.9.3" changeDate="">
<changelogtext>Lock Tachiyomi using your fingerprint/Biometrics</changelogtext>
<changelogtext>Added search/sorting/mass enable/disable to catalouge sources</changelogtext>
<changelogtext>Extensions are now filtered to your locale, with an option to show other languages</changelogtext>
<changelogtext>Fixed AMOLED theme not having dark snackbar</changelogtext>
</changelogversion>
<changelogversion versionName="v0.9.2" changeDate=""> <changelogversion versionName="v0.9.2" changeDate="">
<changelogtext>Fixes notification text when there are multiple chapters</changelogtext> <changelogtext>Fixes notification text when there are multiple chapters</changelogtext>
<changelogtext>Simplified errors now show after restoring a backup on the popup dialog</changelogtext> <changelogtext>Simplified errors now show after restoring a backup on the popup dialog</changelogtext>

View File

@ -27,6 +27,7 @@
<string name="label_extensions">Extensions</string> <string name="label_extensions">Extensions</string>
<string name="label_extension_info">Extension info</string> <string name="label_extension_info">Extension info</string>
<string name="label_help">Help</string> <string name="label_help">Help</string>
<string name="unlock_library">Unlock to access Library</string>
<!-- Actions --> <!-- Actions -->
@ -161,6 +162,11 @@
<string name="system_default">System default</string> <string name="system_default">System default</string>
<string name="default_category">Default category</string> <string name="default_category">Default category</string>
<string name="default_category_summary">Always ask</string> <string name="default_category_summary">Always ask</string>
<string name="lock_with_biometrics">Lock with biometrics</string>
<string name="lock_when_idle">Lock when idle</string>
<string name="lock_always">Always</string>
<string name="lock_never">Never</string>
<string name="lock_after_mins">After %1$s minutes</string>
<!-- Extension section --> <!-- Extension section -->
<string name="all_lang">All</string> <string name="all_lang">All</string>