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: I7abbfc8bcdc5d2b5c89452a417c51b019fbb8d56 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -157,6 +157,12 @@ public final class SystemUiDeviceConfigFlags {
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
@@ -2059,6 +2059,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>
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ import android.hardware.fingerprint.FingerprintManager;
|
||||
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;
|
||||
@@ -320,6 +321,11 @@ public class FrameworkServicesModule {
|
||||
return LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static MediaProjectionManager provideMediaProjectionManager(Context context) {
|
||||
return context.getSystemService(MediaProjectionManager.class);
|
||||
}
|
||||
|
||||
@Provides
|
||||
static MediaRouter2Manager provideMediaRouter2Manager(Context context) {
|
||||
return MediaRouter2Manager.getInstance(context);
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,11 @@ class PrivacyConfig @Inject constructor(
|
||||
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 @@ class PrivacyConfig @Inject constructor(
|
||||
private set
|
||||
var locationAvailable = isLocationEnabled()
|
||||
private set
|
||||
var mediaProjectionAvailable = isMediaProjectionEnabled()
|
||||
private set
|
||||
|
||||
private val devicePropertiesChangedListener =
|
||||
DeviceConfig.OnPropertiesChangedListener { properties ->
|
||||
@@ -67,6 +72,14 @@ class PrivacyConfig @Inject constructor(
|
||||
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 @@ class PrivacyConfig @Inject constructor(
|
||||
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 @@ class PrivacyConfig @Inject constructor(
|
||||
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 @@ class PrivacyConfig @Inject constructor(
|
||||
|
||||
@JvmDefault
|
||||
fun onFlagLocationChanged(flag: Boolean) {}
|
||||
|
||||
@JvmDefault
|
||||
fun onFlagMediaProjectionChanged(flag: Boolean) {}
|
||||
}
|
||||
}
|
||||
@@ -165,6 +165,7 @@ class PrivacyDialog(
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,12 @@ enum class PrivacyType(
|
||||
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)
|
||||
|
||||
@@ -65,7 +65,7 @@ class PrivacyItemController @Inject constructor(
|
||||
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 @@ class PrivacyItemController @Inject constructor(
|
||||
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 {
|
||||
|
||||
@@ -44,6 +44,16 @@ class PrivacyLogger @Inject constructor(
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -39,6 +39,8 @@ import com.android.systemui.plugins.qs.QSFactory;
|
||||
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 javax.inject.Named;
|
||||
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 @@ public abstract class TvSystemUIModule {
|
||||
NotificationListener notificationListener) {
|
||||
return new TvNotificationHandler(context, notificationListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds {@link MediaProjectionPrivacyItemMonitor} into the set of {@link PrivacyItemMonitor}.
|
||||
*/
|
||||
@Binds
|
||||
@IntoSet
|
||||
abstract PrivacyItemMonitor bindMediaProjectionPrivacyItemMonitor(
|
||||
MediaProjectionPrivacyItemMonitor mediaProjectionPrivacyItemMonitor);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ class PrivacyConfigFlagsTest : SysuiTestCase() {
|
||||
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
|
||||
@@ -89,6 +91,16 @@ class PrivacyConfigFlagsTest : SysuiTestCase() {
|
||||
assertFalse(privacyConfig.micCameraAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMediaProjectionChanged() {
|
||||
changeMediaProjection(false) // default is true
|
||||
executor.runAllReady()
|
||||
|
||||
verify(callback).onFlagMediaProjectionChanged(false)
|
||||
|
||||
assertFalse(privacyConfig.mediaProjectionAvailable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLocationChanged() {
|
||||
changeLocation(true)
|
||||
@@ -99,8 +111,8 @@ class PrivacyConfigFlagsTest : SysuiTestCase() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBothChanged() {
|
||||
changeAll(true)
|
||||
fun testMicCamAndLocationChanged() {
|
||||
changeLocation(true)
|
||||
changeMicCamera(false)
|
||||
executor.runAllReady()
|
||||
|
||||
@@ -124,10 +136,7 @@ class PrivacyConfigFlagsTest : SysuiTestCase() {
|
||||
|
||||
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(
|
||||
|
||||
@@ -361,8 +361,10 @@ class PrivacyItemControllerTest : SysuiTestCase() {
|
||||
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)
|
||||
@@ -392,6 +394,17 @@ class PrivacyItemControllerTest : SysuiTestCase() {
|
||||
verify(callback).onFlagLocationChanged(true)
|
||||
}
|
||||
|
||||
@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(
|
||||
|
||||
Reference in New Issue
Block a user