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.text.format.DateFormat
|
||||||
import android.util.TypedValue
|
import android.util.TypedValue
|
||||||
import android.view.View
|
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.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.flags.FeatureFlags
|
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.ClockController
|
||||||
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
|
||||||
import com.android.systemui.shared.regionsampling.RegionSamplingInstance
|
import com.android.systemui.shared.regionsampling.RegionSamplingInstance
|
||||||
import com.android.systemui.statusbar.policy.BatteryController
|
import com.android.systemui.statusbar.policy.BatteryController
|
||||||
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
|
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
|
||||||
@@ -38,13 +44,20 @@ import java.util.Locale
|
|||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import java.util.concurrent.Executor
|
import java.util.concurrent.Executor
|
||||||
import javax.inject.Inject
|
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
|
* Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
|
||||||
* [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
|
* [KeyguardClockSwitchController]. Functionality is forked from [AnimatableClockController].
|
||||||
*/
|
*/
|
||||||
open class ClockEventController @Inject constructor(
|
open class ClockEventController @Inject constructor(
|
||||||
private val statusBarStateController: StatusBarStateController,
|
private val keyguardInteractor: KeyguardInteractor,
|
||||||
|
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
|
||||||
private val broadcastDispatcher: BroadcastDispatcher,
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
private val batteryController: BatteryController,
|
private val batteryController: BatteryController,
|
||||||
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||||
@@ -53,7 +66,7 @@ open class ClockEventController @Inject constructor(
|
|||||||
private val context: Context,
|
private val context: Context,
|
||||||
@Main private val mainExecutor: Executor,
|
@Main private val mainExecutor: Executor,
|
||||||
@Background private val bgExecutor: Executor,
|
@Background private val bgExecutor: Executor,
|
||||||
private val featureFlags: FeatureFlags,
|
private val featureFlags: FeatureFlags
|
||||||
) {
|
) {
|
||||||
var clock: ClockController? = null
|
var clock: ClockController? = null
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -70,9 +83,9 @@ open class ClockEventController @Inject constructor(
|
|||||||
private var isCharging = false
|
private var isCharging = false
|
||||||
private var dozeAmount = 0f
|
private var dozeAmount = 0f
|
||||||
private var isKeyguardVisible = false
|
private var isKeyguardVisible = false
|
||||||
|
private var isRegistered = false
|
||||||
private val regionSamplingEnabled =
|
private var disposableHandle: DisposableHandle? = null
|
||||||
featureFlags.isEnabled(com.android.systemui.flags.Flags.REGION_SAMPLING)
|
private val regionSamplingEnabled = featureFlags.isEnabled(REGION_SAMPLING)
|
||||||
|
|
||||||
private fun updateColors() {
|
private fun updateColors() {
|
||||||
if (regionSamplingEnabled && smallRegionSampler != null && largeRegionSampler != null) {
|
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() {
|
private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
|
||||||
override fun onKeyguardVisibilityChanged(visible: Boolean) {
|
override fun onKeyguardVisibilityChanged(visible: Boolean) {
|
||||||
isKeyguardVisible = visible
|
isKeyguardVisible = visible
|
||||||
@@ -195,13 +199,11 @@ open class ClockEventController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
fun registerListeners(parent: View) {
|
||||||
isDozing = statusBarStateController.isDozing
|
if (isRegistered) {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
fun registerListeners() {
|
isRegistered = true
|
||||||
dozeAmount = statusBarStateController.dozeAmount
|
|
||||||
isDozing = statusBarStateController.isDozing || dozeAmount != 0f
|
|
||||||
|
|
||||||
broadcastDispatcher.registerReceiver(
|
broadcastDispatcher.registerReceiver(
|
||||||
localeBroadcastReceiver,
|
localeBroadcastReceiver,
|
||||||
@@ -210,17 +212,28 @@ open class ClockEventController @Inject constructor(
|
|||||||
configurationController.addCallback(configListener)
|
configurationController.addCallback(configListener)
|
||||||
batteryController.addCallback(batteryCallback)
|
batteryController.addCallback(batteryCallback)
|
||||||
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
|
keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
|
||||||
statusBarStateController.addCallback(statusBarStateListener)
|
|
||||||
smallRegionSampler?.startRegionSampler()
|
smallRegionSampler?.startRegionSampler()
|
||||||
largeRegionSampler?.startRegionSampler()
|
largeRegionSampler?.startRegionSampler()
|
||||||
|
disposableHandle = parent.repeatWhenAttached {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
listenForDozing(this)
|
||||||
|
listenForDozeAmount(this)
|
||||||
|
listenForDozeAmountTransition(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregisterListeners() {
|
fun unregisterListeners() {
|
||||||
|
if (!isRegistered) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
isRegistered = false
|
||||||
|
|
||||||
|
disposableHandle?.dispose()
|
||||||
broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
|
broadcastDispatcher.unregisterReceiver(localeBroadcastReceiver)
|
||||||
configurationController.removeCallback(configListener)
|
configurationController.removeCallback(configListener)
|
||||||
batteryController.removeCallback(batteryCallback)
|
batteryController.removeCallback(batteryCallback)
|
||||||
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
|
keyguardUpdateMonitor.removeCallback(keyguardUpdateMonitorCallback)
|
||||||
statusBarStateController.removeCallback(statusBarStateListener)
|
|
||||||
smallRegionSampler?.stopRegionSampler()
|
smallRegionSampler?.stopRegionSampler()
|
||||||
largeRegionSampler?.stopRegionSampler()
|
largeRegionSampler?.stopRegionSampler()
|
||||||
}
|
}
|
||||||
@@ -235,8 +248,39 @@ open class ClockEventController @Inject constructor(
|
|||||||
largeRegionSampler?.dump(pw)
|
largeRegionSampler?.dump(pw)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
@VisibleForTesting
|
||||||
private val TAG = ClockEventController::class.simpleName
|
internal fun listenForDozeAmount(scope: CoroutineScope): Job {
|
||||||
private const val FORMAT_NUMBER = 1234567890
|
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() {
|
protected void onViewAttached() {
|
||||||
mClockRegistry.registerClockChangeListener(mClockChangedListener);
|
mClockRegistry.registerClockChangeListener(mClockChangedListener);
|
||||||
setClock(mClockRegistry.createCurrentClock());
|
setClock(mClockRegistry.createCurrentClock());
|
||||||
mClockEventController.registerListeners();
|
mClockEventController.registerListeners(mView);
|
||||||
mKeyguardClockTopMargin =
|
mKeyguardClockTopMargin =
|
||||||
mView.getResources().getDimensionPixelSize(R.dimen.keyguard_clock_top_margin);
|
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.KeyguardUnlockAnimationController;
|
||||||
import com.android.systemui.keyguard.KeyguardViewMediator;
|
import com.android.systemui.keyguard.KeyguardViewMediator;
|
||||||
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
|
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.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
|
||||||
import com.android.systemui.navigationbar.NavigationModeController;
|
import com.android.systemui.navigationbar.NavigationModeController;
|
||||||
import com.android.systemui.statusbar.NotificationShadeDepthController;
|
import com.android.systemui.statusbar.NotificationShadeDepthController;
|
||||||
@@ -72,6 +73,7 @@ import dagger.Provides;
|
|||||||
FalsingModule.class,
|
FalsingModule.class,
|
||||||
KeyguardQuickAffordanceModule.class,
|
KeyguardQuickAffordanceModule.class,
|
||||||
KeyguardRepositoryModule.class,
|
KeyguardRepositoryModule.class,
|
||||||
|
StartKeyguardTransitionModule.class,
|
||||||
})
|
})
|
||||||
public class KeyguardModule {
|
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.common.shared.model.Position
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.doze.DozeHost
|
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.plugins.statusbar.StatusBarStateController
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -85,6 +86,9 @@ interface KeyguardRepository {
|
|||||||
*/
|
*/
|
||||||
val dozeAmount: Flow<Float>
|
val dozeAmount: Flow<Float>
|
||||||
|
|
||||||
|
/** Observable for the [StatusBarState] */
|
||||||
|
val statusBarState: Flow<StatusBarState>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns `true` if the keyguard is showing; `false` otherwise.
|
* Returns `true` if the keyguard is showing; `false` otherwise.
|
||||||
*
|
*
|
||||||
@@ -185,6 +189,24 @@ constructor(
|
|||||||
return keyguardStateController.isShowing
|
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) {
|
override fun setAnimateDozingTransitions(animate: Boolean) {
|
||||||
_animateBottomAreaDozingTransitions.value = animate
|
_animateBottomAreaDozingTransitions.value = animate
|
||||||
}
|
}
|
||||||
@@ -197,6 +219,15 @@ constructor(
|
|||||||
_clockPosition.value = Position(x, y)
|
_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 {
|
companion object {
|
||||||
private const val TAG = "KeyguardRepositoryImpl"
|
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
|
val dozeAmount: Flow<Float> = repository.dozeAmount
|
||||||
/** Whether the system is in doze mode. */
|
/** Whether the system is in doze mode. */
|
||||||
val isDozing: Flow<Boolean> = repository.isDozing
|
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
|
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
|
||||||
|
|
||||||
fun isKeyguardShowing(): Boolean {
|
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.content.BroadcastReceiver
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.flags.FeatureFlags
|
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.ClockAnimations
|
||||||
import com.android.systemui.plugins.ClockController
|
import com.android.systemui.plugins.ClockController
|
||||||
import com.android.systemui.plugins.ClockEvents
|
import com.android.systemui.plugins.ClockEvents
|
||||||
import com.android.systemui.plugins.ClockFaceController
|
import com.android.systemui.plugins.ClockFaceController
|
||||||
import com.android.systemui.plugins.ClockFaceEvents
|
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.BatteryController
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController
|
import com.android.systemui.statusbar.policy.ConfigurationController
|
||||||
import com.android.systemui.util.mockito.any
|
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 com.android.systemui.util.mockito.mock
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
import java.util.concurrent.Executor
|
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.Assert.assertEquals
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
@@ -57,7 +64,7 @@ import org.mockito.junit.MockitoJUnit
|
|||||||
class ClockEventControllerTest : SysuiTestCase() {
|
class ClockEventControllerTest : SysuiTestCase() {
|
||||||
|
|
||||||
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
@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 broadcastDispatcher: BroadcastDispatcher
|
||||||
@Mock private lateinit var batteryController: BatteryController
|
@Mock private lateinit var batteryController: BatteryController
|
||||||
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
|
||||||
@@ -72,8 +79,11 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var largeClockController: ClockFaceController
|
@Mock private lateinit var largeClockController: ClockFaceController
|
||||||
@Mock private lateinit var smallClockEvents: ClockFaceEvents
|
@Mock private lateinit var smallClockEvents: ClockFaceEvents
|
||||||
@Mock private lateinit var largeClockEvents: 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
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
@@ -86,8 +96,11 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
whenever(clock.events).thenReturn(events)
|
whenever(clock.events).thenReturn(events)
|
||||||
whenever(clock.animations).thenReturn(animations)
|
whenever(clock.animations).thenReturn(animations)
|
||||||
|
|
||||||
clockEventController = ClockEventController(
|
repository = FakeKeyguardRepository()
|
||||||
statusBarStateController,
|
|
||||||
|
underTest = ClockEventController(
|
||||||
|
KeyguardInteractor(repository = repository),
|
||||||
|
KeyguardTransitionInteractor(repository = transitionRepository),
|
||||||
broadcastDispatcher,
|
broadcastDispatcher,
|
||||||
batteryController,
|
batteryController,
|
||||||
keyguardUpdateMonitor,
|
keyguardUpdateMonitor,
|
||||||
@@ -98,31 +111,33 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
bgExecutor,
|
bgExecutor,
|
||||||
featureFlags
|
featureFlags
|
||||||
)
|
)
|
||||||
|
underTest.clock = clock
|
||||||
|
|
||||||
|
runBlocking(IMMEDIATE) {
|
||||||
|
underTest.registerListeners(parentView)
|
||||||
|
|
||||||
|
repository.setDozing(true)
|
||||||
|
repository.setDozeAmount(1f)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun clockSet_validateInitialization() {
|
fun clockSet_validateInitialization() {
|
||||||
clockEventController.clock = clock
|
|
||||||
|
|
||||||
verify(clock).initialize(any(), anyFloat(), anyFloat())
|
verify(clock).initialize(any(), anyFloat(), anyFloat())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun clockUnset_validateState() {
|
fun clockUnset_validateState() {
|
||||||
clockEventController.clock = clock
|
underTest.clock = null
|
||||||
clockEventController.clock = null
|
|
||||||
|
|
||||||
assertEquals(clockEventController.clock, null)
|
assertEquals(underTest.clock, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun themeChanged_verifyClockPaletteUpdated() {
|
fun themeChanged_verifyClockPaletteUpdated() = runBlocking(IMMEDIATE) {
|
||||||
clockEventController.clock = clock
|
|
||||||
verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
|
verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
|
||||||
verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
|
verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
|
||||||
|
|
||||||
clockEventController.registerListeners()
|
|
||||||
|
|
||||||
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
|
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
|
||||||
verify(configurationController).addCallback(capture(captor))
|
verify(configurationController).addCallback(capture(captor))
|
||||||
captor.value.onThemeChanged()
|
captor.value.onThemeChanged()
|
||||||
@@ -131,13 +146,10 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun fontChanged_verifyFontSizeUpdated() {
|
fun fontChanged_verifyFontSizeUpdated() = runBlocking(IMMEDIATE) {
|
||||||
clockEventController.clock = clock
|
|
||||||
verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
|
verify(smallClockEvents).onRegionDarknessChanged(anyBoolean())
|
||||||
verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
|
verify(largeClockEvents).onRegionDarknessChanged(anyBoolean())
|
||||||
|
|
||||||
clockEventController.registerListeners()
|
|
||||||
|
|
||||||
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
|
val captor = argumentCaptor<ConfigurationController.ConfigurationListener>()
|
||||||
verify(configurationController).addCallback(capture(captor))
|
verify(configurationController).addCallback(capture(captor))
|
||||||
captor.value.onDensityOrFontScaleChanged()
|
captor.value.onDensityOrFontScaleChanged()
|
||||||
@@ -146,10 +158,7 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() {
|
fun batteryCallback_keyguardShowingCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
|
||||||
clockEventController.clock = clock
|
|
||||||
clockEventController.registerListeners()
|
|
||||||
|
|
||||||
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
|
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
|
||||||
verify(batteryController).addCallback(capture(batteryCaptor))
|
verify(batteryController).addCallback(capture(batteryCaptor))
|
||||||
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||||
@@ -161,26 +170,21 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() {
|
fun batteryCallback_keyguardShowingCharging_Duplicate_verifyChargeAnimation() =
|
||||||
clockEventController.clock = clock
|
runBlocking(IMMEDIATE) {
|
||||||
clockEventController.registerListeners()
|
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(animations, times(1)).charge()
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() {
|
fun batteryCallback_keyguardHiddenCharging_verifyChargeAnimation() = runBlocking(IMMEDIATE) {
|
||||||
clockEventController.clock = clock
|
|
||||||
clockEventController.registerListeners()
|
|
||||||
|
|
||||||
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
|
val batteryCaptor = argumentCaptor<BatteryController.BatteryStateChangeCallback>()
|
||||||
verify(batteryController).addCallback(capture(batteryCaptor))
|
verify(batteryController).addCallback(capture(batteryCaptor))
|
||||||
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
val keyguardCaptor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||||
@@ -192,25 +196,20 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() {
|
fun batteryCallback_keyguardShowingNotCharging_verifyChargeAnimation() =
|
||||||
clockEventController.clock = clock
|
runBlocking(IMMEDIATE) {
|
||||||
clockEventController.registerListeners()
|
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(animations, never()).charge()
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun localeCallback_verifyClockNotified() {
|
fun localeCallback_verifyClockNotified() = runBlocking(IMMEDIATE) {
|
||||||
clockEventController.clock = clock
|
|
||||||
clockEventController.registerListeners()
|
|
||||||
|
|
||||||
val captor = argumentCaptor<BroadcastReceiver>()
|
val captor = argumentCaptor<BroadcastReceiver>()
|
||||||
verify(broadcastDispatcher).registerReceiver(
|
verify(broadcastDispatcher).registerReceiver(
|
||||||
capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
|
capture(captor), any(), eq(null), eq(null), anyInt(), eq(null)
|
||||||
@@ -221,10 +220,7 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun keyguardCallback_visibilityChanged_clockDozeCalled() {
|
fun keyguardCallback_visibilityChanged_clockDozeCalled() = runBlocking(IMMEDIATE) {
|
||||||
clockEventController.clock = clock
|
|
||||||
clockEventController.registerListeners()
|
|
||||||
|
|
||||||
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||||
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
||||||
|
|
||||||
@@ -236,10 +232,7 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun keyguardCallback_timeFormat_clockNotified() {
|
fun keyguardCallback_timeFormat_clockNotified() = runBlocking(IMMEDIATE) {
|
||||||
clockEventController.clock = clock
|
|
||||||
clockEventController.registerListeners()
|
|
||||||
|
|
||||||
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||||
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
||||||
captor.value.onTimeFormatChanged("12h")
|
captor.value.onTimeFormatChanged("12h")
|
||||||
@@ -248,11 +241,8 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun keyguardCallback_timezoneChanged_clockNotified() {
|
fun keyguardCallback_timezoneChanged_clockNotified() = runBlocking(IMMEDIATE) {
|
||||||
val mockTimeZone = mock<TimeZone>()
|
val mockTimeZone = mock<TimeZone>()
|
||||||
clockEventController.clock = clock
|
|
||||||
clockEventController.registerListeners()
|
|
||||||
|
|
||||||
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||||
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
||||||
captor.value.onTimeZoneChanged(mockTimeZone)
|
captor.value.onTimeZoneChanged(mockTimeZone)
|
||||||
@@ -261,10 +251,7 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun keyguardCallback_userSwitched_clockNotified() {
|
fun keyguardCallback_userSwitched_clockNotified() = runBlocking(IMMEDIATE) {
|
||||||
clockEventController.clock = clock
|
|
||||||
clockEventController.registerListeners()
|
|
||||||
|
|
||||||
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
val captor = argumentCaptor<KeyguardUpdateMonitorCallback>()
|
||||||
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
verify(keyguardUpdateMonitor).registerCallback(capture(captor))
|
||||||
captor.value.onUserSwitchComplete(10)
|
captor.value.onUserSwitchComplete(10)
|
||||||
@@ -273,25 +260,27 @@ class ClockEventControllerTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun keyguardCallback_verifyKeyguardChanged() {
|
fun keyguardCallback_verifyKeyguardChanged() = runBlocking(IMMEDIATE) {
|
||||||
clockEventController.clock = clock
|
val job = underTest.listenForDozeAmount(this)
|
||||||
clockEventController.registerListeners()
|
repository.setDozeAmount(0.4f)
|
||||||
|
|
||||||
val captor = argumentCaptor<StatusBarStateController.StateListener>()
|
yield()
|
||||||
verify(statusBarStateController).addCallback(capture(captor))
|
|
||||||
captor.value.onDozeAmountChanged(0.4f, 0.6f)
|
|
||||||
|
|
||||||
verify(animations).doze(0.4f)
|
verify(animations).doze(0.4f)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun unregisterListeners_validate() {
|
fun unregisterListeners_validate() = runBlocking(IMMEDIATE) {
|
||||||
clockEventController.clock = clock
|
underTest.unregisterListeners()
|
||||||
clockEventController.unregisterListeners()
|
|
||||||
verify(broadcastDispatcher).unregisterReceiver(any())
|
verify(broadcastDispatcher).unregisterReceiver(any())
|
||||||
verify(configurationController).removeCallback(any())
|
verify(configurationController).removeCallback(any())
|
||||||
verify(batteryController).removeCallback(any())
|
verify(batteryController).removeCallback(any())
|
||||||
verify(keyguardUpdateMonitor).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) {
|
private void verifyAttachment(VerificationMode times) {
|
||||||
verify(mClockRegistry, times).registerClockChangeListener(
|
verify(mClockRegistry, times).registerClockChangeListener(
|
||||||
any(ClockRegistry.ClockChangeListener.class));
|
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
|
package com.android.systemui.keyguard.data.repository
|
||||||
|
|
||||||
import com.android.systemui.common.shared.model.Position
|
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.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -44,6 +45,9 @@ class FakeKeyguardRepository : KeyguardRepository {
|
|||||||
private val _dozeAmount = MutableStateFlow(0f)
|
private val _dozeAmount = MutableStateFlow(0f)
|
||||||
override val dozeAmount: Flow<Float> = _dozeAmount
|
override val dozeAmount: Flow<Float> = _dozeAmount
|
||||||
|
|
||||||
|
private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
|
||||||
|
override val statusBarState: Flow<StatusBarState> = _statusBarState
|
||||||
|
|
||||||
override fun isKeyguardShowing(): Boolean {
|
override fun isKeyguardShowing(): Boolean {
|
||||||
return _isKeyguardShowing.value
|
return _isKeyguardShowing.value
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user