summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Fabian Kozynski <kozynski@google.com> 2021-01-14 14:08:24 -0500
committer Fabian Kozynski <kozynski@google.com> 2021-01-14 14:09:56 -0500
commitd78ae0f93c2771e996119c28890298c64a3132ad (patch)
tree9e6458712537f8f53bd7a7ebdd0362488c07d6b7
parent23c36e41d14c476cef430c90413b4d9a723eb914 (diff)
Hold privacy indicators for 5 sec
If privacy indicators would go from showing to not showing (no active uses), they will be held for 5 extra seconds before disappearing. This timeout is reset if new indicators become active. Test: atest SystemUITests Fixes: 177449285 Change-Id: Icda7d395ed01a28a482911662624a871ac7cd757
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt102
3 files changed, 181 insertions, 6 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index b2c2aa309811..f72900b7f6bf 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -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()
- logger.logUpdatedPrivacyItemsList(
- list.joinToString(separator = ", ", transform = PrivacyItem::toLog))
- privacyList = list
+ 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? {
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
index c88676e713b3..f3b8d2e5fbc0 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -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()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
index a8b305614a4a..7ca468edfd9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -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)