Add PinLocker to handle lock state

This commit is contained in:
ganfra 2020-07-29 20:19:01 +02:00
parent 37521d2d4f
commit bb066b7c65
11 changed files with 175 additions and 4 deletions

View file

@ -49,6 +49,7 @@ import im.vector.riotx.features.lifecycle.VectorActivityLifecycleCallbacks
import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.notifications.NotificationUtils
import im.vector.riotx.features.pin.PinCodeStore
import im.vector.riotx.features.pin.PinLocker
import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.rageshake.VectorUncaughtExceptionHandler
import im.vector.riotx.features.settings.VectorPreferences
@ -84,7 +85,7 @@ class VectorApplication :
@Inject lateinit var appStateHandler: AppStateHandler
@Inject lateinit var rxConfig: RxConfig
@Inject lateinit var popupAlertManager: PopupAlertManager
@Inject lateinit var pinCodeStore: PinCodeStore
@Inject lateinit var pinLocker: PinLocker
lateinit var vectorComponent: VectorComponent
@ -156,6 +157,7 @@ class VectorApplication :
}
})
ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler)
ProcessLifecycleOwner.get().lifecycle.addObserver(pinLocker)
// This should be done as early as possible
// initKnownEmojiHashSet(appContext)
}

View file

@ -54,6 +54,7 @@ import im.vector.riotx.features.media.ImageMediaViewerActivity
import im.vector.riotx.features.media.VideoMediaViewerActivity
import im.vector.riotx.features.navigation.Navigator
import im.vector.riotx.features.permalink.PermalinkHandlerActivity
import im.vector.riotx.features.pin.PinLocker
import im.vector.riotx.features.qrcode.QrCodeScannerActivity
import im.vector.riotx.features.rageshake.BugReportActivity
import im.vector.riotx.features.rageshake.BugReporter
@ -101,6 +102,7 @@ interface ScreenComponent {
fun bugReporter(): BugReporter
fun rageShake(): RageShake
fun navigator(): Navigator
fun pinLocker(): PinLocker
fun errorFormatter(): ErrorFormatter
fun uiStateRepository(): UiStateRepository
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog

View file

@ -49,6 +49,7 @@ import im.vector.riotx.features.notifications.NotificationDrawerManager
import im.vector.riotx.features.notifications.NotificationUtils
import im.vector.riotx.features.notifications.PushRuleTriggerListener
import im.vector.riotx.features.pin.PinCodeStore
import im.vector.riotx.features.pin.PinLocker
import im.vector.riotx.features.popup.PopupAlertManager
import im.vector.riotx.features.rageshake.BugReporter
import im.vector.riotx.features.rageshake.VectorFileLogger
@ -141,6 +142,8 @@ interface VectorComponent {
fun reAuthHelper(): ReAuthHelper
fun pinLocker(): PinLocker
fun webRtcPeerConnectionManager(): WebRtcPeerConnectionManager
@Component.Factory

View file

@ -16,7 +16,9 @@
package im.vector.riotx.core.platform
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.os.Parcelable
@ -58,6 +60,7 @@ import im.vector.riotx.core.dialogs.DialogLocker
import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog
import im.vector.riotx.core.extensions.exhaustive
import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.extensions.observeNotNull
import im.vector.riotx.core.extensions.vectorComponent
import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.MainActivity
@ -65,6 +68,10 @@ import im.vector.riotx.features.MainActivityArgs
import im.vector.riotx.features.configuration.VectorConfiguration
import im.vector.riotx.features.consent.ConsentNotGivenHelper
import im.vector.riotx.features.navigation.Navigator
import im.vector.riotx.features.pin.PinActivity
import im.vector.riotx.features.pin.PinLocker
import im.vector.riotx.features.pin.PinMode
import im.vector.riotx.features.pin.UnlockedActivity
import im.vector.riotx.features.rageshake.BugReportActivity
import im.vector.riotx.features.rageshake.BugReporter
import im.vector.riotx.features.rageshake.RageShake
@ -116,6 +123,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
private lateinit var configurationViewModel: ConfigurationViewModel
private lateinit var sessionListener: SessionListener
protected lateinit var bugReporter: BugReporter
private lateinit var pinLocker: PinLocker
lateinit var rageShake: RageShake
lateinit var navigator: Navigator
@ -181,6 +189,7 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
viewModelFactory = screenComponent.viewModelFactory()
configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java)
bugReporter = screenComponent.bugReporter()
pinLocker = screenComponent.pinLocker()
// Shake detector
rageShake = screenComponent.rageShake()
navigator = screenComponent.navigator()
@ -193,7 +202,11 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
finish()
}
})
pinLocker.getLiveState().observeNotNull(this) {
if(this@VectorBaseActivity !is UnlockedActivity && it == PinLocker.State.LOCKED){
navigator.openPinCode(this, PinMode.AUTH)
}
}
sessionListener = vectorComponent.sessionListener()
sessionListener.globalErrorLiveData.observeEvent(this) {
handleGlobalError(it)
@ -285,6 +298,24 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
uiDisposables.dispose()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PinActivity.PIN_REQUEST_CODE) {
when (resultCode) {
Activity.RESULT_OK -> {
pinLocker.unlock()
}
PinActivity.PIN_RESULT_CODE_FORGOT -> {
pinLocker.block()
}
else -> {
pinLocker.block()
moveTaskToBack(true)
}
}
}
}
override fun onResume() {
super.onResume()
Timber.i("onResume Activity ${this.javaClass.simpleName}")

View file

@ -280,6 +280,11 @@ class DefaultNavigator @Inject constructor(
fragment.startActivityForResult(intent, requestCode)
}
override fun openPinCode(activity: Activity, pinMode: PinMode, requestCode: Int) {
val intent = PinActivity.newIntent(activity, PinArgs(pinMode))
activity.startActivityForResult(intent, requestCode)
}
override fun openMediaViewer(activity: Activity,
roomId: String,
mediaData: AttachmentData,

View file

@ -82,6 +82,8 @@ interface Navigator {
fun openPinCode(fragment: Fragment, pinMode: PinMode, requestCode: Int = PinActivity.PIN_REQUEST_CODE)
fun openPinCode(activity: Activity, pinMode: PinMode, requestCode: Int = PinActivity.PIN_REQUEST_CODE)
fun openTerms(fragment: Fragment,
serviceType: TermsService.ServiceType,
baseUrl: String,

View file

@ -25,7 +25,7 @@ import im.vector.riotx.core.extensions.addFragment
import im.vector.riotx.core.platform.ToolbarConfigurable
import im.vector.riotx.core.platform.VectorBaseActivity
class PinActivity : VectorBaseActivity(), ToolbarConfigurable {
class PinActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity {
companion object {

View file

@ -141,6 +141,7 @@ class PinFragment @Inject constructor(
override fun onFingerprintSuccessful() {
Toast.makeText(requireContext(), "Pin successful", Toast.LENGTH_LONG).show()
vectorBaseActivity.setResult(Activity.RESULT_OK)
vectorBaseActivity.finish()
}
@ -149,6 +150,7 @@ class PinFragment @Inject constructor(
override fun onCodeInputSuccessful() {
Toast.makeText(requireContext(), "Pin successful", Toast.LENGTH_LONG).show()
vectorBaseActivity.setResult(Activity.RESULT_OK)
vectorBaseActivity.finish()
}
})

View file

@ -0,0 +1,103 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.pin
import android.os.SystemClock
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.OnLifecycleEvent
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
private const val PERIOD_OF_GRACE_IN_MS = 0 * 60 * 1000L
/**
* This class is responsible for keeping the status of locking
* It automatically locks when entering background/foreground with a grace period.
* You can force to unlock with unlock method, use it whenever the pin code has been validated.
*/
@Singleton
class PinLocker @Inject constructor(private val pinCodeStore: PinCodeStore) : LifecycleObserver {
enum class State {
// App is locked, can be unlock
LOCKED,
// App is blocked and can't be unlocked as long as the app is in foreground
BLOCKED,
// is unlocked, the app can be used
UNLOCKED
}
private val liveState = MutableLiveData<State>()
private var isBlocked = false
private var shouldBeLocked = true
private var entersBackgroundTs = 0L
fun getLiveState(): LiveData<State> {
return liveState
}
private fun computeState() {
GlobalScope.launch {
val state = if (isBlocked) {
State.BLOCKED
} else if (shouldBeLocked && pinCodeStore.hasEncodedPin()) {
State.LOCKED
} else {
State.UNLOCKED
}
if (liveState.value != state) {
liveState.postValue(state)
}
}
}
fun unlock() {
Timber.v("Unlock app")
shouldBeLocked = false
computeState()
}
fun block() {
Timber.v("Block app")
isBlocked = true
computeState()
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun entersForeground() {
val timeElapsedSinceBackground = SystemClock.elapsedRealtime() - entersBackgroundTs
shouldBeLocked = shouldBeLocked || timeElapsedSinceBackground >= PERIOD_OF_GRACE_IN_MS
Timber.v("App enters foreground after $timeElapsedSinceBackground ms spent in background")
computeState()
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun entersBackground() {
isBlocked = false
entersBackgroundTs = SystemClock.elapsedRealtime()
}
}

View file

@ -0,0 +1,19 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.riotx.features.pin
interface UnlockedActivity

View file

@ -56,15 +56,16 @@ import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet
import im.vector.riotx.features.navigation.Navigator
import im.vector.riotx.features.pin.PinActivity
import im.vector.riotx.features.pin.PinCodeStore
import im.vector.riotx.features.pin.PinLocker
import im.vector.riotx.features.pin.PinMode
import im.vector.riotx.features.themes.ThemeUtils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import kotlinx.coroutines.launch
import javax.inject.Inject
class VectorSettingsSecurityPrivacyFragment @Inject constructor(
private val vectorPreferences: VectorPreferences,
private val pinLocker: PinLocker,
private val activeSessionHolder: ActiveSessionHolder,
private val pinCodeStore: PinCodeStore,
private val navigator: Navigator
@ -298,6 +299,7 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor(
}
}
} else if (requestCode == PinActivity.PIN_REQUEST_CODE) {
pinLocker.unlock()
refreshPinCodeStatus()
} else if (requestCode == REQUEST_E2E_FILE_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK) {