Loading packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -342,6 +342,16 @@ flag { bug: "374159193" } flag { name: "status_bar_privacy_chip_animation_exemption" namespace: "systemui" description: "Exempt the default camera app from the privacy chip animation." bug: "422243884" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "notification_shade_ui_thread" namespace: "systemui" Loading packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt +118 −0 Original line number Diff line number Diff line Loading @@ -15,19 +15,29 @@ */ package com.android.systemui.statusbar.events import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.DisableFlags import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION import com.android.systemui.SysuiTestCase import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay import com.android.systemui.log.logcatLogBuffer import com.android.systemui.privacy.PrivacyApplication import com.android.systemui.privacy.PrivacyItem import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.PrivacyType import com.android.systemui.res.R import com.android.systemui.statusbar.featurepods.av.domain.interactor.AvControlsChipInteractor import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat import com.android.systemui.util.time.FakeSystemClock import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest Loading @@ -38,6 +48,7 @@ import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) Loading @@ -60,6 +71,7 @@ class SystemEventCoordinatorTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) overrideResource(R.string.config_cameraGesturePackage, DEFAULT_CAMERA_PACKAGE_NAME) systemEventCoordinator = SystemEventCoordinator( fakeSystemClock, Loading @@ -69,8 +81,10 @@ class SystemEventCoordinatorTest : SysuiTestCase() { context, TestScope(UnconfinedTestDispatcher()), connectedDisplayInteractor, logcatLogBuffer("SystemEventCoordinatorTest"), ) .apply { attachScheduler(scheduler) } `when`(avControlsChipInteractor.isEnabled).thenReturn(MutableStateFlow(false)) } @Test Loading Loading @@ -100,6 +114,108 @@ class SystemEventCoordinatorTest : SysuiTestCase() { verifyNoMoreInteractions(scheduler) } @Test @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_notDefaultCamera_showsAnimation() = testScope.runTest { val privacyList = listOf( PrivacyItem( application = PrivacyApplication("package1", 1), privacyType = PrivacyType.TYPE_CAMERA, ) ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList) verify(scheduler).onStatusEvent(argThat { it.showAnimation }) } @Test @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_defaultCameraApp_cameraAccess_doesNotShowAnimation() = testScope.runTest { val privacyList = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_CAMERA, ), ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList) verify(scheduler).onStatusEvent(argThat { !it.showAnimation }) } @Test @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_defaultCameraApp_microphoneAccess_doesNotShowAnimation() = testScope.runTest { val privacyList = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_MICROPHONE, ) ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList) verify(scheduler).onStatusEvent(argThat { !it.showAnimation }) } @Test @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_defaultCamera_thenAnotherApp_showsAnimation() = testScope.runTest { val privacyList1 = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_CAMERA, ), ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList1) verify(scheduler).onStatusEvent(argThat { !it.showAnimation }) val privacyList2 = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_CAMERA, ), PrivacyItem( application = PrivacyApplication("package1", 1), privacyType = PrivacyType.TYPE_MICROPHONE, ), ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList2) verify(scheduler).onStatusEvent(argThat { it.showAnimation }) } @Test @DisableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_defaultCameraApp_cameraAccess_flagOff_showsAnimation() = testScope.runTest { val privacyList = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_CAMERA, ) ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList) verify(scheduler).onStatusEvent(argThat { it.showAnimation }) } @Test @DisableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_defaultCameraApp_microphoneAccess_flagOff_showsAnimation() = testScope.runTest { val privacyList = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_MICROPHONE, ) ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList) verify(scheduler).onStatusEvent(argThat { it.showAnimation }) } class FakeConnectedDisplayInteractor : ConnectedDisplayInteractor { private val flow = MutableSharedFlow<Unit>() Loading @@ -118,3 +234,5 @@ class SystemEventCoordinatorTest : SysuiTestCase() { get() = TODO("Not yet implemented") } } private const val DEFAULT_CAMERA_PACKAGE_NAME = "my.camera.package" packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt +7 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,13 @@ interface StatusBarEventsModule { fun provideSystemStatusAnimationSchedulerLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("SystemStatusAnimationSchedulerLog", 60) } @Provides @SysUISingleton @SystemEventCoordinatorLog fun provideSystemEventCoordinatorLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("SystemEventCoordinatorLog", 60) } } @Binds Loading packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt +2 −1 Original line number Diff line number Diff line Loading @@ -128,7 +128,8 @@ open class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEven } override fun toString(): String { return "${javaClass.simpleName}(forceVisible=$forceVisible, privacyItems=$privacyItems)" return "${javaClass.simpleName}(forceVisible=$forceVisible, " + "privacyItems=$privacyItems, showAnimation=$showAnimation)" } override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean { Loading packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt +52 −1 Original line number Diff line number Diff line Loading @@ -20,12 +20,17 @@ import android.annotation.IntRange import android.content.Context import android.provider.DeviceConfig import android.provider.DeviceConfig.NAMESPACE_PRIVACY import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.privacy.PrivacyChipBuilder import com.android.systemui.privacy.PrivacyItem import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.PrivacyType import com.android.systemui.res.R import com.android.systemui.statusbar.featurepods.av.domain.interactor.AvControlsChipInteractor import com.android.systemui.statusbar.policy.BatteryController Loading @@ -51,8 +56,11 @@ constructor( private val context: Context, @Application private val appScope: CoroutineScope, connectedDisplayInteractor: ConnectedDisplayInteractor, @SystemEventCoordinatorLog private val logBuffer: LogBuffer, ) { private val onDisplayConnectedFlow = connectedDisplayInteractor.connectedDisplayAddition private val defaultCameraPackageName = context.resources.getString(R.string.config_cameraGesturePackage) private var connectedDisplayCollectionJob: Job? = null private lateinit var scheduler: SystemStatusAnimationScheduler Loading Loading @@ -154,7 +162,7 @@ constructor( } else { val showAnimation = isChipAnimationEnabled() && !containsOnlyLocation(currentPrivacyItems) && !isExemptFromChipAnimation(currentPrivacyItems) && (!uniqueItemsMatch(currentPrivacyItems, previousPrivacyItems) || systemClock.elapsedRealtime() - timeLastEmpty >= DEBOUNCE_TIME) notifyPrivacyItemsChanged(showAnimation) Loading @@ -167,6 +175,40 @@ constructor( two.map { it.application.uid to it.privacyType.permGroupName }.toSet() } // Returns true if the privacy items are exempt from the chip animation. private fun isExemptFromChipAnimation(items: List<PrivacyItem>): Boolean { if (!Flags.statusBarPrivacyChipAnimationExemption()) { return containsOnlyLocation(items) } // Camera and microphone requests by the default camera app are exempt from the // chip animation. Filter those out. val nonExemptItems = items.filterNot { val shouldFilter = isCameraOrMicrophoneRequest(it) && it.application.packageName == defaultCameraPackageName if (shouldFilter) { logBuffer.log( TAG, LogLevel.DEBUG, { str1 = it.application.packageName str2 = it.privacyType.permGroupName }, { "Privacy item from default camera ($str1) is exempt from " + "chip animation. Permission group=$str2" }, ) } shouldFilter } // If the remaining items are only location, the chip animation is also exempt return containsOnlyLocation(nonExemptItems) } // Return true if the only privacy item is location private fun containsOnlyLocation(items: List<PrivacyItem>): Boolean { return items Loading @@ -176,6 +218,12 @@ constructor( .isEmpty() } private fun isCameraOrMicrophoneRequest(item: PrivacyItem): Boolean { return item.privacyType.let { it == PrivacyType.TYPE_CAMERA || it == PrivacyType.TYPE_MICROPHONE } } private fun isChipAnimationEnabled(): Boolean { val defaultValue = context.resources.getBoolean(R.bool.config_enablePrivacyChipAnimation) Loading @@ -186,6 +234,9 @@ constructor( ) } } @VisibleForTesting fun getPrivacyStateListener() = privacyStateListener } private const val DEBOUNCE_TIME = 3000L Loading Loading
packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -342,6 +342,16 @@ flag { bug: "374159193" } flag { name: "status_bar_privacy_chip_animation_exemption" namespace: "systemui" description: "Exempt the default camera app from the privacy chip animation." bug: "422243884" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "notification_shade_ui_thread" namespace: "systemui" Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/events/SystemEventCoordinatorTest.kt +118 −0 Original line number Diff line number Diff line Loading @@ -15,19 +15,29 @@ */ package com.android.systemui.statusbar.events import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.DisableFlags import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION import com.android.systemui.SysuiTestCase import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor.PendingDisplay import com.android.systemui.log.logcatLogBuffer import com.android.systemui.privacy.PrivacyApplication import com.android.systemui.privacy.PrivacyItem import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.PrivacyType import com.android.systemui.res.R import com.android.systemui.statusbar.featurepods.av.domain.interactor.AvControlsChipInteractor import com.android.systemui.statusbar.policy.BatteryController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argThat import com.android.systemui.util.time.FakeSystemClock import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest Loading @@ -38,6 +48,7 @@ import org.mockito.Mock import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidJUnit4::class) Loading @@ -60,6 +71,7 @@ class SystemEventCoordinatorTest : SysuiTestCase() { @Before fun setup() { MockitoAnnotations.initMocks(this) overrideResource(R.string.config_cameraGesturePackage, DEFAULT_CAMERA_PACKAGE_NAME) systemEventCoordinator = SystemEventCoordinator( fakeSystemClock, Loading @@ -69,8 +81,10 @@ class SystemEventCoordinatorTest : SysuiTestCase() { context, TestScope(UnconfinedTestDispatcher()), connectedDisplayInteractor, logcatLogBuffer("SystemEventCoordinatorTest"), ) .apply { attachScheduler(scheduler) } `when`(avControlsChipInteractor.isEnabled).thenReturn(MutableStateFlow(false)) } @Test Loading Loading @@ -100,6 +114,108 @@ class SystemEventCoordinatorTest : SysuiTestCase() { verifyNoMoreInteractions(scheduler) } @Test @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_notDefaultCamera_showsAnimation() = testScope.runTest { val privacyList = listOf( PrivacyItem( application = PrivacyApplication("package1", 1), privacyType = PrivacyType.TYPE_CAMERA, ) ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList) verify(scheduler).onStatusEvent(argThat { it.showAnimation }) } @Test @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_defaultCameraApp_cameraAccess_doesNotShowAnimation() = testScope.runTest { val privacyList = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_CAMERA, ), ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList) verify(scheduler).onStatusEvent(argThat { !it.showAnimation }) } @Test @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_defaultCameraApp_microphoneAccess_doesNotShowAnimation() = testScope.runTest { val privacyList = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_MICROPHONE, ) ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList) verify(scheduler).onStatusEvent(argThat { !it.showAnimation }) } @Test @EnableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_defaultCamera_thenAnotherApp_showsAnimation() = testScope.runTest { val privacyList1 = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_CAMERA, ), ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList1) verify(scheduler).onStatusEvent(argThat { !it.showAnimation }) val privacyList2 = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_CAMERA, ), PrivacyItem( application = PrivacyApplication("package1", 1), privacyType = PrivacyType.TYPE_MICROPHONE, ), ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList2) verify(scheduler).onStatusEvent(argThat { it.showAnimation }) } @Test @DisableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_defaultCameraApp_cameraAccess_flagOff_showsAnimation() = testScope.runTest { val privacyList = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_CAMERA, ) ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList) verify(scheduler).onStatusEvent(argThat { it.showAnimation }) } @Test @DisableFlags(FLAG_STATUS_BAR_PRIVACY_CHIP_ANIMATION_EXEMPTION) fun onPrivacyItemsChanged_defaultCameraApp_microphoneAccess_flagOff_showsAnimation() = testScope.runTest { val privacyList = listOf( PrivacyItem( application = PrivacyApplication(DEFAULT_CAMERA_PACKAGE_NAME, 1), privacyType = PrivacyType.TYPE_MICROPHONE, ) ) systemEventCoordinator.getPrivacyStateListener().onPrivacyItemsChanged(privacyList) verify(scheduler).onStatusEvent(argThat { it.showAnimation }) } class FakeConnectedDisplayInteractor : ConnectedDisplayInteractor { private val flow = MutableSharedFlow<Unit>() Loading @@ -118,3 +234,5 @@ class SystemEventCoordinatorTest : SysuiTestCase() { get() = TODO("Not yet implemented") } } private const val DEFAULT_CAMERA_PACKAGE_NAME = "my.camera.package"
packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt +7 −0 Original line number Diff line number Diff line Loading @@ -34,6 +34,13 @@ interface StatusBarEventsModule { fun provideSystemStatusAnimationSchedulerLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("SystemStatusAnimationSchedulerLog", 60) } @Provides @SysUISingleton @SystemEventCoordinatorLog fun provideSystemEventCoordinatorLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("SystemEventCoordinatorLog", 60) } } @Binds Loading
packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt +2 −1 Original line number Diff line number Diff line Loading @@ -128,7 +128,8 @@ open class PrivacyEvent(override val showAnimation: Boolean = true) : StatusEven } override fun toString(): String { return "${javaClass.simpleName}(forceVisible=$forceVisible, privacyItems=$privacyItems)" return "${javaClass.simpleName}(forceVisible=$forceVisible, " + "privacyItems=$privacyItems, showAnimation=$showAnimation)" } override fun shouldUpdateFromEvent(other: StatusEvent?): Boolean { Loading
packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt +52 −1 Original line number Diff line number Diff line Loading @@ -20,12 +20,17 @@ import android.annotation.IntRange import android.content.Context import android.provider.DeviceConfig import android.provider.DeviceConfig.NAMESPACE_PRIVACY import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.display.domain.interactor.ConnectedDisplayInteractor import com.android.systemui.log.LogBuffer import com.android.systemui.log.core.LogLevel import com.android.systemui.privacy.PrivacyChipBuilder import com.android.systemui.privacy.PrivacyItem import com.android.systemui.privacy.PrivacyItemController import com.android.systemui.privacy.PrivacyType import com.android.systemui.res.R import com.android.systemui.statusbar.featurepods.av.domain.interactor.AvControlsChipInteractor import com.android.systemui.statusbar.policy.BatteryController Loading @@ -51,8 +56,11 @@ constructor( private val context: Context, @Application private val appScope: CoroutineScope, connectedDisplayInteractor: ConnectedDisplayInteractor, @SystemEventCoordinatorLog private val logBuffer: LogBuffer, ) { private val onDisplayConnectedFlow = connectedDisplayInteractor.connectedDisplayAddition private val defaultCameraPackageName = context.resources.getString(R.string.config_cameraGesturePackage) private var connectedDisplayCollectionJob: Job? = null private lateinit var scheduler: SystemStatusAnimationScheduler Loading Loading @@ -154,7 +162,7 @@ constructor( } else { val showAnimation = isChipAnimationEnabled() && !containsOnlyLocation(currentPrivacyItems) && !isExemptFromChipAnimation(currentPrivacyItems) && (!uniqueItemsMatch(currentPrivacyItems, previousPrivacyItems) || systemClock.elapsedRealtime() - timeLastEmpty >= DEBOUNCE_TIME) notifyPrivacyItemsChanged(showAnimation) Loading @@ -167,6 +175,40 @@ constructor( two.map { it.application.uid to it.privacyType.permGroupName }.toSet() } // Returns true if the privacy items are exempt from the chip animation. private fun isExemptFromChipAnimation(items: List<PrivacyItem>): Boolean { if (!Flags.statusBarPrivacyChipAnimationExemption()) { return containsOnlyLocation(items) } // Camera and microphone requests by the default camera app are exempt from the // chip animation. Filter those out. val nonExemptItems = items.filterNot { val shouldFilter = isCameraOrMicrophoneRequest(it) && it.application.packageName == defaultCameraPackageName if (shouldFilter) { logBuffer.log( TAG, LogLevel.DEBUG, { str1 = it.application.packageName str2 = it.privacyType.permGroupName }, { "Privacy item from default camera ($str1) is exempt from " + "chip animation. Permission group=$str2" }, ) } shouldFilter } // If the remaining items are only location, the chip animation is also exempt return containsOnlyLocation(nonExemptItems) } // Return true if the only privacy item is location private fun containsOnlyLocation(items: List<PrivacyItem>): Boolean { return items Loading @@ -176,6 +218,12 @@ constructor( .isEmpty() } private fun isCameraOrMicrophoneRequest(item: PrivacyItem): Boolean { return item.privacyType.let { it == PrivacyType.TYPE_CAMERA || it == PrivacyType.TYPE_MICROPHONE } } private fun isChipAnimationEnabled(): Boolean { val defaultValue = context.resources.getBoolean(R.bool.config_enablePrivacyChipAnimation) Loading @@ -186,6 +234,9 @@ constructor( ) } } @VisibleForTesting fun getPrivacyStateListener() = privacyStateListener } private const val DEBOUNCE_TIME = 3000L Loading