Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt +43 −8 Original line number Diff line number Diff line Loading @@ -29,7 +29,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_SHADE_WINDOW_GOES_AROUND import com.android.systemui.SysuiTestCase import com.android.systemui.display.data.repository.display import com.android.systemui.kosmos.backgroundScope import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository Loading @@ -42,6 +43,7 @@ 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.statusbar.featurepods.vc.domain.interactor.fakeAvControlsChipInteractor import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.testKosmos Loading @@ -55,10 +57,10 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.concurrent.TimeUnit import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito @SmallTest @RunWith(AndroidJUnit4::class) Loading @@ -67,9 +69,9 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val mockDisplay = createMockDisplay() private val mockAnimationScheduler = mock<SystemStatusAnimationScheduler>() private val context = getContext().createDisplayContext(mockDisplay) private val testScope = TestScope() private val executor = InstantExecutor() private val statusBarStateController = FakeStatusBarStateController() private val configurationController = FakeConfigurationController() Loading @@ -91,12 +93,13 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { private fun createController() = PrivacyDotViewControllerImpl( executor, testScope.backgroundScope, kosmos.backgroundScope, statusBarStateController, configurationController, contentInsetsProvider, animationScheduler = mock<SystemStatusAnimationScheduler>(), animationScheduler = mockAnimationScheduler, shadeInteractor = shadeInteractor, avControlsChipInteractor = kosmos.fakeAvControlsChipInteractor, uiExecutor = executor, displayId = DISPLAY_ID, shadeDisplaysInteractor = { shadeDisplaysInteractor }, Loading Loading @@ -317,7 +320,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_CONNECTED_DISPLAYS, FLAG_SHADE_WINDOW_GOES_AROUND) fun init_shadeExpandedOnDifferentDisplay_doesNotChangeShadeExpandedState() = testScope.runTest { kosmos.runTest { shadeDisplaysRepository.setDisplayId(Display.DEFAULT_DISPLAY) statusBarStateController.state = SHADE statusBarStateController.expanded = true Loading @@ -332,7 +335,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_CONNECTED_DISPLAYS, FLAG_SHADE_WINDOW_GOES_AROUND) fun init_shadeExpandedOnThisDisplay_doesChangeShadeExpandedState() = testScope.runTest { kosmos.runTest { shadeDisplaysRepository.setDisplayId(Display.DEFAULT_DISPLAY) statusBarStateController.state = SHADE statusBarStateController.expanded = false Loading Loading @@ -365,6 +368,38 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { .isNotEqualTo(UNSPECIFIED_GRAVITY) } @Test fun initialize_noShow() { val controller: PrivacyDotViewController = createAndInitializeController() assertThat(controller.currentViewState.shouldShowDot()).isEqualTo(false) } @Test fun initialize_animationFinished_shouldShow() = kosmos.runTest { val captor = ArgumentCaptor.forClass(SystemStatusAnimationCallback::class.java) val controller: PrivacyDotViewController = createAndInitializeController() Mockito.verify(mockAnimationScheduler).addCallback(captor.capture()) val callback: SystemStatusAnimationCallback = captor.value fakeAvControlsChipInteractor.isShowingAvChip.value = false // This informs the controller of an active privacy event. callback.onSystemStatusAnimationTransitionToPersistentDot(null) assertThat(controller.currentViewState.shouldShowDot()).isEqualTo(true) } @Test fun initialize_animationFinished_showingAvChip_noShow() = kosmos.runTest { val captor = ArgumentCaptor.forClass(SystemStatusAnimationCallback::class.java) val controller: PrivacyDotViewController = createAndInitializeController() Mockito.verify(mockAnimationScheduler).addCallback(captor.capture()) val callback: SystemStatusAnimationCallback = captor.value fakeAvControlsChipInteractor.isShowingAvChip.value = true // This informs the controller of an active privacy event. callback.onSystemStatusAnimationTransitionToPersistentDot(null) assertThat(controller.currentViewState.shouldShowDot()).isEqualTo(false) } private fun setRotation(rotation: Int) { whenever(mockDisplay.rotation).thenReturn(rotation) } Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/vc/domain/interactor/AvControlsChipInteractorTest.kt 0 → 100644 +171 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.featurepods.vc.domain.interactor import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.privacy.PrivacyApplication import com.android.systemui.privacy.PrivacyItem import com.android.systemui.privacy.PrivacyType import com.android.systemui.shade.data.repository.fakePrivacyChipRepository import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository import com.android.systemui.statusbar.featurepods.vc.shared.model.AvControlsChipModel import com.android.systemui.statusbar.featurepods.vc.shared.model.SensorActivityModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.flow.first import org.junit.Before import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class AvControlsChipInteractorTest() : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val Kosmos.underTest by Kosmos.Fixture { avControlsChipInteractorImpl } private val cameraItem = PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication("fakepackage", 0)) private val microphoneItem = PrivacyItem(PrivacyType.TYPE_MICROPHONE, PrivacyApplication("fakepackage", 0)) private fun cameraModel() = AvControlsChipModel(SensorActivityModel.Active(SensorActivityModel.Active.Sensors.CAMERA)) private fun microphoneModel() = AvControlsChipModel( SensorActivityModel.Active(SensorActivityModel.Active.Sensors.MICROPHONE) ) private fun cameraAndMicrophoneModel() = AvControlsChipModel( SensorActivityModel.Active(SensorActivityModel.Active.Sensors.CAMERA_AND_MICROPHONE) ) private fun inactiveModel() = AvControlsChipModel(SensorActivityModel.Inactive) private fun Kosmos.lastModel(): AvControlsChipModel? = collectLastValue(underTest.model).invoke() @Before fun setUp() { kosmos.underTest.initialize() } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun flagOn_disabled() = kosmos.runTest { assertThat(underTest.isEnabled.value).isEqualTo(false) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun flagOn_enabled() = kosmos.runTest { assertThat(underTest.isEnabled.value).isEqualTo(true) } @Test fun defaultModel() = kosmos.runTest { assertThat(lastModel()).isEqualTo(inactiveModel()) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) assertThat(lastModel()).isEqualTo(cameraModel()) } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActive_flagOff_InactiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) assertThat(lastModel()).isEqualTo(inactiveModel()) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun microphoneActiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(microphoneItem)) assertThat(lastModel()).isEqualTo(microphoneModel()) } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun microphoneActive_flagOff_InactiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(microphoneItem)) assertThat(lastModel()).isEqualTo(inactiveModel()) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraAndMicrophoneActiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem, microphoneItem)) assertThat(lastModel()).isEqualTo(cameraAndMicrophoneModel()) } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraAndMicrophoneActive_flagOff_InactiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem, microphoneItem)) assertThat(lastModel()).isEqualTo(inactiveModel()) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActive_noFullscreen_shouldSuppressDot() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false assertThat(underTest.isShowingAvChip.first()).isEqualTo(true) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActive_fullscreen_shouldNotSuppressDot() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true assertThat(underTest.isShowingAvChip.first()).isEqualTo(false) } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActive_flagOff_noFullscreen_shouldNotSuppressDot() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false assertThat(underTest.isShowingAvChip.first()).isEqualTo(false) } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActive_flagOff_fullscreen_shouldNotSuppressDot() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true assertThat(underTest.isShowingAvChip.first()).isEqualTo(false) } } packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/vc/ui/viewmodel/AvControlsChipViewModelTest.kt +2 −3 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ import com.android.systemui.statusbar.featurepods.av.ui.viewmodel.AvControlsChip import com.android.systemui.statusbar.featurepods.popups.ui.model.ChipIcon import com.android.systemui.statusbar.featurepods.popups.ui.model.PopupChipId import com.android.systemui.statusbar.featurepods.popups.ui.model.PopupChipModel import com.android.systemui.statusbar.featurepods.vc.domain.interactor.avControlsChipInteractor import com.android.systemui.statusbar.featurepods.vc.domain.interactor.avControlsChipInteractorImpl import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test Loading @@ -46,7 +46,6 @@ import org.junit.runner.RunWith class AvControlsChipViewModelTest() : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val underTest = kosmos.avControlsChipViewModelFactory.create() private val avControlsChipInteractor by lazy { kosmos.avControlsChipInteractor } private val cameraItem = PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication("fakepackage", 0)) private val microphoneItem = Loading @@ -54,7 +53,7 @@ class AvControlsChipViewModelTest() : SysuiTestCase() { @Before fun setUp() { avControlsChipInteractor.initialize() kosmos.avControlsChipInteractorImpl.initialize() underTest.activateIn(kosmos.testScope) } Loading packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +2 −0 Original line number Diff line number Diff line Loading @@ -132,6 +132,7 @@ import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.disableflags.dagger.DisableFlagsModule; import com.android.systemui.statusbar.events.StatusBarEventsModule; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.featurepods.vc.AvControlsChipModule; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; Loading Loading @@ -210,6 +211,7 @@ import javax.inject.Named; @Module(includes = { ActivityManagerModule.class, AmbientModule.class, AvControlsChipModule.class, AppOpsModule.class, AssistModule.class, AuthenticationModule.class, Loading packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +15 −1 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ 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.statusbar.featurepods.vc.domain.interactor.AvControlsChipInteractor import com.android.systemui.statusbar.layout.StatusBarContentInsetsChangedListener import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider import com.android.systemui.statusbar.policy.ConfigurationController Loading Loading @@ -123,6 +124,7 @@ constructor( @Assisted private val contentInsetsProvider: StatusBarContentInsetsProvider, private val animationScheduler: SystemStatusAnimationScheduler, shadeInteractor: ShadeInteractor?, avControlsChipInteractor: AvControlsChipInteractor?, @ScreenDecorationsThread val uiExecutor: DelayableExecutor, @Assisted private val displayId: Int, private val shadeDisplaysInteractor: Lazy<ShadeDisplaysInteractor>?, Loading Loading @@ -189,6 +191,14 @@ constructor( contentInsetsProvider.addCallback(insetsChangedListener) configurationController.addCallback(configurationListener) stateController.addCallback(statusBarStateListener) scope.launch { avControlsChipInteractor?.isShowingAvChip?.collect { shouldSuppress -> synchronized(lock) { nextViewState = nextViewState.copy(dotDuplicatedByAvControlsChip = shouldSuppress) } } } scope.launch { if ( StatusBarConnectedDisplays.isEnabled && Loading Loading @@ -672,6 +682,7 @@ data class ViewState( val systemPrivacyEventIsActive: Boolean = false, val shadeExpanded: Boolean = false, val qsExpanded: Boolean = false, val dotDuplicatedByAvControlsChip: Boolean = false, val portraitRect: Rect? = null, val landscapeRect: Rect? = null, val upsideDownRect: Rect? = null, Loading @@ -684,7 +695,10 @@ data class ViewState( val contentDescription: String? = null, ) { fun shouldShowDot(): Boolean { return systemPrivacyEventIsActive && !shadeExpanded && !qsExpanded return systemPrivacyEventIsActive && !shadeExpanded && !qsExpanded && !dotDuplicatedByAvControlsChip } fun needsLayout(other: ViewState): Boolean { Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/PrivacyDotViewControllerTest.kt +43 −8 Original line number Diff line number Diff line Loading @@ -29,7 +29,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_SHADE_WINDOW_GOES_AROUND import com.android.systemui.SysuiTestCase import com.android.systemui.display.data.repository.display import com.android.systemui.kosmos.backgroundScope import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.res.R import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository Loading @@ -42,6 +43,7 @@ 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.statusbar.featurepods.vc.domain.interactor.fakeAvControlsChipInteractor import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.testKosmos Loading @@ -55,10 +57,10 @@ import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.util.concurrent.TimeUnit import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mockito @SmallTest @RunWith(AndroidJUnit4::class) Loading @@ -67,9 +69,9 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val mockDisplay = createMockDisplay() private val mockAnimationScheduler = mock<SystemStatusAnimationScheduler>() private val context = getContext().createDisplayContext(mockDisplay) private val testScope = TestScope() private val executor = InstantExecutor() private val statusBarStateController = FakeStatusBarStateController() private val configurationController = FakeConfigurationController() Loading @@ -91,12 +93,13 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { private fun createController() = PrivacyDotViewControllerImpl( executor, testScope.backgroundScope, kosmos.backgroundScope, statusBarStateController, configurationController, contentInsetsProvider, animationScheduler = mock<SystemStatusAnimationScheduler>(), animationScheduler = mockAnimationScheduler, shadeInteractor = shadeInteractor, avControlsChipInteractor = kosmos.fakeAvControlsChipInteractor, uiExecutor = executor, displayId = DISPLAY_ID, shadeDisplaysInteractor = { shadeDisplaysInteractor }, Loading Loading @@ -317,7 +320,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_CONNECTED_DISPLAYS, FLAG_SHADE_WINDOW_GOES_AROUND) fun init_shadeExpandedOnDifferentDisplay_doesNotChangeShadeExpandedState() = testScope.runTest { kosmos.runTest { shadeDisplaysRepository.setDisplayId(Display.DEFAULT_DISPLAY) statusBarStateController.state = SHADE statusBarStateController.expanded = true Loading @@ -332,7 +335,7 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { @Test @EnableFlags(FLAG_STATUS_BAR_CONNECTED_DISPLAYS, FLAG_SHADE_WINDOW_GOES_AROUND) fun init_shadeExpandedOnThisDisplay_doesChangeShadeExpandedState() = testScope.runTest { kosmos.runTest { shadeDisplaysRepository.setDisplayId(Display.DEFAULT_DISPLAY) statusBarStateController.state = SHADE statusBarStateController.expanded = false Loading Loading @@ -365,6 +368,38 @@ class PrivacyDotViewControllerTest : SysuiTestCase() { .isNotEqualTo(UNSPECIFIED_GRAVITY) } @Test fun initialize_noShow() { val controller: PrivacyDotViewController = createAndInitializeController() assertThat(controller.currentViewState.shouldShowDot()).isEqualTo(false) } @Test fun initialize_animationFinished_shouldShow() = kosmos.runTest { val captor = ArgumentCaptor.forClass(SystemStatusAnimationCallback::class.java) val controller: PrivacyDotViewController = createAndInitializeController() Mockito.verify(mockAnimationScheduler).addCallback(captor.capture()) val callback: SystemStatusAnimationCallback = captor.value fakeAvControlsChipInteractor.isShowingAvChip.value = false // This informs the controller of an active privacy event. callback.onSystemStatusAnimationTransitionToPersistentDot(null) assertThat(controller.currentViewState.shouldShowDot()).isEqualTo(true) } @Test fun initialize_animationFinished_showingAvChip_noShow() = kosmos.runTest { val captor = ArgumentCaptor.forClass(SystemStatusAnimationCallback::class.java) val controller: PrivacyDotViewController = createAndInitializeController() Mockito.verify(mockAnimationScheduler).addCallback(captor.capture()) val callback: SystemStatusAnimationCallback = captor.value fakeAvControlsChipInteractor.isShowingAvChip.value = true // This informs the controller of an active privacy event. callback.onSystemStatusAnimationTransitionToPersistentDot(null) assertThat(controller.currentViewState.shouldShowDot()).isEqualTo(false) } private fun setRotation(rotation: Int) { whenever(mockDisplay.rotation).thenReturn(rotation) } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/vc/domain/interactor/AvControlsChipInteractorTest.kt 0 → 100644 +171 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.featurepods.vc.domain.interactor import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN import com.android.systemui.SysuiTestCase import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.privacy.PrivacyApplication import com.android.systemui.privacy.PrivacyItem import com.android.systemui.privacy.PrivacyType import com.android.systemui.shade.data.repository.fakePrivacyChipRepository import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository import com.android.systemui.statusbar.featurepods.vc.shared.model.AvControlsChipModel import com.android.systemui.statusbar.featurepods.vc.shared.model.SensorActivityModel import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test import kotlinx.coroutines.flow.first import org.junit.Before import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class AvControlsChipInteractorTest() : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val Kosmos.underTest by Kosmos.Fixture { avControlsChipInteractorImpl } private val cameraItem = PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication("fakepackage", 0)) private val microphoneItem = PrivacyItem(PrivacyType.TYPE_MICROPHONE, PrivacyApplication("fakepackage", 0)) private fun cameraModel() = AvControlsChipModel(SensorActivityModel.Active(SensorActivityModel.Active.Sensors.CAMERA)) private fun microphoneModel() = AvControlsChipModel( SensorActivityModel.Active(SensorActivityModel.Active.Sensors.MICROPHONE) ) private fun cameraAndMicrophoneModel() = AvControlsChipModel( SensorActivityModel.Active(SensorActivityModel.Active.Sensors.CAMERA_AND_MICROPHONE) ) private fun inactiveModel() = AvControlsChipModel(SensorActivityModel.Inactive) private fun Kosmos.lastModel(): AvControlsChipModel? = collectLastValue(underTest.model).invoke() @Before fun setUp() { kosmos.underTest.initialize() } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun flagOn_disabled() = kosmos.runTest { assertThat(underTest.isEnabled.value).isEqualTo(false) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun flagOn_enabled() = kosmos.runTest { assertThat(underTest.isEnabled.value).isEqualTo(true) } @Test fun defaultModel() = kosmos.runTest { assertThat(lastModel()).isEqualTo(inactiveModel()) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) assertThat(lastModel()).isEqualTo(cameraModel()) } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActive_flagOff_InactiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) assertThat(lastModel()).isEqualTo(inactiveModel()) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun microphoneActiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(microphoneItem)) assertThat(lastModel()).isEqualTo(microphoneModel()) } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun microphoneActive_flagOff_InactiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(microphoneItem)) assertThat(lastModel()).isEqualTo(inactiveModel()) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraAndMicrophoneActiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem, microphoneItem)) assertThat(lastModel()).isEqualTo(cameraAndMicrophoneModel()) } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraAndMicrophoneActive_flagOff_InactiveModel() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem, microphoneItem)) assertThat(lastModel()).isEqualTo(inactiveModel()) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActive_noFullscreen_shouldSuppressDot() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false assertThat(underTest.isShowingAvChip.first()).isEqualTo(true) } @Test @EnableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActive_fullscreen_shouldNotSuppressDot() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true assertThat(underTest.isShowingAvChip.first()).isEqualTo(false) } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActive_flagOff_noFullscreen_shouldNotSuppressDot() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = false assertThat(underTest.isShowingAvChip.first()).isEqualTo(false) } @Test @DisableFlags(FLAG_EXPANDED_PRIVACY_INDICATORS_ON_LARGE_SCREEN) fun cameraActive_flagOff_fullscreen_shouldNotSuppressDot() = kosmos.runTest { fakePrivacyChipRepository.setPrivacyItems(listOf(cameraItem)) fakeStatusBarModeRepository.defaultDisplay.isInFullscreenMode.value = true assertThat(underTest.isShowingAvChip.first()).isEqualTo(false) } }
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/vc/ui/viewmodel/AvControlsChipViewModelTest.kt +2 −3 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ import com.android.systemui.statusbar.featurepods.av.ui.viewmodel.AvControlsChip import com.android.systemui.statusbar.featurepods.popups.ui.model.ChipIcon import com.android.systemui.statusbar.featurepods.popups.ui.model.PopupChipId import com.android.systemui.statusbar.featurepods.popups.ui.model.PopupChipModel import com.android.systemui.statusbar.featurepods.vc.domain.interactor.avControlsChipInteractor import com.android.systemui.statusbar.featurepods.vc.domain.interactor.avControlsChipInteractorImpl import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlin.test.Test Loading @@ -46,7 +46,6 @@ import org.junit.runner.RunWith class AvControlsChipViewModelTest() : SysuiTestCase() { private val kosmos = testKosmos().useUnconfinedTestDispatcher() private val underTest = kosmos.avControlsChipViewModelFactory.create() private val avControlsChipInteractor by lazy { kosmos.avControlsChipInteractor } private val cameraItem = PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication("fakepackage", 0)) private val microphoneItem = Loading @@ -54,7 +53,7 @@ class AvControlsChipViewModelTest() : SysuiTestCase() { @Before fun setUp() { avControlsChipInteractor.initialize() kosmos.avControlsChipInteractorImpl.initialize() underTest.activateIn(kosmos.testScope) } Loading
packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +2 −0 Original line number Diff line number Diff line Loading @@ -132,6 +132,7 @@ import com.android.systemui.statusbar.dagger.StatusBarModule; import com.android.systemui.statusbar.disableflags.dagger.DisableFlagsModule; import com.android.systemui.statusbar.events.StatusBarEventsModule; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.featurepods.vc.AvControlsChipModule; import com.android.systemui.statusbar.notification.NotifPipelineFlags; import com.android.systemui.statusbar.notification.collection.NotifPipeline; import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder; Loading Loading @@ -210,6 +211,7 @@ import javax.inject.Named; @Module(includes = { ActivityManagerModule.class, AmbientModule.class, AvControlsChipModule.class, AppOpsModule.class, AssistModule.class, AuthenticationModule.class, Loading
packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt +15 −1 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ 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.statusbar.featurepods.vc.domain.interactor.AvControlsChipInteractor import com.android.systemui.statusbar.layout.StatusBarContentInsetsChangedListener import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider import com.android.systemui.statusbar.policy.ConfigurationController Loading Loading @@ -123,6 +124,7 @@ constructor( @Assisted private val contentInsetsProvider: StatusBarContentInsetsProvider, private val animationScheduler: SystemStatusAnimationScheduler, shadeInteractor: ShadeInteractor?, avControlsChipInteractor: AvControlsChipInteractor?, @ScreenDecorationsThread val uiExecutor: DelayableExecutor, @Assisted private val displayId: Int, private val shadeDisplaysInteractor: Lazy<ShadeDisplaysInteractor>?, Loading Loading @@ -189,6 +191,14 @@ constructor( contentInsetsProvider.addCallback(insetsChangedListener) configurationController.addCallback(configurationListener) stateController.addCallback(statusBarStateListener) scope.launch { avControlsChipInteractor?.isShowingAvChip?.collect { shouldSuppress -> synchronized(lock) { nextViewState = nextViewState.copy(dotDuplicatedByAvControlsChip = shouldSuppress) } } } scope.launch { if ( StatusBarConnectedDisplays.isEnabled && Loading Loading @@ -672,6 +682,7 @@ data class ViewState( val systemPrivacyEventIsActive: Boolean = false, val shadeExpanded: Boolean = false, val qsExpanded: Boolean = false, val dotDuplicatedByAvControlsChip: Boolean = false, val portraitRect: Rect? = null, val landscapeRect: Rect? = null, val upsideDownRect: Rect? = null, Loading @@ -684,7 +695,10 @@ data class ViewState( val contentDescription: String? = null, ) { fun shouldShowDot(): Boolean { return systemPrivacyEventIsActive && !shadeExpanded && !qsExpanded return systemPrivacyEventIsActive && !shadeExpanded && !qsExpanded && !dotDuplicatedByAvControlsChip } fun needsLayout(other: ViewState): Boolean { Loading