Add PinLocker to handle lock state
This commit is contained in:
parent
37521d2d4f
commit
bb066b7c65
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue