diff options
5 files changed, 103 insertions, 120 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorTest.kt index 79edc223bc83..9ca2aea4e963 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorTest.kt @@ -20,7 +20,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository -import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat @@ -37,18 +36,6 @@ class WindowRootViewBlurInteractorTest : SysuiTestCase() { val underTest by lazy { kosmos.windowRootViewBlurInteractor } @Test - fun bouncerBlurIsAppliedImmediately() = - testScope.runTest { - val blurRadius by collectLastValue(underTest.blurRadius) - val isBlurOpaque by collectLastValue(underTest.isBlurOpaque) - - underTest.requestBlurForBouncer(10) - - assertThat(blurRadius).isEqualTo(10) - assertThat(isBlurOpaque).isFalse() - } - - @Test fun shadeBlurIsNotAppliedWhenBouncerBlurIsActive() = testScope.runTest { kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(true) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt index 3c4e3612c053..61ee5e04afd9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt @@ -46,12 +46,14 @@ class WindowRootViewModelTest : SysuiTestCase() { @Test fun bouncerTransitionChangesWindowBlurRadius() = testScope.runTest { - val blurState by collectLastValue(underTest.blurState) + val blurRadius by collectLastValue(underTest.blurRadius) + val isBlurOpaque by collectLastValue(underTest.isBlurOpaque) runCurrent() kosmos.fakeBouncerTransitions.first().windowBlurRadius.value = 30.0f runCurrent() - assertThat(blurState).isEqualTo(BlurState(radius = 30, isOpaque = false)) + assertThat(blurRadius).isEqualTo(30) + assertThat(isBlurOpaque).isEqualTo(false) } } diff --git a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt index bfa349876c42..e21e0a1cadc7 100644 --- a/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt @@ -32,7 +32,9 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -72,39 +74,29 @@ constructor( /** Radius of blur to be applied on the window root view. */ val blurRadius: StateFlow<Int> = repository.blurRadius.asStateFlow() - /** Whether the blur applied is opaque or transparent. */ - val isBlurOpaque: StateFlow<Boolean> = repository.isBlurOpaque.asStateFlow() - /** * Emits the applied blur radius whenever blur is successfully applied to the window root view. */ val onBlurAppliedEvent: Flow<Int> = repository.onBlurApplied - /** - * Request to apply blur while on bouncer, this takes precedence over other blurs (from shade). - */ - fun requestBlurForBouncer(blurRadius: Int) { - repository.isBlurOpaque.value = false - repository.blurRadius.value = blurRadius - } - - /** - * Request to apply blur while on glanceable hub, this takes precedence over other blurs (from - * shade) except for bouncer. - */ - fun requestBlurForGlanceableHub(blurRadius: Int): Boolean { - if (keyguardInteractor.primaryBouncerShowing.value) { - return false + /** Whether the blur applied is opaque or transparent. */ + val isBlurOpaque: Flow<Boolean> = + combine( + if (Flags.bouncerUiRevamp()) { + keyguardInteractor.primaryBouncerShowing.or(isBouncerTransitionInProgress) + } else { + flowOf(false) + }, + if (Flags.glanceableHubBlurredBackground()) { + communalInteractor.isCommunalBlurring + } else { + flowOf(false) + }, + repository.isBlurOpaque, + ) { bouncerActive, ghActive, shadeBlurOpaque -> + if (bouncerActive || ghActive) false else shadeBlurOpaque } - Log.d(TAG, "requestBlurForGlanceableHub for $blurRadius") - - repository.isBlurOpaque.value = false - repository.blurRadius.value = blurRadius - - return true - } - /** * Method that requests blur to be applied on window root view. It is applied only when other * blurs are not applied. @@ -119,10 +111,10 @@ constructor( // We need to check either of these because they are two different sources of truth, // primaryBouncerShowing changes early to true/false, but blur is // coordinated by transition value. - if (keyguardInteractor.primaryBouncerShowing.value || isBouncerTransitionInProgress.value) { + if (isBouncerTransitionInProgress()) { return false } - if (communalInteractor.isCommunalBlurring.value) { + if (isGlanceableHubActive()) { return false } Log.d(TAG, "requestingBlurForShade for $blurRadius $opaque") @@ -131,6 +123,14 @@ constructor( return true } + private fun isGlanceableHubActive() = communalInteractor.isCommunalBlurring.value + + private fun isBouncerTransitionInProgress() = + keyguardInteractor.primaryBouncerShowing.value || isBouncerTransitionInProgress.value + + private fun Flow<Boolean>.or(anotherFlow: Flow<Boolean>): Flow<Boolean> = + this.combine(anotherFlow) { a, b -> a || b } + companion object { const val TAG = "WindowRootViewBlurInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt index bba0c378e2a5..153df7f29737 100644 --- a/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt @@ -20,6 +20,7 @@ import android.util.Log import android.view.Choreographer import android.view.Choreographer.FrameCallback import com.android.app.tracing.coroutines.TrackTracer +import com.android.app.tracing.coroutines.launchTraced import com.android.systemui.Flags import com.android.systemui.lifecycle.WindowLifecycleState import com.android.systemui.lifecycle.repeatWhenAttached @@ -29,8 +30,8 @@ import com.android.systemui.statusbar.BlurUtils import com.android.systemui.window.ui.viewmodel.WindowRootViewModel import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.launch /** * View binder that wires up window level UI transformations like blur to the [WindowRootView] @@ -51,7 +52,6 @@ object WindowRootViewBinder { view.repeatWhenAttached(mainDispatcher) { Log.d(TAG, "Binding root view") - var frameCallbackPendingExecution: FrameCallback? = null view.viewModel( minWindowLifecycleState = WindowLifecycleState.ATTACHED, factory = { viewModelFactory.create() }, @@ -59,34 +59,48 @@ object WindowRootViewBinder { ) { viewModel -> try { Log.d(TAG, "Launching coroutines that update window root view state") - launch { - viewModel.blurState - .filter { it.radius >= 0 } - .collect { blurState -> - val newFrameCallback = FrameCallback { - frameCallbackPendingExecution = null - blurUtils.applyBlur( - view.rootView?.viewRootImpl, - blurState.radius, - blurState.isOpaque, - ) - TrackTracer.instantForGroup( - "windowBlur", - "appliedBlurRadius", - blurState.radius, - ) - viewModel.onBlurApplied(blurState.radius) + launchTraced("WindowBlur") { + var wasUpdateScheduledForThisFrame = false + var lastScheduledBlurRadius = 0 + var lastScheduleBlurOpaqueness = false + + // Creating the callback once and not for every coroutine invocation + val newFrameCallback = FrameCallback { + wasUpdateScheduledForThisFrame = false + val blurRadiusToApply = lastScheduledBlurRadius + blurUtils.applyBlur( + view.rootView?.viewRootImpl, + blurRadiusToApply, + lastScheduleBlurOpaqueness, + ) + TrackTracer.instantForGroup( + "windowBlur", + "appliedBlurRadius", + blurRadiusToApply, + ) + viewModel.onBlurApplied(blurRadiusToApply) + } + + combine(viewModel.blurRadius, viewModel.isBlurOpaque, ::Pair) + .filter { it.first >= 0 } + .collect { (blurRadius, isOpaque) -> + // Expectation is that we schedule only one blur radius value + // per frame + if (wasUpdateScheduledForThisFrame) { + return@collect } TrackTracer.instantForGroup( "windowBlur", "preparedBlurRadius", - blurState.radius, + blurRadius, + ) + lastScheduledBlurRadius = blurRadius.toInt() + lastScheduleBlurOpaqueness = isOpaque + wasUpdateScheduledForThisFrame = true + blurUtils.prepareBlur( + view.rootView?.viewRootImpl, + lastScheduledBlurRadius, ) - blurUtils.prepareBlur(view.rootView?.viewRootImpl, blurState.radius) - if (frameCallbackPendingExecution != null) { - choreographer.removeFrameCallback(frameCallbackPendingExecution) - } - frameCallbackPendingExecution = newFrameCallback choreographer.postFrameCallback(newFrameCallback) } } diff --git a/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt index 72cca75df92c..b18c39dcf5e0 100644 --- a/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt @@ -19,7 +19,7 @@ package com.android.systemui.window.ui.viewmodel import android.os.Build import android.util.Log import com.android.app.tracing.coroutines.launchTraced -import com.android.systemui.Flags.glanceableHubBlurredBackground +import com.android.systemui.Flags import com.android.systemui.keyguard.ui.transitions.GlanceableHubTransition import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition import com.android.systemui.lifecycle.ExclusiveActivatable @@ -29,9 +29,9 @@ import dagger.assisted.AssistedInject import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach @@ -41,14 +41,33 @@ typealias BlurAppliedUiEvent = Int class WindowRootViewModel @AssistedInject constructor( - private val primaryBouncerTransitions: Set<@JvmSuppressWildcards PrimaryBouncerTransition>, - private val glanceableHubTransitions: Set<@JvmSuppressWildcards GlanceableHubTransition>, + primaryBouncerTransitions: Set<@JvmSuppressWildcards PrimaryBouncerTransition>, + glanceableHubTransitions: Set<@JvmSuppressWildcards GlanceableHubTransition>, private val blurInteractor: WindowRootViewBlurInteractor, ) : ExclusiveActivatable() { private val blurEvents = Channel<BlurAppliedUiEvent>(Channel.BUFFERED) - private val _blurState = MutableStateFlow(BlurState(0, false)) - val blurState = _blurState.asStateFlow() + + private val bouncerBlurRadiusFlows = + if (Flags.bouncerUiRevamp()) + primaryBouncerTransitions.map { it.windowBlurRadius.logIfPossible(it.javaClass.name) } + else emptyList() + + private val glanceableHubBlurRadiusFlows = + if (Flags.glanceableHubBlurredBackground()) + glanceableHubTransitions.map { it.windowBlurRadius.logIfPossible(it.javaClass.name) } + else emptyList() + + val blurRadius: Flow<Float> = + listOf( + *bouncerBlurRadiusFlows.toTypedArray(), + *glanceableHubBlurRadiusFlows.toTypedArray(), + blurInteractor.blurRadius.map { it.toFloat() }.logIfPossible("ShadeBlur"), + ) + .merge() + + val isBlurOpaque = + blurInteractor.isBlurOpaque.distinctUntilChanged().logIfPossible("isBlurOpaque") override suspend fun onActivated(): Nothing { coroutineScope { @@ -60,49 +79,6 @@ constructor( blurInteractor.onBlurApplied(event) } } - - launchTraced("WindowRootViewModel#blurState") { - combine(blurInteractor.blurRadius, blurInteractor.isBlurOpaque, ::BlurState) - .collect { _blurState.value = it } - } - - launchTraced("WindowRootViewModel#bouncerTransitions") { - primaryBouncerTransitions - .map { transition -> - transition.windowBlurRadius.onEach { blurRadius -> - if (isLoggable) { - Log.d( - TAG, - "${transition.javaClass.simpleName} windowBlurRadius $blurRadius", - ) - } - } - } - .merge() - .collect { blurRadius -> - blurInteractor.requestBlurForBouncer(blurRadius.toInt()) - } - } - - if (glanceableHubBlurredBackground()) { - launchTraced("WindowRootViewModel#glanceableHubTransitions") { - glanceableHubTransitions - .map { transition -> - transition.windowBlurRadius.onEach { blurRadius -> - if (isLoggable) { - Log.d( - TAG, - "${transition.javaClass.simpleName} windowBlurRadius $blurRadius", - ) - } - } - } - .merge() - .collect { blurRadius -> - blurInteractor.requestBlurForGlanceableHub(blurRadius.toInt()) - } - } - } } awaitCancellation() } @@ -118,7 +94,11 @@ constructor( private companion object { const val TAG = "WindowRootViewModel" - val isLoggable = Log.isLoggable(TAG, Log.DEBUG) || Build.isDebuggable() + val isLoggable = Log.isLoggable(TAG, Log.VERBOSE) || Build.isDebuggable() + + fun <T> Flow<T>.logIfPossible(loggingInfo: String): Flow<T> { + return onEach { if (isLoggable) Log.v(TAG, "$loggingInfo $it") } + } } } |