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:

committed by
Chromium LUCI CQ

parent
a8f59d9947
commit
13988e9ef1
@ -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
|
1
ash/ash_strings_grd/IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED.png.sha1
Normal file
1
ash/ash_strings_grd/IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED.png.sha1
Normal file
@ -0,0 +1 @@
|
||||
72d94e3c13edb72c87fe8f42b3e93ea0e9699353
|
1
ash/ash_strings_grd/IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED.png.sha1
Normal file
1
ash/ash_strings_grd/IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED.png.sha1
Normal file
@ -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
|
Reference in New Issue
Block a user