Merge "Change privacy indicator holding policy" into sc-dev

This commit is contained in:
Fabian Kozynski
2021-03-30 18:19:01 +00:00
committed by Android (Google) Code Review
8 changed files with 149 additions and 148 deletions

View File

@@ -24,15 +24,15 @@ public class AppOpItem {
private int mCode; private int mCode;
private int mUid; private int mUid;
private String mPackageName; private String mPackageName;
private long mTimeStarted; private long mTimeStartedElapsed;
private StringBuilder mState; private StringBuilder mState;
private boolean mIsDisabled; private boolean mIsDisabled;
public AppOpItem(int code, int uid, String packageName, long timeStarted) { public AppOpItem(int code, int uid, String packageName, long timeStartedElapsed) {
this.mCode = code; this.mCode = code;
this.mUid = uid; this.mUid = uid;
this.mPackageName = packageName; this.mPackageName = packageName;
this.mTimeStarted = timeStarted; this.mTimeStartedElapsed = timeStartedElapsed;
mState = new StringBuilder() mState = new StringBuilder()
.append("AppOpItem(") .append("AppOpItem(")
.append("Op code=").append(code).append(", ") .append("Op code=").append(code).append(", ")
@@ -53,8 +53,8 @@ public class AppOpItem {
return mPackageName; return mPackageName;
} }
public long getTimeStarted() { public long getTimeStartedElapsed() {
return mTimeStarted; return mTimeStartedElapsed;
} }
public void setDisabled(boolean misDisabled) { public void setDisabled(boolean misDisabled) {

View File

@@ -50,6 +50,7 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.util.Assert; import com.android.systemui.util.Assert;
import com.android.systemui.util.time.SystemClock;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.PrintWriter; import java.io.PrintWriter;
@@ -83,8 +84,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
private final AppOpsManager mAppOps; private final AppOpsManager mAppOps;
private final AudioManager mAudioManager; private final AudioManager mAudioManager;
private final LocationManager mLocationManager; private final LocationManager mLocationManager;
private final PackageManager mPackageManager;
private final IndividualSensorPrivacyController mSensorPrivacyController; private final IndividualSensorPrivacyController mSensorPrivacyController;
private final SystemClock mClock;
// mLocationProviderPackages are cached and updated only occasionally // mLocationProviderPackages are cached and updated only occasionally
private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000; private static final long LOCATION_PROVIDER_UPDATE_FREQUENCY_MS = 30000;
@@ -126,7 +127,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
PermissionFlagsCache cache, PermissionFlagsCache cache,
AudioManager audioManager, AudioManager audioManager,
IndividualSensorPrivacyController sensorPrivacyController, IndividualSensorPrivacyController sensorPrivacyController,
BroadcastDispatcher dispatcher BroadcastDispatcher dispatcher,
SystemClock clock
) { ) {
mDispatcher = dispatcher; mDispatcher = dispatcher;
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
@@ -142,8 +144,8 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
|| mSensorPrivacyController.isSensorBlocked(MICROPHONE); || mSensorPrivacyController.isSensorBlocked(MICROPHONE);
mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA); mCameraDisabled = mSensorPrivacyController.isSensorBlocked(CAMERA);
mLocationManager = context.getSystemService(LocationManager.class); mLocationManager = context.getSystemService(LocationManager.class);
mPackageManager = context.getPackageManager();
mContext = context; mContext = context;
mClock = clock;
dumpManager.registerDumpable(TAG, this); dumpManager.registerDumpable(TAG, this);
} }
@@ -252,7 +254,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
synchronized (mActiveItems) { synchronized (mActiveItems) {
AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName); AppOpItem item = getAppOpItemLocked(mActiveItems, code, uid, packageName);
if (item == null && active) { if (item == null && active) {
item = new AppOpItem(code, uid, packageName, System.currentTimeMillis()); item = new AppOpItem(code, uid, packageName, mClock.elapsedRealtime());
if (code == AppOpsManager.OP_RECORD_AUDIO) { if (code == AppOpsManager.OP_RECORD_AUDIO) {
item.setDisabled(isAnyRecordingPausedLocked(uid)); item.setDisabled(isAnyRecordingPausedLocked(uid));
} else if (code == AppOpsManager.OP_CAMERA) { } else if (code == AppOpsManager.OP_CAMERA) {
@@ -294,7 +296,7 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
synchronized (mNotedItems) { synchronized (mNotedItems) {
item = getAppOpItemLocked(mNotedItems, code, uid, packageName); item = getAppOpItemLocked(mNotedItems, code, uid, packageName);
if (item == null) { if (item == null) {
item = new AppOpItem(code, uid, packageName, System.currentTimeMillis()); item = new AppOpItem(code, uid, packageName, mClock.elapsedRealtime());
mNotedItems.add(item); mNotedItems.add(item);
if (DEBUG) Log.w(TAG, "Added item: " + item.toString()); if (DEBUG) Log.w(TAG, "Added item: " + item.toString());
createdNew = true; createdNew = true;

View File

@@ -50,8 +50,14 @@ enum class PrivacyType(
fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme) fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme)
} }
data class PrivacyItem(val privacyType: PrivacyType, val application: PrivacyApplication) { private const val UNKNOWN_TIMESTAMP = -1L
fun toLog(): String = "(${privacyType.logName}, ${application.packageName}(${application.uid}))" data class PrivacyItem(
val privacyType: PrivacyType,
val application: PrivacyApplication,
val timeStampElapsed: Long = UNKNOWN_TIMESTAMP
) {
val log = "(${privacyType.logName}, ${application.packageName}(${application.uid}), " +
"$timeStampElapsed)"
} }
data class PrivacyApplication(val packageName: String, val uid: Int) data class PrivacyApplication(val packageName: String, val uid: Int)

View File

@@ -36,6 +36,7 @@ import com.android.systemui.privacy.logging.PrivacyLogger
import com.android.systemui.settings.UserTracker import com.android.systemui.settings.UserTracker
import com.android.systemui.util.DeviceConfigProxy import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.SystemClock
import java.io.FileDescriptor import java.io.FileDescriptor
import java.io.PrintWriter import java.io.PrintWriter
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
@@ -50,6 +51,7 @@ class PrivacyItemController @Inject constructor(
private val deviceConfigProxy: DeviceConfigProxy, private val deviceConfigProxy: DeviceConfigProxy,
private val userTracker: UserTracker, private val userTracker: UserTracker,
private val logger: PrivacyLogger, private val logger: PrivacyLogger,
private val systemClock: SystemClock,
dumpManager: DumpManager dumpManager: DumpManager
) : Dumpable { ) : Dumpable {
@@ -70,10 +72,9 @@ class PrivacyItemController @Inject constructor(
const val TAG = "PrivacyItemController" const val TAG = "PrivacyItemController"
private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
private const val LOCATION = SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED private const val LOCATION = SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED
private const val DEFAULT_ALL_INDICATORS = false
private const val DEFAULT_MIC_CAMERA = true private const val DEFAULT_MIC_CAMERA = true
private const val DEFAULT_LOCATION = false private const val DEFAULT_LOCATION = false
const val TIME_TO_HOLD_INDICATORS = 5000L @VisibleForTesting const val TIME_TO_HOLD_INDICATORS = 5000L
} }
@VisibleForTesting @VisibleForTesting
@@ -95,8 +96,8 @@ class PrivacyItemController @Inject constructor(
private var listening = false private var listening = false
private val callbacks = mutableListOf<WeakReference<Callback>>() private val callbacks = mutableListOf<WeakReference<Callback>>()
private val internalUiExecutor = MyExecutor(uiExecutor) private val internalUiExecutor = MyExecutor(uiExecutor)
private var holdingIndicators = false
private var holdIndicatorsCancelled: Runnable? = null private var holdingRunnableCanceler: Runnable? = null
private val notifyChanges = Runnable { private val notifyChanges = Runnable {
val list = privacyList val list = privacyList
@@ -108,11 +109,6 @@ class PrivacyItemController @Inject constructor(
uiExecutor.execute(notifyChanges) uiExecutor.execute(notifyChanges)
} }
private val stopHoldingAndNotifyChanges = Runnable {
updatePrivacyList(true)
uiExecutor.execute(notifyChanges)
}
var micCameraAvailable = isMicCameraEnabled() var micCameraAvailable = isMicCameraEnabled()
private set private set
var locationAvailable = isLocationEnabled() var locationAvailable = isLocationEnabled()
@@ -189,14 +185,6 @@ class PrivacyItemController @Inject constructor(
userTracker.addCallback(userTrackerCallback, bgExecutor) 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) { private fun update(updateUsers: Boolean) {
bgExecutor.execute { bgExecutor.execute {
if (updateUsers) { if (updateUsers) {
@@ -261,14 +249,12 @@ class PrivacyItemController @Inject constructor(
removeCallback(WeakReference(callback)) removeCallback(WeakReference(callback))
} }
private fun updatePrivacyList(stopHolding: Boolean = false) { private fun updatePrivacyList() {
holdingRunnableCanceler?.run()?.also {
holdingRunnableCanceler = null
}
if (!listening) { if (!listening) {
privacyList = emptyList() privacyList = emptyList()
if (holdingIndicators) {
holdIndicatorsCancelled?.run()
logger.cancelIndicatorsHold()
holdingIndicators = false
}
return return
} }
val list = appOpsController.getActiveAppOpsForUser(UserHandle.USER_ALL).filter { val list = appOpsController.getActiveAppOpsForUser(UserHandle.USER_ALL).filter {
@@ -276,43 +262,36 @@ class PrivacyItemController @Inject constructor(
it.code == AppOpsManager.OP_PHONE_CALL_MICROPHONE || it.code == AppOpsManager.OP_PHONE_CALL_MICROPHONE ||
it.code == AppOpsManager.OP_PHONE_CALL_CAMERA it.code == AppOpsManager.OP_PHONE_CALL_CAMERA
}.mapNotNull { toPrivacyItem(it) }.distinct() }.mapNotNull { toPrivacyItem(it) }.distinct()
processNewList(list, stopHolding) privacyList = processNewList(list)
} }
/** /**
* The controller will only go from indicators to no indicators (and notify its listeners), if * Figure out which items have not been around for long enough and put them back in the list.
* [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 * Also schedule when we should check again to remove expired items. Because we always retrieve
* is retrieved from [AppOpsController], it will stop holding and notify about the new list. * the current list, we have the latest info.
*
* @param list map of list retrieved from [AppOpsController].
* @return a list that may have added items that should be kept for some time.
*/ */
private fun processNewList(list: List<PrivacyItem>, stopHolding: Boolean) { private fun processNewList(list: List<PrivacyItem>): List<PrivacyItem> {
if (list.isNotEmpty()) { logger.logRetrievedPrivacyItemsList(list)
// 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. // Anything earlier than this timestamp can be removed
if (holdingIndicators) { val removeBeforeTime = systemClock.elapsedRealtime() - TIME_TO_HOLD_INDICATORS
holdIndicatorsCancelled?.run() val mustKeep = privacyList.filter { it.timeStampElapsed > removeBeforeTime && it !in list }
logger.cancelIndicatorsHold()
holdingIndicators = false // There are items we must keep because they haven't been around for enough time.
} if (mustKeep.isNotEmpty()) {
logger.logUpdatedPrivacyItemsList( logger.logPrivacyItemsToHold(mustKeep)
list.joinToString(separator = ", ", transform = PrivacyItem::toLog)) val earliestTime = mustKeep.minByOrNull { it.timeStampElapsed }!!.timeStampElapsed
privacyList = list
} else if (holdingIndicators && stopHolding) { // Update the list again when the earliest item should be removed.
// We are holding indicators, received an empty list and were told to stop holding. val delay = earliestTime - removeBeforeTime
logger.finishIndicatorsHold() logger.logPrivacyItemsUpdateScheduled(delay)
logger.logUpdatedPrivacyItemsList("") holdingRunnableCanceler = bgExecutor.executeDelayed(updateListAndNotifyChanges, delay)
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. return list + mustKeep
} }
private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? { private fun toPrivacyItem(appOpItem: AppOpItem): PrivacyItem? {
@@ -329,7 +308,7 @@ class PrivacyItemController @Inject constructor(
return null return null
} }
val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid) val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid)
return PrivacyItem(type, app) return PrivacyItem(type, app, appOpItem.timeStartedElapsed)
} }
interface Callback { interface Callback {

View File

@@ -22,10 +22,13 @@ import com.android.systemui.log.LogLevel
import com.android.systemui.log.LogMessage import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.PrivacyLog import com.android.systemui.log.dagger.PrivacyLog
import com.android.systemui.privacy.PrivacyDialog import com.android.systemui.privacy.PrivacyDialog
import com.android.systemui.privacy.PrivacyItem
import java.text.SimpleDateFormat
import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
private const val TAG = "PrivacyLog" private const val TAG = "PrivacyLog"
private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
class PrivacyLogger @Inject constructor( class PrivacyLogger @Inject constructor(
@PrivacyLog private val buffer: LogBuffer @PrivacyLog private val buffer: LogBuffer
) { ) {
@@ -41,31 +44,29 @@ class PrivacyLogger @Inject constructor(
}) })
} }
fun logUpdatedPrivacyItemsList(listAsString: String) { fun logRetrievedPrivacyItemsList(list: List<PrivacyItem>) {
log(LogLevel.INFO, { log(LogLevel.INFO, {
str1 = listAsString str1 = listToString(list)
}, { }, {
"Updated list: $str1" "Retrieved list to process: $str1"
}) })
} }
fun startIndicatorsHold(time: Long) { fun logPrivacyItemsToHold(list: List<PrivacyItem>) {
log(LogLevel.DEBUG, { log(LogLevel.DEBUG, {
int1 = time.toInt() / 1000 str1 = listToString(list)
}, { }, {
"Starting privacy indicators hold for $int1 seconds" "Holding items: $str1"
}) })
} }
fun cancelIndicatorsHold() { fun logPrivacyItemsUpdateScheduled(delay: Long) {
log(LogLevel.VERBOSE, {}, { log(LogLevel.INFO, {
"Cancel privacy indicators hold" val scheduledFor = System.currentTimeMillis() + delay
}) val formattedTimestamp = DATE_FORMAT.format(scheduledFor)
} str1 = formattedTimestamp
}, {
fun finishIndicatorsHold() { "Updating items scheduled for $str1"
log(LogLevel.DEBUG, {}, {
"Finish privacy indicators hold"
}) })
} }
@@ -130,6 +131,10 @@ class PrivacyLogger @Inject constructor(
}) })
} }
private fun listToString(list: List<PrivacyItem>): String {
return list.joinToString(separator = ", ", transform = PrivacyItem::log)
}
private inline fun log( private inline fun log(
logLevel: LogLevel, logLevel: LogLevel,
initializer: LogMessage.() -> Unit, initializer: LogMessage.() -> Unit,

View File

@@ -53,6 +53,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -137,7 +138,8 @@ public class AppOpsControllerTest extends SysuiTestCase {
mFlagsCache, mFlagsCache,
mAudioManager, mAudioManager,
mSensorPrivacyController, mSensorPrivacyController,
mDispatcher mDispatcher,
new FakeSystemClock()
); );
} }

View File

@@ -69,7 +69,7 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() {
private lateinit var executor: FakeExecutor private lateinit var executor: FakeExecutor
private lateinit var deviceConfigProxy: DeviceConfigProxy private lateinit var deviceConfigProxy: DeviceConfigProxy
fun PrivacyItemController(): PrivacyItemController { fun createPrivacyItemController(): PrivacyItemController {
return PrivacyItemController( return PrivacyItemController(
appOpsController, appOpsController,
executor, executor,
@@ -77,6 +77,7 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() {
deviceConfigProxy, deviceConfigProxy,
userTracker, userTracker,
logger, logger,
FakeSystemClock(),
dumpManager) dumpManager)
} }
@@ -86,7 +87,7 @@ class PrivacyItemControllerFlagsTest : SysuiTestCase() {
executor = FakeExecutor(FakeSystemClock()) executor = FakeExecutor(FakeSystemClock())
deviceConfigProxy = DeviceConfigProxyFake() deviceConfigProxy = DeviceConfigProxyFake()
privacyItemController = PrivacyItemController() privacyItemController = createPrivacyItemController()
privacyItemController.addCallback(callback) privacyItemController.addCallback(callback)
executor.runAllReady() executor.runAllReady()

View File

@@ -99,7 +99,10 @@ class PrivacyItemControllerTest : SysuiTestCase() {
private lateinit var fakeClock: FakeSystemClock private lateinit var fakeClock: FakeSystemClock
private lateinit var deviceConfigProxy: DeviceConfigProxy private lateinit var deviceConfigProxy: DeviceConfigProxy
fun PrivacyItemController(): PrivacyItemController { private val elapsedTime: Long
get() = fakeClock.elapsedRealtime()
fun createPrivacyItemController(): PrivacyItemController {
return PrivacyItemController( return PrivacyItemController(
appOpsController, appOpsController,
executor, executor,
@@ -107,6 +110,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
deviceConfigProxy, deviceConfigProxy,
userTracker, userTracker,
logger, logger,
fakeClock,
dumpManager) dumpManager)
} }
@@ -123,7 +127,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
`when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(CURRENT_USER_ID, "", 0))) `when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(CURRENT_USER_ID, "", 0)))
privacyItemController = PrivacyItemController() privacyItemController = createPrivacyItemController()
} }
@Test @Test
@@ -151,7 +155,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
@Test @Test
fun testDistinctItems() { fun testDistinctItems() {
doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0), doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1))) AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0)))
.`when`(appOpsController).getActiveAppOpsForUser(anyInt()) .`when`(appOpsController).getActiveAppOpsForUser(anyInt())
privacyItemController.addCallback(callback) privacyItemController.addCallback(callback)
@@ -160,6 +164,18 @@ class PrivacyItemControllerTest : SysuiTestCase() {
assertEquals(1, argCaptor.value.size) assertEquals(1, argCaptor.value.size)
} }
@Test
fun testSimilarItemsDifferentTimeStamp() {
doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 1)))
.`when`(appOpsController).getActiveAppOpsForUser(anyInt())
privacyItemController.addCallback(callback)
executor.runAllReady()
verify(callback).onPrivacyItemsChanged(capture(argCaptor))
assertEquals(2, argCaptor.value.size)
}
@Test @Test
fun testRegisterCallback() { fun testRegisterCallback() {
privacyItemController.addCallback(callback) privacyItemController.addCallback(callback)
@@ -251,7 +267,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
@Test @Test
fun testListShouldBeCopy() { fun testListShouldBeCopy() {
val list = listOf(PrivacyItem(PrivacyType.TYPE_CAMERA, val list = listOf(PrivacyItem(PrivacyType.TYPE_CAMERA,
PrivacyApplication("", TEST_UID))) PrivacyApplication("", TEST_UID), 0))
privacyItemController.privacyList = list privacyItemController.privacyList = list
val privacyList = privacyItemController.privacyList val privacyList = privacyItemController.privacyList
assertEquals(list, privacyList) assertEquals(list, privacyList)
@@ -334,14 +350,15 @@ class PrivacyItemControllerTest : SysuiTestCase() {
val expected = PrivacyItem( val expected = PrivacyItem(
PrivacyType.TYPE_LOCATION, PrivacyType.TYPE_LOCATION,
PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID) PrivacyApplication(TEST_PACKAGE_NAME, TEST_UID),
0
) )
val captor = argumentCaptor<String>() val captor = argumentCaptor<List<PrivacyItem>>()
verify(logger, atLeastOnce()).logUpdatedPrivacyItemsList(capture(captor)) verify(logger, atLeastOnce()).logRetrievedPrivacyItemsList(capture(captor))
// Let's look at the last log // Let's look at the last log
val values = captor.allValues val values = captor.allValues
assertTrue(values[values.size - 1].contains(expected.toLog())) assertTrue(values[values.size - 1].contains(expected))
} }
@Test @Test
@@ -424,7 +441,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
@Test @Test
fun testPassageOfTimeDoesNotRemoveIndicators() { fun testPassageOfTimeDoesNotRemoveIndicators() {
doReturn(listOf( doReturn(listOf(
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0) AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
)).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
privacyItemController.addCallback(callback) privacyItemController.addCallback(callback)
@@ -437,86 +454,75 @@ class PrivacyItemControllerTest : SysuiTestCase() {
} }
@Test @Test
fun testHoldingAfterEmptyBeforeTimeExpires() { fun testNotHeldAfterTimeIsOff() {
// Start with some element at time 0
doReturn(listOf( doReturn(listOf(
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0) AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
)).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
privacyItemController.addCallback(callback) privacyItemController.addCallback(callback)
executor.runAllReady() executor.runAllReady()
verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) // Then remove it at time HOLD + 1
doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS + 1)
`when`(appOpsController.getActiveAppOpsForUser(anyInt())).thenReturn(emptyList()) verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
argCaptorCallback.value.onActiveStateChanged( argCaptorCallback.value.onActiveStateChanged(
AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false) AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false)
executor.runAllReady() executor.runAllReady()
fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS / 5) // See it's not there
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()) verify(callback).onPrivacyItemsChanged(emptyList())
assertTrue(privacyItemController.privacyList.isEmpty()) assertTrue(privacyItemController.privacyList.isEmpty())
} }
@Test @Test
fun testHoldingStopsIfNewIndicatorsAppear() { fun testElementNotRemovedBeforeHoldTime() {
// Start with some element at time 0
doReturn(listOf( doReturn(listOf(
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0) AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
)).`when`(appOpsController).getActiveAppOpsForUser(anyInt()) )).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
privacyItemController.addCallback(callback) privacyItemController.addCallback(callback)
executor.runAllReady() executor.runAllReady()
verify(appOpsController).addCallback(any(), capture(argCaptorCallback)) // Then remove it at time HOLD - 1
doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1)
`when`(appOpsController.getActiveAppOpsForUser(anyInt())).thenReturn(emptyList()) verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
argCaptorCallback.value.onActiveStateChanged( argCaptorCallback.value.onActiveStateChanged(
AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false) AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false)
executor.runAllReady() executor.runAllReady()
fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS / 2) // See it's still there
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, never()).onPrivacyItemsChanged(emptyList())
verify(callback, atLeastOnce()).onPrivacyItemsChanged(capture(argCaptor)) assertTrue(privacyItemController.privacyList.isNotEmpty())
}
val lastList = argCaptor.allValues.last() @Test
assertEquals(1, lastList.size) fun testElementAutoRemovedAfterHoldTime() {
assertEquals(PrivacyType.TYPE_MICROPHONE, lastList.single().privacyType) // Start with some element at time 0
doReturn(listOf(
AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, elapsedTime)
)).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
privacyItemController.addCallback(callback)
executor.runAllReady()
// Then remove it at time HOLD - 1
doReturn(emptyList<AppOpItem>()).`when`(appOpsController).getActiveAppOpsForUser(anyInt())
fakeClock.advanceTime(PrivacyItemController.TIME_TO_HOLD_INDICATORS - 1)
verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
argCaptorCallback.value.onActiveStateChanged(
AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, false)
executor.runAllReady()
fakeClock.advanceTime(2L)
executor.runAllReady()
// See it was auto-removed
verify(callback).onPrivacyItemsChanged(emptyList())
assertTrue(privacyItemController.privacyList.isEmpty())
} }
private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value) private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)