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,