#include "ash/system/video_conference/video_conference_tray.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "ash/root_window_controller.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shelf/shelf.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/icon_button.h"
#include "ash/system/status_area_widget.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/system/tray/system_tray_notifier.h"
#include "ash/system/unified/unified_system_tray.h"
#include "ash/system/video_conference/bubble/bubble_view.h"
#include "ash/system/video_conference/bubble/bubble_view_ids.h"
#include "ash/system/video_conference/bubble/linux_apps_bubble_view.h"
#include "ash/system/video_conference/effects/video_conference_tray_effects_manager_types.h"
#include "ash/system/video_conference/fake_video_conference_tray_controller.h"
#include "ash/system/video_conference/video_conference_common.h"
#include "ash/test/ash_test_base.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/location.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 "base/timer/timer.h"
#include "chromeos/crosapi/mojom/video_conference.mojom-shared.h"
#include "chromeos/crosapi/mojom/video_conference.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/animation/ink_drop.h"
#include "ui/views/animation/ink_drop_state.h"
#include "ui/views/widget/widget.h"
namespace {
constexpr char kToggleButtonHistogramName[] =
"Ash.VideoConferenceTray.ToggleBubbleButton.Click";
constexpr char kCameraMuteHistogramName[] =
"Ash.VideoConferenceTray.CameraMuteButton.Click";
constexpr char kMicrophoneMuteHistogramName[] =
"Ash.VideoConferenceTray.MicrophoneMuteButton.Click";
constexpr char kStopScreenShareHistogramName[] =
"Ash.VideoConferenceTray.StopScreenShareButton.Click";
constexpr char kTrayBackgroundViewHistogramName[] =
"Ash.StatusArea.TrayBackgroundView.Pressed";
constexpr base::TimeDelta kGetMediaAppsDelayTime = base::Milliseconds(100);
void SetSessionState(session_manager::SessionState state) {
ash::SessionInfo info;
info.state = state;
ash::Shell::Get()->session_controller()->SetSessionInfo(info);
}
using MediaApps = std::vector<crosapi::mojom::VideoConferenceMediaAppInfoPtr>;
class DelayVideoConferenceTrayController
: public ash::FakeVideoConferenceTrayController {
public:
DelayVideoConferenceTrayController() = default;
DelayVideoConferenceTrayController(
const DelayVideoConferenceTrayController&) = delete;
DelayVideoConferenceTrayController& operator=(
const DelayVideoConferenceTrayController&) = delete;
~DelayVideoConferenceTrayController() override = default;
void GetMediaApps(base::OnceCallback<void(MediaApps)> ui_callback) override {
getting_media_apps_called_++;
MediaApps apps;
for (auto& app : media_apps()) {
apps.push_back(app->Clone());
}
timer_.Start(
FROM_HERE, kGetMediaAppsDelayTime,
base::BindOnce(
[](base::OnceCallback<void(MediaApps)> ui_callback,
MediaApps apps) { std::move(ui_callback).Run(std::move(apps)); },
std::move(ui_callback), std::move(apps)));
}
int getting_media_apps_called() { return getting_media_apps_called_; }
private:
base::OneShotTimer timer_;
int getting_media_apps_called_ = 0;
};
}
namespace ash {
class VideoConferenceTrayTest : public AshTestBase {
public:
VideoConferenceTrayTest()
: AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
VideoConferenceTrayTest(const VideoConferenceTrayTest&) = delete;
VideoConferenceTrayTest& operator=(const VideoConferenceTrayTest&) = delete;
~VideoConferenceTrayTest() override = default;
void SetUp() override {
scoped_feature_list_.InitWithFeatures(
{features::kVcStopAllScreenShare,
features::kFeatureManagementVideoConference},
{});
controller_ = std::make_unique<FakeVideoConferenceTrayController>();
AshTestBase::SetUp();
}
void TearDown() override {
num_media_apps_simulated_ = 0;
AshTestBase::TearDown();
controller_.reset();
}
VideoConferenceTray* GetSecondaryVideoConferenceTray() {
Shelf* const shelf =
Shell::GetRootWindowControllerWithDisplayId(GetSecondaryDisplay().id())
->shelf();
return shelf->status_area_widget()->video_conference_tray();
}
void CreateMediaApps(int num_apps,
bool clear_existing_apps = true,
crosapi::mojom::VideoConferenceAppType app_type =
crosapi::mojom::VideoConferenceAppType::kChromeApp) {
if (clear_existing_apps) {
controller()->ClearMediaApps();
}
auto* title = u"Meet";
const std::string kMeetTestUrl = "https://meet.google.com/abc-xyz/ab-123";
for (int i = 0; i < num_apps; i++) {
controller()->AddMediaApp(
crosapi::mojom::VideoConferenceMediaAppInfo::New(
base::UnguessableToken::Create(),
base::Time::Now(),
true,
true, true,
title,
GURL(kMeetTestUrl), app_type));
}
}
std::unique_ptr<views::Widget> ForceShelfToAutoHideOnPrimaryDisplay() {
Shelf* shelf = Shell::GetPrimaryRootWindowController()->shelf();
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
std::unique_ptr<views::Widget> widget =
CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
widget->SetBounds(gfx::Rect(0, 0, 100, 100));
EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
return widget;
}
VideoConferenceTray* video_conference_tray() {
return StatusAreaWidgetTestHelper::GetStatusAreaWidget()
->video_conference_tray();
}
IconButton* toggle_bubble_button() {
return video_conference_tray()->toggle_bubble_button_;
}
VideoConferenceTrayButton* camera_icon() {
return video_conference_tray()->camera_icon();
}
VideoConferenceTrayButton* audio_icon() {
return video_conference_tray()->audio_icon();
}
VideoConferenceTrayButton* screen_share_icon() {
return video_conference_tray()->screen_share_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;
controller()->UpdateWithMediaState(state);
return state;
}
void ModifyAppsCapturing(bool add) {
if (add) {
CreateMediaApps(++num_media_apps_simulated_,
false);
controller_->OnAppAdded();
} else {
CreateMediaApps(--num_media_apps_simulated_,
false);
}
}
FakeVideoConferenceTrayController* controller() { return controller_.get(); }
private:
int num_media_apps_simulated_ = 0;
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<FakeVideoConferenceTrayController> controller_;
};
TEST_F(VideoConferenceTrayTest, ClickTrayButton) {
base::HistogramTester histogram_tester;
SetTrayAndButtonsVisible();
EXPECT_FALSE(video_conference_tray()->GetBubbleView());
LeftClickOn(toggle_bubble_button());
EXPECT_TRUE(video_conference_tray()->GetBubbleView());
EXPECT_TRUE(video_conference_tray()->GetBubbleView()->GetVisible());
EXPECT_TRUE(toggle_bubble_button()->toggled());
histogram_tester.ExpectBucketCount(kToggleButtonHistogramName, true, 1);
LeftClickOn(toggle_bubble_button());
EXPECT_FALSE(video_conference_tray()->GetBubbleView());
EXPECT_FALSE(toggle_bubble_button()->toggled());
histogram_tester.ExpectBucketCount(kToggleButtonHistogramName, false, 1);
LeftClickOn(toggle_bubble_button());
EXPECT_TRUE(video_conference_tray()->GetBubbleView());
EXPECT_TRUE(video_conference_tray()->GetBubbleView()->GetVisible());
EXPECT_TRUE(toggle_bubble_button()->toggled());
histogram_tester.ExpectBucketCount(kToggleButtonHistogramName, true, 2);
LeftClickOn(
StatusAreaWidgetTestHelper::GetStatusAreaWidget()->unified_system_tray());
EXPECT_FALSE(video_conference_tray()->GetBubbleView());
EXPECT_FALSE(toggle_bubble_button()->toggled());
}
TEST_F(VideoConferenceTrayTest, TrayPressedMetrics) {
base::HistogramTester histogram_tester;
SetTrayAndButtonsVisible();
LeftClickOn(toggle_bubble_button());
histogram_tester.ExpectTotalCount(kTrayBackgroundViewHistogramName, 1);
LeftClickOn(camera_icon());
histogram_tester.ExpectTotalCount(kTrayBackgroundViewHistogramName, 2);
LeftClickOn(audio_icon());
histogram_tester.ExpectTotalCount(kTrayBackgroundViewHistogramName, 3);
LeftClickOn(screen_share_icon());
histogram_tester.ExpectTotalCount(kTrayBackgroundViewHistogramName, 4);
}
TEST_F(VideoConferenceTrayTest, ClickTrayBackgroundViewTogglesBubble) {
SetTrayAndButtonsVisible();
GetEventGenerator()->GestureTapAt(
toggle_bubble_button()->GetBoundsInScreen().bottom_right() +
gfx::Vector2d(4, 0));
EXPECT_TRUE(video_conference_tray()->GetBubbleView());
EXPECT_TRUE(toggle_bubble_button()->toggled());
GetEventGenerator()->GestureTapAt(
toggle_bubble_button()->GetBoundsInScreen().bottom_right() +
gfx::Vector2d(4, 0));
EXPECT_FALSE(video_conference_tray()->GetBubbleView());
EXPECT_FALSE(toggle_bubble_button()->toggled());
}
TEST_F(VideoConferenceTrayTest, ToggleBubbleButtonRotation) {
SetTrayAndButtonsVisible();
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kBottom);
EXPECT_EQ(0,
video_conference_tray()->GetRotationValueForToggleBubbleButton());
LeftClickOn(toggle_bubble_button());
EXPECT_EQ(180,
video_conference_tray()->GetRotationValueForToggleBubbleButton());
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kLeft);
LeftClickOn(toggle_bubble_button());
EXPECT_EQ(90,
video_conference_tray()->GetRotationValueForToggleBubbleButton());
LeftClickOn(toggle_bubble_button());
EXPECT_EQ(270,
video_conference_tray()->GetRotationValueForToggleBubbleButton());
GetPrimaryShelf()->SetAlignment(ShelfAlignment::kRight);
LeftClickOn(toggle_bubble_button());
EXPECT_EQ(270,
video_conference_tray()->GetRotationValueForToggleBubbleButton());
LeftClickOn(toggle_bubble_button());
EXPECT_EQ(90,
video_conference_tray()->GetRotationValueForToggleBubbleButton());
}
TEST_F(VideoConferenceTrayTest, ToggleBubbleInkdrop) {
auto* ink_drop = views::InkDrop::Get(video_conference_tray())->GetInkDrop();
SetTrayAndButtonsVisible();
EXPECT_EQ(views::InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
LeftClickOn(toggle_bubble_button());
EXPECT_EQ(views::InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
LeftClickOn(toggle_bubble_button());
EXPECT_EQ(views::InkDropState::HIDDEN, ink_drop->GetTargetInkDropState());
}
TEST_F(VideoConferenceTrayTest, TrayVisibility) {
VideoConferenceMediaState state;
state.has_media_app = true;
state.has_camera_permission = true;
state.has_microphone_permission = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(video_conference_tray()->GetVisible());
EXPECT_TRUE(audio_icon()->GetVisible());
EXPECT_TRUE(camera_icon()->GetVisible());
EXPECT_TRUE(video_conference_tray()->GetVisible());
EXPECT_TRUE(audio_icon()->GetVisible());
EXPECT_TRUE(camera_icon()->GetVisible());
state.has_media_app = false;
state.has_camera_permission = false;
state.has_microphone_permission = false;
controller()->UpdateWithMediaState(state);
EXPECT_FALSE(video_conference_tray()->GetVisible());
EXPECT_FALSE(audio_icon()->GetVisible());
EXPECT_FALSE(camera_icon()->GetVisible());
}
TEST_F(VideoConferenceTrayTest, TrayVisibilityOnSecondaryDisplay) {
UpdateDisplay("800x700,800x700");
VideoConferenceMediaState state;
state.has_media_app = true;
state.has_camera_permission = true;
state.has_microphone_permission = true;
controller()->UpdateWithMediaState(state);
ASSERT_TRUE(GetSecondaryVideoConferenceTray()->GetVisible());
auto* audio_icon = GetSecondaryVideoConferenceTray()->audio_icon();
auto* camera_icon = GetSecondaryVideoConferenceTray()->camera_icon();
ASSERT_TRUE(audio_icon->GetVisible());
ASSERT_TRUE(camera_icon->GetVisible());
state.has_media_app = false;
state.has_camera_permission = false;
state.has_microphone_permission = false;
controller()->UpdateWithMediaState(state);
EXPECT_FALSE(GetSecondaryVideoConferenceTray()->GetVisible());
EXPECT_FALSE(audio_icon->GetVisible());
EXPECT_FALSE(camera_icon->GetVisible());
}
TEST_F(VideoConferenceTrayTest, CameraButtonVisibility) {
VideoConferenceMediaState state;
state.has_camera_permission = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(camera_icon()->GetVisible());
state.has_camera_permission = false;
controller()->UpdateWithMediaState(state);
EXPECT_FALSE(camera_icon()->GetVisible());
}
TEST_F(VideoConferenceTrayTest, MicrophoneButtonVisibility) {
VideoConferenceMediaState state;
state.has_microphone_permission = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(audio_icon()->GetVisible());
state.has_microphone_permission = false;
controller()->UpdateWithMediaState(state);
EXPECT_FALSE(audio_icon()->GetVisible());
}
TEST_F(VideoConferenceTrayTest, ScreenshareButtonVisibility) {
auto* screen_share_icon = video_conference_tray()->screen_share_icon();
VideoConferenceMediaState state;
state.is_capturing_screen = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(screen_share_icon->GetVisible());
EXPECT_TRUE(screen_share_icon->show_privacy_indicator());
state.is_capturing_screen = false;
controller()->UpdateWithMediaState(state);
EXPECT_FALSE(screen_share_icon->GetVisible());
EXPECT_FALSE(screen_share_icon->show_privacy_indicator());
}
TEST_F(VideoConferenceTrayTest, ToggleCameraButton) {
base::HistogramTester histogram_tester;
SetTrayAndButtonsVisible();
EXPECT_FALSE(camera_icon()->toggled());
LeftClickOn(camera_icon());
EXPECT_TRUE(controller()->GetCameraMuted());
EXPECT_TRUE(camera_icon()->toggled());
histogram_tester.ExpectBucketCount(kCameraMuteHistogramName, false, 1);
LeftClickOn(camera_icon());
EXPECT_FALSE(controller()->GetCameraMuted());
EXPECT_FALSE(camera_icon()->toggled());
histogram_tester.ExpectBucketCount(kCameraMuteHistogramName, true, 1);
}
TEST_F(VideoConferenceTrayTest, ToggleMicrophoneButton) {
base::HistogramTester histogram_tester;
SetTrayAndButtonsVisible();
EXPECT_FALSE(audio_icon()->toggled());
LeftClickOn(audio_icon());
EXPECT_TRUE(controller()->GetMicrophoneMuted());
EXPECT_TRUE(audio_icon()->toggled());
histogram_tester.ExpectBucketCount(kMicrophoneMuteHistogramName, false, 1);
LeftClickOn(audio_icon());
EXPECT_FALSE(controller()->GetMicrophoneMuted());
EXPECT_FALSE(audio_icon()->toggled());
histogram_tester.ExpectBucketCount(kMicrophoneMuteHistogramName, true, 1);
}
TEST_F(VideoConferenceTrayTest, ClickScreenshareButton) {
base::HistogramTester histogram_tester;
SetTrayAndButtonsVisible();
EXPECT_EQ(controller()->stop_all_screen_share_count(), 0);
LeftClickOn(screen_share_icon());
histogram_tester.ExpectBucketCount(kStopScreenShareHistogramName, true, 1);
EXPECT_EQ(controller()->stop_all_screen_share_count(), 1);
}
TEST_F(VideoConferenceTrayTest, PrivacyIndicator) {
auto state = SetTrayAndButtonsVisible();
EXPECT_FALSE(camera_icon()->show_privacy_indicator());
state.is_capturing_camera = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(camera_icon()->show_privacy_indicator());
EXPECT_FALSE(audio_icon()->show_privacy_indicator());
state.is_capturing_microphone = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(audio_icon()->show_privacy_indicator());
state.is_capturing_camera = false;
state.is_capturing_microphone = false;
controller()->UpdateWithMediaState(state);
EXPECT_FALSE(camera_icon()->show_privacy_indicator());
EXPECT_FALSE(audio_icon()->show_privacy_indicator());
}
TEST_F(VideoConferenceTrayTest, CameraIconPrivacyIndicatorOnToggled) {
auto state = SetTrayAndButtonsVisible();
state.is_capturing_camera = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(camera_icon()->show_privacy_indicator());
EXPECT_TRUE(camera_icon()->GetVisible());
LeftClickOn(camera_icon());
EXPECT_FALSE(camera_icon()->show_privacy_indicator());
}
TEST_F(VideoConferenceTrayTest, MicrophoneIconPrivacyIndicatorOnToggled) {
auto state = SetTrayAndButtonsVisible();
state.is_capturing_microphone = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(audio_icon()->show_privacy_indicator());
LeftClickOn(audio_icon());
EXPECT_FALSE(audio_icon()->show_privacy_indicator());
}
TEST_F(VideoConferenceTrayTest, AutoHiddenShelfShownSingleDisplay) {
auto widget = ForceShelfToAutoHideOnPrimaryDisplay();
ModifyAppsCapturing(true);
SetTrayAndButtonsVisible();
auto* shelf = Shell::GetPrimaryRootWindowController()->shelf();
EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
auto& disable_shelf_autohide_timer =
controller()->GetShelfAutoHideTimerForTest();
EXPECT_TRUE(disable_shelf_autohide_timer.IsRunning());
task_environment()->FastForwardBy(base::Seconds(7));
ASSERT_FALSE(disable_shelf_autohide_timer.IsRunning());
EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}
TEST_F(VideoConferenceTrayTest, AutoHiddenShelfReShown) {
auto widget = ForceShelfToAutoHideOnPrimaryDisplay();
ModifyAppsCapturing(true);
auto state = SetTrayAndButtonsVisible();
auto& disable_shelf_autohide_timer =
controller()->GetShelfAutoHideTimerForTest();
task_environment()->FastForwardBy(base::Seconds(7));
auto* shelf = Shell::GetPrimaryRootWindowController()->shelf();
ASSERT_FALSE(disable_shelf_autohide_timer.IsRunning());
EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
ModifyAppsCapturing(true);
controller()->UpdateWithMediaState(state);
EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
EXPECT_TRUE(disable_shelf_autohide_timer.IsRunning());
task_environment()->FastForwardBy(base::Seconds(7));
EXPECT_EQ(SHELF_AUTO_HIDE, shelf->GetVisibilityState());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}
TEST_F(VideoConferenceTrayTest, AutoHiddenShelfTimerRestarted) {
auto widget = ForceShelfToAutoHideOnPrimaryDisplay();
ModifyAppsCapturing(true);
auto state = SetTrayAndButtonsVisible();
task_environment()->FastForwardBy(base::Seconds(4));
ModifyAppsCapturing(true);
controller()->UpdateWithMediaState(state);
auto* shelf = Shell::GetPrimaryRootWindowController()->shelf();
ASSERT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
task_environment()->FastForwardBy(base::Seconds(4));
auto& disable_shelf_autohide_timer =
controller()->GetShelfAutoHideTimerForTest();
EXPECT_TRUE(disable_shelf_autohide_timer.IsRunning());
EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
task_environment()->FastForwardBy(base::Seconds(4));
EXPECT_FALSE(disable_shelf_autohide_timer.IsRunning());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}
TEST_F(VideoConferenceTrayTest, DecreasedAppCountDoesNotShowShelf) {
auto widget = ForceShelfToAutoHideOnPrimaryDisplay();
ModifyAppsCapturing(true);
SetTrayAndButtonsVisible();
task_environment()->FastForwardBy(base::Seconds(7));
auto* shelf = Shell::GetPrimaryRootWindowController()->shelf();
ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
controller()->ClearMediaApps();
controller()->UpdateWithMediaState(VideoConferenceMediaState());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
EXPECT_FALSE(controller()->GetShelfAutoHideTimerForTest().IsRunning());
}
TEST_F(VideoConferenceTrayTest, DecreasedAppCountDoesNotHideShelf) {
auto widget = ForceShelfToAutoHideOnPrimaryDisplay();
ModifyAppsCapturing(true);
ModifyAppsCapturing(true);
auto state = SetTrayAndButtonsVisible();
task_environment()->FastForwardBy(base::Seconds(2));
auto* shelf = Shell::GetPrimaryRootWindowController()->shelf();
ASSERT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
ModifyAppsCapturing(false);
controller()->UpdateWithMediaState(state);
task_environment()->FastForwardBy(base::Seconds(2));
EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
EXPECT_TRUE(controller()->GetShelfAutoHideTimerForTest().IsRunning());
}
TEST_F(VideoConferenceTrayTest, AppCountFromOneToZero) {
auto widget = ForceShelfToAutoHideOnPrimaryDisplay();
ModifyAppsCapturing(true);
SetTrayAndButtonsVisible();
task_environment()->FastForwardBy(base::Seconds(2));
auto* shelf = Shell::GetPrimaryRootWindowController()->shelf();
ASSERT_EQ(SHELF_AUTO_HIDE_SHOWN, shelf->GetAutoHideState());
ModifyAppsCapturing(false);
controller()->UpdateWithMediaState(VideoConferenceMediaState());
do {
task_environment()->RunUntilIdle();
} while (shelf->GetAutoHideState() != SHELF_AUTO_HIDE_HIDDEN);
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
EXPECT_FALSE(controller()->GetShelfAutoHideTimerForTest().IsRunning());
}
TEST_F(VideoConferenceTrayTest, AutoHiddenShelfTwoDisplays) {
UpdateDisplay("800x700,800x700");
for (auto* root_window_controller : Shell::GetAllRootWindowControllers()) {
Shelf* shelf = root_window_controller->shelf();
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
}
auto widget = ForceShelfToAutoHideOnPrimaryDisplay();
auto secondary_display_window =
CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
secondary_display_window->SetBounds(gfx::Rect(900, 0, 100, 100));
auto* secondary_shelf =
Shell::Get()
->GetRootWindowControllerWithDisplayId(GetSecondaryDisplay().id())
->shelf();
ASSERT_EQ(SHELF_AUTO_HIDE, secondary_shelf->GetVisibilityState());
ASSERT_EQ(SHELF_AUTO_HIDE_HIDDEN, secondary_shelf->GetAutoHideState());
ModifyAppsCapturing(true);
SetTrayAndButtonsVisible();
auto* primary_shelf = Shell::GetPrimaryRootWindowController()->shelf();
EXPECT_EQ(SHELF_AUTO_HIDE, primary_shelf->GetVisibilityState());
EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, primary_shelf->GetAutoHideState());
EXPECT_EQ(SHELF_AUTO_HIDE, secondary_shelf->GetVisibilityState());
EXPECT_EQ(SHELF_AUTO_HIDE_SHOWN, secondary_shelf->GetAutoHideState());
UpdateDisplay("800x700");
primary_shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
auto& disable_shelf_autohide_timer =
controller()->GetShelfAutoHideTimerForTest();
ASSERT_TRUE(disable_shelf_autohide_timer.IsRunning());
disable_shelf_autohide_timer.FireNow();
EXPECT_EQ(SHELF_AUTO_HIDE, primary_shelf->GetVisibilityState());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, primary_shelf->GetAutoHideState());
}
TEST_F(VideoConferenceTrayTest, MultiDisplayVideoConferenceTrayVisibility) {
VideoConferenceMediaState state;
state.has_media_app = true;
state.has_camera_permission = true;
state.has_microphone_permission = true;
state.is_capturing_microphone = true;
state.is_capturing_screen = true;
controller()->UpdateWithMediaState(state);
ASSERT_TRUE(video_conference_tray()->GetVisible());
LeftClickOn(camera_icon());
ASSERT_TRUE(camera_icon()->toggled());
UpdateDisplay("800x700,800x700");
EXPECT_TRUE(GetSecondaryVideoConferenceTray()->GetVisible());
auto* secondary_camera_icon =
GetSecondaryVideoConferenceTray()->camera_icon();
EXPECT_TRUE(secondary_camera_icon);
EXPECT_FALSE(secondary_camera_icon->is_capturing());
EXPECT_TRUE(secondary_camera_icon->toggled());
auto* secondary_microphone_icon =
GetSecondaryVideoConferenceTray()->audio_icon();
EXPECT_TRUE(secondary_microphone_icon);
EXPECT_TRUE(secondary_microphone_icon->is_capturing());
EXPECT_FALSE(secondary_microphone_icon->toggled());
auto* secondary_screen_share_icon =
GetSecondaryVideoConferenceTray()->screen_share_icon();
EXPECT_TRUE(secondary_screen_share_icon);
EXPECT_TRUE(secondary_screen_share_icon->is_capturing());
EXPECT_FALSE(secondary_screen_share_icon->toggled());
}
TEST_F(VideoConferenceTrayTest, PrivacyIndicatorOnSecondaryDisplay) {
auto state = SetTrayAndButtonsVisible();
ASSERT_TRUE(video_conference_tray()->GetVisible());
UpdateDisplay("800x700,800x700");
ASSERT_TRUE(GetSecondaryVideoConferenceTray()->GetVisible());
state.is_capturing_camera = true;
controller()->UpdateWithMediaState(state);
auto* secondary_camera_icon =
GetSecondaryVideoConferenceTray()->camera_icon();
EXPECT_TRUE(secondary_camera_icon->GetVisible());
EXPECT_TRUE(secondary_camera_icon->show_privacy_indicator());
auto* secondary_audio_icon = GetSecondaryVideoConferenceTray()->audio_icon();
EXPECT_FALSE(secondary_audio_icon->show_privacy_indicator());
state.is_capturing_microphone = true;
controller()->UpdateWithMediaState(state);
EXPECT_TRUE(secondary_audio_icon->show_privacy_indicator());
state.is_capturing_camera = false;
state.is_capturing_microphone = false;
controller()->UpdateWithMediaState(state);
EXPECT_FALSE(secondary_camera_icon->show_privacy_indicator());
EXPECT_FALSE(secondary_audio_icon->show_privacy_indicator());
}
TEST_F(VideoConferenceTrayTest, CameraButtonToggleAcrossDisplays) {
SetTrayAndButtonsVisible();
ASSERT_TRUE(video_conference_tray()->GetVisible());
UpdateDisplay("800x700,800x700");
ASSERT_TRUE(GetSecondaryVideoConferenceTray()->GetVisible());
LeftClickOn(camera_icon());
ASSERT_TRUE(controller()->GetCameraMuted());
ASSERT_TRUE(camera_icon()->toggled());
auto* secondary_camera_icon =
GetSecondaryVideoConferenceTray()->camera_icon();
EXPECT_TRUE(secondary_camera_icon->toggled());
LeftClickOn(secondary_camera_icon);
EXPECT_FALSE(secondary_camera_icon->toggled());
EXPECT_FALSE(controller()->GetCameraMuted());
EXPECT_FALSE(camera_icon()->toggled());
}
TEST_F(VideoConferenceTrayTest, AudioButtonToggleAcrossDisplays) {
SetTrayAndButtonsVisible();
ASSERT_TRUE(video_conference_tray()->GetVisible());
UpdateDisplay("800x700,800x700");
ASSERT_TRUE(GetSecondaryVideoConferenceTray()->GetVisible());
LeftClickOn(audio_icon());
ASSERT_TRUE(controller()->GetMicrophoneMuted());
ASSERT_TRUE(audio_icon()->toggled());
auto* secondary_audio_icon = GetSecondaryVideoConferenceTray()->audio_icon();
EXPECT_TRUE(secondary_audio_icon->toggled());
LeftClickOn(secondary_audio_icon);
EXPECT_FALSE(secondary_audio_icon->toggled());
EXPECT_FALSE(controller()->GetMicrophoneMuted());
EXPECT_FALSE(audio_icon()->toggled());
}
TEST_F(VideoConferenceTrayTest,
PrivacyIndicatorToggleCameraOnSecondaryDisplay) {
auto state = SetTrayAndButtonsVisible();
ASSERT_TRUE(video_conference_tray()->GetVisible());
UpdateDisplay("800x700,800x700");
ASSERT_TRUE(GetSecondaryVideoConferenceTray()->GetVisible());
state.is_capturing_camera = true;
controller()->UpdateWithMediaState(state);
auto* secondary_camera_icon =
GetSecondaryVideoConferenceTray()->camera_icon();
LeftClickOn(camera_icon());
ASSERT_FALSE(camera_icon()->show_privacy_indicator());
EXPECT_FALSE(secondary_camera_icon->show_privacy_indicator());
LeftClickOn(secondary_camera_icon);
ASSERT_TRUE(secondary_camera_icon->show_privacy_indicator());
EXPECT_TRUE(camera_icon()->show_privacy_indicator());
}
TEST_F(VideoConferenceTrayTest, PrivacyIndicatorToggleAudioOnSecondaryDisplay) {
auto state = SetTrayAndButtonsVisible();
ASSERT_TRUE(video_conference_tray()->GetVisible());
UpdateDisplay("800x700,800x700");
ASSERT_TRUE(GetSecondaryVideoConferenceTray()->GetVisible());
state.is_capturing_microphone = true;
controller()->UpdateWithMediaState(state);
auto* secondary_audio_icon = GetSecondaryVideoConferenceTray()->audio_icon();
LeftClickOn(audio_icon());
ASSERT_FALSE(audio_icon()->show_privacy_indicator());
EXPECT_FALSE(secondary_audio_icon->show_privacy_indicator());
LeftClickOn(secondary_audio_icon);
ASSERT_TRUE(secondary_audio_icon->show_privacy_indicator());
EXPECT_TRUE(audio_icon()->show_privacy_indicator());
}
TEST_F(VideoConferenceTrayTest, SessionChanged) {
SetTrayAndButtonsVisible();
SetSessionState(session_manager::SessionState::OOBE);
EXPECT_FALSE(video_conference_tray()->GetVisible());
SetSessionState(session_manager::SessionState::LOGIN_PRIMARY);
EXPECT_FALSE(video_conference_tray()->GetVisible());
SetSessionState(session_manager::SessionState::ACTIVE);
EXPECT_TRUE(video_conference_tray()->GetVisible());
SetSessionState(session_manager::SessionState::LOCKED);
EXPECT_FALSE(video_conference_tray()->GetVisible());
SetSessionState(session_manager::SessionState::ACTIVE);
EXPECT_TRUE(video_conference_tray()->GetVisible());
}
TEST_F(VideoConferenceTrayTest, MutingChangesTooltip) {
auto state = SetTrayAndButtonsVisible();
ASSERT_TRUE(video_conference_tray()->GetVisible());
ASSERT_FALSE(audio_icon()->toggled());
EXPECT_EQ(
audio_icon()->GetTooltipText(),
l10n_util::GetStringFUTF16(
VIDEO_CONFERENCE_TOGGLE_BUTTON_TOOLTIP,
l10n_util::GetStringUTF16(
VIDEO_CONFERENCE_TOGGLE_BUTTON_TYPE_MICROPHONE),
l10n_util::GetStringUTF16(VIDEO_CONFERENCE_TOGGLE_BUTTON_STATE_ON)));
state.is_capturing_microphone = true;
controller()->UpdateWithMediaState(state);
EXPECT_EQ(audio_icon()->GetTooltipText(),
l10n_util::GetStringFUTF16(
VIDEO_CONFERENCE_TOGGLE_BUTTON_TOOLTIP,
l10n_util::GetStringUTF16(
VIDEO_CONFERENCE_TOGGLE_BUTTON_TYPE_MICROPHONE),
l10n_util::GetStringUTF16(
VIDEO_CONFERENCE_TOGGLE_BUTTON_STATE_ON_AND_IN_USE)));
LeftClickOn(audio_icon());
ASSERT_TRUE(controller()->GetMicrophoneMuted());
ASSERT_TRUE(audio_icon()->toggled());
EXPECT_EQ(
audio_icon()->GetTooltipText(),
l10n_util::GetStringFUTF16(
VIDEO_CONFERENCE_TOGGLE_BUTTON_TOOLTIP,
l10n_util::GetStringUTF16(
VIDEO_CONFERENCE_TOGGLE_BUTTON_TYPE_MICROPHONE),
l10n_util::GetStringUTF16(VIDEO_CONFERENCE_TOGGLE_BUTTON_STATE_OFF)));
}
TEST_F(VideoConferenceTrayTest, CloseBubbleOnEffectSupportStateChange) {
SetTrayAndButtonsVisible();
LeftClickOn(toggle_bubble_button());
ASSERT_TRUE(video_conference_tray()->GetBubbleView());
controller()->GetEffectsManager().NotifyEffectSupportStateChanged(
VcEffectId::kTestEffect, true);
EXPECT_FALSE(video_conference_tray()->GetBubbleView());
}
TEST_F(VideoConferenceTrayTest, BubbleWithOnlyLinuxApps) {
SetTrayAndButtonsVisible();
CreateMediaApps(1, true);
LeftClickOn(toggle_bubble_button());
auto* bubble_view = video_conference_tray()->GetBubbleView();
ASSERT_TRUE(bubble_view);
EXPECT_EQ(video_conference::BubbleViewID::kMainBubbleView,
bubble_view->GetID());
LeftClickOn(toggle_bubble_button());
CreateMediaApps(1, true,
crosapi::mojom::VideoConferenceAppType::kBorealis);
LeftClickOn(toggle_bubble_button());
bubble_view = video_conference_tray()->GetBubbleView();
ASSERT_TRUE(bubble_view);
EXPECT_EQ(video_conference::BubbleViewID::kLinuxAppBubbleView,
bubble_view->GetID());
LeftClickOn(toggle_bubble_button());
CreateMediaApps(1, true,
crosapi::mojom::VideoConferenceAppType::kBorealis);
CreateMediaApps(1, false);
LeftClickOn(toggle_bubble_button());
bubble_view = video_conference_tray()->GetBubbleView();
ASSERT_TRUE(bubble_view);
EXPECT_EQ(video_conference::BubbleViewID::kMainBubbleView,
bubble_view->GetID());
}
TEST_F(VideoConferenceTrayTest, AccessibleNames) {
SetTrayAndButtonsVisible();
ASSERT_TRUE(video_conference_tray());
{
ui::AXNodeData node_data;
video_conference_tray()->GetViewAccessibility().GetAccessibleNodeData(
&node_data);
EXPECT_EQ(
node_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_ACCESSIBLE_NAME));
}
LeftClickOn(toggle_bubble_button());
auto* bubble_view = video_conference_tray()->GetBubbleView();
ASSERT_TRUE(bubble_view);
{
ui::AXNodeData node_data;
bubble_view->GetViewAccessibility().GetAccessibleNodeData(&node_data);
EXPECT_EQ(node_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
video_conference_tray()->GetAccessibleNameForBubble());
}
}
class VideoConferenceTrayDelayTest : public VideoConferenceTrayTest {
public:
VideoConferenceTrayDelayTest() = default;
VideoConferenceTrayDelayTest(const VideoConferenceTrayDelayTest&) = delete;
VideoConferenceTrayDelayTest& operator=(const VideoConferenceTrayDelayTest&) =
delete;
~VideoConferenceTrayDelayTest() override = default;
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(
features::kFeatureManagementVideoConference);
delay_controller_ = std::make_unique<DelayVideoConferenceTrayController>();
AshTestBase::SetUp();
}
DelayVideoConferenceTrayController* delay_controller() {
return delay_controller_.get();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<DelayVideoConferenceTrayController> delay_controller_;
};
TEST_F(VideoConferenceTrayDelayTest, OpenBubble) {
VideoConferenceMediaState state;
state.has_media_app = true;
state.has_camera_permission = true;
state.has_microphone_permission = true;
state.is_capturing_screen = true;
delay_controller()->UpdateWithMediaState(state);
LeftClickOn(toggle_bubble_button());
ASSERT_FALSE(video_conference_tray()->GetBubbleView());
task_environment()->FastForwardBy(kGetMediaAppsDelayTime);
EXPECT_TRUE(video_conference_tray()->GetBubbleView());
EXPECT_EQ(1, delay_controller()->getting_media_apps_called());
LeftClickOn(toggle_bubble_button());
ASSERT_FALSE(video_conference_tray()->GetBubbleView());
LeftClickOn(toggle_bubble_button());
LeftClickOn(toggle_bubble_button());
LeftClickOn(toggle_bubble_button());
EXPECT_EQ(2, delay_controller()->getting_media_apps_called());
task_environment()->FastForwardBy(kGetMediaAppsDelayTime);
EXPECT_TRUE(video_conference_tray()->GetBubbleView());
}
}