Merge "Transitions - Listen for bouncer show & hide" into tm-qpr-dev am: be5367c98a

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20639790

Change-Id: If23b9be9b8a4c0f256272e12eb391d307c2077bc
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Matt Pietal
2022-12-16 13:30:38 +00:00
committed by Automerger Merge Worker
8 changed files with 358 additions and 144 deletions

View File

@@ -22,7 +22,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.keyguard.shared.model.WakefulnessState
@@ -50,27 +50,22 @@ constructor(
override fun start() { override fun start() {
listenForDraggingUpToBouncer() listenForDraggingUpToBouncer()
listenForBouncerHiding() listenForBouncer()
} }
private fun listenForBouncerHiding() { private fun listenForBouncer() {
scope.launch { scope.launch {
keyguardInteractor.isBouncerShowing keyguardInteractor.isBouncerShowing
.sample( .sample(
combine( combine(
keyguardInteractor.wakefulnessModel, keyguardInteractor.wakefulnessModel,
keyguardTransitionInteractor.startedKeyguardTransitionStep, keyguardTransitionInteractor.startedKeyguardTransitionStep,
) { wakefulnessModel, transitionStep -> ::Pair
Pair(wakefulnessModel, transitionStep) ),
} ::toTriple
) { bouncerShowing, wakefulnessAndTransition ->
Triple(
bouncerShowing,
wakefulnessAndTransition.first,
wakefulnessAndTransition.second
) )
} .collect { triple ->
.collect { (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) -> val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
if ( if (
!isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
) { ) {
@@ -91,7 +86,19 @@ constructor(
animator = getAnimator(), animator = getAnimator(),
) )
) )
} else if (
isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
) {
keyguardTransitionRepository.startTransition(
TransitionInfo(
ownerName = name,
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.BOUNCER,
animator = getAnimator(),
)
)
} }
Unit
} }
} }
} }
@@ -104,24 +111,20 @@ constructor(
combine( combine(
keyguardTransitionInteractor.finishedKeyguardState, keyguardTransitionInteractor.finishedKeyguardState,
keyguardInteractor.statusBarState, keyguardInteractor.statusBarState,
) { finishedKeyguardState, statusBarState -> ::Pair
Pair(finishedKeyguardState, statusBarState) ),
} ::toTriple
) { shadeModel, keyguardStateAndStatusBarState ->
Triple(
shadeModel,
keyguardStateAndStatusBarState.first,
keyguardStateAndStatusBarState.second
) )
} .collect { triple ->
.collect { (shadeModel, keyguardState, statusBarState) -> val (shadeModel, keyguardState, statusBarState) = triple
val id = transitionId val id = transitionId
if (id != null) { if (id != null) {
// An existing `id` means a transition is started, and calls to // An existing `id` means a transition is started, and calls to
// `updateTransition` will control it until FINISHED // `updateTransition` will control it until FINISHED
keyguardTransitionRepository.updateTransition( keyguardTransitionRepository.updateTransition(
id, id,
shadeModel.expansionAmount, 1f - shadeModel.expansionAmount,
if ( if (
shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
) { ) {
@@ -137,7 +140,7 @@ constructor(
if ( if (
keyguardState == KeyguardState.LOCKSCREEN && keyguardState == KeyguardState.LOCKSCREEN &&
shadeModel.isUserDragging && shadeModel.isUserDragging &&
statusBarState != SHADE_LOCKED statusBarState == KEYGUARD
) { ) {
transitionId = transitionId =
keyguardTransitionRepository.startTransition( keyguardTransitionRepository.startTransition(

View File

@@ -16,14 +16,17 @@
package com.android.systemui.shade; package com.android.systemui.shade;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.shade.data.repository.ShadeRepository;
import com.android.systemui.shade.data.repository.ShadeRepositoryImpl;
import dagger.Binds; import dagger.Binds;
import dagger.Module; import dagger.Module;
/** Provides a {@link ShadeStateEvents} in {@link SysUISingleton} scope. */ /** Provides Shade-related events and information. */
@Module @Module
public abstract class ShadeEventsModule { public abstract class ShadeEventsModule {
@Binds @Binds
abstract ShadeStateEvents bindShadeEvents(ShadeExpansionStateManager impl); abstract ShadeStateEvents bindShadeEvents(ShadeExpansionStateManager impl);
@Binds abstract ShadeRepository shadeRepository(ShadeRepositoryImpl impl);
} }

View File

@@ -27,11 +27,17 @@ import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
interface ShadeRepository {
/** ShadeModel information regarding shade expansion events */
val shadeModel: Flow<ShadeModel>
}
/** Business logic for shade interactions */ /** Business logic for shade interactions */
@SysUISingleton @SysUISingleton
class ShadeRepository @Inject constructor(shadeExpansionStateManager: ShadeExpansionStateManager) { class ShadeRepositoryImpl
@Inject
val shadeModel: Flow<ShadeModel> = constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepository {
override val shadeModel: Flow<ShadeModel> =
conflatedCallbackFlow { conflatedCallbackFlow {
val callback = val callback =
object : ShadeExpansionListener { object : ShadeExpansionListener {

View File

@@ -16,13 +16,10 @@
package com.android.systemui.keyguard.data.repository package com.android.systemui.keyguard.data.repository
import android.animation.AnimationHandler.AnimationFrameCallbackProvider
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.util.Log import android.util.Log
import android.util.Log.TerribleFailure import android.util.Log.TerribleFailure
import android.util.Log.TerribleFailureHandler import android.util.Log.TerribleFailureHandler
import android.view.Choreographer.FrameCallback
import androidx.test.filters.FlakyTest
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators import com.android.systemui.animation.Interpolators
@@ -32,22 +29,17 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.TransitionInfo import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.keyguard.util.KeyguardTransitionRunner
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import java.math.BigDecimal import java.math.BigDecimal
import java.math.RoundingMode import java.math.RoundingMode
import java.util.UUID import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.yield import kotlinx.coroutines.test.runTest
import org.junit.After import org.junit.After
import org.junit.Assert.fail
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@@ -60,12 +52,14 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: KeyguardTransitionRepository private lateinit var underTest: KeyguardTransitionRepository
private lateinit var oldWtfHandler: TerribleFailureHandler private lateinit var oldWtfHandler: TerribleFailureHandler
private lateinit var wtfHandler: WtfHandler private lateinit var wtfHandler: WtfHandler
private lateinit var runner: KeyguardTransitionRunner
@Before @Before
fun setUp() { fun setUp() {
underTest = KeyguardTransitionRepositoryImpl() underTest = KeyguardTransitionRepositoryImpl()
wtfHandler = WtfHandler() wtfHandler = WtfHandler()
oldWtfHandler = Log.setWtfHandler(wtfHandler) oldWtfHandler = Log.setWtfHandler(wtfHandler)
runner = KeyguardTransitionRunner(underTest)
} }
@After @After
@@ -75,56 +69,37 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
@Test @Test
fun `startTransition runs animator to completion`() = fun `startTransition runs animator to completion`() =
runBlocking(IMMEDIATE) { TestScope().runTest {
val (animator, provider) = setupAnimator(this)
val steps = mutableListOf<TransitionStep>() val steps = mutableListOf<TransitionStep>()
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator)) runner.startTransition(
this,
val startTime = System.currentTimeMillis() TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
while (animator.isRunning()) { maxFrames = 100
yield() )
if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
}
}
assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN) assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN)
job.cancel() job.cancel()
provider.stop()
} }
@Test @Test
@FlakyTest(bugId = 260213291) fun `starting second transition will cancel the first transition`() =
fun `starting second transition will cancel the first transition`() { TestScope().runTest {
runBlocking(IMMEDIATE) {
val (animator, provider) = setupAnimator(this)
val steps = mutableListOf<TransitionStep>() val steps = mutableListOf<TransitionStep>()
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
runner.startTransition(
underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator)) this,
// 3 yields(), alternating with the animator, results in a value 0.1, which can be TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
// canceled and tested against maxFrames = 3,
yield() )
yield()
yield()
// Now start 2nd transition, which will interrupt the first // Now start 2nd transition, which will interrupt the first
val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this) val job2 = underTest.transition(LOCKSCREEN, AOD).onEach { steps.add(it) }.launchIn(this)
val (animator2, provider2) = setupAnimator(this) runner.startTransition(
underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, animator2)) this,
TransitionInfo(OWNER_NAME, LOCKSCREEN, AOD, getAnimator()),
val startTime = System.currentTimeMillis() )
while (animator2.isRunning()) {
yield()
if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
}
}
val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1)) val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN) assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
@@ -134,31 +109,25 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
job.cancel() job.cancel()
job2.cancel() job2.cancel()
provider.stop()
provider2.stop()
}
} }
@Test @Test
fun `Null animator enables manual control with updateTransition`() = fun `Null animator enables manual control with updateTransition`() =
runBlocking(IMMEDIATE) { TestScope().runTest {
val steps = mutableListOf<TransitionStep>() val steps = mutableListOf<TransitionStep>()
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this) val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
val uuid = val uuid =
underTest.startTransition( underTest.startTransition(
TransitionInfo( TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator = null)
ownerName = OWNER_NAME,
from = AOD,
to = LOCKSCREEN,
animator = null,
)
) )
runCurrent()
checkNotNull(uuid).let { checkNotNull(uuid).let {
underTest.updateTransition(it, 0.5f, TransitionState.RUNNING) underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
underTest.updateTransition(it, 1f, TransitionState.FINISHED) underTest.updateTransition(it, 1f, TransitionState.FINISHED)
} }
runCurrent()
assertThat(steps.size).isEqualTo(3) assertThat(steps.size).isEqualTo(3)
assertThat(steps[0]) assertThat(steps[0])
@@ -256,57 +225,11 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
assertThat(wtfHandler.failed).isFalse() assertThat(wtfHandler.failed).isFalse()
} }
private fun setupAnimator( private fun getAnimator(): ValueAnimator {
scope: CoroutineScope return ValueAnimator().apply {
): Pair<ValueAnimator, TestFrameCallbackProvider> {
val animator =
ValueAnimator().apply {
setInterpolator(Interpolators.LINEAR) setInterpolator(Interpolators.LINEAR)
setDuration(ANIMATION_DURATION) setDuration(10)
} }
val provider = TestFrameCallbackProvider(animator, scope)
provider.start()
return Pair(animator, provider)
}
/** Gives direct control over ValueAnimator. See [AnimationHandler] */
private class TestFrameCallbackProvider(
private val animator: ValueAnimator,
private val scope: CoroutineScope,
) : AnimationFrameCallbackProvider {
private var frameCount = 1L
private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
private var job: Job? = null
fun start() {
animator.getAnimationHandler().setProvider(this)
job =
scope.launch {
frames.collect {
// Delay is required for AnimationHandler to properly register a callback
yield()
val (frameNumber, callback) = it
callback?.doFrame(frameNumber)
}
}
}
fun stop() {
job?.cancel()
animator.getAnimationHandler().setProvider(null)
}
override fun postFrameCallback(cb: FrameCallback) {
frames.value = Pair(frameCount++, cb)
}
override fun postCommitCallback(runnable: Runnable) {}
override fun getFrameTime() = frameCount
override fun getFrameDelay() = 1L
override fun setFrameDelay(delay: Long) {}
} }
private class WtfHandler : TerribleFailureHandler { private class WtfHandler : TerribleFailureHandler {
@@ -317,9 +240,6 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
} }
companion object { companion object {
private const val MAX_TEST_DURATION = 100L private const val OWNER_NAME = "KeyguardTransitionRunner"
private const val ANIMATION_DURATION = 10L
private const val OWNER_NAME = "Test"
private val IMMEDIATE = Dispatchers.Main.immediate
} }
} }

View File

@@ -0,0 +1,140 @@
/*
* 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.animation.ValueAnimator
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Interpolators
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.WakeSleepReason
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.keyguard.util.KeyguardTransitionRunner
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
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.Mockito.verify
import org.mockito.MockitoAnnotations
/**
* Class for testing user journeys through the interactors. They will all be activated during setup,
* to ensure the expected transitions are still triggered.
*/
@SmallTest
@RunWith(JUnit4::class)
class KeyguardTransitionScenariosTest : SysuiTestCase() {
private lateinit var testScope: TestScope
private lateinit var keyguardRepository: FakeKeyguardRepository
private lateinit var shadeRepository: ShadeRepository
// Used to issue real transition steps for test input
private lateinit var runner: KeyguardTransitionRunner
private lateinit var transitionRepository: KeyguardTransitionRepository
// Used to verify transition requests for test output
@Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
private lateinit var lockscreenBouncerTransitionInteractor:
LockscreenBouncerTransitionInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testScope = TestScope()
keyguardRepository = FakeKeyguardRepository()
shadeRepository = FakeShadeRepository()
/* Used to issue full transition steps, to better simulate a real device */
transitionRepository = KeyguardTransitionRepositoryImpl()
runner = KeyguardTransitionRunner(transitionRepository)
lockscreenBouncerTransitionInteractor =
LockscreenBouncerTransitionInteractor(
scope = testScope,
keyguardInteractor = KeyguardInteractor(keyguardRepository),
shadeRepository = shadeRepository,
keyguardTransitionRepository = mockTransitionRepository,
keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
)
lockscreenBouncerTransitionInteractor.start()
}
@Test
fun `LOCKSCREEN to BOUNCER via bouncer showing call`() =
testScope.runTest {
// GIVEN a device that has at least woken up
keyguardRepository.setWakefulnessModel(startingToWake())
runCurrent()
// GIVEN a transition has run to LOCKSCREEN
runner.startTransition(
testScope,
TransitionInfo(
ownerName = "",
from = KeyguardState.OFF,
to = KeyguardState.LOCKSCREEN,
animator =
ValueAnimator().apply {
duration = 10
interpolator = Interpolators.LINEAR
},
)
)
runCurrent()
// WHEN the bouncer is set to show
keyguardRepository.setBouncerShowing(true)
runCurrent()
val info =
withArgCaptor<TransitionInfo> {
verify(mockTransitionRepository).startTransition(capture())
}
// THEN a transition to BOUNCER should occur
assertThat(info.ownerName).isEqualTo("LockscreenBouncerTransitionInteractor")
assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
assertThat(info.to).isEqualTo(KeyguardState.BOUNCER)
assertThat(info.animator).isNotNull()
coroutineContext.cancelChildren()
}
private fun startingToWake() =
WakefulnessModel(
WakefulnessState.STARTING_TO_WAKE,
true,
WakeSleepReason.OTHER,
WakeSleepReason.OTHER
)
}

View File

@@ -0,0 +1,101 @@
/*
* 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.util
import android.animation.AnimationHandler.AnimationFrameCallbackProvider
import android.animation.ValueAnimator
import android.view.Choreographer.FrameCallback
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.TransitionInfo
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.junit.Assert.fail
/**
* Gives direct control over ValueAnimator, in order to make transition tests deterministic. See
* [AnimationHandler]. Animators are required to be run on the main thread, so dispatch accordingly.
*/
class KeyguardTransitionRunner(
val repository: KeyguardTransitionRepository,
) : AnimationFrameCallbackProvider {
private var frameCount = 1L
private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
private var job: Job? = null
private var isTerminated = false
/**
* For transitions being directed by an animator. Will control the number of frames being
* generated so the values are deterministic.
*/
suspend fun startTransition(scope: CoroutineScope, info: TransitionInfo, maxFrames: Int = 100) {
// AnimationHandler uses ThreadLocal storage, and ValueAnimators MUST start from main
// thread
withContext(Dispatchers.Main) {
info.animator!!.getAnimationHandler().setProvider(this@KeyguardTransitionRunner)
}
job =
scope.launch {
frames.collect {
val (frameNumber, callback) = it
isTerminated = frameNumber >= maxFrames
if (!isTerminated) {
withContext(Dispatchers.Main) { callback?.doFrame(frameNumber) }
}
}
}
withContext(Dispatchers.Main) { repository.startTransition(info) }
waitUntilComplete(info.animator!!)
}
suspend private fun waitUntilComplete(animator: ValueAnimator) {
withContext(Dispatchers.Main) {
val startTime = System.currentTimeMillis()
while (!isTerminated && animator.isRunning()) {
delay(1)
if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
}
}
animator.getAnimationHandler().setProvider(null)
}
job?.cancel()
}
override fun postFrameCallback(cb: FrameCallback) {
frames.value = Pair(frameCount++, cb)
}
override fun postCommitCallback(runnable: Runnable) {}
override fun getFrameTime() = frameCount
override fun getFrameDelay() = 1L
override fun setFrameDelay(delay: Long) {}
companion object {
private const val MAX_TEST_DURATION = 100L
}
}

View File

@@ -120,6 +120,14 @@ class FakeKeyguardRepository : KeyguardRepository {
_dozeAmount.value = dozeAmount _dozeAmount.value = dozeAmount
} }
fun setWakefulnessModel(model: WakefulnessModel) {
_wakefulnessModel.value = model
}
fun setBouncerShowing(isShowing: Boolean) {
_isBouncerShowing.value = isShowing
}
fun setBiometricUnlockState(state: BiometricUnlockModel) { fun setBiometricUnlockState(state: BiometricUnlockModel) {
_biometricUnlockState.tryEmit(state) _biometricUnlockState.tryEmit(state)
} }

View File

@@ -0,0 +1,33 @@
/*
* 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.shade.data.repository
import com.android.systemui.shade.domain.model.ShadeModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
/** Fake implementation of [KeyguardRepository] */
class FakeShadeRepository : ShadeRepository {
private val _shadeModel = MutableStateFlow(ShadeModel())
override val shadeModel: Flow<ShadeModel> = _shadeModel
fun setShadeModel(model: ShadeModel) {
_shadeModel.value = model
}
}