Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 126edfe8 authored by Fabian Kozynski's avatar Fabian Kozynski Committed by Android (Google) Code Review
Browse files

Merge "Hold privacy indicators for 5 sec"

parents 0fadc948 d78ae0f9
Loading
Loading
Loading
Loading
+60 −5
Original line number Diff line number Diff line
@@ -46,7 +46,7 @@ import javax.inject.Inject
class PrivacyItemController @Inject constructor(
    private val appOpsController: AppOpsController,
    @Main uiExecutor: DelayableExecutor,
    @Background private val bgExecutor: Executor,
    @Background private val bgExecutor: DelayableExecutor,
    private val deviceConfigProxy: DeviceConfigProxy,
    private val userTracker: UserTracker,
    private val logger: PrivacyLogger,
@@ -75,6 +75,7 @@ class PrivacyItemController @Inject constructor(
        private const val DEFAULT_ALL_INDICATORS = false
        private const val DEFAULT_MIC_CAMERA = true
        private const val DEFAULT_LOCATION = false
        const val TIME_TO_HOLD_INDICATORS = 5000L
    }

    @VisibleForTesting
@@ -101,6 +102,8 @@ class PrivacyItemController @Inject constructor(
    private var listening = false
    private val callbacks = mutableListOf<WeakReference<Callback>>()
    private val internalUiExecutor = MyExecutor(uiExecutor)
    private var holdingIndicators = false
    private var holdIndicatorsCancelled: Runnable? = null

    private val notifyChanges = Runnable {
        val list = privacyList
@@ -112,6 +115,11 @@ class PrivacyItemController @Inject constructor(
        uiExecutor.execute(notifyChanges)
    }

    private val stopHoldingAndNotifyChanges = Runnable {
        updatePrivacyList(true)
        uiExecutor.execute(notifyChanges)
    }

    var allIndicatorsAvailable = isAllIndicatorsEnabled()
        private set
    var micCameraAvailable = isMicCameraEnabled()
@@ -193,6 +201,14 @@ class PrivacyItemController @Inject constructor(
        userTracker.addCallback(userTrackerCallback, bgExecutor)
    }

    private fun setHoldTimer() {
        holdIndicatorsCancelled?.run()
        holdingIndicators = true
        holdIndicatorsCancelled = bgExecutor.executeDelayed({
            stopHoldingAndNotifyChanges.run()
        }, TIME_TO_HOLD_INDICATORS)
    }

    private fun update(updateUsers: Boolean) {
        bgExecutor.execute {
            if (updateUsers) {
@@ -257,9 +273,14 @@ class PrivacyItemController @Inject constructor(
        removeCallback(WeakReference(callback))
    }

    private fun updatePrivacyList() {
    private fun updatePrivacyList(stopHolding: Boolean = false) {
        if (!listening) {
            privacyList = emptyList()
            if (holdingIndicators) {
                holdIndicatorsCancelled?.run()
                logger.cancelIndicatorsHold()
                holdingIndicators = false
            }
            return
        }
        val list = appOpsController.getActiveAppOpsForUser(UserHandle.USER_ALL).filter {
@@ -267,9 +288,43 @@ class PrivacyItemController @Inject constructor(
                    it.code == AppOpsManager.OP_PHONE_CALL_MICROPHONE ||
                    it.code == AppOpsManager.OP_PHONE_CALL_CAMERA
        }.mapNotNull { toPrivacyItem(it) }.distinct()
        processNewList(list, stopHolding)
    }

    /**
     * The controller will only go from indicators to no indicators (and notify its listeners), if
     * [TIME_TO_HOLD_INDICATORS] has passed since it received an empty list from [AppOpsController].
     *
     * If holding the last list (in the [TIME_TO_HOLD_INDICATORS] period) and a new non-empty list
     * is retrieved from [AppOpsController], it will stop holding and notify about the new list.
     */
    private fun processNewList(list: List<PrivacyItem>, stopHolding: Boolean) {
        if (list.isNotEmpty()) {
            // The new elements is not empty, so regardless of whether we are holding or not, we
            // clear the holding flag and cancel the delayed runnable.
            if (holdingIndicators) {
                holdIndicatorsCancelled?.run()
                logger.cancelIndicatorsHold()
                holdingIndicators = false
            }
            logger.logUpdatedPrivacyItemsList(
                    list.joinToString(separator = ", ", transform = PrivacyItem::toLog))
            privacyList = list
        } else if (holdingIndicators && stopHolding) {
            // We are holding indicators, received an empty list and were told to stop holding.
            logger.finishIndicatorsHold()
            logger.logUpdatedPrivacyItemsList("")
            holdingIndicators = false
            privacyList = list
        } else if (holdingIndicators && !stopHolding) {
            // Empty list while we are holding. Ignore
        } else if (!holdingIndicators && privacyList.isNotEmpty()) {
            // We are not holding, we were showing some indicators but now we should show nothing.
            // Start holding.
            logger.startIndicatorsHold(TIME_TO_HOLD_INDICATORS)
            setHoldTimer()
        }
        // Else. We are not holding, we were not showing anything and the new list is empty. Ignore.
    }

    private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
+20 −0
Original line number Diff line number Diff line
@@ -47,6 +47,26 @@ class PrivacyLogger @Inject constructor(
        })
    }

    fun startIndicatorsHold(time: Long) {
        log(LogLevel.DEBUG, {
            int1 = time.toInt() / 1000
        }, {
            "Starting privacy indicators hold for $int1 seconds"
        })
    }

    fun cancelIndicatorsHold() {
        log(LogLevel.VERBOSE, {}, {
            "Cancel privacy indicators hold"
        })
    }

    fun finishIndicatorsHold() {
        log(LogLevel.DEBUG, {}, {
            "Finish privacy indicators hold"
        })
    }

    fun logCurrentProfilesChanged(profiles: List<Int>) {
        log(LogLevel.INFO, {
            str1 = profiles.toString()
+101 −1
Original line number Diff line number Diff line
@@ -97,6 +97,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {

    private lateinit var privacyItemController: PrivacyItemController
    private lateinit var executor: FakeExecutor
    private lateinit var fakeClock: FakeSystemClock
    private lateinit var deviceConfigProxy: DeviceConfigProxy

    fun PrivacyItemController(): PrivacyItemController {
@@ -113,7 +114,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
    @Before
    fun setup() {
        MockitoAnnotations.initMocks(this)
        executor = FakeExecutor(FakeSystemClock())
        fakeClock = FakeSystemClock()
        executor = FakeExecutor(fakeClock)
        deviceConfigProxy = DeviceConfigProxyFake()

        // Listen to everything by default
@@ -420,6 +422,104 @@ class PrivacyItemControllerTest : SysuiTestCase() {
        assertEquals(PrivacyType.TYPE_MICROPHONE, argCaptor.value[0].privacyType)
    }

    @Test
    fun testPassageOfTimeDoesNotRemoveIndicators() {
        doReturn(listOf(
                AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0)
        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())

        privacyItemController.addCallback(callback)

        fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS * 10)
        executor.runAllReady()

        verify(callback, never()).onPrivacyItemsChanged(emptyList())
        assertTrue(privacyItemController.privacyList.isNotEmpty())
    }

    @Test
    fun testHoldingAfterEmptyBeforeTimeExpires() {
        doReturn(listOf(
                AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0)
        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())

        privacyItemController.addCallback(callback)
        executor.runAllReady()

        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))

        `when`(appOpsController.getActiveAppOpsForUser(anyInt())).thenReturn(emptyList())
        argCaptorCallback.value.onActiveStateChanged(
                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false)
        executor.runAllReady()

        fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS / 5)
        executor.runAllReady()

        verify(callback, never()).onPrivacyItemsChanged(emptyList())
        assertTrue(privacyItemController.privacyList.isNotEmpty())
    }

    @Test
    fun testAfterHoldingIndicatorsAreEmpty() {
        doReturn(listOf(
                AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0)
        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())

        privacyItemController.addCallback(callback)
        executor.runAllReady()

        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))

        `when`(appOpsController.getActiveAppOpsForUser(anyInt())).thenReturn(emptyList())
        argCaptorCallback.value.onActiveStateChanged(
                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false)
        executor.runAllReady()

        executor.advanceClockToLast()
        executor.runAllReady()

        verify(callback).onPrivacyItemsChanged(emptyList())
        assertTrue(privacyItemController.privacyList.isEmpty())
    }

    @Test
    fun testHoldingStopsIfNewIndicatorsAppear() {
        doReturn(listOf(
                AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0)
        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())

        privacyItemController.addCallback(callback)
        executor.runAllReady()

        verify(appOpsController).addCallback(any(), capture(argCaptorCallback))

        `when`(appOpsController.getActiveAppOpsForUser(anyInt())).thenReturn(emptyList())
        argCaptorCallback.value.onActiveStateChanged(
                AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false)
        executor.runAllReady()

        fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS / 2)
        executor.runAllReady()

        doReturn(listOf(
                AppOpItem(AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, 0)
        )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
        argCaptorCallback.value.onActiveStateChanged(
                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true)
        executor.runAllReady()

        executor.advanceClockToLast()
        executor.runAllReady()

        verify(callback, never()).onPrivacyItemsChanged(emptyList())
        verify(callback, atLeastOnce()).onPrivacyItemsChanged(capture(argCaptor))

        val lastList = argCaptor.allValues.last()
        assertEquals(1, lastList.size)
        assertEquals(PrivacyType.TYPE_MICROPHONE, lastList.single().privacyType)
    }

    private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
    private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)