Merge "Separate mic + camera from location" into rvc-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
515db30387
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user