0

VC UI: Add toast when app is accessing camera/mic while disabled

Handle for all the cases of camera/microphone being software/hardware
muted.

Fixed: b:253284704
Change-Id: I25a6dc7e88892ab3ba0eb31dc508a96e8cea1676
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4168833
Reviewed-by: Alex Newcomer <newcomer@chromium.org>
Commit-Queue: Andre Le <leandre@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1117236}
This commit is contained in:
Andre Le
2023-03-14 22:23:38 +00:00
committed by Chromium LUCI CQ
parent a8f59d9947
commit 13988e9ef1
12 changed files with 214 additions and 2 deletions

@ -1499,6 +1499,18 @@ Style notes:
<message name="IDS_ASH_VIDEO_CONFERENCE_TOAST_SPEAK_ON_MUTE_DETECTED" desc="A toast message that we show when user tries to speak while muted on system-level.">
Are you speaking? You are on mute. Learn more
</message>
<message name="IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED" desc="A toast message that we show when an app tries to access camera or microphone while it is disabled by software.">
<ph name="APP_NAME">$1<ex>Meet</ex></ph> wants to use your <ph name="DEVICE_NAME">$2<ex>camera</ex></ph>
</message>
<message name="IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED" desc="A toast message that we show when an app tries to access camera or microphone while it is disabled by hardware.">
<ph name="APP_NAME">$1<ex>Meet</ex></ph> wants to use the <ph name="DEVICE_NAME">$2<ex>camera</ex></ph>. Turn on your device's physical <ph name="DEVICE_NAME">$2<ex>camera</ex></ph> switch.
</message>
<message name="IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME" desc="Text display for the camera.">
camera
</message>
<message name="IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME" desc="Text display for the microphone.">
microphone
</message>
<!-- Phone Hub tray-->
<message name="IDS_ASH_PHONE_HUB_TRAY_ACCESSIBLE_NAME" desc="The accessible name of the Phone Hub tray bubble for screen readers.">

@ -0,0 +1 @@
1912b0d0ccda35ce873534b075d362cca01dd65b

@ -0,0 +1 @@
3782095551679f986df3db06ba1b82b06ae5e4bb

@ -0,0 +1 @@
72d94e3c13edb72c87fe8f42b3e93ea0e9699353

@ -0,0 +1 @@
48a3c5219668e53b2d2acf454fd14ebd164063f2

@ -252,7 +252,8 @@ enum class ToastCatalogName {
kCopyToClipboardAction = 40,
kVideoConferenceTraySpeakOnMuteDetected = 41,
kCopyGifToClipboardAction = 42,
kMaxValue = kCopyGifToClipboardAction,
kVideoConferenceTrayUseWhileDisabled = 43,
kMaxValue = kVideoConferenceTrayUseWhileDisabled,
};
} // namespace ash

@ -228,6 +228,11 @@ void ToastManagerImpl::OnSessionStateChanged(
}
}
const ToastData& ToastManagerImpl::GetCurrentToastDataForTesting() const {
DCHECK(current_toast_data_);
return current_toast_data_.value();
}
void ToastManagerImpl::ShowLatest() {
DCHECK(!HasActiveToasts());
DCHECK(!current_toast_data_);

@ -57,6 +57,8 @@ class ASH_EXPORT ToastManagerImpl : public ToastManager,
// SessionObserver:
void OnSessionStateChanged(session_manager::SessionState state) override;
const ToastData& GetCurrentToastDataForTesting() const;
private:
class PausableTimer;
friend class AutoConnectNotifierTest;

@ -88,6 +88,8 @@ void FakeVideoConferenceTrayController::ReturnToApp(
void FakeVideoConferenceTrayController::HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice device,
const std::u16string& app_name) {
VideoConferenceTrayController::HandleDeviceUsedWhileDisabled(device,
app_name);
device_used_while_disabled_records_.emplace_back(device, app_name);
}

@ -4,6 +4,8 @@
#include "ash/system/video_conference/video_conference_tray_controller.h"
#include <string>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/notifier_catalogs.h"
@ -17,6 +19,7 @@
#include "ash/system/status_area_widget.h"
#include "ash/system/video_conference/video_conference_common.h"
#include "ash/system/video_conference/video_conference_tray.h"
#include "base/notreached.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
#include "components/prefs/pref_service.h"
@ -32,6 +35,10 @@ namespace {
constexpr char kVideoConferenceTraySpeakOnMuteDetectedId[] =
"video_conference_tray_toast_ids.speak_on_mute_detected";
// The ID for the "use while disabled" toast.
constexpr char kVideoConferenceTrayUseWhileDisabledToastId[] =
"video_conference_tray_toast_ids.use_while_disable";
// The cool down duration for speak-on-mute detection notification in seconds.
constexpr int KSpeakOnMuteNotificationCoolDownDuration = 60;
@ -193,6 +200,12 @@ void VideoConferenceTrayController::OnCameraHWPrivacySwitchStateChanged(
state == cros::mojom::CameraPrivacySwitchState::ON;
UpdateCameraIcons();
if (video_conference_manager_) {
video_conference_manager_->SetSystemMediaDeviceStatus(
crosapi::mojom::VideoConferenceMediaDevice::kCamera,
/*disabled=*/GetCameraMuted());
}
}
void VideoConferenceTrayController::OnCameraSWPrivacySwitchStateChanged(
@ -201,6 +214,12 @@ void VideoConferenceTrayController::OnCameraSWPrivacySwitchStateChanged(
state == cros::mojom::CameraPrivacySwitchState::ON;
UpdateCameraIcons();
if (video_conference_manager_) {
video_conference_manager_->SetSystemMediaDeviceStatus(
crosapi::mojom::VideoConferenceMediaDevice::kCamera,
/*disabled=*/GetCameraMuted());
}
}
void VideoConferenceTrayController::OnInputMuteChanged(
@ -218,9 +237,19 @@ void VideoConferenceTrayController::OnInputMuteChanged(
audio_icon->SetToggled(mute_on);
audio_icon->UpdateCapturingState();
}
if (video_conference_manager_) {
video_conference_manager_->SetSystemMediaDeviceStatus(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone,
/*disabled=*/mute_on);
}
microphone_muted_by_hardware_switch_ =
method == CrasAudioHandler::InputMuteChangeMethod::kPhysicalShutter;
}
void VideoConferenceTrayController::OnSpeakOnMuteDetected() {
// TODO(b/273374112): Add unit test for this toast.
const base::TimeTicks current_time = base::TimeTicks::Now();
if (!last_speak_on_mute_notification_time_.has_value() ||
@ -291,7 +320,40 @@ bool VideoConferenceTrayController::HasMicrophonePermission() const {
void VideoConferenceTrayController::HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice device,
const std::u16string& app_name) {
// TODO(b/249828245): Implement logic to handle this.
// TODO(b/273570886): Handle the case when both camera and microphone are
// being used while disabled.
std::u16string device_name;
int toast_text_id;
switch (device) {
case crosapi::mojom::VideoConferenceMediaDevice::kMicrophone:
device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME);
toast_text_id =
microphone_muted_by_hardware_switch_
? IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED
: IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED;
break;
case crosapi::mojom::VideoConferenceMediaDevice::kCamera:
device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME);
toast_text_id =
camera_muted_by_hardware_switch_
? IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED
: IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED;
break;
default:
NOTREACHED();
return;
}
ToastData toast_data(
kVideoConferenceTrayUseWhileDisabledToastId,
ToastCatalogName::kVideoConferenceTrayUseWhileDisabled,
l10n_util::GetStringFUTF16(toast_text_id, app_name, device_name),
ToastData::kDefaultToastDuration,
/*visible_on_lock_screen=*/false);
toast_data.show_on_all_root_windows = true;
ToastManager::Get()->Show(std::move(toast_data));
}
void VideoConferenceTrayController::UpdateCameraIcons() {

@ -170,6 +170,11 @@ class ASH_EXPORT VideoConferenceTrayController
bool camera_muted_by_hardware_switch_ = false;
bool camera_muted_by_software_switch_ = false;
// True if microphone is muted by the hardware switch, false if the microphone
// is muted through software. If the microphone is not muted, disregards this
// value.
bool microphone_muted_by_hardware_switch_ = false;
// Used by the views to construct and lay out effects in the bubble.
VideoConferenceTrayEffectsManager effects_manager_;

@ -6,8 +6,11 @@
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/system/toast/toast_manager_impl.h"
#include "ash/system/video_conference/fake_video_conference_tray_controller.h"
#include "ash/system/video_conference/video_conference_common.h"
#include "ash/system/video_conference/video_conference_tray.h"
@ -15,10 +18,17 @@
#include "base/command_line.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
#include "media/capture/video/chromeos/mojom/cros_camera_service.mojom-shared.h"
#include "ui/base/l10n/l10n_util.h"
namespace ash {
namespace {
constexpr char kVideoConferenceTrayUseWhileDisabledToastId[] =
"video_conference_tray_toast_ids.use_while_disable";
} // namespace
class VideoConferenceTrayControllerTest : public AshTestBase {
public:
VideoConferenceTrayControllerTest() = default;
@ -152,4 +162,113 @@ TEST_F(VideoConferenceTrayControllerTest, ClickCameraWhenHardwareMuted) {
EXPECT_TRUE(camera_icon()->toggled());
}
TEST_F(VideoConferenceTrayControllerTest,
HandleCameraUsedWhileSoftwaredDisabled) {
auto* toast_manager = Shell::Get()->toast_manager();
auto* app_name = u"app_name";
auto camera_device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME);
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::ON);
// No toast show be shown before `HandleDeviceUsedWhileDisabled()` is called.
EXPECT_FALSE(
toast_manager->IsRunning(kVideoConferenceTrayUseWhileDisabledToastId));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
// Toast should be displayed. Showing that app is accessing while camera is
// software-muted.
EXPECT_TRUE(
toast_manager->IsRunning(kVideoConferenceTrayUseWhileDisabledToastId));
EXPECT_EQ(l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED,
app_name, camera_device_name),
toast_manager->GetCurrentToastDataForTesting().text);
}
TEST_F(VideoConferenceTrayControllerTest,
HandleMicrophoneUsedWhileSoftwaredDisabled) {
auto* toast_manager = Shell::Get()->toast_manager();
auto* app_name = u"app_name";
auto microphone_device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME);
controller()->OnInputMuteChanged(
/*mute_on=*/true, CrasAudioHandler::InputMuteChangeMethod::kOther);
// No toast show be shown before `HandleDeviceUsedWhileDisabled()` is called.
EXPECT_FALSE(
toast_manager->IsRunning(kVideoConferenceTrayUseWhileDisabledToastId));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
// Toast should be displayed. Showing that app is accessing while microphone
// is software-muted.
EXPECT_TRUE(
toast_manager->IsRunning(kVideoConferenceTrayUseWhileDisabledToastId));
EXPECT_EQ(l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED,
app_name, microphone_device_name),
toast_manager->GetCurrentToastDataForTesting().text);
}
TEST_F(VideoConferenceTrayControllerTest,
HandleCameraUsedWhileHardwaredDisabled) {
auto* toast_manager = Shell::Get()->toast_manager();
auto* app_name = u"app_name";
auto camera_device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME);
controller()->OnCameraHWPrivacySwitchStateChanged(
/*device_id=*/"device_id", cros::mojom::CameraPrivacySwitchState::ON);
// No toast show be shown before `HandleDeviceUsedWhileDisabled()` is called.
EXPECT_FALSE(
toast_manager->IsRunning(kVideoConferenceTrayUseWhileDisabledToastId));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
// Toast should be displayed. Showing that app is accessing while camera is
// hardware-muted.
EXPECT_TRUE(
toast_manager->IsRunning(kVideoConferenceTrayUseWhileDisabledToastId));
EXPECT_EQ(l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED,
app_name, camera_device_name),
toast_manager->GetCurrentToastDataForTesting().text);
}
TEST_F(VideoConferenceTrayControllerTest,
HandleMicrophoneUsedWhileHardwaredDisabled) {
auto* toast_manager = Shell::Get()->toast_manager();
auto* app_name = u"app_name";
auto microphone_device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME);
controller()->OnInputMuteChanged(
/*mute_on=*/true,
CrasAudioHandler::InputMuteChangeMethod::kPhysicalShutter);
// No toast show be shown before `HandleDeviceUsedWhileDisabled()` is called.
EXPECT_FALSE(
toast_manager->IsRunning(kVideoConferenceTrayUseWhileDisabledToastId));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
// Toast should be displayed. Showing that app is accessing while microphone
// is hardware-muted.
EXPECT_TRUE(
toast_manager->IsRunning(kVideoConferenceTrayUseWhileDisabledToastId));
EXPECT_EQ(l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED,
app_name, microphone_device_name),
toast_manager->GetCurrentToastDataForTesting().text);
}
} // namespace ash