blob: d5636323182738830d589587e3fe6682eb64a513 [file] [log] [blame]
/*
* 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 <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
fun <T> any(): T = Mockito.any<T>()
}
@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<List<PrivacyItem>>
@Captor
private lateinit var argCaptorCallback: ArgumentCaptor<PrivacyItemMonitor.Callback>
@Captor
private lateinit var argCaptorConfigCallback: ArgumentCaptor<PrivacyConfig.Callback>
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<PrivacyItem>()).`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<PrivacyItem>()).`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<List<PrivacyItem>>()
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<PrivacyItem>()).`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<PrivacyItem>()).`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<PrivacyItem>()).`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())
}
}