/* * Copyright (C) 2020 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.privacy import android.app.ActivityManager import android.os.UserHandle import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager import com.android.systemui.privacy.logging.PrivacyLogger import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.DeviceConfigProxyFake import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.time.FakeSystemClock import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyList import org.mockito.Captor import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.`when` import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.doReturn import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest @RunWithLooper class PrivacyItemControllerTest : SysuiTestCase() { companion object { val CURRENT_USER_ID = ActivityManager.getCurrentUser() val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE const val TEST_PACKAGE_NAME = "test" fun capture(argumentCaptor: ArgumentCaptor): T = argumentCaptor.capture() fun any(): T = Mockito.any() } @Mock private lateinit var callback: PrivacyItemController.Callback @Mock private lateinit var privacyConfig: PrivacyConfig @Mock private lateinit var privacyItemMonitor: PrivacyItemMonitor @Mock private lateinit var privacyItemMonitor2: PrivacyItemMonitor @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var logger: PrivacyLogger @Captor private lateinit var argCaptor: ArgumentCaptor> @Captor private lateinit var argCaptorCallback: ArgumentCaptor @Captor private lateinit var argCaptorConfigCallback: ArgumentCaptor private lateinit var privacyItemController: PrivacyItemController private lateinit var executor: FakeExecutor private lateinit var fakeClock: FakeSystemClock private lateinit var deviceConfigProxy: DeviceConfigProxy fun createPrivacyItemController(): PrivacyItemController { return PrivacyItemController( executor, executor, privacyConfig, setOf(privacyItemMonitor, privacyItemMonitor2), logger, fakeClock, dumpManager) } @Before fun setup() { MockitoAnnotations.initMocks(this) fakeClock = FakeSystemClock() executor = FakeExecutor(fakeClock) deviceConfigProxy = DeviceConfigProxyFake() privacyItemController = createPrivacyItemController() } @Test fun testStartListeningByAddingCallback() { privacyItemController.addCallback(callback) executor.runAllReady() verify(privacyItemMonitor).startListening(any()) verify(privacyItemMonitor2).startListening(any()) verify(callback).onPrivacyItemsChanged(anyList()) } @Test fun testStopListeningByRemovingLastCallback() { privacyItemController.addCallback(callback) executor.runAllReady() verify(privacyItemMonitor, never()).stopListening() privacyItemController.removeCallback(callback) executor.runAllReady() verify(privacyItemMonitor).stopListening() verify(privacyItemMonitor2).stopListening() verify(callback).onPrivacyItemsChanged(emptyList()) } @Test fun testPrivacyItemsAggregated() { val item1 = PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0) val item2 = PrivacyItem(PrivacyType.TYPE_MICROPHONE, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 1) doReturn(listOf(item1)) .`when`(privacyItemMonitor).getActivePrivacyItems() doReturn(listOf(item2)) .`when`(privacyItemMonitor2).getActivePrivacyItems() privacyItemController.addCallback(callback) executor.runAllReady() verify(callback).onPrivacyItemsChanged(capture(argCaptor)) assertEquals(2, argCaptor.value.size) assertTrue(argCaptor.value.contains(item1)) assertTrue(argCaptor.value.contains(item2)) } @Test fun testDistinctItems() { doReturn(listOf( PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0), PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0))) .`when`(privacyItemMonitor).getActivePrivacyItems() doReturn(listOf( PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0))) .`when`(privacyItemMonitor2).getActivePrivacyItems() privacyItemController.addCallback(callback) executor.runAllReady() verify(callback).onPrivacyItemsChanged(capture(argCaptor)) assertEquals(1, argCaptor.value.size) } @Test fun testSimilarItemsDifferentTimeStamp() { doReturn(listOf( PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0), PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 1))) .`when`(privacyItemMonitor).getActivePrivacyItems() privacyItemController.addCallback(callback) executor.runAllReady() verify(callback).onPrivacyItemsChanged(capture(argCaptor)) assertEquals(2, argCaptor.value.size) } @Test fun testAddMultipleCallbacks() { val otherCallback = mock(PrivacyItemController.Callback::class.java) privacyItemController.addCallback(callback) executor.runAllReady() verify(callback).onPrivacyItemsChanged(anyList()) privacyItemController.addCallback(otherCallback) executor.runAllReady() verify(otherCallback).onPrivacyItemsChanged(anyList()) // Adding a callback should not unnecessarily call previous ones verifyNoMoreInteractions(callback) } @Test fun testMultipleCallbacksAreUpdated() { doReturn(emptyList()).`when`(privacyItemMonitor).getActivePrivacyItems() val otherCallback = mock(PrivacyItemController.Callback::class.java) privacyItemController.addCallback(callback) privacyItemController.addCallback(otherCallback) executor.runAllReady() reset(callback) reset(otherCallback) verify(privacyItemMonitor).startListening(capture(argCaptorCallback)) argCaptorCallback.value.onPrivacyItemsChanged() executor.runAllReady() verify(callback).onPrivacyItemsChanged(anyList()) verify(otherCallback).onPrivacyItemsChanged(anyList()) } @Test fun testRemoveCallback() { doReturn(emptyList()).`when`(privacyItemMonitor).getActivePrivacyItems() val otherCallback = mock(PrivacyItemController.Callback::class.java) privacyItemController.addCallback(callback) privacyItemController.addCallback(otherCallback) executor.runAllReady() executor.runAllReady() reset(callback) reset(otherCallback) verify(privacyItemMonitor).startListening(capture(argCaptorCallback)) privacyItemController.removeCallback(callback) argCaptorCallback.value.onPrivacyItemsChanged() executor.runAllReady() verify(callback, never()).onPrivacyItemsChanged(anyList()) verify(otherCallback).onPrivacyItemsChanged(anyList()) } @Test fun testListShouldBeCopy() { val list = listOf(PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0)) privacyItemController.privacyList = list val privacyList = privacyItemController.privacyList assertEquals(list, privacyList) assertTrue(list !== privacyList) } @Test fun testLogListUpdated() { val privacyItem = PrivacyItem( PrivacyType.TYPE_LOCATION, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0 ) doReturn(listOf(privacyItem)).`when`(privacyItemMonitor).getActivePrivacyItems() privacyItemController.addCallback(callback) executor.runAllReady() verify(privacyItemMonitor).startListening(capture(argCaptorCallback)) argCaptorCallback.value.onPrivacyItemsChanged() executor.runAllReady() val captor = argumentCaptor>() verify(logger, atLeastOnce()).logRetrievedPrivacyItemsList(capture(captor)) // Let's look at the last log val values = captor.allValues assertTrue(values[values.size - 1].contains(privacyItem)) } @Test fun testPassageOfTimeDoesNotRemoveIndicators() { doReturn(listOf( PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0) )).`when`(privacyItemMonitor).getActivePrivacyItems() privacyItemController.addCallback(callback) fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS * 10) executor.runAllReady() verify(callback, never()).onPrivacyItemsChanged(emptyList()) assertTrue(privacyItemController.privacyList.isNotEmpty()) } @Test fun testNotHeldAfterTimeIsOff() { // Start with some element at time 0 doReturn(listOf( PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0) )).`when`(privacyItemMonitor).getActivePrivacyItems() privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD + 1 doReturn(emptyList()).`when`(privacyItemMonitor).getActivePrivacyItems() fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS + 1) verify(privacyItemMonitor).startListening(capture(argCaptorCallback)) argCaptorCallback.value.onPrivacyItemsChanged() executor.runAllReady() // See it's not there verify(callback).onPrivacyItemsChanged(emptyList()) assertTrue(privacyItemController.privacyList.isEmpty()) } @Test fun testElementNotRemovedBeforeHoldTime() { // Start with some element at current time doReturn(listOf( PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), fakeClock.elapsedRealtime()) )).`when`(privacyItemMonitor).getActivePrivacyItems() privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD - 1 doReturn(emptyList()).`when`(privacyItemMonitor).getActivePrivacyItems() fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1) verify(privacyItemMonitor).startListening(capture(argCaptorCallback)) argCaptorCallback.value.onPrivacyItemsChanged() executor.runAllReady() // See it's still there verify(callback, never()).onPrivacyItemsChanged(emptyList()) assertTrue(privacyItemController.privacyList.isNotEmpty()) } @Test fun testElementAutoRemovedAfterHoldTime() { // Start with some element at time 0 doReturn(listOf( PrivacyItem(PrivacyType.TYPE_CAMERA, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0) )).`when`(privacyItemMonitor).getActivePrivacyItems() privacyItemController.addCallback(callback) executor.runAllReady() // Then remove it at time HOLD - 1 doReturn(emptyList()).`when`(privacyItemMonitor).getActivePrivacyItems() fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1) verify(privacyItemMonitor).startListening(capture(argCaptorCallback)) argCaptorCallback.value.onPrivacyItemsChanged() executor.runAllReady() fakeClock.advanceTime(2L) executor.runAllReady() // See it was auto-removed verify(callback).onPrivacyItemsChanged(emptyList()) assertTrue(privacyItemController.privacyList.isEmpty()) } @Test fun testFlagsAll_listeningToAll() { verify(privacyConfig).addCallback(capture(argCaptorConfigCallback)) privacyItemController.addCallback(callback) `when`(privacyConfig.micCameraAvailable).thenReturn(true) `when`(privacyConfig.locationAvailable).thenReturn(true) `when`(privacyConfig.mediaProjectionAvailable).thenReturn(true) argCaptorConfigCallback.value.onFlagMicCameraChanged(true) argCaptorConfigCallback.value.onFlagLocationChanged(true) argCaptorConfigCallback.value.onFlagMediaProjectionChanged(true) executor.runAllReady() assertTrue(privacyItemController.allIndicatorsAvailable) } @Test fun testFlags_onFlagMicCameraChanged() { verify(privacyConfig).addCallback(capture(argCaptorConfigCallback)) privacyItemController.addCallback(callback) `when`(privacyConfig.micCameraAvailable).thenReturn(true) argCaptorConfigCallback.value.onFlagMicCameraChanged(true) executor.runAllReady() assertTrue(privacyItemController.micCameraAvailable) verify(callback).onFlagMicCameraChanged(true) } @Test fun testFlags_onFlagLocationChanged() { verify(privacyConfig).addCallback(capture(argCaptorConfigCallback)) privacyItemController.addCallback(callback) `when`(privacyConfig.locationAvailable).thenReturn(true) argCaptorConfigCallback.value.onFlagLocationChanged(true) executor.runAllReady() assertTrue(privacyItemController.locationAvailable) verify(callback).onFlagLocationChanged(true) } @Test fun testFlags_onFlagMediaProjectionChanged() { verify(privacyConfig).addCallback(capture(argCaptorConfigCallback)) privacyItemController.addCallback(callback) `when`(privacyConfig.mediaProjectionAvailable).thenReturn(true) argCaptorConfigCallback.value.onFlagMediaProjectionChanged(true) executor.runAllReady() verify(callback).onFlagMediaProjectionChanged(true) } @Test fun testPausedElementsAreRemoved() { doReturn(listOf( PrivacyItem(PrivacyType.TYPE_MICROPHONE, PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID), 0, true))) .`when`(privacyItemMonitor).getActivePrivacyItems() privacyItemController.addCallback(callback) executor.runAllReady() assertTrue(privacyItemController.privacyList.isEmpty()) } }