summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Chandru S <chandruis@google.com> 2024-11-06 13:30:06 -0800
committer Chandru S <chandruis@google.com> 2024-12-20 10:07:47 -0800
commitdc0d0cdf40f47ccff3a1655c68ec5539a27ef9bb (patch)
treefedeb0c2eb746b59cf199d5ef51c4321155132e7
parent08c94bb94459771de15b54f693e73613a5a8ec10 (diff)
Create separate classes that are responsible for applying window root view surface effects
The background surface was being blurred previously as part of the NotificationShadeDepthController We need to do the same blurring as part of the transition to the primary bouncer. Bug: 370555003 Flag: com.android.systemui.bouncer_ui_revamp Test: verified manually Test: added unit tests Change-Id: I62a90882842322ecb8edc4e046c8fb40645758c9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorTest.kt60
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt88
-rw-r--r--packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt106
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorKosmos.kt29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelKosmos.kt30
15 files changed, 654 insertions, 6 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 3d3d666825ad..ee9cb141a700 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -19,6 +19,8 @@ import android.content.res.Configuration
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
+import android.view.Choreographer
+import android.view.MotionEvent
import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -44,6 +46,7 @@ import com.android.systemui.settings.brightness.data.repository.BrightnessMirror
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.DragDownHelper
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationInsetsController
@@ -66,6 +69,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.window.ui.viewmodel.WindowRootViewModel
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -91,6 +95,9 @@ import org.mockito.kotlin.eq
@SmallTest
class NotificationShadeWindowViewTest : SysuiTestCase() {
+ @Mock private lateinit var choreographer: Choreographer
+ @Mock private lateinit var blurUtils: BlurUtils
+ @Mock private lateinit var windowRootViewModelFactory: WindowRootViewModel.Factory
@Mock private lateinit var dragDownHelper: DragDownHelper
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var shadeController: ShadeController
@@ -168,6 +175,9 @@ class NotificationShadeWindowViewTest : SysuiTestCase() {
testScope = TestScope()
controller =
NotificationShadeWindowViewController(
+ blurUtils,
+ windowRootViewModelFactory,
+ choreographer,
lockscreenShadeTransitionController,
FalsingCollectorFake(),
statusBarStateController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 9f94cff4ead4..f48b3e1eaeff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -17,15 +17,19 @@
package com.android.systemui.statusbar
import android.os.IBinder
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.view.Choreographer
import android.view.View
import android.view.ViewRootImpl
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dump.DumpManager
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
import com.android.systemui.shade.ShadeExpansionChangeEvent
@@ -35,8 +39,10 @@ import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.FakeConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.WallpaperController
import com.android.systemui.util.mockito.eq
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.google.common.truth.Truth.assertThat
import java.util.function.Consumer
import org.junit.Before
@@ -44,6 +50,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.floatThat
import org.mockito.Captor
@@ -63,7 +70,10 @@ import org.mockito.junit.MockitoJUnit
@RunWithLooper
@SmallTest
class NotificationShadeDepthControllerTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val applicationScope = kosmos.testScope.backgroundScope
+ @Mock private lateinit var windowRootViewBlurInteractor: WindowRootViewBlurInteractor
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var blurUtils: BlurUtils
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@@ -115,9 +125,12 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
notificationShadeWindowController,
dozeParameters,
context,
- ResourcesSplitShadeStateController(),
+ ResourcesSplitShadeStateController(),
+ windowRootViewBlurInteractor,
+ applicationScope,
dumpManager,
- configurationController,)
+ configurationController,
+ )
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
notificationShadeDepthController.root = root
@@ -356,6 +369,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
fun ignoreShadeBlurUntilHidden_schedulesFrame() {
notificationShadeDepthController.blursDisabledForAppLaunch = true
verify(blurUtils).prepareBlur(any(), anyInt())
@@ -364,6 +378,13 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+ fun ignoreShadeBlurUntilHidden_requestsBlur_windowBlurFlag() {
+ notificationShadeDepthController.blursDisabledForAppLaunch = true
+ verify(windowRootViewBlurInteractor).requestBlurForShade(anyInt(), anyBoolean())
+ }
+
+ @Test
fun ignoreBlurForUnlock_ignores() {
notificationShadeDepthController.onPanelExpansionChanged(
ShadeExpansionChangeEvent(
@@ -410,6 +431,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
fun brightnessMirror_hidesShadeBlur() {
// Brightness mirror is fully visible
`when`(brightnessSpring.ratio).thenReturn(1f)
@@ -427,6 +449,23 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_BOUNCER_UI_REVAMP)
+ fun brightnessMirror_hidesShadeBlur_withWindowBlurFlag() {
+ // Brightness mirror is fully visible
+ `when`(brightnessSpring.ratio).thenReturn(1f)
+ // And shade is blurred
+ notificationShadeDepthController.onPanelExpansionChanged(
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
+ `when`(shadeAnimation.radius).thenReturn(maxBlur.toFloat())
+
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+ verify(notificationShadeWindowController).setBackgroundBlurRadius(eq(0))
+ verify(wallpaperController).setNotificationShadeZoom(eq(1f))
+ verify(windowRootViewBlurInteractor).requestBlurForShade(0, false)
+ }
+
+ @Test
fun ignoreShadeBlurUntilHidden_whennNull_ignoresIfShadeHasNoBlur() {
`when`(shadeAnimation.radius).thenReturn(0f)
notificationShadeDepthController.blursDisabledForAppLaunch = true
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
new file mode 100644
index 000000000000..bc16bec5d5cd
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 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.window.domain.interactor
+
+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
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WindowRootViewBlurInteractorTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ 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)
+
+ assertThat(underTest.requestBlurForShade(30, true)).isFalse()
+ }
+}
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
new file mode 100644
index 000000000000..b97fe5725285
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 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.window.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WindowRootViewModelTest : SysuiTestCase() {
+ val kosmos = testKosmos()
+ val testScope = kosmos.testScope
+
+ val underTest by lazy { kosmos.windowRootViewModel }
+
+ @Before
+ fun setup() {
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ fun bouncerTransitionChangesWindowBlurRadius() =
+ testScope.runTest {
+ val blurState by collectLastValue(underTest.blurState)
+ runCurrent()
+
+ kosmos.fakeBouncerTransitions.first().windowBlurRadius.value = 30.0f
+ runCurrent()
+
+ assertThat(blurState).isEqualTo(BlurState(radius = 30, isOpaque = false))
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 839d4596bb7c..e5dcd2338b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -23,6 +23,7 @@ import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.app.StatusBarManager;
import android.util.Log;
+import android.view.Choreographer;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -60,6 +61,7 @@ import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirr
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor;
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
import com.android.systemui.shared.animation.DisableSubpixelTextTransitionListener;
+import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationInsetsController;
@@ -79,6 +81,8 @@ import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.window.ui.WindowRootViewBinder;
+import com.android.systemui.window.ui.viewmodel.WindowRootViewModel;
import kotlinx.coroutines.ExperimentalCoroutinesApi;
@@ -160,6 +164,9 @@ public class NotificationShadeWindowViewController implements Dumpable {
@ExperimentalCoroutinesApi
@Inject
public NotificationShadeWindowViewController(
+ BlurUtils blurUtils,
+ WindowRootViewModel.Factory windowRootViewModelFactory,
+ Choreographer choreographer,
LockscreenShadeTransitionController transitionController,
FalsingCollector falsingCollector,
SysuiStatusBarStateController statusBarStateController,
@@ -259,9 +266,18 @@ public class NotificationShadeWindowViewController implements Dumpable {
if (ShadeWindowGoesAround.isEnabled()) {
mView.setConfigurationForwarder(configurationForwarder.get());
}
+ bindWindowRootView(blurUtils, windowRootViewModelFactory, choreographer);
dumpManager.registerDumpable(this);
}
+ private void bindWindowRootView(BlurUtils blurUtils,
+ WindowRootViewModel.Factory windowRootViewModelFactory, Choreographer choreographer) {
+ if (SceneContainerFlag.isEnabled()) return;
+
+ WindowRootViewBinder.INSTANCE.bind(mView, windowRootViewModelFactory, blurUtils,
+ choreographer);
+ }
+
private void bindBouncer(BouncerViewBinder bouncerViewBinder) {
mBouncerParentView = mView.findViewById(R.id.keyguard_bouncer_container);
bouncerViewBinder.bind(mBouncerParentView);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index 3408f4ffd082..443b5415b3a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -34,9 +34,9 @@ import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.app.animation.Interpolators
import com.android.systemui.Dumpable
-import com.android.systemui.Flags.notificationShadeBlur
import com.android.systemui.animation.ShadeInterpolation
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
@@ -50,10 +50,14 @@ import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.WallpaperController
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
+import com.android.systemui.window.flag.WindowBlurFlag
import java.io.PrintWriter
import javax.inject.Inject
import kotlin.math.max
import kotlin.math.sign
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
/**
* Responsible for blurring the notification shade window, and applying a zoom effect to the
@@ -73,6 +77,8 @@ constructor(
private val dozeParameters: DozeParameters,
private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
+ private val windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
+ @Application private val applicationScope: CoroutineScope,
dumpManager: DumpManager,
configurationController: ConfigurationController,
) : ShadeExpansionListener, Dumpable {
@@ -162,6 +168,8 @@ constructor(
shadeAnimation.finishIfRunning()
}
+ private var zoomOutCalculatedFromShadeRadius: Float = 0.0f
+
/** We're unlocking, and should not blur as the panel expansion changes. */
var blursDisabledForUnlock: Boolean = false
set(value) {
@@ -216,7 +224,7 @@ constructor(
val zoomOut = blurRadiusToZoomOut(blurRadius = shadeRadius)
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
- if (!notificationShadeBlur()) {
+ if (!WindowBlurFlag.isEnabled) {
blur = 0
}
}
@@ -244,7 +252,7 @@ constructor(
}
private val shouldBlurBeOpaque: Boolean
- get() = if (notificationShadeBlur()) false else scrimsVisible && !blursDisabledForAppLaunch
+ get() = if (WindowBlurFlag.isEnabled) false else scrimsVisible && !blursDisabledForAppLaunch
/** Callback that updates the window blur value and is called only once per frame. */
@VisibleForTesting
@@ -363,6 +371,26 @@ constructor(
}
}
)
+ initBlurListeners()
+ }
+
+ private fun initBlurListeners() {
+ if (!WindowBlurFlag.isEnabled) return
+
+ applicationScope.launch {
+ Log.d(TAG, "Starting coroutines for window root view blur")
+ windowRootViewBlurInteractor.onBlurAppliedEvent.collect { appliedBlurRadius ->
+ if (updateScheduled) {
+ // Process the blur applied event only if we scheduled the update
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "shade_blur_radius", appliedBlurRadius)
+ updateScheduled = false
+ onBlurApplied(appliedBlurRadius, zoomOutCalculatedFromShadeRadius)
+ } else {
+ // Try scheduling an update now, maybe our blur request will be scheduled now.
+ scheduleUpdate()
+ }
+ }
+ }
}
private fun updateResources() {
@@ -480,11 +508,17 @@ constructor(
}
private fun scheduleUpdate() {
+ val (blur, zoomOutFromShadeRadius) = computeBlurAndZoomOut()
+ zoomOutCalculatedFromShadeRadius = zoomOutFromShadeRadius
+ if (WindowBlurFlag.isEnabled) {
+ updateScheduled =
+ windowRootViewBlurInteractor.requestBlurForShade(blur, shouldBlurBeOpaque)
+ return
+ }
if (updateScheduled) {
return
}
updateScheduled = true
- val (blur, _) = computeBlurAndZoomOut()
blurUtils.prepareBlur(root.viewRootImpl, blur)
choreographer.postFrameCallback(updateBlurCallback)
}
diff --git a/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
new file mode 100644
index 000000000000..6b7de982e00a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepository.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 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.window.data.repository
+
+import android.annotation.SuppressLint
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Repository that maintains state for the window blur effect. */
+@SysUISingleton
+class WindowRootViewBlurRepository @Inject constructor() {
+ val blurRadius = MutableStateFlow(0)
+
+ val isBlurOpaque = MutableStateFlow(false)
+
+ @SuppressLint("SharedFlowCreation") val onBlurApplied = MutableSharedFlow<Int>()
+}
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
new file mode 100644
index 000000000000..fee32b5e7e78
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractor.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2024 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.window.domain.interactor
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.window.data.repository.WindowRootViewBlurRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Interactor that provides the blur state for the window root view
+ * [com.android.systemui.scene.ui.view.WindowRootView]
+ */
+@SysUISingleton
+class WindowRootViewBlurInteractor
+@Inject
+constructor(
+ private val keyguardInteractor: KeyguardInteractor,
+ private val repository: WindowRootViewBlurRepository,
+) {
+
+ /**
+ * Invoked by the view after blur of [appliedBlurRadius] was successfully applied on the window
+ * root view.
+ */
+ suspend fun onBlurApplied(appliedBlurRadius: Int) {
+ repository.onBlurApplied.emit(appliedBlurRadius)
+ }
+
+ /** 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
+ }
+
+ /**
+ * Method that requests blur to be applied on window root view. It is applied only when other
+ * blurs are not applied.
+ *
+ * This method is present to temporarily support the blur for notification shade, ideally shade
+ * should expose state that is used by this interactor to determine the blur that has to be
+ * applied.
+ *
+ * @return whether the request for blur was processed or not.
+ */
+ fun requestBlurForShade(blurRadius: Int, opaque: Boolean): Boolean {
+ if (keyguardInteractor.primaryBouncerShowing.value) {
+ return false
+ }
+ Log.d(TAG, "requestingBlurForShade for $blurRadius $opaque")
+ repository.blurRadius.value = blurRadius
+ repository.isBlurOpaque.value = opaque
+ return true
+ }
+
+ 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
new file mode 100644
index 000000000000..2491ca7565c7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/ui/WindowRootViewBinder.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 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.window.ui
+
+import android.util.Log
+import android.view.Choreographer
+import android.view.Choreographer.FrameCallback
+import com.android.systemui.lifecycle.WindowLifecycleState
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.viewModel
+import com.android.systemui.scene.ui.view.WindowRootView
+import com.android.systemui.statusbar.BlurUtils
+import com.android.systemui.window.flag.WindowBlurFlag
+import com.android.systemui.window.ui.viewmodel.WindowRootViewModel
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.launch
+
+/**
+ * View binder that wires up window level UI transformations like blur to the [WindowRootView]
+ * instance.
+ */
+object WindowRootViewBinder {
+ private const val TAG = "WindowRootViewBinder"
+
+ fun bind(
+ view: WindowRootView,
+ viewModelFactory: WindowRootViewModel.Factory,
+ blurUtils: BlurUtils?,
+ choreographer: Choreographer?,
+ ) {
+ if (!WindowBlurFlag.isEnabled) return
+ if (blurUtils == null || choreographer == null) return
+
+ view.repeatWhenAttached {
+ Log.d(TAG, "Binding root view")
+ var frameCallbackPendingExecution: FrameCallback? = null
+ val viewRootImpl = view.rootView.viewRootImpl
+ view.viewModel(
+ minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+ factory = { viewModelFactory.create() },
+ traceName = "WindowRootViewBinder#bind",
+ ) { 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(
+ viewRootImpl,
+ blurState.radius,
+ blurState.isOpaque,
+ )
+ viewModel.onBlurApplied(blurState.radius)
+ }
+ blurUtils.prepareBlur(viewRootImpl, blurState.radius)
+ if (frameCallbackPendingExecution != null) {
+ choreographer.removeFrameCallback(frameCallbackPendingExecution)
+ }
+ frameCallbackPendingExecution = newFrameCallback
+ choreographer.postFrameCallback(newFrameCallback)
+ }
+ }
+ awaitCancellation()
+ } finally {
+ Log.d(TAG, "Wrapped up coroutines that update window root view state")
+ }
+ }
+ }
+ }
+}
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
new file mode 100644
index 000000000000..199d02d267ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModel.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 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.window.ui.viewmodel
+
+import android.os.Build
+import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced
+import com.android.systemui.keyguard.ui.transitions.PrimaryBouncerTransition
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
+import dagger.assisted.AssistedFactory
+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.merge
+import kotlinx.coroutines.flow.onEach
+
+typealias BlurAppliedUiEvent = Int
+
+/** View model for window root view. */
+class WindowRootViewModel
+@AssistedInject
+constructor(
+ private val primaryBouncerTransitions: Set<@JvmSuppressWildcards PrimaryBouncerTransition>,
+ private val blurInteractor: WindowRootViewBlurInteractor,
+) : ExclusiveActivatable() {
+
+ private val blurEvents = Channel<BlurAppliedUiEvent>(Channel.BUFFERED)
+ private val _blurState = MutableStateFlow(BlurState(0, false))
+ val blurState = _blurState.asStateFlow()
+
+ override suspend fun onActivated(): Nothing {
+ coroutineScope {
+ launchTraced("WindowRootViewModel#blurEvents") {
+ for (event in blurEvents) {
+ if (isLoggable) {
+ Log.d(TAG, "blur applied for $event")
+ }
+ 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())
+ }
+ }
+ }
+ awaitCancellation()
+ }
+
+ fun onBlurApplied(blurRadius: Int) {
+ blurEvents.trySend(blurRadius)
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): WindowRootViewModel
+ }
+
+ private companion object {
+ const val TAG = "WindowRootViewModel"
+ val isLoggable = Log.isLoggable(TAG, Log.DEBUG) || Build.isDebuggable()
+ }
+}
+
+/**
+ * @property radius Radius of blur to be applied on the window root view.
+ * @property isOpaque Whether the blur applied is opaque or transparent.
+ */
+data class BlurState(val radius: Int, val isOpaque: Boolean)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 4b11e2c24722..e68153ad2606 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -22,6 +22,7 @@ import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.flag.junit.FlagsParameterization
import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
+import android.view.Choreographer
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
@@ -57,6 +58,7 @@ import com.android.systemui.settings.brightness.data.repository.BrightnessMirror
import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
+import com.android.systemui.statusbar.BlurUtils
import com.android.systemui.statusbar.DragDownHelper
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationInsetsController
@@ -80,6 +82,7 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.window.ui.viewmodel.WindowRootViewModel
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.Dispatchers
@@ -155,6 +158,9 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
@Mock lateinit var sysUIKeyEventHandler: SysUIKeyEventHandler
@Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
@Mock lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+ @Mock private lateinit var blurUtils: BlurUtils
+ @Mock private lateinit var choreographer: Choreographer
+ @Mock private lateinit var windowViewModelFactory: WindowRootViewModel.Factory
private val notificationLaunchAnimationRepository = NotificationLaunchAnimationRepository()
private val notificationLaunchAnimationInteractor =
NotificationLaunchAnimationInteractor(notificationLaunchAnimationRepository)
@@ -203,6 +209,9 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) :
fakeClock = FakeSystemClock()
underTest =
NotificationShadeWindowViewController(
+ blurUtils,
+ windowViewModelFactory,
+ choreographer,
lockscreenShadeTransitionController,
falsingCollector,
sysuiStatusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt
new file mode 100644
index 000000000000..15d00d9f6994
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/transitions/FakeBouncerTransition.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 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.ui.transitions
+
+import kotlinx.coroutines.flow.MutableStateFlow
+
+class FakeBouncerTransition : PrimaryBouncerTransition {
+ override val windowBlurRadius: MutableStateFlow<Float> = MutableStateFlow(0.0f)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
new file mode 100644
index 000000000000..7281e03a5ea4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/window/data/repository/WindowRootViewBlurRepositoryKosmos.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 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.window.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.windowRootViewBlurRepository by Kosmos.Fixture { WindowRootViewBlurRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorKosmos.kt
new file mode 100644
index 000000000000..ad30ea26c5b8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/window/domain/interactor/WindowRootViewBlurInteractorKosmos.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.window.domain.interactor
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.window.data.repository.windowRootViewBlurRepository
+
+val Kosmos.windowRootViewBlurInteractor by
+ Kosmos.Fixture {
+ WindowRootViewBlurInteractor(
+ repository = windowRootViewBlurRepository,
+ keyguardInteractor = keyguardInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelKosmos.kt
new file mode 100644
index 000000000000..864048d51873
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/window/ui/viewmodel/WindowRootViewModelKosmos.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 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.window.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.transitions.FakeBouncerTransition
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
+import org.mockito.internal.util.collections.Sets
+
+val Kosmos.fakeBouncerTransitions by
+ Kosmos.Fixture<Set<FakeBouncerTransition>> {
+ Sets.newSet(FakeBouncerTransition(), FakeBouncerTransition())
+ }
+
+val Kosmos.windowRootViewModel by
+ Kosmos.Fixture { WindowRootViewModel(fakeBouncerTransitions, windowRootViewBlurInteractor) }