chromium/chrome/browser/ash/video_conference/video_conference_integration_browsertest.cc

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/capture_mode/capture_mode_test_util.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "ash/public/cpp/capture_mode/capture_mode_test_api.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/audio/audio_effects_controller.h"
#include "ash/system/camera/camera_effects_controller.h"
#include "ash/system/status_area_widget_test_helper.h"
#include "ash/system/toast/anchored_nudge_manager_impl.h"
#include "ash/system/video_conference/bubble/bubble_view_ids.h"
#include "ash/system/video_conference/bubble/return_to_app_panel.h"
#include "ash/system/video_conference/video_conference_tray.h"
#include "ash/system/video_conference/video_conference_tray_controller.h"
#include "ash/webui/common/mojom/sea_pen.mojom.h"
#include "ash/webui/vc_background_ui/url_constants.h"
#include "base/command_line.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/gtest_tags.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_timeouts.h"
#include "chrome/browser/ash/system_web_apps/system_web_app_manager.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chromeos/ash/components/audio/cras_audio_handler.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_names.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/visibility.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/events/base_event_utils.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/views/test/button_test_api.h"

namespace ash::video_conference {

namespace {

// Helper to generate a fake image.
std::string CreateJpgBytes() {
  SkBitmap bitmap;
  bitmap.allocN32Pixels(1, 1);
  std::vector<unsigned char> data;
  gfx::JPEGCodec::Encode(bitmap, /*quality=*/100, &data);
  return std::string(data.begin(), data.end());
}

bool IsNudgeShown(const std::string& id) {
  return Shell::Get()->anchored_nudge_manager()->IsNudgeShown(id);
}

const std::u16string& 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);
}

}  // namespace

constexpr char kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId[] =
    "video_conference_tray_nudge_ids.microphone_use_while_sw_disabled";
constexpr char kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId[] =
    "video_conference_tray_nudge_ids.camera_use_while_sw_disabled";
const char16_t kTitle1[] = u"Title1";
const char16_t kTitle2[] = u"Title2";

// Periodically checks the condition and move on with the rest of the code when
// the condition becomes true. Please tell me this is something you have been
// wanting for very long time.
#define WAIT_FOR_CONDITION(condition)                                 \
  {                                                                   \
    base::RunLoop run_loop;                                           \
    CheckForConditionAndWaitMoreIfNeeded(                             \
        base::BindRepeating(                                          \
            base::BindLambdaForTesting([&]() { return condition; })), \
        run_loop.QuitClosure());                                      \
    run_loop.Run();                                                   \
  }

// Periodically calls `condition` until it becomes true then calls
// `quit_closure`.
void CheckForConditionAndWaitMoreIfNeeded(
    base::RepeatingCallback<bool()> condition,
    base::OnceClosure quit_closure) {
  if (condition.Run()) {
    std::move(quit_closure).Run();
    return;
  }
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&CheckForConditionAndWaitMoreIfNeeded,
                     std::move(condition), std::move(quit_closure)),
      TestTimeouts::tiny_timeout());
}

// Helper for getting the VcTray.
ash::VideoConferenceTray* GetVcTray() {
  return ash::StatusAreaWidgetTestHelper::GetStatusAreaWidget()
      ->video_conference_tray();
}

// Simulates left click on the `button`.
void ClickButton(views::Button* button) {
  ui::MouseEvent event(ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
                       ui::EventTimeForNow(), 0, 0);
  views::test::ButtonTestApi(button).NotifyClick(event);
}

// Parameter stands for whether in incognito mode; and whether in guest mode.
class VideoConferenceIntegrationTest
    : public testing::WithParamInterface<std::tuple<bool, bool>>,
      public WebRtcTestBase {
 public:
  VideoConferenceIntegrationTest() {
    // kOnDeviceSpeechRecognition is to support live caption.
    scoped_feature_list_.InitWithFeatures(
        {ash::features::kVcStopAllScreenShare,
         ash::features::kOnDeviceSpeechRecognition,
         ash::features::kFeatureManagementVideoConference,
         ash::features::kVcBackgroundReplace,
         ash::features::kShowLiveCaptionInVideoConferenceTray},
        {});
  }

  ~VideoConferenceIntegrationTest() override = default;

  void SetUpOnMainThread() override {
    is_incognito_mode_ = std::get<0>(GetParam());
    is_guest_mode_ = std::get<1>(GetParam());

    WebRtcTestBase::SetUpOnMainThread();

    ASSERT_TRUE(embedded_test_server()->Start());

    // Create an incognito browser when parameter is true.
    if (is_incognito_mode_) {
      browser_ = Browser::Create(Browser::CreateParams(
          browser()->profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true),
          true));
      // This creates a blank page which is more consistent with normal mode.
      ui_test_utils::NavigateToURLWithDispositionBlockUntilNavigationsComplete(
          browser_, GURL("chrome://blank"), 1,
          WindowOpenDisposition::NEW_FOREGROUND_TAB,
          ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB |
              ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
    } else {
      browser_ = browser();
    }

    // Enable test mode to mock the SetCameraEffects calls.
    camera_effects_controller()->bypass_set_camera_effects_for_testing(true);

    camera_background_img_dir_ = browser()->profile()->GetPath().AppendASCII(
        "camera_background_img_dir");
    camera_background_run_dir_ = browser()->profile()->GetPath().AppendASCII(
        "camera_background_run_dir");
    camera_effects_controller()->set_camera_background_img_dir_for_testing(
        camera_background_img_dir_);
    camera_effects_controller()->set_camera_background_run_dir_for_testing(
        camera_background_run_dir_);

    // Required for the VcBackgroundApp.
    ash::SystemWebAppManager::Get(browser()->profile())
        ->InstallSystemAppsForTesting();
  }

  // Navigate to the url in a new tab.
  content::WebContents* NavigateTo(const std::string& url_str) {
    const GURL url(embedded_test_server()->GetURL(url_str));
    content::RenderFrameHost* main_rfh = ui_test_utils::
        NavigateToURLWithDispositionBlockUntilNavigationsComplete(
            browser_, url, 1, WindowOpenDisposition::NEW_FOREGROUND_TAB,
            ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB |
                ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
    content::WebContents* web_contents =
        content::WebContents::FromRenderFrameHost(main_rfh);
    return web_contents;
  }

  // Allows or disallow permissions for `web_contents`.
  void SetPermission(content::WebContents* web_contents,
                     ContentSettingsType type,
                     ContentSetting result) {
    HostContentSettingsMapFactory::GetForProfile(browser_->profile())
        ->SetContentSettingDefaultScope(web_contents->GetURL(), GURL(), type,
                                        result);
  }

  void StartCamera(content::WebContents* web_contents) {
    // Foreground is required for multiple tabs cases.
    web_contents->GetDelegate()->ActivateContents(web_contents);
    EXPECT_TRUE(content::ExecJs(web_contents, "startVideo();"));
  }

  void StopCamera(content::WebContents* web_contents) {
    // Foreground is required for multiple tabs cases.
    web_contents->GetDelegate()->ActivateContents(web_contents);
    EXPECT_TRUE(content::ExecJs(web_contents, "stopVideo();"));
  }

  void StartMicrophone(content::WebContents* web_contents) {
    // Foreground is required for multiple tabs cases.
    web_contents->GetDelegate()->ActivateContents(web_contents);
    EXPECT_TRUE(content::ExecJs(web_contents, "startAudio();"));
  }
  void StopMicrophone(content::WebContents* web_contents) {
    // Foreground is required for multiple tabs cases.
    web_contents->GetDelegate()->ActivateContents(web_contents);
    EXPECT_TRUE(content::ExecJs(web_contents, "stopAudio();"));
  }

  void StartScreenSharing(content::WebContents* web_contents) {
    // Foreground is required for multiple tabs cases.
    web_contents->GetDelegate()->ActivateContents(web_contents);
    EXPECT_TRUE(content::ExecJs(web_contents, "startScreenSharing();"));
  }
  void StopScreenSharing(content::WebContents* web_contents) {
    // Foreground is required for multiple tabs cases.
    web_contents->GetDelegate()->ActivateContents(web_contents);
    EXPECT_TRUE(content::ExecJs(web_contents, "stopScreenSharing();"));
  }

  // Changes the title of the `web_contents` to be `title`.
  void SetTitle(content::WebContents* web_contents,
                const std::u16string& title) {
    web_contents->GetController().GetLastCommittedEntry()->SetTitle(title);
  }

  // Generates a background image with given id as name, and apply that as the
  // camera background.
  base::FilePath CreateAndApplyBackgroundImage(uint32_t id) {
    base::RunLoop run_loop;
    camera_effects_controller()->SetBackgroundImageFromContent(
        SeaPenImage(CreateJpgBytes(), id), "",
        base::BindLambdaForTesting([&](bool call_succeeded) {
          EXPECT_TRUE(call_succeeded);
          run_loop.Quit();
        }));
    run_loop.Run();

    return CameraEffectsController::SeaPenIdToRelativePath(id);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    // Flags use to automatically select the right desktop source and get
    // around security restrictions.
    // TODO(crbug.com/40274188): Use a less error-prone flag.
#if BUILDFLAG(IS_CHROMEOS_ASH)
    command_line->AppendSwitchASCII(::switches::kAutoSelectDesktopCaptureSource,
                                    "Display");
#else
    command_line->AppendSwitchASCII(::switches::kAutoSelectDesktopCaptureSource,
                                    "Entire screen");
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

    // If in guest mode.
    if (is_guest_mode_) {
      command_line->AppendSwitch(ash::switches::kGuestSession);
      command_line->AppendSwitchASCII(ash::switches::kLoginUser,
                                      user_manager::kGuestUserName);
      command_line->AppendSwitchASCII(ash::switches::kLoginProfile,
                                      TestingProfile::kTestUserProfileDir);
      command_line->AppendSwitch(::switches::kIncognito);
    }
  }

  // Returns all `ReturnToAppButton`s into a vector for easier check.
  std::vector<ReturnToAppButton*> GetReturnToAppButtons() {
    ReturnToAppPanel* return_to_app_panel = static_cast<ReturnToAppPanel*>(
        GetVcTray()->GetBubbleView()->GetViewByID(BubbleViewID::kReturnToApp));

    std::vector<ReturnToAppButton*> output;
    for (views::View* button :
         return_to_app_panel->container_view_->children()) {
      output.push_back(static_cast<ReturnToAppButton*>(button));
    }
    return output;
  }

  // Returns the ReturnToAppButton with the given `title`.
  ReturnToAppButton* FindReturnToAppButtonByTitle(const std::u16string& title) {
    for (auto* bt : GetReturnToAppButtons()) {
      if (bt->label()->GetText() == title) {
        return bt;
      }
    }

    return nullptr;
  }

  // Helper function that triggers the VcTray with
  //  (1) tab video_conference_demo.html
  //  (2) both camera and microphone permissions.
  //  (3) capturing state based the parameters.
  // This function is to simplify test cases.
  content::WebContents* TriggeringTray(bool use_camera,
                                       bool use_microphone,
                                       bool use_screen_sharing) {
    // Open a tab.
    content::WebContents* web_contents =
        NavigateTo("/video_conference_demo.html");
    // Set permissions as allow.
    SetPermission(web_contents, ContentSettingsType::MEDIASTREAM_CAMERA,
                  CONTENT_SETTING_ALLOW);
    SetPermission(web_contents, ContentSettingsType::MEDIASTREAM_MIC,
                  CONTENT_SETTING_ALLOW);

    // Change title.
    SetTitle(web_contents, kTitle1);

    if (use_camera) {
      StartCamera(web_contents);
      WAIT_FOR_CONDITION(camera_bt()->is_capturing());
    }

    if (use_microphone) {
      StartMicrophone(web_contents);
      WAIT_FOR_CONDITION(mic_bt()->is_capturing());
    }

    if (use_screen_sharing) {
      StartScreenSharing(web_contents);
      WAIT_FOR_CONDITION(share_bt()->is_capturing());
    }

    return web_contents;
  }

  VideoConferenceTrayButton* camera_bt() { return GetVcTray()->camera_icon(); }
  VideoConferenceTrayButton* mic_bt() { return GetVcTray()->audio_icon(); }
  VideoConferenceTrayButton* share_bt() {
    return GetVcTray()->screen_share_icon();
  }

  CameraEffectsController* camera_effects_controller() {
    return Shell::Get()->camera_effects_controller();
  }

 protected:
  raw_ptr<Browser, DanglingUntriaged> browser_ = nullptr;

  base::FilePath camera_background_img_dir_;
  base::FilePath camera_background_run_dir_;

  bool is_guest_mode_ = false;
  bool is_incognito_mode_ = false;

  base::test::ScopedFeatureList scoped_feature_list_;
};

INSTANTIATE_TEST_SUITE_P(
    ,  // Empty to simplify gtest output
    VideoConferenceIntegrationTest,
    ::testing::Values(std::make_tuple<bool, bool>(false, false),
                      std::make_tuple<bool, bool>(true, false),
                      std::make_tuple<bool, bool>(false, true)));

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       CaptureVideoShowsVcTray) {
  // Trigger the VcTray with camera accessing.
  content::WebContents* web_contents =
      TriggeringTray(/*use_camera=*/true,
                     /*use_microphone=*/false,
                     /*use_screen_sharing=*/false);

  // camera_icon should be visible with green_dot.
  EXPECT_TRUE(camera_bt()->GetVisible());
  EXPECT_TRUE(camera_bt()->is_capturing());
  EXPECT_TRUE(camera_bt()->show_privacy_indicator());

  // audio_icon should be visible without green_dot.
  EXPECT_TRUE(mic_bt()->GetVisible());
  EXPECT_FALSE(mic_bt()->is_capturing());
  EXPECT_FALSE(mic_bt()->show_privacy_indicator());

  // screen_share_icon should be invisible.
  EXPECT_FALSE(share_bt()->GetVisible());
  EXPECT_FALSE(share_bt()->is_capturing());
  EXPECT_FALSE(share_bt()->show_privacy_indicator());

  // Stop camera and wait for is_capturing to populate.
  StopCamera(web_contents);
  WAIT_FOR_CONDITION(!camera_bt()->is_capturing());

  // camera_icon should be visible without green_dot.
  EXPECT_TRUE(camera_bt()->GetVisible());
  EXPECT_FALSE(camera_bt()->is_capturing());
  EXPECT_FALSE(camera_bt()->show_privacy_indicator());

  // Close tab and wait for the tray to dispear.
  web_contents->Close();
  WAIT_FOR_CONDITION(!GetVcTray()->GetVisible());
  // camera_icon should be invisible.
  EXPECT_FALSE(camera_bt()->GetVisible());
  EXPECT_FALSE(camera_bt()->is_capturing());
  EXPECT_FALSE(camera_bt()->show_privacy_indicator());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       CaptureAudioShowsVcTray) {
  // Trigger the VcTray with microphone accessing.
  content::WebContents* web_contents =
      TriggeringTray(/*use_camera=*/false,
                     /*use_microphone=*/true,
                     /*use_screen_sharing=*/false);

  // camera_icon should be visible without green_dot.
  EXPECT_TRUE(camera_bt()->GetVisible());
  EXPECT_FALSE(camera_bt()->is_capturing());
  EXPECT_FALSE(camera_bt()->show_privacy_indicator());

  // audio_icon should be visible with green_dot.
  EXPECT_TRUE(mic_bt()->GetVisible());
  EXPECT_TRUE(mic_bt()->is_capturing());
  EXPECT_TRUE(mic_bt()->show_privacy_indicator());

  // screen_share_icon should be invisible.
  EXPECT_FALSE(share_bt()->GetVisible());
  EXPECT_FALSE(share_bt()->is_capturing());
  EXPECT_FALSE(share_bt()->show_privacy_indicator());

  // Stop microphone and wait for is_capturing to populate.
  StopMicrophone(web_contents);
  WAIT_FOR_CONDITION(!mic_bt()->is_capturing());

  // audio_icon should be visible without green_dot.
  EXPECT_TRUE(mic_bt()->GetVisible());
  EXPECT_FALSE(mic_bt()->is_capturing());
  EXPECT_FALSE(mic_bt()->show_privacy_indicator());

  // Close tab and wait for the tray to dispear.
  web_contents->Close();
  WAIT_FOR_CONDITION(!GetVcTray()->GetVisible());
  // audio_icon should be invisible.
  EXPECT_FALSE(mic_bt()->GetVisible());
  EXPECT_FALSE(mic_bt()->is_capturing());
  EXPECT_FALSE(mic_bt()->show_privacy_indicator());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       ScreenSharingShowsVcTray) {
  // Trigger the VcTray with screen sharing.
  content::WebContents* web_contents =
      TriggeringTray(/*use_camera=*/false,
                     /*use_microphone=*/false,
                     /*use_screen_sharing=*/true);

  // camera_icon should be invisible.
  EXPECT_TRUE(camera_bt()->GetVisible());
  EXPECT_FALSE(camera_bt()->is_capturing());
  EXPECT_FALSE(camera_bt()->show_privacy_indicator());

  // audio_icon should be invisible with green_dot.
  EXPECT_TRUE(mic_bt()->GetVisible());
  EXPECT_FALSE(mic_bt()->is_capturing());
  EXPECT_FALSE(mic_bt()->show_privacy_indicator());

  // screen_share_icon should be visible.
  EXPECT_TRUE(share_bt()->GetVisible());
  EXPECT_TRUE(share_bt()->is_capturing());
  EXPECT_TRUE(share_bt()->show_privacy_indicator());

  // Stop microphone and wait for is_capturing to populate.
  StopScreenSharing(web_contents);
  WAIT_FOR_CONDITION(!share_bt()->is_capturing());

  EXPECT_FALSE(share_bt()->GetVisible());
  EXPECT_FALSE(share_bt()->is_capturing());
  EXPECT_FALSE(share_bt()->show_privacy_indicator());

  // VcTray should be invisible.
  EXPECT_TRUE(GetVcTray()->GetVisible());

  web_contents->Close();
  WAIT_FOR_CONDITION(!GetVcTray()->GetVisible());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       MicWithoutPermissionShouldNotShow) {
  base::AddFeatureIdTagToTestResult(
      "screenplay-92ebd14e-9017-4734-ae47-e9dc6afc6e87");

  // Open a tab.
  content::WebContents* web_contents =
      NavigateTo("/video_conference_demo.html");

  // Set permissions.
  SetPermission(web_contents, ContentSettingsType::MEDIASTREAM_CAMERA,
                CONTENT_SETTING_ALLOW);
  SetPermission(web_contents, ContentSettingsType::MEDIASTREAM_MIC,
                CONTENT_SETTING_BLOCK);

  // Start camera and wait for the tray to show.
  StartCamera(web_contents);
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());

  // Audio icon should not show because the permission is blocked.
  EXPECT_TRUE(camera_bt()->GetVisible());
  EXPECT_FALSE(mic_bt()->GetVisible());
  EXPECT_FALSE(share_bt()->GetVisible());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       CameraWithoutPermissionShouldNotShow) {
  base::AddFeatureIdTagToTestResult(
      "screenplay-2d9bddf4-8d96-4304-a605-1140f9a0b45e");

  // Open a tab.
  content::WebContents* web_contents =
      NavigateTo("/video_conference_demo.html");

  // Set permissions.
  SetPermission(web_contents, ContentSettingsType::MEDIASTREAM_CAMERA,
                CONTENT_SETTING_BLOCK);
  SetPermission(web_contents, ContentSettingsType::MEDIASTREAM_MIC,
                CONTENT_SETTING_ALLOW);

  // Start microphone and wait for the tray to show.
  StartMicrophone(web_contents);
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());

  // Camera icon should not show because the permission is blocked.
  EXPECT_FALSE(camera_bt()->GetVisible());
  EXPECT_TRUE(mic_bt()->GetVisible());
  EXPECT_FALSE(share_bt()->GetVisible());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       CameraMicWithoutPermissionShouldNotShow) {
  // Open a tab.
  content::WebContents* web_contents =
      NavigateTo("/video_conference_demo.html");

  // Set permissions.
  SetPermission(web_contents, ContentSettingsType::MEDIASTREAM_CAMERA,
                CONTENT_SETTING_BLOCK);
  SetPermission(web_contents, ContentSettingsType::MEDIASTREAM_MIC,
                CONTENT_SETTING_BLOCK);

  // Start screen sharing and wait for the tray to show.
  StartScreenSharing(web_contents);
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());

  // Both microphone and camera should not show.
  EXPECT_FALSE(camera_bt()->GetVisible());
  EXPECT_FALSE(mic_bt()->GetVisible());
  EXPECT_TRUE(share_bt()->GetVisible());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       ClickOnTheMicOrCameraIconsShouldMute) {
  base::AddFeatureIdTagToTestResult(
      "screenplay-cc18f9a3-e46f-4192-b505-876975c5ef4b");

  // Trigger the VcTray with microphone.
  TriggeringTray(/*use_camera=*/false,
                 /*use_microphone=*/true,
                 /*use_screen_sharing=*/false);

  // Clicking on the mic icon should mute it.
  ClickButton(mic_bt());
  WAIT_FOR_CONDITION(mic_bt()->toggled());
  EXPECT_FALSE(mic_bt()->show_privacy_indicator());

  // Clicking on the mic icon again should unmute it.
  ClickButton(mic_bt());
  WAIT_FOR_CONDITION(!mic_bt()->toggled());
  EXPECT_TRUE(mic_bt()->show_privacy_indicator());

  // Clicking on the camera icon should mute it.
  ClickButton(camera_bt());
  WAIT_FOR_CONDITION(camera_bt()->toggled());
  EXPECT_FALSE(camera_bt()->show_privacy_indicator());

  // Clicking on the camera icon again should unmute it.
  ClickButton(camera_bt());
  WAIT_FOR_CONDITION(!camera_bt()->toggled());
  EXPECT_FALSE(camera_bt()->show_privacy_indicator());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       OneTabReturnToAppInformation) {
  // Trigger the VcTray with microphone.
  content::WebContents* web_contents =
      TriggeringTray(/*use_camera=*/false,
                     /*use_microphone=*/true,
                     /*use_screen_sharing=*/false);

  // Get the ReturnToApp Panel.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());

  // Chceck the button for title and capturing information.
  // Only is_capturing_microphone should be true.
  auto buttons = GetReturnToAppButtons();
  EXPECT_EQ(buttons.size(), 1u);
  EXPECT_FALSE(buttons[0]->is_capturing_camera());
  EXPECT_TRUE(buttons[0]->is_capturing_microphone());
  EXPECT_FALSE(buttons[0]->is_capturing_screen());
  EXPECT_EQ(buttons[0]->label()->GetText(), kTitle1);

  // We want to close the panel and open it every time.
  GetVcTray()->CloseBubble();

  // Start accessing camera.
  StartCamera(web_contents);
  WAIT_FOR_CONDITION(camera_bt()->show_privacy_indicator());

  // Get the ReturnToApp Panel.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());

  // Chceck the button for title and capturing information.
  // is_capturing_camera and is_capturing_microphone should be true.
  buttons = GetReturnToAppButtons();
  EXPECT_EQ(buttons.size(), 1u);
  EXPECT_TRUE(buttons[0]->is_capturing_camera());
  EXPECT_TRUE(buttons[0]->is_capturing_microphone());
  EXPECT_FALSE(buttons[0]->is_capturing_screen());
  EXPECT_EQ(buttons[0]->label()->GetText(), kTitle1);

  // We want to close the panel and open it every time.
  GetVcTray()->CloseBubble();

  // Start screen sharing.
  StartScreenSharing(web_contents);
  WAIT_FOR_CONDITION(share_bt()->show_privacy_indicator());

  // Get the ReturnToApp Panel.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());

  // Chceck the button for title and capturing information.
  // All capturing should be true.
  buttons = GetReturnToAppButtons();
  EXPECT_EQ(buttons.size(), 1u);
  EXPECT_TRUE(buttons[0]->is_capturing_camera());
  EXPECT_TRUE(buttons[0]->is_capturing_microphone());
  EXPECT_TRUE(buttons[0]->is_capturing_screen());
  EXPECT_EQ(buttons[0]->label()->GetText(), kTitle1);
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest, OneTabReturnToApp) {
  // Trigger the VcTray with microphone.
  content::WebContents* web_contents =
      TriggeringTray(/*use_camera=*/false,
                     /*use_microphone=*/true,
                     /*use_screen_sharing=*/false);

  // Switch to the default tab at 0; this should make the `web_contents` hidden.
  browser_->tab_strip_model()->ActivateTabAt(0);
  WAIT_FOR_CONDITION(web_contents->GetVisibility() ==
                     content::Visibility::HIDDEN);

  // Get the ReturnToApp Panel.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());

  // Click on the ReturnToApp button should make the `web_contents` visible
  // again.
  ClickButton(GetReturnToAppButtons()[0]);
  WAIT_FOR_CONDITION(web_contents->GetVisibility() ==
                     content::Visibility::VISIBLE);

  // We want to close the panel and open it every time.
  GetVcTray()->CloseBubble();

  // Minimize the browser window; this should make the `web_contents` hidden.
  browser_->window()->Minimize();
  WAIT_FOR_CONDITION(web_contents->GetVisibility() ==
                     content::Visibility::HIDDEN);

  // Get the ReturnToApp Panel.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());

  // Click on the ReturnToApp button should make the `web_contents` visible
  // again.
  ClickButton(GetReturnToAppButtons()[0]);
  WAIT_FOR_CONDITION(web_contents->GetVisibility() ==
                     content::Visibility::VISIBLE);
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest, UseWhileDisabled) {
  base::AddFeatureIdTagToTestResult(
      "screenplay-f583c1ff-db6f-460e-b1f2-ddec173359a6");
  base::AddFeatureIdTagToTestResult(
      "screenplay-3042cdd9-978d-432c-8488-77684b09a9e4");

  // Prevent "Speak-on-mute opt-in" nudge from showing so it doesn't cancel
  // other shown nudges.
  Shell::Get()->session_controller()->GetActivePrefService()->SetBoolean(
      prefs::kShouldShowSpeakOnMuteOptInNudge, false);

  // Trigger the VcTray with microphone.
  content::WebContents* web_contents =
      TriggeringTray(/*use_camera=*/false,
                     /*use_microphone=*/true,
                     /*use_screen_sharing=*/false);

  auto* microphone_nudge_id =
      kVideoConferenceTrayMicrophoneUseWhileSWDisabledNudgeId;

  // Stop microphone and wait for is_capturing to populate.
  StopMicrophone(web_contents);
  WAIT_FOR_CONDITION(!mic_bt()->is_capturing());

  // Clicking on the mic icon should mute it.
  ClickButton(mic_bt());
  WAIT_FOR_CONDITION(mic_bt()->toggled());

  // Start accessing microphone should trigger UseWhileDisabled.
  StartMicrophone(web_contents);
  WAIT_FOR_CONDITION(IsNudgeShown(microphone_nudge_id));

  // Check the nudge message and anchor view is as expected.
  EXPECT_EQ(
      GetNudgeText(microphone_nudge_id),
      l10n_util::GetStringFUTF16(
          IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED, kTitle1,
          l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_MICROPHONE_NAME)));
  EXPECT_EQ(GetNudgeAnchorView(microphone_nudge_id), GetVcTray()->audio_icon());

  // Remove current nudge for the next step.
  Shell::Get()->anchored_nudge_manager()->Cancel(microphone_nudge_id);
  WAIT_FOR_CONDITION(!IsNudgeShown(microphone_nudge_id));

  auto* camera_nudge_id = kVideoConferenceTrayCameraUseWhileSWDisabledNudgeId;

  // Clicking on the camera icon should mute it.
  ClickButton(camera_bt());
  WAIT_FOR_CONDITION(camera_bt()->toggled());

  // Start accessing camera should trigger UseWhileDisabled.
  StartCamera(web_contents);
  WAIT_FOR_CONDITION(IsNudgeShown(camera_nudge_id));

  // Check the nudge message and anchor view is as expected.
  EXPECT_EQ(
      GetNudgeText(camera_nudge_id),
      l10n_util::GetStringFUTF16(
          IDS_ASH_VIDEO_CONFERENCE_TOAST_USE_WHILE_DISABLED, kTitle1,
          l10n_util::GetStringUTF16(IDS_ASH_VIDEO_CONFERENCE_CAMERA_NAME)));
  EXPECT_EQ(GetNudgeAnchorView(camera_nudge_id), GetVcTray()->camera_icon());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       TwoTabsButtonInformation) {
  // Open a tab.
  content::WebContents* web_contents_1 =
      NavigateTo("/video_conference_demo.html");
  // Set permissions as allow.
  SetPermission(web_contents_1, ContentSettingsType::MEDIASTREAM_CAMERA,
                CONTENT_SETTING_ALLOW);
  SetPermission(web_contents_1, ContentSettingsType::MEDIASTREAM_MIC,
                CONTENT_SETTING_ALLOW);
  // Set title.
  SetTitle(web_contents_1, kTitle1);

  // Open second tab.
  content::WebContents* web_contents_2 =
      NavigateTo("/video_conference_demo.html");
  // Set title.
  SetTitle(web_contents_2, kTitle2);

  StartMicrophone(web_contents_1);
  StartMicrophone(web_contents_2);
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());

  // Get the ReturnToApp Panel.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());

  // There should be three buttons, the first one is the summary button.
  auto buttons = GetReturnToAppButtons();
  EXPECT_EQ(buttons.size(), 3u);

  // Button[0] is the summary button.
  EXPECT_FALSE(buttons[0]->is_capturing_camera());
  EXPECT_TRUE(buttons[0]->is_capturing_microphone());
  EXPECT_FALSE(buttons[0]->is_capturing_screen());
  EXPECT_EQ(buttons[0]->label()->GetText(), u"Used by 2 apps");

  // Check information of the button for web_contents_1.
  const auto* bt_1 = FindReturnToAppButtonByTitle(kTitle1);
  EXPECT_FALSE(bt_1->is_capturing_camera());
  EXPECT_TRUE(bt_1->is_capturing_microphone());
  EXPECT_FALSE(bt_1->is_capturing_screen());

  // Check information of the button for web_contents_2.
  const auto* bt_2 = FindReturnToAppButtonByTitle(kTitle2);
  EXPECT_FALSE(bt_2->is_capturing_camera());
  EXPECT_TRUE(bt_2->is_capturing_microphone());
  EXPECT_FALSE(bt_2->is_capturing_screen());

  // We want to close the panel and open it every time.
  GetVcTray()->CloseBubble();
  StartCamera(web_contents_1);
  StartScreenSharing(web_contents_2);

  // Wait for signals to populate.
  WAIT_FOR_CONDITION(camera_bt()->show_privacy_indicator());
  WAIT_FOR_CONDITION(share_bt()->show_privacy_indicator());

  // Get the ReturnToApp Panel.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());

  // There should be three buttons, the first one is the summary button.
  buttons = GetReturnToAppButtons();
  EXPECT_EQ(buttons.size(), 3u);

  // Button[0] is the summary button.
  EXPECT_TRUE(buttons[0]->is_capturing_camera());
  EXPECT_TRUE(buttons[0]->is_capturing_microphone());
  EXPECT_TRUE(buttons[0]->is_capturing_screen());
  EXPECT_EQ(buttons[0]->label()->GetText(), u"Used by 2 apps");

  // Check information of the button for web_contents_1.
  bt_1 = FindReturnToAppButtonByTitle(kTitle1);
  EXPECT_TRUE(bt_1->is_capturing_camera());
  EXPECT_TRUE(bt_1->is_capturing_microphone());
  EXPECT_FALSE(bt_1->is_capturing_screen());

  // Check information of the button for web_contents_2.
  bt_2 = FindReturnToAppButtonByTitle(kTitle2);
  EXPECT_FALSE(bt_2->is_capturing_camera());
  EXPECT_TRUE(bt_2->is_capturing_microphone());
  EXPECT_TRUE(bt_2->is_capturing_screen());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest, TwoTabsReturnToApp) {
  // Open a tab.
  content::WebContents* web_contents_1 =
      NavigateTo("/video_conference_demo.html");
  // Set permissions as allow.
  SetPermission(web_contents_1, ContentSettingsType::MEDIASTREAM_CAMERA,
                CONTENT_SETTING_ALLOW);
  SetPermission(web_contents_1, ContentSettingsType::MEDIASTREAM_MIC,
                CONTENT_SETTING_ALLOW);
  // Set title.
  SetTitle(web_contents_1, kTitle1);

  // Open second tab.
  content::WebContents* web_contents_2 =
      NavigateTo("/video_conference_demo.html");
  // Set title.
  SetTitle(web_contents_2, kTitle2);

  StartMicrophone(web_contents_1);
  StartMicrophone(web_contents_2);
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());

  // Get the ReturnToApp Panel.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());

  auto buttons = GetReturnToAppButtons();
  EXPECT_EQ(buttons.size(), 3u);

  // Verify that web_contents_2 is foregrounded and web_contents_1 is
  // backgrounded.
  EXPECT_EQ(web_contents_2->GetVisibility(), content::Visibility::VISIBLE);
  EXPECT_NE(web_contents_1->GetVisibility(), content::Visibility::VISIBLE);

  // Click on web_contents_1 and expect the visibility to change.
  ClickButton(FindReturnToAppButtonByTitle(kTitle1));
  WAIT_FOR_CONDITION(web_contents_1->GetVisibility() ==
                     content::Visibility::VISIBLE);
  EXPECT_NE(web_contents_2->GetVisibility(), content::Visibility::VISIBLE);
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       ExpectedButtonsAreShown) {
  // In order to set noise cancellation support, we need to call
  //   CrasAudioHandler::Get()->SetNoiseCancellationSupportedForTesting(true)
  // But by the time it reaches here, the audio_effects_controller is already
  // constructed based on the fact that the Noise Cancellation is not supported.
  // The way to fix that is:
  //  (1) Unregister audio_effects_controller
  //  (2) SetNoiseCancellationSupportedForTesting(true)
  //  (3) Register audio_effects_controller again with
  //      OnActiveUserPrefServiceChanged.
  auto* audio_effects_controller = Shell::Get()->audio_effects_controller();
  VideoConferenceTrayController::Get()->GetEffectsManager().UnregisterDelegate(
      audio_effects_controller);
  CrasAudioHandler::Get()->SetNoiseCancellationSupportedForTesting(true);
  audio_effects_controller->OnActiveUserPrefServiceChanged(
      g_browser_process->local_state());

  // Trigger the VcTray with microphone.
  TriggeringTray(/*use_camera=*/false,
                 /*use_microphone=*/true,
                 /*use_screen_sharing=*/false);

  // Wait for VcPanel to appear.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());

  bool found_live_caption_button = false;
  bool found_noise_cancellation_buttion = false;

  auto* toggle_effects_view = GetVcTray()->GetBubbleView()->GetViewByID(
      BubbleViewID::kToggleEffectsView);

  for (views::View* row : toggle_effects_view->children()) {
    for (views::View* tile : row->children()) {
      // Each tile is a button to click on; we want its label.
      views::Label* label = static_cast<views::Label*>(
          tile->GetViewByID(BubbleViewID::kToggleEffectLabel));

      if (label->GetText() ==
          l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LIVE_CAPTION)) {
        found_live_caption_button = true;
      }

      if (label->GetText() ==
          l10n_util::GetStringUTF16(
              IDS_ASH_STATUS_TRAY_AUDIO_INPUT_NOISE_CANCELLATION)) {
        found_noise_cancellation_buttion = true;
      }
    }
  }

  EXPECT_TRUE(found_live_caption_button);
  EXPECT_TRUE(found_noise_cancellation_buttion);
}

// TODO(crbug.com/40071631): re-enable once the bug is fixed.
IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       DISABLED_StopAllScreenShare) {
  // Open a tab.
  content::WebContents* web_contents_1 =
      NavigateTo("/video_conference_demo.html");

  // Start the screen sharing.
  StartScreenSharing(web_contents_1);
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());

  // Get the ReturnToApp Panel.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());

  // Check that web_contents_1 is sharing screen.
  auto buttons = GetReturnToAppButtons();
  EXPECT_EQ(buttons.size(), 1u);
  EXPECT_FALSE(buttons[0]->is_capturing_camera());
  EXPECT_FALSE(buttons[0]->is_capturing_microphone());
  EXPECT_TRUE(buttons[0]->is_capturing_screen());

  // Hide the ReturnToApp Panel.
  ClickButton(GetVcTray()->toggle_bubble_button());

  // Click on the screen share button.
  EXPECT_TRUE(share_bt()->is_capturing());
  ClickButton(share_bt());
  WAIT_FOR_CONDITION(!share_bt()->is_capturing());

  // Check that web_contents_1 has stopped sharing screen.
  ClickButton(GetVcTray()->toggle_bubble_button());
  WAIT_FOR_CONDITION(GetVcTray()->GetBubbleView()->GetVisible());
  EXPECT_FALSE(GetReturnToAppButtons()[0]->is_capturing_screen());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       ImageButtonShouldActivateWebui) {
  if (is_guest_mode_) {
    GTEST_SKIP() << "Skip for guest mode.";
  }

  // Trigger the VcTray with camera accessing.
  TriggeringTray(/*use_camera=*/true,
                 /*use_microphone=*/false,
                 /*use_screen_sharing=*/false);
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());

  // Wait until the VcBubble is visible.
  ClickButton(GetVcTray()->toggle_bubble_button());
  auto* buble_view = GetVcTray()->GetBubbleView();
  WAIT_FOR_CONDITION(buble_view->GetVisible());

  views::View* background_image_button =
      buble_view->GetViewByID(BubbleViewID::kBackgroundBlurImageButton);

  // Expect the image button to be visible.
  EXPECT_TRUE(background_image_button->GetVisible());

  // Expect the SetCameraBackgroundView to be invisible.
  EXPECT_FALSE(buble_view->GetViewByID(BubbleViewID::kSetCameraBackgroundView)
                   ->GetVisible());

  content::WebContentsAddedObserver web_contents_added_observer;

  // Clicking on the Image button should open the VcBackgroundApp.
  ClickButton(views::AsViewClass<views::Button>(background_image_button));
  content::WebContents* new_contents =
      web_contents_added_observer.GetWebContents();

  EXPECT_EQ(GURL(vc_background_ui::kChromeUIVcBackgroundURL),
            new_contents->GetVisibleURL());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       CreateWithAiButtonShouldActivateWebui) {
  if (is_guest_mode_) {
    GTEST_SKIP() << "Skip for guest mode.";
  }

  CreateAndApplyBackgroundImage(123u);

  // Trigger the VcTray with camera accessing.
  TriggeringTray(/*use_camera=*/true,
                 /*use_microphone=*/false,
                 /*use_screen_sharing=*/false);
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());

  // Wait until the VcBubble is visible.
  ClickButton(GetVcTray()->toggle_bubble_button());
  auto* buble_view = GetVcTray()->GetBubbleView();
  WAIT_FOR_CONDITION(buble_view->GetVisible());

  // SetCameraBackgroundView should be visible.
  EXPECT_TRUE(buble_view->GetViewByID(BubbleViewID::kSetCameraBackgroundView)
                  ->GetVisible());

  // Create with AI button should be visible.
  views::View* create_with_ai_button =
      buble_view->GetViewByID(BubbleViewID::kCreateWithAiButton);
  EXPECT_TRUE(create_with_ai_button->GetVisible());

  content::WebContentsAddedObserver web_contents_added_observer;

  // Clicking on the Create with AI button should open VcBackgroundApp.
  ClickButton(views::AsViewClass<views::Button>(create_with_ai_button));
  content::WebContents* new_contents =
      web_contents_added_observer.GetWebContents();

  EXPECT_EQ(GURL(vc_background_ui::kChromeUIVcBackgroundURL),
            new_contents->GetVisibleURL());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       ClickingOnBackgroundBlurHidesSetCameraBackgroundView) {
  if (is_guest_mode_) {
    GTEST_SKIP() << "Skip for guest mode.";
  }

  CreateAndApplyBackgroundImage(123u);

  // Trigger the VcTray with camera accessing.
  TriggeringTray(/*use_camera=*/true,
                 /*use_microphone=*/false,
                 /*use_screen_sharing=*/false);
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());

  // Wait until the VcBubble is visible.
  ClickButton(GetVcTray()->toggle_bubble_button());
  auto* buble_view = GetVcTray()->GetBubbleView();
  WAIT_FOR_CONDITION(buble_view->GetVisible());

  // SetCameraBackgroundView should be visible.
  auto* set_camera_background_view =
      buble_view->GetViewByID(BubbleViewID::kSetCameraBackgroundView);
  EXPECT_TRUE(set_camera_background_view->GetVisible());

  auto* background_image_button = views::AsViewClass<views::Button>(
      buble_view->GetViewByID(BubbleViewID::kBackgroundBlurImageButton));

  // Constructing the image button is async, we need wait until that completes.
  WAIT_FOR_CONDITION(buble_view->GetViewByID(BubbleViewID::kBackgroundImage0) !=
                     nullptr);
  auto* first_background_image = views::AsViewClass<views::Button>(
      buble_view->GetViewByID(BubbleViewID::kBackgroundImage0));

  for (int button_id : {BubbleViewID::kBackgroundBlurOffButton,
                        BubbleViewID::kBackgroundBlurLightButton,
                        BubbleViewID::kBackgroundBlurFullButton}) {
    // Clicking on background blur button should turn off background replace.
    auto* button =
        views::AsViewClass<views::Button>(buble_view->GetViewByID(button_id));
    ClickButton(button);

    WAIT_FOR_CONDITION(!set_camera_background_view->GetVisible());
    EXPECT_FALSE(
        camera_effects_controller()->GetCameraEffects()->replace_enabled);

    // Clicking image button should set SetCameraBackgroundView visible.
    ClickButton(background_image_button);
    WAIT_FOR_CONDITION(set_camera_background_view->GetVisible());
    EXPECT_FALSE(
        camera_effects_controller()->GetCameraEffects()->replace_enabled);

    // Clicking on image button should apply that as background.
    // Setting background replace is async, so we can't directly check the
    // replace_enabled; but wait for that becomes true.
    ClickButton(first_background_image);
    WAIT_FOR_CONDITION(
        camera_effects_controller()->GetCameraEffects()->replace_enabled);
  }
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       ClickingOnBackgroundImageAppliesBackgroundReplace) {
  if (is_guest_mode_) {
    GTEST_SKIP() << "Skip for guest mode.";
  }

  const base::FilePath image1 = CreateAndApplyBackgroundImage(123u);
  const base::FilePath image2 = CreateAndApplyBackgroundImage(456u);

  // Trigger the VcTray with camera accessing.
  TriggeringTray(/*use_camera=*/true,
                 /*use_microphone=*/false,
                 /*use_screen_sharing=*/false);
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());

  // Wait until the VcBubble is visible.
  ClickButton(GetVcTray()->toggle_bubble_button());
  auto* buble_view = GetVcTray()->GetBubbleView();
  WAIT_FOR_CONDITION(buble_view->GetVisible());

  // Currently image2 should be set as background.
  EXPECT_EQ(
      camera_effects_controller()->GetCameraEffects()->background_filepath,
      image2);

  // Constructing the image button is async, we need wait until that completes.
  WAIT_FOR_CONDITION(buble_view->GetViewByID(BubbleViewID::kBackgroundImage0) !=
                     nullptr);
  WAIT_FOR_CONDITION(buble_view->GetViewByID(BubbleViewID::kBackgroundImage1) !=
                     nullptr);

  auto* first_background_image = views::AsViewClass<views::Button>(
      buble_view->GetViewByID(BubbleViewID::kBackgroundImage0));
  auto* second_background_image = views::AsViewClass<views::Button>(
      buble_view->GetViewByID(BubbleViewID::kBackgroundImage1));

  // Clicking on the second image will set image1 as background.
  ClickButton(second_background_image);
  // Setting background replace is async, so we can't directly check the
  // background_filepath; but wait for that becomes true.
  WAIT_FOR_CONDITION(
      camera_effects_controller()->GetCameraEffects()->background_filepath ==
      image1);

  // Clicking on the first image will set image2 as background.
  // Setting background replace is async, so we can't directly check the
  // background_filepath; but wait for that becomes true.
  ClickButton(first_background_image);
  WAIT_FOR_CONDITION(
      camera_effects_controller()->GetCameraEffects()->background_filepath ==
      image2);
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       TrayTriggeredByCaptureCamera) {
  ASSERT_EQ(1u, ash::WaitForCameraAvailabilityWithTimeout(base::Seconds(5)));
  ash::CaptureModeTestApi test_api;
  test_api.SelectCameraAtIndex(0);
  test_api.StartForFullscreen(/*for_video=*/true);
  ASSERT_TRUE(test_api.IsSessionActive());

  // VcTray should be triggered.
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());
}

IN_PROC_BROWSER_TEST_P(VideoConferenceIntegrationTest,
                       TrayTriggeredByCaptureMicrophone) {
  ASSERT_EQ(1u, ash::WaitForCameraAvailabilityWithTimeout(base::Seconds(5)));
  ash::CaptureModeTestApi test_api;
  test_api.SetAudioRecordingMode(AudioRecordingMode::kMicrophone);
  test_api.StartForFullscreen(/*for_video=*/true);
  ASSERT_TRUE(test_api.IsSessionActive());
  test_api.PerformCapture();
  test_api.FlushRecordingServiceForTesting();
  EXPECT_TRUE(test_api.IsVideoRecordingInProgress());

  // VcTray should be triggered.
  WAIT_FOR_CONDITION(GetVcTray()->GetVisible());
}

}  // namespace ash::video_conference