Merge "Hold privacy indicators for 5 sec"
This commit is contained in:
committed by
Android (Google) Code Review
commit
126edfe890
@@ -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? {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user