Skip to content

Commit 51e735a

Browse files
committed
Fingerprint/Biometric Unlock Support
Version 0.9.3 set
1 parent 5448a16 commit 51e735a

12 files changed

Lines changed: 194 additions & 17 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Tachiyomi is a free and open source manga reader for Android.
44
![screenshots of app](./.github/readme-images/theming-screenshots.gif)
55

66
## Newest Release
7-
[v0.9.2](https://github.com/Jays2Kings/tachiyomi/releases)
7+
[v0.9.3](https://github.com/Jays2Kings/tachiyomi/releases)
88

99
## Features
1010

app/build.gradle

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ android {
3838
minSdkVersion 21
3939
targetSdkVersion 29
4040
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
41-
versionCode 44
42-
versionName '0.9.2'
41+
versionCode 45
42+
versionName '0.9.3'
4343

4444
buildConfigField "String", "COMMIT_COUNT", "\"${getCommitCount()}\""
4545
buildConfigField "String", "COMMIT_SHA", "\"${getGitSha()}\""
@@ -115,17 +115,22 @@ dependencies {
115115
implementation 'androidx.appcompat:appcompat:1.1.0'
116116
implementation 'androidx.cardview:cardview:1.0.0'
117117
implementation 'com.google.android.material:material:1.0.0'
118-
implementation 'androidx.recyclerview:recyclerview:1.0.0'
118+
implementation 'androidx.recyclerview:recyclerview:1.1.0'
119119
implementation 'androidx.preference:preference:1.1.0'
120120
implementation 'androidx.annotation:annotation:1.1.0'
121121
implementation 'androidx.browser:browser:1.0.0'
122+
implementation 'androidx.biometric:biometric:1.0.0'
122123

123124
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
124125

125126
implementation 'androidx.multidex:multidex:2.0.1'
126127

127128
standardImplementation 'com.google.firebase:firebase-core:17.2.1'
128129

130+
final lifecycle_version = "2.1.0"
131+
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
132+
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
133+
129134
// ReactiveX
130135
implementation 'io.reactivex:rxandroid:1.2.1'
131136
implementation 'io.reactivex:rxjava:1.3.8'
@@ -245,7 +250,7 @@ dependencies {
245250
}
246251

247252
buildscript {
248-
ext.kotlin_version = '1.3.50'
253+
ext.kotlin_version = '1.3.61'
249254
repositories {
250255
mavenCentral()
251256
}

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
<activity
5151
android:name=".ui.manga.info.WebViewActivity"
5252
android:configChanges="uiMode|orientation|screenSize"/>
53+
<activity
54+
android:name=".ui.main.BiometricActivity" />
5355
<activity
5456
android:name=".widget.CustomLayoutPickerActivity"
5557
android:label="@string/app_name"

app/src/main/java/eu/kanade/tachiyomi/App.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,26 @@ package eu.kanade.tachiyomi
33
import android.app.Application
44
import android.content.Context
55
import android.content.res.Configuration
6+
import androidx.lifecycle.Lifecycle
7+
import androidx.lifecycle.LifecycleObserver
8+
import androidx.lifecycle.OnLifecycleEvent
9+
import androidx.lifecycle.ProcessLifecycleOwner
610
import androidx.multidex.MultiDex
711
import com.evernote.android.job.JobManager
812
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
913
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
1014
import eu.kanade.tachiyomi.data.notification.Notifications
15+
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
16+
import eu.kanade.tachiyomi.data.preference.getOrDefault
1117
import eu.kanade.tachiyomi.data.updater.UpdaterJob
18+
import eu.kanade.tachiyomi.ui.main.MainActivity
1219
import eu.kanade.tachiyomi.util.LocaleHelper
1320
import org.acra.ACRA
1421
import org.acra.annotation.ReportsCrashes
1522
import timber.log.Timber
1623
import uy.kohesive.injekt.Injekt
1724
import uy.kohesive.injekt.api.InjektScope
25+
import uy.kohesive.injekt.injectLazy
1826
import uy.kohesive.injekt.registry.default.DefaultRegistrar
1927

2028
@ReportsCrashes(
@@ -24,7 +32,7 @@ import uy.kohesive.injekt.registry.default.DefaultRegistrar
2432
buildConfigClass = BuildConfig::class,
2533
excludeMatchingSharedPreferencesKeys = arrayOf(".*username.*", ".*password.*", ".*token.*")
2634
)
27-
open class App : Application() {
35+
open class App : Application(), LifecycleObserver {
2836

2937
override fun onCreate() {
3038
super.onCreate()
@@ -38,6 +46,16 @@ open class App : Application() {
3846
setupNotificationChannels()
3947

4048
LocaleHelper.updateConfiguration(this, resources.configuration)
49+
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
50+
}
51+
52+
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
53+
fun onAppBackgrounded() {
54+
//App in background
55+
val preferences: PreferencesHelper by injectLazy()
56+
if (preferences.lockAfter().getOrDefault() >= 0) {
57+
MainActivity.unlocked = false
58+
}
4159
}
4260

4361
override fun attachBaseContext(base: Context) {

app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceKeys.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ object PreferenceKeys {
117117

118118
const val downloadBadge = "display_download_badge"
119119

120+
const val useBiometrics = "use_biometrics"
121+
122+
const val lockAfter = "lock_after"
123+
124+
const val lastUnlock = "last_unlock"
125+
120126
@Deprecated("Use the preferences of the source")
121127
fun sourceUsername(sourceId: Long) = "pref_source_username_$sourceId"
122128

app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ class PreferencesHelper(val context: Context) {
176176

177177
fun skipRead() = prefs.getBoolean(Keys.skipRead, false)
178178

179+
fun useBiometrics() = rxPrefs.getBoolean(Keys.useBiometrics, false)
180+
181+
fun lockAfter() = rxPrefs.getInteger(Keys.lockAfter, 0)
182+
183+
fun lastUnlock() = rxPrefs.getLong(Keys.lastUnlock, 0)
184+
179185
fun migrateFlags() = rxPrefs.getInteger("migrate_flags", Int.MAX_VALUE)
180186

181187
fun trustedSignatures() = rxPrefs.getStringSet("trusted_signatures", emptySet())
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package eu.kanade.tachiyomi.ui.main
2+
3+
import android.os.Bundle
4+
import androidx.biometric.BiometricPrompt
5+
import eu.kanade.tachiyomi.R
6+
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
7+
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
8+
import uy.kohesive.injekt.injectLazy
9+
import java.util.Date
10+
import java.util.concurrent.Executors
11+
12+
class BiometricActivity : BaseActivity() {
13+
val executor = Executors.newSingleThreadExecutor()
14+
15+
val preferences: PreferencesHelper by injectLazy()
16+
override fun onCreate(savedInstanceState: Bundle?) {
17+
super.onCreate(savedInstanceState)
18+
val biometricPrompt = BiometricPrompt(this, executor, object : BiometricPrompt
19+
.AuthenticationCallback() {
20+
21+
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
22+
super.onAuthenticationError(errorCode, errString)
23+
finishAffinity()
24+
}
25+
26+
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
27+
super.onAuthenticationSucceeded(result)
28+
MainActivity.unlocked = true
29+
preferences.lastUnlock().set(Date().time)
30+
finish()
31+
}
32+
33+
override fun onAuthenticationFailed() {
34+
super.onAuthenticationFailed()
35+
// TODO("Called when a biometric is valid but not recognized.")
36+
}
37+
})
38+
39+
val promptInfo = BiometricPrompt.PromptInfo.Builder()
40+
.setTitle(getString(R.string.unlock_library))
41+
.setNegativeButtonText(getString(android.R.string.cancel))
42+
.build()
43+
44+
biometricPrompt.authenticate(promptInfo)
45+
}
46+
47+
}

app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ import android.view.View
1616
import android.view.ViewGroup
1717
import android.widget.FrameLayout
1818
import android.widget.LinearLayout
19+
import androidx.biometric.BiometricManager
1920
import androidx.core.graphics.ColorUtils
2021
import com.bluelinelabs.conductor.*
2122
import com.google.android.material.snackbar.Snackbar
2223
import eu.kanade.tachiyomi.Migrations
2324
import eu.kanade.tachiyomi.R
2425
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
2526
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
27+
import eu.kanade.tachiyomi.data.preference.getOrDefault
2628
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
2729
import eu.kanade.tachiyomi.ui.base.controller.*
2830
import eu.kanade.tachiyomi.ui.catalogue.CatalogueController
@@ -46,6 +48,7 @@ import eu.kanade.tachiyomi.util.updatePaddingRelative
4648
import kotlinx.android.synthetic.main.main_activity.*
4749
import kotlinx.coroutines.delay
4850
import uy.kohesive.injekt.injectLazy
51+
import java.util.Date
4952

5053
class MainActivity : BaseActivity() {
5154

@@ -60,7 +63,6 @@ class MainActivity : BaseActivity() {
6063
private var snackBar:Snackbar? = null
6164
var extraViewForUndo:View? = null
6265
private var canDismissSnackBar = false
63-
6466
fun setUndoSnackBar(snackBar: Snackbar?, extraViewToCheck: View? = null) {
6567
this.snackBar = snackBar
6668
canDismissSnackBar = false
@@ -248,6 +250,22 @@ class MainActivity : BaseActivity() {
248250
}
249251
}
250252

253+
override fun onResume() {
254+
super.onResume()
255+
val useBiometrics = preferences.useBiometrics().getOrDefault()
256+
if (useBiometrics && BiometricManager.from(this)
257+
.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
258+
if (!unlocked && (preferences.lockAfter().getOrDefault() <= 0 || Date().time >=
259+
preferences.lastUnlock().getOrDefault() + 60 * 1000 * preferences.lockAfter().getOrDefault())) {
260+
val intent = Intent(this, BiometricActivity::class.java)
261+
startActivity(intent)
262+
this.overridePendingTransition(0, 0)
263+
}
264+
}
265+
else if (useBiometrics)
266+
preferences.useBiometrics().set(false)
267+
}
268+
251269
override fun onNewIntent(intent: Intent) {
252270
if (!handleIntentAction(intent)) {
253271
super.onNewIntent(intent)
@@ -314,6 +332,7 @@ class MainActivity : BaseActivity() {
314332
} else if (backstackSize == 1 && router.getControllerWithTag("$startScreenId") == null) {
315333
setSelectedDrawerItem(startScreenId)
316334
} else if (backstackSize == 1 || !router.handleBack()) {
335+
unlocked = false
317336
super.onBackPressed()
318337
}
319338
}
@@ -412,6 +431,8 @@ class MainActivity : BaseActivity() {
412431
const val INTENT_SEARCH_FILTER = "filter"
413432

414433
private const val URL_HELP = "https://tachiyomi.org/help/"
434+
435+
var unlocked = false
415436
}
416437

417438
}

app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,28 @@ import android.graphics.Bitmap
1111
import android.graphics.Color
1212
import android.os.Build
1313
import android.os.Bundle
14-
import com.google.android.material.bottomsheet.BottomSheetDialog
15-
import android.view.*
14+
import android.view.KeyEvent
15+
import android.view.Menu
16+
import android.view.MenuItem
17+
import android.view.MotionEvent
18+
import android.view.View
19+
import android.view.WindowManager
1620
import android.view.animation.Animation
1721
import android.view.animation.AnimationUtils
18-
import android.widget.LinearLayout
1922
import android.widget.SeekBar
23+
import androidx.biometric.BiometricManager
2024
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
25+
import com.google.android.material.bottomsheet.BottomSheetDialog
2126
import eu.kanade.tachiyomi.R
22-
import eu.kanade.tachiyomi.data.database.DatabaseHelper
2327
import eu.kanade.tachiyomi.data.database.models.Chapter
2428
import eu.kanade.tachiyomi.data.database.models.Manga
2529
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
2630
import eu.kanade.tachiyomi.data.notification.Notifications
2731
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
2832
import eu.kanade.tachiyomi.data.preference.getOrDefault
2933
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
34+
import eu.kanade.tachiyomi.ui.main.BiometricActivity
35+
import eu.kanade.tachiyomi.ui.main.MainActivity
3036
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.AddToLibraryFirst
3137
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Error
3238
import eu.kanade.tachiyomi.ui.reader.ReaderPresenter.SetAsCoverResult.Success
@@ -38,12 +44,17 @@ import eu.kanade.tachiyomi.ui.reader.viewer.pager.L2RPagerViewer
3844
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
3945
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
4046
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonViewer
41-
import eu.kanade.tachiyomi.util.*
47+
import eu.kanade.tachiyomi.util.GLUtil
48+
import eu.kanade.tachiyomi.util.getResourceColor
49+
import eu.kanade.tachiyomi.util.getUriCompat
50+
import eu.kanade.tachiyomi.util.gone
51+
import eu.kanade.tachiyomi.util.launchUI
52+
import eu.kanade.tachiyomi.util.plusAssign
53+
import eu.kanade.tachiyomi.util.toast
54+
import eu.kanade.tachiyomi.util.visible
4255
import eu.kanade.tachiyomi.widget.SimpleAnimationListener
4356
import eu.kanade.tachiyomi.widget.SimpleSeekBarListener
4457
import kotlinx.android.synthetic.main.reader_activity.*
45-
import kotlinx.android.synthetic.main.reader_activity.toolbar
46-
import kotlinx.coroutines.CoroutineScope
4758
import kotlinx.coroutines.Job
4859
import kotlinx.coroutines.delay
4960
import me.zhanghai.android.systemuihelper.SystemUiHelper
@@ -55,6 +66,7 @@ import rx.subscriptions.CompositeSubscription
5566
import timber.log.Timber
5667
import uy.kohesive.injekt.injectLazy
5768
import java.io.File
69+
import java.util.Date
5870
import java.util.concurrent.TimeUnit
5971

6072
/**
@@ -506,6 +518,23 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>(),
506518
presenter.shareImage(page)
507519
}
508520

521+
override fun onResume() {
522+
super.onResume()
523+
val useBiometrics = preferences.useBiometrics().getOrDefault()
524+
if (useBiometrics && BiometricManager.from(this)
525+
.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
526+
if (!MainActivity.unlocked && (preferences.lockAfter().getOrDefault() <= 0 || Date()
527+
.time >=
528+
preferences.lastUnlock().getOrDefault() + 60 * 1000 * preferences.lockAfter().getOrDefault())) {
529+
val intent = Intent(this, BiometricActivity::class.java)
530+
startActivity(intent)
531+
this.overridePendingTransition(0, 0)
532+
}
533+
}
534+
else if (useBiometrics)
535+
preferences.useBiometrics().set(false)
536+
}
537+
509538
/**
510539
* Called from the presenter when a page is ready to be shared. It shows Android's default
511540
* sharing tool.

0 commit comments

Comments
 (0)