Separate mic + camera from location
The different states are: - No indicators enabled. Location is displayed using LocationControllerImpl - Only mic + camera indicators. Location is displayed using LocationControllerImpl - All indicators enabled. LocationControllerImpl is ignored. Also, hardwire OP_MONITOR_HIGH_POWER_LOCATION as user sensitive. Bug: 162552566 Test: atest com.android.systemui.privacy Test: manual Change-Id: I1e7f62d6fccb4a3bc4fdf2a47a8d5c8a4f55421d
This commit is contained in:
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -281,9 +281,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class PrivacyItemController @Inject constructor(
|
||||
context: Context,
|
||||
private val appOpsController: AppOpsController,
|
||||
@Main uiExecutor: DelayableExecutor,
|
||||
@Background private val bgExecutor: Executor,
|
||||
@@ -57,16 +56,21 @@ class PrivacyItemController @Inject constructor(
|
||||
|
||||
@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
|
||||
@@ -74,9 +78,14 @@ class PrivacyItemController @Inject constructor(
|
||||
@Synchronized get() = field.toList() // Returns a shallow copy of the list
|
||||
@Synchronized set
|
||||
|
||||
private fun isPermissionsHubEnabled(): Boolean {
|
||||
private 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 var currentUserIds = emptyList<Int>()
|
||||
@@ -94,23 +103,28 @@ class PrivacyItemController @Inject constructor(
|
||||
uiExecutor.execute(notifyChanges)
|
||||
}
|
||||
|
||||
var indicatorsAvailable = isPermissionsHubEnabled()
|
||||
var allIndicatorsAvailable = isAllIndicatorsEnabled()
|
||||
private set
|
||||
@VisibleForTesting
|
||||
internal val devicePropertiesChangedListener =
|
||||
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)) {
|
||||
val flag = properties.getBoolean(
|
||||
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED, false)
|
||||
if (indicatorsAvailable != flag) {
|
||||
// This is happening already in the UI executor, so we can iterate in the
|
||||
indicatorsAvailable = flag
|
||||
callbacks.forEach { it.get()?.onFlagChanged(flag) }
|
||||
(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()
|
||||
}
|
||||
}
|
||||
@@ -123,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)
|
||||
@@ -166,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) {
|
||||
@@ -233,14 +254,19 @@ 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)
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
fun onPrivacyItemsChanged(privacyItems: List<PrivacyItem>)
|
||||
|
||||
@JvmDefault
|
||||
fun onFlagChanged(flag: Boolean) {}
|
||||
fun onFlagAllChanged(flag: Boolean) {}
|
||||
|
||||
@JvmDefault
|
||||
fun onFlagMicCameraChanged(flag: Boolean) {}
|
||||
}
|
||||
|
||||
internal inner class Receiver : BroadcastReceiver() {
|
||||
|
||||
@@ -151,7 +151,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;
|
||||
@@ -178,13 +179,26 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFlagChanged(boolean flag) {
|
||||
if (mPermissionsHubEnabled != flag) {
|
||||
StatusIconContainer iconContainer = requireViewById(R.id.statusIcons);
|
||||
iconContainer.setIgnoredSlots(getIgnoredIconSlots());
|
||||
setChipVisibility(!mPrivacyChip.getPrivacyList().isEmpty());
|
||||
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
|
||||
@@ -267,7 +281,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
mRingerModeTextView.setSelected(true);
|
||||
mNextAlarmTextView.setSelected(true);
|
||||
|
||||
mPermissionsHubEnabled = mPrivacyItemController.getIndicatorsAvailable();
|
||||
mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
|
||||
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
|
||||
}
|
||||
|
||||
public QuickQSPanel getHeaderQsPanel() {
|
||||
@@ -276,13 +291,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;
|
||||
@@ -300,7 +317,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
|
||||
@@ -607,7 +624,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
mAlarmController.addCallback(this);
|
||||
mLifecycle.setCurrentState(Lifecycle.State.RESUMED);
|
||||
// Get the most up to date info
|
||||
mPermissionsHubEnabled = mPrivacyItemController.getIndicatorsAvailable();
|
||||
mAllIndicatorsEnabled = mPrivacyItemController.getAllIndicatorsAvailable();
|
||||
mMicCameraIndicatorsEnabled = mPrivacyItemController.getMicCameraAvailable();
|
||||
mPrivacyItemController.addCallback(mPICCallback);
|
||||
} else {
|
||||
mZenController.removeCallback(this);
|
||||
@@ -747,4 +765,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements
|
||||
updateHeaderTextContainerAlphaAnimator();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean getChipEnabled() {
|
||||
return mMicCameraIndicatorsEnabled || mAllIndicatorsEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,16 +662,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.getIndicatorsAvailable()) 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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,9 +97,8 @@ 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,
|
||||
@@ -116,11 +115,8 @@ class PrivacyItemControllerTest : SysuiTestCase() {
|
||||
executor = FakeExecutor(FakeSystemClock())
|
||||
deviceConfigProxy = DeviceConfigProxyFake()
|
||||
|
||||
appOpsController = mDependency.injectMockDependency(AppOpsController::class.java)
|
||||
|
||||
deviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
|
||||
SystemUiDeviceConfigFlags.PROPERTY_PERMISSIONS_HUB_ENABLED,
|
||||
"true", false)
|
||||
// Listen to everything by default
|
||||
changeAll(true)
|
||||
|
||||
doReturn(listOf(object : UserInfo() {
|
||||
init {
|
||||
@@ -128,7 +124,7 @@ class PrivacyItemControllerTest : SysuiTestCase() {
|
||||
}
|
||||
})).`when`(userManager).getProfiles(anyInt())
|
||||
|
||||
privacyItemController = PrivacyItemController(mContext)
|
||||
privacyItemController = PrivacyItemController()
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -276,15 +272,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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user