[DO NOT MERGE] Bouncer - Intercept touch events when not visible
There is a moment, when swiping on the lockscreen, that the bouncer showing state is set to true but it isn't quite visible. In this state, it is possible to interact with it. Only allow interaction if the bouncer is at least 90% visible. Also, refactor bouncer repository to an interface and add a FakeKeyguardBouncerRepository so it can be tested with coroutines. Test: atest PrimaryBouncerInteractorTest PrimaryBouncerInteractorWithCoroutinesTest Fixes: 260820843 Change-Id: I78cdf14521cc8cd02b6f2ab06e7ca518c92718c0
This commit is contained in:
@@ -19,6 +19,7 @@ package com.android.keyguard;
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
/**
|
||||
@@ -33,7 +34,7 @@ import android.widget.FrameLayout;
|
||||
public class KeyguardHostView extends FrameLayout {
|
||||
|
||||
protected ViewMediatorCallback mViewMediatorCallback;
|
||||
|
||||
private boolean mIsInteractable;
|
||||
|
||||
public KeyguardHostView(Context context) {
|
||||
this(context, null);
|
||||
@@ -54,4 +55,24 @@ public class KeyguardHostView extends FrameLayout {
|
||||
public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
|
||||
mViewMediatorCallback = viewMediatorCallback;
|
||||
}
|
||||
|
||||
/** Set true if the view can be interacted with */
|
||||
public void setInteractable(boolean isInteractable) {
|
||||
mIsInteractable = isInteractable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure to disallow touches while transitioning the bouncer, otherwise
|
||||
* it can remain interactable even when barely visible.
|
||||
*/
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
return !mIsInteractable;
|
||||
}
|
||||
|
||||
/** True to consume any events that are sent to it */
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,4 +527,9 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
|
||||
mKeyguardSecurityContainerController.updateKeyguardPosition(x);
|
||||
}
|
||||
}
|
||||
|
||||
/** Set true if the view can be interacted with */
|
||||
public void setInteractable(boolean isInteractable) {
|
||||
mView.setInteractable(isInteractable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,31 +41,96 @@ import kotlinx.coroutines.flow.map
|
||||
*
|
||||
* Make sure to add newly added flows to the logger.
|
||||
*/
|
||||
interface KeyguardBouncerRepository {
|
||||
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
|
||||
val primaryBouncerVisible: StateFlow<Boolean>
|
||||
val primaryBouncerShow: StateFlow<KeyguardBouncerModel?>
|
||||
val primaryBouncerShowingSoon: StateFlow<Boolean>
|
||||
val primaryBouncerHide: StateFlow<Boolean>
|
||||
val primaryBouncerStartingToHide: StateFlow<Boolean>
|
||||
val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
|
||||
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
|
||||
val primaryBouncerScrimmed: StateFlow<Boolean>
|
||||
/**
|
||||
* Set how much of the notification panel is showing on the screen.
|
||||
* ```
|
||||
* 0f = panel fully hidden = bouncer fully showing
|
||||
* 1f = panel fully showing = bouncer fully hidden
|
||||
* ```
|
||||
*/
|
||||
val panelExpansionAmount: StateFlow<Float>
|
||||
val keyguardPosition: StateFlow<Float>
|
||||
val onScreenTurnedOff: StateFlow<Boolean>
|
||||
val isBackButtonEnabled: StateFlow<Boolean?>
|
||||
/** Determines if user is already unlocked */
|
||||
val keyguardAuthenticated: StateFlow<Boolean?>
|
||||
val showMessage: StateFlow<BouncerShowMessageModel?>
|
||||
val resourceUpdateRequests: StateFlow<Boolean>
|
||||
val bouncerPromptReason: Int
|
||||
val bouncerErrorMessage: CharSequence?
|
||||
val isAlternateBouncerVisible: StateFlow<Boolean>
|
||||
val isAlternateBouncerUIAvailable: StateFlow<Boolean>
|
||||
var lastAlternateBouncerVisibleTime: Long
|
||||
|
||||
fun setPrimaryScrimmed(isScrimmed: Boolean)
|
||||
|
||||
fun setPrimaryVisible(isVisible: Boolean)
|
||||
|
||||
fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?)
|
||||
|
||||
fun setPrimaryShowingSoon(showingSoon: Boolean)
|
||||
|
||||
fun setPrimaryHide(hide: Boolean)
|
||||
|
||||
fun setPrimaryStartingToHide(startingToHide: Boolean)
|
||||
|
||||
fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
|
||||
|
||||
fun setPanelExpansion(panelExpansion: Float)
|
||||
|
||||
fun setKeyguardPosition(keyguardPosition: Float)
|
||||
|
||||
fun setResourceUpdateRequests(willUpdateResources: Boolean)
|
||||
|
||||
fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
|
||||
|
||||
fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
|
||||
|
||||
fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
|
||||
|
||||
fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean)
|
||||
|
||||
fun setAlternateVisible(isVisible: Boolean)
|
||||
|
||||
fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
|
||||
}
|
||||
|
||||
@SysUISingleton
|
||||
class KeyguardBouncerRepository
|
||||
class KeyguardBouncerRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
private val viewMediatorCallback: ViewMediatorCallback,
|
||||
private val clock: SystemClock,
|
||||
@Application private val applicationScope: CoroutineScope,
|
||||
@BouncerLog private val buffer: TableLogBuffer,
|
||||
) {
|
||||
) : KeyguardBouncerRepository {
|
||||
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
|
||||
private val _primaryBouncerVisible = MutableStateFlow(false)
|
||||
val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
|
||||
override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
|
||||
private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
|
||||
val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
|
||||
override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
|
||||
private val _primaryBouncerShowingSoon = MutableStateFlow(false)
|
||||
val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
|
||||
override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
|
||||
private val _primaryBouncerHide = MutableStateFlow(false)
|
||||
val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
|
||||
override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
|
||||
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
|
||||
val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
|
||||
override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
|
||||
private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
|
||||
val primaryBouncerStartingDisappearAnimation = _primaryBouncerDisappearAnimation.asStateFlow()
|
||||
override val primaryBouncerStartingDisappearAnimation =
|
||||
_primaryBouncerDisappearAnimation.asStateFlow()
|
||||
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
|
||||
private val _primaryBouncerScrimmed = MutableStateFlow(false)
|
||||
val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
|
||||
override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
|
||||
/**
|
||||
* Set how much of the notification panel is showing on the screen.
|
||||
* ```
|
||||
@@ -74,23 +139,23 @@ constructor(
|
||||
* ```
|
||||
*/
|
||||
private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
|
||||
val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
|
||||
override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
|
||||
private val _keyguardPosition = MutableStateFlow(0f)
|
||||
val keyguardPosition = _keyguardPosition.asStateFlow()
|
||||
override val keyguardPosition = _keyguardPosition.asStateFlow()
|
||||
private val _onScreenTurnedOff = MutableStateFlow(false)
|
||||
val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
|
||||
override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
|
||||
private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
|
||||
val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
|
||||
override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
|
||||
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
|
||||
/** Determines if user is already unlocked */
|
||||
val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
|
||||
override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
|
||||
private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
|
||||
val showMessage = _showMessage.asStateFlow()
|
||||
override val showMessage = _showMessage.asStateFlow()
|
||||
private val _resourceUpdateRequests = MutableStateFlow(false)
|
||||
val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
|
||||
val bouncerPromptReason: Int
|
||||
override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
|
||||
override val bouncerPromptReason: Int
|
||||
get() = viewMediatorCallback.bouncerPromptReason
|
||||
val bouncerErrorMessage: CharSequence?
|
||||
override val bouncerErrorMessage: CharSequence?
|
||||
get() = viewMediatorCallback.consumeCustomMessage()
|
||||
|
||||
init {
|
||||
@@ -99,21 +164,21 @@ constructor(
|
||||
|
||||
/** Values associated with the AlternateBouncer */
|
||||
private val _isAlternateBouncerVisible = MutableStateFlow(false)
|
||||
val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
|
||||
var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
|
||||
override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
|
||||
override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
|
||||
private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
|
||||
val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
|
||||
override val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
|
||||
_isAlternateBouncerUIAvailable.asStateFlow()
|
||||
|
||||
fun setPrimaryScrimmed(isScrimmed: Boolean) {
|
||||
override fun setPrimaryScrimmed(isScrimmed: Boolean) {
|
||||
_primaryBouncerScrimmed.value = isScrimmed
|
||||
}
|
||||
|
||||
fun setPrimaryVisible(isVisible: Boolean) {
|
||||
override fun setPrimaryVisible(isVisible: Boolean) {
|
||||
_primaryBouncerVisible.value = isVisible
|
||||
}
|
||||
|
||||
fun setAlternateVisible(isVisible: Boolean) {
|
||||
override fun setAlternateVisible(isVisible: Boolean) {
|
||||
if (isVisible && !_isAlternateBouncerVisible.value) {
|
||||
lastAlternateBouncerVisibleTime = clock.uptimeMillis()
|
||||
} else if (!isVisible) {
|
||||
@@ -122,55 +187,55 @@ constructor(
|
||||
_isAlternateBouncerVisible.value = isVisible
|
||||
}
|
||||
|
||||
fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
|
||||
override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
|
||||
_isAlternateBouncerUIAvailable.value = isAvailable
|
||||
}
|
||||
|
||||
fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
|
||||
override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
|
||||
_primaryBouncerShow.value = keyguardBouncerModel
|
||||
}
|
||||
|
||||
fun setPrimaryShowingSoon(showingSoon: Boolean) {
|
||||
override fun setPrimaryShowingSoon(showingSoon: Boolean) {
|
||||
_primaryBouncerShowingSoon.value = showingSoon
|
||||
}
|
||||
|
||||
fun setPrimaryHide(hide: Boolean) {
|
||||
override fun setPrimaryHide(hide: Boolean) {
|
||||
_primaryBouncerHide.value = hide
|
||||
}
|
||||
|
||||
fun setPrimaryStartingToHide(startingToHide: Boolean) {
|
||||
override fun setPrimaryStartingToHide(startingToHide: Boolean) {
|
||||
_primaryBouncerStartingToHide.value = startingToHide
|
||||
}
|
||||
|
||||
fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
|
||||
override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
|
||||
_primaryBouncerDisappearAnimation.value = runnable
|
||||
}
|
||||
|
||||
fun setPanelExpansion(panelExpansion: Float) {
|
||||
override fun setPanelExpansion(panelExpansion: Float) {
|
||||
_panelExpansionAmount.value = panelExpansion
|
||||
}
|
||||
|
||||
fun setKeyguardPosition(keyguardPosition: Float) {
|
||||
override fun setKeyguardPosition(keyguardPosition: Float) {
|
||||
_keyguardPosition.value = keyguardPosition
|
||||
}
|
||||
|
||||
fun setResourceUpdateRequests(willUpdateResources: Boolean) {
|
||||
override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
|
||||
_resourceUpdateRequests.value = willUpdateResources
|
||||
}
|
||||
|
||||
fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
|
||||
override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
|
||||
_showMessage.value = bouncerShowMessageModel
|
||||
}
|
||||
|
||||
fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
|
||||
override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
|
||||
_keyguardAuthenticated.value = keyguardAuthenticated
|
||||
}
|
||||
|
||||
fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
|
||||
override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
|
||||
_isBackButtonEnabled.value = isBackButtonEnabled
|
||||
}
|
||||
|
||||
fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
|
||||
override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
|
||||
_onScreenTurnedOff.value = onScreenTurnedOff
|
||||
}
|
||||
|
||||
|
||||
@@ -37,4 +37,7 @@ interface KeyguardRepositoryModule {
|
||||
fun deviceEntryFingerprintAuthRepository(
|
||||
impl: DeviceEntryFingerprintAuthRepositoryImpl
|
||||
): DeviceEntryFingerprintAuthRepository
|
||||
|
||||
@Binds
|
||||
fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
|
||||
}
|
||||
|
||||
@@ -113,6 +113,8 @@ constructor(
|
||||
0f
|
||||
}
|
||||
}
|
||||
/** Allow for interaction when just about fully visible */
|
||||
val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
|
||||
|
||||
// TODO(b/243685699): Move isScrimmed logic to data layer.
|
||||
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
|
||||
|
||||
@@ -160,6 +160,12 @@ object KeyguardBouncerViewBinder {
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.isInteractable.collect { isInteractable ->
|
||||
hostViewController.setInteractable(isInteractable)
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.isBouncerVisible
|
||||
.filter { !it }
|
||||
|
||||
@@ -41,6 +41,9 @@ constructor(
|
||||
/** Observe on bouncer visibility. */
|
||||
val isBouncerVisible: Flow<Boolean> = interactor.isVisible
|
||||
|
||||
/** Can the user interact with the view? */
|
||||
val isInteractable: Flow<Boolean> = interactor.isInteractable
|
||||
|
||||
/** Observe whether bouncer is showing. */
|
||||
val show: Flow<KeyguardBouncerModel> = interactor.show
|
||||
|
||||
|
||||
@@ -52,7 +52,6 @@ import android.view.WindowMetrics
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.keyguard.ViewMediatorCallback
|
||||
import com.android.systemui.R
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.SysuiTestableContext
|
||||
@@ -61,9 +60,8 @@ import com.android.systemui.flags.FakeFeatureFlags
|
||||
import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER
|
||||
import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
|
||||
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.android.systemui.recents.OverviewProxyService
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
@@ -111,7 +109,7 @@ class SideFpsControllerTest : SysuiTestCase() {
|
||||
@Captor lateinit var overlayCaptor: ArgumentCaptor<View>
|
||||
@Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
|
||||
|
||||
private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
|
||||
private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
|
||||
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
|
||||
private val featureFlags = FakeFeatureFlags()
|
||||
private val executor = FakeExecutor(FakeSystemClock())
|
||||
@@ -135,13 +133,7 @@ class SideFpsControllerTest : SysuiTestCase() {
|
||||
@Before
|
||||
fun setup() {
|
||||
featureFlags.set(MODERN_ALTERNATE_BOUNCER, true)
|
||||
keyguardBouncerRepository =
|
||||
KeyguardBouncerRepository(
|
||||
mock(ViewMediatorCallback::class.java),
|
||||
FakeSystemClock(),
|
||||
TestCoroutineScope(),
|
||||
mock(TableLogBuffer::class.java),
|
||||
)
|
||||
keyguardBouncerRepository = FakeKeyguardBouncerRepository()
|
||||
alternateBouncerInteractor =
|
||||
AlternateBouncerInteractor(
|
||||
keyguardBouncerRepository,
|
||||
|
||||
@@ -29,6 +29,7 @@ import com.android.systemui.keyguard.data.BouncerView
|
||||
import com.android.systemui.keyguard.data.repository.BiometricRepository
|
||||
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
|
||||
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
|
||||
@@ -65,7 +66,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle
|
||||
allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
|
||||
MockitoAnnotations.initMocks(this)
|
||||
keyguardBouncerRepository =
|
||||
KeyguardBouncerRepository(
|
||||
KeyguardBouncerRepositoryImpl(
|
||||
mock(com.android.keyguard.ViewMediatorCallback::class.java),
|
||||
FakeSystemClock(),
|
||||
TestCoroutineScope(),
|
||||
|
||||
@@ -45,7 +45,7 @@ class KeyguardBouncerRepositoryTest : SysuiTestCase() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
val testCoroutineScope = TestCoroutineScope()
|
||||
underTest =
|
||||
KeyguardBouncerRepository(
|
||||
KeyguardBouncerRepositoryImpl(
|
||||
viewMediatorCallback,
|
||||
systemClock,
|
||||
testCoroutineScope,
|
||||
|
||||
@@ -25,6 +25,7 @@ import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.keyguard.data.repository.FakeBiometricRepository
|
||||
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
|
||||
import com.android.systemui.log.table.TableLogBuffer
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.android.systemui.util.time.SystemClock
|
||||
@@ -58,7 +59,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
|
||||
fun setup() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
bouncerRepository =
|
||||
KeyguardBouncerRepository(
|
||||
KeyguardBouncerRepositoryImpl(
|
||||
mock(ViewMediatorCallback::class.java),
|
||||
FakeSystemClock(),
|
||||
TestCoroutineScope(),
|
||||
|
||||
@@ -68,13 +68,13 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
|
||||
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
|
||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
private val mainHandler = FakeHandler(Looper.getMainLooper())
|
||||
private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
|
||||
private lateinit var underTest: PrimaryBouncerInteractor
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
DejankUtils.setImmediate(true)
|
||||
mPrimaryBouncerInteractor =
|
||||
underTest =
|
||||
PrimaryBouncerInteractor(
|
||||
repository,
|
||||
bouncerView,
|
||||
@@ -94,7 +94,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testShow_isScrimmed() {
|
||||
mPrimaryBouncerInteractor.show(true)
|
||||
underTest.show(true)
|
||||
verify(repository).setOnScreenTurnedOff(false)
|
||||
verify(repository).setKeyguardAuthenticated(null)
|
||||
verify(repository).setPrimaryHide(false)
|
||||
@@ -124,7 +124,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
|
||||
|
||||
@Test
|
||||
fun testHide() {
|
||||
mPrimaryBouncerInteractor.hide()
|
||||
underTest.hide()
|
||||
verify(falsingCollector).onBouncerHidden()
|
||||
verify(keyguardStateController).notifyBouncerShowing(false)
|
||||
verify(repository).setPrimaryShowingSoon(false)
|
||||
@@ -137,7 +137,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testExpansion() {
|
||||
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
|
||||
mPrimaryBouncerInteractor.setPanelExpansion(0.6f)
|
||||
underTest.setPanelExpansion(0.6f)
|
||||
verify(repository).setPanelExpansion(0.6f)
|
||||
verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
|
||||
}
|
||||
@@ -146,7 +146,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
|
||||
fun testExpansion_fullyShown() {
|
||||
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
|
||||
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
|
||||
mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
|
||||
underTest.setPanelExpansion(EXPANSION_VISIBLE)
|
||||
verify(falsingCollector).onBouncerShown()
|
||||
verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
|
||||
}
|
||||
@@ -155,7 +155,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
|
||||
fun testExpansion_fullyHidden() {
|
||||
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
|
||||
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
|
||||
mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
|
||||
underTest.setPanelExpansion(EXPANSION_HIDDEN)
|
||||
verify(repository).setPrimaryVisible(false)
|
||||
verify(repository).setPrimaryShow(null)
|
||||
verify(repository).setPrimaryHide(true)
|
||||
@@ -167,7 +167,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testExpansion_startingToHide() {
|
||||
`when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
|
||||
mPrimaryBouncerInteractor.setPanelExpansion(0.1f)
|
||||
underTest.setPanelExpansion(0.1f)
|
||||
verify(repository).setPrimaryStartingToHide(true)
|
||||
verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
|
||||
}
|
||||
@@ -175,7 +175,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
|
||||
@Test
|
||||
fun testShowMessage() {
|
||||
val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
|
||||
mPrimaryBouncerInteractor.showMessage("abc", null)
|
||||
underTest.showMessage("abc", null)
|
||||
verify(repository).setShowMessage(argCaptor.capture())
|
||||
assertThat(argCaptor.value.message).isEqualTo("abc")
|
||||
}
|
||||
@@ -184,62 +184,62 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
|
||||
fun testDismissAction() {
|
||||
val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
|
||||
val cancelAction = mock(Runnable::class.java)
|
||||
mPrimaryBouncerInteractor.setDismissAction(onDismissAction, cancelAction)
|
||||
underTest.setDismissAction(onDismissAction, cancelAction)
|
||||
verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateResources() {
|
||||
mPrimaryBouncerInteractor.updateResources()
|
||||
underTest.updateResources()
|
||||
verify(repository).setResourceUpdateRequests(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotifyKeyguardAuthenticated() {
|
||||
mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(true)
|
||||
underTest.notifyKeyguardAuthenticated(true)
|
||||
verify(repository).setKeyguardAuthenticated(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotifyShowedMessage() {
|
||||
mPrimaryBouncerInteractor.onMessageShown()
|
||||
underTest.onMessageShown()
|
||||
verify(repository).setShowMessage(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOnScreenTurnedOff() {
|
||||
mPrimaryBouncerInteractor.onScreenTurnedOff()
|
||||
underTest.onScreenTurnedOff()
|
||||
verify(repository).setOnScreenTurnedOff(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetKeyguardPosition() {
|
||||
mPrimaryBouncerInteractor.setKeyguardPosition(0f)
|
||||
underTest.setKeyguardPosition(0f)
|
||||
verify(repository).setKeyguardPosition(0f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotifyKeyguardAuthenticatedHandled() {
|
||||
mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedHandled()
|
||||
underTest.notifyKeyguardAuthenticatedHandled()
|
||||
verify(repository).setKeyguardAuthenticated(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNotifyUpdatedResources() {
|
||||
mPrimaryBouncerInteractor.notifyUpdatedResources()
|
||||
underTest.notifyUpdatedResources()
|
||||
verify(repository).setResourceUpdateRequests(false)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSetBackButtonEnabled() {
|
||||
mPrimaryBouncerInteractor.setBackButtonEnabled(true)
|
||||
underTest.setBackButtonEnabled(true)
|
||||
verify(repository).setIsBackButtonEnabled(true)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStartDisappearAnimation() {
|
||||
val runnable = mock(Runnable::class.java)
|
||||
mPrimaryBouncerInteractor.startDisappearAnimation(runnable)
|
||||
underTest.startDisappearAnimation(runnable)
|
||||
verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
|
||||
}
|
||||
|
||||
@@ -248,42 +248,42 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
|
||||
`when`(repository.primaryBouncerVisible.value).thenReturn(true)
|
||||
`when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
|
||||
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
|
||||
assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isTrue()
|
||||
assertThat(underTest.isFullyShowing()).isTrue()
|
||||
`when`(repository.primaryBouncerVisible.value).thenReturn(false)
|
||||
assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isFalse()
|
||||
assertThat(underTest.isFullyShowing()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIsScrimmed() {
|
||||
`when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
|
||||
assertThat(mPrimaryBouncerInteractor.isScrimmed()).isTrue()
|
||||
assertThat(underTest.isScrimmed()).isTrue()
|
||||
`when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
|
||||
assertThat(mPrimaryBouncerInteractor.isScrimmed()).isFalse()
|
||||
assertThat(underTest.isScrimmed()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIsInTransit() {
|
||||
`when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
|
||||
assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
|
||||
assertThat(underTest.isInTransit()).isTrue()
|
||||
`when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
|
||||
assertThat(mPrimaryBouncerInteractor.isInTransit()).isFalse()
|
||||
assertThat(underTest.isInTransit()).isFalse()
|
||||
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
|
||||
assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
|
||||
assertThat(underTest.isInTransit()).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIsAnimatingAway() {
|
||||
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
|
||||
assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isTrue()
|
||||
assertThat(underTest.isAnimatingAway()).isTrue()
|
||||
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
|
||||
assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isFalse()
|
||||
assertThat(underTest.isAnimatingAway()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWillDismissWithAction() {
|
||||
`when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
|
||||
assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isTrue()
|
||||
assertThat(underTest.willDismissWithAction()).isTrue()
|
||||
`when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
|
||||
assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isFalse()
|
||||
assertThat(underTest.willDismissWithAction()).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 com.android.systemui.keyguard.domain.interactor
|
||||
|
||||
import android.os.Looper
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.keyguard.KeyguardSecurityModel
|
||||
import com.android.keyguard.KeyguardUpdateMonitor
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.classifier.FalsingCollector
|
||||
import com.android.systemui.coroutines.collectLastValue
|
||||
import com.android.systemui.keyguard.DismissCallbackRegistry
|
||||
import com.android.systemui.keyguard.data.BouncerView
|
||||
import com.android.systemui.keyguard.data.BouncerViewDelegate
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
|
||||
import com.android.systemui.statusbar.phone.KeyguardBypassController
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
import com.android.systemui.utils.os.FakeHandler
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
|
||||
private lateinit var repository: FakeKeyguardBouncerRepository
|
||||
@Mock private lateinit var bouncerView: BouncerView
|
||||
@Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
|
||||
@Mock private lateinit var keyguardStateController: KeyguardStateController
|
||||
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
|
||||
@Mock private lateinit var primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
|
||||
@Mock private lateinit var falsingCollector: FalsingCollector
|
||||
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
|
||||
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
|
||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
private val mainHandler = FakeHandler(Looper.getMainLooper())
|
||||
private lateinit var underTest: PrimaryBouncerInteractor
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
repository = FakeKeyguardBouncerRepository()
|
||||
underTest =
|
||||
PrimaryBouncerInteractor(
|
||||
repository,
|
||||
bouncerView,
|
||||
mainHandler,
|
||||
keyguardStateController,
|
||||
keyguardSecurityModel,
|
||||
primaryBouncerCallbackInteractor,
|
||||
falsingCollector,
|
||||
dismissCallbackRegistry,
|
||||
keyguardBypassController,
|
||||
keyguardUpdateMonitor,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
|
||||
val isInteractable = collectLastValue(underTest.isInteractable)
|
||||
|
||||
repository.setPrimaryVisible(true)
|
||||
repository.setPanelExpansion(0.15f)
|
||||
|
||||
assertThat(isInteractable()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
|
||||
val isInteractable = collectLastValue(underTest.isInteractable)
|
||||
|
||||
repository.setPrimaryVisible(false)
|
||||
repository.setPanelExpansion(0.05f)
|
||||
|
||||
assertThat(isInteractable()).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
|
||||
var isInteractable = collectLastValue(underTest.isInteractable)
|
||||
|
||||
repository.setPrimaryVisible(true)
|
||||
repository.setPanelExpansion(0.09f)
|
||||
|
||||
assertThat(isInteractable()).isTrue()
|
||||
|
||||
repository.setPanelExpansion(0.12f)
|
||||
assertThat(isInteractable()).isFalse()
|
||||
|
||||
repository.setPanelExpansion(0f)
|
||||
assertThat(isInteractable()).isTrue()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* 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 com.android.systemui.keyguard.data.repository
|
||||
|
||||
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
|
||||
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/** Fake implementation of [KeyguardRepository] */
|
||||
class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
|
||||
private val _primaryBouncerVisible = MutableStateFlow(false)
|
||||
override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
|
||||
private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
|
||||
override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
|
||||
private val _primaryBouncerShowingSoon = MutableStateFlow(false)
|
||||
override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
|
||||
private val _primaryBouncerHide = MutableStateFlow(false)
|
||||
override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
|
||||
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
|
||||
override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
|
||||
private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
|
||||
override val primaryBouncerStartingDisappearAnimation =
|
||||
_primaryBouncerDisappearAnimation.asStateFlow()
|
||||
private val _primaryBouncerScrimmed = MutableStateFlow(false)
|
||||
override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
|
||||
private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
|
||||
override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
|
||||
private val _keyguardPosition = MutableStateFlow(0f)
|
||||
override val keyguardPosition = _keyguardPosition.asStateFlow()
|
||||
private val _onScreenTurnedOff = MutableStateFlow(false)
|
||||
override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
|
||||
private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
|
||||
override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
|
||||
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
|
||||
override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
|
||||
private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
|
||||
override val showMessage = _showMessage.asStateFlow()
|
||||
private val _resourceUpdateRequests = MutableStateFlow(false)
|
||||
override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
|
||||
override val bouncerPromptReason = 0
|
||||
override val bouncerErrorMessage: CharSequence? = null
|
||||
private val _isAlternateBouncerVisible = MutableStateFlow(false)
|
||||
override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
|
||||
override var lastAlternateBouncerVisibleTime: Long = 0L
|
||||
private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
|
||||
override val isAlternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
|
||||
|
||||
override fun setPrimaryScrimmed(isScrimmed: Boolean) {
|
||||
_primaryBouncerScrimmed.value = isScrimmed
|
||||
}
|
||||
|
||||
override fun setPrimaryVisible(isVisible: Boolean) {
|
||||
_primaryBouncerVisible.value = isVisible
|
||||
}
|
||||
|
||||
override fun setAlternateVisible(isVisible: Boolean) {
|
||||
_isAlternateBouncerVisible.value = isVisible
|
||||
}
|
||||
|
||||
override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
|
||||
_isAlternateBouncerUIAvailable.value = isAvailable
|
||||
}
|
||||
|
||||
override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
|
||||
_primaryBouncerShow.value = keyguardBouncerModel
|
||||
}
|
||||
|
||||
override fun setPrimaryShowingSoon(showingSoon: Boolean) {
|
||||
_primaryBouncerShowingSoon.value = showingSoon
|
||||
}
|
||||
|
||||
override fun setPrimaryHide(hide: Boolean) {
|
||||
_primaryBouncerHide.value = hide
|
||||
}
|
||||
|
||||
override fun setPrimaryStartingToHide(startingToHide: Boolean) {
|
||||
_primaryBouncerStartingToHide.value = startingToHide
|
||||
}
|
||||
|
||||
override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
|
||||
_primaryBouncerDisappearAnimation.value = runnable
|
||||
}
|
||||
|
||||
override fun setPanelExpansion(panelExpansion: Float) {
|
||||
_panelExpansionAmount.value = panelExpansion
|
||||
}
|
||||
|
||||
override fun setKeyguardPosition(keyguardPosition: Float) {
|
||||
_keyguardPosition.value = keyguardPosition
|
||||
}
|
||||
|
||||
override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
|
||||
_resourceUpdateRequests.value = willUpdateResources
|
||||
}
|
||||
|
||||
override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
|
||||
_showMessage.value = bouncerShowMessageModel
|
||||
}
|
||||
|
||||
override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
|
||||
_keyguardAuthenticated.value = keyguardAuthenticated
|
||||
}
|
||||
|
||||
override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
|
||||
_isBackButtonEnabled.value = isBackButtonEnabled
|
||||
}
|
||||
|
||||
override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
|
||||
_onScreenTurnedOff.value = onScreenTurnedOff
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user