diff options
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, + ) + } |