Merge "Separate mic + camera from location" into rvc-qpr-dev

This commit is contained in:
Fabian Kozynski
2020-08-07 13:22:04 +00:00
committed by Android (Google) Code Review
7 changed files with 403 additions and 89 deletions

View File

@@ -123,10 +123,15 @@ public final class SystemUiDeviceConfigFlags {
// Flag related to Privacy Indicators
/**
* Whether the Permissions Hub is showing.
* Whether to show the complete ongoing app ops chip.
*/
public static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_2_enabled";
/**
* Whether to show app ops chip for just microphone + camera.
*/
public static final String PROPERTY_MIC_CAMERA_ENABLED = "camera_mic_icons_enabled";
// Flags related to Assistant
/**

View File

@@ -279,9 +279,11 @@ public class AppOpsControllerImpl implements AppOpsController,
* @return {@code true} iff the app-op for should be shown to the user
*/
private boolean isUserVisible(int appOpCode, int uid, String packageName) {
// currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission
// which may be user senstive, so for now always show it to the user.
if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) {
// currently OP_SYSTEM_ALERT_WINDOW and OP_MONITOR_HIGH_POWER_LOCATION
// does not correspond to a platform permission
// which may be user sensitive, so for now always show it to the user.
if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW
|| appOpCode == AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION) {
return true;
}

View File

@@ -45,27 +45,32 @@ import javax.inject.Singleton
@Singleton
class PrivacyItemController @Inject constructor(
context: Context,
private val appOpsController: AppOpsController,
@Main uiExecutor: DelayableExecutor,
@Background private val bgExecutor: Executor,
private val broadcastDispatcher: BroadcastDispatcher,
private val deviceConfigProxy: DeviceConfigProxy,
private val userManager: UserManager,
dumpManager: DumpManager
) : Dumpable {
@VisibleForTesting
internal companion object {
val OPS = intArrayOf(AppOpsManager.OP_CAMERA,
AppOpsManager.OP_RECORD_AUDIO,
val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA,
AppOpsManager.OP_RECORD_AUDIO)
val OPS_LOCATION = intArrayOf(
AppOpsManager.OP_COARSE_LOCATION,
AppOpsManager.OP_FINE_LOCATION)
val OPS = OPS_MIC_CAMERA + OPS_LOCATION
val intentFilter = IntentFilter().apply {
addAction(Intent.ACTION_USER_SWITCHED)
addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE)
addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)
}
const val TAG = "PrivacyItemController"
private const val ALL_INDICATORS =
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
}
@VisibleForTesting
@@ -73,12 +78,16 @@ class PrivacyItemController @Inject constructor(
@Synchronized get() = field.toList() // Returns a shallow copy of the list
@Synchronized set
fun isPermissionsHubEnabled(): Boolean {
fun isAllIndicatorsEnabled(): Boolean {
return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false)
ALL_INDICATORS, false)
}
private fun isMicCameraEnabled(): Boolean {
return deviceConfigProxy.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
MIC_CAMERA, false)
}
private val userManager = context.getSystemService(UserManager::class.java)
private var currentUserIds = emptyList<Int>()
private var listening = false
private val callbacks = mutableListOf<WeakReference<Callback>>()
@@ -86,7 +95,7 @@ class PrivacyItemController @Inject constructor(
private val notifyChanges = Runnable {
val list = privacyList
callbacks.forEach { it.get()?.privacyChanged(list) }
callbacks.forEach { it.get()?.onPrivacyItemsChanged(list) }
}
private val updateListAndNotifyChanges = Runnable {
@@ -94,20 +103,32 @@ class PrivacyItemController @Inject constructor(
uiExecutor.execute(notifyChanges)
}
private var indicatorsAvailable = isPermissionsHubEnabled()
@VisibleForTesting
internal val devicePropertiesChangedListener =
var allIndicatorsAvailable = isAllIndicatorsEnabled()
private set
var micCameraAvailable = isMicCameraEnabled()
private set
private val devicePropertiesChangedListener =
object : DeviceConfig.OnPropertiesChangedListener {
override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
if (DeviceConfig.NAMESPACE_PRIVACY.equals(properties.getNamespace()) &&
properties.getKeyset().contains(
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED)) {
indicatorsAvailable = properties.getBoolean(
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false)
internalUiExecutor.updateListeningState()
if (DeviceConfig.NAMESPACE_PRIVACY.equals(properties.getNamespace()) &&
(properties.keyset.contains(ALL_INDICATORS) ||
properties.keyset.contains(MIC_CAMERA))) {
// Running on the ui executor so can iterate on callbacks
if (properties.keyset.contains(ALL_INDICATORS)) {
allIndicatorsAvailable = properties.getBoolean(ALL_INDICATORS, false)
callbacks.forEach { it.get()?.onFlagAllChanged(allIndicatorsAvailable) }
}
if (properties.keyset.contains(MIC_CAMERA)) {
micCameraAvailable = properties.getBoolean(MIC_CAMERA, false)
callbacks.forEach { it.get()?.onFlagMicCameraChanged(micCameraAvailable) }
}
internalUiExecutor.updateListeningState()
}
}
}
}
private val cb = object : AppOpsController.Callback {
override fun onActiveStateChanged(
@@ -116,6 +137,10 @@ class PrivacyItemController @Inject constructor(
packageName: String,
active: Boolean
) {
// Check if we care about this code right now
if (!allIndicatorsAvailable && code in OPS_LOCATION) {
return
}
val userId = UserHandle.getUserId(uid)
if (userId in currentUserIds) {
update(false)
@@ -159,13 +184,16 @@ class PrivacyItemController @Inject constructor(
}
/**
* Updates listening status based on whether there are callbacks and the indicators are enabled
* Updates listening status based on whether there are callbacks and the indicators are enabled.
*
* Always listen to all OPS so we don't have to figure out what we should be listening to. We
* still have to filter anyway. Updates are filtered in the callback.
*
* This is only called from private (add/remove)Callback and from the config listener, all in
* main thread.
*/
private fun setListeningState() {
val listen = !callbacks.isEmpty() and indicatorsAvailable
val listen = !callbacks.isEmpty() and (allIndicatorsAvailable || micCameraAvailable)
if (listening == listen) return
listening = listen
if (listening) {
@@ -226,13 +254,20 @@ class PrivacyItemController @Inject constructor(
AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
else -> return null
}
if (type == PrivacyType.TYPE_LOCATION && !allIndicatorsAvailable) return null
val app = PrivacyApplication(appOpItem.packageName, appOpItem.uid)
return PrivacyItem(type, app)
}
// Used by containing class to get notified of changes
interface Callback {
fun privacyChanged(privacyItems: List<PrivacyItem>)
fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>)
@JvmDefault
fun onFlagAllChanged(flag: Boolean) {}
@JvmDefault
fun onFlagMicCameraChanged(flag: Boolean) {}
}
internal inner class Receiver : BroadcastReceiver() {
@@ -248,7 +283,7 @@ class PrivacyItemController @Inject constructor(
private val list: List<PrivacyItem>
) : Runnable {
override fun run() {
callback?.privacyChanged(list)
callback?.onPrivacyItemsChanged(list)
}
}

View File

@@ -33,7 +33,6 @@ import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import android.provider.AlarmClock;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.service.notification.ZenModeConfig;
import android.text.format.DateUtils;
@@ -59,7 +58,6 @@ import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.UiEventLogger;
import com.android.settingslib.Utils;
import com.android.systemui.BatteryMeterView;
@@ -154,7 +152,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private Space mSpace;
private BatteryMeterView mBatteryRemainingIcon;
private RingerModeTracker mRingerModeTracker;
private boolean mPermissionsHubEnabled;
private boolean mAllIndicatorsEnabled;
private boolean mMicCameraIndicatorsEnabled;
private PrivacyItemController mPrivacyItemController;
private final UiEventLogger mUiEventLogger;
@@ -173,27 +172,34 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private float mKeyguardExpansionFraction;
private boolean mPrivacyChipLogged = false;
private final DeviceConfig.OnPropertiesChangedListener mPropertiesListener =
new DeviceConfig.OnPropertiesChangedListener() {
@Override
public void onPropertiesChanged(DeviceConfig.Properties properties) {
if (DeviceConfig.NAMESPACE_PRIVACY.equals(properties.getNamespace())
&& properties.getKeyset()
.contains(SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED)) {
mPermissionsHubEnabled = properties.getBoolean(
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false);
StatusIconContainer iconContainer = findViewById(R.id.statusIcons);
iconContainer.setIgnoredSlots(getIgnoredIconSlots());
}
}
};
private PrivacyItemController.Callback mPICCallback = new PrivacyItemController.Callback() {
@Override
public void privacyChanged(List<PrivacyItem> privacyItems) {
public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) {
mPrivacyChip.setPrivacyList(privacyItems);
setChipVisibility(!privacyItems.isEmpty());
}
@Override
public void onFlagAllChanged(boolean flag) {
if (mAllIndicatorsEnabled != flag) {
mAllIndicatorsEnabled = flag;
update();
}
}
@Override
public void onFlagMicCameraChanged(boolean flag) {
if (mMicCameraIndicatorsEnabled != flag) {
mMicCameraIndicatorsEnabled = flag;
update();
}
}
private void update() {
StatusIconContainer iconContainer = requireViewById(R.id.statusIcons);
iconContainer.setIgnoredSlots(getIgnoredIconSlots());
setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty());
}
};
@Inject
@@ -276,7 +282,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mRingerModeTextView.setSelected(true);
mNextAlarmTextView.setSelected(true);
mPermissionsHubEnabled = mPrivacyItemController.isPermissionsHubEnabled();
mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
}
public QuickQSPanel getHeaderQsPanel() {
@@ -285,13 +292,15 @@ public class QuickStatusBarHeader extends RelativeLayout implements
private List<String> getIgnoredIconSlots() {
ArrayList<String> ignored = new ArrayList<>();
ignored.add(mContext.getResources().getString(
com.android.internal.R.string.status_bar_camera));
ignored.add(mContext.getResources().getString(
com.android.internal.R.string.status_bar_microphone));
if (mPermissionsHubEnabled) {
if (getChipEnabled()) {
ignored.add(mContext.getResources().getString(
com.android.internal.R.string.status_bar_location));
com.android.internal.R.string.status_bar_camera));
ignored.add(mContext.getResources().getString(
com.android.internal.R.string.status_bar_microphone));
if (mAllIndicatorsEnabled) {
ignored.add(mContext.getResources().getString(
com.android.internal.R.string.status_bar_location));
}
}
return ignored;
@@ -309,7 +318,7 @@ public class QuickStatusBarHeader extends RelativeLayout implements
}
private void setChipVisibility(boolean chipVisible) {
if (chipVisible && mPermissionsHubEnabled) {
if (chipVisible && getChipEnabled()) {
mPrivacyChip.setVisibility(View.VISIBLE);
// Makes sure that the chip is logged as viewed at most once each time QS is opened
// mListening makes sure that the callback didn't return after the user closed QS
@@ -519,9 +528,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
});
mStatusBarIconController.addIconGroup(mIconManager);
requestApplyInsets();
// Change the ignored slots when DeviceConfig flag changes
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
mContext.getMainExecutor(), mPropertiesListener);
}
@Override
@@ -601,7 +607,6 @@ public class QuickStatusBarHeader extends RelativeLayout implements
setListening(false);
mRingerModeTracker.getRingerModeInternal().removeObservers(this);
mStatusBarIconController.removeIconGroup(mIconManager);
DeviceConfig.removeOnPropertiesChangedListener(mPropertiesListener);
super.onDetachedFromWindow();
}
@@ -619,6 +624,9 @@ public class QuickStatusBarHeader extends RelativeLayout implements
mZenController.addCallback(this);
mAlarmController.addCallback(this);
mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
// Get the most up to date info
mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
mPrivacyItemController.addCallback(mPICCallback);
} else {
mZenController.removeCallback(this);
@@ -760,4 +768,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements
updateHeaderTextContainerAlphaAnimator();
}
}
private boolean getChipEnabled() {
return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled;
}
}

View File

@@ -631,7 +631,7 @@ public class PhoneStatusBarPolicy
}
@Override // PrivacyItemController.Callback
public void privacyChanged(List<PrivacyItem> privacyItems) {
public void onPrivacyItemsChanged(List<PrivacyItem> privacyItems) {
updatePrivacyItems(privacyItems);
}
@@ -664,16 +664,18 @@ public class PhoneStatusBarPolicy
mIconController.setIconVisibility(mSlotCamera, showCamera);
mIconController.setIconVisibility(mSlotMicrophone, showMicrophone);
mIconController.setIconVisibility(mSlotLocation, showLocation);
if (mPrivacyItemController.getAllIndicatorsAvailable()) {
mIconController.setIconVisibility(mSlotLocation, showLocation);
}
}
@Override
public void onLocationActiveChanged(boolean active) {
if (!mPrivacyItemController.isPermissionsHubEnabled()) updateLocation();
if (!mPrivacyItemController.getAllIndicatorsAvailable()) updateLocationFromController();
}
// Updates the status view based on the current state of location requests.
private void updateLocation() {
private void updateLocationFromController() {
if (mLocationController.isLocationActive()) {
mIconController.setIconVisibility(mSlotLocation, true);
} else {

View File

@@ -0,0 +1,219 @@
/*
* Copyright (C) 2020 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.os.UserManager
import android.provider.DeviceConfig
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.systemui.SysuiTestCase
import com.android.systemui.appops.AppOpsController
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class PrivacyItemControllerFlagsTest : SysuiTestCase() {
companion object {
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
fun <T> eq(value: T): T = Mockito.eq(value) ?: value
fun <T> any(): T = Mockito.any<T>()
private const val ALL_INDICATORS =
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
}
@Mock
private lateinit var appOpsController: AppOpsController
@Mock
private lateinit var callback: PrivacyItemController.Callback
@Mock
private lateinit var userManager: UserManager
@Mock
private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock
private lateinit var dumpManager: DumpManager
private lateinit var privacyItemController: PrivacyItemController
private lateinit var executor: FakeExecutor
private lateinit var deviceConfigProxy: DeviceConfigProxy
fun PrivacyItemController(): PrivacyItemController {
return PrivacyItemController(
appOpsController,
executor,
executor,
broadcastDispatcher,
deviceConfigProxy,
userManager,
dumpManager
)
}
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
executor = FakeExecutor(FakeSystemClock())
deviceConfigProxy = DeviceConfigProxyFake()
privacyItemController = PrivacyItemController()
privacyItemController.addCallback(callback)
executor.runAllReady()
}
@Test
fun testNotListeningByDefault() {
assertFalse(privacyItemController.allIndicatorsAvailable)
assertFalse(privacyItemController.micCameraAvailable)
verify(appOpsController, never()).addCallback(any(), any())
}
@Test
fun testMicCameraChanged() {
changeMicCamera(true)
executor.runAllReady()
verify(callback).onFlagMicCameraChanged(true)
verify(callback, never()).onFlagAllChanged(anyBoolean())
assertTrue(privacyItemController.micCameraAvailable)
assertFalse(privacyItemController.allIndicatorsAvailable)
}
@Test
fun testAllChanged() {
changeAll(true)
executor.runAllReady()
verify(callback).onFlagAllChanged(true)
verify(callback, never()).onFlagMicCameraChanged(anyBoolean())
assertTrue(privacyItemController.allIndicatorsAvailable)
assertFalse(privacyItemController.micCameraAvailable)
}
@Test
fun testBothChanged() {
changeAll(true)
changeMicCamera(true)
executor.runAllReady()
verify(callback, atLeastOnce()).onFlagAllChanged(true)
verify(callback, atLeastOnce()).onFlagMicCameraChanged(true)
assertTrue(privacyItemController.allIndicatorsAvailable)
assertTrue(privacyItemController.micCameraAvailable)
}
@Test
fun testAll_listeningToAll() {
changeAll(true)
executor.runAllReady()
verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
}
@Test
fun testMicCamera_listening() {
changeMicCamera(true)
executor.runAllReady()
verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
}
@Test
fun testAll_listening() {
changeAll(true)
executor.runAllReady()
verify(appOpsController).addCallback(eq(PrivacyItemController.OPS), any())
}
@Test
fun testAllFalse_notListening() {
changeAll(true)
executor.runAllReady()
changeAll(false)
executor.runAllReady()
verify(appOpsController).removeCallback(any(), any())
}
@Test
fun testSomeListening_stillListening() {
changeAll(true)
changeMicCamera(true)
executor.runAllReady()
changeAll(false)
executor.runAllReady()
verify(appOpsController, never()).removeCallback(any(), any())
}
@Test
fun testAllDeleted_stopListening() {
changeAll(true)
executor.runAllReady()
changeAll(null)
executor.runAllReady()
verify(appOpsController).removeCallback(any(), any())
}
@Test
fun testMicDeleted_stopListening() {
changeMicCamera(true)
executor.runAllReady()
changeMicCamera(null)
executor.runAllReady()
verify(appOpsController).removeCallback(any(), any())
}
private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
private fun changeProperty(name: String, value: Boolean?) {
deviceConfigProxy.setProperty(
DeviceConfig.NAMESPACE_PRIVACY,
name,
value?.toString(),
false
)
}
}

View File

@@ -18,7 +18,6 @@ package com.android.systemui.privacy
import android.app.ActivityManager
import android.app.AppOpsManager
import android.content.Context
import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserHandle
@@ -69,10 +68,11 @@ class PrivacyItemControllerTest : SysuiTestCase() {
companion object {
val CURRENT_USER_ID = ActivityManager.getCurrentUser()
val TEST_UID = CURRENT_USER_ID * UserHandle.PER_USER_RANGE
const val SYSTEM_UID = 1000
const val TEST_PACKAGE_NAME = "test"
const val DEVICE_SERVICES_STRING = "Device services"
const val TAG = "PrivacyItemControllerTest"
private const val ALL_INDICATORS =
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED
private const val MIC_CAMERA = SystemUiDeviceConfigFlags.PROPERTY_MIC_CAMERA_ENABLED
fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
fun <T> eq(value: T): T = Mockito.eq(value) ?: value
fun <T> any(): T = Mockito.any<T>()
@@ -97,14 +97,14 @@ class PrivacyItemControllerTest : SysuiTestCase() {
private lateinit var executor: FakeExecutor
private lateinit var deviceConfigProxy: DeviceConfigProxy
fun PrivacyItemController(context: Context): PrivacyItemController {
fun PrivacyItemController(): PrivacyItemController {
return PrivacyItemController(
context,
appOpsController,
executor,
executor,
broadcastDispatcher,
deviceConfigProxy,
userManager,
dumpManager
)
}
@@ -115,12 +115,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
executor = FakeExecutor(FakeSystemClock())
deviceConfigProxy = DeviceConfigProxyFake()
appOpsController = mDependency.injectMockDependency(AppOpsController::class.java)
mContext.addMockSystemService(UserManager::class.java, userManager)
deviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED,
"true", false)
changeAll(true)
doReturn(listOf(object : UserInfo() {
init {
@@ -128,7 +123,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
}
})).`when`(userManager).getProfiles(anyInt())
privacyItemController = PrivacyItemController(mContext)
privacyItemController = PrivacyItemController()
}
@Test
@@ -137,7 +132,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
executor.runAllReady()
verify(appOpsController).addCallback(eq(PrivacyItemController.OPS),
any())
verify(callback).privacyChanged(anyList())
verify(callback).onPrivacyItemsChanged(anyList())
}
@Test
@@ -150,7 +145,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
executor.runAllReady()
verify(appOpsController).removeCallback(eq(PrivacyItemController.OPS),
any())
verify(callback).privacyChanged(emptyList())
verify(callback).onPrivacyItemsChanged(emptyList())
}
@Test
@@ -161,7 +156,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
privacyItemController.addCallback(callback)
executor.runAllReady()
verify(callback).privacyChanged(capture(argCaptor))
verify(callback).onPrivacyItemsChanged(capture(argCaptor))
assertEquals(1, argCaptor.value.size)
}
@@ -204,11 +199,11 @@ class PrivacyItemControllerTest : SysuiTestCase() {
val otherCallback = mock(PrivacyItemController.Callback::class.java)
privacyItemController.addCallback(callback)
executor.runAllReady()
verify(callback).privacyChanged(anyList())
verify(callback).onPrivacyItemsChanged(anyList())
privacyItemController.addCallback(otherCallback)
executor.runAllReady()
verify(otherCallback).privacyChanged(anyList())
verify(otherCallback).onPrivacyItemsChanged(anyList())
// Adding a callback should not unnecessarily call previous ones
verifyNoMoreInteractions(callback)
}
@@ -227,8 +222,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
executor.runAllReady()
verify(callback).privacyChanged(anyList())
verify(otherCallback).privacyChanged(anyList())
verify(callback).onPrivacyItemsChanged(anyList())
verify(otherCallback).onPrivacyItemsChanged(anyList())
}
@Test
@@ -246,8 +241,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
privacyItemController.removeCallback(callback)
argCaptorCallback.value.onActiveStateChanged(0, TEST_UID, "", true)
executor.runAllReady()
verify(callback, never()).privacyChanged(anyList())
verify(otherCallback).privacyChanged(anyList())
verify(callback, never()).onPrivacyItemsChanged(anyList())
verify(otherCallback).onPrivacyItemsChanged(anyList())
}
@Test
@@ -259,7 +254,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
executor.runAllReady()
executor.runAllReady()
verify(callback).privacyChanged(capture(argCaptor))
verify(callback).onPrivacyItemsChanged(capture(argCaptor))
assertEquals(1, argCaptor.value.size)
assertThat(argCaptor.value, not(hasItem(nullValue())))
}
@@ -276,15 +271,59 @@ class PrivacyItemControllerTest : SysuiTestCase() {
@Test
fun testNotListeningWhenIndicatorsDisabled() {
deviceConfigProxy.setProperty(
DeviceConfig.NAMESPACE_PRIVACY,
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED,
"false",
false
)
changeAll(false)
privacyItemController.addCallback(callback)
executor.runAllReady()
verify(appOpsController, never()).addCallback(eq(PrivacyItemController.OPS),
any())
}
@Test
fun testNotSendingLocationWhenOnlyMicCamera() {
changeAll(false)
changeMicCamera(true)
executor.runAllReady()
doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, "", 0),
AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
.`when`(appOpsController).getActiveAppOpsForUser(anyInt())
privacyItemController.addCallback(callback)
executor.runAllReady()
verify(callback).onPrivacyItemsChanged(capture(argCaptor))
assertEquals(1, argCaptor.value.size)
assertEquals(PrivacyType.TYPE_CAMERA, argCaptor.value[0].privacyType)
}
@Test
fun testNotUpdated_LocationChangeWhenOnlyMicCamera() {
doReturn(listOf(AppOpItem(AppOpsManager.OP_COARSE_LOCATION, TEST_UID, "", 0)))
.`when`(appOpsController).getActiveAppOpsForUser(anyInt())
privacyItemController.addCallback(callback)
changeAll(false)
changeMicCamera(true)
executor.runAllReady()
reset(callback) // Clean callback
verify(appOpsController).addCallback(any(), capture(argCaptorCallback))
argCaptorCallback.value.onActiveStateChanged(
AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true)
verify(callback, never()).onPrivacyItemsChanged(any())
}
private fun changeMicCamera(value: Boolean?) = changeProperty(MIC_CAMERA, value)
private fun changeAll(value: Boolean?) = changeProperty(ALL_INDICATORS, value)
private fun changeProperty(name: String, value: Boolean?) {
deviceConfigProxy.setProperty(
DeviceConfig.NAMESPACE_PRIVACY,
name,
value?.toString(),
false
)
}
}