summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt73
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt108
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt185
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt112
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt8
-rw-r--r--packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt61
-rw-r--r--packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt32
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt50
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt42
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt63
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt26
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt36
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt29
31 files changed, 1449 insertions, 52 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
index 8dcc44463213..5be5fb4aa9ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.core
import android.platform.test.annotations.EnableFlags
+import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.data.repository.fakePrivacyDotWindowControllerStore
import com.android.systemui.testKosmos
import com.google.common.truth.Expect
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -30,6 +32,7 @@ import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.never
import org.mockito.kotlin.verify
@OptIn(ExperimentalCoroutinesApi::class)
@@ -39,16 +42,12 @@ import org.mockito.kotlin.verify
class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
@get:Rule val expect: Expect = Expect.create()
- private val kosmos =
- testKosmos().also {
- it.statusBarOrchestratorFactory = it.fakeStatusBarOrchestratorFactory
- it.statusBarInitializerStore = it.fakeStatusBarInitializerStore
- }
+ private val kosmos = testKosmos()
private val testScope = kosmos.testScope
private val fakeDisplayRepository = kosmos.displayRepository
private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory
private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
-
+ private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
// Lazy, so that @EnableFlags is set before initializer is instantiated.
private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
@@ -83,6 +82,31 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
}
@Test
+ fun start_startsPrivacyDotForCurrentDisplays() =
+ testScope.runTest {
+ fakeDisplayRepository.addDisplay(displayId = 1)
+ fakeDisplayRepository.addDisplay(displayId = 2)
+
+ underTest.start()
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = 1)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = 2)).start()
+ }
+
+ @Test
+ fun start_doesNotStartPrivacyDotForDefaultDisplay() =
+ testScope.runTest {
+ fakeDisplayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
+
+ underTest.start()
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = Display.DEFAULT_DISPLAY), never())
+ .start()
+ }
+
+ @Test
fun displayAdded_orchestratorForNewDisplayIsStarted() =
testScope.runTest {
underTest.start()
@@ -109,6 +133,18 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
}
@Test
+ fun displayAdded_privacyDotForNewDisplayIsStarted() =
+ testScope.runTest {
+ underTest.start()
+ runCurrent()
+
+ fakeDisplayRepository.addDisplay(displayId = 3)
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+ }
+
+ @Test
fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
testScope.runTest {
underTest.start()
@@ -129,8 +165,17 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
fakeDisplayRepository.addDisplay(displayId = 3)
runCurrent()
- expect
- .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
- .isTrue()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+ }
+
+ @Test
+ fun displayAddedDuringStart_privacyDotForNewDisplayIsStarted() =
+ testScope.runTest {
+ underTest.start()
+
+ fakeDisplayRepository.addDisplay(displayId = 3)
+ runCurrent()
+
+ verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
new file mode 100644
index 000000000000..ae734b3ca04f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreImplTest.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class PrivacyDotWindowControllerStoreImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val underTest by lazy { kosmos.privacyDotWindowControllerStoreImpl }
+
+ @Before
+ fun installDisplays() = runBlocking {
+ kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY)
+ kosmos.displayRepository.addDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
+ }
+
+ @Test(expected = IllegalArgumentException::class)
+ fun forDisplay_defaultDisplay_throws() {
+ underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY)
+ }
+
+ @Test
+ fun forDisplay_nonDefaultDisplay_doesNotThrow() {
+ underTest.forDisplay(displayId = Display.DEFAULT_DISPLAY + 1)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
new file mode 100644
index 000000000000..e65c04c45382
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreImplTest.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class SystemEventChipAnimationControllerStoreImplTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ // Lazy so that @EnableFlags has time to run before underTest is instantiated.
+ private val underTest by lazy { kosmos.systemEventChipAnimationControllerStoreImpl }
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @Test
+ fun beforeDisplayRemoved_doesNotStopInstances() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance, never()).stop()
+ }
+
+ @Test
+ fun displayRemoved_stopsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).stop()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt
new file mode 100644
index 000000000000..d4007d7df7be
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationControllerTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.statusbar.events
+
+import android.platform.test.annotations.EnableFlags
+import androidx.core.animation.AnimatorSet
+import androidx.core.animation.ValueAnimator
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplaySystemEventChipAnimationControllerTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val displayRepository = kosmos.displayRepository
+ private val store = kosmos.systemEventChipAnimationControllerStore
+
+ // Lazy so that @EnableFlags has time to switch the flags before the instance is created.
+ private val underTest by lazy { kosmos.multiDisplaySystemEventChipAnimationController }
+
+ @Before
+ fun installDisplays() = runBlocking {
+ INSTALLED_DISPLAY_IDS.forEach { displayRepository.addDisplay(displayId = it) }
+ }
+
+ @Test
+ fun init_forwardsToAllControllers() {
+ underTest.init()
+
+ INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).init() }
+ }
+
+ @Test
+ fun stop_forwardsToAllControllers() {
+ underTest.stop()
+
+ INSTALLED_DISPLAY_IDS.forEach { verify(store.forDisplay(it)).stop() }
+ }
+
+ @Test
+ fun announceForAccessibility_forwardsToAllControllers() {
+ val contentDescription = "test content description"
+ underTest.announceForAccessibility(contentDescription)
+
+ INSTALLED_DISPLAY_IDS.forEach {
+ verify(store.forDisplay(it)).announceForAccessibility(contentDescription)
+ }
+ }
+
+ @Test
+ fun onSystemEventAnimationBegin_returnsAnimatorSetWithOneAnimatorPerDisplay() {
+ INSTALLED_DISPLAY_IDS.forEach {
+ val controller = store.forDisplay(it)
+ whenever(controller.onSystemEventAnimationBegin()).thenReturn(ValueAnimator.ofInt(0, 1))
+ }
+ val animator = underTest.onSystemEventAnimationBegin() as AnimatorSet
+
+ assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
+ }
+
+ @Test
+ fun onSystemEventAnimationFinish_returnsAnimatorSetWithOneAnimatorPerDisplay() {
+ INSTALLED_DISPLAY_IDS.forEach {
+ val controller = store.forDisplay(it)
+ whenever(controller.onSystemEventAnimationFinish(any()))
+ .thenReturn(ValueAnimator.ofInt(0, 1))
+ }
+ val animator =
+ underTest.onSystemEventAnimationFinish(hasPersistentDot = true) as AnimatorSet
+
+ assertThat(animator.childAnimations).hasSize(INSTALLED_DISPLAY_IDS.size)
+ }
+
+ companion object {
+ private const val DISPLAY_ID_1 = 123
+ private const val DISPLAY_ID_2 = 456
+ private val INSTALLED_DISPLAY_IDS = listOf(DISPLAY_ID_1, DISPLAY_ID_2)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt
new file mode 100644
index 000000000000..6bcd735e9a9f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerTest.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.statusbar.events
+
+import android.view.Gravity.BOTTOM
+import android.view.Gravity.LEFT
+import android.view.Gravity.RIGHT
+import android.view.Gravity.TOP
+import android.view.Surface
+import android.view.View
+import android.view.WindowManager
+import android.view.fakeWindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PrivacyDotWindowControllerTest : SysuiTestCase() {
+
+ @get:Rule val expect: Expect = Expect.create()
+
+ private val kosmos = testKosmos()
+ private val underTest = kosmos.privacyDotWindowController
+ private val viewController = kosmos.privacyDotViewController
+ private val windowManager = kosmos.fakeWindowManager
+ private val executor = kosmos.fakeExecutor
+
+ @After
+ fun cleanUpCustomDisplay() {
+ context.display = null
+ }
+
+ @Test
+ fun start_beforeUiThreadExecutes_doesNotAddWindows() {
+ underTest.start()
+
+ assertThat(windowManager.addedViews).isEmpty()
+ }
+
+ @Test
+ fun start_beforeUiThreadExecutes_doesNotInitializeViewController() {
+ underTest.start()
+
+ assertThat(viewController.isInitialized).isFalse()
+ }
+
+ @Test
+ fun start_afterUiThreadExecutes_addsWindowsOnUiThread() {
+ underTest.start()
+
+ executor.runAllReady()
+
+ assertThat(windowManager.addedViews).hasSize(4)
+ }
+
+ @Test
+ fun start_afterUiThreadExecutes_initializesViewController() {
+ underTest.start()
+
+ executor.runAllReady()
+
+ assertThat(viewController.isInitialized).isTrue()
+ }
+
+ @Test
+ fun start_initializesTopLeft() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.topLeft?.id).isEqualTo(R.id.privacy_dot_top_left_container)
+ }
+
+ @Test
+ fun start_initializesTopRight() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.topRight?.id).isEqualTo(R.id.privacy_dot_top_right_container)
+ }
+
+ @Test
+ fun start_initializesTopBottomLeft() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.bottomLeft?.id).isEqualTo(R.id.privacy_dot_bottom_left_container)
+ }
+
+ @Test
+ fun start_initializesBottomRight() {
+ underTest.start()
+ executor.runAllReady()
+
+ assertThat(viewController.bottomRight?.id)
+ .isEqualTo(R.id.privacy_dot_bottom_right_container)
+ }
+
+ @Test
+ fun start_viewsAddedInRespectiveCorners() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_0 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or LEFT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or RIGHT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or LEFT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or RIGHT)
+ }
+
+ @Test
+ fun start_rotation90_viewsPositionIsShifted90degrees() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_90 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or LEFT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(TOP or LEFT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(BOTTOM or RIGHT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or RIGHT)
+ }
+
+ @Test
+ fun start_rotation180_viewsPositionIsShifted180degrees() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_180 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(BOTTOM or RIGHT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or LEFT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or RIGHT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(TOP or LEFT)
+ }
+
+ @Test
+ fun start_rotation270_viewsPositionIsShifted270degrees() {
+ context.display = mock { on { rotation } doReturn Surface.ROTATION_270 }
+
+ underTest.start()
+ executor.runAllReady()
+
+ expect.that(gravityForView(viewController.topLeft!!)).isEqualTo(TOP or RIGHT)
+ expect.that(gravityForView(viewController.topRight!!)).isEqualTo(BOTTOM or RIGHT)
+ expect.that(gravityForView(viewController.bottomLeft!!)).isEqualTo(TOP or LEFT)
+ expect.that(gravityForView(viewController.bottomRight!!)).isEqualTo(BOTTOM or LEFT)
+ }
+
+ private fun paramsForView(view: View): WindowManager.LayoutParams {
+ return windowManager.addedViews.entries
+ .first { it.key == view || it.key.findViewById<View>(view.id) != null }
+ .value
+ }
+
+ private fun gravityForView(view: View): Int {
+ return paramsForView(view).gravity
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 46e45aaf8a8a..66b3e189b6c9 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -905,7 +905,18 @@ public class ScreenDecorations implements
return lp;
}
- private WindowManager.LayoutParams getWindowLayoutBaseParams() {
+ public static WindowManager.LayoutParams getWindowLayoutBaseParams() {
+ return getWindowLayoutBaseParams(/* excludeFromScreenshots= */ true);
+ }
+
+ /**
+ * Creates the base {@link WindowManager.LayoutParams} that are used for all decoration windows.
+ *
+ * @param excludeFromScreenshots whether to set the {@link
+ * WindowManager.LayoutParams#PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY} flag.
+ */
+ public static WindowManager.LayoutParams getWindowLayoutBaseParams(
+ boolean excludeFromScreenshots) {
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
@@ -921,7 +932,7 @@ public class ScreenDecorations implements
// FLAG_SLIPPERY can only be set by trusted overlays
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
- if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS) {
+ if (!DEBUG_SCREENSHOT_ROUNDED_CORNERS && excludeFromScreenshots) {
lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
}
diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
index 9aa7fd100068..78d8d8fe4e48 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -81,7 +81,7 @@ class PrivacyDotCornerDecorProviderImpl(
override val viewId: Int,
@DisplayCutout.BoundsPosition override val alignedBound1: Int,
@DisplayCutout.BoundsPosition override val alignedBound2: Int,
- private val layoutId: Int,
+ val layoutId: Int,
) : CornerDecorProvider() {
override fun onReloadResAndMeasure(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index e1159220e366..9b3513e8a363 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,11 +17,13 @@
package com.android.systemui.statusbar.core
import android.view.Display
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStore
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore
@@ -29,7 +31,6 @@ import com.android.systemui.util.kotlin.pairwiseBy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Responsible for creating and starting the status bar components for each display. Also does it
@@ -48,6 +49,7 @@ constructor(
private val initializerStore: StatusBarInitializerStore,
private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
private val statusBarInitializerStore: StatusBarInitializerStore,
+ private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
) : CoreStartable {
init {
@@ -71,6 +73,7 @@ constructor(
val displayId = display.displayId
createAndStartOrchestratorForDisplay(displayId)
createAndStartInitializerForDisplay(displayId)
+ startPrivacyDotForDisplay(displayId)
}
private fun createAndStartOrchestratorForDisplay(displayId: Int) {
@@ -89,4 +92,12 @@ constructor(
private fun createAndStartInitializerForDisplay(displayId: Int) {
statusBarInitializerStore.forDisplay(displayId).start()
}
+
+ private fun startPrivacyDotForDisplay(displayId: Int) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ // For the default display, privacy dot is started via ScreenDecorations
+ return
+ }
+ privacyDotWindowControllerStore.forDisplay(displayId).start()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index c416bf7b4f92..f2d926fc22b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -20,6 +20,7 @@ import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModul
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryModule
+import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStoreModule
import com.android.systemui.statusbar.phone.data.StatusBarPhoneDataLayerModule
import dagger.Module
@@ -32,6 +33,7 @@ import dagger.Module
StatusBarContentInsetsProviderStoreModule::class,
StatusBarModeRepositoryModule::class,
StatusBarPhoneDataLayerModule::class,
+ SystemEventChipAnimationControllerStoreModule::class,
]
)
object StatusBarDataLayerModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
new file mode 100644
index 000000000000..a1f56552629b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.view.Display
+import android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.events.PrivacyDotWindowController
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Providers per display instances of [PrivacyDotWindowController]. */
+interface PrivacyDotWindowControllerStore : PerDisplayStore<PrivacyDotWindowController>
+
+@SysUISingleton
+class PrivacyDotWindowControllerStoreImpl
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val windowControllerFactory: PrivacyDotWindowController.Factory,
+ private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ private val privacyDotViewControllerStore: PrivacyDotViewControllerStore,
+ private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
+) :
+ PrivacyDotWindowControllerStore,
+ PerDisplayStoreImpl<PrivacyDotWindowController>(backgroundApplicationScope, displayRepository) {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun createInstanceForDisplay(displayId: Int): PrivacyDotWindowController {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ throw IllegalArgumentException("This class should only be used for connected displays")
+ }
+ val displayWindowProperties =
+ displayWindowPropertiesRepository.get(displayId, TYPE_NAVIGATION_BAR_PANEL)
+ return windowControllerFactory.create(
+ displayId = displayId,
+ privacyDotViewController = privacyDotViewControllerStore.forDisplay(displayId),
+ viewCaptureAwareWindowManager =
+ viewCaptureAwareWindowManagerFactory.create(displayWindowProperties.windowManager),
+ inflater = displayWindowProperties.layoutInflater,
+ )
+ }
+
+ override val instanceClass = PrivacyDotWindowController::class.java
+}
+
+@Module
+interface PrivacyDotWindowControllerStoreModule {
+
+ @Binds fun store(impl: PrivacyDotWindowControllerStoreImpl): PrivacyDotWindowControllerStore
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(PrivacyDotWindowControllerStore::class)
+ fun storeAsCoreStartable(
+ storeLazy: Lazy<PrivacyDotWindowControllerStoreImpl>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ storeLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
new file mode 100644
index 000000000000..7760f58805c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStore.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.events.SystemEventChipAnimationController
+import com.android.systemui.statusbar.events.SystemEventChipAnimationControllerImpl
+import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import dagger.Binds
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [SystemEventChipAnimationController]. */
+interface SystemEventChipAnimationControllerStore :
+ PerDisplayStore<SystemEventChipAnimationController>
+
+@SysUISingleton
+class SystemEventChipAnimationControllerStoreImpl
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val factory: SystemEventChipAnimationControllerImpl.Factory,
+ private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
+ private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+) :
+ SystemEventChipAnimationControllerStore,
+ PerDisplayStoreImpl<SystemEventChipAnimationController>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun createInstanceForDisplay(displayId: Int): SystemEventChipAnimationController {
+ return factory.create(
+ displayWindowPropertiesRepository.get(displayId, TYPE_STATUS_BAR).context,
+ statusBarWindowControllerStore.forDisplay(displayId),
+ statusBarContentInsetsProviderStore.forDisplay(displayId),
+ )
+ }
+
+ override suspend fun onDisplayRemovalAction(instance: SystemEventChipAnimationController) {
+ instance.stop()
+ }
+
+ override val instanceClass = SystemEventChipAnimationController::class.java
+}
+
+@Module
+interface SystemEventChipAnimationControllerStoreModule {
+
+ @Binds
+ @SysUISingleton
+ fun store(
+ impl: SystemEventChipAnimationControllerStoreImpl
+ ): SystemEventChipAnimationControllerStore
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(SystemEventChipAnimationControllerStore::class)
+ fun storeAsCoreStartable(
+ implLazy: Lazy<SystemEventChipAnimationControllerStoreImpl>
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ implLazy.get()
+ } else {
+ CoreStartable.NOP
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
new file mode 100644
index 000000000000..f2bb7b16439d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/MultiDisplaySystemEventChipAnimationController.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.statusbar.events
+
+import androidx.core.animation.Animator
+import androidx.core.animation.AnimatorSet
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.statusbar.data.repository.SystemEventChipAnimationControllerStore
+import javax.inject.Inject
+
+/**
+ * A [SystemEventChipAnimationController] that handles animations for multiple displays. It
+ * delegates the animation tasks to individual controllers for each display.
+ */
+@SysUISingleton
+class MultiDisplaySystemEventChipAnimationController
+@Inject
+constructor(
+ private val displayRepository: DisplayRepository,
+ private val controllerStore: SystemEventChipAnimationControllerStore,
+) : SystemEventChipAnimationController {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun prepareChipAnimation(viewCreator: ViewCreator) {
+ forEachController { it.prepareChipAnimation(viewCreator) }
+ }
+
+ override fun init() {
+ forEachController { it.init() }
+ }
+
+ override fun stop() {
+ forEachController { it.stop() }
+ }
+
+ override fun announceForAccessibility(contentDescriptions: String) {
+ forEachController { it.announceForAccessibility(contentDescriptions) }
+ }
+
+ override fun onSystemEventAnimationBegin(): Animator {
+ val animators = controllersForAllDisplays().map { it.onSystemEventAnimationBegin() }
+ return AnimatorSet().apply { playTogether(animators) }
+ }
+
+ override fun onSystemEventAnimationFinish(hasPersistentDot: Boolean): Animator {
+ val animators =
+ controllersForAllDisplays().map { it.onSystemEventAnimationFinish(hasPersistentDot) }
+ return AnimatorSet().apply { playTogether(animators) }
+ }
+
+ private fun forEachController(consumer: (SystemEventChipAnimationController) -> Unit) {
+ controllersForAllDisplays().forEach { consumer(it) }
+ }
+
+ private fun controllersForAllDisplays() =
+ displayRepository.displays.value.map { controllerStore.forDisplay(it.displayId) }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
new file mode 100644
index 000000000000..9928ac67f185
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.statusbar.events
+
+import android.view.Display
+import android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM
+import android.view.DisplayCutout.BOUNDS_POSITION_LEFT
+import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT
+import android.view.DisplayCutout.BOUNDS_POSITION_TOP
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager.LayoutParams.WRAP_CONTENT
+import android.widget.FrameLayout
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.ScreenDecorations
+import com.android.systemui.ScreenDecorationsThread
+import com.android.systemui.decor.DecorProvider
+import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl
+import com.android.systemui.decor.PrivacyDotDecorProviderFactory
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.BottomRight
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopLeft
+import com.android.systemui.statusbar.events.PrivacyDotCorner.TopRight
+import com.android.systemui.util.containsExactly
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
+
+/**
+ * Responsible for adding the privacy dot to a window.
+ *
+ * It will create one window per corner (top left, top right, bottom left, bottom right), which are
+ * used dependant on the display's rotation.
+ */
+class PrivacyDotWindowController
+@AssistedInject
+constructor(
+ @Assisted private val displayId: Int,
+ @Assisted private val privacyDotViewController: PrivacyDotViewController,
+ @Assisted private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ @Assisted private val inflater: LayoutInflater,
+ @ScreenDecorationsThread private val uiExecutor: Executor,
+ private val dotFactory: PrivacyDotDecorProviderFactory,
+) {
+
+ fun start() {
+ uiExecutor.execute { startOnUiThread() }
+ }
+
+ private fun startOnUiThread() {
+ val providers = dotFactory.providers
+
+ val topLeft = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_LEFT)
+ val topRight = providers.inflate(BOUNDS_POSITION_TOP, BOUNDS_POSITION_RIGHT)
+ val bottomLeft = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_LEFT)
+ val bottomRight = providers.inflate(BOUNDS_POSITION_BOTTOM, BOUNDS_POSITION_RIGHT)
+
+ topLeft.addToWindow(TopLeft)
+ topRight.addToWindow(TopRight)
+ bottomLeft.addToWindow(BottomLeft)
+ bottomRight.addToWindow(BottomRight)
+
+ privacyDotViewController.initialize(topLeft, topRight, bottomLeft, bottomRight)
+ }
+
+ private fun List<DecorProvider>.inflate(alignedBound1: Int, alignedBound2: Int): View {
+ val provider =
+ first { it.alignedBounds.containsExactly(alignedBound1, alignedBound2) }
+ as PrivacyDotCornerDecorProviderImpl
+ return inflater.inflate(/* resource= */ provider.layoutId, /* root= */ null)
+ }
+
+ private fun View.addToWindow(corner: PrivacyDotCorner) {
+ val excludeFromScreenshots = displayId == Display.DEFAULT_DISPLAY
+ val params =
+ ScreenDecorations.getWindowLayoutBaseParams(excludeFromScreenshots).apply {
+ width = WRAP_CONTENT
+ height = WRAP_CONTENT
+ gravity = corner.rotatedCorner(context.display.rotation).gravity
+ title = "PrivacyDot${corner.title}$displayId"
+ }
+ // PrivacyDotViewController expects the dot view to have a FrameLayout parent.
+ val rootView = FrameLayout(context)
+ rootView.addView(this)
+ viewCaptureAwareWindowManager.addView(rootView, params)
+ }
+
+ @AssistedFactory
+ fun interface Factory {
+ fun create(
+ displayId: Int,
+ privacyDotViewController: PrivacyDotViewController,
+ viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ inflater: LayoutInflater,
+ ): PrivacyDotWindowController
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index b28660590ad0..1038ad4124ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -32,13 +32,16 @@ import androidx.core.animation.AnimatorSet
import androidx.core.animation.ValueAnimator
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Default
import com.android.systemui.res.R
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
import com.android.systemui.statusbar.phone.StatusBarContentInsetsChangedListener
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.util.animation.AnimationUtil.Companion.frames
+import dagger.Lazy
import dagger.Module
import dagger.Provides
import dagger.assisted.Assisted
@@ -57,6 +60,8 @@ interface SystemEventChipAnimationController : SystemStatusAnimationCallback {
fun init()
+ fun stop()
+
/** Announces [contentDescriptions] for accessibility. */
fun announceForAccessibility(contentDescriptions: String)
@@ -287,6 +292,26 @@ constructor(
return animSet
}
+ private val statusBarContentInsetsChangedListener =
+ object : StatusBarContentInsetsChangedListener {
+ override fun onStatusBarContentInsetsChanged() {
+ val newContentArea =
+ contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
+ updateDimens(newContentArea)
+
+ // If we are currently animating, we have to re-solve for the chip bounds. If
+ // we're not animating then [prepareChipAnimation] will take care of it for us.
+ currentAnimatedView?.let {
+ updateChipBounds(it, newContentArea)
+ // Since updateCurrentAnimatedView can only be called during an animation,
+ // we have to create a no-op animator here to apply the new chip bounds.
+ val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
+ animator.addUpdateListener { updateCurrentAnimatedView() }
+ animator.start()
+ }
+ }
+ }
+
override fun init() {
initialized = true
themedContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings)
@@ -303,28 +328,11 @@ constructor(
// Use contentInsetsProvider rather than configuration controller, since we only care
// about status bar dimens
- contentInsetsProvider.addCallback(
- object : StatusBarContentInsetsChangedListener {
- override fun onStatusBarContentInsetsChanged() {
- val newContentArea =
- contentInsetsProvider.getStatusBarContentAreaForCurrentRotation()
- updateDimens(newContentArea)
-
- // If we are currently animating, we have to re-solve for the chip bounds. If
- // we're
- // not animating then [prepareChipAnimation] will take care of it for us
- currentAnimatedView?.let {
- updateChipBounds(it, newContentArea)
- // Since updateCurrentAnimatedView can only be called during an animation,
- // we
- // have to create a dummy animator here to apply the new chip bounds
- val animator = ValueAnimator.ofInt(0, 1).setDuration(0)
- animator.addUpdateListener { updateCurrentAnimatedView() }
- animator.start()
- }
- }
- }
- )
+ contentInsetsProvider.addCallback(statusBarContentInsetsChangedListener)
+ }
+
+ override fun stop() {
+ contentInsetsProvider.removeCallback(statusBarContentInsetsChangedListener)
}
override fun announceForAccessibility(contentDescriptions: String) {
@@ -418,7 +426,7 @@ constructor(
}
@AssistedFactory
- interface Factory {
+ fun interface Factory {
fun create(
context: Context,
statusBarWindowController: StatusBarWindowController,
@@ -446,20 +454,36 @@ private const val LEFT = 1
private const val RIGHT = 2
@Module
-object SystemEventChipAnimationControllerModule {
-
- @Provides
- @SysUISingleton
- fun controller(
- factory: SystemEventChipAnimationControllerImpl.Factory,
- context: Context,
- statusBarWindowControllerStore: StatusBarWindowControllerStore,
- contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
- ): SystemEventChipAnimationController {
- return factory.create(
- context,
- statusBarWindowControllerStore.defaultDisplay,
- contentInsetsProviderStore.defaultDisplay,
- )
+interface SystemEventChipAnimationControllerModule {
+
+ companion object {
+ @Provides
+ @Default
+ @SysUISingleton
+ fun defaultController(
+ factory: SystemEventChipAnimationControllerImpl.Factory,
+ context: Context,
+ statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ contentInsetsProviderStore: StatusBarContentInsetsProviderStore,
+ ): SystemEventChipAnimationController {
+ return factory.create(
+ context,
+ statusBarWindowControllerStore.defaultDisplay,
+ contentInsetsProviderStore.defaultDisplay,
+ )
+ }
+
+ @Provides
+ @SysUISingleton
+ fun controller(
+ @Default defaultLazy: Lazy<SystemEventChipAnimationController>,
+ multiDisplayLazy: Lazy<MultiDisplaySystemEventChipAnimationController>,
+ ): SystemEventChipAnimationController {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ defaultLazy.get()
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 23f3482d40bd..94de3510188c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -32,6 +32,7 @@ import com.android.systemui.statusbar.core.StatusBarInitializerStore
import com.android.systemui.statusbar.core.StatusBarOrchestrator
import com.android.systemui.statusbar.core.StatusBarSimpleFragment
import com.android.systemui.statusbar.data.repository.PrivacyDotViewControllerStoreModule
+import com.android.systemui.statusbar.data.repository.PrivacyDotWindowControllerStoreModule
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.events.PrivacyDotViewControllerModule
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks
@@ -48,7 +49,12 @@ import kotlinx.coroutines.CoroutineScope
/** Similar in purpose to [StatusBarModule], but scoped only to phones */
@Module(
- includes = [PrivacyDotViewControllerModule::class, PrivacyDotViewControllerStoreModule::class]
+ includes =
+ [
+ PrivacyDotViewControllerModule::class,
+ PrivacyDotWindowControllerStoreModule::class,
+ PrivacyDotViewControllerStoreModule::class,
+ ]
)
interface StatusBarPhoneModule {
diff --git a/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt
new file mode 100644
index 000000000000..c28449feeab7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/view/FakeWindowManager.kt
@@ -0,0 +1,61 @@
+/*
+ * 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 android.view
+
+import android.content.Context
+import android.graphics.Region
+import android.view.WindowManager.LayoutParams
+
+class FakeWindowManager(private val context: Context) : WindowManager {
+
+ val addedViews = mutableMapOf<View, LayoutParams>()
+
+ override fun addView(view: View, params: ViewGroup.LayoutParams) {
+ addedViews[view] = params as LayoutParams
+ }
+
+ override fun removeView(view: View) {
+ addedViews.remove(view)
+ }
+
+ override fun updateViewLayout(view: View, params: ViewGroup.LayoutParams) {
+ addedViews[view] = params as LayoutParams
+ }
+
+ override fun getApplicationLaunchKeyboardShortcuts(deviceId: Int): KeyboardShortcutGroup {
+ return KeyboardShortcutGroup("Fake group")
+ }
+
+ override fun getCurrentImeTouchRegion(): Region {
+ return Region.obtain()
+ }
+
+ override fun getDefaultDisplay(): Display {
+ return context.display
+ }
+
+ override fun removeViewImmediate(view: View) {
+ addedViews.remove(view)
+ }
+
+ override fun requestAppKeyboardShortcuts(
+ receiver: WindowManager.KeyboardShortcutsReceiver,
+ deviceId: Int,
+ ) {
+ receiver.onKeyboardShortcutsReceived(emptyList())
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index d5451ee8eb10..025f556991f2 100644
--- a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -16,9 +16,12 @@
package android.view
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import org.mockito.Mockito.mock
+val Kosmos.fakeWindowManager by Kosmos.Fixture { FakeWindowManager(applicationContext) }
+
val Kosmos.mockWindowManager: WindowManager by Kosmos.Fixture { mock(WindowManager::class.java) }
var Kosmos.windowManager: WindowManager by Kosmos.Fixture { mockWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
index e1c6699348a9..021c7bbb44cd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
@@ -16,11 +16,21 @@
package com.android.app.viewcapture
+import android.view.fakeWindowManager
import com.android.systemui.kosmos.Kosmos
import org.mockito.kotlin.mock
val Kosmos.mockViewCaptureAwareWindowManager by
Kosmos.Fixture { mock<ViewCaptureAwareWindowManager>() }
+val Kosmos.realCaptureAwareWindowManager by
+ Kosmos.Fixture {
+ ViewCaptureAwareWindowManager(
+ fakeWindowManager,
+ lazyViewCapture = lazy { mock<ViewCapture>() },
+ isViewCaptureEnabled = false,
+ )
+ }
+
var Kosmos.viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager by
Kosmos.Fixture { mockViewCaptureAwareWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 3041240e8c86..b8be6aa50015 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -29,6 +29,8 @@ import android.util.ArraySet;
import android.util.Log;
import android.view.Display;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.GuardedBy;
import com.android.systemui.res.R;
@@ -43,6 +45,9 @@ public class SysuiTestableContext extends TestableContext {
private final Map<UserHandle, Context> mContextForUser = new HashMap<>();
private final Map<String, Context> mContextForPackage = new HashMap<>();
+ @Nullable
+ private Display mCustomDisplay;
+
public SysuiTestableContext(Context base) {
super(base);
setTheme(R.style.Theme_SystemUI);
@@ -64,6 +69,18 @@ public class SysuiTestableContext extends TestableContext {
return context;
}
+ public void setDisplay(Display display) {
+ mCustomDisplay = display;
+ }
+
+ @Override
+ public Display getDisplay() {
+ if (mCustomDisplay != null) {
+ return mCustomDisplay;
+ }
+ return super.getDisplay();
+ }
+
public SysuiTestableContext createDefaultDisplayContext() {
Display display = getBaseContext().getSystemService(DisplayManager.class).getDisplays()[0];
return (SysuiTestableContext) createDisplayContext(display);
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
new file mode 100644
index 000000000000..4bcff5577e4b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.decor
+
+import android.content.testableContext
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.privacyDotDecorProviderFactory by
+ Kosmos.Fixture {
+ PrivacyDotDecorProviderFactory(testableContext.orCreateTestableResources.resources)
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 87f7142b8817..ad2654a6b471 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -29,6 +29,7 @@ import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.shade.mockNotificationShadeWindowViewController
import com.android.systemui.shade.mockShadeSurface
import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.data.repository.privacyDotWindowControllerStore
import com.android.systemui.statusbar.data.repository.statusBarModeRepository
import com.android.systemui.statusbar.mockNotificationRemoteInputManager
import com.android.systemui.statusbar.phone.mockAutoHideController
@@ -77,5 +78,6 @@ val Kosmos.multiDisplayStatusBarStarter by
statusBarInitializerStore,
statusBarWindowControllerStore,
statusBarInitializerStore,
+ privacyDotWindowControllerStore,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt
new file mode 100644
index 000000000000..27845aae50dc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotViewControllerStore.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.PrivacyDotViewController
+import org.mockito.kotlin.mock
+
+class FakePrivacyDotViewControllerStore : PrivacyDotViewControllerStore {
+ private val perDisplayMockControllers = mutableMapOf<Int, PrivacyDotViewController>()
+
+ override val defaultDisplay: PrivacyDotViewController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): PrivacyDotViewController {
+ return perDisplayMockControllers.computeIfAbsent(displayId) { mock() }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.kt
new file mode 100644
index 000000000000..f0aacc0dc9b7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakePrivacyDotWindowControllerStore.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.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.PrivacyDotWindowController
+import org.mockito.kotlin.mock
+
+class FakePrivacyDotWindowControllerStore : PrivacyDotWindowControllerStore {
+
+ private val perDisplayMockControllers = mutableMapOf<Int, PrivacyDotWindowController>()
+
+ override val defaultDisplay: PrivacyDotWindowController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): PrivacyDotWindowController {
+ return perDisplayMockControllers.computeIfAbsent(displayId) { mock() }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.kt
new file mode 100644
index 000000000000..fa9f1bfa9f92
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeSystemEventChipAnimationControllerStore.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.statusbar.data.repository
+
+import android.view.Display
+import com.android.systemui.statusbar.events.SystemEventChipAnimationController
+import org.mockito.kotlin.mock
+
+class FakeSystemEventChipAnimationControllerStore : SystemEventChipAnimationControllerStore {
+
+ private val perDisplayMocks = mutableMapOf<Int, SystemEventChipAnimationController>()
+
+ override val defaultDisplay: SystemEventChipAnimationController
+ get() = forDisplay(Display.DEFAULT_DISPLAY)
+
+ override fun forDisplay(displayId: Int): SystemEventChipAnimationController {
+ return perDisplayMocks.computeIfAbsent(displayId) { mock() }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
new file mode 100644
index 000000000000..3d428a12610c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotViewControllerStoreKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.statusbar.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakePrivacyDotViewControllerStore by
+ Kosmos.Fixture { FakePrivacyDotViewControllerStore() }
+
+var Kosmos.privacyDotViewControllerStore: PrivacyDotViewControllerStore by
+ Kosmos.Fixture { fakePrivacyDotViewControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
new file mode 100644
index 000000000000..aae32cfaafa6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.fakePrivacyDotWindowControllerStore by
+ Kosmos.Fixture { FakePrivacyDotWindowControllerStore() }
+
+val Kosmos.privacyDotWindowControllerStoreImpl by
+ Kosmos.Fixture {
+ PrivacyDotWindowControllerStoreImpl(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ windowControllerFactory = { _, _, _, _ -> mock() },
+ displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+ privacyDotViewControllerStore = privacyDotViewControllerStore,
+ viewCaptureAwareWindowManagerFactory =
+ object : ViewCaptureAwareWindowManager.Factory {
+ override fun create(
+ windowManager: WindowManager
+ ): ViewCaptureAwareWindowManager {
+ return mock()
+ }
+ },
+ )
+ }
+
+var Kosmos.privacyDotWindowControllerStore: PrivacyDotWindowControllerStore by
+ Kosmos.Fixture { fakePrivacyDotWindowControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt
new file mode 100644
index 000000000000..f0c8f4b87ab6
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/SystemEventChipAnimationControllerStoreKosmos.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.statusbar.data.repository
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.window.statusBarWindowControllerStore
+import org.mockito.kotlin.mock
+
+val Kosmos.fakeSystemEventChipAnimationControllerStore by
+ Kosmos.Fixture { FakeSystemEventChipAnimationControllerStore() }
+
+val Kosmos.systemEventChipAnimationControllerStoreImpl by
+ Kosmos.Fixture {
+ SystemEventChipAnimationControllerStoreImpl(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ factory = { _, _, _ -> mock() },
+ displayWindowPropertiesRepository = displayWindowPropertiesRepository,
+ statusBarWindowControllerStore = statusBarWindowControllerStore,
+ statusBarContentInsetsProviderStore = statusBarContentInsetsProviderStore,
+ )
+ }
+
+var Kosmos.systemEventChipAnimationControllerStore by
+ Kosmos.Fixture { fakeSystemEventChipAnimationControllerStore }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt
new file mode 100644
index 000000000000..53c39a6581d2
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/FakePrivacyDotViewController.kt
@@ -0,0 +1,63 @@
+/*
+ * 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.statusbar.events
+
+import android.view.View
+
+class FakePrivacyDotViewController : PrivacyDotViewController {
+
+ var topLeft: View? = null
+ private set
+
+ var topRight: View? = null
+ private set
+
+ var bottomLeft: View? = null
+ private set
+
+ var bottomRight: View? = null
+ private set
+
+ var isInitialized = false
+ private set
+
+ override fun stop() {}
+
+ override var currentViewState: ViewState = ViewState()
+
+ override var showingListener: PrivacyDotViewController.ShowingListener? = null
+
+ override fun setNewRotation(rot: Int) {}
+
+ override fun hideDotView(dot: View, animate: Boolean) {}
+
+ override fun showDotView(dot: View, animate: Boolean) {}
+
+ override fun updateRotations(rotation: Int, paddingTop: Int) {}
+
+ override fun setCornerSizes(state: ViewState) {}
+
+ override fun initialize(topLeft: View, topRight: View, bottomLeft: View, bottomRight: View) {
+ this.topLeft = topLeft
+ this.topRight = topRight
+ this.bottomLeft = bottomLeft
+ this.bottomRight = bottomRight
+ isInitialized = true
+ }
+
+ override fun updateDotView(state: ViewState) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
new file mode 100644
index 000000000000..9cbc9756cf15
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * 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.statusbar.events
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+val Kosmos.mockPrivacyDotViewController by Kosmos.Fixture { mock<PrivacyDotViewController>() }
+
+val Kosmos.fakePrivacyDotViewController by Kosmos.Fixture { FakePrivacyDotViewController() }
+
+var Kosmos.privacyDotViewController by Kosmos.Fixture { fakePrivacyDotViewController }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
new file mode 100644
index 000000000000..c73838708a7a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.statusbar.events
+
+import android.content.testableContext
+import android.view.layoutInflater
+import com.android.app.viewcapture.realCaptureAwareWindowManager
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.decor.privacyDotDecorProviderFactory
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.privacyDotWindowController by
+ Kosmos.Fixture {
+ PrivacyDotWindowController(
+ testableContext.displayId,
+ privacyDotViewController,
+ realCaptureAwareWindowManager,
+ layoutInflater,
+ fakeExecutor,
+ privacyDotDecorProviderFactory,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.kt
new file mode 100644
index 000000000000..186b04516fd3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/SystemEventChipAnimationControllerKosmos.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.statusbar.events
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.data.repository.systemEventChipAnimationControllerStore
+
+val Kosmos.multiDisplaySystemEventChipAnimationController by
+ Kosmos.Fixture {
+ MultiDisplaySystemEventChipAnimationController(
+ displayRepository,
+ systemEventChipAnimationControllerStore,
+ )
+ }