0

VC UI: Add nudge for both camera and microphone in use while disabled

Due to some constraint in VideoConferenceManagerAsh, when both
microphone and camera is being accessed when disabled,
`HandleDeviceUsedWhileDisabled` will be called twice for each device.
Thus, we need to wait for both 2 calls and display one nudge for both.

Fixed: b:273570886
Change-Id: Ia271a90ba0dc8e36c7501019e922b0b3667cc56d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4705990
Commit-Queue: Andre Le <leandre@chromium.org>
Reviewed-by: Alex Newcomer <newcomer@chromium.org>
Reviewed-by: James Cook <jamescook@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1174302}
This commit is contained in:
Andre Le
2023-07-24 17:51:46 +00:00
committed by Chromium LUCI CQ
parent aec792867c
commit fcacfc7827
9 changed files with 303 additions and 80 deletions

@ -1629,7 +1629,7 @@ 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."> <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 talking? Your mic is off. Select the mic to turn it on. Are you talking? Your mic is off. Select the mic to turn it on.
</message> </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."> <message name="IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_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> <ph name="APP_NAME">$1<ex>Meet</ex></ph> wants to use your <ph name="DEVICE_NAME">$2<ex>camera</ex></ph>
</message> </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."> <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.">
@ -1662,6 +1662,9 @@ Style notes:
<message name="IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME" desc="Text display for the microphone."> <message name="IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME" desc="Text display for the microphone.">
microphone microphone
</message> </message>
<message name="IDS_ASH_VIDEO_CONFERENCE_CAMERA_MICROPHONE_NAME" desc="Text display for both the camera and microphone.">
camera and microphone
</message>
<message name="VIDEO_CONFERENCE_RETURN_TO_APP_PERIPHERALS_ACCESSIBLE_NAME" desc="Tooltip shown for the return to app button regarding peripherals in the video conference panel."> <message name="VIDEO_CONFERENCE_RETURN_TO_APP_PERIPHERALS_ACCESSIBLE_NAME" desc="Tooltip shown for the return to app button regarding peripherals in the video conference panel.">
<ph name="CAPTURE_MEDIUM">$1<ex>Camera</ex></ph> in use. <ph name="CAPTURE_MEDIUM">$1<ex>Camera</ex></ph> in use.
</message> </message>

@ -0,0 +1 @@
e4271f7390f667a1a8c209f5e488999f4d8fc56d

@ -216,7 +216,8 @@ enum class NudgeCatalogName {
kVideoConferenceTraySpeakOnMuteOptIn = 16, kVideoConferenceTraySpeakOnMuteOptIn = 16,
kVideoConferenceTraySpeakOnMuteOptInConfirmation = 17, kVideoConferenceTraySpeakOnMuteOptInConfirmation = 17,
kScalableIphBubble = 18, kScalableIphBubble = 18,
kMaxValue = kScalableIphBubble kVideoConferenceTrayCameraMicrophoneUseWhileDisabled = 19,
kMaxValue = kVideoConferenceTrayCameraMicrophoneUseWhileDisabled
}; };
// A living catalog that registers toasts. // A living catalog that registers toasts.

@ -66,6 +66,8 @@ constexpr char kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.camera_use_while_hw_disabled"; "video_conference_tray_nudge_ids.camera_use_while_hw_disabled";
constexpr char kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId[] = constexpr char kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.camera_use_while_sw_disabled"; "video_conference_tray_nudge_ids.camera_use_while_sw_disabled";
constexpr char kVideoConferenceTrayBothUseWhileDisabledNudgeId[] =
"video_conference_tray_nudge_ids.camera_microphone_use_while_disabled";
// VC nudge ids vector that is iterated whenever `CloseAllVcNudges()` is // VC nudge ids vector that is iterated whenever `CloseAllVcNudges()` is
// called. Please keep in sync whenever adding/removing/updating a nudge id. // called. Please keep in sync whenever adding/removing/updating a nudge id.
@ -82,6 +84,7 @@ const char* const kNudgeIds[] = {
constexpr int KSpeakOnMuteNotificationCoolDownDuration = 60; constexpr int KSpeakOnMuteNotificationCoolDownDuration = 60;
constexpr auto kRepeatedShowTimerInterval = base::Milliseconds(100); constexpr auto kRepeatedShowTimerInterval = base::Milliseconds(100);
constexpr auto kHandleDeviceUsedWhileDisabledWaitTime = base::Milliseconds(200);
// The max amount of times the "Speak-on-mute opt-in" nudge can show. // The max amount of times the "Speak-on-mute opt-in" nudge can show.
constexpr int kSpeakOnMuteOptInNudgeMaxShownCount = 3; constexpr int kSpeakOnMuteOptInNudgeMaxShownCount = 3;
@ -430,10 +433,15 @@ void VideoConferenceTrayController::OnCameraHWPrivacySwitchStateChanged(
// Attempt recording "Use while disabled" nudge action when camera is unmuted. // Attempt recording "Use while disabled" nudge action when camera is unmuted.
if (!camera_muted_by_hardware_switch_) { if (!camera_muted_by_hardware_switch_) {
AnchoredNudgeManager::Get()->MaybeRecordNudgeAction( auto* nudge_manager = AnchoredNudgeManager::Get();
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTrayCameraUseWhileHWDisabled); NudgeCatalogName::kVideoConferenceTrayCameraUseWhileHWDisabled);
AnchoredNudgeManager::Get()->Cancel( nudge_manager->Cancel(kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId);
kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId);
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTrayCameraMicrophoneUseWhileDisabled);
nudge_manager->Cancel(kVideoConferenceTrayBothUseWhileDisabledNudgeId);
} }
} }
@ -452,10 +460,15 @@ void VideoConferenceTrayController::OnCameraSWPrivacySwitchStateChanged(
// Attempt recording "Use while disabled" nudge action when camera is unmuted. // Attempt recording "Use while disabled" nudge action when camera is unmuted.
if (!camera_muted_by_software_switch_) { if (!camera_muted_by_software_switch_) {
AnchoredNudgeManager::Get()->MaybeRecordNudgeAction( auto* nudge_manager = AnchoredNudgeManager::Get();
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTrayCameraUseWhileSWDisabled); NudgeCatalogName::kVideoConferenceTrayCameraUseWhileSWDisabled);
AnchoredNudgeManager::Get()->Cancel( nudge_manager->Cancel(kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId);
kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId);
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTrayCameraMicrophoneUseWhileDisabled);
nudge_manager->Cancel(kVideoConferenceTrayBothUseWhileDisabledNudgeId);
} }
} }
@ -494,26 +507,30 @@ void VideoConferenceTrayController::OnInputMuteChanged(
// Attempt showing the speak-on-mute opt-in nudge when input is muted. // Attempt showing the speak-on-mute opt-in nudge when input is muted.
MaybeShowSpeakOnMuteOptInNudge(GetVcTrayInActiveWindow()); MaybeShowSpeakOnMuteOptInNudge(GetVcTrayInActiveWindow());
} else { } else {
auto* nudge_manager = AnchoredNudgeManager::Get();
// Cancel speak-on-mute opt-in nudge if one was being shown. // Cancel speak-on-mute opt-in nudge if one was being shown.
AnchoredNudgeManager::Get()->Cancel( nudge_manager->Cancel(kVideoConferenceTraySpeakOnMuteOptInNudgeId);
kVideoConferenceTraySpeakOnMuteOptInNudgeId);
// Attempt recording "Speak-on-mute" nudge action when mic is unmuted. // Attempt recording "Speak-on-mute" nudge action when mic is unmuted.
AnchoredNudgeManager::Get()->MaybeRecordNudgeAction( nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTraySpeakOnMuteDetected); NudgeCatalogName::kVideoConferenceTraySpeakOnMuteDetected);
AnchoredNudgeManager::Get()->Cancel( nudge_manager->Cancel(kVideoConferenceTraySpeakOnMuteDetectedNudgeId);
kVideoConferenceTraySpeakOnMuteDetectedNudgeId);
// Attempt recording "Use while disabled" nudge action when mic is unmuted. // Attempt recording "Use while disabled" nudge action when mic is unmuted.
AnchoredNudgeManager::Get()->MaybeRecordNudgeAction( nudge_manager->MaybeRecordNudgeAction(
microphone_muted_by_hardware_switch_ microphone_muted_by_hardware_switch_
? NudgeCatalogName::kVideoConferenceTrayMicrophoneUseWhileHWDisabled ? NudgeCatalogName::kVideoConferenceTrayMicrophoneUseWhileHWDisabled
: NudgeCatalogName:: : NudgeCatalogName::
kVideoConferenceTrayMicrophoneUseWhileSWDisabled); kVideoConferenceTrayMicrophoneUseWhileSWDisabled);
AnchoredNudgeManager::Get()->Cancel( nudge_manager->Cancel(
microphone_muted_by_hardware_switch_ microphone_muted_by_hardware_switch_
? kVideoConferenceTrayMicrophoneUseWhileHWDisabledNudgeId ? kVideoConferenceTrayMicrophoneUseWhileHWDisabledNudgeId
: kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId); : kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId);
nudge_manager->MaybeRecordNudgeAction(
NudgeCatalogName::kVideoConferenceTrayCameraMicrophoneUseWhileDisabled);
nudge_manager->Cancel(kVideoConferenceTrayBothUseWhileDisabledNudgeId);
} }
} }
@ -677,62 +694,34 @@ bool VideoConferenceTrayController::HasMicrophonePermission() const {
void VideoConferenceTrayController::HandleDeviceUsedWhileDisabled( void VideoConferenceTrayController::HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice device, crosapi::mojom::VideoConferenceMediaDevice device,
const std::u16string& app_name) { const std::u16string& app_name) {
// Do not show "Use while disabled" nudge if another nudge is showing. if (device == crosapi::mojom::VideoConferenceMediaDevice::kUnusedDefault) {
if (IsAnyVcNudgeShown()) {
return; return;
} }
// TODO(b/273570886): Handle the case when both camera and microphone are UsedWhileDisabledNudgeType type = GetUsedWhileDisabledNudgeType(device);
// being used while disabled.
std::u16string device_name; if (!use_while_disabled_signal_waiter_.IsRunning()) {
int text_id; // Cache the type and starts the timer to wait for the signal of the other
NudgeCatalogName catalog_name; // device.
std::string nudge_id; use_while_disabled_nudge_on_wait_ = type;
views::View* anchor_view = nullptr;
switch (device) { use_while_disabled_signal_waiter_.Start(
case crosapi::mojom::VideoConferenceMediaDevice::kMicrophone: FROM_HERE, kHandleDeviceUsedWhileDisabledWaitTime,
device_name = base::BindOnce(
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME); &VideoConferenceTrayController::DisplayUsedWhileDisabledNudge,
if (microphone_muted_by_hardware_switch_) { weak_ptr_factory_.GetWeakPtr(), type, app_name));
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED; return;
nudge_id = kVideoConferenceTrayMicrophoneUseWhileHWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayMicrophoneUseWhileHWDisabled;
} else {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED;
nudge_id = kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayMicrophoneUseWhileSWDisabled;
}
anchor_view = GetVcTrayInActiveWindow()->audio_icon();
break;
case crosapi::mojom::VideoConferenceMediaDevice::kCamera:
device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME);
if (camera_muted_by_hardware_switch_) {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED;
nudge_id = kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayCameraUseWhileHWDisabled;
} else {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED;
nudge_id = kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayCameraUseWhileSWDisabled;
}
anchor_view = GetVcTrayInActiveWindow()->camera_icon();
break;
default:
NOTREACHED();
return;
} }
AnchoredNudgeData nudge_data( if (type == use_while_disabled_nudge_on_wait_) {
nudge_id, catalog_name, return;
l10n_util::GetStringFUTF16(text_id, app_name, device_name), anchor_view); }
nudge_data.anchored_to_shelf = true;
CreateNudgeRequest( use_while_disabled_signal_waiter_.Stop();
std::make_unique<AnchoredNudgeData>(std::move(nudge_data)));
// If we receive the signal for both camera and microphone, display the nudge
// for both.
DisplayUsedWhileDisabledNudge(UsedWhileDisabledNudgeType::kBoth, app_name);
} }
void VideoConferenceTrayController::UpdateCameraIcons() { void VideoConferenceTrayController::UpdateCameraIcons() {
@ -797,4 +786,94 @@ void VideoConferenceTrayController::RecordRepeatedShows() {
count_repeated_shows_ = 0; count_repeated_shows_ = 0;
} }
void VideoConferenceTrayController::DisplayUsedWhileDisabledNudge(
VideoConferenceTrayController::UsedWhileDisabledNudgeType type,
const std::u16string& app_name) {
// Do not show "Use while disabled" nudge if another nudge is showing.
if (IsAnyVcNudgeShown()) {
return;
}
std::u16string device_name;
int text_id;
NudgeCatalogName catalog_name;
std::string nudge_id;
views::View* anchor_view = nullptr;
switch (type) {
case VideoConferenceTrayController::UsedWhileDisabledNudgeType::kMicrophone:
device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME);
if (microphone_muted_by_hardware_switch_) {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED;
nudge_id = kVideoConferenceTrayMicrophoneUseWhileHWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayMicrophoneUseWhileHWDisabled;
} else {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED;
nudge_id = kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayMicrophoneUseWhileSWDisabled;
}
anchor_view = GetVcTrayInActiveWindow()->audio_icon();
break;
case VideoConferenceTrayController::UsedWhileDisabledNudgeType::kCamera:
device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME);
if (camera_muted_by_hardware_switch_) {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED;
nudge_id = kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayCameraUseWhileHWDisabled;
} else {
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED;
nudge_id = kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId;
catalog_name =
NudgeCatalogName::kVideoConferenceTrayCameraUseWhileSWDisabled;
}
anchor_view = GetVcTrayInActiveWindow()->camera_icon();
break;
case VideoConferenceTrayController::UsedWhileDisabledNudgeType::kBoth:
device_name = l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_CAMERA_MICROPHONE_NAME);
text_id = IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED;
nudge_id = kVideoConferenceTrayBothUseWhileDisabledNudgeId;
catalog_name = NudgeCatalogName::
kVideoConferenceTrayCameraMicrophoneUseWhileDisabled;
anchor_view = GetVcTrayInActiveWindow()->audio_icon();
break;
default:
NOTREACHED();
return;
}
AnchoredNudgeData nudge_data(
nudge_id, catalog_name,
l10n_util::GetStringFUTF16(text_id, app_name, device_name), anchor_view);
nudge_data.anchored_to_shelf = true;
CreateNudgeRequest(
std::make_unique<AnchoredNudgeData>(std::move(nudge_data)));
}
VideoConferenceTrayController::UsedWhileDisabledNudgeType
VideoConferenceTrayController::GetUsedWhileDisabledNudgeType(
crosapi::mojom::VideoConferenceMediaDevice device) {
DCHECK_NE(device, crosapi::mojom::VideoConferenceMediaDevice::kUnusedDefault);
VideoConferenceTrayController::UsedWhileDisabledNudgeType type;
switch (device) {
case crosapi::mojom::VideoConferenceMediaDevice::kCamera:
type = VideoConferenceTrayController::UsedWhileDisabledNudgeType::kCamera;
break;
case crosapi::mojom::VideoConferenceMediaDevice::kMicrophone:
type = VideoConferenceTrayController::UsedWhileDisabledNudgeType::
kMicrophone;
break;
default:
NOTREACHED();
type = VideoConferenceTrayController::UsedWhileDisabledNudgeType::kCamera;
}
return type;
}
} // namespace ash } // namespace ash

@ -201,6 +201,14 @@ class ASH_EXPORT VideoConferenceTrayController
bool initialized() const { return initialized_; } bool initialized() const { return initialized_; }
private: private:
// All the types of the use while disabled nudge.
enum class UsedWhileDisabledNudgeType {
kCamera = 0,
kMicrophone = 1,
kBoth = 2,
kMaxValue = kBoth
};
// Updates the state of the camera icons across all `VideoConferenceTray`. // Updates the state of the camera icons across all `VideoConferenceTray`.
void UpdateCameraIcons(); void UpdateCameraIcons();
@ -214,6 +222,13 @@ class ASH_EXPORT VideoConferenceTrayController
// Returns true if any of the VC nudges are visible on screen. // Returns true if any of the VC nudges are visible on screen.
bool IsAnyVcNudgeShown(); bool IsAnyVcNudgeShown();
// Displays the use while disabled nudge according to the given `type`.
void DisplayUsedWhileDisabledNudge(UsedWhileDisabledNudgeType type,
const std::u16string& app_name);
UsedWhileDisabledNudgeType GetUsedWhileDisabledNudgeType(
crosapi::mojom::VideoConferenceMediaDevice device);
// The number of capturing apps, fetched from `VideoConferenceManagerAsh`. // The number of capturing apps, fetched from `VideoConferenceManagerAsh`.
int capturing_apps_ = 0; int capturing_apps_ = 0;
@ -268,6 +283,14 @@ class ASH_EXPORT VideoConferenceTrayController
int count_repeated_shows_ = 0; int count_repeated_shows_ = 0;
base::DelayTimer repeated_shows_timer_; base::DelayTimer repeated_shows_timer_;
// Due to some constraint in `VideoConferenceManagerAsh`, when both microphone
// and camera is being accessed when disabled,`HandleDeviceUsedWhileDisabled`
// will be called twice for each device. Thus, we need to wait for both 2
// calls and display one nudge for both. These are the timer and the cache
// type to make that happen.
base::OneShotTimer use_while_disabled_signal_waiter_;
UsedWhileDisabledNudgeType use_while_disabled_nudge_on_wait_;
// The contents of a nudge data object that is cached so it can be shown once // The contents of a nudge data object that is cached so it can be shown once
// the tray has fully animated in. // the tray has fully animated in.
std::unique_ptr<AnchoredNudgeData> requested_nudge_data_; std::unique_ptr<AnchoredNudgeData> requested_nudge_data_;

@ -21,6 +21,7 @@
#include "base/test/metrics/histogram_tester.h" #include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h" #include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h" #include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/crosapi/mojom/video_conference.mojom.h" #include "chromeos/crosapi/mojom/video_conference.mojom.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
@ -51,10 +52,14 @@ constexpr char kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.camera_use_while_hw_disabled"; "video_conference_tray_nudge_ids.camera_use_while_hw_disabled";
constexpr char kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId[] = constexpr char kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.camera_use_while_sw_disabled"; "video_conference_tray_nudge_ids.camera_use_while_sw_disabled";
constexpr char kVideoConferenceTrayBothUseWhileDisabledNudgeId[] =
"video_conference_tray_nudge_ids.camera_microphone_use_while_disabled";
constexpr char kRepeatedShowsHistogramName[] = constexpr char kRepeatedShowsHistogramName[] =
"Ash.VideoConference.NumberOfRepeatedShows"; "Ash.VideoConference.NumberOfRepeatedShows";
constexpr auto kHandleDeviceUsedWhileDisabledWaitTime = base::Milliseconds(200);
bool IsNudgeShown(const std::string& id) { bool IsNudgeShown(const std::string& id) {
return Shell::Get()->anchored_nudge_manager()->IsNudgeShown(id); return Shell::Get()->anchored_nudge_manager()->IsNudgeShown(id);
} }
@ -261,14 +266,15 @@ TEST_F(VideoConferenceTrayControllerTest,
controller()->HandleDeviceUsedWhileDisabled( controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name); crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
// Nudge should be displayed. Showing that app is accessing while camera is // After `kHandleDeviceUsedWhileDisabledWaitTime`, nudge should be displayed.
// software-muted. // Showing that app is accessing while camera is software-muted.
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
ASSERT_TRUE(IsNudgeShown(nudge_id)); ASSERT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(GetNudgeAnchorView(nudge_id), camera_icon()); EXPECT_EQ(GetNudgeAnchorView(nudge_id), camera_icon());
EXPECT_EQ(GetNudgeText(nudge_id), EXPECT_EQ(GetNudgeText(nudge_id),
l10n_util::GetStringFUTF16( l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED, IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED, app_name,
app_name, camera_device_name)); camera_device_name));
// Unmute camera through SW. Nudge should be dismissed. // Unmute camera through SW. Nudge should be dismissed.
controller()->OnCameraSWPrivacySwitchStateChanged( controller()->OnCameraSWPrivacySwitchStateChanged(
@ -294,14 +300,15 @@ TEST_F(VideoConferenceTrayControllerTest,
controller()->HandleDeviceUsedWhileDisabled( controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name); crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
// Nudge should be displayed. Showing that app is accessing while microphone // After `kHandleDeviceUsedWhileDisabledWaitTime`, nudge should be displayed.
// is software-muted. // Showing that app is accessing while microphone is software-muted.
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
ASSERT_TRUE(IsNudgeShown(nudge_id)); ASSERT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(GetNudgeAnchorView(nudge_id), audio_icon()); EXPECT_EQ(GetNudgeAnchorView(nudge_id), audio_icon());
EXPECT_EQ(GetNudgeText(nudge_id), EXPECT_EQ(GetNudgeText(nudge_id),
l10n_util::GetStringFUTF16( l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED, IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED, app_name,
app_name, microphone_device_name)); microphone_device_name));
// Unmute microphone through SW. Nudge should be dismissed. // Unmute microphone through SW. Nudge should be dismissed.
controller()->OnInputMuteChanged( controller()->OnInputMuteChanged(
@ -327,8 +334,9 @@ TEST_F(VideoConferenceTrayControllerTest,
controller()->HandleDeviceUsedWhileDisabled( controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name); crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
// Nudge should be displayed. Showing that app is accessing while camera is // After `kHandleDeviceUsedWhileDisabledWaitTime`, nudge should be displayed.
// hardware-muted. // Showing that app is accessing while camera is hardware-muted.
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
ASSERT_TRUE(IsNudgeShown(nudge_id)); ASSERT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(GetNudgeAnchorView(nudge_id), camera_icon()); EXPECT_EQ(GetNudgeAnchorView(nudge_id), camera_icon());
EXPECT_EQ(GetNudgeText(nudge_id), EXPECT_EQ(GetNudgeText(nudge_id),
@ -361,8 +369,9 @@ TEST_F(VideoConferenceTrayControllerTest,
controller()->HandleDeviceUsedWhileDisabled( controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name); crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
// Nudge should be displayed. Showing that app is accessing while microphone // After `kHandleDeviceUsedWhileDisabledWaitTime`, nudge should be displayed.
// is hardware-muted. // Showing that app is accessing while microphone is hardware-muted.
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
ASSERT_TRUE(IsNudgeShown(nudge_id)); ASSERT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(GetNudgeAnchorView(nudge_id), audio_icon()); EXPECT_EQ(GetNudgeAnchorView(nudge_id), audio_icon());
EXPECT_EQ(GetNudgeText(nudge_id), EXPECT_EQ(GetNudgeText(nudge_id),
@ -377,6 +386,110 @@ TEST_F(VideoConferenceTrayControllerTest,
EXPECT_FALSE(IsNudgeShown(nudge_id)); EXPECT_FALSE(IsNudgeShown(nudge_id));
} }
TEST_F(VideoConferenceTrayControllerTest,
HandleCameraMicrophoneUsedWhileDisabled) {
auto* app_name = u"app_name";
auto device_name = l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_CAMERA_MICROPHONE_NAME);
auto* nudge_id = kVideoConferenceTrayBothUseWhileDisabledNudgeId;
SetTrayAndButtonsVisible();
controller()->OnInputMuteChanged(
/*mute_on=*/true, CrasAudioHandler::InputMuteChangeMethod::kOther);
controller()->OnCameraHWPrivacySwitchStateChanged(
/*device_id=*/"device_id", cros::mojom::CameraPrivacySwitchState::ON);
// No nudge is shown before `HandleDeviceUsedWhileDisabled()` is called.
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
task_environment()->FastForwardBy(base::Milliseconds(20));
// No nudge is shown yet since we are waiting for more signal for
// `HandleDeviceUsedWhileDisabled`.
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
// Nudge should be displayed when receiving signal for both camera and
// microphone.
ASSERT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(GetNudgeAnchorView(nudge_id), audio_icon());
EXPECT_EQ(GetNudgeText(nudge_id),
l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED, app_name,
device_name));
}
TEST_F(VideoConferenceTrayControllerTest,
UnmuteCameraWithCameraMicrophoneUsedWhileDisabledNudge) {
auto* app_name = u"app_name";
auto* nudge_id = kVideoConferenceTrayBothUseWhileDisabledNudgeId;
SetTrayAndButtonsVisible();
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
ASSERT_TRUE(IsNudgeShown(nudge_id));
// Now unmute camera. Nudge should also be dismissed.
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::OFF);
EXPECT_FALSE(IsNudgeShown(nudge_id));
// Test the same thing for hw-unmuting camera.
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
ASSERT_TRUE(IsNudgeShown(nudge_id));
controller()->OnCameraHWPrivacySwitchStateChanged(
/*device_id=*/"device_id", cros::mojom::CameraPrivacySwitchState::OFF);
EXPECT_FALSE(IsNudgeShown(nudge_id));
}
TEST_F(VideoConferenceTrayControllerTest,
UnmuteMicrophoneWithCameraMicrophoneUsedWhileDisabledNudge) {
auto* app_name = u"app_name";
auto* nudge_id = kVideoConferenceTrayBothUseWhileDisabledNudgeId;
SetTrayAndButtonsVisible();
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
ASSERT_TRUE(IsNudgeShown(nudge_id));
// Now unmute microphone. Nudge should also be dismissed.
controller()->OnInputMuteChanged(
/*mute_on=*/false, CrasAudioHandler::InputMuteChangeMethod::kOther);
EXPECT_FALSE(IsNudgeShown(nudge_id));
// Test the same thing for hw-unmuting microphone.
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
ASSERT_TRUE(IsNudgeShown(nudge_id));
controller()->OnInputMuteChanged(
/*mute_on=*/false,
CrasAudioHandler::InputMuteChangeMethod::kPhysicalShutter);
EXPECT_FALSE(IsNudgeShown(nudge_id));
}
TEST_F(VideoConferenceTrayControllerTest, SpeakOnMuteNudge) { TEST_F(VideoConferenceTrayControllerTest, SpeakOnMuteNudge) {
auto* nudge_id = kVideoConferenceTraySpeakOnMuteDetectedNudgeId; auto* nudge_id = kVideoConferenceTraySpeakOnMuteDetectedNudgeId;
@ -673,6 +786,7 @@ TEST_F(VideoConferenceTrayControllerTest, NudgeBlocksOtherNudges) {
/*device_id=*/"device_id", cros::mojom::CameraPrivacySwitchState::ON); /*device_id=*/"device_id", cros::mojom::CameraPrivacySwitchState::ON);
controller()->HandleDeviceUsedWhileDisabled( controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name); crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
EXPECT_TRUE(IsNudgeShown(use_while_disabled_nudge_id)); EXPECT_TRUE(IsNudgeShown(use_while_disabled_nudge_id));
// Show opt-in nudge by muting the microphone, "use while disabled" nudge // Show opt-in nudge by muting the microphone, "use while disabled" nudge

@ -681,7 +681,7 @@ IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest, UseWhileDisabled) {
EXPECT_EQ( EXPECT_EQ(
GetNudgeText(microphone_nudge_id), GetNudgeText(microphone_nudge_id),
l10n_util::GetStringFUTF16( l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED, kTitle1, IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED, kTitle1,
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME))); l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME)));
EXPECT_EQ(GetNudgeAnchorView(microphone_nudge_id), GetVcTray()->audio_icon()); EXPECT_EQ(GetNudgeAnchorView(microphone_nudge_id), GetVcTray()->audio_icon());
@ -703,7 +703,7 @@ IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest, UseWhileDisabled) {
EXPECT_EQ( EXPECT_EQ(
GetNudgeText(camera_nudge_id), GetNudgeText(camera_nudge_id),
l10n_util::GetStringFUTF16( l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_SOFTWARE_DISABLED, kTitle1, IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED, kTitle1,
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME))); l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME)));
EXPECT_EQ(GetNudgeAnchorView(camera_nudge_id), GetVcTray()->camera_icon()); EXPECT_EQ(GetNudgeAnchorView(camera_nudge_id), GetVcTray()->camera_icon());
} }

@ -77094,6 +77094,8 @@ Called by update_net_trust_anchors.py.-->
<int value="17" <int value="17"
label="Video Conference Tray Speak On Mute Opt In Confirmation"/> label="Video Conference Tray Speak On Mute Opt In Confirmation"/>
<int value="18" label="Scalable IPH Bubble"/> <int value="18" label="Scalable IPH Bubble"/>
<int value="19"
label="Video Conference Tray Camera And Microphone Use While Disabled"/>
</enum> </enum>
<enum name="NukeProfileResult"> <enum name="NukeProfileResult">