Merge changes from topic "mpietal_rewire" into tm-qpr-dev
* changes: [DO NOT MERGE] Add KeyguardTransitionRepository [DO NOT MERGE] Rewire dozing for clock events
This commit is contained in:
@@ -23,12 +23,18 @@ import android.content.res.Resources
|
||||
import android.text.format.DateFormat
|
||||
import android.util.TypedValue
|
||||
import android.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.dagger.qualifiers.Background
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.flags.Flags.REGION_SAMPLING
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
|
||||
import com.android.systemui.lifecycle.repeatWhenAttached
|
||||
import com.android.systemui.plugins.ClockController
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.shared.regionsampling.RegionSamplingInstance
|
||||
import com.android.systemui.statusbar.policy.BatteryController
|
||||
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
|
||||
@@ -38,13 +44,20 @@ import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.Executor
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DisposableHandle
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
|
||||
* [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
|
||||
*/
|
||||
open class ClockEventController @Inject constructor(
|
||||
private val statusBarStateController: StatusBarStateController,
|
||||
private val keyguardInteractor: KeyguardInteractor,
|
||||
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
|
||||
private val broadcastDispatcher: BroadcastDispatcher,
|
||||
private val batteryController: BatteryController,
|
||||
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||
@@ -53,7 +66,7 @@ open class ClockEventController @Inject constructor(
|
||||
private val context: Context,
|
||||
@Main private val mainExecutor: Executor,
|
||||
@Background private val bgExecutor: Executor,
|
||||
private val featureFlags: FeatureFlags,
|
||||
private val featureFlags: FeatureFlags
|
||||
) {
|
||||
var clock: ClockController? = null
|
||||
set(value) {
|
||||
@@ -70,9 +83,9 @@ open class ClockEventController @Inject constructor(
|
||||
private var isCharging = false
|
||||
private var dozeAmount = 0f
|
||||
private var isKeyguardVisible = false
|
||||
|
||||
private val regionSamplingEnabled =
|
||||
featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
|
||||
private var isRegistered = false
|
||||
private var disposableHandle: DisposableHandle? = null
|
||||
private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
|
||||
|
||||
private fun updateColors() {
|
||||
if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
|
||||
@@ -165,15 +178,6 @@ open class ClockEventController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val statusBarStateListener = object : StatusBarStateController.StateListener {
|
||||
override fun onDozeAmountChanged(linear: Float, eased: Float) {
|
||||
clock?.animations?.doze(linear)
|
||||
|
||||
isDozing = linear > dozeAmount
|
||||
dozeAmount = linear
|
||||
}
|
||||
}
|
||||
|
||||
private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
|
||||
override fun onKeyguardVisibilityChanged(visible: Boolean) {
|
||||
isKeyguardVisible = visible
|
||||
@@ -195,13 +199,11 @@ open class ClockEventController @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
isDozing = statusBarStateController.isDozing
|
||||
}
|
||||
|
||||
fun registerListeners() {
|
||||
dozeAmount = statusBarStateController.dozeAmount
|
||||
isDozing = statusBarStateController.isDozing || dozeAmount != 0f
|
||||
fun registerListeners(parent: View) {
|
||||
if (isRegistered) {
|
||||
return
|
||||
}
|
||||
isRegistered = true
|
||||
|
||||
broadcastDispatcher.registerReceiver(
|
||||
localeBroadcastReceiver,
|
||||
@@ -210,17 +212,28 @@ open class ClockEventController @Inject constructor(
|
||||
configurationController.addCallback(configListener)
|
||||
batteryController.addCallback(batteryCallback)
|
||||
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
|
||||
statusBarStateController.addCallback(statusBarStateListener)
|
||||
smallRegionSampler?.startRegionSampler()
|
||||
largeRegionSampler?.startRegionSampler()
|
||||
disposableHandle = parent.repeatWhenAttached {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
listenForDozing(this)
|
||||
listenForDozeAmount(this)
|
||||
listenForDozeAmountTransition(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterListeners() {
|
||||
if (!isRegistered) {
|
||||
return
|
||||
}
|
||||
isRegistered = false
|
||||
|
||||
disposableHandle?.dispose()
|
||||
broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
|
||||
configurationController.removeCallback(configListener)
|
||||
batteryController.removeCallback(batteryCallback)
|
||||
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
|
||||
statusBarStateController.removeCallback(statusBarStateListener)
|
||||
smallRegionSampler?.stopRegionSampler()
|
||||
largeRegionSampler?.stopRegionSampler()
|
||||
}
|
||||
@@ -235,8 +248,39 @@ open class ClockEventController @Inject constructor(
|
||||
largeRegionSampler?.dump(pw)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = ClockEventController::class.simpleName
|
||||
private const val FORMAT_NUMBER = 1234567890
|
||||
@VisibleForTesting
|
||||
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
|
||||
return scope.launch {
|
||||
keyguardInteractor.dozeAmount.collect {
|
||||
dozeAmount = it
|
||||
clock?.animations?.doze(dozeAmount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
|
||||
return scope.launch {
|
||||
keyguardTransitionInteractor.aodToLockscreenTransition.collect {
|
||||
// Would eventually run this:
|
||||
// dozeAmount = it.value
|
||||
// clock?.animations?.doze(dozeAmount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun listenForDozing(scope: CoroutineScope): Job {
|
||||
return scope.launch {
|
||||
combine (
|
||||
keyguardInteractor.dozeAmount,
|
||||
keyguardInteractor.isDozing,
|
||||
) { localDozeAmount, localIsDozing ->
|
||||
localDozeAmount > dozeAmount || localIsDozing
|
||||
}
|
||||
.collect { localIsDozing ->
|
||||
isDozing = localIsDozing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
|
||||
protected void onViewAttached() {
|
||||
mClockRegistry.registerClockChangeListener(mClockChangedListener);
|
||||
setClock(mClockRegistry.createCurrentClock());
|
||||
mClockEventController.registerListeners();
|
||||
mClockEventController.registerListeners(mView);
|
||||
mKeyguardClockTopMargin =
|
||||
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry;
|
||||
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
||||
import com.android.systemui.keyguard.KeyguardViewMediator;
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
|
||||
import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
|
||||
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
|
||||
import com.android.systemui.navigationbar.NavigationModeController;
|
||||
import com.android.systemui.statusbar.NotificationShadeDepthController;
|
||||
@@ -72,6 +73,7 @@ import dagger.Provides;
|
||||
FalsingModule.class,
|
||||
KeyguardQuickAffordanceModule.class,
|
||||
KeyguardRepositoryModule.class,
|
||||
StartKeyguardTransitionModule.class,
|
||||
})
|
||||
public class KeyguardModule {
|
||||
/**
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall
|
||||
import com.android.systemui.common.shared.model.Position
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.doze.DozeHost
|
||||
import com.android.systemui.keyguard.shared.model.StatusBarState
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
import javax.inject.Inject
|
||||
@@ -85,6 +86,9 @@ interface KeyguardRepository {
|
||||
*/
|
||||
val dozeAmount: Flow<Float>
|
||||
|
||||
/** Observable for the [StatusBarState] */
|
||||
val statusBarState: Flow<StatusBarState>
|
||||
|
||||
/**
|
||||
* Returns `true` if the keyguard is showing; `false` otherwise.
|
||||
*
|
||||
@@ -185,6 +189,24 @@ constructor(
|
||||
return keyguardStateController.isShowing
|
||||
}
|
||||
|
||||
override val statusBarState: Flow<StatusBarState> = conflatedCallbackFlow {
|
||||
val callback =
|
||||
object : StatusBarStateController.StateListener {
|
||||
override fun onStateChanged(state: Int) {
|
||||
trySendWithFailureLogging(statusBarStateIntToObject(state), TAG, "state")
|
||||
}
|
||||
}
|
||||
|
||||
statusBarStateController.addCallback(callback)
|
||||
trySendWithFailureLogging(
|
||||
statusBarStateIntToObject(statusBarStateController.getState()),
|
||||
TAG,
|
||||
"initial state"
|
||||
)
|
||||
|
||||
awaitClose { statusBarStateController.removeCallback(callback) }
|
||||
}
|
||||
|
||||
override fun setAnimateDozingTransitions(animate: Boolean) {
|
||||
_animateBottomAreaDozingTransitions.value = animate
|
||||
}
|
||||
@@ -197,6 +219,15 @@ constructor(
|
||||
_clockPosition.value = Position(x, y)
|
||||
}
|
||||
|
||||
private fun statusBarStateIntToObject(value: Int): StatusBarState {
|
||||
return when (value) {
|
||||
0 -> StatusBarState.SHADE
|
||||
1 -> StatusBarState.KEYGUARD
|
||||
2 -> StatusBarState.SHADE_LOCKED
|
||||
else -> throw IllegalArgumentException("Invalid StatusBarState value: $value")
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "KeyguardRepositoryImpl"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ValueAnimator
|
||||
import android.animation.ValueAnimator.AnimatorUpdateListener
|
||||
import android.annotation.FloatRange
|
||||
import android.util.Log
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState
|
||||
import com.android.systemui.keyguard.shared.model.TransitionInfo
|
||||
import com.android.systemui.keyguard.shared.model.TransitionState
|
||||
import com.android.systemui.keyguard.shared.model.TransitionStep
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
|
||||
@SysUISingleton
|
||||
class KeyguardTransitionRepository @Inject constructor() {
|
||||
/*
|
||||
* Each transition between [KeyguardState]s will have an associated Flow.
|
||||
* In order to collect these events, clients should call [transition].
|
||||
*/
|
||||
private val _transitions = MutableStateFlow(TransitionStep())
|
||||
val transitions = _transitions.asStateFlow()
|
||||
|
||||
/* Information about the active transition. */
|
||||
private var currentTransitionInfo: TransitionInfo? = null
|
||||
/*
|
||||
* When manual control of the transition is requested, a unique [UUID] is used as the handle
|
||||
* to permit calls to [updateTransition]
|
||||
*/
|
||||
private var updateTransitionId: UUID? = null
|
||||
|
||||
/**
|
||||
* Interactors that require information about changes between [KeyguardState]s will call this to
|
||||
* register themselves for flowable [TransitionStep]s when that transition occurs.
|
||||
*/
|
||||
fun transition(from: KeyguardState, to: KeyguardState): Flow<TransitionStep> {
|
||||
return transitions.filter { step -> step.from == from && step.to == to }
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin a transition from one state to another. The [info.from] must match
|
||||
* [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid
|
||||
* unplanned transitions.
|
||||
*/
|
||||
fun startTransition(info: TransitionInfo): UUID? {
|
||||
if (currentTransitionInfo != null) {
|
||||
// Open questions:
|
||||
// * Queue of transitions? buffer of 1?
|
||||
// * Are transitions cancellable if a new one is triggered?
|
||||
// * What validation does this need to do?
|
||||
Log.wtf(TAG, "Transition still active: $currentTransitionInfo")
|
||||
return null
|
||||
}
|
||||
currentTransitionInfo?.animator?.cancel()
|
||||
|
||||
currentTransitionInfo = info
|
||||
info.animator?.let { animator ->
|
||||
// An animator was provided, so use it to run the transition
|
||||
animator.setFloatValues(0f, 1f)
|
||||
val updateListener =
|
||||
object : AnimatorUpdateListener {
|
||||
override fun onAnimationUpdate(animation: ValueAnimator) {
|
||||
emitTransition(
|
||||
info,
|
||||
(animation.getAnimatedValue() as Float),
|
||||
TransitionState.RUNNING
|
||||
)
|
||||
}
|
||||
}
|
||||
val adapter =
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationStart(animation: Animator) {
|
||||
Log.i(TAG, "Starting transition: $info")
|
||||
emitTransition(info, 0f, TransitionState.STARTED)
|
||||
}
|
||||
override fun onAnimationCancel(animation: Animator) {
|
||||
Log.i(TAG, "Cancelling transition: $info")
|
||||
}
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
Log.i(TAG, "Ending transition: $info")
|
||||
emitTransition(info, 1f, TransitionState.FINISHED)
|
||||
animator.removeListener(this)
|
||||
animator.removeUpdateListener(updateListener)
|
||||
}
|
||||
}
|
||||
animator.addListener(adapter)
|
||||
animator.addUpdateListener(updateListener)
|
||||
animator.start()
|
||||
return@startTransition null
|
||||
}
|
||||
?: run {
|
||||
Log.i(TAG, "Starting transition (manual): $info")
|
||||
emitTransition(info, 0f, TransitionState.STARTED)
|
||||
|
||||
// No animator, so it's manual. Provide a mechanism to callback
|
||||
updateTransitionId = UUID.randomUUID()
|
||||
return@startTransition updateTransitionId
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows manual control of a transition. When calling [startTransition], the consumer must pass
|
||||
* in a null animator. In return, it will get a unique [UUID] that will be validated to allow
|
||||
* further updates.
|
||||
*
|
||||
* When the transition is over, TransitionState.FINISHED must be passed into the [state]
|
||||
* parameter.
|
||||
*/
|
||||
fun updateTransition(
|
||||
transitionId: UUID,
|
||||
@FloatRange(from = 0.0, to = 1.0) value: Float,
|
||||
state: TransitionState
|
||||
) {
|
||||
if (updateTransitionId != transitionId) {
|
||||
Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
|
||||
return
|
||||
}
|
||||
|
||||
if (currentTransitionInfo == null) {
|
||||
Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'")
|
||||
return
|
||||
}
|
||||
|
||||
currentTransitionInfo?.let { info ->
|
||||
if (state == TransitionState.FINISHED) {
|
||||
updateTransitionId = null
|
||||
Log.i(TAG, "Ending transition: $info")
|
||||
}
|
||||
|
||||
emitTransition(info, value, state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun emitTransition(
|
||||
info: TransitionInfo,
|
||||
value: Float,
|
||||
transitionState: TransitionState
|
||||
) {
|
||||
if (transitionState == TransitionState.FINISHED) {
|
||||
currentTransitionInfo = null
|
||||
}
|
||||
_transitions.value = TransitionStep(info.from, info.to, value, transitionState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "KeyguardTransitionRepository"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 com.android.systemui.animation.Interpolators
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState
|
||||
import com.android.systemui.keyguard.shared.model.TransitionInfo
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@SysUISingleton
|
||||
class AodLockscreenTransitionInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val scope: CoroutineScope,
|
||||
private val keyguardRepository: KeyguardRepository,
|
||||
private val keyguardTransitionRepository: KeyguardTransitionRepository,
|
||||
) : TransitionInteractor("AOD<->LOCKSCREEN") {
|
||||
|
||||
override fun start() {
|
||||
scope.launch {
|
||||
keyguardRepository.isDozing.collect { isDozing ->
|
||||
if (isDozing) {
|
||||
keyguardTransitionRepository.startTransition(
|
||||
TransitionInfo(
|
||||
name,
|
||||
KeyguardState.LOCKSCREEN,
|
||||
KeyguardState.AOD,
|
||||
getAnimator(),
|
||||
)
|
||||
)
|
||||
} else {
|
||||
keyguardTransitionRepository.startTransition(
|
||||
TransitionInfo(
|
||||
name,
|
||||
KeyguardState.AOD,
|
||||
KeyguardState.LOCKSCREEN,
|
||||
getAnimator(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAnimator(): ValueAnimator {
|
||||
return ValueAnimator().apply {
|
||||
setInterpolator(Interpolators.LINEAR)
|
||||
setDuration(TRANSITION_DURATION_MS)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TRANSITION_DURATION_MS = 500L
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ constructor(
|
||||
val dozeAmount: Flow<Float> = repository.dozeAmount
|
||||
/** Whether the system is in doze mode. */
|
||||
val isDozing: Flow<Boolean> = repository.isDozing
|
||||
/** Whether the keyguard is showing ot not. */
|
||||
/** Whether the keyguard is showing to not. */
|
||||
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
|
||||
|
||||
fun isKeyguardShowing(): Boolean {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.util.Log
|
||||
import com.android.systemui.CoreStartable
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import java.util.Set
|
||||
import javax.inject.Inject
|
||||
|
||||
@SysUISingleton
|
||||
class KeyguardTransitionCoreStartable
|
||||
@Inject
|
||||
constructor(
|
||||
private val interactors: Set<TransitionInteractor>,
|
||||
) : CoreStartable {
|
||||
|
||||
override fun start() {
|
||||
// By listing the interactors in a when, the compiler will help enforce all classes
|
||||
// extending the sealed class [TransitionInteractor] will be initialized.
|
||||
interactors.forEach {
|
||||
// `when` needs to be an expression in order for the compiler to enforce it being
|
||||
// exhaustive
|
||||
val ret =
|
||||
when (it) {
|
||||
is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
|
||||
is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
|
||||
}
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "KeyguardTransitionCoreStartable"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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 com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
|
||||
import com.android.systemui.keyguard.shared.model.TransitionStep
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Encapsulates business-logic related to the keyguard transitions. */
|
||||
@SysUISingleton
|
||||
class KeyguardTransitionInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
repository: KeyguardTransitionRepository,
|
||||
) {
|
||||
/** AOD->LOCKSCREEN transition information. */
|
||||
val aodToLockscreenTransition: Flow<TransitionStep> = repository.transition(AOD, LOCKSCREEN)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.dagger.qualifiers.Application
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
|
||||
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.TransitionInfo
|
||||
import com.android.systemui.keyguard.shared.model.TransitionState
|
||||
import com.android.systemui.shade.data.repository.ShadeRepository
|
||||
import com.android.systemui.util.kotlin.sample
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@SysUISingleton
|
||||
class LockscreenBouncerTransitionInteractor
|
||||
@Inject
|
||||
constructor(
|
||||
@Application private val scope: CoroutineScope,
|
||||
private val keyguardRepository: KeyguardRepository,
|
||||
private val shadeRepository: ShadeRepository,
|
||||
private val keyguardTransitionRepository: KeyguardTransitionRepository,
|
||||
) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
|
||||
|
||||
private var transitionId: UUID? = null
|
||||
|
||||
override fun start() {
|
||||
scope.launch {
|
||||
shadeRepository.shadeModel.sample(
|
||||
combine(
|
||||
keyguardTransitionRepository.transitions,
|
||||
keyguardRepository.statusBarState,
|
||||
) { transitions, statusBarState ->
|
||||
Pair(transitions, statusBarState)
|
||||
}
|
||||
) { shadeModel, pair ->
|
||||
val (transitions, statusBarState) = pair
|
||||
|
||||
val id = transitionId
|
||||
if (id != null) {
|
||||
// An existing `id` means a transition is started, and calls to
|
||||
// `updateTransition` will control it until FINISHED
|
||||
keyguardTransitionRepository.updateTransition(
|
||||
id,
|
||||
shadeModel.expansionAmount,
|
||||
if (shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f) {
|
||||
transitionId = null
|
||||
TransitionState.FINISHED
|
||||
} else {
|
||||
TransitionState.RUNNING
|
||||
}
|
||||
)
|
||||
} else {
|
||||
// TODO (b/251849525): Remove statusbarstate check when that state is integrated
|
||||
// into KeyguardTransitionRepository
|
||||
val isOnLockscreen =
|
||||
transitions.transitionState == TransitionState.FINISHED &&
|
||||
transitions.to == KeyguardState.LOCKSCREEN
|
||||
if (
|
||||
isOnLockscreen &&
|
||||
shadeModel.isUserDragging &&
|
||||
statusBarState != SHADE_LOCKED
|
||||
) {
|
||||
transitionId =
|
||||
keyguardTransitionRepository.startTransition(
|
||||
TransitionInfo(
|
||||
ownerName = name,
|
||||
from = KeyguardState.LOCKSCREEN,
|
||||
to = KeyguardState.BOUNCER,
|
||||
animator = null,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 com.android.systemui.CoreStartable
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import dagger.multibindings.IntoSet
|
||||
|
||||
@Module
|
||||
abstract class StartKeyguardTransitionModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(KeyguardTransitionCoreStartable::class)
|
||||
abstract fun bind(impl: KeyguardTransitionCoreStartable): CoreStartable
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun lockscreenBouncer(
|
||||
impl: LockscreenBouncerTransitionInteractor
|
||||
): TransitionInteractor
|
||||
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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
|
||||
/**
|
||||
* Each TransitionInteractor is responsible for determining under which conditions to notify
|
||||
* [KeyguardTransitionRepository] to signal a transition. When (and if) the transition occurs is
|
||||
* determined by [KeyguardTransitionRepository].
|
||||
*
|
||||
* [name] field should be a unique identifiable string representing this state, used primarily for
|
||||
* logging
|
||||
*
|
||||
* MUST list implementing classes in dagger module [StartKeyguardTransitionModule] and also in the
|
||||
* 'when' clause of [KeyguardTransitionCoreStartable]
|
||||
*/
|
||||
sealed class TransitionInteractor(val name: String) {
|
||||
|
||||
abstract fun start()
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.shared.model
|
||||
|
||||
/** List of all possible states to transition to/from */
|
||||
enum class KeyguardState {
|
||||
/** For initialization only */
|
||||
NONE,
|
||||
/* Always-on Display. The device is in a low-power mode with a minimal UI visible */
|
||||
AOD,
|
||||
/*
|
||||
* The security screen prompt UI, containing PIN, Password, Pattern, and all FPS
|
||||
* (Fingerprint Sensor) variations, for the user to verify their credentials
|
||||
*/
|
||||
BOUNCER,
|
||||
/*
|
||||
* Device is actively displaying keyguard UI and is not in low-power mode. Device may be
|
||||
* unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
|
||||
*/
|
||||
LOCKSCREEN,
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.shared.model
|
||||
|
||||
/** See [com.android.systemui.statusbar.StatusBarState] for definitions */
|
||||
enum class StatusBarState {
|
||||
SHADE,
|
||||
KEYGUARD,
|
||||
SHADE_LOCKED,
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.shared.model
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
|
||||
/** Tracks who is controlling the current transition, and how to run it. */
|
||||
data class TransitionInfo(
|
||||
val ownerName: String,
|
||||
val from: KeyguardState,
|
||||
val to: KeyguardState,
|
||||
val animator: ValueAnimator?, // 'null' animator signal manual control
|
||||
) {
|
||||
override fun toString(): String =
|
||||
"TransitionInfo(ownerName=$ownerName, from=$from, to=$to, " +
|
||||
(if (animator != null) {
|
||||
"animated"
|
||||
} else {
|
||||
"manual"
|
||||
}) +
|
||||
")"
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.shared.model
|
||||
|
||||
/** Possible states for a running transition between [State] */
|
||||
enum class TransitionState {
|
||||
NONE,
|
||||
STARTED,
|
||||
RUNNING,
|
||||
FINISHED
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.shared.model
|
||||
|
||||
/** This information will flow from the [KeyguardTransitionRepository] to control the UI layer */
|
||||
data class TransitionStep(
|
||||
val from: KeyguardState = KeyguardState.NONE,
|
||||
val to: KeyguardState = KeyguardState.NONE,
|
||||
val value: Float = 0f, // constrained [0.0, 1.0]
|
||||
val transitionState: TransitionState = TransitionState.NONE,
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.shade.ShadeExpansionChangeEvent
|
||||
import com.android.systemui.shade.ShadeExpansionListener
|
||||
import com.android.systemui.shade.ShadeExpansionStateManager
|
||||
import com.android.systemui.shade.domain.model.ShadeModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
||||
/** Business logic for shade interactions */
|
||||
@SysUISingleton
|
||||
class ShadeRepository @Inject constructor(shadeExpansionStateManager: ShadeExpansionStateManager) {
|
||||
|
||||
val shadeModel: Flow<ShadeModel> =
|
||||
conflatedCallbackFlow {
|
||||
val callback =
|
||||
object : ShadeExpansionListener {
|
||||
override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) {
|
||||
// Don't propagate ShadeExpansionChangeEvent.dragDownPxAmount field.
|
||||
// It is too noisy and produces extra events that consumers won't care
|
||||
// about
|
||||
val info =
|
||||
ShadeModel(
|
||||
expansionAmount = event.fraction,
|
||||
isExpanded = event.expanded,
|
||||
isUserDragging = event.tracking
|
||||
)
|
||||
trySendWithFailureLogging(info, TAG, "updated shade expansion info")
|
||||
}
|
||||
}
|
||||
|
||||
shadeExpansionStateManager.addExpansionListener(callback)
|
||||
trySendWithFailureLogging(ShadeModel(), TAG, "initial shade expansion info")
|
||||
|
||||
awaitClose { shadeExpansionStateManager.removeExpansionListener(callback) }
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ShadeRepository"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.domain.model
|
||||
|
||||
import android.annotation.FloatRange
|
||||
|
||||
/** Information about shade (NotificationPanel) expansion */
|
||||
data class ShadeModel(
|
||||
/** 0 when collapsed, 1 when fully expanded. */
|
||||
@FloatRange(from = 0.0, to = 1.0) val expansionAmount: Float = 0f,
|
||||
/** Whether the panel should be considered expanded */
|
||||
val isExpanded: Boolean = false,
|
||||
/** Whether the user is actively dragging the panel. */
|
||||
val isUserDragging: Boolean = false,
|
||||
)
|
||||
@@ -17,17 +17,21 @@ package com.android.keyguard
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||
import com.android.systemui.flags.FeatureFlags
|
||||
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
|
||||
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
|
||||
import com.android.systemui.plugins.ClockAnimations
|
||||
import com.android.systemui.plugins.ClockController
|
||||
import com.android.systemui.plugins.ClockEvents
|
||||
import com.android.systemui.plugins.ClockFaceController
|
||||
import com.android.systemui.plugins.ClockFaceEvents
|
||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||
import com.android.systemui.statusbar.policy.BatteryController
|
||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.util.mockito.any
|
||||
@@ -37,6 +41,9 @@ import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import java.util.TimeZone
|
||||
import java.util.concurrent.Executor
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.yield
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
@@ -57,7 +64,7 @@ import org.mockito.junit.MockitoJUnit
|
||||
class ClockEventControllerTest : SysuiTestCase() {
|
||||
|
||||
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||
@Mock private lateinit var statusBarStateController: StatusBarStateController
|
||||
@Mock private lateinit var keyguardInteractor: KeyguardInteractor
|
||||
@Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
|
||||
@Mock private lateinit var batteryController: BatteryController
|
||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||
@@ -72,8 +79,11 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
@Mock private lateinit var largeClockController: ClockFaceController
|
||||
@Mock private lateinit var smallClockEvents: ClockFaceEvents
|
||||
@Mock private lateinit var largeClockEvents: ClockFaceEvents
|
||||
@Mock private lateinit var parentView: View
|
||||
@Mock private lateinit var transitionRepository: KeyguardTransitionRepository
|
||||
private lateinit var repository: FakeKeyguardRepository
|
||||
|
||||
private lateinit var clockEventController: ClockEventController
|
||||
private lateinit var underTest: ClockEventController
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
@@ -86,8 +96,11 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
whenever(clock.events).thenReturn(events)
|
||||
whenever(clock.animations).thenReturn(animations)
|
||||
|
||||
clockEventController = ClockEventController(
|
||||
statusBarStateController,
|
||||
repository = FakeKeyguardRepository()
|
||||
|
||||
underTest = ClockEventController(
|
||||
KeyguardInteractor(repository = repository),
|
||||
KeyguardTransitionInteractor(repository = transitionRepository),
|
||||
broadcastDispatcher,
|
||||
batteryController,
|
||||
keyguardUpdateMonitor,
|
||||
@@ -98,31 +111,33 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
bgExecutor,
|
||||
featureFlags
|
||||
)
|
||||
underTest.clock = clock
|
||||
|
||||
runBlocking(IMMEDIATE) {
|
||||
underTest.registerListeners(parentView)
|
||||
|
||||
repository.setDozing(true)
|
||||
repository.setDozeAmount(1f)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clockSet_validateInitialization() {
|
||||
clockEventController.clock = clock
|
||||
|
||||
verify(clock).initialize(any(), anyFloat(), anyFloat())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun clockUnset_validateState() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.clock = null
|
||||
underTest.clock = null
|
||||
|
||||
assertEquals(clockEventController.clock, null)
|
||||
assertEquals(underTest.clock, null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun themeChanged_verifyClockPaletteUpdated() {
|
||||
clockEventController.clock = clock
|
||||
fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
|
||||
verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
|
||||
verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
|
||||
|
||||
clockEventController.registerListeners()
|
||||
|
||||
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
|
||||
verify(configurationController).addCallback(capture(captor))
|
||||
captor.value.onThemeChanged()
|
||||
@@ -131,13 +146,10 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fontChanged_verifyFontSizeUpdated() {
|
||||
clockEventController.clock = clock
|
||||
fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) {
|
||||
verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
|
||||
verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
|
||||
|
||||
clockEventController.registerListeners()
|
||||
|
||||
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
|
||||
verify(configurationController).addCallback(capture(captor))
|
||||
captor.value.onDensityOrFontScaleChanged()
|
||||
@@ -146,10 +158,7 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.registerListeners()
|
||||
|
||||
fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
|
||||
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
|
||||
verify(batteryController).addCallback(capture(batteryCaptor))
|
||||
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||
@@ -161,26 +170,21 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.registerListeners()
|
||||
fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
|
||||
verify(batteryController).addCallback(capture(batteryCaptor))
|
||||
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||
verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
|
||||
keyguardCaptor.value.onKeyguardVisibilityChanged(true)
|
||||
batteryCaptor.value.onBatteryLevelChanged(10, false, true)
|
||||
batteryCaptor.value.onBatteryLevelChanged(10, false, true)
|
||||
|
||||
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
|
||||
verify(batteryController).addCallback(capture(batteryCaptor))
|
||||
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||
verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
|
||||
keyguardCaptor.value.onKeyguardVisibilityChanged(true)
|
||||
batteryCaptor.value.onBatteryLevelChanged(10, false, true)
|
||||
batteryCaptor.value.onBatteryLevelChanged(10, false, true)
|
||||
|
||||
verify(animations, times(1)).charge()
|
||||
}
|
||||
verify(animations, times(1)).charge()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.registerListeners()
|
||||
|
||||
fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
|
||||
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
|
||||
verify(batteryController).addCallback(capture(batteryCaptor))
|
||||
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||
@@ -192,25 +196,20 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.registerListeners()
|
||||
fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
|
||||
verify(batteryController).addCallback(capture(batteryCaptor))
|
||||
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||
verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
|
||||
keyguardCaptor.value.onKeyguardVisibilityChanged(true)
|
||||
batteryCaptor.value.onBatteryLevelChanged(10, false, false)
|
||||
|
||||
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
|
||||
verify(batteryController).addCallback(capture(batteryCaptor))
|
||||
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||
verify(keyguardUpdateMonitor).registerCallback(capture(keyguardCaptor))
|
||||
keyguardCaptor.value.onKeyguardVisibilityChanged(true)
|
||||
batteryCaptor.value.onBatteryLevelChanged(10, false, false)
|
||||
|
||||
verify(animations, never()).charge()
|
||||
}
|
||||
verify(animations, never()).charge()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun localeCallback_verifyClockNotified() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.registerListeners()
|
||||
|
||||
fun localeCallback_verifyClockNotified() = runBlocking(IMMEDIATE) {
|
||||
val captor = argumentCaptor<BroadcastReceiver>()
|
||||
verify(broadcastDispatcher).registerReceiver(
|
||||
capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
|
||||
@@ -221,10 +220,7 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keyguardCallback_visibilityChanged_clockDozeCalled() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.registerListeners()
|
||||
|
||||
fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) {
|
||||
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
||||
|
||||
@@ -236,10 +232,7 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keyguardCallback_timeFormat_clockNotified() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.registerListeners()
|
||||
|
||||
fun keyguardCallback_timeFormat_clockNotified() = runBlocking(IMMEDIATE) {
|
||||
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
||||
captor.value.onTimeFormatChanged("12h")
|
||||
@@ -248,11 +241,8 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keyguardCallback_timezoneChanged_clockNotified() {
|
||||
fun keyguardCallback_timezoneChanged_clockNotified() = runBlocking(IMMEDIATE) {
|
||||
val mockTimeZone = mock<TimeZone>()
|
||||
clockEventController.clock = clock
|
||||
clockEventController.registerListeners()
|
||||
|
||||
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
||||
captor.value.onTimeZoneChanged(mockTimeZone)
|
||||
@@ -261,10 +251,7 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keyguardCallback_userSwitched_clockNotified() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.registerListeners()
|
||||
|
||||
fun keyguardCallback_userSwitched_clockNotified() = runBlocking(IMMEDIATE) {
|
||||
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
||||
captor.value.onUserSwitchComplete(10)
|
||||
@@ -273,25 +260,27 @@ class ClockEventControllerTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun keyguardCallback_verifyKeyguardChanged() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.registerListeners()
|
||||
fun keyguardCallback_verifyKeyguardChanged() = runBlocking(IMMEDIATE) {
|
||||
val job = underTest.listenForDozeAmount(this)
|
||||
repository.setDozeAmount(0.4f)
|
||||
|
||||
val captor = argumentCaptor<StatusBarStateController.StateListener>()
|
||||
verify(statusBarStateController).addCallback(capture(captor))
|
||||
captor.value.onDozeAmountChanged(0.4f, 0.6f)
|
||||
yield()
|
||||
|
||||
verify(animations).doze(0.4f)
|
||||
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unregisterListeners_validate() {
|
||||
clockEventController.clock = clock
|
||||
clockEventController.unregisterListeners()
|
||||
fun unregisterListeners_validate() = runBlocking(IMMEDIATE) {
|
||||
underTest.unregisterListeners()
|
||||
verify(broadcastDispatcher).unregisterReceiver(any())
|
||||
verify(configurationController).removeCallback(any())
|
||||
verify(batteryController).removeCallback(any())
|
||||
verify(keyguardUpdateMonitor).removeCallback(any())
|
||||
verify(statusBarStateController).removeCallback(any())
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +265,6 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase {
|
||||
private void verifyAttachment(VerificationMode times) {
|
||||
verify(mClockRegistry, times).registerClockChangeListener(
|
||||
any(ClockRegistry.ClockChangeListener.class));
|
||||
verify(mClockEventController, times).registerListeners();
|
||||
verify(mClockEventController, times).registerListeners(mView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* 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 android.animation.AnimationHandler.AnimationFrameCallbackProvider
|
||||
import android.animation.ValueAnimator
|
||||
import android.util.Log
|
||||
import android.util.Log.TerribleFailure
|
||||
import android.util.Log.TerribleFailureHandler
|
||||
import android.view.Choreographer.FrameCallback
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.animation.Interpolators
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState.BOUNCER
|
||||
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
|
||||
import com.android.systemui.keyguard.shared.model.TransitionInfo
|
||||
import com.android.systemui.keyguard.shared.model.TransitionState
|
||||
import com.android.systemui.keyguard.shared.model.TransitionStep
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
import java.util.UUID
|
||||
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.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.yield
|
||||
import org.junit.After
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.JUnit4
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnit4::class)
|
||||
class KeyguardTransitionRepositoryTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: KeyguardTransitionRepository
|
||||
private lateinit var oldWtfHandler: TerribleFailureHandler
|
||||
private lateinit var wtfHandler: WtfHandler
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
underTest = KeyguardTransitionRepository()
|
||||
wtfHandler = WtfHandler()
|
||||
oldWtfHandler = Log.setWtfHandler(wtfHandler)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
oldWtfHandler?.let { Log.setWtfHandler(it) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `startTransition runs animator to completion`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val (animator, provider) = setupAnimator(this)
|
||||
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
|
||||
|
||||
underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, animator))
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
while (animator.isRunning()) {
|
||||
yield()
|
||||
if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
|
||||
fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
|
||||
}
|
||||
}
|
||||
|
||||
assertSteps(steps, listWithStep(BigDecimal(.1)))
|
||||
|
||||
job.cancel()
|
||||
provider.stop()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `startTransition called during another transition fails`() {
|
||||
underTest.startTransition(TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, null))
|
||||
underTest.startTransition(TransitionInfo(OWNER_NAME, LOCKSCREEN, BOUNCER, null))
|
||||
|
||||
assertThat(wtfHandler.failed).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Null animator enables manual control with updateTransition`() =
|
||||
runBlocking(IMMEDIATE) {
|
||||
val steps = mutableListOf<TransitionStep>()
|
||||
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
|
||||
|
||||
val uuid =
|
||||
underTest.startTransition(
|
||||
TransitionInfo(
|
||||
ownerName = OWNER_NAME,
|
||||
from = AOD,
|
||||
to = LOCKSCREEN,
|
||||
animator = null,
|
||||
)
|
||||
)
|
||||
|
||||
checkNotNull(uuid).let {
|
||||
underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
|
||||
underTest.updateTransition(it, 1f, TransitionState.FINISHED)
|
||||
}
|
||||
|
||||
assertThat(steps.size).isEqualTo(3)
|
||||
assertThat(steps[0])
|
||||
.isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
|
||||
assertThat(steps[1])
|
||||
.isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
|
||||
assertThat(steps[2])
|
||||
.isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
|
||||
job.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Attempt to manually update transition with invalid UUID throws exception`() {
|
||||
underTest.updateTransition(UUID.randomUUID(), 0f, TransitionState.RUNNING)
|
||||
assertThat(wtfHandler.failed).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Attempt to manually update transition after FINISHED state throws exception`() {
|
||||
val uuid =
|
||||
underTest.startTransition(
|
||||
TransitionInfo(
|
||||
ownerName = OWNER_NAME,
|
||||
from = AOD,
|
||||
to = LOCKSCREEN,
|
||||
animator = null,
|
||||
)
|
||||
)
|
||||
|
||||
checkNotNull(uuid).let {
|
||||
underTest.updateTransition(it, 1f, TransitionState.FINISHED)
|
||||
underTest.updateTransition(it, 0.5f, TransitionState.RUNNING)
|
||||
}
|
||||
assertThat(wtfHandler.failed).isTrue()
|
||||
}
|
||||
|
||||
private fun listWithStep(step: BigDecimal): List<BigDecimal> {
|
||||
val steps = mutableListOf<BigDecimal>()
|
||||
|
||||
var i = BigDecimal.ZERO
|
||||
while (i.compareTo(BigDecimal.ONE) <= 0) {
|
||||
steps.add(i)
|
||||
i = (i + step).setScale(2, RoundingMode.HALF_UP)
|
||||
}
|
||||
|
||||
return steps
|
||||
}
|
||||
|
||||
private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
|
||||
// + 2 accounts for start and finish of automated transition
|
||||
assertThat(steps.size).isEqualTo(fractions.size + 2)
|
||||
|
||||
assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
|
||||
fractions.forEachIndexed { index, fraction ->
|
||||
assertThat(steps[index + 1])
|
||||
.isEqualTo(
|
||||
TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
|
||||
)
|
||||
}
|
||||
assertThat(steps[steps.size - 1])
|
||||
.isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
|
||||
|
||||
assertThat(wtfHandler.failed).isFalse()
|
||||
}
|
||||
|
||||
private fun setupAnimator(
|
||||
scope: CoroutineScope
|
||||
): Pair<ValueAnimator, TestFrameCallbackProvider> {
|
||||
val animator =
|
||||
ValueAnimator().apply {
|
||||
setInterpolator(Interpolators.LINEAR)
|
||||
setDuration(ANIMATION_DURATION)
|
||||
}
|
||||
|
||||
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
|
||||
delay(1)
|
||||
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 {
|
||||
var failed = false
|
||||
override fun onTerribleFailure(tag: String, what: TerribleFailure, system: Boolean) {
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MAX_TEST_DURATION = 100L
|
||||
private const val ANIMATION_DURATION = 10L
|
||||
private const val OWNER_NAME = "Test"
|
||||
private val IMMEDIATE = Dispatchers.Main.immediate
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package com.android.systemui.keyguard.data.repository
|
||||
|
||||
import com.android.systemui.common.shared.model.Position
|
||||
import com.android.systemui.keyguard.shared.model.StatusBarState
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
@@ -44,6 +45,9 @@ class FakeKeyguardRepository : KeyguardRepository {
|
||||
private val _dozeAmount = MutableStateFlow(0f)
|
||||
override val dozeAmount: Flow<Float> = _dozeAmount
|
||||
|
||||
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
|
||||
override val statusBarState: Flow<StatusBarState> = _statusBarState
|
||||
|
||||
override fun isKeyguardShowing(): Boolean {
|
||||
return _isKeyguardShowing.value
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user