Merge changes from topic "privacy-type-media-projection" into tm-dev am: d59d17d74a
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18375274
Change-Id: I03c6e7abd4245653accc4a2fa1a472861dd4f0f0
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index d72073d..777104d8 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -158,6 +158,12 @@
public static final String PROPERTY_LOCATION_INDICATORS_ENABLED = "location_indicators_enabled";
/**
+ * Whether to show privacy chip for media projection.
+ */
+ public static final String PROPERTY_MEDIA_PROJECTION_INDICATORS_ENABLED =
+ "media_projection_indicators_enabled";
+
+ /**
* Whether to show old location indicator on all location accesses.
*/
public static final String PROPERTY_LOCATION_INDICATORS_SMALL_ENABLED =
diff --git a/packages/SystemUI/res/drawable/privacy_item_circle_media_projection.xml b/packages/SystemUI/res/drawable/privacy_item_circle_media_projection.xml
new file mode 100644
index 0000000..ac563de
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_item_circle_media_projection.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@id/background"
+ android:gravity="center"
+ >
+ <shape android:shape="oval">
+ <size
+ android:height="@dimen/ongoing_appops_dialog_circle_size"
+ android:width="@dimen/ongoing_appops_dialog_circle_size"
+ />
+ <solid android:color="@color/privacy_chip_background" />
+ </shape>
+ </item>
+ <item android:id="@id/icon"
+ android:gravity="center"
+ android:width="@dimen/ongoing_appops_dialog_icon_size"
+ android:height="@dimen/ongoing_appops_dialog_icon_size"
+ android:drawable="@drawable/stat_sys_cast"
+ />
+</layer-list>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c1e485b..1dd41a3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2046,6 +2046,9 @@
<!-- Text for microphone app op [CHAR LIMIT=20]-->
<string name="privacy_type_microphone">microphone</string>
+ <!-- Text for media projection privacy type [CHAR LIMIT=20]-->
+ <string name="privacy_type_media_projection">screen recording</string>
+
<!-- What to show on the ambient display player when song doesn't have a title. [CHAR LIMIT=20] -->
<string name="music_controls_no_title">No title</string>
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 5b6ddd8..aeda20f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -54,6 +54,7 @@
import android.media.AudioManager;
import android.media.IAudioService;
import android.media.MediaRouter2Manager;
+import android.media.projection.MediaProjectionManager;
import android.media.session.MediaSessionManager;
import android.net.ConnectivityManager;
import android.net.NetworkScoreManager;
@@ -321,6 +322,11 @@
}
@Provides
+ static MediaProjectionManager provideMediaProjectionManager(Context context) {
+ return context.getSystemService(MediaProjectionManager.class);
+ }
+
+ @Provides
static MediaRouter2Manager provideMediaRouter2Manager(Context context) {
return MediaRouter2Manager.getInstance(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/MediaProjectionPrivacyItemMonitor.kt b/packages/SystemUI/src/com/android/systemui/privacy/MediaProjectionPrivacyItemMonitor.kt
new file mode 100644
index 0000000..9b5a675
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/privacy/MediaProjectionPrivacyItemMonitor.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2022 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.content.pm.PackageManager
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.util.Log
+import androidx.annotation.WorkerThread
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.privacy.logging.PrivacyLogger
+import com.android.systemui.util.asIndenting
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.util.withIncreasedIndent
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Monitors the active media projection to update privacy items.
+ */
+@SysUISingleton
+class MediaProjectionPrivacyItemMonitor @Inject constructor(
+ private val mediaProjectionManager: MediaProjectionManager,
+ private val packageManager: PackageManager,
+ private val privacyConfig: PrivacyConfig,
+ @Background private val bgHandler: Handler,
+ private val systemClock: SystemClock,
+ private val logger: PrivacyLogger
+) : PrivacyItemMonitor {
+
+ companion object {
+ const val TAG = "MediaProjectionPrivacyItemMonitor"
+ const val DEBUG = false
+ }
+
+ private val lock = Any()
+
+ @GuardedBy("lock")
+ private var callback: PrivacyItemMonitor.Callback? = null
+
+ @GuardedBy("lock")
+ private var mediaProjectionAvailable = privacyConfig.mediaProjectionAvailable
+ @GuardedBy("lock")
+ private var listening = false
+
+ @GuardedBy("lock")
+ private val privacyItems = mutableListOf<PrivacyItem>()
+
+ private val optionsCallback = object : PrivacyConfig.Callback {
+ override fun onFlagMediaProjectionChanged(flag: Boolean) {
+ synchronized(lock) {
+ mediaProjectionAvailable = privacyConfig.mediaProjectionAvailable
+ setListeningStateLocked()
+ }
+ dispatchOnPrivacyItemsChanged()
+ }
+ }
+
+ private val mediaProjectionCallback = object : MediaProjectionManager.Callback() {
+ @WorkerThread
+ override fun onStart(info: MediaProjectionInfo) {
+ synchronized(lock) { onMediaProjectionStartedLocked(info) }
+ dispatchOnPrivacyItemsChanged()
+ }
+
+ @WorkerThread
+ override fun onStop(info: MediaProjectionInfo) {
+ synchronized(lock) { onMediaProjectionStoppedLocked(info) }
+ dispatchOnPrivacyItemsChanged()
+ }
+ }
+
+ init {
+ privacyConfig.addCallback(optionsCallback)
+ setListeningStateLocked()
+ }
+
+ override fun startListening(callback: PrivacyItemMonitor.Callback) {
+ synchronized(lock) {
+ this.callback = callback
+ }
+ }
+
+ override fun stopListening() {
+ synchronized(lock) {
+ this.callback = null
+ }
+ }
+
+ @GuardedBy("lock")
+ @WorkerThread
+ private fun onMediaProjectionStartedLocked(info: MediaProjectionInfo) {
+ if (DEBUG) Log.d(TAG, "onMediaProjectionStartedLocked: info=$info")
+ val item = makePrivacyItem(info)
+ privacyItems.add(item)
+ logItemActive(item, true)
+ }
+
+ @GuardedBy("lock")
+ @WorkerThread
+ private fun onMediaProjectionStoppedLocked(info: MediaProjectionInfo) {
+ if (DEBUG) Log.d(TAG, "onMediaProjectionStoppedLocked: info=$info")
+ val item = makePrivacyItem(info)
+ privacyItems.removeAt(privacyItems.indexOfFirst { it.application == item.application })
+ logItemActive(item, false)
+ }
+
+ @WorkerThread
+ private fun makePrivacyItem(info: MediaProjectionInfo): PrivacyItem {
+ val userId = info.userHandle.identifier
+ val uid = packageManager.getPackageUidAsUser(info.packageName, userId)
+ val app = PrivacyApplication(info.packageName, uid)
+ val now = systemClock.elapsedRealtime()
+ return PrivacyItem(PrivacyType.TYPE_MEDIA_PROJECTION, app, now)
+ }
+
+ private fun logItemActive(item: PrivacyItem, active: Boolean) {
+ logger.logUpdatedItemFromMediaProjection(
+ item.application.uid, item.application.packageName, active)
+ }
+
+ /**
+ * Updates listening status based on whether there are callbacks and the indicator is enabled.
+ */
+ @GuardedBy("lock")
+ private fun setListeningStateLocked() {
+ val shouldListen = mediaProjectionAvailable
+ if (DEBUG) {
+ Log.d(TAG, "shouldListen=$shouldListen, " +
+ "mediaProjectionAvailable=$mediaProjectionAvailable")
+ }
+ if (listening == shouldListen) {
+ return
+ }
+
+ listening = shouldListen
+ if (shouldListen) {
+ if (DEBUG) Log.d(TAG, "Registering MediaProjectionManager callback")
+ mediaProjectionManager.addCallback(mediaProjectionCallback, bgHandler)
+
+ val activeProjection = mediaProjectionManager.activeProjectionInfo
+ if (activeProjection != null) {
+ onMediaProjectionStartedLocked(activeProjection)
+ dispatchOnPrivacyItemsChanged()
+ }
+ } else {
+ if (DEBUG) Log.d(TAG, "Unregistering MediaProjectionManager callback")
+ mediaProjectionManager.removeCallback(mediaProjectionCallback)
+ privacyItems.forEach { logItemActive(it, false) }
+ privacyItems.clear()
+ dispatchOnPrivacyItemsChanged()
+ }
+ }
+
+ override fun getActivePrivacyItems(): List<PrivacyItem> {
+ synchronized(lock) {
+ if (DEBUG) Log.d(TAG, "getActivePrivacyItems: privacyItems=$privacyItems")
+ return privacyItems.toList()
+ }
+ }
+
+ private fun dispatchOnPrivacyItemsChanged() {
+ if (DEBUG) Log.d(TAG, "dispatchOnPrivacyItemsChanged")
+ val cb = synchronized(lock) { callback }
+ if (cb != null) {
+ bgHandler.post {
+ cb.onPrivacyItemsChanged()
+ }
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ val ipw = pw.asIndenting()
+ ipw.println("MediaProjectionPrivacyItemMonitor:")
+ ipw.withIncreasedIndent {
+ synchronized(lock) {
+ ipw.println("Listening: $listening")
+ ipw.println("mediaProjectionAvailable: $mediaProjectionAvailable")
+ ipw.println("Callback: $callback")
+ ipw.println("Privacy Items: $privacyItems")
+ }
+ }
+ ipw.flush()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
index 6d29ba1..d652889 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
@@ -43,8 +43,11 @@
const val TAG = "PrivacyConfig"
private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
private const val LOCATION = SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED
+ private const val MEDIA_PROJECTION =
+ SystemUiDeviceConfigFlags.PROPERTY_MEDIA_PROJECTION_INDICATORS_ENABLED
private const val DEFAULT_MIC_CAMERA = true
private const val DEFAULT_LOCATION = false
+ private const val DEFAULT_MEDIA_PROJECTION = true
}
private val callbacks = mutableListOf<WeakReference<Callback>>()
@@ -53,6 +56,8 @@
private set
var locationAvailable = isLocationEnabled()
private set
+ var mediaProjectionAvailable = isMediaProjectionEnabled()
+ private set
private val devicePropertiesChangedListener =
DeviceConfig.OnPropertiesChangedListener { properties ->
@@ -67,6 +72,14 @@
locationAvailable = properties.getBoolean(LOCATION, DEFAULT_LOCATION)
callbacks.forEach { it.get()?.onFlagLocationChanged(locationAvailable) }
}
+
+ if (properties.keyset.contains(MEDIA_PROJECTION)) {
+ mediaProjectionAvailable =
+ properties.getBoolean(MEDIA_PROJECTION, DEFAULT_MEDIA_PROJECTION)
+ callbacks.forEach {
+ it.get()?.onFlagMediaProjectionChanged(mediaProjectionAvailable)
+ }
+ }
}
}
@@ -88,6 +101,11 @@
LOCATION, DEFAULT_LOCATION)
}
+ private fun isMediaProjectionEnabled(): Boolean {
+ return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ MEDIA_PROJECTION, DEFAULT_MEDIA_PROJECTION)
+ }
+
fun addCallback(callback: Callback) {
addCallback(WeakReference(callback))
}
@@ -115,6 +133,7 @@
ipw.withIncreasedIndent {
ipw.println("micCameraAvailable: $micCameraAvailable")
ipw.println("locationAvailable: $locationAvailable")
+ ipw.println("mediaProjectionAvailable: $mediaProjectionAvailable")
ipw.println("Callbacks:")
ipw.withIncreasedIndent {
callbacks.forEach { callback ->
@@ -131,5 +150,8 @@
@JvmDefault
fun onFlagLocationChanged(flag: Boolean) {}
+
+ @JvmDefault
+ fun onFlagMediaProjectionChanged(flag: Boolean) {}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
index d4e1642..03145a7 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt
@@ -165,6 +165,7 @@
PrivacyType.TYPE_LOCATION -> R.drawable.privacy_item_circle_location
PrivacyType.TYPE_CAMERA -> R.drawable.privacy_item_circle_camera
PrivacyType.TYPE_MICROPHONE -> R.drawable.privacy_item_circle_microphone
+ PrivacyType.TYPE_MEDIA_PROJECTION -> R.drawable.privacy_item_circle_media_projection
}) as LayerDrawable
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
index 76199bf..8b41000 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt
@@ -43,6 +43,12 @@
com.android.internal.R.drawable.perm_group_location,
android.Manifest.permission_group.LOCATION,
"location"
+ ),
+ TYPE_MEDIA_PROJECTION(
+ R.string.privacy_type_media_projection,
+ R.drawable.stat_sys_cast,
+ android.Manifest.permission_group.UNDEFINED,
+ "media projection"
);
fun getName(context: Context) = context.resources.getString(nameId)
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index f72e022..a676150 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -65,7 +65,7 @@
val locationAvailable
get() = privacyConfig.locationAvailable
val allIndicatorsAvailable
- get() = micCameraAvailable && locationAvailable
+ get() = micCameraAvailable && locationAvailable && privacyConfig.mediaProjectionAvailable
private val notifyChanges = Runnable {
val list = privacyList
@@ -85,6 +85,10 @@
override fun onFlagMicCameraChanged(flag: Boolean) {
callbacks.forEach { it.get()?.onFlagMicCameraChanged(flag) }
}
+
+ override fun onFlagMediaProjectionChanged(flag: Boolean) {
+ callbacks.forEach { it.get()?.onFlagMediaProjectionChanged(flag) }
+ }
}
private val privacyItemMonitorCallback = object : PrivacyItemMonitor.Callback {
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 1a268b5..1ea9347 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt
@@ -44,6 +44,16 @@
})
}
+ fun logUpdatedItemFromMediaProjection(uid: Int, packageName: String, active: Boolean) {
+ log(LogLevel.INFO, {
+ int1 = uid
+ str1 = packageName
+ bool1 = active
+ }, {
+ "MediaProjection: $str1($int1), active=$bool1"
+ })
+ }
+
fun logRetrievedPrivacyItemsList(list: List<PrivacyItem>) {
log(LogLevel.INFO, {
str1 = listToString(list)
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 4685c14..9a19d8d 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -39,6 +39,8 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
import com.android.systemui.power.dagger.PowerModule;
+import com.android.systemui.privacy.MediaProjectionPrivacyItemMonitor;
+import com.android.systemui.privacy.PrivacyItemMonitor;
import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.recents.Recents;
@@ -78,6 +80,7 @@
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.IntoSet;
/**
* A dagger module for injecting default implementations of components of System UI that may be
@@ -212,4 +215,12 @@
NotificationListener notificationListener) {
return new TvNotificationHandler(context, notificationListener);
}
+
+ /**
+ * Binds {@link MediaProjectionPrivacyItemMonitor} into the set of {@link PrivacyItemMonitor}.
+ */
+ @Binds
+ @IntoSet
+ abstract PrivacyItemMonitor bindMediaProjectionPrivacyItemMonitor(
+ MediaProjectionPrivacyItemMonitor mediaProjectionPrivacyItemMonitor);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
index 1b8564c..272f149 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt
@@ -43,6 +43,8 @@
companion object {
private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
private const val LOCATION = SystemUiDeviceConfigFlags.PROPERTY_LOCATION_INDICATORS_ENABLED
+ private const val MEDIA_PROJECTION =
+ SystemUiDeviceConfigFlags.PROPERTY_MEDIA_PROJECTION_INDICATORS_ENABLED
}
private lateinit var privacyConfig: PrivacyConfig
@@ -90,6 +92,16 @@
}
@Test
+ fun testMediaProjectionChanged() {
+ changeMediaProjection(false) // default is true
+ executor.runAllReady()
+
+ verify(callback).onFlagMediaProjectionChanged(false)
+
+ assertFalse(privacyConfig.mediaProjectionAvailable)
+ }
+
+ @Test
fun testLocationChanged() {
changeLocation(true)
executor.runAllReady()
@@ -99,8 +111,8 @@
}
@Test
- fun testBothChanged() {
- changeAll(true)
+ fun testMicCamAndLocationChanged() {
+ changeLocation(true)
changeMicCamera(false)
executor.runAllReady()
@@ -124,10 +136,7 @@
private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
private fun changeLocation(value: Boolean?) = changeProperty(LOCATION, value)
- private fun changeAll(value: Boolean?) {
- changeMicCamera(value)
- changeLocation(value)
- }
+ private fun changeMediaProjection(value: Boolean?) = changeProperty(MEDIA_PROJECTION, value)
private fun changeProperty(name: String, value: Boolean?) {
deviceConfigProxy.setProperty(
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 41f8f04..d563632 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt
@@ -361,8 +361,10 @@
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)
@@ -393,6 +395,17 @@
}
@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,