disabled screen timeout when displaying QR code (#617)

* disabled screen timeout when displaying QR code

* added tests

* fixed ktlint errors

* Remove unnecessary test scope

Co-authored-by: Carter Jernigan <git@carterjernigan.com>
This commit is contained in:
Alex 2022-10-13 15:13:41 +02:00 committed by GitHub
parent ed408a6be3
commit 83b79afe4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 236 additions and 48 deletions

View File

@ -0,0 +1,64 @@
package co.electriccoin.zcash.ui.common
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.filters.MediumTest
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class ScreenTimeoutTest : UiTestPrerequisites() {
@get:Rule
val composeTestRule = createComposeRule()
@Test
@MediumTest
fun acquireAndReleaseScreenTimeout() = runTest {
val testSetup = TestSetup(composeTestRule)
assertEquals(1, testSetup.getScreenTimeoutCount())
testSetup.mutableScreenTimeoutFlag.update { false }
composeTestRule.awaitIdle()
assertEquals(0, testSetup.getScreenTimeoutCount())
}
private class TestSetup(composeTestRule: ComposeContentTestRule) {
val mutableScreenTimeoutFlag = MutableStateFlow(true)
private val screenTimeout = ScreenTimeout()
fun getScreenTimeoutCount() = screenTimeout.referenceCount.value
init {
composeTestRule.setContent {
CompositionLocalProvider(LocalScreenTimeout provides screenTimeout) {
ZcashTheme {
val disableScreenTimeout by mutableScreenTimeoutFlag.collectAsState()
TestView(disableScreenTimeout)
}
}
}
}
@Composable
private fun TestView(disableScreenTimeout: Boolean) {
if (disableScreenTimeout) {
DisableScreenTimeout()
}
}
}
}

View File

@ -0,0 +1,61 @@
package co.electriccoin.zcash.ui.screen.profile.view
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.filters.MediumTest
import cash.z.ecc.sdk.fixture.WalletAddressFixture
import cash.z.ecc.sdk.model.WalletAddress
import co.electriccoin.zcash.test.UiTestPrerequisites
import co.electriccoin.zcash.ui.common.LocalScreenTimeout
import co.electriccoin.zcash.ui.common.ScreenTimeout
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import kotlin.test.assertEquals
@OptIn(ExperimentalCoroutinesApi::class)
class ProfileViewScreenTimeoutTest : UiTestPrerequisites() {
@get:Rule
val composeTestRule = createComposeRule()
@Test
@MediumTest
fun testFullBrightness() = runTest {
val testSetup = newTestSetup(WalletAddressFixture.unified())
assertEquals(1, testSetup.getScreenTimeoutCount())
}
private fun newTestSetup(walletAddress: WalletAddress) = TestSetup(composeTestRule, walletAddress)
private class TestSetup(private val composeTestRule: ComposeContentTestRule, walletAddress: WalletAddress) {
private val screenTimeout = ScreenTimeout()
fun getScreenTimeoutCount() = screenTimeout.referenceCount.value
init {
composeTestRule.setContent {
CompositionLocalProvider(LocalScreenTimeout provides screenTimeout) {
ZcashTheme {
ZcashTheme {
Profile(
walletAddress,
onBack = { },
onAddressDetails = { },
onAddressBook = { },
onSettings = { },
onCoinholderVote = {},
onSupport = { },
onAbout = { }
)
}
}
}
}
}
}
}

View File

@ -2,7 +2,6 @@ package co.electriccoin.zcash.ui
import android.os.Bundle
import android.os.SystemClock
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
@ -10,7 +9,6 @@ import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
@ -18,12 +16,7 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavHostController
import cash.z.ecc.android.sdk.ext.collectWith
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
import co.electriccoin.zcash.ui.common.LocalScreenBrightness
import co.electriccoin.zcash.ui.common.LocalScreenSecurity
import co.electriccoin.zcash.ui.common.ScreenBrightness
import co.electriccoin.zcash.ui.common.ScreenSecurity
import co.electriccoin.zcash.ui.common.BindCompLocalProvider
import co.electriccoin.zcash.ui.design.compat.FontCompat
import co.electriccoin.zcash.ui.design.component.ConfigurationOverride
import co.electriccoin.zcash.ui.design.component.GradientSurface
@ -36,7 +29,6 @@ import co.electriccoin.zcash.ui.screen.onboarding.WrapOnboarding
import co.electriccoin.zcash.ui.screen.warning.WrapNotEnoughSpace
import co.electriccoin.zcash.ui.screen.warning.viewmodel.StorageCheckViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
@ -90,11 +82,6 @@ class MainActivity : ComponentActivity() {
}
private fun setupUiContent() {
val screenSecurity = ScreenSecurity()
val screenBrightness = ScreenBrightness()
observeScreenSecurityFlag(screenSecurity)
observeScreenBrightnessFlag(screenBrightness)
setContent {
Override(configurationOverrideFlow) {
ZcashTheme {
@ -103,10 +90,7 @@ class MainActivity : ComponentActivity() {
.fillMaxWidth()
.fillMaxHeight()
) {
CompositionLocalProvider(
LocalScreenSecurity provides screenSecurity,
LocalScreenBrightness provides screenBrightness
) {
BindCompLocalProvider {
val isEnoughSpace by storageCheckViewModel.isEnoughSpace.collectAsState()
if (isEnoughSpace == false) {
WrapNotEnoughSpace()
@ -148,36 +132,6 @@ class MainActivity : ComponentActivity() {
}
}
private fun observeScreenSecurityFlag(screenSecurity: ScreenSecurity) {
screenSecurity.referenceCount.map { it > 0 }.collectWith(lifecycleScope) { isSecure ->
val isTest = FirebaseTestLabUtil.isFirebaseTestLab(applicationContext) ||
EmulatorWtfUtil.isEmulatorWtf(applicationContext)
if (isSecure && !isTest) {
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
}
private fun observeScreenBrightnessFlag(screenBrightness: ScreenBrightness) {
screenBrightness.referenceCount.map { it > 0 }.collectWith(lifecycleScope) { maxBrightness ->
if (maxBrightness) {
window.attributes = window.attributes.apply {
this.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
}
} else {
window.attributes = window.attributes.apply {
this.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
}
}
}
}
companion object {
@VisibleForTesting
internal val SPLASH_SCREEN_DELAY = 0.seconds

View File

@ -0,0 +1,69 @@
package co.electriccoin.zcash.ui.common
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.sdk.ext.collectWith
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
import kotlinx.coroutines.flow.map
@Composable
fun ComponentActivity.BindCompLocalProvider(content: @Composable () -> Unit) {
val screenSecurity = ScreenSecurity()
observeScreenSecurityFlag(screenSecurity)
val screenBrightness = ScreenBrightness()
observeScreenBrightnessFlag(screenBrightness)
val screenTimeout = ScreenTimeout()
observeScreenTimeoutFlag(screenTimeout)
CompositionLocalProvider(
LocalScreenSecurity provides screenSecurity,
LocalScreenBrightness provides screenBrightness,
LocalScreenTimeout provides screenTimeout,
content = content
)
}
private fun ComponentActivity.observeScreenSecurityFlag(screenSecurity: ScreenSecurity) {
screenSecurity.referenceCount.map { it > 0 }.collectWith(lifecycleScope) { isSecure ->
val isTest = FirebaseTestLabUtil.isFirebaseTestLab(applicationContext) ||
EmulatorWtfUtil.isEmulatorWtf(applicationContext)
if (isSecure && !isTest) {
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
}
private fun ComponentActivity.observeScreenBrightnessFlag(screenBrightness: ScreenBrightness) {
screenBrightness.referenceCount.map { it > 0 }.collectWith(lifecycleScope) { maxBrightness ->
if (maxBrightness) {
window.attributes = window.attributes.apply {
this.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_FULL
}
} else {
window.attributes = window.attributes.apply {
this.screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
}
}
}
}
private fun ComponentActivity.observeScreenTimeoutFlag(screenTimeout: ScreenTimeout) {
screenTimeout.referenceCount.map { it > 0 }.collectWith(lifecycleScope) { disableTimeout ->
if (disableTimeout) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
}
}

View File

@ -0,0 +1,38 @@
package co.electriccoin.zcash.ui.common
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.compositionLocalOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.updateAndGet
class ScreenTimeout {
private val mutableReferenceCount: MutableStateFlow<Int> = MutableStateFlow(0)
val referenceCount = mutableReferenceCount.asStateFlow()
fun disableScreenTimeout() {
mutableReferenceCount.update { it + 1 }
}
fun restoreTimeout() {
val after = mutableReferenceCount.updateAndGet { it - 1 }
if (after < 0) {
error("Restored timeout reference count too many times")
}
}
}
val LocalScreenTimeout = compositionLocalOf { ScreenTimeout() }
@Composable
fun DisableScreenTimeout() {
val screenTimeout = LocalScreenTimeout.current
DisposableEffect(screenTimeout) {
screenTimeout.disableScreenTimeout()
onDispose { screenTimeout.restoreTimeout() }
}
}

View File

@ -26,6 +26,7 @@ import cash.z.ecc.sdk.fixture.WalletAddressFixture
import cash.z.ecc.sdk.model.WalletAddress
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.common.BrightenScreen
import co.electriccoin.zcash.ui.common.DisableScreenTimeout
import co.electriccoin.zcash.ui.design.component.Body
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.PrimaryButton
@ -145,6 +146,7 @@ private fun ProfileContents(
@Composable
private fun QrCode(data: String, size: Dp, modifier: Modifier) {
BrightenScreen()
DisableScreenTimeout()
val sizePixels = with(LocalDensity.current) { size.toPx() }.roundToInt()
// In the future, use actual/expect to switch QR code generator implementations for multiplatform