#include "ash/system/video_conference/video_conference_tray_controller.h"
#include <string_view>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/test/test_system_tray_client.h"
#include "ash/session/session_controller_impl.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/anchored_nudge_manager_impl.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"
#include "ash/test/ash_test_base.h"
#include "base/command_line.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
#include "components/prefs/pref_service.h"
#include "media/capture/video/chromeos/mojom/cros_camera_service.mojom-shared.h"
#include "ui/base/l10n/l10n_util.h"
namespace ash {
namespace {
constexpr int kSpeakOnMuteOptInNudgeMaxShownCount = 3;
constexpr char kVideoConferenceTraySpeakOnMuteDetectedNudgeId[] =
"video_conference_tray_nudge_ids.speak_on_mute_detected";
constexpr char kVideoConferenceTraySpeakOnMuteOptInNudgeId[] =
"video_conference_tray_nudge_ids.speak_on_mute_opt_in";
constexpr char kVideoConferenceTraySpeakOnMuteOptInConfirmationToastId[] =
"video_conference_tray_toast_ids.speak_on_mute_opt_in_confirmation";
constexpr char kVideoConferenceTrayMicrophoneUseWhileHWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.microphone_use_while_hw_disabled";
constexpr char kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.microphone_use_while_sw_disabled";
constexpr char kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId[] =
"video_conference_tray_nudge_ids.camera_use_while_hw_disabled";
constexpr char kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId[] =
"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[] =
"Ash.VideoConference.NumberOfRepeatedShows";
constexpr auto kHandleDeviceUsedWhileDisabledWaitTime = base::Milliseconds(200);
bool IsNudgeShown(const std::string& id) {
return Shell::Get()->anchored_nudge_manager()->IsNudgeShown(id);
}
std::u16string_view GetNudgeText(const std::string& id) {
return Shell::Get()->anchored_nudge_manager()->GetNudgeBodyTextForTest(id);
}
views::View* GetNudgeAnchorView(const std::string& id) {
return Shell::Get()->anchored_nudge_manager()->GetNudgeAnchorViewForTest(id);
}
views::LabelButton* GetNudgePrimaryButton(const std::string& id) {
return Shell::Get()->anchored_nudge_manager()->GetNudgePrimaryButtonForTest(
id);
}
views::LabelButton* GetNudgeSecondaryButton(const std::string& id) {
return Shell::Get()->anchored_nudge_manager()->GetNudgeSecondaryButtonForTest(
id);
}
AnchoredNudge* GetShownNudge(const std::string& id) {
return Shell::Get()->anchored_nudge_manager()->GetShownNudgeForTest(id);
}
bool IsToastShown(const std::string& id) {
return Shell::Get()->toast_manager()->IsToastShown(id);
}
}
class VideoConferenceTrayControllerTest : public AshTestBase {
public:
VideoConferenceTrayControllerTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
VideoConferenceTrayControllerTest(const VideoConferenceTrayControllerTest&) =
delete;
VideoConferenceTrayControllerTest& operator=(
const VideoConferenceTrayControllerTest&) = delete;
~VideoConferenceTrayControllerTest() override = default;
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
features::kFeatureManagementVideoConference);
controller_ = std::make_unique<FakeVideoConferenceTrayController>();
AshTestBase::SetUp();
}
void TearDown() override {
AshTestBase::TearDown();
controller_.reset();
}
VideoConferenceTray* video_conference_tray() {
return StatusAreaWidgetTestHelper::GetStatusAreaWidget()
->video_conference_tray();
}
VideoConferenceTrayButton* camera_icon() {
return video_conference_tray()->camera_icon();
}
VideoConferenceTrayButton* audio_icon() {
return video_conference_tray()->audio_icon();
}
VideoConferenceMediaState SetTrayAndButtonsVisible() {
VideoConferenceMediaState state;
state.has_media_app = true;
state.has_camera_permission = true;
state.has_microphone_permission = true;
state.is_capturing_screen = true;
state.is_capturing_microphone = true;
controller()->UpdateWithMediaState(state);
return state;
}
VideoConferenceMediaState SetTrayAndButtonsInvisible() {
VideoConferenceMediaState state;
controller()->UpdateWithMediaState(state);
return state;
}
void ToggleVcTrayBubble() {
LeftClickOn(video_conference_tray()->toggle_bubble_button_);
}
FakeVideoConferenceTrayController* controller() { return controller_.get(); }
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<FakeVideoConferenceTrayController> controller_;
};
TEST_F(VideoConferenceTrayControllerTest, UpdateButtonWhenCameraMuted) {
EXPECT_FALSE(camera_icon()->toggled());
EXPECT_FALSE(camera_icon()->show_privacy_indicator());
VideoConferenceMediaState state;
state.is_capturing_camera = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(camera_icon()->show_privacy_indicator());
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::ON);
EXPECT_TRUE(camera_icon()->toggled());
EXPECT_FALSE(camera_icon()->show_privacy_indicator());
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::OFF);
EXPECT_FALSE(camera_icon()->toggled());
EXPECT_TRUE(camera_icon()->show_privacy_indicator());
}
TEST_F(VideoConferenceTrayControllerTest, UpdateButtonWhenMicrophoneMuted) {
EXPECT_FALSE(audio_icon()->toggled());
EXPECT_FALSE(audio_icon()->show_privacy_indicator());
VideoConferenceMediaState state;
state.is_capturing_microphone = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(audio_icon()->show_privacy_indicator());
controller()->OnInputMuteChanged(
true, CrasAudioHandler::InputMuteChangeMethod::kOther);
EXPECT_TRUE(audio_icon()->toggled());
EXPECT_FALSE(audio_icon()->show_privacy_indicator());
controller()->OnInputMuteChanged(
false, CrasAudioHandler::InputMuteChangeMethod::kOther);
EXPECT_FALSE(audio_icon()->toggled());
EXPECT_TRUE(audio_icon()->show_privacy_indicator());
}
TEST_F(VideoConferenceTrayControllerTest, CameraHardwareMuted) {
controller()->OnCameraHWPrivacySwitchStateChanged(
"device_id", cros::mojom::CameraPrivacySwitchState::ON);
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::ON);
EXPECT_TRUE(camera_icon()->toggled());
controller()->OnCameraHWPrivacySwitchStateChanged(
"device_id", cros::mojom::CameraPrivacySwitchState::ON);
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::OFF);
EXPECT_TRUE(camera_icon()->toggled());
controller()->OnCameraHWPrivacySwitchStateChanged(
"device_id", cros::mojom::CameraPrivacySwitchState::OFF);
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::ON);
EXPECT_TRUE(camera_icon()->toggled());
controller()->OnCameraHWPrivacySwitchStateChanged(
"device_id", cros::mojom::CameraPrivacySwitchState::OFF);
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::OFF);
EXPECT_FALSE(camera_icon()->toggled());
}
TEST_F(VideoConferenceTrayControllerTest, ClickCameraWhenHardwareMuted) {
controller()->OnCameraHWPrivacySwitchStateChanged(
"device_id", cros::mojom::CameraPrivacySwitchState::ON);
EXPECT_TRUE(camera_icon()->toggled());
LeftClickOn(camera_icon());
EXPECT_TRUE(camera_icon()->toggled());
}
TEST_F(VideoConferenceTrayControllerTest,
HandleCameraUsedWhileSoftwaredDisabled) {
auto* app_name = u"app_name";
auto camera_device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME);
auto* nudge_id = kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId;
SetTrayAndButtonsVisible();
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::ON);
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
ASSERT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(GetNudgeAnchorView(nudge_id), camera_icon());
EXPECT_EQ(GetNudgeText(nudge_id),
l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED, app_name,
camera_device_name));
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::OFF);
EXPECT_FALSE(IsNudgeShown(nudge_id));
}
TEST_F(VideoConferenceTrayControllerTest,
HandleMicrophoneUsedWhileSoftwaredDisabled) {
auto* app_name = u"app_name";
auto microphone_device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME);
auto* nudge_id = kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId;
SetTrayAndButtonsVisible();
controller()->OnInputMuteChanged(
true, CrasAudioHandler::InputMuteChangeMethod::kOther);
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
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,
microphone_device_name));
controller()->OnInputMuteChanged(
false, CrasAudioHandler::InputMuteChangeMethod::kOther);
EXPECT_FALSE(IsNudgeShown(nudge_id));
}
TEST_F(VideoConferenceTrayControllerTest,
HandleCameraUsedWhileHardwaredDisabled) {
auto* app_name = u"app_name";
auto camera_device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME);
auto* nudge_id = kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId;
SetTrayAndButtonsVisible();
controller()->OnCameraHWPrivacySwitchStateChanged(
"device_id", cros::mojom::CameraPrivacySwitchState::ON);
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
ASSERT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(GetNudgeAnchorView(nudge_id), camera_icon());
EXPECT_EQ(GetNudgeText(nudge_id),
l10n_util::GetStringFUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_HARDWARE_DISABLED,
app_name, camera_device_name));
controller()->OnCameraHWPrivacySwitchStateChanged(
"device_id", cros::mojom::CameraPrivacySwitchState::OFF);
EXPECT_FALSE(IsNudgeShown(nudge_id));
}
TEST_F(VideoConferenceTrayControllerTest,
HandleMicrophoneUsedWhileHardwaredDisabled) {
auto* app_name = u"app_name";
auto microphone_device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME);
auto* nudge_id = kVideoConferenceTrayMicrophoneUseWhileHWDisabledNudgeId;
SetTrayAndButtonsVisible();
controller()->OnInputMuteChanged(
true,
CrasAudioHandler::InputMuteChangeMethod::kPhysicalShutter);
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
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_HARDWARE_DISABLED,
app_name, microphone_device_name));
controller()->OnInputMuteChanged(
false,
CrasAudioHandler::InputMuteChangeMethod::kPhysicalShutter);
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(
true, CrasAudioHandler::InputMuteChangeMethod::kOther);
controller()->OnCameraHWPrivacySwitchStateChanged(
"device_id", cros::mojom::CameraPrivacySwitchState::ON);
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
task_environment()->FastForwardBy(base::Milliseconds(20));
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
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));
controller()->OnCameraSWPrivacySwitchStateChanged(
cros::mojom::CameraPrivacySwitchState::OFF);
EXPECT_FALSE(IsNudgeShown(nudge_id));
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", 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));
controller()->OnInputMuteChanged(
false, CrasAudioHandler::InputMuteChangeMethod::kOther);
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kMicrophone, app_name);
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
ASSERT_TRUE(IsNudgeShown(nudge_id));
controller()->OnInputMuteChanged(
false,
CrasAudioHandler::InputMuteChangeMethod::kPhysicalShutter);
EXPECT_FALSE(IsNudgeShown(nudge_id));
}
TEST_F(VideoConferenceTrayControllerTest, SpeakOnMuteNudge) {
auto* nudge_id = kVideoConferenceTraySpeakOnMuteDetectedNudgeId;
SetTrayAndButtonsVisible();
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->OnSpeakOnMuteDetected();
ASSERT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(GetNudgeAnchorView(nudge_id), audio_icon());
EXPECT_EQ(GetNudgeText(nudge_id),
l10n_util::GetStringUTF16(
IDS_ASH_VIDEO_CONFERENCE_TOAST_SPEAK_ON_MUTE_DETECTED));
AnchoredNudgeManager::Get()->Cancel(nudge_id);
task_environment()->AdvanceClock(base::Minutes(2) - base::Seconds(5));
controller()->OnSpeakOnMuteDetected();
EXPECT_FALSE(IsNudgeShown(nudge_id));
task_environment()->AdvanceClock(base::Seconds(5));
controller()->OnSpeakOnMuteDetected();
EXPECT_TRUE(IsNudgeShown(nudge_id));
AnchoredNudgeManager::Get()->Cancel(nudge_id);
task_environment()->AdvanceClock(base::Minutes(4) - base::Seconds(5));
controller()->OnSpeakOnMuteDetected();
EXPECT_FALSE(IsNudgeShown(nudge_id));
task_environment()->AdvanceClock(base::Seconds(5));
controller()->OnSpeakOnMuteDetected();
EXPECT_TRUE(IsNudgeShown(nudge_id));
AnchoredNudgeManager::Get()->Cancel(nudge_id);
task_environment()->AdvanceClock(base::Minutes(8) - base::Seconds(5));
controller()->OnSpeakOnMuteDetected();
EXPECT_FALSE(IsNudgeShown(nudge_id));
task_environment()->AdvanceClock(base::Seconds(5));
controller()->OnSpeakOnMuteDetected();
EXPECT_TRUE(IsNudgeShown(nudge_id));
AnchoredNudgeManager::Get()->Cancel(nudge_id);
task_environment()->AdvanceClock(base::Minutes(16));
controller()->OnSpeakOnMuteDetected();
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->OnInputMuteChanged(
false,
CrasAudioHandler::InputMuteChangeMethod::kPhysicalShutter);
controller()->OnInputMuteChanged(
true,
CrasAudioHandler::InputMuteChangeMethod::kPhysicalShutter);
controller()->OnSpeakOnMuteDetected();
EXPECT_TRUE(IsNudgeShown(nudge_id));
controller()->OnInputMuteChanged(
false,
CrasAudioHandler::InputMuteChangeMethod::kPhysicalShutter);
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->OnInputMuteChanged(
true, CrasAudioHandler::InputMuteChangeMethod::kOther);
controller()->OnSpeakOnMuteDetected();
EXPECT_TRUE(IsNudgeShown(nudge_id));
controller()->OnInputMuteChanged(
false, CrasAudioHandler::InputMuteChangeMethod::kOther);
EXPECT_FALSE(IsNudgeShown(nudge_id));
SetTrayAndButtonsInvisible();
SetTrayAndButtonsVisible();
controller()->OnSpeakOnMuteDetected();
EXPECT_TRUE(IsNudgeShown(nudge_id));
}
TEST_F(VideoConferenceTrayControllerTest, SpeakOnMuteNudgeClick) {
auto* nudge_id = kVideoConferenceTraySpeakOnMuteDetectedNudgeId;
SetTrayAndButtonsVisible();
controller()->OnSpeakOnMuteDetected();
ASSERT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(GetSystemTrayClient()->show_speak_on_mute_detection_count(), 0);
LeftClickOn(GetShownNudge(nudge_id));
EXPECT_EQ(GetSystemTrayClient()->show_speak_on_mute_detection_count(), 1);
}
TEST_F(VideoConferenceTrayControllerTest, RecordRepeatedShows) {
UpdateDisplay("100x200,300x400");
base::HistogramTester histograms;
auto flicker_vc_tray = [](int number_of_flicker,
FakeVideoConferenceTrayController* controller,
base::test::TaskEnvironment* task_environment) {
for (auto i = 0; i < number_of_flicker; i++) {
VideoConferenceMediaState state;
state.has_media_app = true;
controller->UpdateWithMediaState(state);
state.has_media_app = false;
controller->UpdateWithMediaState(state);
task_environment->FastForwardBy(base::Milliseconds(80));
}
task_environment->FastForwardBy(base::Milliseconds(100));
};
int expected_sample = 6;
flicker_vc_tray(expected_sample, controller(), task_environment());
histograms.ExpectBucketCount(kRepeatedShowsHistogramName, expected_sample, 1);
VideoConferenceMediaState state;
state.has_media_app = true;
controller()->UpdateWithMediaState(state);
state.has_media_app = false;
controller()->UpdateWithMediaState(state);
task_environment()->FastForwardBy(base::Milliseconds(100));
histograms.ExpectBucketCount(kRepeatedShowsHistogramName, expected_sample + 1,
0);
histograms.ExpectBucketCount(kRepeatedShowsHistogramName, 1, 1);
flicker_vc_tray(8, controller(), task_environment());
histograms.ExpectBucketCount(kRepeatedShowsHistogramName, 8, 1);
flicker_vc_tray(2, controller(), task_environment());
histograms.ExpectBucketCount(kRepeatedShowsHistogramName, 2, 1);
flicker_vc_tray(1, controller(), task_environment());
histograms.ExpectBucketCount(kRepeatedShowsHistogramName, 1, 2);
}
TEST_F(VideoConferenceTrayControllerTest, SpeakOnMuteOptInNudge) {
auto* nudge_id = kVideoConferenceTraySpeakOnMuteOptInNudgeId;
PrefService* prefs =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
EXPECT_TRUE(prefs->FindPreference(prefs::kShouldShowSpeakOnMuteOptInNudge));
EXPECT_TRUE(prefs->FindPreference(prefs::kSpeakOnMuteOptInNudgeShownCount));
SetTrayAndButtonsVisible();
EXPECT_TRUE(video_conference_tray()->GetVisible());
EXPECT_EQ(0, prefs->GetInteger(prefs::kSpeakOnMuteOptInNudgeShownCount));
EXPECT_TRUE(prefs->GetBoolean(prefs::kShouldShowSpeakOnMuteOptInNudge));
EXPECT_FALSE(controller()->GetMicrophoneMuted());
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->SetMicrophoneMuted(true);
EXPECT_TRUE(controller()->GetMicrophoneMuted());
EXPECT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(1, prefs->GetInteger(prefs::kSpeakOnMuteOptInNudgeShownCount));
controller()->SetMicrophoneMuted(false);
EXPECT_FALSE(controller()->GetMicrophoneMuted());
EXPECT_FALSE(IsNudgeShown(nudge_id));
controller()->SetMicrophoneMuted(true);
EXPECT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(2, prefs->GetInteger(prefs::kSpeakOnMuteOptInNudgeShownCount));
ToggleVcTrayBubble();
EXPECT_FALSE(IsNudgeShown(nudge_id));
ToggleVcTrayBubble();
controller()->SetMicrophoneMuted(false);
controller()->SetMicrophoneMuted(true);
EXPECT_TRUE(IsNudgeShown(nudge_id));
EXPECT_EQ(3, prefs->GetInteger(prefs::kSpeakOnMuteOptInNudgeShownCount));
EXPECT_EQ(kSpeakOnMuteOptInNudgeMaxShownCount,
prefs->GetInteger(prefs::kSpeakOnMuteOptInNudgeShownCount));
EXPECT_FALSE(prefs->GetBoolean(prefs::kShouldShowSpeakOnMuteOptInNudge));
controller()->SetMicrophoneMuted(false);
controller()->SetMicrophoneMuted(true);
EXPECT_FALSE(IsNudgeShown(nudge_id));
}
TEST_F(VideoConferenceTrayControllerTest, SpeakOnMuteOptInNudge_OptOut) {
const auto* nudge_id = kVideoConferenceTraySpeakOnMuteOptInNudgeId;
PrefService* prefs =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
EXPECT_TRUE(prefs->FindPreference(prefs::kShouldShowSpeakOnMuteOptInNudge));
EXPECT_TRUE(prefs->FindPreference(prefs::kUserSpeakOnMuteDetectionEnabled));
SetTrayAndButtonsVisible();
EXPECT_TRUE(video_conference_tray()->GetVisible());
EXPECT_TRUE(prefs->GetBoolean(prefs::kShouldShowSpeakOnMuteOptInNudge));
EXPECT_FALSE(prefs->GetBoolean(prefs::kUserSpeakOnMuteDetectionEnabled));
controller()->SetMicrophoneMuted(true);
EXPECT_TRUE(IsNudgeShown(nudge_id));
LeftClickOn(GetNudgeSecondaryButton(nudge_id));
EXPECT_FALSE(IsNudgeShown(nudge_id));
EXPECT_FALSE(prefs->GetBoolean(prefs::kShouldShowSpeakOnMuteOptInNudge));
EXPECT_FALSE(prefs->GetBoolean(prefs::kUserSpeakOnMuteDetectionEnabled));
EXPECT_TRUE(
IsToastShown(kVideoConferenceTraySpeakOnMuteOptInConfirmationToastId));
controller()->SetMicrophoneMuted(false);
controller()->SetMicrophoneMuted(true);
EXPECT_FALSE(IsNudgeShown(nudge_id));
}
TEST_F(VideoConferenceTrayControllerTest, SpeakOnMuteOptInNudge_OptIn) {
const auto* nudge_id = kVideoConferenceTraySpeakOnMuteOptInNudgeId;
PrefService* prefs =
Shell::Get()->session_controller()->GetLastActiveUserPrefService();
EXPECT_TRUE(prefs->FindPreference(prefs::kShouldShowSpeakOnMuteOptInNudge));
EXPECT_TRUE(prefs->FindPreference(prefs::kUserSpeakOnMuteDetectionEnabled));
SetTrayAndButtonsVisible();
EXPECT_TRUE(video_conference_tray()->GetVisible());
EXPECT_TRUE(prefs->GetBoolean(prefs::kShouldShowSpeakOnMuteOptInNudge));
EXPECT_FALSE(prefs->GetBoolean(prefs::kUserSpeakOnMuteDetectionEnabled));
controller()->SetMicrophoneMuted(true);
EXPECT_TRUE(IsNudgeShown(nudge_id));
LeftClickOn(GetNudgePrimaryButton(nudge_id));
EXPECT_FALSE(IsNudgeShown(nudge_id));
EXPECT_FALSE(prefs->GetBoolean(prefs::kShouldShowSpeakOnMuteOptInNudge));
EXPECT_TRUE(prefs->GetBoolean(prefs::kUserSpeakOnMuteDetectionEnabled));
EXPECT_TRUE(
IsToastShown(kVideoConferenceTraySpeakOnMuteOptInConfirmationToastId));
controller()->SetMicrophoneMuted(false);
controller()->SetMicrophoneMuted(true);
EXPECT_FALSE(IsNudgeShown(nudge_id));
}
TEST_F(VideoConferenceTrayControllerTest, NudgeBlocksOtherNudges) {
const auto* opt_in_nudge_id = kVideoConferenceTraySpeakOnMuteOptInNudgeId;
const auto* speak_on_mute_nudge_id =
kVideoConferenceTraySpeakOnMuteDetectedNudgeId;
const auto* use_while_disabled_nudge_id =
kVideoConferenceTrayCameraUseWhileHWDisabledNudgeId;
const auto* app_name = u"app_name";
const auto camera_device_name =
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME);
SetTrayAndButtonsVisible();
controller()->OnCameraHWPrivacySwitchStateChanged(
"device_id", cros::mojom::CameraPrivacySwitchState::ON);
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
EXPECT_TRUE(IsNudgeShown(use_while_disabled_nudge_id));
controller()->SetMicrophoneMuted(true);
EXPECT_TRUE(controller()->GetMicrophoneMuted());
EXPECT_TRUE(IsNudgeShown(opt_in_nudge_id));
EXPECT_FALSE(IsNudgeShown(use_while_disabled_nudge_id));
LeftClickOn(GetNudgePrimaryButton(opt_in_nudge_id));
EXPECT_FALSE(IsNudgeShown(opt_in_nudge_id));
task_environment()->AdvanceClock(base::Seconds(60));
controller()->OnCameraHWPrivacySwitchStateChanged(
"device_id", cros::mojom::CameraPrivacySwitchState::ON);
controller()->HandleDeviceUsedWhileDisabled(
crosapi::mojom::VideoConferenceMediaDevice::kCamera, app_name);
task_environment()->FastForwardBy(kHandleDeviceUsedWhileDisabledWaitTime);
EXPECT_TRUE(IsNudgeShown(use_while_disabled_nudge_id));
controller()->OnSpeakOnMuteDetected();
EXPECT_FALSE(IsNudgeShown(speak_on_mute_nudge_id));
AnchoredNudgeManager::Get()->Cancel(use_while_disabled_nudge_id);
EXPECT_FALSE(IsNudgeShown(use_while_disabled_nudge_id));
controller()->OnSpeakOnMuteDetected();
EXPECT_TRUE(IsNudgeShown(speak_on_mute_nudge_id));
}
}