Commit d17ed102 authored by Jason Lin's avatar Jason Lin Committed by Chromium LUCI CQ

VmCameraMicManager observers camera privacy switch

When camera privacy switch is on, even if a vm is accessing the camera,
the camera is considered inactive since the vm can only get black
frames.

Bug: b/167491603
Change-Id: I159501040da557661a17d61a1fed4499c74d6907
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2593795
Commit-Queue: Jason Lin <lxj@google.com>
Reviewed-by: default avatarJoel Hockey <joelhockey@chromium.org>
Cr-Commit-Position: refs/heads/master@{#837463}
parent 994334aa
...@@ -86,17 +86,19 @@ void VmCameraMicManager::OnPrimaryUserSessionStarted(Profile* primary_profile) { ...@@ -86,17 +86,19 @@ void VmCameraMicManager::OnPrimaryUserSessionStarted(Profile* primary_profile) {
primary_profile_, base::BindRepeating(OpenPluginVmSettings)); primary_profile_, base::BindRepeating(OpenPluginVmSettings));
for (VmType vm : {VmType::kCrostiniVm, VmType::kPluginVm}) { for (VmType vm : {VmType::kCrostiniVm, VmType::kPluginVm}) {
notification_map_[vm] = {}; vm_info_map_[vm] = {};
} }
// Camera service does not behave in non ChromeOS environment (e.g. testing, // Camera service does not behave in non ChromeOS environment (e.g. testing,
// linux chromeos). // linux chromeos).
if (base::SysInfo::IsRunningOnChromeOS() && if (base::SysInfo::IsRunningOnChromeOS() &&
media::ShouldUseCrosCameraService()) { media::ShouldUseCrosCameraService()) {
auto* camera = media::CameraHalDispatcherImpl::GetInstance();
// OnActiveClientChange() will be called automatically after the // OnActiveClientChange() will be called automatically after the
// subscription, so there is no need to get the current status here. // subscription, so there is no need to get the current status here.
media::CameraHalDispatcherImpl::GetInstance()->AddActiveClientObserver( camera->AddActiveClientObserver(this);
this); OnCameraPrivacySwitchStatusChanged(
camera->AddCameraPrivacySwitchObserver(this));
} }
} }
...@@ -104,13 +106,19 @@ void VmCameraMicManager::OnPrimaryUserSessionStarted(Profile* primary_profile) { ...@@ -104,13 +106,19 @@ void VmCameraMicManager::OnPrimaryUserSessionStarted(Profile* primary_profile) {
// we do not do clean up (e.g. deregister as observers) here. // we do not do clean up (e.g. deregister as observers) here.
VmCameraMicManager::~VmCameraMicManager() = default; VmCameraMicManager::~VmCameraMicManager() = default;
void VmCameraMicManager::SetActive(VmType vm, DeviceType device, bool active) { void VmCameraMicManager::UpdateVmInfoAndNotifications(
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); VmType vm,
void (VmInfo::*updator)(bool),
bool value) {
auto it = vm_info_map_.find(vm);
CHECK(it != vm_info_map_.end());
auto& vm_info = it->second;
NotificationType& notification_type = notification_map_[vm]; const NotificationType old_notification_type = vm_info.notification_type();
const NotificationType old_notification_type = notification_type; (vm_info.*updator)(value);
notification_type.set(static_cast<size_t>(device), active); const NotificationType new_notification_type = vm_info.notification_type();
if (old_notification_type == notification_type)
if (old_notification_type == new_notification_type)
return; return;
if (!observer_timer_.IsRunning()) { if (!observer_timer_.IsRunning()) {
...@@ -122,14 +130,15 @@ void VmCameraMicManager::SetActive(VmType vm, DeviceType device, bool active) { ...@@ -122,14 +130,15 @@ void VmCameraMicManager::SetActive(VmType vm, DeviceType device, bool active) {
if (old_notification_type != kNoNotification) { if (old_notification_type != kNoNotification) {
CloseNotification(vm, old_notification_type); CloseNotification(vm, old_notification_type);
} }
if (notification_type != kNoNotification) { if (new_notification_type != kNoNotification) {
OpenNotification(vm, notification_type); OpenNotification(vm, new_notification_type);
} }
} }
bool VmCameraMicManager::IsDeviceActive(DeviceType device) const { bool VmCameraMicManager::IsDeviceActive(DeviceType device) const {
for (const auto& vm_notification : notification_map_) { for (const auto& vm_info : vm_info_map_) {
const NotificationType& notification_type = vm_notification.second; const NotificationType& notification_type =
vm_info.second.notification_type();
if (notification_type[static_cast<size_t>(device)]) { if (notification_type[static_cast<size_t>(device)]) {
return true; return true;
} }
...@@ -138,8 +147,9 @@ bool VmCameraMicManager::IsDeviceActive(DeviceType device) const { ...@@ -138,8 +147,9 @@ bool VmCameraMicManager::IsDeviceActive(DeviceType device) const {
} }
bool VmCameraMicManager::IsNotificationActive(DeviceType device) const { bool VmCameraMicManager::IsNotificationActive(DeviceType device) const {
for (const auto& vm_notification : notification_map_) { for (const auto& vm_info : vm_info_map_) {
const NotificationType& notification_type = vm_notification.second; const NotificationType& notification_type =
vm_info.second.notification_type();
switch (device) { switch (device) {
case DeviceType::kMic: case DeviceType::kMic:
if (notification_type == kMicNotification) { if (notification_type == kMicNotification) {
...@@ -161,14 +171,42 @@ bool VmCameraMicManager::IsNotificationActive(DeviceType device) const { ...@@ -161,14 +171,42 @@ bool VmCameraMicManager::IsNotificationActive(DeviceType device) const {
void VmCameraMicManager::OnActiveClientChange( void VmCameraMicManager::OnActiveClientChange(
cros::mojom::CameraClientType type, cros::mojom::CameraClientType type,
bool is_active) { bool is_active) {
// Crostini does not support camera yet.
// TODO(b/167491603): `UNKNOWN` is for Parallels using v0 camera API. We // TODO(b/167491603): `UNKNOWN` is for Parallels using v0 camera API. We
// should be able to remove it soon. // should be able to remove it soon.
if (type == cros::mojom::CameraClientType::UNKNOWN || if (type == cros::mojom::CameraClientType::UNKNOWN ||
type == cros::mojom::CameraClientType::PLUGINVM) { type == cros::mojom::CameraClientType::PLUGINVM) {
content::GetUIThreadTaskRunner({})->PostTask( content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, FROM_HERE,
base::BindOnce(&VmCameraMicManager::SetActive, base::Unretained(this), base::BindOnce(&VmCameraMicManager::UpdateVmInfoAndNotifications,
VmType::kPluginVm, DeviceType::kCamera, is_active)); base::Unretained(this), VmType::kPluginVm,
&VmInfo::SetCameraAccessing, is_active));
}
}
void VmCameraMicManager::OnCameraPrivacySwitchStatusChanged(
cros::mojom::CameraPrivacySwitchState state) {
using cros::mojom::CameraPrivacySwitchState;
bool is_on;
switch (state) {
case CameraPrivacySwitchState::UNKNOWN:
case CameraPrivacySwitchState::OFF:
is_on = false;
break;
case CameraPrivacySwitchState::ON:
is_on = true;
break;
}
DCHECK(!vm_info_map_.empty());
for (auto& vm_and_info : vm_info_map_) {
VmType vm = vm_and_info.first;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&VmCameraMicManager::UpdateVmInfoAndNotifications,
base::Unretained(this), vm,
&VmInfo::SetCameraPrivacyIsOn, is_on));
} }
} }
...@@ -282,6 +320,29 @@ void VmCameraMicManager::CloseNotification(VmType vm, NotificationType type) { ...@@ -282,6 +320,29 @@ void VmCameraMicManager::CloseNotification(VmType vm, NotificationType type) {
GetNotificationId(vm, type)); GetNotificationId(vm, type));
} }
VmCameraMicManager::VmInfo::VmInfo() = default;
VmCameraMicManager::VmInfo::VmInfo(const VmInfo&) = default;
VmCameraMicManager::VmInfo::~VmInfo() = default;
void VmCameraMicManager::VmInfo::SetMicActive(bool active) {
notification_type_.set(static_cast<size_t>(DeviceType::kMic), active);
}
void VmCameraMicManager::VmInfo::SetCameraAccessing(bool accessing) {
camera_accessing_ = accessing;
OnCameraUpdated();
}
void VmCameraMicManager::VmInfo::SetCameraPrivacyIsOn(bool on) {
camera_privacy_is_on_ = on;
OnCameraUpdated();
}
void VmCameraMicManager::VmInfo::OnCameraUpdated() {
notification_type_.set(static_cast<size_t>(DeviceType::kCamera),
camera_accessing_ && !camera_privacy_is_on_);
}
VmCameraMicManager::VmNotificationObserver::VmNotificationObserver() = default; VmCameraMicManager::VmNotificationObserver::VmNotificationObserver() = default;
VmCameraMicManager::VmNotificationObserver::~VmNotificationObserver() = default; VmCameraMicManager::VmNotificationObserver::~VmNotificationObserver() = default;
......
...@@ -30,7 +30,8 @@ namespace chromeos { ...@@ -30,7 +30,8 @@ namespace chromeos {
// to change this if we extend this class to support the browser, in which case // to change this if we extend this class to support the browser, in which case
// we will also need to make the notification ids different for different // we will also need to make the notification ids different for different
// profiles. // profiles.
class VmCameraMicManager : public media::CameraActiveClientObserver { class VmCameraMicManager : public media::CameraActiveClientObserver,
public media::CameraPrivacySwitchObserver {
public: public:
enum class VmType { kCrostiniVm, kPluginVm }; enum class VmType { kCrostiniVm, kPluginVm };
...@@ -58,7 +59,8 @@ class VmCameraMicManager : public media::CameraActiveClientObserver { ...@@ -58,7 +59,8 @@ class VmCameraMicManager : public media::CameraActiveClientObserver {
void AddObserver(Observer* observer); void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer); void RemoveObserver(Observer* observer);
// Return true if any of the VMs is using the device. // Return true if any of the VMs is using the device. Note that if the camera
// privacy switch is on, this always returns false for `kCamera`.
bool IsDeviceActive(DeviceType device) const; bool IsDeviceActive(DeviceType device) const;
// When a VM is using both camera and mic, we only show a single "camera and // When a VM is using both camera and mic, we only show a single "camera and
...@@ -68,14 +70,11 @@ class VmCameraMicManager : public media::CameraActiveClientObserver { ...@@ -68,14 +70,11 @@ class VmCameraMicManager : public media::CameraActiveClientObserver {
// `kCamera` but false for `kMic`. If a "mic only" notification is shown, this // `kCamera` but false for `kMic`. If a "mic only" notification is shown, this
// function returns true for `kMic`. // function returns true for `kMic`.
bool IsNotificationActive(DeviceType device) const; bool IsNotificationActive(DeviceType device) const;
private: private:
friend class VmCameraMicManagerTest; friend class VmCameraMicManagerTest;
using NotificationType = using NotificationType =
std::bitset<static_cast<size_t>(DeviceType::kMaxValue) + 1>; std::bitset<static_cast<size_t>(DeviceType::kMaxValue) + 1>;
using NotificationMap = base::flat_map<VmType, NotificationType>;
static constexpr NotificationType kNoNotification{}; static constexpr NotificationType kNoNotification{};
static constexpr NotificationType kMicNotification{ static constexpr NotificationType kMicNotification{
1 << static_cast<size_t>(DeviceType::kMic)}; 1 << static_cast<size_t>(DeviceType::kMic)};
...@@ -85,6 +84,29 @@ class VmCameraMicManager : public media::CameraActiveClientObserver { ...@@ -85,6 +84,29 @@ class VmCameraMicManager : public media::CameraActiveClientObserver {
(1 << static_cast<size_t>(DeviceType::kMic)) | (1 << static_cast<size_t>(DeviceType::kMic)) |
(1 << static_cast<size_t>(DeviceType::kCamera))}; (1 << static_cast<size_t>(DeviceType::kCamera))};
class VmInfo {
public:
VmInfo();
VmInfo(const VmInfo&);
~VmInfo();
NotificationType notification_type() const { return notification_type_; }
void SetMicActive(bool active);
void SetCameraAccessing(bool accessing);
void SetCameraPrivacyIsOn(bool on);
private:
void OnCameraUpdated();
bool camera_accessing_ = false;
// We don't actually need to store this separately for each VM, but this
// makes code simpler.
bool camera_privacy_is_on_ = false;
NotificationType notification_type_;
};
class VmNotificationObserver : public message_center::NotificationObserver { class VmNotificationObserver : public message_center::NotificationObserver {
public: public:
using OpenSettingsFunction = base::RepeatingCallback<void(Profile*)>; using OpenSettingsFunction = base::RepeatingCallback<void(Profile*)>;
...@@ -110,9 +132,15 @@ class VmCameraMicManager : public media::CameraActiveClientObserver { ...@@ -110,9 +132,15 @@ class VmCameraMicManager : public media::CameraActiveClientObserver {
void OnActiveClientChange(cros::mojom::CameraClientType type, void OnActiveClientChange(cros::mojom::CameraClientType type,
bool is_active) override; bool is_active) override;
// media::CameraPrivacySwitchObserver
void OnCameraPrivacySwitchStatusChanged(
cros::mojom::CameraPrivacySwitchState state) override;
static std::string GetNotificationId(VmType vm, NotificationType type); static std::string GetNotificationId(VmType vm, NotificationType type);
void SetActive(VmType vm, DeviceType device, bool active); void UpdateVmInfoAndNotifications(VmType vm,
void (VmInfo::*updator)(bool),
bool value);
void NotifyActiveChanged(); void NotifyActiveChanged();
void OpenNotification(VmType vm, NotificationType type); void OpenNotification(VmType vm, NotificationType type);
...@@ -121,7 +149,7 @@ class VmCameraMicManager : public media::CameraActiveClientObserver { ...@@ -121,7 +149,7 @@ class VmCameraMicManager : public media::CameraActiveClientObserver {
Profile* primary_profile_ = nullptr; Profile* primary_profile_ = nullptr;
VmNotificationObserver crostini_vm_notification_observer_; VmNotificationObserver crostini_vm_notification_observer_;
VmNotificationObserver plugin_vm_notification_observer_; VmNotificationObserver plugin_vm_notification_observer_;
NotificationMap notification_map_; base::flat_map<VmType, VmInfo> vm_info_map_;
base::RetainingOneShotTimer observer_timer_; base::RetainingOneShotTimer observer_timer_;
base::ObserverList<Observer> observers_; base::ObserverList<Observer> observers_;
......
...@@ -102,6 +102,8 @@ class VmCameraMicManagerTest : public testing::Test { ...@@ -102,6 +102,8 @@ class VmCameraMicManagerTest : public testing::Test {
} }
}; };
using VmInfo = VmCameraMicManager::VmInfo;
VmCameraMicManagerTest() { VmCameraMicManagerTest() {
// Make the profile the primary one. // Make the profile the primary one.
auto mock_user_manager = auto mock_user_manager =
...@@ -127,12 +129,35 @@ class VmCameraMicManagerTest : public testing::Test { ...@@ -127,12 +129,35 @@ class VmCameraMicManagerTest : public testing::Test {
vm_camera_mic_manager_->OnPrimaryUserSessionStarted(&testing_profile_); vm_camera_mic_manager_->OnPrimaryUserSessionStarted(&testing_profile_);
} }
void SetCameraAccessing(VmType vm, bool value) {
vm_camera_mic_manager_->UpdateVmInfoAndNotifications(
vm, &VmInfo::SetCameraAccessing, value);
}
void SetCameraPrivacyIsOn(VmType vm, bool value) {
vm_camera_mic_manager_->UpdateVmInfoAndNotifications(
vm, &VmInfo::SetCameraPrivacyIsOn, value);
}
void SetMicActive(VmType vm, bool value) {
vm_camera_mic_manager_->UpdateVmInfoAndNotifications(
vm, &VmInfo::SetMicActive, value);
}
// Note that camera privacy is always set to off by this function.
void SetActive(const ActiveMap& active_map) { void SetActive(const ActiveMap& active_map) {
for (const auto& vm_and_device_active_map : active_map) { for (const auto& vm_and_device_active_map : active_map) {
VmType vm = vm_and_device_active_map.first;
SetCameraPrivacyIsOn(vm, false);
for (const auto& device_active : vm_and_device_active_map.second) { for (const auto& device_active : vm_and_device_active_map.second) {
vm_camera_mic_manager_->SetActive(vm_and_device_active_map.first, switch (device_active.first) {
device_active.first, case kCamera:
device_active.second); SetCameraAccessing(vm, device_active.second);
break;
case kMic:
SetMicActive(vm, device_active.second);
break;
}
} }
} }
} }
...@@ -149,6 +174,28 @@ class VmCameraMicManagerTest : public testing::Test { ...@@ -149,6 +174,28 @@ class VmCameraMicManagerTest : public testing::Test {
DISALLOW_COPY_AND_ASSIGN(VmCameraMicManagerTest); DISALLOW_COPY_AND_ASSIGN(VmCameraMicManagerTest);
}; };
TEST_F(VmCameraMicManagerTest, CameraPrivacy) {
SetCameraAccessing(kPluginVm, false);
SetCameraPrivacyIsOn(kPluginVm, false);
EXPECT_FALSE(vm_camera_mic_manager_->IsDeviceActive(kCamera));
EXPECT_FALSE(vm_camera_mic_manager_->IsNotificationActive(kCamera));
SetCameraAccessing(kPluginVm, true);
SetCameraPrivacyIsOn(kPluginVm, false);
EXPECT_TRUE(vm_camera_mic_manager_->IsDeviceActive(kCamera));
EXPECT_TRUE(vm_camera_mic_manager_->IsNotificationActive(kCamera));
SetCameraAccessing(kPluginVm, false);
SetCameraPrivacyIsOn(kPluginVm, true);
EXPECT_FALSE(vm_camera_mic_manager_->IsDeviceActive(kCamera));
EXPECT_FALSE(vm_camera_mic_manager_->IsNotificationActive(kCamera));
SetCameraAccessing(kPluginVm, true);
SetCameraPrivacyIsOn(kPluginVm, true);
EXPECT_FALSE(vm_camera_mic_manager_->IsDeviceActive(kCamera));
EXPECT_FALSE(vm_camera_mic_manager_->IsNotificationActive(kCamera));
}
// Test `IsDeviceActive()` and `IsNotificationActive()`. // Test `IsDeviceActive()` and `IsNotificationActive()`.
class VmCameraMicManagerIsActiveTest class VmCameraMicManagerIsActiveTest
: public VmCameraMicManagerTest, : public VmCameraMicManagerTest,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment